7.2 X64
x86-64架构下有点不同,函数参数(4或6)使用寄存器传递,被调用函数通过访问寄存器来访问传递进来的参数。
7.2.1 MSVC
MSVC优化后:
Listing 7.4: MSVC 2012 /Ox x64
$SG2997 DB ’%d’, 0aH, 00H
main PROC
sub rsp, 40
mov edx, 2
lea r8d, QWORD PTR [rdx+1] ; R8D=3
lea ecx, QWORD PTR [rdx-1] ; ECX=1
call f
lea rcx, OFFSET FLAT:$SG2997 ; ’%d’
mov edx, eax
call printf
xor eax, eax
add rsp, 40
ret 0
main ENDP
f PROC
; ECX - 1st argument
; EDX - 2nd argument
; R8D - 3rd argument
imul ecx, edx
lea eax, DWORD PTR [r8+rcx]
ret 0
f ENDP
我们可以看到函数f()直接使用寄存器来操作参数,LEA指令用来做加法,编译器认为使用LEA比使用ADD指令要更快。在mian()中也使用了LEA指令,编译器认为使用LEA比使用MOV指令效率更高。
我们来看看MSVC没有优化的情况:
Listing 7.5: MSVC 2012 x64
f proc near
; shadow space:
arg_0 = dword ptr 8
arg_8 = dword ptr 10h
arg_10 = dword ptr 18h
; ECX - 1st argument
; EDX - 2nd argument
; R8D - 3rd argument
mov [rsp+arg_10], r8d
mov [rsp+arg_8], edx
mov [rsp+arg_0], ecx
mov eax, [rsp+arg_0]
imul eax, [rsp+arg_8]
add eax, [rsp+arg_10]
retn
f endp
main proc near
sub rsp, 28h
mov r8d, 3 ; 3rd argument
mov edx, 2 ; 2nd argument
mov ecx, 1 ; 1st argument
call f
mov edx, eax
lea rcx, $SG2931 ; "%d
"
call printf
; return 0
xor eax, eax
add rsp, 28h
retn
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
f:
; EDI - 1st argument
; ESI - 2nd argument
; EDX - 3rd argument
imul esi, edi
lea eax, [rdx+rsi]
ret
main:
sub rsp, 8
mov edx, 3
mov esi, 2
mov edi, 1
call f
mov edi, OFFSET FLAT:.LC0 ; "%d
"
mov esi, eax
xor eax, eax ; number of vector registers passed
call printf
xor eax, eax
add rsp, 8
ret
GCC无优化代码:
Listing 7.7: GCC 4.4.6 x64
f:
; EDI - 1st argument
; ESI - 2nd argument
; EDX - 3rd argument
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov DWORD PTR [rbp-12], edx
mov eax, DWORD PTR [rbp-4]
imul eax, DWORD PTR [rbp-8]
add eax, DWORD PTR [rbp-12]
leave
ret
main:
push rbp
mov rbp, rsp
mov edx, 3
mov esi, 2
mov edi, 1
call f
mov edx, eax
mov eax, OFFSET FLAT:.LC0 ; "%d
"
mov esi, edx
mov rdi, rax
mov eax, 0 ; number of vector registers passed
call printf
mov eax, 0
leave
ret
System V *NIX [21]没有“shadow space”,但被调用者可能会保存参数,这也是造成寄存器短缺的原因。
7.2.3 GCC: uint64_t instead int
我们例子使用的是32位int,寄存器也为32位寄存器(前缀为E-)。
为处理64位数值内部会自动调整为64位寄存器:
#include <stdio.h>
#include <stdint.h>
uint64_t f (uint64_t a, uint64_t b, uint64_t c)
{
return a*b+c;
};
int main()
{
printf ("%lld
", f(0x1122334455667788,0x1111111122222222,0x3333333344444444));
return 0;
};
Listing 7.8: GCC 4.4.6 -O3 x64
f proc near
imul rsi, rdi
lea rax, [rdx+rsi]
retn
f endp
main proc near
sub rsp, 8
mov rdx, 3333333344444444h ; 3rd argument
mov rsi, 1111111122222222h ; 2nd argument
mov rdi, 1122334455667788h ; 1st argument
call f
mov edi, offset format ; "%lld
"
mov rsi, rax
xor eax, eax ; number of vector registers passed
call _printf
xor eax, eax
add rsp, 8
retn
main endp
代码非常相似,只是使用了64位寄存器(前缀为R)。
当前内容版权归 Dennis Yurichev 或其关联方所有,如需对内容或内容相关联开源项目进行关注与资助,请访问 Dennis Yurichev .