AT&T汇编-x86
x86CPU下AT&T汇编
与AT&T汇编对应的是inter汇编,还有go的plan9
c语言中基本数据类型得大小(byets)
c data type | typical 32-bit | x86-32 | x86-64 |
---|---|---|---|
char | 1 | 1 | 1 |
short | 2 | 2 | 2 |
int | 4 | 4 | 4 |
long | 4 | 4 | 8 |
long long | 8 | 8 | 8 |
float | 4 | 4 | 4 |
double | 8 | 8 | 8 |
long double | 8 | 10/12 | 10/16 |
cahr * | 4 | 4 | 8 |
- at&t汇编语言数据格式
c声明 | inter数据类型 | 汇编代码后缀 | 大小(byte) |
---|---|---|---|
char | 字节 | b | 1 |
short | 字 | w | 2 |
int | 双字 | l | 4 |
long | 双字 | l | 4 |
long long | 4 | ||
char * | 双字 | l | 4 |
float | 单精度 | s | 4 |
double | 双精度 | l | 8 |
long double | 扩展精度 | t | 10/12 |
数据传送指令(mov)
mov? S,D
- 将S传送到D
mov S,D | S→D | 传送 |
---|---|---|
movb | 传送字节 | |
movw | 传送字 | |
movl | 传送双字 |
movs S,D | 符号扩展(S)→D | 高位填充符号位 |
---|---|---|
movsbw | 扩展字节为字 | |
movsbl | 扩展字节为双字 | |
movswl | 扩展字为双字 |
movz S,D | 零扩展(S)→D | 高位充零 |
---|---|---|
movzbw | 扩展字节为字 | |
movzbl | 扩展字节为双字 | |
movzwl | 扩展字为双字 | |
pushl S | R[%esp]-4 → R[%esp] S→M[R[%esp]] | 双字压栈 |
popl D | M[R[%esp]] → D R[%esp]+4→R[%esp] | 双字出栈 |
- 注意栈是从高地址到低地址增长
允许操作的类型:
- 立即数:长整数
- 如:¥0x400,$-533
- 寄存器:8个通用寄存器之一
- %eax
- %edx
- %ecx
- %ebx
- %esi
- %edi
- %esp
- %ebp
- 存储器:4个连续字节
- 不能从内存到内存
寻址模式
D(RB,RI,S) = Mem[Reg[RB]+S*REG[Ri]+D]
D:常量(地址偏移量)
Rb:基址寄存器:8个通用寄存器之一
RI:索引寄存器:%esp不作为索引寄存器,一般%ebp也不用作这个用途
S:比例因子 1,2,4,or 8
其他变形:
(RB,Ri) = Mem[Reg[RB]+REG[Ri]
D(RB,Ri) = Mem[Reg[RB]+REG[Ri]+D
(RB,Ri,S) = Mem[Reg[RB]+S*REG[Ri]
- 地址计算指令leal
- lea 不解引用, leaq (%rsp) %rax == movq %rsp %rax 而不是 movq (%rsp) %rax
leal src,dest
计算出来得地址赋给dest
整数计算指令
- 以寄存器之间操作举例
|
|
- 补充
|
|
x86-64的通用寄存器
name | name |
---|---|
[%rax [%eax] ] | [%r8 [%r8d] ] |
[%rdx [%edx] ] | [%r9 [%r9d] ] |
[%rcx [%ecx] ] | [%r10 [%r10d] ] |
[%rbx [%ebx] ] | [%r11 [%r11d] ] |
[%rsi [%esi] ] | [%r12 [%r12d] ] |
[%rdi [%edi] ] | [%r13 [%r13d] ] |
[%rsp [%esp] ] | [%r14 [%r14d] ] |
[%rbp [%ebp] ] | [%r15 [%r15d] ] |
-
兼容32位下的寄存器(e开头),仍然可以使用,%eax也兼容16位下ah,al寄存器
-
64位传参:少于等于6个时放入寄存器rdi,rsi,rdx,rcx,r8,r9。多出的放入栈中
-
32位传参:放在栈中
条件码
CF
:carry flag
SF
:sign flag
ZF
:zero flag
OF
:overflow flag
- 条件码由算术指令隐含设置
addl src,edst
如果产生进位,CF被设置,可以检测无符号数运算溢出
结果为0,ZF被设置
结果小于0,SF
补码运算溢出,OF,可以看作符号数运算溢出(两个大于0的数相加为负)
- 比较指令
cmpl src2,src1
cmpl b,a 类似 a-b
相等ZF置1
结果小于0,SF置1
溢出OF置1(a>0&&b<0&&(a-b)<0)||(a<0&&b>0&&(a-b)>0)
- 测试指令
testl src2,src1
testl b,a 类似a&b
当为0,ZF置1
当<0,SF置1
test使 CF,OF 置0
读取条件码
- setX指令 读取当前条件码(组合)到目的字节寄存器
setX | condition | description |
---|---|---|
sete | ZF | equal/zero |
setne | ~ZF | not equal/not zero |
sets | SF | Negative |
setns | ~SF | nonnegative |
setg | ~(SF^OF)&~ZF | greater(signed) |
setge | ~(SF^OF) | greater or equal(signed) |
setl | (SF^OF) | less(signed) |
setle | (SF^OF)|ZF | less or equal(signed) |
seta | ~CF&~ZF | above(unsigned) |
setb | CF | below(unsigned) |
|
|
跳转指令
- 依赖当前条件码选择下一条执行语句
jX | condition | description |
---|---|---|
jmp | 1 | unconditional |
je | ZF | equal/zero |
jne | ~ZF | not equal/not zero |
js | SF | negative |
jns | ~SF | nonnegative |
jg | ~(SF^OF)&~ZF | greater(signed) |
jge | ~(SF^OF) | greater or equal(signed) |
jl | (SF^OF) | less(signed) |
jle | (SF^OF)|ZF | less or equal(signed) |
ja | ~CF&~ZF | above(unsigned) |
jb | CF | below(unsigned) |
|
|
- x86-64 下条件传送指令 比较新的机器有这个指令(比如i686)
- cmovC src,dest
- 如果C成立,将数据从src移动到dest
switch的汇编表示
表结构
基地址是 .L62
通过jump table来进行跳转
取值比较稀疏通过二叉树表示
程序运行栈
%esp
存储栈顶地址,栈网低地址延伸
pushl src
- 从src取得操作数
%esp=%esp-4
,32位汇编下- 数据写入栈顶地址
%esp
popl dest
- 读取栈顶
%esp
数据 %esp=%esp+4
- 数据写入dest
过程调用指令:call
-
call label
将返回地址压入栈,跳转至label -
返回地址是指:call指令的下一条指令地址
-
ret
跳转至栈顶的返回地址
栈帧:%ebp
-
存储的内容,自”顶“(栈顶)向下
- 子过程参数
- 局部变量,因为寄存器个数有限
- 被保存的寄存器值
- 父过程的栈帧起始地址(old
%ebp
)
-
栈帧的分配与释放
- 进入过程后先分配栈帧空间
- 过程返回时释放
%ebp
指向当前栈帧起始地址,与%esp
永远指向当前活跃栈帧的头尾
|
|
|
|
寄存器使用惯例
-
这是一个软件层的约定
-
使用惯例—通用寄存器分为两类
- 调用者负责保存
- %eax %edx %ecx
- %eax 用于保存过程返回值
- 被调用者负责保存
- %ebx %esi %edi
- %ebp其实也是
- 特殊
- %ebp %esp
- 调用者负责保存
x86-64过程调用与运行栈
过程参数(不超过6个)通过寄存器传参,大于部分使用栈传
当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。 当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。
所有对于栈帧内容的访问都是基于%rsp,%rbp完全当作通用寄存器
x86-64下的栈帧有一些不用操作特性
- 一次性分配整个帧
- 将%rsp减去某个值(栈帧的大小)
- 对于栈帧内容的访问都是基于%rsp完成
- 可以延迟分配,可以访问(%esp)128字节的栈上空间
- 释放简单
- %rsp直接加上某个值
数组的存储
T A[L];
基本数据类型:T 数组长度:L
连续存储在大小为L*sizeof(T)字节的空间内
int a[5];
寄存器edx赋值为数组的起始地址,寄存器%eax赋值为下标,对应的元素地址为edx+4*eax
- (%edx,%eax,4)
|
|
|
|
8086汇编与c语言
结构struct
|
|
相应汇编代码
|
|
数据存储位置对齐
对齐的一般原则
- 已知某种基本类型的大小为k字节
- 那么,其存储地址必须是k的整数倍
- x86-64的对齐要求基本上就是这样
- 但是32位系统,linux与windows系统的要求都略有不同
为何需要对齐
- 计算机访问内存一般是以内存块为单位的,块的大小是地址对齐的,如4,8,16字节对齐等
- 如果数据访问地址跨越“块”边界会引起额外的内存访问
编译器的工作
- 在结构的各个元素间插入额外空间来满足不同元素的对齐要求
x86-32下不同元素的对齐要求
- 基本数据类型
- 1 byte (char) 无要求
- 2 byte (short)2字节对齐 地址最后一位为0
- 4 byte (int,float,char *) 4字节对齐 地址后两位为0
- 8 byte (double) win(及大多数) 8字节对齐,3位0,linux 四字节对齐,2位0
- 12 byte (long double) win,linux 四字节对齐 两位0
x86-64下不同元素的对齐要求
- 基本数据类型
- 1,2,4 byte同32位一样
- 8 bytes (double ,char *) win&linux 8字节对齐,地址后三位为0
- 16 bytes (long double)linux 8位对齐
结构的存储对齐要求
- 必须满足结构中各个元素的对齐要求
- 结构自身的对齐要求等于其各个元素中对齐要求最高的那个,设为k字节
- 结构的起始地址与结构长度必须是k的整数倍
80x86-32汇编编程
hello world
|
|
|
|
汇编指示
- 汇编代码中以
.
开头的都是汇编指示(Directives),如.file
,.def
,.text
等,用以指导汇编器如何进行汇编。其中.file
.def
均用于调试(可以将其忽略) - 以
:
结尾的字符串,如_main:
是用以表示变量或函数地址的符号(Symbol) - 其他均为汇编指令
|
|
linux汇编命令&系统调用
as -o my-object-file.o helloworld.s
- -gstabs 产生带调试信息的object文件
- 64位环境下使用32位汇编,加参数:–32
ld -o my-exe-file my-object-file.o
- 可以有多个.o文件
- 64位下生成32位可执行程序加参数:-m elf_i386
如果有什么找不见:sudo apt-get install g++-multilib
- helloworld程序
|
|
- x86-linux 下的系统调用是通过中断指令(int 0X80)来实现的。
- 在执行int $0x80指令时
- 寄存器eax中存放的是系统调用的功能号,而传给系统调用的参数则必须按顺序放到寄存器ebx,ecx,edx,esi,edi中
- 系统调用完成之后,返回值可以在eax中获得
- 当系统调用需要参数大于5个,功能号保存在eax,全部参数依次存在一块连续内存中,同时在寄存器ebx中保存指向该内存区域的指针
查看系统调用功能号列表:locate unistd_32
或locate unistd_32
或者http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/syscalls/syscall_32.tbl
http://syscalls.kernelgrok.com/
- 处理命令行参数: 将参数按行输出
|
|
- 调用libc库函数:打印cpu信息
|
|
go p88