csapp第三章中,对汇编指令的要求比较高
这里把常见的指令整理一下
1
| g++ -Og -S -masm=intel xxx.cpp
|
得到反汇编文件查阅
程序编码
代码示例和解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
using namespace std;
long mult2(long a, long b) { return a*b; }
void multstore(long x, long y, long* des) { long t = mult2(x, y); *des = t; }
int main() { long a = 100, b = 150; long dest;
multstore(a, b, &dest); cout << dest << endl; }
|
反汇编代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| __Z9multstorellPl: .cfi_startproc
push rbp .cfi_def_cfa_offset 16 .cfi_offset rbp, -16 mov rbp, rsp .cfi_def_cfa_register rbp push rbx push rax .cfi_offset rbx, -24 mov rbx, rdx call __Z5mult2ll mov qword ptr [rbx], rax add rsp, 8 pop rbx pop rbp ret .cfi_endproc .globl _main .p2align 4, 0x90 _main:
|
数据传输
寄存器
第一个参数:rdi, 第二个参数:rsi, 第三个参数:rdx, 第四个参数:rcx
汇编器常见的错误
1 2
| movl %rax, %(rsp) movl %eax, %rdx
|
数据传送示例
1 2 3 4 5 6 7
| char -> int
char类型4字,转int需要符号扩展,4字用movl,这里需要符号扩展 用movsbl
movsbl (%rdi), %eax movl %eax, (%rsi)
|
1 2 3 4 5 6
| char -> unsigned
同样4字对4字,需要符号扩展movsbl
movsbl (%rdi), %eax movl %eax, (%rsi)
|
1 2 3 4 5 6 7
| unsigned char -> long
0扩展,转成8字存储,操作的是unsigned char,本来用的是movl, 需要0扩展 用movzbl
movzbl (%rdi), %eax; movq %rax, (%rsi)
|
1 2 3 4 5 6
| int -> char
操作数是int, 直接用movl即可, int是4字,存放到eax中 movl (%rdi), %eax movb %al, (%rsi) char只需要低位的,所以用movb %al, (%rsi)
|
1 2 3 4 5
| unsigned -> unsigned char
操作数unsigned,4字,movl存放在eax中 movl (%rdi), %eax movb %al, (%rsi)
|
1 2 3 4 5 6 7
| char -> short
需要做符号扩展,并且char要截断,取低位,存放在ax中,本来用movw 这里涉及到符号扩展,用movsbw
movsbw (%rdi), %ax movw %ax, (%rsi)
|
移位操作
移位量是由%cl寄存器的低m位决定的,高位会被忽略
1 2 3 4 5 6 7 8 9 10 11
| long shift_left4_rightn(long x, long n) { x <<= 4; x >>= n; return x; }
shift_left4_rightn: movq %rdi, %rax salq $4, %rax movl %esi, %ecx sarq %cl, %rax
|
特殊的算术操作
乘法,乘积低位放在%rax中,高位放在%rdx中
除法,需要用到%rdx寄存器来存放参数,商放在%rax中,余数放在%rdx中
控制
条件控制实现条件分支
1
| setnle D <-- ~(SF ^ OF) & ~ZF
|
溢出的处理,用xor,看作是不进位的加法
如果有符号溢出,相应的值要变化,比如
SF: t < 0
溢出的话,要xor OF
实际上SF 0->1
注意跳转指令前面有符号,f8表示符号位是-1,所以是0xd-0x8
1 2 3 4 5 6
| 0xffffff73所代表的跳转值,首先最高位为负 值为8位16进制数 所以最高位为-16^7
-16^7 + 0xfffff73 = -141 即为跳转偏移量
|
跳转指令翻译
1 2 3 4 5 6 7 8 9
| cmpq xxx1, xxx2 jge .L2 code....
means: if(xxx2 < xxx1) code... else .L2
|
用条件传送来实现条件分支
1 2 3 4 5 6 7 8
| testq %rdi, %rdi cmovns %rdi, %rax
if(x >= 0) v = x;
|
用循环来实现条件分支
for循环
1 2 3 4 5 6 7 8 9 10 11
| fun_a: movl $0, %eax jmp .L5 .L6: xorq %rdi, %rax shrq %rdi .L5: testq %rdi, %rdi jne .L6 andl $1, %eax ret
|
转换成c语言代码,可以发现L5是循环的主体
1 2 3 4 5 6 7 8
| while(%rdi != 0) { .L6 } return %eax & 0x1
.L6的实现如下 val ^= x x >>= 1
|
该实现如下:
1 2 3 4 5 6 7 8
| long fun_a(unsigned long x) { long val = 0; while(x) { val ^= x; x >>= 1; } return val & 0x1; }
|
代码功能:奇数个1, 每一位取出来xor,其值还是1
偶数个1,每一位取出来,其值是0
如果有奇数个1,返回1,偶数个1,返回0
1 2 3 4 5 6 7 8 9 10 11 12
| fun_b: movl $64, %edx movl $0, %eax .L10: movq $rdi, %rcx andl $1, %ecx addq %rax, %rax orq %rcx, %rax shrq %rdi subq $1, %rdx jne .L10 rep; ret
|
for循环主体
1 2 3 4 5 6 7 8 9 10 11 12 13
| subq $1, %rdx
eax = val = 0; for(edx = 64; edx != 0; edx--) { .L10 }
.L10: val in rax
tmp = x; tmp &= 1; (tmp in rcx) val << 1; val |= tmp x >> = 1
|
综上:
1 2 3 4 5 6 7 8 9
| long fun_b(unsigned long x) { long val = 0; long i; for(i = 64; i != 0; i--) { val = (x & 0x1) | (val << 1) x >>= 1; } return val; }
|
这个代码的作用很有意思
如图所示,创造x的镜像
switch语句
1 2 3 4 5
| switch2: addq $1, %rdi cmpq $8, %rdi ja .L2 jmp *.L4(, %rdi, 8)
|
start: rdi+1
x+1=0
x+1>8(default)
x+1<8
x=−1,0,1,2,3,4,5,6,7
函数调用过程
转移控制
函数调用的具体分析如下
stack上的局部存储
寄存器中的局部存储空间
数组的分配和访问
定长数组
异质的数据结构,联合,结构体
联合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| typedef union { struct { long u; short v; char w; } t1;
struct { int a[2]; char* p; } t2; } u_type;
void get(u_type *up, type* dest) { *dest = expr; }
// up in %rdi, dest in %rsi
|
具体的内存分配如下:
数据对齐
数据对齐的时候,start位置必须是类型的整数倍
如果不满足,则会填充
这里又一个优化技巧
结构体对数据类型进行降序排列
1 2 3 4 5 6 7 8
| struct P { char* x1; long x2; float x3; int x4; short x5; ... };
|
指针与缓冲区溢出
对抗缓冲区溢出攻击
支持变长栈帧
1 2 3
| long vframe(long n, long idx, long *q) { long *p[n]; }
|