The Hardware Software Interface Section 4 x86 Assembly
课程主页:https://courses.cs.washington.edu/courses/cse351/16sp/
课程资料:
实验部分:https://github.com/vuquangtrong/HW-SW_Interface_Course
实验说明:https://courses.cs.washington.edu/courses/cse351/13sp/lab-0.html
课件:http://academictorrents.com/details/b63a566df824b39740eb9754e4fe4c0140306f4b
课程视频:https://www.bilibili.com/video/BV1Zt411s7Gg?from=search&seid=8781593976070799647
这次回顾第四部分,这部分介绍了汇编码。
第4节:x86汇编编程
移动指令、寄存器和操作数
三种基本指令
- 内存与寄存器之间的数据传输
- 将数据从内存加载到寄存器
- $\%\text{reg = Mem[address]}$
- 将寄存器数据存入内存
- $\text{Mem[address] = }\%\text{reg}$
- 将数据从内存加载到寄存器
- 对寄存器或内存数据执行算术函数
- $\text{c=a+b}$
- 控制转移
- 无条件跳转
- 条件分支
移动数据
移动数据
movq Source, Dest:
操作数类型
- 立即数(Immediate):常量整型数据
- 例如:$$ 0 \times 400, $-533$
- 类似于C常量,但以$$$开头
- 1,2,或4个字节编码
- 寄存器(Register):16个整数寄存器之一
- 例子:%rax, %r13
- 但是%rsp保留作特殊用途
- 内存(Memory):在寄存器指定的地址处连续8个字节的内存
- 最简单的例子:(%rax)
- 其他各种“地址模式”
- 立即数(Immediate):常量整型数据
各种组合如下:
内存寻址方式
完整的地址寻址模式
最一般的形式
D(Rb,Ri,S) Mem[Reg[Rb]+S*Reg[Ri]+D]
- D:常量“位移” ,$ 1,2,$或4个字节
- Rb:基地址寄存器:16个整数寄存器中的任何一个
- Ri:索引寄存器:任何,除了%rsp
- S:比例系数:$ 1,2,4$或$8$
特殊情形
全部情形如下:
例子
32位 vs 64位操作
- 4比特使用l$\leftrightarrow$8比特使用q
- 新指令:
- $\text{movl}\to\text{movq}$
- $\text{addl}\to\text{addq}$
- $\text{sall}\to\text{salq}$
- x86-64仍然可以使用32位指令来生成32位结果
- 目的寄存器高位被置0。
- 示例:$\text{addl}$
算术运算
地址计算指令
- leaq Src,Dst
- Src是地址模式表达式
- 将Dst设置为表达式表示的地址
- 用途
- 在没有内存引用的情况下计算地址
- 例如,翻译$ p = \& x [i] $
- 计算形式为$ x + k * y $的算术表达式
- $ k = 1,2,4,$或8
- 在没有内存引用的情况下计算地址
例子:
long m12(long x)
{
return x*12;
}
转换成汇编语言得到
leaq (%rdi,%rdi,2), %rax # t <- x+x*2
salq $2, %rax # return t<<2
某些算数指令
二元指令
一元指令
条件码
条件码(隐式设置)
单比特寄存器
- CF:进位标志(无符号)
- SF:符号标志(有符号)
- ZF:零标记
- OF:溢出标志(有符号)
通过算术运算隐式设置
例如:addq Src, Dest $ \leftrightarrow t = a + b $
如果最高有效位进位,则CF置为1(无符号溢出)
如果$ t == 0 $,则ZF置为1
如果$ t <0 $,则SF置为1
如果
则OF置为1
leaq指令不会改变条件码
条件码(显式设置)
通过比较指令进行显式设置
cmpq Src2,Src1
cmpq b,类似于计算a-b而不设置
如果最高有效位进位,则CF置为1(无符号溢出)
如果$ a == b $,则ZF置为1
如果$ (a-b) <0 $,则SF置为1
如果
则OF置为1
通过测试指令进行显式设置
- testq Src2,Src1
- testq b,a类似于计算$a \& b$而不设置dest
- 根据$ \operatorname {Src} 1\& \operatorname {Src} 2 $的值设置条件码
- 使一个操作数成为掩码很有用
- 当$ a \& b == 0 $时设置ZF为1
- 当$a \& b <0 $时设置SF为1
- testq Src2,Src1
完整比较测试指令如下:
读取条件码
- SetX指令
- 根据条件码的组合,将dest的低位字节设置为0或1
- 不会更改剩余的7个字节
来看一个具体例子:
int gt (long x, long y)
{
return x > y;
}
汇编代码为
cmpq %rsi, %rdi # Compare x:y
setg %al # Set when >
movzbl %al, %eax # Zero rest of %rax
ret
变量和寄存器的对应关系为
条件分支和无条件分支
跳转指令
条件分支例子
考虑如下例子:
long absdiff(long x, long y)
{
long result;
if (x > y)
result = x-y;
else
result = y-x;
return result;
}
为了和汇编码格风格相近,首先将上述代码改写为goto的格式:
long absdiff_j(long x, long y)
{
long result;
int ntest = x <= y;
if (ntest) goto Else;
result = x-y;
goto Done;
Else:
result = y-x;
Done:
return result;
}
汇编码为
absdiff:
cmpq %rsi, %rdi # x:y
jle .L4
movq %rdi, %rax
subq %rsi, %rax
ret
.L4: # x <= y
movq %rsi, %rax
subq
ret
变量和寄存器的对应关系为
一般情形如下:
C语言:
val = Test ? Then_Expr : Else_Expr;
汇编码:
ntest = !Test;
if (ntest) goto Else;
val = Then_Expr;
goto Done;
Else:
val = Else_Expr;
Done:
. . .
循环
汇编语言的循环都是goto模式,所以C中的循环首先要转换为goto模式,转换方法如后续所述,这里给出汇编语言循环的例子:
C代码
int fact_do(int x)
{
int result = 1;
do {
result *= x;
x = x-1;
} while (x > 1);
return result;
}
Goto版本
int fact_goto(int x)
{
int result = 1;
loop:
result *= x;
x = x-1;
if (x > 1) goto loop;
return result;
}
汇编代码
fact_goto:
pushl %ebp # Setup
movl %esp,%ebp # Setup
movl $1,%eax # eax = 1
movl 8(%ebp),%edx # edx = x
.L11:
imull %edx,%eax # result *= x
decl %edx # x--
cmpl $1,%edx # Compare x : 1
jg .L11 # if > goto loop
movl %ebp,%esp # Finish
popl %ebp # Finish
ret # Finish
一般的”Do-While”翻译
Do-While代码
do
Body
while (Test);
Goto版本
loop:
Body
if (Test)
goto loop
其中Body的形式如下
{ Statement1; Statement2; … Statementn; }
一般的”While”翻译
While代码
while (Test)
Body
Goto版本
goto test;
loop:
Body
test:
if (Test)
goto loop;
done:
另一种方法是将While修改为Do-While
if (!Test)
goto done;
do
Body
while(Test);
done:
Goto版本
if (!Test)
goto done;
loop:
Body
if (Test)
goto loop;
done:
一般的”For”循环翻译
For循环形式
for (Init; Test; Update )
Body
修改为While循环
Init;
while (Test) {
Body
Update;
}
Switch语句
跳转表结构
Switch分支使用了跳转表结构:
跳转表将分支的条件转换为0到n,然后补齐中间缺失的部分。
例子
C代码
long switch_eg (long x, long y, long z)
{ long w = 1;
switch(x) {
case 1: //.L3
w = y*z;
break;
case 2: //.L5
w = y/z;
/* Fall Through */
case 3: //.L9
w += z;
break;
case 5:
case 6: //.L7
w -= z;
break;
default: //.L8
w = 2;
}
return w;
}
跳转表
.section .rodata
.align 8
.L4:
.quad .L8 # x = 0
.quad .L3 # x = 1
.quad .L5 # x = 2
.quad .L9 # x = 3
.quad .L8 # x = 4
.quad .L7 # x = 5
.quad .L7 # x = 6
汇编码例子
汇编码
switch_eg:
. . .
ja .L61 # if > goto default
jmp *.L62(,%edx,4) # goto JTab[x]
反汇编结果
08048610 <switch_eg>:
. . .
08048622: 77 0c ja 8048630
08048624: ff 24 95 dc 88 04 08 jmp *0x80488dc(,%edx,4)
8048630: bb 02 00 00 00 mov $0x2,%ebx
8048635: 89 d8 mov %ebx,%eax
8048637: 5b pop %ebx
8048638: c9 leave
8048639: c3 ret
804863a: 8b 45 0c mov 0xc(%ebp),%eax
804863d: 99 cltd
804863e: f7 f9 idiv %ecx
8048640: 89 c3 mov %eax,%ebx
8048642: 01 cb add %ecx,%ebx
8048644: 89 d8 mov %ebx,%eax
8048646: 5b pop %ebx
8048647: c9 leave
8048648: c3 ret
8048649: 29 cb sub %ecx,%ebx
804864b: 89 d8 mov %ebx,%eax
804864d: 5b pop %ebx
804864e: c9 leave
804864f: c3 ret
8048650: 8b 5d 0c mov 0xc(%ebp),%ebx
8048653: 0f af d9 imul %ecx,%ebx
8048656: 89 d8 mov %ebx,%eax
8048658: 5b pop %ebx
8048659: c9 leave
804865a: c3 ret
查看跳转表的方法
gdb asm-cntl
(gdb) x/7xw 0x080488dc
0x080488dc:
0x08048630
0x08048650
0x0804863a
0x08048642
0x08048630
0x08048649
0x08048649