HOW 2 GCC Inline Assembly

参考资料:https://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html

https://gcc.gnu.org/onlinedocs/gcc/Using-Assembly-Language-with-C.html

Asm Syntax

linux上我一般都在用GNU C 编译器 GCC,支持AT&T和intel格式的内联汇编,gcc默认是att,我个人是习惯了intel格式的汇编,但是大部分操作系统源码都是att格式的内联汇编,cao

+------------------------------+------------------------------------+
| Intel Code | AT&T Code |
+------------------------------+------------------------------------+
| mov eax,1 | movl $1,%eax |
| mov ebx,0ffh | movl $0xff,%ebx |
| int 80h | int $0x80 |
| mov ebx, eax | movl %eax, %ebx |
| mov eax,[ecx] | movl (%ecx),%eax |
| mov eax,[ebx+3] | movl 3(%ebx),%eax |
| mov eax,[ebx+20h] | movl 0x20(%ebx),%eax |
| add eax,[ebx+ecx*2h] | addl (%ebx,%ecx,0x2),%eax |
| lea eax,[ebx+ecx] | leal (%ebx,%ecx),%eax |
| sub eax,[ebx+ecx*4h-20h] | subl -0x20(%ebx,%ecx,0x4),%eax |
+------------------------------+------------------------------------+
c语言变量寻址
intel mov eax,[_value] at&t mov _value,%eax
at&t 这样把eax的值写入0x26fa8地址处:mov %eax,0x26fa8
  • 源操作数和目的操作数顺序相反
  • att寄存器%前缀
  • att立即数前有$,16进制0x而不是后加h
  • att mov 操作大小’b’、’w’、’l’ 分别指明了字节(8位)、字(16位)、长型(32位)替代intel”byte ptr”、 “word ptr” 和 “dword ptr” 前缀
  • intel:section:[base + index*scale + disp]—>att:section:disp(base, index, scale),这里立即数不用$

Basic Inline

两种语法都可以

asm("assembly code")
__asm__("assembly code")
asm("assembly code1;"
"assembly code2;")
asm("assembly code1\n\t"
"assembly code2\n\t")
#include <stdio.h>
#include <unistd.h>
int main() {
asm("mov rcx,0x666;"
"mov rax, rcx;");
asm("mov rcx,0x666\n\t;"
"mov rax, rcx;\n\t");
}
//切换到intel格式gcc -g -masm=intel -o test test.c

image-20230322143535231

#include <stdio.h>
#include <unistd.h>
int main() {
__asm__("mov %rax, %rbx\n\t"
"mov $56, %rsi\n\t");
}
//gcc -g -o test test.c
//gdb改变汇编风格set disassembly-flavor intel | att

image-20230322144433403

这样插入汇编很简单但是gcc并不知道我们改变了那些寄存器的值,gcc可能进行某些代码优化时,会用到这些寄存器但是它里面的值已经被我们插入汇编修改掉了,就会导致一些error

Extended Asm

为了解决上述问题并且和c有个更好的交互,就有了拓展汇编

asm ( assembler template 
: output operands /* optional */
: input operands /* optional */
: list of clobbered registers /* optional */
);

operands

总操作数的数目限制在 10 个,或者机器描述中的任何指令格式中的最大操作数数目,以较大者为准。

在汇编程序模板中,每个操作数用数字引用。编号方式从0开始,从上往下

  • r表示我们让gcc自动帮我们去选择一个输入寄存器一个输出寄存器
asm ("leal (%1,%1,4), %0"
: "=r" (five_times_x)
: "r" (x)
);

image-20230322163200644

  • 如果我们想要输入和输出放在同一个寄存器
asm ("leal (%0,%0,4), %0"
: "=r" (five_times_x)
: "0" (x)
);
  • 指定ecx寄存器,为了和操作数进行区分,寄存器我们采用两个%
asm ("leal (%%ecx,%%ecx,4), %%ecx"
: "=c" (x)
: "c" (x)
);

image-20230322162844426

我们没有往list of clobbered registers去添加寄存器,因为目前情况gcc知道我们破坏了哪个,因为它们被显式地指定为约束了

Clobber List

如果指令隐式或显式地使用了任何其他寄存器,(并且寄存器没有出现在输出或者输出约束列表里),那么就需要在Clobber List中指定这些寄存器。

如果我们的指令可以修改条件码寄存器(cc),我们必须将 “cc” 添加进修饰寄存器列表。

如果我们的指令以不可预测的方式修改了内存,那么需要将 “memory” 添加进修饰寄存器列表。

Volatile修饰符

gcc会在source–>code的过程中会进行一些优化,例如删除一些代码改变一些代码位置等

为了不让gcc去优化我们的inline asm将关键词volatile 或者 __volatile__ 放置在 asm 后面、()的前面。

constraints

寄存器约束

“reg constraints”(variable),输出操作数还要有“=”的约束在”=reg constraints”(variable)

+---+--------------------+
| r | Register(s) |
+---+--------------------+
| a | %eax, %ax, %al |
| b | %ebx, %bx, %bl |
| c | %ecx, %cx, %cl |
| d | %edx, %dx, %dl |
| S | %esi, %si |
| D | %edi, %di |
+---+--------------------+
q eax, ebx, ecx, edx

内存操作数约束

“m”(variable),使用memory而不是reg作为temp来做运算

匹配(数字)约束

在某些情况下,一个变量可能既充当输入操作数,也充当输出操作数。可以通过使用匹配约束在 “asm” 中指定这种情况。

asm (“incl %0” :”=a”(var):”0”(var));

+

“+”号用于指定一个操作数既可以用作输入,也可以用作输出,即输入/输出操作数。

