获取源程序对应的汇编程序
如果想要获取自己编写的源程序对应的汇编程序,基本步骤是:
- 将自己的文件用GCC转换成目标代码
- 使用反汇编器得到格式良好的汇编代码
其中第一步, 也可以直接使用GCC转换成汇编代码,可以指定优化的级别.
按照书上写了这么一个程序:
long mult2(long, long);
void multstore(long x, long y, long *dest){
long t = mult2(x, y);
*dest = t;
}
然后放到虚拟机的Linux 64位中, 运行gcc -Og -S mstore.c编译成汇编文件, -Og选项是基本上按照C语言的级别进行编译, 不进行过多的优化.
再对C源文件进行编译: gcc -Og -c mstore.c, 得到一个目标文件.
查看汇编文件的内容, 如下:
.file "mstore.c"
.text
.globl multstore
.type multstore, @function
multstore:
.LFB0:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
movq %rdx, %rbx
call mult2
movq %rax, (%rbx)
popq %rbx
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size multstore, .-multstore
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)"
.section .note.GNU-stack,"",@progbits
这里以.开头的都是指示汇编器和链接器工作的伪指令,实际的指令是那些不以.开头,也就是multstore中的部分:
multstore:
pushq %rbx
movq %rdx, %rbx
call mult2
movq %rax, (%rbx)
popq %rbx
ret
实际上可以直接对编译过的目标文件在linux下使用反汇编器来得到格式良好的汇编代码, 执行objdump -d mstore.o >> assem.txt, 然后把这个文件拿到windows底下来查看:
mstore.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <multstore>:
0: 53 push %rbx
1: 48 89 d3 mov %rdx,%rbx
4: e8 00 00 00 00 callq 9 <multstore+0x9>
9: 48 89 03 mov %rax,(%rbx)
c: 5b pop %rbx
d: c3 retq
前边是转换程序的说明, 这是一个elf64, 即linux下的64位可执行文件. 关键是下边的几行, 可以看到这个目标文件里在callq的地方留下了函数的名称, 这是未来链接器要替换成地址的地方.
再来继续试验,编写一个使用这个函数的main函数,来实际生成链接后的可执行文件:
#include <stdio.h>
void multstore(long ,long, long *);
int main()
{
long d;
multstore(2, 3, &d);
printf("2 * 3 -> %ld\n", d);
return 0;
}
long mult2(long a, long b){
long s = a * b;
return s;
}
然后与刚才的mstore.c放到一起,编译成可执行文件: gcc -Og -o prog main.c mstore.c.
然后对可执行文件执行反汇编: objdump -d prog >> prog.txt, 文件内容如下:
prog: file format elf64-x86-64
Disassembly of section .init:
00000000004003c8 <_init>:
4003c8: 48 83 ec 08 sub $0x8,%rsp
4003cc: 48 8b 05 25 0c 20 00 mov 0x200c25(%rip),%rax # 600ff8 <__gmon_start__>
4003d3: 48 85 c0 test %rax,%rax
4003d6: 74 05 je 4003dd <_init+0x15>
4003d8: e8 43 00 00 00 callq 400420 <.plt.got>
4003dd: 48 83 c4 08 add $0x8,%rsp
4003e1: c3 retq
Disassembly of section .plt:
00000000004003f0 <.plt>:
4003f0: ff 35 12 0c 20 00 pushq 0x200c12(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
4003f6: ff 25 14 0c 20 00 jmpq *0x200c14(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
4003fc: 0f 1f 40 00 nopl 0x0(%rax)
0000000000400400 <printf@plt>:
400400: ff 25 12 0c 20 00 jmpq *0x200c12(%rip) # 601018 <printf@GLIBC_2.2.5>
400406: 68 00 00 00 00 pushq $0x0
40040b: e9 e0 ff ff ff jmpq 4003f0 <.plt>
0000000000400410 <__libc_start_main@plt>:
400410: ff 25 0a 0c 20 00 jmpq *0x200c0a(%rip) # 601020 <__libc_start_main@GLIBC_2.2.5>
400416: 68 01 00 00 00 pushq $0x1
40041b: e9 d0 ff ff ff jmpq 4003f0 <.plt>
Disassembly of section .plt.got:
0000000000400420 <.plt.got>:
400420: ff 25 d2 0b 20 00 jmpq *0x200bd2(%rip) # 600ff8 <__gmon_start__>
400426: 66 90 xchg %ax,%ax
Disassembly of section .text:
0000000000400430 <_start>:
400430: 31 ed xor %ebp,%ebp
400432: 49 89 d1 mov %rdx,%r9
400435: 5e pop %rsi
400436: 48 89 e2 mov %rsp,%rdx
400439: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
40043d: 50 push %rax
40043e: 54 push %rsp
40043f: 49 c7 c0 e0 05 40 00 mov $0x4005e0,%r8
400446: 48 c7 c1 70 05 40 00 mov $0x400570,%rcx
40044d: 48 c7 c7 1d 05 40 00 mov $0x40051d,%rdi
400454: e8 b7 ff ff ff callq 400410 <__libc_start_main@plt>
400459: f4 hlt
40045a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
0000000000400460 <deregister_tm_clones>:
400460: b8 37 10 60 00 mov $0x601037,%eax
400465: 55 push %rbp
400466: 48 2d 30 10 60 00 sub $0x601030,%rax
40046c: 48 83 f8 0e cmp $0xe,%rax
400470: 48 89 e5 mov %rsp,%rbp
400473: 77 02 ja 400477 <deregister_tm_clones+0x17>
400475: 5d pop %rbp
400476: c3 retq
400477: b8 00 00 00 00 mov $0x0,%eax
40047c: 48 85 c0 test %rax,%rax
40047f: 74 f4 je 400475 <deregister_tm_clones+0x15>
400481: 5d pop %rbp
400482: bf 30 10 60 00 mov $0x601030,%edi
400487: ff e0 jmpq *%rax
400489: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000400490 <register_tm_clones>:
400490: b8 30 10 60 00 mov $0x601030,%eax
400495: 55 push %rbp
400496: 48 2d 30 10 60 00 sub $0x601030,%rax
40049c: 48 c1 f8 03 sar $0x3,%rax
4004a0: 48 89 e5 mov %rsp,%rbp
4004a3: 48 89 c2 mov %rax,%rdx
4004a6: 48 c1 ea 3f shr $0x3f,%rdx
4004aa: 48 01 d0 add %rdx,%rax
4004ad: 48 d1 f8 sar %rax
4004b0: 75 02 jne 4004b4 <register_tm_clones+0x24>
4004b2: 5d pop %rbp
4004b3: c3 retq
4004b4: ba 00 00 00 00 mov $0x0,%edx
4004b9: 48 85 d2 test %rdx,%rdx
4004bc: 74 f4 je 4004b2 <register_tm_clones+0x22>
4004be: 5d pop %rbp
4004bf: 48 89 c6 mov %rax,%rsi
4004c2: bf 30 10 60 00 mov $0x601030,%edi
4004c7: ff e2 jmpq *%rdx
4004c9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000004004d0 <__do_global_dtors_aux>:
4004d0: 80 3d 55 0b 20 00 00 cmpb $0x0,0x200b55(%rip) # 60102c <_edata>
4004d7: 75 11 jne 4004ea <__do_global_dtors_aux+0x1a>
4004d9: 55 push %rbp
4004da: 48 89 e5 mov %rsp,%rbp
4004dd: e8 7e ff ff ff callq 400460 <deregister_tm_clones>
4004e2: 5d pop %rbp
4004e3: c6 05 42 0b 20 00 01 movb $0x1,0x200b42(%rip) # 60102c <_edata>
4004ea: f3 c3 repz retq
4004ec: 0f 1f 40 00 nopl 0x0(%rax)
00000000004004f0 <frame_dummy>:
4004f0: 48 83 3d 28 09 20 00 cmpq $0x0,0x200928(%rip) # 600e20 <__JCR_END__>
4004f7: 00
4004f8: 74 1e je 400518 <frame_dummy+0x28>
4004fa: b8 00 00 00 00 mov $0x0,%eax
4004ff: 48 85 c0 test %rax,%rax
400502: 74 14 je 400518 <frame_dummy+0x28>
400504: 55 push %rbp
400505: bf 20 0e 60 00 mov $0x600e20,%edi
40050a: 48 89 e5 mov %rsp,%rbp
40050d: ff d0 callq *%rax
40050f: 5d pop %rbp
400510: e9 7b ff ff ff jmpq 400490 <register_tm_clones>
400515: 0f 1f 00 nopl (%rax)
400518: e9 73 ff ff ff jmpq 400490 <register_tm_clones>
000000000040051d <main>:
40051d: 48 83 ec 18 sub $0x18,%rsp
400521: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
400526: be 03 00 00 00 mov $0x3,%esi
40052b: bf 02 00 00 00 mov $0x2,%edi
400530: e8 26 00 00 00 callq 40055b <multstore>
400535: 48 8b 74 24 08 mov 0x8(%rsp),%rsi
40053a: bf 00 06 40 00 mov $0x400600,%edi
40053f: b8 00 00 00 00 mov $0x0,%eax
400544: e8 b7 fe ff ff callq 400400 <printf@plt>
400549: b8 00 00 00 00 mov $0x0,%eax
40054e: 48 83 c4 18 add $0x18,%rsp
400552: c3 retq
0000000000400553 <mult2>:
400553: 48 89 f8 mov %rdi,%rax
400556: 48 0f af c6 imul %rsi,%rax
40055a: c3 retq
000000000040055b <multstore>:
40055b: 53 push %rbx
40055c: 48 89 d3 mov %rdx,%rbx
40055f: e8 ef ff ff ff callq 400553 <mult2>
400564: 48 89 03 mov %rax,(%rbx)
400567: 5b pop %rbx
400568: c3 retq
400569: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000400570 <__libc_csu_init>:
400570: 41 57 push %r15
400572: 41 89 ff mov %edi,%r15d
400575: 41 56 push %r14
400577: 49 89 f6 mov %rsi,%r14
40057a: 41 55 push %r13
40057c: 49 89 d5 mov %rdx,%r13
40057f: 41 54 push %r12
400581: 4c 8d 25 88 08 20 00 lea 0x200888(%rip),%r12 # 600e10 <__frame_dummy_init_array_entry>
400588: 55 push %rbp
400589: 48 8d 2d 88 08 20 00 lea 0x200888(%rip),%rbp # 600e18 <__init_array_end>
400590: 53 push %rbx
400591: 4c 29 e5 sub %r12,%rbp
400594: 31 db xor %ebx,%ebx
400596: 48 c1 fd 03 sar $0x3,%rbp
40059a: 48 83 ec 08 sub $0x8,%rsp
40059e: e8 25 fe ff ff callq 4003c8 <_init>
4005a3: 48 85 ed test %rbp,%rbp
4005a6: 74 1e je 4005c6 <__libc_csu_init+0x56>
4005a8: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
4005af: 00
4005b0: 4c 89 ea mov %r13,%rdx
4005b3: 4c 89 f6 mov %r14,%rsi
4005b6: 44 89 ff mov %r15d,%edi
4005b9: 41 ff 14 dc callq *(%r12,%rbx,8)
4005bd: 48 83 c3 01 add $0x1,%rbx
4005c1: 48 39 eb cmp %rbp,%rbx
4005c4: 75 ea jne 4005b0 <__libc_csu_init+0x40>
4005c6: 48 83 c4 08 add $0x8,%rsp
4005ca: 5b pop %rbx
4005cb: 5d pop %rbp
4005cc: 41 5c pop %r12
4005ce: 41 5d pop %r13
4005d0: 41 5e pop %r14
4005d2: 41 5f pop %r15
4005d4: c3 retq
4005d5: 90 nop
4005d6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
4005dd: 00 00 00
00000000004005e0 <__libc_csu_fini>:
4005e0: f3 c3 repz retq
Disassembly of section .fini:
00000000004005e4 <_fini>:
4005e4: 48 83 ec 08 sub $0x8,%rsp
4005e8: 48 83 c4 08 add $0x8,%rsp
4005ec: c3 retq
现在肯定看不懂, 不过其中有这段:
000000000040055b <multstore>:
40055b: 53 push %rbx
40055c: 48 89 d3 mov %rdx,%rbx
40055f: e8 ef ff ff ff callq 400553 <mult2>
400564: 48 89 03 mov %rax,(%rbx)
400567: 5b pop %rbx
400568: c3 retq
400569: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000400553 <mult2>:
400553: 48 89 f8 mov %rdi,%rax
400556: 48 0f af c6 imul %rsi,%rax
40055a: c3 retq
可以看到, 链接器把调用函数的地址换成了实际的mult2的地址400553. 其他的等学完这一章, 估计付出一些头发的代价就可以看懂了.
X86体系中的寄存器
X86体系是从16位结构中扩展而来, 所以在汇编里, word表示16位数据类型. 称32位为双字 double words, 64位为四字 quad words.
C语言的基本数据类型对应的汇编代码后缀如下:
数据类型 |
Intel 数据类型 |
汇编代码后缀 |
字节大小 |
char |
字节 |
b |
1 |
short |
字 |
w |
2 |
int |
双字 |
l |
4 |
long |
四字 |
q |
8 |
char* |
指针 |
q |
8 |
float |
单精度 |
s |
4 |
double |
双精度 |
l |
8 |
这里的汇编代码后缀表示汇编代码的指令会有变种, 指示了操作数的大小, 比如 movb是传送一个字节, movq是传送64位 . 还需要注意后缀l同时表示了long和double,这不会有问题,因为浮点数使用的指令和寄存器完全不同.
再来看存储整数数据和指针的寄存器, 每个CPU都包含16个64位的寄存器,而且有着特定的名称.由于寄存器的演化, 一个寄存器实际上可以拆分成不同长度的寄存器,这就可以兼容原来的指令.这些寄存器如下:
完整长度寄存器名称 |
2字寄存器 |
单字寄存器 |
低位字节寄存器 |
用途 |
%rax |
%eax |
%ax |
%al |
返回值 |
%rbx |
%ebx |
%bx |
%bl |
被调用者保存 |
%rcx |
%ecx |
%cx |
%cl |
第四个参数 |
%rdx |
%edx |
%dx |
%dl |
第三个参数 |
%rsi |
%esi |
%si |
%sil |
第二个参数 |
%rdi |
%edi |
%di |
%dil |
第一个参数 |
%rbp |
%ebp |
%bp |
%bpl |
被调用者保存 |
%rsp |
%esp |
%sp |
%spl |
栈指针 |
%r8 |
%r8d |
%r8w |
%r8b |
第五个参数 |
%r9 |
%r9d |
%r9w |
%r9b |
第六个参数 |
%r10 |
%r10d |
%r10w |
%r10b |
调用者保存 |
%r11 |
%r11d |
%r11w |
%r11b |
调用者保存 |
%r12 |
%r12d |
%r12w |
%r12b |
被调用者保存 |
%r13 |
%r13d |
%r13w |
%r13b |
被调用者保存 |
%r14 |
%r14d |
%r14w |
%r14b |
被调用者保存 |
%r15 |
%r15d |
%r15w |
%r15b |
被调用者保存 |
可以看到,从%r8开始的寄存器命名都比较规律,这是因为这一部分是后来扩展出来的64位寄存器,而之前的8个寄存器还是采用了和过去的名称同类的名称.不同长度的指令,实际上是操作对应长度的低位开始的寄存器.
如果一个命令操作小于64位寄存器, 如果指令生成的数字为1或者2字节,则寄存器的高位不会变化,如果生成的数字是4字节,则会将寄存器的高位全部置0.
除了%rsp这个特别的栈指针之外,其他的寄存器使用比较灵活,但有一组编程规范或者说使用这些寄存器的套路.
之后就来看看汇编指令的基础,就是指令与对应的操作数的关系.
汇编指令的操作数
操作数可以分为三类:
- 立即值, 用来表示常数, 用$开头.可以直接写整数,汇编器会将其编译成对应的二进制格式.
- 寄存器, 即寄存器的名称, 代表某个寄存器内部的值.
- 内存引用, 代表某个内存地址的值, 也就是寻址或者说是取地址运算, 用括号包起来一个值, 这个值可以是常量, 取得的值或者是计算所得.
寻址的模式,最通用的模式是 计算出的内存地址 = 立即数偏移量 + 基址寄存器 + 变址寄存器 * 比例因子s
. 其中s可能为1,2,4或8.
其他寻址模式都是这种通用模式的简化,省略了某些部分.在引用了复杂的数据结构的时候, 就会碰到复杂的寻址模式.
练习3.1 取值
下列内存地址和寄存器存放了值:
地址 |
值 |
内存地址 0x100 |
0xFF |
内存地址 0x104 |
0xAB |
内存地址 0x108 |
0x13 |
内存地址 0x10C |
0x11 |
%rax |
0x100 |
%rcx |
0x1 |
%rdx |
0x3 |
填一下下列操作数的值:
操作数 |
值 |
%rax |
寄存器名称就是值,即 0x100 |
0x104 |
不是常数也不是寄存器,表示寻绝对地址,结果是0xAB |
$0x108 |
常数 0x108 |
(%rax) |
将%rax寄存器中的内容当做地址寻址,结果是 0xFF |
4(%rax) |
表示基址+偏移量地址,为 %rax中的 0x100地址加4,即 0x104地址,结果为0xAB |
9(%rax, %rdx) |
表示变址寻址, 为 9+ 0x100 + 0x3 = 0x10C, 所以值是0x11 |
260(%rcx, %rdx) |
计算后的地址是 260 + 1 + 3 = 0x108, 值是0x13 |
0xFC(,%rcx,4) |
这是比例变址寻址,结果是 0xFC + 4 = 0x100, 值是 0xFF |
(%rax, %rdx, 4) |
这也是比例变址寻址,计算后的内存是 0x100 + 4*3 = 0x10C,值是0x11 |
其实这里理解也很简单, 操作数只要不是寄存器的名称,或者是以$开头的常数,全部代表寻址.
CSAPP的汇编指令采取的是ATT的写法, 即 指令 源操作数 目标操作数. 知道了这些, 就可以来看汇编指令了.