GCC 内联汇编
LINUX下的汇编入门
AT&T风格 汇编 和GCC风格汇编
汇编代码的调试
前面写了三篇,是自我摸索三篇,摸着石头过河,有些或许是错误的细节,不必在意! 今天我们直接用GCC编译C语言代码,且在C语言里面内嵌AT&T风格的汇编! 前三篇大家了解即可,我们重点放在内嵌汇编里,简单快捷舒服!
GCC支持在C/C++代码中嵌入汇编代码,这些汇编代码被称作GCC Inline ASM——GCC内联汇编。
这是一个非常有用的功能,有利于我们将一些C/C++语法无法表达的指令直接潜入C/C++代码中,另外也允许我们直接写C/C++代码中使用汇编编写简洁高效的代码。其实为了装逼和护城河!
gcc 编译器支持 2 种形式的内联 asm 代码:
基本 asm 格式:不支持操作数;
扩展 asm 格式:支持操作数;
1. 语法规则
asm [volatile] ("汇编指令")
所有指令,必须用双引号包裹起来;
超过一条指令,必须用n分隔符进行分割,为了排版,一般会加上t;
多条汇编指令,可以写在一行,也可以写在多行;
关键字 asm 可以使用 asm 来替换;
volatile 是可选的,编译器有可能对汇编代码进行优化,使用 volatile 关键字之后,告诉编译器不要优化手写的内联汇编代码。
基本内联汇编的格式是
__asm__ __volatile__("Instruction List");
可选风格 有比较多种,不过测试了下 就下面代码的风格支持得好,
每行汇编命令 要加冒号和NT 确实麻烦. 不过可以使用软件前后追加特定符号就行了.
下面是基本格式 不支持操作数,它必须使用全局变量,这限制太麻烦了
#include <stdio.h>
<br>
<br>
int a =
1;
<br>
int b =
2;
<br>
int c;
<br>
<br>
int
main
()
<br>{
<br>
asm
volatile
(
<br>
"movl a, %eaxnt"
<br>
"addl b, %eaxnt"
<br>
"movl %eax, c"
<br> );
<br>
printf(
"c = %d n", c);
<br>
return
0;
<br>}
<br>
其实汇编 内嵌 相当于调用个 C函数 而已, 不过这C函数是汇编函数而已
<br>
1.
include
<stdio.h>
<br>
<br>
int
main
()
<br>{
<br>
int data1 =
1;
<br>
int data2 =
2;
<br>
int data3;
<br>
<br>
asm
volatile
<br>
(
<br>
"movl %%ebx, %%eaxnt"
<br>
"addl %%ecx, %%eax"
<br> :
"=a"(data3) :
"b"(data1),
"c"(data2)
<br>);
<br>
<br>
/*asm [volatile] ("汇编指令nt" : "输出操作数列表" : "输入操作数列表" : "改动的寄存器")*/
<br>
printf(
"data3 = %d n", data3);
<br>
return
0;
<br>}
<br>
<br>
这里必须使用扩展ASM格式,才能不使用全局变量当作参数传入汇编函数里
所以汇编命令 寄存器要多个%号 "movl %%ebx,%%eax" 把EBX的值覆盖进
EAX
里.
所有汇编命令结束后 最后个小挂号前 开始定义我们的函数参数
第一个冒开始是 输出参数 :"=a"(data3)
第二个冒号开始是 输入参数 有多个参数用逗号分隔
第三个冒号是不要优化寄存器列表
其中
:
"=a
"
(data3
) 的 data3 是C的变量 用小挂号保护起来, 前面的=A
叫做
"修饰符"
对输出寄存器或内存地址提供 额外的说明,包括下面4个修饰符:
+:被修饰的操作数可以读取,可以写入;
=:被修饰的操作数只能写入;
%:被修饰的操作数可以和下一个操作数互换;
&:在内联函数完成之前,可以删除或者重新使用被修饰的操作数;
其中A 使用寄存器的别名 "=a" 表示只能写A的寄出器(EAX)
通俗讲下面的
叫约束
a: 使用 eax/ax/al 寄存器;
b: 使用 ebx /bx/bl 寄存器;
c: 使用 ecx/cx/cl 寄存器;
d: 使用 edx/dx/dl 寄存器;
r: 使用任何可用的通用寄存器;
m: 使用变量的内存位置;
b(data1)表示 data1变量的值复制到B寄出器里
在内联汇编代码中,没有声明“改动的寄存器”列表,也就是说可以省略掉(前面的冒号也不需要);
使用占位符来代替寄存器名称
如果操作数有
很多,那么在内联汇编代码中去写每个寄存器的名称,就显得
很不方便。占位符有点类似于批处理脚本中,利用
2...来引用输入参数一样,内联汇编代码中的占位符,从
输出操作数列表中的寄存器开始从
0 编号,一直编号到
输入操作数列表中的所有寄存器。
1.
include
<stdio.h>
<br>
int
main
()
<br>{
<br>
int data1 =
1;
<br>
int data2 =
2;
<br>
int data3;
<br>
<br>
asm(
"movl %1, %%eaxnt"
<br>
"addl %2, %0"
<br>
:
"=r"
(data3) :
"r"
(data1),
"r"
(data2) );
<br>
<br>
printf(
"data3 = %d n", data3);
<br>
return
0;
<br>}
<br>
<br>
%0 是输入参数 依次是 两个输入参数 %1 %2
内联汇编的C语言 正常编译就好了
[root@dsmart=>LINUX_ASM]
$gcc main_add.c -o main.add.exe
<br>[root@dsmart=>LINUX_ASM]$./main.add.exe
<br>data3 = 3
<br>
我们可以查看下GCC 汇编的代码
[root@dsmart=>LINUX_ASM]$gcc -S main_add.c -o main_add.asm[root@dsmart=>LINUX_ASM]$vim main_add.asm
下面是我们GCC把MAIN_ADD.C全部翻译成了汇编代码,而我们重点的内嵌汇编用蓝色注解#APP----#NOAPP 范围内
.file "main_add.c" <br> .section .rodata <br> .LC0: <br> .string "data3 = %d n" <br> .text <br> .globl main <br> . type main, @ function <br> main: <br> .LFB0: <br> .cfi_startproc <br> pushq %rbp <br> .cfi_def_cfa_offset 16 <br> .cfi_offset 6, -16 <br> movq %rsp, %rbp <br> .cfi_def_cfa_register 6 <br> subq $16 , %rsp <br> movl $1 , -4(%rbp) <br> movl $2 , -8(%rbp) <br> movl -4(%rbp), %eax <br> movl -8(%rbp), %edx <br> #APP <br> # 9 "main_add.c" 1 <br> movl %eax, %eax <br> addl %edx, %eax <br> <br> # 0 "" 2 <br> #NO_APP <br> movl %eax, -12(%rbp) <br> movl -12(%rbp), %eax <br> movl %eax, %esi <br> movl $.LC0, %edi <br> movl $0 , %eax <br> call printf <br> movl $0 , %eax <br> leave <br> .cfi_def_cfa 7, 8 <br> ret <br> .cfi_endproc <br> .LFE0: <br> .size main, .-main <br> .ident "GCC: (GNU) 5.5.0" .section .note.GNU-stack,"",@progbits
里面这段代码不是%1了,被具体替换成了寄出器名.
这段是C语言调用汇编函数进行参数压栈操作,分别把参数1,2压入栈底