本文大部分内容学习自《Professional Assembly Language》
内联汇编
系统调用的参数存于EBX,ECX,EDX,ESI,EDI,EBP. 返回值存放在EAX寄存器中。
内联汇编(inline assembly)即在高级语言(C,C++)中加入汇编内容进行编码。
在高级语言中使用汇编常常有这些方法:
单独用汇编编写函数然后在C程序中调用;
用C写好函数,使用gcc的-S选项得到汇编程序,然后改动这些汇编程序,编译后连接;
在C程序中直接编写汇编程序然后编译处理。
内联汇编程序编写注意点:汇编指令每条一行;C传给汇编程序的数据是全局变量不用局部变量。
简单的内联汇编程序:
#include <stdio.h>
#include <stdlib.h>
int main(){
printf("hello world\n");
/* exit(0) */
asm("movl $1,%eax\n"
"movl $0,%ebx\n"
"int $0x80");
}
编译执行:
[edemon@CentOS workspace]$ gcc inline.c
[edemon@CentOS workspace]$ ./a.out
hello world
查看编译器得到的汇编文件:
[edemon@CentOS workspace]$ gcc -S -o inline.s inline.c
[edemon@CentOS workspace]$ cat inline.s
.file "inline.c"
.section .rodata
.LC0:
.string "hello world"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
leal 4(%esp), %ecx
.cfi_def_cfa 1, 0
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
.cfi_escape 0x10,0x5,0x2,0x75,0
movl %esp, %ebp
pushl %ecx
.cfi_escape 0xf,0x3,0x75,0x7c,0x6
subl $4, %esp
subl $12, %esp
pushl $.LC0
call puts
addl $16, %esp
#APP
# 7 "inline.c" 1
movl $1,%eax
movl $0,%ebx
int $0x80
# 0 "" 2
#NO_APP
movl -4(%ebp), %ecx
.cfi_def_cfa 1, 0
leave
.cfi_restore 5
leal -4(%ecx), %esp
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.9.1"
.section .note.GNU-stack,"",@progbits
编译器往往会对我们的代码作出优化,使用valatile修饰符可以设置不优化。asm volatile ("assembly code")
在ANSI C (C89)标准下内联汇编的关键字需要改成__asm__
。还是以上面的代码为例进行C89编译:
gcc -std=c89 -o exe inline.c
/tmp/cccKuHfI.o: In function `main':
inline.c:(.text+0x2a): undefined reference to `asm'
我们需要将asm
修改成:__asm__
gcc -std=c89 -o exe inline.c
$ ./exe
hello world
拓展asm
拓展asm可以允许我们使用C程序的局部变量,改变寄存器的值。格式:asm ("assembly code" : output locations : input operands : changed registers)
,在拓展汇编中引用寄存器必须使用两个百分号符号。
拓展ASM例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
int val1 = 1;
int val2 = 2;
int result;
asm("addl %%ecx,%%ebx\n"
"movl %%ebx,%%eax"
: "+a"(result)
: "c"(val1),"b"(val2));
printf("result: %d\n",result);
return 0;
}
/*
./a.out
result: 3
*/
拓展asm占位符
拓展asm能使用占位符,这样可以支配任何的寄存器或内存位置。
asm ("assembly code" : "=r"(result) : "r"(data1), "r"(data2))
%0表示包含result的寄存器,%1表示包含data1的寄存器,%2表示包含data2的寄存器。
那么上面的代码改成:
int main(){
int val1 = 1;
int val2 = 2;
int result;
asm ("addl %1,%2\n"
"movl %2,%0"
: "=r"(result)
: "r"(val1),"r"(val2));
printf("result: %d\n",result);
return 0;
}
带有可变寄存器的拓展汇编
和普通汇编格式类似,我们也可以声明改变的寄存器。
例子:
计算1+2
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
int data1 = 1;
int result = 2;
asm("movl %1,%%eax\n\t"
"addl %%eax,%0"
:"=r"(result)
:"r"(data1),"0"(result)
:"%eax");
printf("The result is %d\n",result);
return 0;
}
分析:
# 汇编:
$ gcc -S -o out1 changeRegister.c
# 查看关键内容
movl $1, -12(%ebp)
movl $2, -16(%ebp)
movl -12(%ebp), %ecx
movl -16(%ebp), %eax
movl %eax, %edx
#APP
# 8 "changeRegister.c" 1
movl %ecx,%eax
addl %eax,%edx
# 0 "" 2
#NO_APP
可以发现,data1 ($1)存于ecx寄存器。
内联汇编jump
在内联汇编中可以跳转,不过需要注意的是我们只能在同一部分的汇编中跳转,且注意不能和C的关键字冲突。
比如:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
int val1 = 2, val2 = 3;
int result;
asm("cmp %1,%2\n\t"
"jge greater\n\t"
"jmp end\n"
"greater:\n\t"
"movl %2,%0\n"
"end:\n\t"
"movl %1,%0\n\t"
:"=r"(result)
:"r"(val1),"r"(val2));
printf("the greater one between (2,3) is %d\n",result);
return 0;
}
/*
[edemon@CentOS workspace]$ gcc greater.c
[edemon@CentOS workspace]$ ./a.out
the greater one between (2,3) is 3
*/
C中的宏函数
一般,我们将宏按照大写字母来命名。这样避免和C库函数产生冲突。在书写宏函数的时候需要注意这样几点:宏的本质是替换,宏替换成一行文本,为了代码的可读性,我们使用反斜杠分开每一行,反斜杠后面不能出现任何的字符,特别注意空格,在({ })
中书写函数内容。在调用函数中可以像普通函数那样使用这个宏函数。
#include <stdio.h>
#define GREATER(a,b,result) ({ \
if(a-b>1e-6) \
result = a; \
else result =b;\
})
int main(){
int gt1;
int a1=1,b1=2;
GREATER(a1,b1,gt1);
printf("greater one between (%d,%d) is %d\n",a1,b1,gt1);
float a2=1.2,b2=2.5;
float gt2;
GREATER(a2,b2,gt2);
printf("greater one between (%f,%f) is %f\n",a2,b2,gt2);
return 0;
}
/*
[edemon@CentOS workspace]$ gcc sum.c
[edemon@CentOS workspace]$ ./a.out
greater one between (1,2) is 2
greater one between (1.200000,2.500000) is 2.500000
*/
可以使用gcc的-E选项来查看拓展后的代码内容:
shell:
[edemon@CentOS workspace]$ gcc -E -o out sum.c
[edemon@CentOS workspace]$ tail -n 20
int main(){
int gt1;
int a1=1,b1=2;
({ if(a1-b1>1e-6) gt1 = a1; else gt1 =b1; });
printf("greater one between (%d,%d) is %d\n",a1,b1,gt1);
float a2=1.2,b2=2.5;
float gt2;
({ if(a2-b2>1e-6) gt2 = a2; else gt2 =b2; });
printf("greater one between (%f,%f) is %f\n",a2,b2,gt2);
return 0;
}
内联汇编宏函数
和C的宏函数格式很像。
例子:比较大小
#include <stdio.h>
#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 a;
GREATER(2,4,a);
printf("the greater one in (2,4) is %d\n",a);
GREATER(18,17,a);
printf("the greater one in (18,17) is %d\n",a);
return 0;
}
/*
the greater one in (2,4) is 4
the greater one in (18,17) is 18
*/