C---Go 調用約定的原理

什麼是調用約定?

在計算機科學中,調用約定是一種定義子過程從調用處接受參數以及返回結果的方法的約定

彙編調用約定

如下彙編代碼中,通過中斷 0x10 將 BP 指向的字符串輸出到顯示設備中,字符串長度儲存在 CX 中, CPU 處理中斷時會通過 CX、BP 等寄存器獲得調用參數,將字符串 Hello World 寫入顯存中,進而展示在屏幕上。

hello: DB "Hello World" ; store string

start:
MOV AH, 0x13            ; move BIOS interrupt number in AH
MOV CX, 11              ; move length of string in cx
MOV BX, 0               ; mov 0 to bx, so we can move it to es
MOV ES, BX              ; move segment start of string to es, 0
MOV BP, OFFSET hello    ; move start offset of string in bp
MOV DL, 0               ; start writing from col 0
int 0x10                ; BIOS interrupt

該彙編代碼使用的是 intel 語法,除此之外還有 AT&T 語法,Go 語言的 Plan 9 語法就和 AT&T 很相似。

// 逆向工程權威指南

在Intel語法中:<instruction> <destination operand> <source operand> 在AT&T語法中:<instruction> <source operand> <destination operand>

有一個理解它們的方法: 當你面對 intel 語法的時候,你可以想象把等號放到 2 個操作數中間,當面對 AT&T 語法的時候,你可以放一個右箭頭 (→)到兩個操作數之間。

AT&T: 在寄存器名之前需要寫一個百分號 (%) 並且在數字前面需要美元符($)。

高級語言調用約定

高級語言的函數調用約定區分於不同的語言 (C、Go)、不同的架構 (x86、x86_64)、不同的編譯器版本 (go1.16、go1.17),可能通過寄存器傳遞入參和出參,也可能通過棧傳遞。

在分析高級語言的調用約定時,不約而同地都需要將高級語言源碼編譯爲彙編語言,然後分析調用過程中的傳參特性。

本文主要分析 C/C++ 和 Go 語言的調用約定。C/C++ 語言更貼近底層,通過它來了解調用約定更清晰。

高級語言編譯彙編

C/C++ 編譯彙編

C 語言源代碼使用 gcc 編譯,C++ 源代碼則使用 g++ 編譯;g++ 就是 gcc 的 c++ 版本。

指令

編譯 1.cc 源文件,生成 1.s 彙編代碼。

gcc -m32 -S -fverbose-asm 1.cc -o 1.s

源碼

// 1.cc
int sum(int a, int b) {
    return a + b;
}

int main() {
    int c = sum(1, 2);
    return c;
}

彙編

彙編代碼 (32 位) 如下,使用 AT&T 語法。本文注重於彙編調用的過程,後續彙編代碼中無關內容不再呈現,例如:有很多宏 (用點開始) 可以忽略不看,大部分註釋可忽略。

    .file    "1.cc"
# GNU C++14 (Debian 10.2.1-6) version 10.2.1 20210110 (x86_64-linux-gnu)
#    compiled by GNU C version 10.2.1 20210110, GMP version 6.2.1, MPFR version 4.1.0, MPC version 1.2.0, isl version isl-0.23-GMP

# GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
# options passed:  -imultilib 32 -imultiarch i386-linux-gnu -D_GNU_SOURCE
# 1.cc -m32 -mtune=generic -march=i686 -auxbase-strip 1.s -fverbose-asm
# -fasynchronous-unwind-tables
# options enabled:  -fPIC -fPIE -faggressive-loop-optimizations
# -fallocation-dce -fasynchronous-unwind-tables -fauto-inc-dec
# -fdelete-null-pointer-checks -fdwarf2-cfi-asm -fearly-inlining
# -feliminate-unused-debug-symbols -feliminate-unused-debug-types
# -fexceptions -ffp-int-builtin-inexact -ffunction-cse -fgcse-lm
# -fgnu-unique -fident -finline-atomics -fipa-stack-alignment
# -fira-hoist-pressure -fira-share-save-slots -fira-share-spill-slots
# -fivopts -fkeep-static-consts -fleading-underscore -flifetime-dse
# -fmath-errno -fmerge-debug-strings -fpcc-struct-return -fpeephole -fplt
# -fprefetch-loop-arrays -fsched-critical-path-heuristic
# -fsched-dep-count-heuristic -fsched-group-heuristic -fsched-interblock
# -fsched-last-insn-heuristic -fsched-rank-heuristic -fsched-spec
# -fsched-spec-insn-heuristic -fsched-stalled-insns-dep -fschedule-fusion
# -fsemantic-interposition -fshow-column -fshrink-wrap-separate
# -fsigned-zeros -fsplit-ivs-in-unroller -fssa-backprop -fstdarg-opt
# -fstrict-volatile-bitfields -fsync-libcalls -ftrapping-math -ftree-cselim
# -ftree-forwprop -ftree-loop-if-convert -ftree-loop-im -ftree-loop-ivcanon
# -ftree-loop-optimize -ftree-parallelize-loops= -ftree-phiprop
# -ftree-reassoc -ftree-scev-cprop -funit-at-a-time -funwind-tables
# -fverbose-asm -fzero-initialized-in-bss -m32 -m80387 -m96bit-long-double
# -malign-stringops -mavx256-split-unaligned-load
# -mavx256-split-unaligned-store -mfancy-math-387 -mfp-ret-in-387 -mglibc
# -mieee-fp -mlong-double-80 -mno-red-zone -mno-sse4 -mpush-args -msahf
# -mstv -mtls-direct-seg-refs -mvzeroupper

    .text
    .globl    _Z3sumii
    .type    _Z3sumii, @function
_Z3sumii:
.LFB0:
    .cfi_startproc
    pushl    %ebp    #
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp    #,
    .cfi_def_cfa_register 5
    call    __x86.get_pc_thunk.ax    #
    addl    $_GLOBAL_OFFSET_TABLE_, %eax    # tmp82,
# 1.cc:3:     return a + b;
    movl    8(%ebp), %edx    # a, tmp85
    movl    12(%ebp), %eax    # b, tmp86
    addl    %edx, %eax    # tmp85, _3
# 1.cc:4: }
    popl    %ebp    #
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret    
    .cfi_endproc
.LFE0:
    .size    _Z3sumii, .-_Z3sumii
    .globl    main
    .type    main, @function
main:
.LFB1:
    .cfi_startproc
    pushl    %ebp    #
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp    #,
    .cfi_def_cfa_register 5
    subl    $16, %esp    #,
    call    __x86.get_pc_thunk.ax    #
    addl    $_GLOBAL_OFFSET_TABLE_, %eax    # tmp82,
# 1.cc:7:     int c = sum(1, 2);
    pushl    $2    #
    pushl    $1    #
    call    _Z3sumii    #
    addl    $8, %esp    #,
    movl    %eax, -4(%ebp)    # tmp85, c
# 1.cc:8:     return c;
    movl    -4(%ebp), %eax    # c, _4
# 1.cc:9: }
    leave    
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret    
    .cfi_endproc
.LFE1:
    .size    main, .-main
    .section    .text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat
    .globl    __x86.get_pc_thunk.ax
    .hidden    __x86.get_pc_thunk.ax
    .type    __x86.get_pc_thunk.ax, @function
__x86.get_pc_thunk.ax:
.LFB2:
    .cfi_startproc
    movl    (%esp), %eax    #,
    ret    
    .cfi_endproc
.LFE2:
    .ident    "GCC: (Debian 10.2.1-6) 10.2.1 20210110"
    .section    .note.GNU-stack,"",@progbits

Go 編譯彙編

指令

編譯 main.go 文件,生成彙編代碼:

> go tool compile -N -l -S main.go

其中:

源碼

main.go

package main

import "fmt"

func main() {
    c := add(1, 2)
    fmt.Println(c)
}
func add(a, b int) int {
    fmt.Printf("%p"&a)
    return a + b
}

彙編

