返回介绍

7.2 X64

发布于 2025-02-22 14:00:43 字数 4784 浏览 0 评论 0 收藏 0

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

7.2.1 MSVC

MSVC 优化后:

Listing 7.4: MSVC 2012 /Ox x64

#!bash
$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

#!bash
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

#!bash
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

#!bash
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 位寄存器:

#!cpp
#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

#!cpp
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)。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文