7.2 X64

x86-64架构下有点不同,函数参数(4或6)使用寄存器传递,被调用函数通过访问寄存器来访问传递进来的参数。

7.2.1 MSVC

MSVC优化后:

Listing 7.4: MSVC 2012 /Ox x64

  1. $SG2997 DB ’%d’, 0aH, 00H
  2. main PROC
  3. sub rsp, 40
  4. mov edx, 2
  5. lea r8d, QWORD PTR [rdx+1] ; R8D=3
  6. lea ecx, QWORD PTR [rdx-1] ; ECX=1
  7. call f
  8. lea rcx, OFFSET FLAT:$SG2997 ; ’%d
  9. mov edx, eax
  10. call printf
  11. xor eax, eax
  12. add rsp, 40
  13. ret 0
  14. main ENDP
  15. f PROC
  16. ; ECX - 1st argument
  17. ; EDX - 2nd argument
  18. ; R8D - 3rd argument
  19. imul ecx, edx
  20. lea eax, DWORD PTR [r8+rcx]
  21. ret 0
  22. f ENDP

我们可以看到函数f()直接使用寄存器来操作参数,LEA指令用来做加法,编译器认为使用LEA比使用ADD指令要更快。在mian()中也使用了LEA指令,编译器认为使用LEA比使用MOV指令效率更高。

我们来看看MSVC没有优化的情况:

Listing 7.5: MSVC 2012 x64

  1. f proc near
  2. ; shadow space:
  3. arg_0 = dword ptr 8
  4. arg_8 = dword ptr 10h
  5. arg_10 = dword ptr 18h
  6. ; ECX - 1st argument
  7. ; EDX - 2nd argument
  8. ; R8D - 3rd argument
  9. mov [rsp+arg_10], r8d
  10. mov [rsp+arg_8], edx
  11. mov [rsp+arg_0], ecx
  12. mov eax, [rsp+arg_0]
  13. imul eax, [rsp+arg_8]
  14. add eax, [rsp+arg_10]
  15. retn
  16. f endp
  17. main proc near
  18. sub rsp, 28h
  19. mov r8d, 3 ; 3rd argument
  20. mov edx, 2 ; 2nd argument
  21. mov ecx, 1 ; 1st argument
  22. call f
  23. mov edx, eax
  24. lea rcx, $SG2931 ; "%d
  25. "
  26. call printf
  27. ; return 0
  28. xor eax, eax
  29. add rsp, 28h
  30. retn
  31. main endp

这里从寄存器传递进来的3个参数因为某种情况又被保存到栈里。这就是所谓的“shadow space”2:每个Win64通常(不是必需)会保存所有4个寄存器的值。这样做由两个原因:1)为输入参数分配所有寄存器(即使是4个)太浪费,所以要通过堆栈来访问;2)每次中断下来调试器总是能够定位函数参数3。

调用者负责在栈中分配“shadow space”。

7.2.2 GCC

GCC优化后的代码:

Listing 7.6: GCC 4.4.6 -O3 x64

  1. f:
  2. ; EDI - 1st argument
  3. ; ESI - 2nd argument
  4. ; EDX - 3rd argument
  5. imul esi, edi
  6. lea eax, [rdx+rsi]
  7. ret
  8. main:
  9. sub rsp, 8
  10. mov edx, 3
  11. mov esi, 2
  12. mov edi, 1
  13. call f
  14. mov edi, OFFSET FLAT:.LC0 ; "%d
  15. "
  16. mov esi, eax
  17. xor eax, eax ; number of vector registers passed
  18. call printf
  19. xor eax, eax
  20. add rsp, 8
  21. ret

GCC无优化代码:

Listing 7.7: GCC 4.4.6 x64

  1. f:
  2. ; EDI - 1st argument
  3. ; ESI - 2nd argument
  4. ; EDX - 3rd argument
  5. push rbp
  6. mov rbp, rsp
  7. mov DWORD PTR [rbp-4], edi
  8. mov DWORD PTR [rbp-8], esi
  9. mov DWORD PTR [rbp-12], edx
  10. mov eax, DWORD PTR [rbp-4]
  11. imul eax, DWORD PTR [rbp-8]
  12. add eax, DWORD PTR [rbp-12]
  13. leave
  14. ret
  15. main:
  16. push rbp
  17. mov rbp, rsp
  18. mov edx, 3
  19. mov esi, 2
  20. mov edi, 1
  21. call f
  22. mov edx, eax
  23. mov eax, OFFSET FLAT:.LC0 ; "%d
  24. "
  25. mov esi, edx
  26. mov rdi, rax
  27. mov eax, 0 ; number of vector registers passed
  28. call printf
  29. mov eax, 0
  30. leave
  31. ret

System V *NIX [21]没有“shadow space”,但被调用者可能会保存参数,这也是造成寄存器短缺的原因。

7.2.3 GCC: uint64_t instead int

我们例子使用的是32位int,寄存器也为32位寄存器(前缀为E-)。

为处理64位数值内部会自动调整为64位寄存器:

  1. #include <stdio.h>
  2. #include <stdint.h>
  3. uint64_t f (uint64_t a, uint64_t b, uint64_t c)
  4. {
  5. return a*b+c;
  6. };
  7. int main()
  8. {
  9. printf ("%lld
  10. ", f(0x1122334455667788,0x1111111122222222,0x3333333344444444));
  11. return 0;
  12. };

Listing 7.8: GCC 4.4.6 -O3 x64

  1. f proc near
  2. imul rsi, rdi
  3. lea rax, [rdx+rsi]
  4. retn
  5. f endp
  6. main proc near
  7. sub rsp, 8
  8. mov rdx, 3333333344444444h ; 3rd argument
  9. mov rsi, 1111111122222222h ; 2nd argument
  10. mov rdi, 1122334455667788h ; 1st argument
  11. call f
  12. mov edi, offset format ; "%lld
  13. "
  14. mov rsi, rax
  15. xor eax, eax ; number of vector registers passed
  16. call _printf
  17. xor eax, eax
  18. add rsp, 8
  19. retn
  20. main endp

代码非常相似,只是使用了64位寄存器(前缀为R)。