編譯成的彙編較多,可通過參考中的 Go 彙編詳解 瞭解 Go 的彙編知識。

main.main STEXT size=188 args=0x0 locals=0x60 funcid=0x0 align=0x0
        0x0000 00000 (main.go:5)        TEXT    main.main(SB), ABIInternal, $96-0
        0x0000 00000 (main.go:5)        CMPQ    SP, 16(R14)
        0x0004 00004 (main.go:5)        PCDATA  $0$-2
        0x0004 00004 (main.go:5)        JLS     178
        0x000a 00010 (main.go:5)        PCDATA  $0$-1
        0x000a 00010 (main.go:5)        SUBQ    $96, SP
        0x000e 00014 (main.go:5)        MOVQ    BP, 88(SP)
        0x0013 00019 (main.go:5)        LEAQ    88(SP), BP
        0x0018 00024 (main.go:5)        FUNCDATA        $0, gclocals·J5F+7Qw7O7ve2QcWC7DpeQ==(SB)
        0x0018 00024 (main.go:5)        FUNCDATA        $1, gclocals·bDfKCdmtOiGIuJz/x+yQyQ==(SB)
        0x0018 00024 (main.go:5)        FUNCDATA        $2, main.main.stkobj(SB)
        0x0018 00024 (main.go:6)        MOVL    $1, AX
        0x001d 00029 (main.go:6)        MOVL    $2, BX
        0x0022 00034 (main.go:6)        PCDATA  $1$0
        0x0022 00034 (main.go:6)        CALL    main.add(SB)
        0x0027 00039 (main.go:6)        MOVQ    AX, main.c+24(SP)
        0x002c 00044 (main.go:7)        MOVUPS  X15, main..autotmp_1+48(SP)
        0x0032 00050 (main.go:7)        LEAQ    main..autotmp_1+48(SP), CX
        0x0037 00055 (main.go:7)        MOVQ    CX, main..autotmp_3+40(SP)
        0x003c 00060 (main.go:7)        MOVQ    main.c+24(SP), AX
        0x0041 00065 (main.go:7)        PCDATA  $1$1
        0x0041 00065 (main.go:7)        CALL    runtime.convT64(SB)
        0x0046 00070 (main.go:7)        MOVQ    AX, main..autotmp_4+32(SP)
        0x004b 00075 (main.go:7)        MOVQ    main..autotmp_3+40(SP), DI
        0x0050 00080 (main.go:7)        TESTB   AL, (DI)
        0x0052 00082 (main.go:7)        LEAQ    type.int(SB), CX
        0x0059 00089 (main.go:7)        MOVQ    CX, (DI)
        0x005c 00092 (main.go:7)        LEAQ    8(DI), CX
        0x0060 00096 (main.go:7)        PCDATA  $0$-2
        0x0060 00096 (main.go:7)        CMPL    runtime.writeBarrier(SB)$0
        0x0067 00103 (main.go:7)        JEQ     107
        0x0069 00105 (main.go:7)        JMP     113
        0x006b 00107 (main.go:7)        MOVQ    AX, 8(DI)
        0x006f 00111 (main.go:7)        JMP     123
        0x0071 00113 (main.go:7)        MOVQ    CX, DI
        0x0074 00116 (main.go:7)        CALL    runtime.gcWriteBarrier(SB)
        0x0079 00121 (main.go:7)        JMP     123
        0x007b 00123 (main.go:7)        PCDATA  $0$-1
        0x007b 00123 (main.go:7)        MOVQ    main..autotmp_3+40(SP), AX
        0x0080 00128 (main.go:7)        TESTB   AL, (AX)
        0x0082 00130 (main.go:7)        JMP     132
        0x0084 00132 (main.go:7)        MOVQ    AX, main..autotmp_2+64(SP)
        0x0089 00137 (main.go:7)        MOVQ    $1, main..autotmp_2+72(SP)
        0x0092 00146 (main.go:7)        MOVQ    $1, main..autotmp_2+80(SP)
        0x009b 00155 (main.go:7)        MOVL    $1, BX
        0x00a0 00160 (main.go:7)        MOVQ    BX, CX
        0x00a3 00163 (main.go:7)        PCDATA  $1$0
        0x00a3 00163 (main.go:7)        CALL    fmt.Println(SB)
        0x00a8 00168 (main.go:8)        MOVQ    88(SP), BP
        0x00ad 00173 (main.go:8)        ADDQ    $96, SP
        0x00b1 00177 (main.go:8)        RET
        0x00b2 00178 (main.go:8)        NOP
        0x00b2 00178 (main.go:5)        PCDATA  $1$-1
        0x00b2 00178 (main.go:5)        PCDATA  $0$-2
        0x00b2 00178 (main.go:5)        CALL    runtime.morestack_noctxt(SB)
        0x00b7 00183 (main.go:5)        PCDATA  $0$-1
        0x00b7 00183 (main.go:5)        JMP     0
        0x0000 49 3b 66 10 0f 86 a8 00 00 00 48 83 ec 60 48 89  I;f.......H..`H.
        0x0010 6c 24 58 48 8d 6c 24 58 b8 01 00 00 00 bb 02 00  l$XH.l$X........
        0x0020 00 00 e8 00 00 00 00 48 89 44 24 18 44 0f 11 7c  .......H.D$.D..|
        0x0030 24 30 48 8d 4c 24 30 48 89 4c 24 28 48 8b 44 24  $0H.L$0H.L$(H.D$
        0x0040 18 e8 00 00 00 00 48 89 44 24 20 48 8b 7c 24 28  ......H.D$ H.|$(
        0x0050 84 07 48 8d 0d 00 00 00 00 48 89 0f 48 8d 4f 08  ..H......H..H.O.
        0x0060 83 3d 00 00 00 00 00 74 02 eb 06 48 89 47 08 eb  .=.....t...H.G..
        0x0070 0a 48 89 cf e8 00 00 00 00 eb 00 48 8b 44 24 28  .H.........H.D$(
        0x0080 84 00 eb 00 48 89 44 24 40 48 c7 44 24 48 01 00  ....H.D$@H.D$H..
        0x0090 00 00 48 c7 44 24 50 01 00 00 00 bb 01 00 00 00  ..H.D$P.........
        0x00a0 48 89 d9 e8 00 00 00 00 48 8b 6c 24 58 48 83 c4  H.......H.l$XH..
        0x00b0 60 c3 e8 00 00 00 00 e9 44 ff ff ff              `.......D...
        rel 3+0 t=23 type.int+0
        rel 35+4 t=7 main.add+0
        rel 66+4 t=7 runtime.convT64+0
        rel 85+4 t=14 type.int+0
        rel 98+4 t=14 runtime.writeBarrier+-1
        rel 117+4 t=7 runtime.gcWriteBarrier+0
        rel 164+4 t=7 fmt.Println+0
        rel 179+4 t=7 runtime.morestack_noctxt+0
