获取源程序对应的汇编程序

如果想要获取自己编写的源程序对应的汇编程序,基本步骤是:

  1. 将自己的文件用GCC转换成目标代码
  2. 使用反汇编器得到格式良好的汇编代码

其中第一步, 也可以直接使用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这个特别的栈指针之外,其他的寄存器使用比较灵活,但有一组编程规范或者说使用这些寄存器的套路.

之后就来看看汇编指令的基础,就是指令与对应的操作数的关系.

汇编指令的操作数

操作数可以分为三类:

  1. 立即值, 用来表示常数, 用$开头.可以直接写整数,汇编器会将其编译成对应的二进制格式.
  2. 寄存器, 即寄存器的名称, 代表某个寄存器内部的值.
  3. 内存引用, 代表某个内存地址的值, 也就是寻址或者说是取地址运算, 用括号包起来一个值, 这个值可以是常量, 取得的值或者是计算所得.

寻址的模式,最通用的模式是 计算出的内存地址 = 立即数偏移量 + 基址寄存器 + 变址寄存器 * 比例因子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的写法, 即 指令 源操作数 目标操作数. 知道了这些, 就可以来看汇编指令了.