内联汇编总结

内联汇编是AT&T语法,对比Intel汇编语法学习

先说基本区别:
1、AT&T 源操作数在左边
2、寄存器前要加%
3、立即数前要有$
4、操作指令要指定操作数据大小

b: byte, w: word, l: long
movb, movw, movl
ljmp, lcall

5、访问内存不用 [] ,用 ()

寻址:

  • 直接寻址
// Intel
segreg(段基址): [base_address + offset_address + index * size]
// AT&T
segreg(段基址): base_address + offset_address + index * size
  • 寄存器寻址
// Intel
mov ebx,[eax]
// AT&T
mov (%eax), %ebx
  • 寄存器相对寻址
// Intel
mov al, [ebx-4]
// AT&T
movb -4(%ebx), %al
  • 变址寻址
// Intel
mov [esi*2], eax
mov [ebx+esi*2], eax
mov [base_value+esi*2], eax
mov [base_value+ebx+esi*2], eax

// AT&T
// 无 base_address ,无 offset_address:
movl %eax,(,%esi,2)
//功能是将 eax的值写入 esi*2 所指向的内存。

//无 base_address ,有 offset _ address:
movl %eax,(%ebx,%esi,2)
//功能是将 eax的值写入 ebx+esi*2 所指向的内存。

//有 base_address ,无 offset_address:
movl %eax,base_value(,%esi,2)
//功能是将 eax 的值写入 base_value+esi*2 所指向的内存 。

//有 base_ address ,有 offset_ address:
movl %eax,base_value(%ebx, %esi,2)
//功能是将 eax 的值写入 base_value+ebx+esi*2 所指向的内存。

内联汇编

最简单的内联形式

asm [volatile] ("assembly code")

volatile: 可选项,告诉gcc不要修改我写的汇编代码,原样保留
assembly code: 自己写的汇编代码,汇编指令之前用 “;” 分隔开

提醒 一 下,即使是指令分布在多个双引号中, gcc 最终也要把它们合并到一起来处理,合并之后,指令间必须要有分隔符。所以,当指令在多个双引号中时,除最后一个双引号外,其余双引号中的代码最后一定要有分隔符 “;”

// example
asm"movl $9, %eax;""pushl %eax")

asm ("pusha; \
		movl $4, %eax ; \
		movl $1, %ebx; \
		movl str, %ecx;\
		movl $12, %edx ; \
		int $0x80; \
		mov %eax,count;\
		pop a \
		")

扩展内联汇编

解决的问题:

  1. 防止和C代码中使用的寄存器冲突
  2. 汇编语言访问C代码中变量

扩展后的内联汇编格式:

asm [volatile] ("assembly code" : output : input : clobber/modify)

多了 output 、 input 和clobber/modify 三项。其中的每 一 部分都可以省略,甚至包括 assembly code。省略的部分要保留冒号分隔符来占位,如果省略的是后面的 一 个或多个连续的部分,分隔符也不用保留,比如省略了clobber/modify,不需 要保留 input 后面的冒号。

input:给汇编代码提供要处理的数据
ouput:用来给指定汇编代码的数据如何输出给 C 代码使用

input 格式
“[操作数修饰符]约束名”(C 变量名)
output 格式
“[操作数修饰符] 约束名”(C 变量名)

clobber/modify :汇编代码执行后会破坏 一 些内存或寄存器资源,通过此项通知编译器,可能造成寄存器或内存数据的破坏,这样 gcc 就知道哪些寄存器或内存需要提前保护起来

约束
  • 寄存器约束
a :表示寄存器 eax/ax/al
b :表示寄存器 ebx/bx/bl
c :表示寄存器 eex/ex/cl
d :表示寄存器 edx/dx/dl
D :表示寄存器 edi/di
S :表示寄存器 esi/si
q :表示任意这 4 个通用寄存器之-: eax/ebx/ecx/edx
r :表示任意这 6 个通用寄存器之一: eax/ebx/ecx/edx/esi/edi
g :表示可以存放到任意地点(寄存器和内存)。相当于除了同 q 一样外,还可以让 gcc 安排在内存中
A :把 eax 和 edx 组合成 64 位整数
f :表示浮点寄存器
t :表示第 1 个浮点寄存器
u :表示第 2 个浮点寄存器

扩展内联汇编中寄存器前缀是两个%,单个%用来占位

void main() {
		int in_a = 1, in b = 2, out_sum;
		/*
		功能:ebx加到eax
		输出out_sum约束到eax中,"=" 表示只写
		输入in_a, in_b 分别约束到eax和ebx中
		*/
		asm ("addl %%ebx, %%eax""=a"(out_sum)"a"(in_a), "b"(in_b));
		printf ("sum is %d\n", out _ sum);
}
  • 内存约束
