GCC 内联汇编
在
MIT6.828
的实验中,有几处用到了很底层的函数,都以内联汇编的形式存在,例如 static inline uint32_t
read_esp(void)
{
uint32_t esp;
asm volatile("movl %%esp,%0" : "=r" (esp));
return esp;
}
static inline uint32_t
read_ebp(void)
{
uint32_t ebp;
asm volatile("movl %%ebp,%0" : "=r" (ebp));
return ebp;
}
因此这篇博客对于内联汇编的基本用法做一个总结。
首先是一般的形式,只能使用全局变量来传递数据,例如如下程序(插入在
kern/monitor.c
):
uint32_t test_val=0;
int mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
uint32_t *ebp=(uint32_t*)read_ebp();
cprintf("the ebp provided is :%8x and eip is %8x\n",ebp,ebp[1]);
// you could use simple asm if you use global variables
asm volatile(
"push %eax \n"
"mov %ebp,%eax \n"
"mov %eax,test_val \n"
"popl %eax \n"
);
cprintf("my ebp is %8x\n",(uint32_t*)test_val);
return 0;
}
运行结果如下
这里就跟普通的汇编一样的使用方式,注意关键字
volatile
是为了防止被优化,还有每行汇编语句后面的
\n
。又或者如这段程序
int32_t eip=0;
int main()
{
//basic inline assembly
//global variable can be use
asm volatile(
"push %eax \n"
"call .testpop \n"
".testpop: \n"
"pop %ebx \n"
"movl %ebx,%eax \n"
"movl %eax,eip \n"
"pop %eax \n"
);
printf("the eip is :%x\n",eip);
asm volatile(
"push %eax \n"
"movl %ebp,%eax \n"
"movl %eax,eip \n"
"pop %eax \n"
);
printf("the ebp is :%x\n",eip);
return 0;
}
如果只能使用全局变量,必然会有很多不方便。为了能使用局部变量,需要使用扩展的内联汇编。扩展的内联汇编形式如下
asm("assembly code"
:output location
:input orperands
:changed registers
);
其中
output location :输出的放哪儿
input operands :哪些输入
changed registers:改变了哪些寄存器
并且输入和输出部分都是如下形式
"constraint"(variable)
约束符主要是限制寄存器的使用
a use %eax %ax or %al
b use %ebx,%bx,or %bl
c use %ecx,%cx,or %cl
d use %edx,%dx,or %dl
S use %esi or %si
D use %edi or %si
r use any available register
q use one of %eax,%ebx,%ecx or %edx
A use %eax&%edx for 64-bit value
f use float register
m use memory location of variable
同时可以加上修饰符
+ read & write
= only write
来看一段程序
uint32_t ebp;
__asm__ __volatile__(
"push %%eax \n\t"
"mov %%ebp,%%eax \n\t"
"mov %%eax,%%edx \n\t"
"pop %%eax "
:"=d"(ebp)
:
:"%eax"
);
printf("the ebp is %8x\n",(uint32_t*)ebp);
这里
"=d"(ebp)
意思是输出使用寄存器
%edx
,并且把结果放到变量
ebp
中。没有输入所以省略,但是冒号不能省。这个过程改变了
%eax
。注意的是,编译器默认输入输出中涉及的寄存器都被改变,因此不能再将这部分寄存器写到改变部分去。注意汇编代码中的寄存器
%eax
要写成
%%eax
,每条语句完要写
\n\t
。
再来看一段程序
int xa=6;
int xb=2;
int result_1;
__asm__ __volatile__(
"add %%ebx,%%eax \n\t"
"movl $2,%%ecx \n\t"
"mul %%ecx \n\t"
"movl %%eax,%%edi \n\t"
"movl %%eax,%%edx"
:"=d"(result_1)
:"a"(xa),"b"(xb)
:"%ecx","%edi"
);
printf("the result is %d\n",result_1);
:"=d"(result_1)
输出使用
%edx
,放到
result_1
这个变量中;
:"a"(xa),"b"(xb)
输入变量
xa
的值放到
%eax
中,
xb
%ebx
中;
:"%ecx","%edi"
这个过程还改变了
%ecx
和
%edi
再来看一个例子
int data1=10;
int data2=20;
int result;
__asm__ __volatile__(
"imul %%edx ,%%ecx \n\t"
"movl %%ecx ,%%eax \n\t"
:"=a"(result)
:"d"(data1),"c"(data2)
);
printf("10*20 is %d\n",result);
有了上面的例子,这个应该就很好理解了。然而在我们看别人写的内联汇编中,有时会出现
%0,%1
这种。这叫占位符,就是代表第几个操作数所在的寄存器,例如看如下代码
// %0 is the register to store result
// %1 is the register to store data1
// %2 is the register to store data2
__asm__ __volatile__(
"imul %1 ,%2 \n\t"
"movl %2 ,%0 \n\t"
:"=r"(result)
:"r"(data1),"r"(data2)
);
printf("10*20 is %d\n",result);
这里使用了限定符
r
就是,让编译器自己选择可用的寄存器。注意这里改变的寄存器列表为空,需要连带冒号一起省略。
同时,也可以用输入变量来接受结果,结合占位符,有如下代码
__asm__ __volatile__(
"imul %1 ,%0 \n\t"
:"=r"(data2)
:"r"(data1),"0"(data2)
);
输入和输出都是
data2
但是,如果输入输出过多,还用数字就会显得不太好,因此
gcc
也有一个方便的做法
data1=10;
data2=20;
__asm__ __volatile__(
"imul %[value1] ,%[value2] \n\t"
:[value2]"=r"(data2)
:[value1]"r"(data1),"0"(data2)
);
printf("10*20 is %d\n",data2);
全文的测试代码如下:
//test_asm.c
#include <stdio.h>
#include <stdint.h>
int32_t eip=0;
int main()
{
//basic inline assembly
asm volatile(
"push %eax \n"
"call .testpop \n"
".testpop: \n"
"pop %ebx \n"
"movl %ebx,%eax\n"
"movl %eax,eip \n"
"pop %eax \n"
);
printf("the eip is :%x\n",eip);
asm volatile(
"push %eax \n"
"movl %ebp,%eax\n"
"movl %eax,eip \n"
"pop %eax \n"
);
printf("the ebp is :%x\n",eip);
uint32_t ebp;
__asm__ __volatile__(
"push %%eax \n\t"
"mov %%ebp,%%eax \n\t"
"mov %%eax,%%edx \n\t"
"pop %%eax "
:"=d"(ebp)
:
:"%eax"
);
printf("the ebp is %8x\n",(uint32_t*)ebp);
int xa=6;
int xb=2;
int result_1;
__asm__ __volatile__(
"add %%ebx,%%eax \n\t"
"movl $2,%%ecx \n\t"
"mul %%ecx \n\t"
"movl %%eax,%%edi \n\t"
"movl %%eax,%%edx"
:"=d"(result_1)
:"a"(xa),"b"(xb)
:"%ecx","%edi"
);
printf("the result is %d\n",result_1);
int data1=10;
int data2=20;
int result;
__asm__ __volatile__(
"imul %%edx ,%%ecx \n\t"
"movl %%ecx ,%%eax \n\t"
:"=a"(result)
:"d"(data1),"c"(data2)
);
printf("10*20 is %d\n",result);
__asm__ __volatile__(
"imul %1 ,%2 \n\t"
"movl %2 ,%0 \n\t"
:"=r"(result)
:"r"(data1),"r"(data2)
);
printf("10*20 is %d\n",result);
// you could refer them
// 0 means use the first register to store the input and output
__asm__ __volatile__(
"imul %1 ,%0 \n\t"
:"=r"(data2)
:"r"(data1),"0"(data2)
);
printf("10*20 is %d\n",data2);
// you could rename
// [name] "constraint"(variable)
data1=10;
data2=20;
__asm__ __volatile__(
"imul %[value1] ,%[value2] \n\t"
:[value2]"=r"(data2)
:[value1]"r"(data1),"0"(data2)
);
printf("10*20 is %d\n",data2);
return 0;
}