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;
}