@perkyoung
2015-07-12T14:36:09.000000Z
字数 5118
阅读 3382
汇编语言
都是正数,所有的位都表示数字,例如0XFFFF,表示一个正数,但在gdb的时候会输出其补码,也就是负数。
最高为如果是1,表示负数,-16的补码表示方法:取16的二进制值,取反,加1
如果我们只用长度较小的无符号整数赋值给长度较大的。注意要将后者的高位清零。
movl $0, %ebx
movl %cl, %ebx
很麻烦,所以intel提供了一个指令,movzx %cl, %ebx
后者的高位自动清零
如果扩展的是有符号整数,则不能将长度较大的寄存器的高位清零,因为最高为表示符号。intel提供了
movw $-79, %cx
movl $0, %ebx //下面两行显然是错了,因为%ebx显然失去了负数的属性
movw %cx, %bx
movsx %cx, %eax //这个是ok的,据算cx是整数,也会正确
如果想显示8字节整数x/5gd
以后再看
这个需要看,包括溢出,进位等标志的操作
暂时略过
看完后面的函数之后在回来看,不着急
输入
定义函数处理
.type func1, @function
funcl:
<instruction....>
ret
定义输出
.section .data
precision:
.byte 0x7f, 0x00
.section .bss
.lcomm radius, 4
.lcomm result, 4
.lcomm trach, 4
.section .text
.globl main
main:
nop
finit
fldcw precision
movl $10, radius
call area
movl $2, radius
call area
movl $120, radius
call area
movl $1, %eax
movl $0, %ebx
int $0x80
.type area, @function
area:
fldpi
filds radius
fmul %st(0), %st(0)
fmulp %st(0), %st(1)
fstps result
ret
但是,寄存器,就那么几个,全局变量搞得太多头疼,函数调用却很多很多,显然不够用。如果真这么写的话,绝对每天晚上睡不着觉。
经典程序堆栈
虚拟内存地址空间,0X80480000~0XBFFFFFFF
想当然的,程序一启动,esp就指向了0XBFFFFFFF,其实不是,在加载程序之前,linux就把一些内容存放到堆栈中,命令行参数就是如此
#include <stdio.h>
int main(int argc, char * argv[]) {
int c = argc;
char * p = argv[1];
return 0;
}
反汇编的结果是
08048354 <main>:
8048354: 55 push %ebp
8048355: 89 e5 mov %esp,%ebp
8048357: 83 ec 10 sub $0x10,%esp
804835a: 8b 45 08 mov 0x8(%ebp),%eax //0x4(%ebp)是返回地址,这个就是第一个参数
804835d: 89 45 fc mov %eax,-0x4(%ebp)//赋值给函数内的临时变量
8048360: 8b 45 0c mov 0xc(%ebp),%eax //第二个参数是包含多个指针的数组
8048363: 83 c0 04 add $0x4,%eax //指向了该数组的第2个(如果下标为0的是第一个的话),
8048366: 8b 00 mov (%eax),%eax //取出该内存地址的值,该值是指向一个字符串的指针
8048368: 89 45 f8 mov %eax,-0x8(%ebp) //赋值给第二个临时变量
804836b: b8 00 00 00 00 mov $0x0,%eax
8048370: c9 leave
先跳过
#include <stdio.h>
int main(){
int i = 100;
char * output = "the result is %d";
asm("nop\n\t"
"movl $0, %eax\n\t"
"movl $2, %ebx\n\t"
"int $0x80");
}
编译出的汇编代码是
main:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl $100, -4(%ebp)
movl $.LC0, -8(%ebp)
#APP //从这里到后面的NO_APP
# 6 "./test.c" 1
nop
movl $0, %eax
movl $2, %ebx
int $0x80
# 0 "" 2
#NO_APP
pushl $100
pushl $.LC0
call printf
addl $8, %esp
leave
使用全局变量
#include <stdio.h>
int a = 10;
int b = 20;
int result;
int main() {
asm("pusha\n\t"
"movl a, %eax\n\t"
"movl b, %ebx\n\t"
"imull %ebx, %eax\n\t"
"movl %eax, result\n\t");
printf("the answer is %d\n", result);
return 0;
}
对应点汇编代码是
27 #APP
28 # 7 "./globaltest.c" 1
29 pusha //内部代码使用了这些寄存器,最好压栈
30 movl a, %eax //如果objdump -D ./a.out ,a表示的是内存地址,而不是一个立即数
31 movl b, %ebx
32 imull %ebx, %eax
33 movl %eax, result
34
35 # 0 "" 2
36 #NO_APP
使用volatile修饰符,避免编译器优化
asm volatile ("nop");
__asm__ __volatile__("");
到目前,所有的输入值和输出值都必须使用都必须使用C的全局变量,下面要解决这个问题
asm ("assembly code" : output location : input operands : chagned registers)
int main() {
int data1 = 10;
int data2 = 20;
int result;
__asm__("imull %%edx, %%ecx\n\t" //寄存器要多一个%
"movl %%ecx, %%eax\n\t"
: "=a"(result) //=表示只能写入result
: "d"(data1), "c"(data2));
printf("the result is %d\n", result);
return 0;
}
不一定总要在内联汇编中指定输出值
#include <stdio.h>
int main() {
char input[30] = "this is a test message.\n";
char output[30];
int length = 25;
asm volatile ("cld\n\t"
"rep movsb"
:
: "S"(input), "D"(output), "c"(length)); //output同时也是输出,movsb指令负责复制字符串,没有定义专门的输出值,所以,volatile很重要,编译器不会误以为它没必要,而删除它。
printf("%s", output);
return 0;
}
前面的例子中,我们将变量赋值给寄存器,但是如果变量特别多的话,这样编程会很麻烦,所以利用占位符。使用占位符引用输入输出。占位符就是前面加%的数字
,引用的是输入输出值在asm中出现的位置,从0开始
int main() {
int data1 = 10;
int data2 = 20;
int result;
__asm__ ("imull %1, %2\n\t"
"movl %2, %0"
: "=r"(result)
: "r"(data1), "r"(data2));
printf("the result is %d\n", result);
return 0;
}
有兴趣可以看下对应的汇编代码
#APP
# 7 "./reg.c" 1
imull %edx, %eax
movl %eax, %eax //其实已经算好了,结果我们的asm代码又一次赋值
# 0 "" 2
#NO_APP
从上面的例子中可以看到,有时候使用相同的变量作为输入和输出值是很不错的。
int main() {
int data1 = 10;
int data2 = 20;
int result;
__asm__ ("imull %1, %0\n\t"
: "=r"(data2) //data2输出为%0寄存器
: "r"(data1), "0"(data2)); //输入data2,指定到%0,
printf("the result is %d\n", data2);
return 0;
}
如果输入输出变量多,用0,1,2表示就会很混乱,可以用替换的占位符
int main() {
int data1 = 10;
int data2 = 20;
int result;
__asm__ ("imull %[value1], %[value2]\n\t"
: [value2]"=r"(data2)
: [value1]"r"(data1), "0"(data2));
printf("the result is %d\n", data2);
return 0;
}
还记得扩展asm格式吗?对,最后一个是改动的寄存器列表,下面谈谈
int main(){
int data1 = 10;
int result = 20;
asm ("addl %1, %0\n\t"
: "=d"(result)
: "c"(data1) ,"0"(result)
: "edx"); //这里告诉编译器,edx改动了,其实这个明显是废话,因为赋值的时候就用到了edx作为输出的。所以这里是编译不过去的
printf("result is %d\n", result);
return 0;
}
正确用法
int main(){
int data1 = 10;
int result = 20;
asm ("movl %1, %%eax\n\t"
"addl %%eax, %0"
: "=r"(result)
: "r"(data1) ,"0"(result)
: "eax"); //eax不是作为输入输出的,是在汇编代码中改动的,所以要写出来,一定要写出来。
printf("result is %d\n", result);
return 0;
}
不用寄存器,直接引用内存位置
int main() {
int dividend = 20;
int divisor = 5;
int result;
asm("divb %2\n\t"
"movl %%eax, %0"
: "=m"(result)
: "a"(dividend), "m"(divisor));
printf("result is %d\n", result);
return 0;
}
格式:#define NAME(input value, output value) (function)
#include <stdio.h>
#define SUM(a,b,result) \
((result) = (a) + (b))
int main() {
int data1 = 5, data2 = 10;
int result ;
SUM(data1, data2, result);
printf("%d\n", result);
float fdata1 = 5.0, fdata2 = 10.0;
float fresult;
SUM(fdata1, fdata2,fresult);
printf("%f\n", fresult);
return 0;
}
#define GREATER(a, b, result)({ \
asm("cmp %1, %2\n\t"\
"jge 0f\n\t"\
"movl %1, %0\n\t" \
"jmp 1f\n\t"\
"0:\n\t" \
"movl %2, %0\n\t"\
"1:"\
:"=r"(result)\
:"r"(a), "r"(b));})
int main() {
int data1 = 20, data2 = 10;
int result;
GREATER(data1, data2, result);
printf("a = %d, b = %d result: %d\n", data1, data2, result);
data1 = 30;
GREATER(data1, data2, result);
printf("a = %d, b = %d result: %d\n", data1, data2, result);
return 0;
}