m :表示操作数可以使用任意一种内存形式。
o :操作数为内存变量,但访问它是通过偏移量的形式访问,即包含 offset_address的格式。
void main() {
		int in_a = 1, in_b = 2;
		printf ("in_b is %d\n", in_b);
		/*
		in_b 约束到内存空间
		*/
		asm ("movb %b0, %1;" ::"a"(in_a), "m"(in_b));
		printf ("in_b now is %d\n", in_b);
}

内存约束使用时注意,要求汇编指令是可以操作内存的,例如 movl 就不允许内存到内存的复制

  • 立即数约束

立即数只能在 input 中

i :表示操作数为整数立即数
F :表示操作数为浮点数立即数
I :表示操作数为 031 之间的立即数
J :表示操作数为 063 之间的立即数
N :表示操作数为 0255 之间的立即数
O :表示操作数为 032 之间的立即数
X :表示操作数为任何类型立即数
  • 通用约束
09 :此约束只用在 input 部分,但表示可与 output 和 input 中第 n 个操作数用相同的寄存器或内存
  • 序号占位符
asm("addl %2, %1""=a"(out_sum) : "a"(in_a), "b"(in _ b));

从左到右,最先出现的的是 “=a”(out_sum) ,%0 对应的就是eax
其次 %1 对应的是 “a”(in_a) 中的 eax,%2 对应的是 “b”(in _ b) 中的 ebx

对于 %0~9,还可以加 ‘b’, ‘h’ 来精细控制是16位数据中的低八位还是高八位,如%b0,%h0。
b,h属于机器模式的内容,相关的还有 w,k 等。

机器模式命名:数据大小+数据类型+mod
列举整型相关的机器模式
机器模式列举额一些常用的

  • 名称占位符
[名称]"约束名"(C 变量)
asm ("divb %[divisor];movb %%al, %[result]" \
		:[result]"=m"(out) \
		:"a "(in_a), [divisor]"m"(in_b) \
		);

“m”(in_b) 这个内存约束就有个名字 divisor ,以后在汇编指令中可以直接使用该名字操作这块内存数据

  • 操作数类型修饰符
= :表示操作数是只写,相当于为 output 括号中的 C 变量赋值,如= a(c_var),此修饰符相当于 c_var=eax 。
+:表示操作数是可读写的,告诉 gee 所约束的寄存器或内存先被读入,再被写入。
&:表示此 output 中的操作数要独占所约束(分配)的寄存器,只供 output 使用,任何 input 中所分配的寄存器不能与此相同。注意,当表达式中有多个修饰符时,&要与约束名挨着,不能分隔。
% :该操作数可以和下一个输入操作数互换。

补充知识:

注意,常见的字符串操作指令有movs[bwd]、ins[bwd] 和 outs[bwd]、lods[bwd]和stos[bwd]。
但是,esi和edi并不是被以上三组指令同时使用.
1、只有movs[bwd]才同时使用esi和edi,它是把esi所指向的地址处的数据复制到edi所指向的内存地址处。
2、ins[bwd]是从端口读入数据到内存的目的地址,故只涉及到edi。
3、outs[bwd]是把内存中的源数据写入端口,故只涉及到esi。lods[bwd]是 把内存中的源数据加载到寄存器al、ax或eax,故只涉及到esi。
4、stos[bwd]是将寄存器al、ax 或eax中的值写入内存中的目的地址,故只涉及到edi。

以上字符串指令每执行一次,所涉及到的源变址寄存器esi或目的变址寄存器edi都要根据操作数大小有所增减,至于是增加,还是减少,要取决于标志寄存器中的方向位DF,若DF为0,esi 和edi都自增,地址值越来越大,否则DF为1,esi和edi都自减,地址值越来越小。
这些字符串操作指令在读写数据时,esi 和edi作为它们的输入操作数,执行完成后,根据DF位的情况自增或自减,这时又作为输出。

内联汇编的第三部分:
clobber/modify:告诉编译器我们改了那些寄存器和内存,编译器能够合理安排。
如果在 output 和 input 中通过寄存器约束指定了寄存器,gcc必然会知道这些寄存器会被修改,所以,需要在 clobber/modify 中通知的寄存器肯定不是在 output 和 input 中出现过的。

这个很简单,只要在 clobber/modify 部分明确写出来就行了,记得要用双引号把寄存器名称引起来,多个寄存器之间用逗号’,’分隔开。

asm ("movl %%eax, %0 ;movl %%eax, %%ebx ":"=m"(ret_value ):: "bx")
// eflag寄存器用 cc 来声明
// 修改了内存用 memory 来声明
// volatile表示不把内存中的数据缓存在寄存器(不是asm后面的volatile)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值