main.add STEXT size=244 args=0x10 locals=0x78 funcid=0x0 align=0x0
        0x0000 00000 (main.go:9)        TEXT    main.add(SB), ABIInternal, $120-16
        0x0000 00000 (main.go:9)        CMPQ    SP, 16(R14)
        0x0004 00004 (main.go:9)        PCDATA  $0$-2
        0x0004 00004 (main.go:9)        JLS     210
        0x000a 00010 (main.go:9)        PCDATA  $0$-1
        0x000a 00010 (main.go:9)        SUBQ    $120, SP
        0x000e 00014 (main.go:9)        MOVQ    BP, 112(SP)
        0x0013 00019 (main.go:9)        LEAQ    112(SP), BP
        0x0018 00024 (main.go:9)        FUNCDATA        $0, gclocals·J5F+7Qw7O7ve2QcWC7DpeQ==(SB)
        0x0018 00024 (main.go:9)        FUNCDATA        $1, gclocals·ojjeUbKZdec9+9S2K3feng==(SB)
        0x0018 00024 (main.go:9)        FUNCDATA        $2, main.add.stkobj(SB)
        0x0018 00024 (main.go:9)        FUNCDATA        $5, main.add.arginfo1(SB)
        0x0018 00024 (main.go:9)        MOVQ    AX, main.a+128(SP)
        0x0020 00032 (main.go:9)        MOVQ    BX, main.b+136(SP)
        0x0028 00040 (main.go:9)        MOVQ    $0, main.~r0+40(SP)
        0x0031 00049 (main.go:9)        LEAQ    type.int(SB), AX
        0x0038 00056 (main.go:9)        PCDATA  $1$0
        0x0038 00056 (main.go:9)        CALL    runtime.newobject(SB)
        0x003d 00061 (main.go:9)        MOVQ    AX, main.&a+64(SP)
        0x0042 00066 (main.go:9)        MOVQ    main.a+128(SP), CX
        0x004a 00074 (main.go:9)        MOVQ    CX, (AX)
        0x004d 00077 (main.go:10)       MOVQ    main.&a+64(SP), CX
        0x0052 00082 (main.go:10)       MOVQ    CX, main..autotmp_3+56(SP)
        0x0057 00087 (main.go:10)       MOVUPS  X15, main..autotmp_4+72(SP)
        0x005d 00093 (main.go:10)       LEAQ    main..autotmp_4+72(SP), CX
        0x0062 00098 (main.go:10)       MOVQ    CX, main..autotmp_6+48(SP)
        0x0067 00103 (main.go:10)       TESTB   AL, (CX)
        0x0069 00105 (main.go:10)       MOVQ    main..autotmp_3+56(SP), DX
        0x006e 00110 (main.go:10)       LEAQ    type.*int(SB), BX
        0x0075 00117 (main.go:10)       MOVQ    BX, main..autotmp_4+72(SP)
        0x007a 00122 (main.go:10)       MOVQ    DX, main..autotmp_4+80(SP)
        0x007f 00127 (main.go:10)       TESTB   AL, (CX)
        0x0081 00129 (main.go:10)       JMP     131
        0x0083 00131 (main.go:10)       MOVQ    CX, main..autotmp_5+88(SP)
        0x0088 00136 (main.go:10)       MOVQ    $1, main..autotmp_5+96(SP)
        0x0091 00145 (main.go:10)       MOVQ    $1, main..autotmp_5+104(SP)
        0x009a 00154 (main.go:10)       LEAQ    go.string."%p"(SB), AX
        0x00a1 00161 (main.go:10)       MOVL    $2, BX
        0x00a6 00166 (main.go:10)       MOVL    $1, DI
        0x00ab 00171 (main.go:10)       MOVQ    DI, SI
        0x00ae 00174 (main.go:10)       PCDATA  $1$1
        0x00ae 00174 (main.go:10)       CALL    fmt.Printf(SB)
        0x00b3 00179 (main.go:11)       MOVQ    main.&a+64(SP), DX
        0x00b8 00184 (main.go:11)       MOVQ    (DX), AX
        0x00bb 00187 (main.go:11)       ADDQ    main.b+136(SP), AX
        0x00c3 00195 (main.go:11)       MOVQ    AX, main.~r0+40(SP)
        0x00c8 00200 (main.go:11)       MOVQ    112(SP), BP
        0x00cd 00205 (main.go:11)       ADDQ    $120, SP
        0x00d1 00209 (main.go:11)       RET
        0x00d2 00210 (main.go:11)       NOP
        0x00d2 00210 (main.go:9)        PCDATA  $1$-1
        0x00d2 00210 (main.go:9)        PCDATA  $0$-2
        0x00d2 00210 (main.go:9)        MOVQ    AX, 8(SP)
        0x00d7 00215 (main.go:9)        MOVQ    BX, 16(SP)
        0x00dc 00220 (main.go:9)        NOP
        0x00e0 00224 (main.go:9)        CALL    runtime.morestack_noctxt(SB)
        0x00e5 00229 (main.go:9)        MOVQ    8(SP), AX
        0x00ea 00234 (main.go:9)        MOVQ    16(SP), BX
        0x00ef 00239 (main.go:9)        PCDATA  $0$-1
        0x00ef 00239 (main.go:9)        JMP     0
        0x0000 49 3b 66 10 0f 86 c8 00 00 00 48 83 ec 78 48 89  I;f.......H..xH.
        0x0010 6c 24 70 48 8d 6c 24 70 48 89 84 24 80 00 00 00  l$pH.l$pH..$....
        0x0020 48 89 9c 24 88 00 00 00 48 c7 44 24 28 00 00 00  H..$....H.D$(...
        0x0030 00 48 8d 05 00 00 00 00 e8 00 00 00 00 48 89 44  .H...........H.D
        0x0040 24 40 48 8b 8c 24 80 00 00 00 48 89 08 48 8b 4c  $@H..$....H..H.L
        0x0050 24 40 48 89 4c 24 38 44 0f 11 7c 24 48 48 8d 4c  $@H.L$8D..|$HH.L
        0x0060 24 48 48 89 4c 24 30 84 01 48 8b 54 24 38 48 8d  $HH.L$0..H.T$8H.
        0x0070 1d 00 00 00 00 48 89 5c 24 48 48 89 54 24 50 84  .....H.\$HH.T$P.
        0x0080 01 eb 00 48 89 4c 24 58 48 c7 44 24 60 01 00 00  ...H.L$XH.D$`...
        0x0090 00 48 c7 44 24 68 01 00 00 00 48 8d 05 00 00 00  .H.D$h....H.....
        0x00a0 00 bb 02 00 00 00 bf 01 00 00 00 48 89 fe e8 00  ...........H....
        0x00b0 00 00 00 48 8b 54 24 40 48 8b 02 48 03 84 24 88  ...H.T$@H..H..$.
        0x00c0 00 00 00 48 89 44 24 28 48 8b 6c 24 70 48 83 c4  ...H.D$(H.l$pH..
        0x00d0 78 c3 48 89 44 24 08 48 89 5c 24 10 0f 1f 40 00  x.H.D$.H.\$...@.
        0x00e0 e8 00 00 00 00 48 8b 44 24 08 48 8b 5c 24 10 e9  .....H.D$.H.\$..
        0x00f0 0c ff ff ff                                      ....
        rel 3+0 t=23 type.*int+0
        rel 52+4 t=14 type.int+0
        rel 57+4 t=7 runtime.newobject+0
        rel 113+4 t=14 type.*int+0
        rel 157+4 t=14 go.string."%p"+0
        rel 175+4 t=7 fmt.Printf+0
        rel 225+4 t=7 runtime.morestack_noctxt+0
go.cuinfo.producer.<unlinkable> SDWARFCUINFO dupok size=0
        0x0000 2d 4e 20 2d 6c 20 72 65 67 61 62 69              -N -l regabi
go.cuinfo.packagename.main SDWARFCUINFO dupok size=0
        0x0000 6d 61 69 6e                                      main
main..inittask SNOPTRDATA size=32
        0x0000 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00  ................
        0x0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        rel 24+8 t=1 fmt..inittask+0
go.string."%p" SRODATA dupok size=2
        0x0000 25 70                                            %p
runtime.nilinterequal·f SRODATA dupok size=8
        0x0000 00 00 00 00 00 00 00 00                          ........
        rel 0+8 t=1 runtime.nilinterequal+0
runtime.memequal64·f SRODATA dupok size=8
        0x0000 00 00 00 00 00 00 00 00                          ........
        rel 0+8 t=1 runtime.memequal64+0
runtime.gcbits.01 SRODATA dupok size=1
        0x0000 01                                               .
type..namedata.*interface {}- SRODATA dupok size=15
        0x0000 00 0d 2a 69 6e 74 65 72 66 61 63 65 20 7b 7d     ..*interface {}
type.*interface {} SRODATA dupok size=56
        0x0000 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00  ................
        0x0010 3b fc f8 8f 08 08 08 36 00 00 00 00 00 00 00 00  ;......6........
        0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0030 00 00 00 00 00 00 00 00                          ........
        rel 24+8 t=1 runtime.memequal64·f+0
        rel 32+8 t=1 runtime.gcbits.01+0
        rel 40+4 t=5 type..namedata.*interface {}-+0
        rel 48+8 t=1 type.interface {}+0
runtime.gcbits.02 SRODATA dupok size=1
        0x0000 02                                               .
type.interface {} SRODATA dupok size=80
        0x0000 10 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00  ................
        0x0010 39 7a 09 0f 02 08 08 14 00 00 00 00 00 00 00 00  9z..............
        0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        rel 24+8 t=1 runtime.nilinterequal·f+0
        rel 32+8 t=1 runtime.gcbits.02+0
        rel 40+4 t=5 type..namedata.*interface {}-+0
        rel 44+4 t=-32763 type.*interface {}+0
        rel 56+8 t=1 type.interface {}+80
type..namedata.*[]interface {}- SRODATA dupok size=17
        0x0000 00 0f 2a 5b 5d 69 6e 74 65 72 66 61 63 65 20 7b  ..*[]interface {
        0x0010 7d                                               }
type.*[]interface {} SRODATA dupok size=56
        0x0000 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00  ................
        0x0010 9d 9c 0e 59 08 08 08 36 00 00 00 00 00 00 00 00  ...Y...6........
        0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0030 00 00 00 00 00 00 00 00                          ........
        rel 24+8 t=1 runtime.memequal64·f+0
        rel 32+8 t=1 runtime.gcbits.01+0
        rel 40+4 t=5 type..namedata.*[]interface {}-+0
        rel 48+8 t=1 type.[]interface {}+0
type.[]interface {} SRODATA dupok size=56
        0x0000 18 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00  ................
        0x0010 76 de 99 0d 02 08 08 17 00 00 00 00 00 00 00 00  v...............
        0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0030 00 00 00 00 00 00 00 00                          ........
        rel 32+8 t=1 runtime.gcbits.01+0
        rel 40+4 t=5 type..namedata.*[]interface {}-+0
        rel 44+4 t=-32763 type.*[]interface {}+0
        rel 48+8 t=1 type.interface {}+0
type..namedata.*[1]interface {}- SRODATA dupok size=18
        0x0000 00 10 2a 5b 31 5d 69 6e 74 65 72 66 61 63 65 20  ..*[1]interface
        0x0010 7b 7d                                            {}
type.[1]interface {} SRODATA dupok size=72
        0x0000 10 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00  ................
        0x0010 6e 20 6a 3d 02 08 08 11 00 00 00 00 00 00 00 00  n j=............
        0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0040 01 00 00 00 00 00 00 00                          ........
        rel 24+8 t=1 runtime.nilinterequal·f+0
        rel 32+8 t=1 runtime.gcbits.02+0
        rel 40+4 t=5 type..namedata.*[1]interface {}-+0
        rel 44+4 t=-32763 type.*[1]interface {}+0
        rel 48+8 t=1 type.interface {}+0
        rel 56+8 t=1 type.[]interface {}+0
type.*[1]interface {} SRODATA dupok size=56
        0x0000 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00  ................
        0x0010 a8 0e 57 36 08 08 08 36 00 00 00 00 00 00 00 00  ..W6...6........
        0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0030 00 00 00 00 00 00 00 00                          ........
        rel 24+8 t=1 runtime.memequal64·f+0
        rel 32+8 t=1 runtime.gcbits.01+0
        rel 40+4 t=5 type..namedata.*[1]interface {}-+0
        rel 48+8 t=1 type.[1]interface {}+0
type..importpath.fmt. SRODATA dupok size=5
        0x0000 00 03 66 6d 74                                   ..fmt
gclocals·J5F+7Qw7O7ve2QcWC7DpeQ== SRODATA dupok size=8
        0x0000 02 00 00 00 00 00 00 00                          ........
gclocals·bDfKCdmtOiGIuJz/x+yQyQ== SRODATA dupok size=10
        0x0000 02 00 00 00 07 00 00 00 00 02                    ..........
main.main.stkobj SRODATA static size=24
        0x0000 01 00 00 00 00 00 00 00 d8 ff ff ff 10 00 00 00  ................
        0x0010 10 00 00 00 00 00 00 00                          ........
        rel 20+4 t=5 runtime.gcbits.02+0
gclocals·ojjeUbKZdec9+9S2K3feng== SRODATA dupok size=10
        0x0000 02 00 00 00 08 00 00 00 00 04                    ..........
main.add.stkobj SRODATA static size=24
        0x0000 01 00 00 00 00 00 00 00 d8 ff ff ff 10 00 00 00  ................
        0x0010 10 00 00 00 00 00 00 00                          ........
        rel 20+4 t=5 runtime.gcbits.02+0
main.add.arginfo1 SRODATA static dupok size=5
        0x0000 00 08 08 08 ff                                   .....

C/C++ 函數調用約定

以 linux C 爲例,x86 和 x86 架構的調用約定不同。

x86 架構

C 語言常用的三種調用約定:

cdecl

x86_cdecl.cc 源代碼如下,通過 __attribute__((cdecl)) 聲明函數使用該調用約定。

main 函數調用 sum,計算 1+2+3 的值,並作爲返回值返回。

__attribute__((cdecl)) int sum(int a, int b, int c) {
    return a + b + c;
}

int main() {
    return sum(1, 2, 3);
}

彙編代碼在調用 _Z3sumiii 之前通過三個 pushl 語句將入參壓棧,_Z3sumiii 使用 eax 儲存返回值,且返回後 main 函數通過 addl $12, %esp 清理輸入參數佔用的堆棧。

你可能會問:_Z3sumiii 也通過 subl $16, %esp 分配了 16 個字節的棧內存,在哪裏釋放的呢?

答案是 leave 指令,它就相當於:

movl %ebp, %esp
popl %ebp

彙編代碼如下:

_Z3sumiii:
    pushl    %ebp    #
    movl    %esp, %ebp    #,
    subl    $16, %esp    #,
    call    __x86.get_pc_thunk.ax    #
    addl    $_GLOBAL_OFFSET_TABLE_, %eax    # tmp82,
# x86_cdecl.cc:2:     int d = a + b + c;
    movl    8(%ebp), %edx    # a, tmp86
    movl    12(%ebp), %eax    # b, tmp87
    addl    %eax, %edx    # tmp87, _1
# x86_cdecl.cc:2:     int d = a + b + c;
    movl    16(%ebp), %eax    # c, tmp91
    addl    %edx, %eax    # _1, tmp90
    movl    %eax, -4(%ebp)    # tmp90, d
# x86_cdecl.cc:3:     return d;
    movl    -4(%ebp), %eax    # d, _6
# x86_cdecl.cc:4: }
    leave    
    ret    
main:
    pushl    %ebp    #
    movl    %esp, %ebp    #,
    subl    $16, %esp    #,
    call    __x86.get_pc_thunk.ax    #
    addl    $_GLOBAL_OFFSET_TABLE_, %eax    # tmp82,
# x86_cdecl.cc:7:     int c = sum(1, 2, 3);
    pushl    $3    # 從右到左依次入棧
    pushl    $2    #
    pushl    $1    #
    call    _Z3sumiii    #
    addl    $12, %esp    # 清理堆棧
    movl    %eax, -4(%ebp)    # tmp85, c
# x86_cdecl.cc:8:     return c;
    movl    -4(%ebp), %eax    # c, _4
# x86_cdecl.cc:9: }
    leave    
    ret    
__x86.get_pc_thunk.ax:
    movl    (%esp), %eax    #,
    ret

stdcall

stdcall(Standard Call) 與 cdecl 規範類似,只是有一點不同:被調用方函數在返回之前會執行 "RET x" 指令還原參數棧,而不會使用單純的 "RET" 指令直接返回。這裏 x 的計算方式是:x = 參數個數 * 指針長度。

x86_stdecl.cc 源代碼,通過 __attribute__((stdcall))聲明約定。

__attribute__((stdcall)) int sum(int a, int b, int c) {
    int d = a + b + c;
    return d;
}

int main() {
    int c = sum(1, 2, 3);
    return c;
}

彙編中,main 函數通過三個 pushl 將入參壓棧,調用 _Z3sumiii 返回後沒有清理堆棧。

_Z3sumiii 通過 ret $12 清理參數佔用的堆棧,並通過 eax 儲存返回值。

_Z3sumiii:
    pushl    %ebp    #
    movl    %esp, %ebp    #,
    subl    $16, %esp    #,
    call    __x86.get_pc_thunk.ax    #
    addl    $_GLOBAL_OFFSET_TABLE_, %eax    # tmp82,
# x86_stdecl.cc:2:     int d = a + b + c;
    movl    8(%ebp), %edx    # a, tmp86
    movl    12(%ebp), %eax    # b, tmp87
    addl    %eax, %edx    # tmp87, _1
# x86_stdecl.cc:2:     int d = a + b + c;
    movl    16(%ebp), %eax    # c, tmp91
    addl    %edx, %eax    # _1, tmp90
    movl    %eax, -4(%ebp)    # tmp90, d
# x86_stdecl.cc:3:     return d;
    movl    -4(%ebp), %eax    # d, _6
# x86_stdecl.cc:4: }
    leave    
    ret    $12        # 被調自己清理堆棧
main:
    pushl    %ebp    #
    movl    %esp, %ebp    #,
    subl    $16, %esp    #,
    call    __x86.get_pc_thunk.ax    #
    addl    $_GLOBAL_OFFSET_TABLE_, %eax    # tmp82,
# x86_stdecl.cc:7:     int c = sum(1, 2, 3);
    pushl    $3    # 按參數從右到左依次壓棧
    pushl    $2    #
    pushl    $1    #
    call    _Z3sumiii    #
    movl    %eax, -4(%ebp)    # tmp85, c
# x86_stdecl.cc:8:     return c;
    movl    -4(%ebp), %eax    # c, _4
# x86_stdecl.cc:9: }
    leave    
    ret    
__x86.get_pc_thunk.ax:
    movl    (%esp), %eax    #,
    ret

fastcall

x86_fastcall.cc 源代碼中通過 __attribute__((fastcall)) 聲明約定。

__attribute__((fastcall)) int sum(int a, int b, int c) {
    int d = a + b + c;
    return d;
}

int main() {
    int c = sum(1, 2, 3);
    return c;
}

彙編中通過 edx、ecx 傳遞前兩個參數,剩餘的壓棧傳遞。_Z3sumiii 函數返回之前通過 ret $4 清理參數的堆棧。

_Z3sumiii:
    pushl    %ebp    #
    movl    %esp, %ebp    #,
    subl    $24, %esp    #,
    call    __x86.get_pc_thunk.ax    #
    addl    $_GLOBAL_OFFSET_TABLE_, %eax    # tmp82,
    movl    %ecx, -20(%ebp)    # a, a
    movl    %edx, -24(%ebp)    # b, b
# x86_fastcall.cc:2:     int d = a + b + c;
    movl    -20(%ebp), %edx    # a, tmp86
    movl    -24(%ebp), %eax    # b, tmp87
    addl    %eax, %edx    # tmp87, _1
# x86_fastcall.cc:2:     int d = a + b + c;
    movl    8(%ebp), %eax    # c, tmp91
    addl    %edx, %eax    # _1, tmp90
    movl    %eax, -4(%ebp)    # tmp90, d
# x86_fastcall.cc:3:     return d;
    movl    -4(%ebp), %eax    # d, _6
# x86_fastcall.cc:4: }
    leave    
    ret    $4        # 被調自己清理堆棧
main:
    pushl    %ebp    #
    movl    %esp, %ebp    #,
    subl    $16, %esp    #,
    call    __x86.get_pc_thunk.ax    #
    addl    $_GLOBAL_OFFSET_TABLE_, %eax    # tmp82,
# x86_fastcall.cc:7:     int c = sum(1, 2, 3);
    pushl    $3    # 前兩個通過 ecx、edx 傳,後面的從右向左壓棧
    movl    $2, %edx    #,
    movl    $1, %ecx    #,
    call    _Z3sumiii    #
    movl    %eax, -4(%ebp)    # tmp85, c
# x86_fastcall.cc:8:     return c;
    movl    -4(%ebp), %eax    # c, _4
# x86_fastcall.cc:9: }
    leave    
    ret    
__x86.get_pc_thunk.ax:
    movl    (%esp), %eax    #,
    ret

x86-64 架構

64 位平臺中,函數前 6 個參數通過寄存器 rdi、rsi、rdx、rcx、r8、r9 傳遞,超出的參數從右向左依次入棧。

x86_64.cc 源代碼,無需聲明 attribute

int sum(int a, int b, int c, int d, int e, int f, int g) {
    int h = a + b + c + d + e + f + g;
    return h;
}

int main() {
    int c = sum(1, 2, 3, 4, 5, 6, 7);
    return c;
}

彙編代碼優先通過寄存器傳遞參數,超出的參數通過壓棧傳遞,main 函數負責清理被調 _Z3sumiiiiiii 參數佔用的堆棧。

_Z3sumiiiiiii:
    pushq    %rbp    #
    movq    %rsp, %rbp    #,
    movl    %edi, -20(%rbp)    # a, a
    movl    %esi, -24(%rbp)    # b, b
    movl    %edx, -28(%rbp)    # c, c
    movl    %ecx, -32(%rbp)    # d, d
    movl    %r8d, -36(%rbp)    # e, e
    movl    %r9d, -40(%rbp)    # f, f
# x86_64.cc:2:     int h = a + b + c + d + e + f + g;
    movl    -20(%rbp), %edx    # a, tmp89
    movl    -24(%rbp), %eax    # b, tmp90
    addl    %eax, %edx    # tmp90, _1
# x86_64.cc:2:     int h = a + b + c + d + e + f + g;
    movl    -28(%rbp), %eax    # c, tmp91
    addl    %eax, %edx    # tmp91, _2
# x86_64.cc:2:     int h = a + b + c + d + e + f + g;
    movl    -32(%rbp), %eax    # d, tmp92
    addl    %eax, %edx    # tmp92, _3
# x86_64.cc:2:     int h = a + b + c + d + e + f + g;
    movl    -36(%rbp), %eax    # e, tmp93
    addl    %eax, %edx    # tmp93, _4
# x86_64.cc:2:     int h = a + b + c + d + e + f + g;
    movl    -40(%rbp), %eax    # f, tmp94
    addl    %eax, %edx    # tmp94, _5
# x86_64.cc:2:     int h = a + b + c + d + e + f + g;
    movl    16(%rbp), %eax    # g, tmp98
    addl    %edx, %eax    # _5, tmp97
    movl    %eax, -4(%rbp)    # tmp97, h
# x86_64.cc:3:     return h;
    movl    -4(%rbp), %eax    # h, _14
# x86_64.cc:4: }
    popq    %rbp    #
    ret    
main:
    pushq    %rbp    #
    movq    %rsp, %rbp    #,
    subq    $16, %rsp    #,
# x86_64.cc:7:     int c = sum(1, 2, 3, 4, 5, 6, 7);
    pushq    $7    #
    movl    $6, %r9d    #,
    movl    $5, %r8d    #,
    movl    $4, %ecx    #,
    movl    $3, %edx    #,
    movl    $2, %esi    #,
    movl    $1, %edi    #,
    call    _Z3sumiiiiiii    #
    addq    $8, %rsp    #,
    movl    %eax, -4(%rbp)    # tmp84, c
# x86_64.cc:8:     return c;
    movl    -4(%rbp), %eax    # c, _4
# x86_64.cc:9: }
    leave    
    ret

Go 調用約定

Go1.17 使用寄存器替代棧傳遞參數,使得性能提升 5%,編譯後二進制包大小降低 2%。

Go 1.17 implements a new way of passing function arguments and results using registers instead of the stack. Benchmarks for a representative set of Go packages and programs show performance improvements of about 5%, and a typical reduction in binary size of about 2%.

因此分別使用 Go1.16、Go1.17 編譯源文件,分析其調用傳參特點。

源文件如下,定義 test 函數,輸入 11 個參數,將這 11 個參數自增後再輸出。

1: package main
2:
3: func test(a, b, c, d, e, f, g, h, i, j, k int) (
4:         int, int, int, int, int, int, int, int, int, int, int,
5: ) {
6:         return a + 1, b + 1, c + 1, d + 1, e + 1, f + 1, g + 1, h + 1, i + 1, j + 1, k + 1
7: }
8: 
9: func main() {
10:     _, _, _, _, _, _, _, _, _, _, _ = test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
11: }

平臺:x86_64,地址長度 8 字節。

Go 使用的僞寄存器:

  • • SB:全局靜態指針,即程序地址空間的開始地址。一般用在聲明函數、全局變量中。

  • • FP:指向的是主調傳的第一個參數的位置(最後一個壓棧的參數),需要用 symbol+offset(FP) 來獲取入參的參數值。Plan 9 的僞寄存器都需要添加 symbol,否則會報錯。

  • • SP:SP 寄存器 分爲僞 SP 寄存器和硬件 SP 寄存器。symbol+offset(SP) 形式,則表示僞寄存器 SP (這個也簡稱爲 BP)。如果是 offset(SP) 則表示硬件寄存器 SP。僞 SP 寄存器指向當前棧幀第一個局部變量的結束位置;硬件 SP 指向的是整個函數棧結束的位置。

  • • PC:在 x86 平臺下對應 ip 寄存器,amd64 上則是 rip。

Go1.16

編譯指令:

CGO_ENABLE=0 /home/hsp/sdk/go1.16.4/bin/go tool compile -N -l -S call_convention.go

彙編如下,可知:

0x0018 00024 (call_convention.go:9)     SUBQ    $184, SP
0x001f 00031 (call_convention.go:9)     MOVQ    BP, 176(SP)
        0x002f 00047 (call_convention.go:10)    MOVQ    $1(SP)
        0x0037 00055 (call_convention.go:10)    MOVQ    $2, 8(SP)
        0x0040 00064 (call_convention.go:10)    MOVQ    $3, 16(SP)
        0x0049 00073 (call_convention.go:10)    MOVQ    $4, 24(SP)
        0x0052 00082 (call_convention.go:10)    MOVQ    $5, 32(SP)
        0x005b 00091 (call_convention.go:10)    MOVQ    $6, 40(SP)
        0x0064 00100 (call_convention.go:10)    MOVQ    $7, 48(SP)
        0x006d 00109 (call_convention.go:10)    MOVQ    $8, 56(SP)
        0x0076 00118 (call_convention.go:10)    MOVQ    $9, 64(SP)
        0x007f 00127 (call_convention.go:10)    MOVQ    $10, 72(SP)
        0x0088 00136 (call_convention.go:10)    MOVQ    $11, 80(SP)
      0x0091 00145 (call_convention.go:10)    CALL    "".test(SB)
        0x0000 00000 (call_convention.go:3)     MOVQ    $0"".~r11+96(SP)
        0x0009 00009 (call_convention.go:3)     MOVQ    $0"".~r12+104(SP)
        0x0012 00018 (call_convention.go:3)     MOVQ    $0"".~r13+112(SP)
        0x001b 00027 (call_convention.go:3)     MOVQ    $0"".~r14+120(SP)
        0x0024 00036 (call_convention.go:3)     MOVQ    $0"".~r15+128(SP)
        0x0030 00048 (call_convention.go:3)     MOVQ    $0"".~r16+136(SP)
        0x003c 00060 (call_convention.go:3)     MOVQ    $0"".~r17+144(SP)
        0x0048 00072 (call_convention.go:3)     MOVQ    $0"".~r18+152(SP)
        0x0054 00084 (call_convention.go:3)     MOVQ    $0"".~r19+160(SP)
        0x0060 00096 (call_convention.go:3)     MOVQ    $0"".~r20+168(SP)
        0x006c 00108 (call_convention.go:3)     MOVQ    $0"".~r21+176(SP)
        0x0096 00150 (call_convention.go:11)    MOVQ    176(SP), BP
        0x009e 00158 (call_convention.go:11)    ADDQ    $184, SP

由上可知,Go1.16 分配的棧內存中,從棧底到棧頂先儲存返回參數,然後儲存輸入參數,壓棧順序按參數順序從右到左。且 main 函數分配的棧內存由 main 函數自己銷燬。

main 函數調用 test 函數時的棧幀示意圖:

全部彙編代碼:

"".test STEXT nosplit size=285 args=0xb0 locals=0x0 funcid=0x0
        0x0000 00000 (call_convention.go:3)     TEXT    "".test(SB), NOSPLIT|ABIInternal, $0-176
        0x0000 00000 (call_convention.go:3)     MOVQ    $0"".~r11+96(SP)
        0x0009 00009 (call_convention.go:3)     MOVQ    $0"".~r12+104(SP)
        0x0012 00018 (call_convention.go:3)     MOVQ    $0"".~r13+112(SP)
        0x001b 00027 (call_convention.go:3)     MOVQ    $0"".~r14+120(SP)
        0x0024 00036 (call_convention.go:3)     MOVQ    $0"".~r15+128(SP)
        0x0030 00048 (call_convention.go:3)     MOVQ    $0"".~r16+136(SP)
        0x003c 00060 (call_convention.go:3)     MOVQ    $0"".~r17+144(SP)
        0x0048 00072 (call_convention.go:3)     MOVQ    $0"".~r18+152(SP)
        0x0054 00084 (call_convention.go:3)     MOVQ    $0"".~r19+160(SP)
        0x0060 00096 (call_convention.go:3)     MOVQ    $0"".~r20+168(SP)
        0x006c 00108 (call_convention.go:3)     MOVQ    $0"".~r21+176(SP)
        0x0078 00120 (call_convention.go:6)     MOVQ    "".a+8(SP), AX
        0x007d 00125 (call_convention.go:6)     INCQ    AX
        0x0080 00128 (call_convention.go:6)     MOVQ    AX, "".~r11+96(SP)
        0x0085 00133 (call_convention.go:6)     MOVQ    "".b+16(SP), AX
        0x008a 00138 (call_convention.go:6)     INCQ    AX
        0x008d 00141 (call_convention.go:6)     MOVQ    AX, "".~r12+104(SP)
        0x0092 00146 (call_convenion.go:6)     MOVQ    "".c+24(SP), AX
        0x0097 00151 (call_convention.go:6)     INCQ    AX
        0x009a 00154 (call_convention.go:6)     MOVQ    AX, "".~r13+112(SP)
        0x009f 00159 (call_convention.go:6)     MOVQ    "".d+32(SP), AX
        0x00a4 00164 (call_convention.go:6)     INCQ    AX
        0x00a7 00167 (call_convention.go:6)     MOVQ    AX, "".~r14+120(SP)
        0x00ac 00172 (call_convention.go:6)     MOVQ    "".e+40(SP), AX
        0x00b1 00177 (call_convention.go:6)     INCQ    AX
        0x00b4 00180 (call_convention.go:6)     MOVQ    AX, "".~r15+128(SP)
        0x00bc 00188 (call_convention.go:6)     MOVQ    "".f+48(SP), AX
        0x00c1 00193 (call_convention.go:6)     INCQ    AX
        0x00c4 00196 (call_convention.go:6)     MOVQ    AX, "".~r16+136(SP)
        0x00cc 00204 (call_convention.go:6)     MOVQ    "".g+56(SP), AX
        0x00d1 00209 (call_convention.go:6)     INCQ    AX
        0x00d4 00212 (call_convention.go:6)     MOVQ    AX, "".~r17+144(SP)
        0x00dc 00220 (call_convention.go:6)     MOVQ    "".h+64(SP), AX
        0x00e1 00225 (call_convention.go:6)     INCQ    AX
        0x00e4 00228 (call_convention.go:6)     MOVQ    AX, "".~r18+152(SP)
        0x00ec 00236 (call_convention.go:6)     MOVQ    "".i+72(SP), AX
        0x00f1 00241 (call_convention.go:6)     INCQ    AX
        0x00f4 00244 (call_convention.go:6)     MOVQ    AX, "".~r19+160(SP)
        0x00fc 00252 (call_convention.go:6)     MOVQ    "".j+80(SP), AX
        0x0101 00257 (call_convention.go:6)     INCQ    AX
        0x0104 00260 (call_convention.go:6)     MOVQ    AX, "".~r20+168(SP)
        0x010c 00268 (call_convention.go:6)     MOVQ    "".k+88(SP), AX
        0x0111 00273 (call_convention.go:6)     INCQ    AX
        0x0114 00276 (call_convention.go:6)     MOVQ    AX, "".~r21+176(SP)
        0x011c 00284 (call_convention.go:6)     RET
"".main STEXT size=176 args=0x0 locals=0xb8 funcid=0x0
        0x0000 00000 (call_convention.go:9)     TEXT    "".main(SB), ABIInternal, $184-0
        0x0000 00000 (call_convention.go:9)     MOVQ    (TLS), CX
        0x0009 00009 (call_convention.go:9)     LEAQ    -56(SP), AX
        0x000e 00014 (call_convention.go:9)     CMPQ    AX, 16(CX)
        0x0012 00018 (call_convention.go:9)     PCDATA  $0$-2
        0x0012 00018 (call_convention.go:9)     JLS     166
        0x0018 00024 (call_convention.go:9)     PCDATA  $0$-1
        0x0018 00024 (call_convention.go:9)     SUBQ    $184, SP
        0x001f 00031 (call_convention.go:9)     MOVQ    BP, 176(SP)
        0x0027 00039 (call_convention.go:9)     LEAQ    176(SP), BP
        0x002f 00047 (call_convention.go:9)     FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x002f 00047 (call_convention.go:9)     FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x002f 00047 (call_convention.go:10)    MOVQ    $1(SP)
        0x0037 00055 (call_convention.go:10)    MOVQ    $2, 8(SP)
        0x0040 00064 (call_convention.go:10)    MOVQ    $3, 16(SP)
        0x0049 00073 (call_convention.go:10)    MOVQ    $4, 24(SP)
        0x0052 00082 (call_convention.go:10)    MOVQ    $5, 32(SP)
        0x005b 00091 (call_convention.go:10)    MOVQ    $6, 40(SP)
        0x0064 00100 (call_convention.go:10)    MOVQ    $7, 48(SP)
        0x006d 00109 (call_convention.go:10)    MOVQ    $8, 56(SP)
        0x0076 00118 (call_convention.go:10)    MOVQ    $9, 64(SP)
        0x007f 00127 (call_convention.go:10)    MOVQ    $10, 72(SP)
        0x0088 00136 (call_convention.go:10)    MOVQ    $11, 80(SP)
        0x0091 00145 (call_convention.go:10)    PCDATA  $1$0
        0x0091 00145 (call_convention.go:10)    CALL    "".test(SB)
        0x0096 00150 (call_convention.go:11)    MOVQ    176(SP), BP
        0x009e 00158 (call_convention.go:11)    ADDQ    $184, SP
        0x00a5 00165 (call_convention.go:11)    RET
        0x00a6 00166 (call_convention.go:11)    NOP
        0x00a6 00166 (call_convention.go:9)     PCDATA  $1$-1
        0x00a6 00166 (call_convention.go:9)     PCDATA  $0$-2
        0x00a6 00166 (call_convention.go:9)     CALL    runtime.morestack_noctxt(SB)
        0x00ab 00171 (call_convention.go:9)     PCDATA  $0$-1
        0x00ab 00171 (call_convention.go:9)     JMP     0

Go1.17

編譯指令:

CGO_ENABLE=0 /home/hsp/sdk/go1.17.4/bin/go tool compile -N -l -S call_convention.go > ca

具體處理操作:

        0x0006 00006 (call_convention.go:9)     SUBQ    $112, SP
        0x000a 00010 (call_convention.go:9)     MOVQ    BP, 104(SP)
        0x0014 00020 (call_convention.go:10)    MOVQ    $10(SP)
        0x001c 00028 (call_convention.go:10)    MOVQ    $11, 8(SP)
        0x0025 00037 (call_convention.go:10)    MOVL    $1, AX
        0x002a 00042 (call_convention.go:10)    MOVL    $2, BX
        0x002f 00047 (call_convention.go:10)    MOVL    $3, CX
        0x0034 00052 (call_convention.go:10)    MOVL    $4, DI
        0x0039 00057 (call_convention.go:10)    MOVL    $5, SI
        0x003e 00062 (call_convention.go:10)    MOVL    $6, R8
        0x0044 00068 (call_convention.go:10)    MOVL    $7, R9
        0x004a 00074 (call_convention.go:10)    MOVL    $8, R10
        0x0050 00080 (call_convention.go:10)    MOVL    $9, R11
        0x0056 00086 (call_convention.go:10)    CALL    "".test(SB)
        0x005b 00091 (call_convention.go:11)    MOVQ    104(SP), BP
        0x0000 00000 (call_convention.go:3)     SUBQ    $80, SP
        0x0004 00004 (call_convention.go:3)     MOVQ    BP, 72(SP)
        0x0009 00009 (call_convention.go:3)     LEAQ    72(SP), BP
        0x0187 00391 (call_convention.go:6)     MOVQ    72(SP), BP
        0x018c 00396 (call_convention.go:6)     ADDQ    $80, SP
        0x005b 00091 (call_convention.go:11)    MOVQ    104(SP), BP
        0x0060 00096 (call_convention.go:11)    ADDQ    $112, SP

test 清理堆棧且返回前的堆棧示意圖:

彙編代碼如下,其中 MOVQ AX, "".a+120(SP) 這種格式使用的是真 sp 寄存器。

"".test STEXT nosplit size=401 args=0x68 locals=0x50 funcid=0x0
        0x0000 00000 (call_convention.go:3)     TEXT    "".test(SB), NOSPLIT|ABIInternal, $80-104
        0x0000 00000 (call_convention.go:3)     SUBQ    $80, SP
        0x0004 00004 (call_convention.go:3)     MOVQ    BP, 72(SP)
        0x0009 00009 (call_convention.go:3)     LEAQ    72(SP), BP
        0x000e 00014 (call_convention.go:3)     FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x000e 00014 (call_convention.go:3)     FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x000e 00014 (call_convention.go:3)     FUNCDATA        $5"".test.arginfo1(SB)
        0x000e 00014 (call_convention.go:3)     MOVQ    AX, "".a+120(SP)
        0x0013 00019 (call_convention.go:3)     MOVQ    BX, "".b+128(SP)
        0x001b 00027 (call_convention.go:3)     MOVQ    CX, "".c+136(SP)
        0x0023 00035 (call_convention.go:3)     MOVQ    DI, "".d+144(SP)
        0x002b 00043 (call_convention.go:3)     MOVQ    SI, "".e+152(SP)
        0x0033 00051 (call_convention.go:3)     MOVQ    R8, "".f+160(SP)
        0x003b 00059 (call_convention.go:3)     MOVQ    R9, "".g+168(SP)
        0x0043 00067 (call_convention.go:3)     MOVQ    R10, "".h+176(SP)
        0x004b 00075 (call_convention.go:3)     MOVQ    R11, "".i+184(SP)
        0x0053 00083 (call_convention.go:3)     MOVQ    $0"".~r11+64(SP)
        0x005c 00092 (call_convention.go:3)     MOVQ    $0"".~r12+56(SP)
        0x0065 00101 (call_convention.go:3)     MOVQ    $0"".~r13+48(SP)
        0x006e 00110 (call_convention.go:3)     MOVQ    $0"".~r14+40(SP)
        0x0077 00119 (call_convention.go:3)     MOVQ    $0"".~r15+32(SP)
        0x0080 00128 (call_convention.go:3)     MOVQ    $0"".~r16+24(SP)
        0x0089 00137 (call_convention.go:3)     MOVQ    $0"".~r17+16(SP)
        0x0092 00146 (call_convention.go:3)     MOVQ    $0"".~r18+8(SP)
        0x009b 00155 (call_convention.go:3)     MOVQ    $0"".~r19(SP)
        0x00a3 00163 (call_convention.go:3)     MOVQ    $0"".~r20+104(SP)
        0x00ac 00172 (call_convention.go:3)     MOVQ    0, "".~r21+112(SP)
        0x00b5 00181 (call_convention.go:6)     MOVQ    "".a+120(SP), DX
        0x00ba 00186 (call_convention.go:6)     INCQ    DX
        0x00bd 00189 (call_convention.go:6)     MOVQ    DX, "".~r11+64(SP)
        0x00c2 00194 (call_convention.go:6)     MOVQ    "".b+128(SP), DX
        0x00ca 00202 (call_convention.go:6)     INCQ    DX
        0x00cd 00205 (call_convention.go:6)     MOVQ    DX, "".~r12+56(SP)
        0x00d2 00210 (call_convention.go:6)     MOVQ    "".c+136(SP), DX
        0x00da 00218 (call_convention.go:6)     INCQ    DX
        0x00dd 00221 (call_convention.go:6)     MOVQ    DX, "".~r13+48(SP)
        0x00e2 00226 (call_convention.go:6)     MOVQ    "".d+144(SP), DX
        0x00ea 00234 (call_convention.go:6)     INCQ    DX
        0x00ed 00237 (call_convention.go:6)     MOVQ    DX, "".~r14+40(SP)
        0x00f2 00242 (call_convention.go:6)     MOVQ    "".e+152(SP), DX
        0x00fa 00250 (call_convention.go:6)     INCQ    DX
        0x00fd 00253 (call_convention.go:6)     MOVQ    DX, "".~r15+32(SP)
        0x0102 00258 (call_convention.go:6)     MOVQ    "".f+160(SP), DX
        0x010a 00266 (call_convention.go:6)     INCQ    DX
        0x010d 00269 (call_convention.go:6)     MOVQ    DX, "".~r16+24(SP)
        0x0112 00274 (call_convention.go:6)     MOVQ    "".g+168(SP), DX
        0x011a 00282 (call_convention.go:6)     INCQ    DX
        0x011d 00285 (call_convention.go:6)     MOVQ    DX, "".~r17+16(SP)
        0x0122 00290 (call_convention.go:6)     MOVQ    "".h+176(SP), DX
        0x012a 00298 (call_convention.go:6)     INCQ    DX
        0x012d 00301 (call_convention.go:6)     MOVQ    DX, "".~r18+8(SP)
        0x0132 00306 (call_convention.go:6)     MOVQ    "".i+184(SP), DX
        0x013a 00314 (call_convention.go:6)     INCQ    DX
        0x013d 00317 (call_convention.go:6)     MOVQ    DX, "".~r19(SP)
        0x0141 00321 (call_convention.go:6)     MOVQ    "".j+88(SP), DX
        0x0146 00326 (call_convention.go:6)     INCQ    DX
        0x0149 00329 (call_convention.go:6)     MOVQ    DX, "".~r20+104(SP)
        0x014e 00334 (call_convention.go:6)     MOVQ    "".k+96(SP), DX
        0x0153 00339 (call_convention.go:6)     INCQ    DX
        0x0156 00342 (call_convention.go:6)     MOVQ    DX, "".~r21+112(SP)
        0x015b 00347 (call_convention.go:6)     MOVQ    "".~r11+64(SP), AX
        0x0160 00352 (call_convention.go:6)     MOVQ    "".~r12+56(SP), BX
        0x0165 00357 (call_convention.go:6)     MOVQ    "".~r13+48(SP), CX
        0x016a 00362 (call_convention.go:6)     MOVQ    "".~r14+40(SP), DI
        0x016f 00367 (call_convention.go:6)     MOVQ    "".~r15+32(SP), SI
        0x0174 00372 (call_convention.go:6)     MOVQ    "".~r16+24(SP), R8
        0x0179 00377 (call_convention.go:6)     MOVQ    "".~r17+16(SP), R9
        0x017e 00382 (call_convention.go:6)     MOVQ    "".~r18+8(SP), R10
        0x0183 00387 (call_convention.go:6)     MOVQ    "".~r19(SP), R11
        0x0187 00391 (call_convention.go:6)     MOVQ    72(SP), BP
        0x018c 00396 (call_convention.go:6)     ADDQ    $80, SP
        0x0190 00400 (call_convention.go:6)     RET                                          .
"".main STEXT size=108 args=0x0 locals=0x70 funcid=0x0
        0x0000 00000 (call_convention.go:9)     TEXT    "".main(SB), ABIInternal, $112-0
        0x0000 00000 (call_convention.go:9)     CMPQ    SP, 16(R14)
        0x0004 00004 (call_convention.go:9)     PCDATA  $0$-2
        0x0004 00004 (call_convention.go:9)     JLS     101
        0x0006 00006 (call_convention.go:9)     PCDATA  $0$-1
        0x0006 00006 (call_convention.go:9)     SUBQ    $112, SP
        0x000a 00010 (call_convention.go:9)     MOVQ    BP, 104(SP)
        0x000f 00015 (call_convention.go:9)     LEAQ    104(SP), BP
        0x0014 00020 (call_convention.go:9)     FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0014 00020 (call_convention.go:9)     FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0014 00020 (call_convention.go:10)    MOVQ    $10(SP)
        0x001c 00028 (call_convention.go:10)    MOVQ    $11, 8(SP)
        0x0025 00037 (call_convention.go:10)    MOVL    $1, AX
        0x002a 00042 (call_convention.go:10)    MOVL    $2, BX
        0x002f 00047 (call_convention.go:10)    MOVL    $3, CX
        0x0034 00052 (call_convention.go:10)    MOVL    $4, DI
        0x0039 00057 (call_convention.go:10)    MOVL    $5, SI
        0x003e 00062 (call_convention.go:10)    MOVL    $6, R8
        0x0044 00068 (call_convention.go:10)    MOVL    $7, R9
        0x004a 00074 (call_convention.go:10)    MOVL    $8, R10
        0x0050 00080 (call_convention.go:10)    MOVL    $9, R11
        0x0056 00086 (call_convention.go:10)    PCDATA  $1$0
        0x0056 00086 (call_convention.go:10)    CALL    "".test(SB)
        0x005b 00091 (call_convention.go:11)    MOVQ    104(SP), BP
        0x0060 00096 (call_convention.go:11)    ADDQ    $112, SP
        0x0064 00100 (call_convention.go:11)    RET
        0x0065 00101 (call_convention.go:11)    NOP
        0x0065 00101 (call_convention.go:9)     PCDATA  $1$-1
        0x0065 00101 (call_convention.go:9)     PCDATA  $0$-2
        0x0065 00101 (call_convention.go:9)     CALL    runtime.morestack_noctxt(SB)
        0x006a 00106 (call_convention.go:9)     PCDATA  $0$-1
        0x006a 00106 (call_convention.go:9)     JMP     0

總結

調用約定根據不同語言版本、不同架構而不同。

根據架構的不同,C/C++ 在 x86 和 x86_64 展現不同的特性

根據語言版本的不同,Go1.16 和 Go1.17 展現不同的特性

參考

在線 8086 彙編編譯器

函數調用約定

gcc 編譯命令詳解及最佳實踐

C++ 生成彙編代碼

逆向工程權威指南 - x86 編譯彙編

x86 彙編語言基礎 (AT&T 語法)

__x86.get_pc_thunk.ax 函數

GLOBAL_OFFSET_TABLE

system V ABI

go1.17 compiler

Go 彙編詳解 - 掘金

再探 go 彙編 - 掘金

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/xYnzVz2FDX-rNCgfKwOgAQ