说明
文档来源
https://flint.cs.yale.edu/cs421/papers/x86-asm/asm.html
使用AT&T
语法; 原文档是intel
语法翻译过来的;
内容
基于32
位, x86
的硬件环境;
指令仅仅介绍常用, 即还有很大一部分的指令并没有支持;
编译器(汇编器)
GAS(GNU assembler:
即gnu
组织提供; 使用标准AT&T
语法;
注意
- 金介绍少部分指令; 其他指令查官方文档;
- 使用
32
位而不是16
位;16
比较复杂, 有偏移量之类的;
寄存器
32
寄存器
eax: Accumulator, 数学计算
ebx:
ecx: Counter, 循环的index
edx:
esp: stack pointer
, 栈顶;
ebp: base pointer
, 函数第一个局部变量在栈的位置; 上一个是之前的eip
地址;
eip: instruction pointer
, 指令寄存器;
16,8
位寄存器
共用寄存器, 也就是一改其他都被改;
eax, ax, ah, al
; ax = (ah << 8) + al
好处就是兼容且可以操作更小字节数据;
内存
说明
全局变量或静态变量; 即共享的; bss + dss
;
汇编全局变量申请
.data
var:
.byte 64 /* Declare a byte, referred to as location var, containing the value 64. */
.byte 10 /* Declare a byte with no label, containing the value 10. Its location is var + 1. */
x:
.short 42 /* Declare a 2-byte value initialized to 42, referred to as location x. */
y:
.long 30000 /* Declare a 4-byte value, referred to as location y, initialized to 30000. */
类型, 初始值, 类型决定数据占用字节;
汇编数组申请
s:
.long 1, 2, 3 /* Declare three 4-byte values, initialized to 1, 2, and 3. The value at location s + 8 will be 3. */
barr:
.zero 10 /* Declare 10 bytes starting at location barr, initialized to 0. */
str:
.string "hello" /* Declare 6 bytes starting at the address str initialized to
the ASCII character values for hello followed by a nul (0) byte. */
- 类型加若干初始值;
.zero
加长度- 字符串, 加
\0
地址格式
地址对应操作
- 加载地址对应的内存放入寄存器;
reg=*addr
; lea: load effective address
, 即给地址代表地址, 用地址赋值;reg=addr
三种
- 一元组:
offset(reg)
- 三元组:
offset(reg1, reg2, number)
,number={2^n|n=[0,3]}
即,number={1,2,4,8}
; 其他错误; - 地址:
var(,1)
,*var
offset, var
常数;offset
可以为正负数,var
同理;
label
编译后编程地址; 编程时方便阅读而已;
地址访问几种格式
// eax = *ebx
mov (%ebx), %eax /* Load 4 bytes from the memory address in EBX into EAX. */
// *var=*ebx
mov %ebx, var(,1) /* Move the contents of EBX into the 4 bytes at memory address var. (Note, var is a 32-bit constant). */
// eax=*(esi - 4)
mov -4(%esi), %eax /* Move 4 bytes at memory address ESI + (-4) into EAX. */
// *(esi + eax * 1) = *cl
mov %cl, (%esi,%eax,1) /* Move the contents of CL into the byte at address ESI+EAX. */
mov (%esi,%ebx,4), %edx /* Move the 4 bytes of data at address ESI+4*EBX into EDX. */
指定操作字节大小
目的
消除二义性;
常规
可以通过寄存器确定大小;
mov $2, (%ebx)
二义性: 目标地址大小未知, 通过后缀可以区分;
mov[bwl]
: Byte,Word,Long
, 默认32
; Long
根据不同机器字节不同;
程序跳转
说明
实际就是间接修改ip(Instruction Pointer)
的地址;
跳转
通过比较, 然后条件跳转; 条件跳转即查询状态寄存器对应位是0,1
, 然后再跳转;
跳转机制
if else
不同结果跳转不同位置;switch case
: 同理for, while
: 同if else
; 只是有返回而已;call
: 也会跳转, 这里是执行一些预处理后无条件跳转; 上面的是比较;
ip
偏移
ip
偏移: 即当前地址+
当前指令长度; 执行成功后进行偏移;
- 先执行, 再
ip + len
;
label
这个是编码的时候用, 这种就不需要写固定地址; 而是由链接器赋予地址;
调用
背景
模块化, 代码共用;
调用约定
- 调用者需要干一些事;
- 函数实现者需要干一些事;
调用约定可以让代码更加通用; 跨语言之间交互;
栈与变量
- 参数是倒序入栈, 然后就是顺序出栈;
- 局部变量一般使用
offset(rbp)
的形式, 而不是push
的形式, 因为 局部变量的偏移量是固定的; 栈特性;
调用者
调用前
- 备份(肯定会被使用的)寄存器到栈中;
- 参数入栈; 从后往前, 倒序; 和调用协议有关;
- 执行
call
: 这个指令将ip
入栈并跳转;
返回后
- 参数弹栈; 即
sp
恢复;(上面步骤2.的反向操作;)
- 栈中备份的寄存器数据恢复;
(上面步骤1.的反向操作;)
调用前备份, 调用后恢复, 表现的就像执行了一条简单的语句而不是函数一样;
函数(被调用者)
开始
- 将
ebp
备份并用当前esp
替换;(bp)
即当前函数的栈开始的地方, 可以用来快速定位参数和局部变量; - 局部变量分配:
局部变量一般是固定offset
, 即offset(ebp)
的形式; - 如果接下来会修改不常用寄存器; 如
(edi,esi,ebx)
之类的; 这种就需要在使用前自行备份, 使用完之后恢复;
退出
- 将返回值存储到
eax
中; (3. reverse)
将之前备份的edi,esi
寄存器值恢复;(2. reverse)
局部变量谈栈, 析构;c
直接将esp
恢复成ebp
,C++
会有析构函数;(1. reverse)``ebp
的值恢复, 即上一个函数的栈顶;(调用者调用前的 3.)
执行ret
: 将之前call
存储的ip
弹栈, 再跳转回ip
;