5.2 x64: 8个参数

为了看其他参数如何通过栈传递的,我们再次修改代码将参数个数增加到9个(printf()格式化字符串和8个int 变量)

  1. #include <stdio.h>
  2. int main() {
  3. printf("a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
  4. ", 1, 2, 3, 4, 5, 6, 7, 8);
  5. return 0;
  6. };

5.2.1 MSVC

正如我们之前所见,在win64下开始的4个参数传递至RCX,RDX,R8,R9寄存器,

然而 MOV指令,替代PUSH指令。用来准备栈数据,所以值都是直接写入栈中

  1. $SG2923 DB a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d’, 0aH, 00H
  2. main PROC
  3. sub rsp, 88
  4. mov DWORD PTR [rsp+64], 8
  5. mov DWORD PTR [rsp+56], 7
  6. mov DWORD PTR [rsp+48], 6
  7. mov DWORD PTR [rsp+40], 5
  8. mov DWORD PTR [rsp+32], 4
  9. mov r9d, 3
  10. mov r8d, 2
  11. mov edx, 1
  12. lea rcx, OFFSET FLAT:$SG2923
  13. call printf
  14. ; return 0
  15. xor eax, eax
  16. add rsp, 88
  17. ret 0
  18. main ENDP
  19. _TEXT ENDS
  20. END

表5.2:msvc 2010 x64

5.2.2 GCC

在*NIX系统,对于x86-64这也是同样的原理,除了前6个参数传递给了RDI,RSI,RDX,RCX,R8,R9寄存器。GCC将生成的代码字符指针写入了EDI而不是RDI(如果有的话)——我们在2.2.2节看到过这部分

同样我们也看到在寄存器EAX被清零前有个printf() call:

  1. .LC0:
  2. .string "a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
  3. "
  4. main:
  5. sub rsp, 40
  6. mov r9d, 5
  7. mov r8d, 4
  8. mov ecx, 3
  9. mov edx, 2
  10. mov esi, 1
  11. mov edi, OFFSET FLAT:.LC0
  12. xor eax, eax ; number of vector registers passed
  13. mov DWORD PTR [rsp+16], 8
  14. mov DWORD PTR [rsp+8], 7
  15. mov DWORD PTR [rsp], 6
  16. call printf
  17. ; return 0
  18. xor eax, eax
  19. add rsp, 40
  20. ret

表5.3:GCC 4.4.6 –o 3 x64

5.2.3 GCC + GDB

让我们在GDB中尝试这个例子。

$ gcc -g 2.c -o 2

反编译:

  1. $ gdb 2
  2. GNU gdb (GDB) 7.6.1-ubuntu
  3. Copyright (C) 2013 Free Software Foundation, Inc.
  4. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
  5. This is free software: you are free to change and redistribute it.
  6. There is NO WARRANTY, to the extent permitted by law. Type "show copying"
  7. and "show warranty" for details.
  8. This GDB was configured as "x86_64-linux-gnu".
  9. For bug reporting instructions, please see:
  10. <http://www.gnu.org/software/gdb/bugs/>...
  11. Reading symbols from /home/dennis/polygon/2...done.

表5.4:在printf()处下断点,然后run

  1. (gdb) b printf
  2. Breakpoint 1 at 0x400410
  3. (gdb) run
  4. Starting program: /home/dennis/polygon/2
  5. Breakpoint 1, __printf (format=0x400628 "a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
  6. ") at
  7. printf.c:29
  8. 29 printf.c: No such file or directory.

寄存器RSI/RDX/RCX/R8/R9都有应有的值,RIP则是printf()函数地址

  1. (gdb) info registers
  2. rax 0x0 0
  3. rbx 0x0 0
  4. rcx 0x3 3
  5. rdx 0x2 2
  6. rsi 0x1 1
  7. rdi 0x400628 4195880
  8. rbp 0x7fffffffdf60 0x7fffffffdf60
  9. rsp 0x7fffffffdf38 0x7fffffffdf38
  10. r8 0x4 4
  11. r9 0x5 5
  12. r10 0x7fffffffdce0 140737488346336
  13. r11 0x7ffff7a65f60 140737348263776
  14. r12 0x400440 4195392
  15. r13 0x7fffffffe040 140737488347200
  16. r14 0x0 0
  17. r15 0x0 0
  18. rip 0x7ffff7a65f60 0x7ffff7a65f60 <__printf>
  19. ...

表5.5 检查格式化字符串

  1. (gdb) x/s $rdi
  2. 0x400628: "a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
  3. "

用x/g命令显示栈内容

  1. (gdb) x/10g $rsp
  2. 0x7fffffffdf38: 0x0000000000400576 0x0000000000000006
  3. 0x7fffffffdf48: 0x0000000000000007 0x00007fff00000008
  4. 0x7fffffffdf58: 0x0000000000000000 0x0000000000000000
  5. 0x7fffffffdf68: 0x00007ffff7a33de5 0x0000000000000000
  6. 0x7fffffffdf78: 0x00007fffffffe048 0x0000000100000000

与之前一样,第一个栈元素是返回地址,我们也同时也看到在高32位的8也没有被清除。 0x00007fff00000008,这是因为是32位int类型的,因此,高寄存器或堆栈部分可能包含一些随机垃圾数值。

printf()函数执行之后将返回控制,GDB会显示整个main()函数。

  1. (gdb) set disassembly-flavor intel
  2. (gdb) disas 0x0000000000400576
  3. Dump of assembler code for function main:
  4. 0x000000000040052d <+0>: push rbp
  5. 0x000000000040052e <+1>: mov rbp,rsp
  6. 0x0000000000400531 <+4>: sub rsp,0x20
  7. 0x0000000000400535 <+8>: mov DWORD PTR [rsp+0x10],0x8
  8. 0x000000000040053d <+16>: mov DWORD PTR [rsp+0x8],0x7
  9. 0x0000000000400545 <+24>: mov DWORD PTR [rsp],0x6
  10. 0x000000000040054c <+31>: mov r9d,0x5
  11. 0x0000000000400552 <+37>: mov r8d,0x4
  12. 0x0000000000400558 <+43>: mov ecx,0x3
  13. 0x000000000040055d <+48>: mov edx,0x2
  14. 0x0000000000400562 <+53>: mov esi,0x1
  15. 0x0000000000400567 <+58>: mov edi,0x400628
  16. 0x000000000040056c <+63>: mov eax,0x0
  17. 0x0000000000400571 <+68>: call 0x400410 <printf@plt>
  18. 0x0000000000400576 <+73>: mov eax,0x0
  19. 0x000000000040057b <+78>: leave
  20. 0x000000000040057c <+79>: ret
  21. End of assembler dump.

执行完printf()后,就会清零EAX,然后发现EAX早已为0,RIP现在则指向LEAVE指令。

  1. (gdb) finish
  2. Run till exit from #0 __printf (format=0x400628 "a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
  3. n") at printf.c:29
  4. a=1; b=2; c=3; d=4; e=5; f=6; g=7; h=8
  5. main () at 2.c:6
  6. 6 return 0;
  7. Value returned is $1 = 39
  8. (gdb) next
  9. 7 };
  10. (gdb) info registers
  11. rax 0x0 0
  12. rbx 0x0 0
  13. rcx 0x26 38
  14. rdx 0x7ffff7dd59f0 140737351866864
  15. rsi 0x7fffffd9 2147483609
  16. rdi 0x0 0
  17. rbp 0x7fffffffdf60 0x7fffffffdf60
  18. rsp 0x7fffffffdf40 0x7fffffffdf40
  19. r8 0x7ffff7dd26a0 140737351853728
  20. r9 0x7ffff7a60134 140737348239668
  21. r10 0x7fffffffd5b0 140737488344496
  22. r11 0x7ffff7a95900 140737348458752
  23. r12 0x400440 4195392
  24. r13 0x7fffffffe040 140737488347200
  25. r14 0x0 0
  26. r15 0x0 0
  27. rip 0x40057b 0x40057b <main+78>
  28. ...