int a = 10, b = 20;
asm("addl %[a], %[b]"
: [b] "+r" (b)
: [a] "r" (a));

&

“&” : 意味着这个操作数为一个早期改动的操作数,其在该指令完成前通过使用输入操作数被修改了。因此,这个操作数不可以位于一个被用作输出操作数或任何内存地址部分的寄存器。如果在旧值被写入之前它仅用作输入而已,一个输入操作数可以为一个早期改动操作数。

#include <stdio.h>
#include <unistd.h>
int main() {
int count = 10, fill_value = 0x6;
char dest[10] = {0};
asm("cld\n\t"
"rep\n\t"
"stosb\n\t"
:
: "c" (count), "a" (fill_value), "D" (dest)
:
);
for (size_t i = 0; i < 10; i++)
{
printf("%d\n", dest[i]);
}
}
//gcc -g -o test test.c
//cld将标志寄存器Flag的方向标志位DF清零。DI的地址指针自动增加
//rep重新rcx次数的stosb把al里面的数据往rdi指向地址去写

image-20230322153906189

0x0000000000001169 <+0>:    endbr64 
0x000000000000116d <+4>: push rbp
0x000000000000116e <+5>: mov rbp,rsp
0x0000000000001171 <+8>: sub rsp,0x30
0x0000000000001175 <+12>: mov rax,QWORD PTR fs:0x28
0x000000000000117e <+21>: mov QWORD PTR [rbp-0x8],rax
0x0000000000001182 <+25>: xor eax,eax
0x0000000000001184 <+27>: mov DWORD PTR [rbp-0x28],0xa
0x000000000000118b <+34>: mov DWORD PTR [rbp-0x24],0x6
0x0000000000001192 <+41>: mov QWORD PTR [rbp-0x12],0x0
0x000000000000119a <+49>: mov WORD PTR [rbp-0xa],0x0
0x00000000000011a0 <+55>: mov edx,DWORD PTR [rbp-0x28]
0x00000000000011a3 <+58>: mov eax,DWORD PTR [rbp-0x24]
0x00000000000011a6 <+61>: lea rsi,[rbp-0x12]
0x00000000000011aa <+65>: mov ecx,edx
0x00000000000011ac <+67>: mov rdi,rsi
0x00000000000011af <+70>: cld
0x00000000000011b0 <+71>: rep stos BYTE PTR es:[rdi],al
0x00000000000011b2 <+73>: mov QWORD PTR [rbp-0x20],0x0
0x00000000000011ba <+81>: jmp 0x11e8 <main+127>
0x00000000000011bc <+83>: lea rdx,[rbp-0x12]
0x00000000000011c0 <+87>: mov rax,QWORD PTR [rbp-0x20]
0x00000000000011c4 <+91>: add rax,rdx
0x00000000000011c7 <+94>: movzx eax,BYTE PTR [rax]
0x00000000000011ca <+97>: movsx eax,al
0x00000000000011cd <+100>: mov esi,eax
0x00000000000011cf <+102>: lea rax,[rip+0xe2e] # 0x2004
0x00000000000011d6 <+109>: mov rdi,rax
0x00000000000011d9 <+112>: mov eax,0x0
0x00000000000011de <+117>: call 0x1070 <printf@plt>
0x00000000000011e3 <+122>: add QWORD PTR [rbp-0x20],0x1
0x00000000000011e8 <+127>: cmp QWORD PTR [rbp-0x20],0x9
0x00000000000011ed <+132>: jbe 0x11bc <main+83>
0x00000000000011ef <+134>: mov eax,0x0
0x00000000000011f4 <+139>: mov rdx,QWORD PTR [rbp-0x8]
0x00000000000011f8 <+143>: sub rdx,QWORD PTR fs:0x28
0x0000000000001201 <+152>: je 0x1208 <main+159>
0x0000000000001203 <+154>: call 0x1060 <__stack_chk_fail@plt>
0x0000000000001208 <+159>: leave
0x0000000000001209 <+160>: ret

一直到+67处做好准备工作,去提升堆栈,初始化局部变量

image-20230322153828870

#include <stdio.h>
#include <unistd.h>
int main() {

int a=10, b=0;
asm ("mov %1, %%eax;"
"mov %%eax, %0;"
:"=r"(b)
:"r"(a)
:"%eax"
);
printf("%d,%d", a, b);

}

image-20230322154308379

0x0000000000001149 <+0>:    endbr64 
0x000000000000114d <+4>: push rbp
0x000000000000114e <+5>: mov rbp,rsp
0x0000000000001151 <+8>: sub rsp,0x10
0x0000000000001155 <+12>: mov DWORD PTR [rbp-0x8],0xa
0x000000000000115c <+19>: mov DWORD PTR [rbp-0x4],0x0
0x0000000000001163 <+26>: mov eax,DWORD PTR [rbp-0x8]
0x0000000000001166 <+29>: mov eax,eax
0x0000000000001168 <+31>: mov eax,eax
0x000000000000116a <+33>: mov DWORD PTR [rbp-0x4],eax
0x000000000000116d <+36>: mov edx,DWORD PTR [rbp-0x4]
0x0000000000001170 <+39>: mov eax,DWORD PTR [rbp-0x8]
0x0000000000001173 <+42>: mov esi,eax
0x0000000000001175 <+44>: lea rax,[rip+0xe88] # 0x2004
0x000000000000117c <+51>: mov rdi,rax
0x000000000000117f <+54>: mov eax,0x0
0x0000000000001184 <+59>: call 0x1050 <printf@plt>
0x0000000000001189 <+64>: mov eax,0x0
0x000000000000118e <+69>: leave
0x000000000000118f <+70>: ret