go 通過匯編分析函數傳參與返回值機制

文章目錄

    • 概要
    • 一、前置知識
    • 二、匯編分析
        • 2.1、示例
        • 2.2、匯編
        • 2.2.1、 寄存器傳值的匯編
        • 2.2.2、 棧內存傳值的匯編
    • 三、拓展
        • 3.1 了解go中的Duff’s Device
        • 3.2 go tool compile
        • 3.2 call 0x46dc70 & call 0x46dfda

概要

在上一篇文章中,我們研究了go函數調用時的棧布局,觀察到了其函數參數和返回值一般通過AX、BX等通用寄存器傳遞,但是函數示例的傳參和返回值個數都很少,自然會想到,如果函數參數和返回值很多,怎么辦?畢竟CPU的寄存器數量是有限的,而go函數參數數量可沒限制。

調試環境:Centos Linux 7 ,CPU AMD x86_64,Go version 1.24

先說結論:當函數參數和返回值過多時,多余的值直接通過棧來傳遞,下面就讓我們通過go匯編來驗證下吧。

一、前置知識

AMD x86_64 CPU 常見寄存器:
x86_64寄存器簡介
另外還有8個專為Steraming SIMD Extensions(SSE——多指令多數據流擴展)準備的寄存器(128位):xmm0~xmm15。

對于AX系列寄存器,在16位下是ax、32位下是eax、64位下是rax,如果在64位cpu下使用eax,表示只用該寄存器前32位,其他同理。

二、匯編分析

2.1、示例
1 package main23 func main() { // 探索棧布局4         x := int64(6)5         callMaxTest(x)6         rv := returnMax()7         x = rv.Id58 }910 func callMaxTest(z int64) {11         d := MaxData{12                 Id:  1,13                 Id1: 2,14                 Id2: 3,15                 Id3: z,16                 Id4: 5,17                 Id5: 6,18                 Id6: 7,19                 Id7: 8,20                 Id8: 9,21         }22         d.Id = callMax(d) //過大的傳參數據,寄存器不夠用,所以會用過棧內存傳遞數據23 }2425 func callMax(md MaxData) int64 {26         a, b := md.Id2, md.Id527         return a + b28 }2930 func returnMax() MaxData { //過大的返回數據,寄存器不夠用,所以會用過棧內存傳遞數據31         y := int64(7)32         md := MaxData{33                 Id:  1,34                 Id1: 2,35                 Id2: 3,36                 Id3: 4,37                 Id4: 5,38                 Id5: 6,39                 Id6: 7,40                 Id7: 8,41                 Id8: 9,42         }43         md.Id5 = y44         return md45 }4647 type MaxData struct { //x86_6448         Id  int64 //AX49         Id1 int64 //BX50         Id2 int64 //CX51         Id3 int64 //DI52         Id4 int64 //SI53         Id5 int64 //R854         Id6 int64 //R955         Id7 int64 //R1056         Id8 int64 //R1157         //Id9 int64 //導致寄存器不足, 編譯器判定使用棧內存傳遞數據58 }

通過調整MaxData結構體字段個數,得到9個字段是是否使用寄存器傳參的極限。

2.2、匯編

通過寄存器傳值和棧內存傳值兩種模式分析。
仍然使用dlv debug 來調試。

2.2.1、 寄存器傳值的匯編

即MaxData結構體字段個數是9個的情況下的匯編,由于本文只分析函數傳參和返回值,所以我們只研究callMaxTest和returnMax的匯編。

TEXT main.callMaxTest(SB) /home/gofunc/ret.goret.go:10       0x470be0        4c8d6424e0                      lea r12, ptr [rsp-0x20]ret.go:10       0x470be5        4d3b6610                        cmp r12, qword ptr [r14+0x10]ret.go:10       0x470be9        0f86c0000000                    jbe 0x470cafret.go:10       0x470bef        55                              push rbpret.go:10       0x470bf0        4889e5                          mov rbp, rsp
=>      ret.go:10       0x470bf3*       4881ec98000000                  sub rsp, 0x98ret.go:10       0x470bfa        48898424a8000000                mov qword ptr [rsp+0xa8], rax#令rsp+0xa8地址保存參數zret.go:20       0x470c02        48c744244801000000              mov qword ptr [rsp+0x48], 0x1#開始設置各個字段的值,令字段Id=1ret.go:20       0x470c0b        48c744245002000000              mov qword ptr [rsp+0x50], 0x2ret.go:20       0x470c14        48c744245803000000              mov qword ptr [rsp+0x58], 0x3ret.go:20       0x470c1d        48c744246000000000              mov qword ptr [rsp+0x60], 0x0#令字段Id3=0ret.go:20       0x470c26        48c744246805000000              mov qword ptr [rsp+0x68], 0x5ret.go:20       0x470c2f        48c744247006000000              mov qword ptr [rsp+0x70], 0x6ret.go:20       0x470c38        48c744247807000000              mov qword ptr [rsp+0x78], 0x7ret.go:20       0x470c41        48c784248000000008000000        mov qword ptr [rsp+0x80], 0x8ret.go:20       0x470c4d        48c784248800000009000000        mov qword ptr [rsp+0x88], 0x9#令字段Id8=9ret.go:15       0x470c59        488bbc24a8000000                mov rdi, qword ptr [rsp+0xa8]#開始設置參數,令RDX傳遞字段Id3的值ret.go:15       0x470c61        48897c2460                      mov qword ptr [rsp+0x60], rdi#令字段Id3等于參數zret.go:22       0x470c66        488b442448                      mov rax, qword ptr [rsp+0x48]#令RAX傳遞字段Id的值ret.go:22       0x470c6b        488b5c2450                      mov rbx, qword ptr [rsp+0x50]ret.go:22       0x470c70        488b4c2458                      mov rcx, qword ptr [rsp+0x58]ret.go:22       0x470c75        488b742468                      mov rsi, qword ptr [rsp+0x68]ret.go:22       0x470c7a        4c8b442470                      mov r8, qword ptr [rsp+0x70]ret.go:22       0x470c7f        4c8b4c2478                      mov r9, qword ptr [rsp+0x78]ret.go:22       0x470c84        4c8b942480000000                mov r10, qword ptr [rsp+0x80]ret.go:22       0x470c8c        4c8b9c2488000000                mov r11, qword ptr [rsp+0x88]#令R11傳遞字段Id8的值ret.go:22       0x470c94        e847000000                      call $main.callMaxret.go:22       0x470c99        4889842490000000                mov qword ptr [rsp+0x90], raxret.go:22       0x470ca1        4889442448                      mov qword ptr [rsp+0x48], raxret.go:23       0x470ca6        4881c498000000                  add rsp, 0x98ret.go:23       0x470cad        5d                              pop rbpret.go:23       0x470cae        c3                              retret.go:10       0x470caf        4889442408                      mov qword ptr [rsp+0x8], raxret.go:10       0x470cb4        e847acffff                      call $runtime.morestack_noctxtret.go:10       0x470cb9        488b442408                      mov rax, qword ptr [rsp+0x8]ret.go:10       0x470cbe        6690                            data16 nopret.go:10       0x470cc0        e91bffffff                      jmp $main.callMaxTest
TEXT main.returnMax(SB) /home/gofunc/ret.goret.go:30       0x470d40        4c8d6424e0                      lea r12, ptr [rsp-0x20]ret.go:30       0x470d45        4d3b6610                        cmp r12, qword ptr [r14+0x10]ret.go:30       0x470d49        0f86fe000000                    jbe 0x470e4dret.go:30       0x470d4f        55                              push rbpret.go:30       0x470d50        4889e5                          mov rbp, rsp
=>      ret.go:30       0x470d53*       4881ec98000000                  sub rsp, 0x98ret.go:30       0x470d5a        440f113c24                      movups xmmword ptr [rsp], xmm15#令rsp+0x0地址值等于0ret.go:30       0x470d5f        440f117c2408                    movups xmmword ptr [rsp+0x8], xmm15ret.go:30       0x470d65        440f117c2418                    movups xmmword ptr [rsp+0x18], xmm15ret.go:30       0x470d6b        440f117c2428                    movups xmmword ptr [rsp+0x28], xmm15ret.go:30       0x470d71        440f117c2438                    movups xmmword ptr [rsp+0x38], xmm15ret.go:31       0x470d77        48c744244807000000              mov qword ptr [rsp+0x48], 0x7#令變量y=7ret.go:41       0x470d80        48c744245001000000              mov qword ptr [rsp+0x50], 0x1#開始設置各個字段的值,令字段Id=1ret.go:41       0x470d89        48c744245802000000              mov qword ptr [rsp+0x58], 0x2ret.go:41       0x470d92        48c744246003000000              mov qword ptr [rsp+0x60], 0x3ret.go:41       0x470d9b        48c744246804000000              mov qword ptr [rsp+0x68], 0x4ret.go:41       0x470da4        48c744247005000000              mov qword ptr [rsp+0x70], 0x5ret.go:41       0x470dad        48c744247806000000              mov qword ptr [rsp+0x78], 0x6#令字段Id5=6ret.go:41       0x470db6        48c784248000000007000000        mov qword ptr [rsp+0x80], 0x7ret.go:41       0x470dc2        48c784248800000008000000        mov qword ptr [rsp+0x88], 0x8ret.go:41       0x470dce        48c784249000000009000000        mov qword ptr [rsp+0x90], 0x9#令字段Id8=9ret.go:43       0x470dda        488b542448                      mov rdx, qword ptr [rsp+0x48]#令RDX等于變量yret.go:43       0x470ddf        4889542478                      mov qword ptr [rsp+0x78], rdx#令字段Id5等于RDX,即等于變量yret.go:44       0x470de4        488b542450                      mov rdx, qword ptr [rsp+0x50]#令RDX等于字段Idret.go:44       0x470de9        48891424                        mov qword ptr [rsp], rdx#令rsp地址等于RDX,即等于字段Idret.go:44       0x470ded        0f10442458                      movups xmm0, xmmword ptr [rsp+0x58] #將XMM0寄存器(16字節)等于rsp+0x58地址到rsp+0x58+16字節地址之間的內容,即將字段Id1和字段Id2的值設給XMM0寄存器ret.go:44       0x470df2        0f11442408                      movups xmmword ptr [rsp+0x8], xmm0#將字段Id1和字段Id2的值依次設置給地址rsp+0x8 和 rsp+0x10ret.go:44       0x470df7        0f10442468                      movups xmm0, xmmword ptr [rsp+0x68]ret.go:44       0x470dfc        0f11442418                      movups xmmword ptr [rsp+0x18], xmm0ret.go:44       0x470e01        0f10442478                      movups xmm0, xmmword ptr [rsp+0x78]ret.go:44       0x470e06        0f11442428                      movups xmmword ptr [rsp+0x28], xmm0ret.go:44       0x470e0b        0f10842488000000                movups xmm0, xmmword ptr [rsp+0x88]ret.go:44       0x470e13        0f11442438                      movups xmmword ptr [rsp+0x38], xmm0ret.go:44       0x470e18        488b0424                        mov rax, qword ptr [rsp]#開始處理返回值,RAX等于rsp地址,即等于字段Id, 則是令RAX返回字段Id的值ret.go:44       0x470e1c        488b5c2408                      mov rbx, qword ptr [rsp+0x8]#RBX 返回字段Id1的值ret.go:44       0x470e21        488b4c2410                      mov rcx, qword ptr [rsp+0x10]#RCX 返回字段Id2的值ret.go:44       0x470e26        488b7c2418                      mov rdi, qword ptr [rsp+0x18]ret.go:44       0x470e2b        488b742420                      mov rsi, qword ptr [rsp+0x20]ret.go:44       0x470e30        4c8b442428                      mov r8, qword ptr [rsp+0x28]ret.go:44       0x470e35        4c8b4c2430                      mov r9, qword ptr [rsp+0x30]ret.go:44       0x470e3a        4c8b542438                      mov r10, qword ptr [rsp+0x38]ret.go:44       0x470e3f        4c8b5c2440                      mov r11, qword ptr [rsp+0x40]#R11 返回字段Id8的值ret.go:44       0x470e44        4881c498000000                  add rsp, 0x98ret.go:44       0x470e4b        5d                              pop rbpret.go:44       0x470e4c        c3                              retret.go:30       0x470e4d        e8aeaaffff                      call $runtime.morestack_noctxtret.go:30       0x470e52        e9e9feffff                      jmp $main.returnMax

可以看到寄存器RAX、RBX、RCX、RDI、RSI、R8、R9、R10、R11這9個寄存器都參與了函數參數和返回值的傳遞。

2.2.2、 棧內存傳值的匯編

此時需要將結構體MaxData的Id9字段放出來,即10個字段時可以觸發函數之間通過棧內存傳值,參數和返回值在函數之間的傳遞原理一樣的,所以這里只分析returnMax返回值的傳遞。

TEXT main.main(SB) /home/gofunc/ret.goret.go:3        0x470b00        4c8d6424d0              lea r12, ptr [rsp-0x30]ret.go:3        0x470b05        4d3b6610                cmp r12, qword ptr [r14+0x10]ret.go:3        0x470b09        765e                    jbe 0x470b69ret.go:3        0x470b0b        55                      push rbpret.go:3        0x470b0c        4889e5                  mov rbp, rsp
=>      ret.go:3        0x470b0f*       4881eca8000000          sub rsp, 0xa8ret.go:4        0x470b16        48c744245006000000      mov qword ptr [rsp+0x50], 0x6#設置變量x=6ret.go:5        0x470b1f        b806000000              mov eax, 0x6 #設置EAX等與變量x的值6,用于調用callMaxTest函數傳參ret.go:5        0x470b24        e857000000              call $main.callMaxTestret.go:6        0x470b29        e852010000              call $main.returnMax#調用returnMax函數ret.go:6        0x470b2e        488d7c2458              lea rdi, ptr [rsp+0x58]ret.go:6        0x470b33        4889e6                  mov rsi, rspret.go:6        0x470b36        660f1f840000000000      nop word ptr [rax+rax*1], axret.go:6        0x470b3f        90                      nopret.go:6        0x470b40        48896c24f0              mov qword ptr [rsp-0x10], rbpret.go:6        0x470b45        488d6c24f0              lea rbp, ptr [rsp-0x10]ret.go:6        0x470b4a        e88bd4ffff              call 0x46dfda#跳到0x46dfda地址處,執行相應機器碼,作用是將rsp+0x8到rsp+0x8+80字節(rsp+0x58)地址之間的內容依次復制到rsp+0x58到rsp+0x58+80字節之間地址上。結合returnMax邏輯,可知,main函數棧rsp+0x58到rsp+0x58+80字節(rsp+0xa8)地址之間的棧內存用于保存returnMax函數返回值。rsp+0x58 = rv.Id,rsp+0x60 = rv.Id1 依次類推。ret.go:6        0x470b4f        488b6d00                mov rbp, qword ptr [rbp]ret.go:7        0x470b53        488b8c2480000000        mov rcx, qword ptr [rsp+0x80]#取rv.Id5值放到RCXret.go:7        0x470b5b        48894c2450              mov qword ptr [rsp+0x50], rcx#令x=rv.Id5ret.go:8        0x470b60        4881c4a8000000          add rsp, 0xa8ret.go:8        0x470b67        5d                      pop rbpret.go:8        0x470b68        c3                      retret.go:3        0x470b69        e892adffff              call $runtime.morestack_noctxtret.go:3        0x470b6e        eb90                    jmp $main.main
TEXT main.returnMax(SB) /home/gofunc/ret.goret.go:30       0x470c80        55                      push rbpret.go:30       0x470c81        4889e5                  mov rbp, rsp
=>      ret.go:30       0x470c84*       4883ec58                sub rsp, 0x58ret.go:30       0x470c88        488d7c2468              lea rdi, ptr [rsp+0x68]ret.go:30       0x470c8d        488d7fd0                lea rdi, ptr [rdi-0x30]#給call 0x46dc70對應函數用的ret.go:30       0x470c91        660f1f840000000000      nop word ptr [rax+rax*1], axret.go:30       0x470c9a        660f1f440000            nop word ptr [rax+rax*1], axret.go:30       0x470ca0        48896c24f0              mov qword ptr [rsp-0x10], rbpret.go:30       0x470ca5        488d6c24f0              lea rbp, ptr [rsp-0x10]ret.go:30       0x470caa        e8c1cfffff              call 0x46dc70 #跳到0x46dc70地址處,執行相應機器碼,作用是將rsp+0x68 到rsp+0x68+80字節(MaxData結構體大小)之間地址置為0ret.go:30       0x470caf        488b6d00                mov rbp, qword ptr [rbp]ret.go:31       0x470cb3        48c7042407000000        mov qword ptr [rsp], 0x7 #令變量y=7ret.go:41       0x470cbb        488d7c2408              lea rdi, ptr [rsp+0x8]#給call 0x46dfda函數用的ret.go:41       0x470cc0        488d35b9dd0100          lea rsi, ptr [rip+0x1ddb9]#給call 0x46dfda函數用的ret.go:41       0x470cc7        48896c24f0              mov qword ptr [rsp-0x10], rbpret.go:41       0x470ccc        488d6c24f0              lea rbp, ptr [rsp-0x10]ret.go:41       0x470cd1        e804d3ffff              call 0x46dfda#跳到0x46dfda地址處,執行相應機器碼,作用是將rip+0x1ddb9到rip+0x1ddb98+80字節地址之間的內容依次復制到rsp+0x8到rsp+0x8+80字節之間地址上。即將變量md的值從數據區復制到returnMax函數的棧上。ret.go:41       0x470cd6        488b6d00                mov rbp, qword ptr [rbp]ret.go:43       0x470cda        488b0424                mov rax, qword ptr [rsp]#令RAX等于變量yret.go:43       0x470cde        4889442430              mov qword ptr [rsp+0x30], rax#令md.Id5=yret.go:44       0x470ce3        488d7c2468              lea rdi, ptr [rsp+0x68]#rsp+0x60是main函數的棧頂ret.go:44       0x470ce8        488d742408              lea rsi, ptr [rsp+0x8]ret.go:44       0x470ced        660f1f840000000000      nop word ptr [rax+rax*1], axret.go:44       0x470cf6        660f1f840000000000      nop word ptr [rax+rax*1], axret.go:44       0x470cff        90                      nopret.go:44       0x470d00        48896c24f0              mov qword ptr [rsp-0x10], rbpret.go:44       0x470d05        488d6c24f0              lea rbp, ptr [rsp-0x10]ret.go:44       0x470d0a        e8cbd2ffff              call 0x46dfda#跳到0x46dfda地址處,執行相應機器碼,作用是將rsp+0x8到rsp+0x8+80字節地址之間的內容依次復制到rsp+0x68到rsp+0x68+80字節之間地址上。即將變量md的值從returnMax函數的棧復制到main函數棧上,實現棧內存傳遞值。ret.go:44       0x470d0f        488b6d00                mov rbp, qword ptr [rbp]ret.go:44       0x470d13        4883c458                add rsp, 0x58ret.go:44       0x470d17        5d                      pop rbpret.go:44       0x470d18        c3                      ret

看到這里讀者就清晰的知道棧內存傳值的原理了,就是caller開辟棧空間(參數和返回值都是caller開辟)時專門多申請一部分內存接收callee的返回值,callee執行時會將返回值直接寫入到caller棧上,樸素而又好用吧。

假設main函數的BP是0x3f0(1008),則main和returnMax的棧結構如下(注意此時MaxData大小是80字節):
go棧布局

三、拓展

在2.2.2小節,可以看到我對call 0x46dc70和call 0x46dfda兩個匯編指令一長串的注釋,怎么得來的呢?

3.1 了解go中的Duff’s Device

Duff’s Device就是將循環展開,減少判斷次數來提升性能的一種機制。
在runtime/mkduff.go中,可以看到在amd x86_64下的兩個Duff函數:runtime.duffzero() 【高效將某段連續內存清零】和runtime·duffcopy【高效將某段連續內存內容復制到另一段內存中】

func zeroAMD64(w io.Writer) {// X15: zero// DI: ptr to memory to be zeroed 通過DI寄存器確定要清零內存得低地址的一側起始地址// DI is updated as a side effect.fmt.Fprintln(w, "TEXT runtime·duffzero<ABIInternal>(SB), NOSPLIT|NOFRAME, $0-0")for i := 0; i < 16; i++ {fmt.Fprintln(w, "\tMOVUPS\tX15,(DI)")fmt.Fprintln(w, "\tMOVUPS\tX15,16(DI)")fmt.Fprintln(w, "\tMOVUPS\tX15,32(DI)")fmt.Fprintln(w, "\tMOVUPS\tX15,48(DI)")fmt.Fprintln(w, "\tLEAQ\t64(DI),DI") // We use lea instead of add, to avoid clobbering flagsfmt.Fprintln(w)}fmt.Fprintln(w, "\tRET")
}
func copyAMD64(w io.Writer) {// SI: ptr to source memory SI寄存器指向源內存地址// DI: ptr to destination memory DI寄存器指向目的內存地址// SI and DI are updated as a side effect.// This is equivalent to a sequence of MOVSQ but for some reason that is 3.5x slower than this code.fmt.Fprintln(w, "TEXT runtime·duffcopy<ABIInternal>(SB), NOSPLIT|NOFRAME, $0-0")for i := 0; i < 64; i++ {fmt.Fprintln(w, "\tMOVUPS\t(SI), X0")fmt.Fprintln(w, "\tADDQ\t$16, SI")fmt.Fprintln(w, "\tMOVUPS\tX0, (DI)")fmt.Fprintln(w, "\tADDQ\t$16, DI")fmt.Fprintln(w)}fmt.Fprintln(w, "\tRET")
}

具體內容可以到runtime/duff_amd64.s文件中看對應匯編內容。
runtime·duffzero就是16個

MOVUPS X15,(DI) #4字節 [44 0f 11 3f] 匯編對應的機器碼
MOVUPS X15,16(DI) #5字節 [44 0f 11 7f 10]
MOVUPS X15,32(DI) #5字節 [44 0f 11 7f 20]
MOVUPS X15,48(DI) #5字節 [44 0f 11 7f 30]
LEAQ 64(DI),DI #4字節 [48 8d 7f 40]

依次展開,一次循環的指令是23字節,留意這個數字。
runtime·duffcopy就是64個

MOVUPS (SI), X0 #3字節
ADDQ $16, SI #4字節
MOVUPS X0, (DI) #3字節
ADDQ $16, DI #4字節

依次展開,一次循環的指令是14字節,留意這個數字。

3.2 go tool compile

我們go tool compile -S -N -l ret.go看看其plan9匯編,選取returnMax函數關鍵部分,與2.2.2小節returnMax函數匯編對比,可以發現

call 0x46dc70 對應 DUFFZERO $336
call 0x46dfda 對應 DUFFCOPY $826
lea rsi, ptr [rip+0x1ddb9] 對應 LEAQ main…stmp_1(SB), SI

那么[DUFFZERO $336]表示跳到在代碼區相對runtime·duffzero第一個機器碼偏移336個字節的機器碼;
那么[DUFFCOPY $826]表示跳到在代碼區相對runtime·duffcopy第一個機器碼偏移826個字節的機器碼;

程序內存一般分為代碼區(程序編譯后的機器碼)、數據區(常量、全局變量等)、堆區、棧區。

main.returnMax STEXT nosplit size=153 args=0x50 locals=0x60 funcid=0x0 align=0x0#...省略0x000d 00013 (/home/gofunc/ret.go:30) LEAQ    -48(DI), DI0x0011 00017 (/home/gofunc/ret.go:30) NOP0x0020 00032 (/home/gofunc/ret.go:30) DUFFZERO        $3360x0033 00051 (/home/gofunc/ret.go:31) PCDATA  $0, $-10x0033 00051 (/home/gofunc/ret.go:31) MOVQ    $7, main.y(SP)0x003b 00059 (/home/gofunc/ret.go:41) LEAQ    main.md+8(SP), DI0x0040 00064 (/home/gofunc/ret.go:41) LEAQ    main..stmp_1(SB), SI0x0047 00071 (/home/gofunc/ret.go:41) PCDATA  $0, $-20x0047 00071 (/home/gofunc/ret.go:41) DUFFCOPY        $826#...省略	0x0098 00152 (/home/gofunc/ret.go:44) RET
main..stmp_1 SRODATA static size=80 #數據區,returnMax函數變量md對應的值(只讀,80字節)0x0000 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00  ................0x0010 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00  ................0x0020 05 00 00 00 00 00 00 00 06 00 00 00 00 00 00 00  ................0x0030 07 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00  ................0x0040 09 00 00 00 00 00 00 00            

LEAQ main…stmp_1(SB), SI表示將在數據區存儲的returnMax函數變量md對應的值的起始地址裝在到SI寄存器中,即方便后面通過DUFFCOPY函數將數據拷貝到returnMax函數棧內存中。

在dlv debug ret.go中,可以通過si命令進入call 0x46dc70和call 0x46dfda的函數中。

3.2 call 0x46dc70 & call 0x46dfda
(dlv) si
> runtime.duffzero() /usr/local/go/src/runtime/duff_amd64.s:95 (PC: 0x46dc70)
Warning: debugging optimized functionduff_amd64.s:89         0x46dc59        440f117f30      movups xmmword ptr [rdi+0x30], xmm15duff_amd64.s:90         0x46dc5e        488d7f40        lea rdi, ptr [rdi+0x40]duff_amd64.s:92         0x46dc62        440f113f        movups xmmword ptr [rdi], xmm15duff_amd64.s:93         0x46dc66        440f117f10      movups xmmword ptr [rdi+0x10], xmm15duff_amd64.s:94         0x46dc6b        440f117f20      movups xmmword ptr [rdi+0x20], xmm15
=>      duff_amd64.s:95         0x46dc70        440f117f30      movups xmmword ptr [rdi+0x30], xmm15 #從本指令開始執行 ,這里在rdi+0x30 基礎上操作的,也解釋了2.2.2小節匯編中lea rdi, ptr [rsp+0x68]之后為啥強行來一個lea rdi, ptr [rsp-0x30]了duff_amd64.s:96         0x46dc75        488d7f40        lea rdi, ptr [rdi+0x40]duff_amd64.s:98         0x46dc79        440f113f        movups xmmword ptr [rdi], xmm15duff_amd64.s:99         0x46dc7d        440f117f10      movups xmmword ptr [rdi+0x10], xmm15duff_amd64.s:100        0x46dc82        440f117f20      movups xmmword ptr [rdi+0x20], xmm15duff_amd64.s:101        0x46dc87        440f117f30      movups xmmword ptr [rdi+0x30], xmm15duff_amd64.s:102        0x46dc8c        488d7f40        lea rdi, ptr [rdi+0x40]duff_amd64.s:104        0x46dc90        c3              ret                   #函數執行結束

顯示,call 0x46dc70是從runtime.duffzero()函數的中間某個位置開始執行的,即duff_amd64.s:95位置,為什么?

在3.1小節中知道runtime·duffzero一個循環的機器碼是23字節,ret指令占1字節,那么runtime·duffzero函數的機器碼是16*23+1=369字節,
在3.2小節中得知call 0x46dc70 對應 DUFFZERO $336,即偏移了336字節,則369-336=33字節,從duff_amd64.s:95位置的機器碼開始算,到duff_amd64.s:104的機器碼正好33字節,這下知道為什么從duff_amd64.s:95位置的機器碼開始執行了嗎?

我們在前置知識中了解到XMM系列寄存器是16字節,那么movups一次就操作16字節:

duff_amd64.s:95
duff_amd64.s:98
duff_amd64.s:99
duff_amd64.s:100
duff_amd64.s:101
這5次清零操作剛好是16*5=80字節,等于結構體MaxData大小了,都是go編譯器精密計算好的。

【call 0x46dfda】匯編如下:

> runtime.duffcopy() /usr/local/go/src/runtime/duff_amd64.s:402 (PC: 0x46dfda)
Warning: debugging optimized functionduff_amd64.s:395        0x46dfc8        4883c710        add rdi, 0x10duff_amd64.s:397        0x46dfcc        0f1006          movups xmm0, xmmword ptr [rsi]duff_amd64.s:398        0x46dfcf        4883c610        add rsi, 0x10duff_amd64.s:399        0x46dfd3        0f1107          movups xmmword ptr [rdi], xmm0duff_amd64.s:400        0x46dfd6        4883c710        add rdi, 0x10
=>      duff_amd64.s:402        0x46dfda        0f1006          movups xmm0, xmmword ptr [rsi] #從本機器碼開始duff_amd64.s:403        0x46dfdd        4883c610        add rsi, 0x10duff_amd64.s:404        0x46dfe1        0f1107          movups xmmword ptr [rdi], xmm0duff_amd64.s:405        0x46dfe4        4883c710        add rdi, 0x10duff_amd64.s:407        0x46dfe8        0f1006          movups xmm0, xmmword ptr [rsi]duff_amd64.s:408        0x46dfeb        4883c610        add rsi, 0x10duff_amd64.s:409        0x46dfef        0f1107          movups xmmword ptr [rdi], xmm0duff_amd64.s:410        0x46dff2        4883c710        add rdi, 0x10duff_amd64.s:412        0x46dff6        0f1006          movups xmm0, xmmword ptr [rsi]duff_amd64.s:413        0x46dff9        4883c610        add rsi, 0x10duff_amd64.s:414        0x46dffd        0f1107          movups xmmword ptr [rdi], xmm0duff_amd64.s:415        0x46e000        4883c710        add rdi, 0x10duff_amd64.s:417        0x46e004        0f1006          movups xmm0, xmmword ptr [rsi]duff_amd64.s:418        0x46e007        4883c610        add rsi, 0x10duff_amd64.s:419        0x46e00b        0f1107          movups xmmword ptr [rdi], xmm0duff_amd64.s:420        0x46e00e        4883c710        add rdi, 0x10duff_amd64.s:422        0x46e012        0f1006          movups xmm0, xmmword ptr [rsi]duff_amd64.s:423        0x46e015        4883c610        add rsi, 0x10duff_amd64.s:424        0x46e019        0f1107          movups xmmword ptr [rdi], xmm0duff_amd64.s:425        0x46e01c        4883c710        add rdi, 0x10duff_amd64.s:427        0x46e020        c3              ret

有興趣的朋友可以在評論區說出其偏移原理了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/76512.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/76512.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/76512.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python-1. 找單獨的數

問題描述 在一個班級中&#xff0c;每位同學都拿到了一張卡片&#xff0c;上面有一個整數。有趣的是&#xff0c;除了一個數字之外&#xff0c;所有的數字都恰好出現了兩次。現在需要你幫助班長小C快速找到那個拿了獨特數字卡片的同學手上的數字是什么。 要求&#xff1a; 設…

算法學習C++需注意的基本知識

文章目錄 01_算法中C需注意的基本知識cmath頭文件一些計算符ASCII碼表數據類型長度運算符cout固定輸出格式浮點數的比較max排序自定義類型字符的大小寫轉換與判斷判斷字符是數字還是字母 02_數據結構需要注意的內容1.stringgetline函數的使用string::findsubstr截取字符串strin…

從零開始寫android 的智能指針

Android中定義了兩種智能指針類型&#xff0c;一種是強指針sp&#xff08;strong pointer&#xff09;&#xff0c;源碼中的位置在system/core/include/utils/StrongPointer.h。另外一種是弱指針&#xff08;weak pointer&#xff09;。其實稱之為強引用和弱引用更合適一些。強…

【leetcode hot 100 152】乘積最大子數組

錯誤解法&#xff1a;db[i]表示以i結尾的最大的非空連續&#xff0c;動態規劃&#xff1a;dp[i] Math.max(nums[i], nums[i] * dp[i - 1]); class Solution {public int maxProduct(int[] nums) {int n nums.length;int[] dp new int[n]; // db[i]表示以i結尾的最大的非空連…

圖論整理復習

回溯&#xff1a; 模板&#xff1a; void backtracking(參數) {if (終止條件) {存放結果;return;}for (選擇&#xff1a;本層集合中元素&#xff08;樹中節點孩子的數量就是集合的大小&#xff09;) {處理節點;backtracking(路徑&#xff0c;選擇列表); // 遞歸回溯&#xff…

uniapp離線打包提示未添加videoplayer模塊

uniapp中使用到video標簽&#xff0c;但是離線打包放到安卓工程中&#xff0c;運行到真機中時提示如下&#xff1a; 解決方案&#xff1a; 1、把media-release.aar、weex_videoplayer-release.aar放到工程的libs目錄下; 文檔&#xff1a;https://nativesupport.dcloud.net.cn/…

打包構建替換App名稱

方案適用背景 一套代碼出多個安裝包&#xff0c;且安裝包的應用名稱、圖標都不一樣考慮三語名稱問題 通過 Gradle 腳本實現 gradle.properties 里面定義標識來區分應用&#xff0c;如下文里的 APP_TYPEAAA 、APP_TYPEBBB// 定義 groovy 替換方法 def replaceAppName(String …

DrissionPage移動端自動化:從H5到原生App的跨界測試

一、移動端自動化測試的挑戰與機遇 移動端測試面臨多維度挑戰&#xff1a; 設備碎片化&#xff1a;Android/iOS版本、屏幕分辨率差異 混合應用架構&#xff1a;H5頁面與原生組件的深度耦合 交互復雜性&#xff1a;多點觸控、手勢操作、傳感器模擬 性能監控&#xff1a;內存…

達夢數據庫用函數實現身份證合法校驗

達夢數據庫用函數實現身份證合法校驗 拿走不謝~ CREATE OR REPLACE FUNCTION CHECK_IDCARD(A_SFZ IN VARCHAR2) RETURN VARCHAR2 IS TYPE WEIGHT_TAB IS VARRAY(17) OF NUMBER; TYPE CHECK_TAB IS VARRAY(11) OF CHAR; WEIGHT_FACTOR WEIGHT_TAB : WEIGHT_TAB(7,9,10,5,8,4,…

3dmax的python通過普通的攝像頭動捕表情

1、安裝python 進入cdm&#xff0c;打python要能顯示版本號 >>>&#xff08;進入python提示符模式&#xff09; import sys sys.path顯示python的安裝路徑&#xff0c; 進入到python.exe的路徑 在python目錄中安裝(ctrlz退出python交互模式) 2、pip install mediapipe…

國產Linux統信安裝mysql8教程步驟

系統環境 uname -a Linux FlencherHU-PC 6.12.9-amd64-desktop-rolling #23.01.01.18 SMP PREEMPT_DYNAMIC Fri Jan 10 18:29:31 CST 2025 x86_64 GNU/Linux下載離線安裝包 瀏覽器下載https://downloads.mysql.com/archives/get/p/23/file/mysql-test-8.0.33-linux-glibc2.28…

Vite 權限繞過導致任意文件讀取(CVE-2025-32395)(附腳本)

免責申明: 本文所描述的漏洞及其復現步驟僅供網絡安全研究與教育目的使用。任何人不得將本文提供的信息用于非法目的或未經授權的系統測試。作者不對任何由于使用本文信息而導致的直接或間接損害承擔責任。如涉及侵權,請及時與我們聯系,我們將盡快處理并刪除相關內容。 前言…

poi-tl

官網地址 Poi-tl Documentationword模板引擎https://deepoove.com/poi-tl github 地址 https://github.com/Sayi/poi-tl/tree/master gitcode 加速地址 GitCode - 全球開發者的開源社區,開源代碼托管平臺GitCode是面向全球開發者的開源社區,包括原創博客,開源代碼托管,代碼…

操作系統 4.1-I/O與顯示器

外設工作起來 操作系統讓外設工作的基本原理和過程&#xff0c;具體來說&#xff0c;它概括了以下幾個關鍵步驟&#xff1a; 發出指令&#xff1a;操作系統通過向控制器中的寄存器發送指令來啟動外設的工作。這些指令通常是通過I/O指令&#xff08;如out指令&#xff09;來實現…

琥珀掃描 2.0.5.0 | 文檔處理全能助手,支持掃描、文字提取及表格識別

琥珀掃描是一款功能強大的文檔處理應用程序。它不僅僅支持基本的文檔掃描功能&#xff0c;還涵蓋了文字提取、證件掃描、表格識別等多種實用功能。無論是學生、職員還是教師&#xff0c;都能從中找到適合自己的功能。該應用支持拍照生成電子件&#xff0c;并能自動矯正文檔邊緣…

jQuery UI 小部件方法調用詳解

jQuery UI 小部件方法調用詳解 引言 jQuery UI 是一個基于 jQuery 的用戶界面和交互庫,它提供了一系列小部件,如按鈕、對話框、進度條等,這些小部件極大地豐富了網頁的交互性和用戶體驗。本文將詳細介紹 jQuery UI 中小部件的方法調用,幫助開發者更好地理解和應用這些小部…

浮點數比較在Eigen數學庫中的處理方法

浮點數比較在Eigen數學庫中的處理方法 在Eigen數學庫中進行浮點數比較時&#xff0c;由于浮點數的精度問題&#xff0c;直接使用運算符通常不是推薦的做法。Eigen提供了幾種更安全的方法來進行浮點數比較&#xff1a; 1. 近似相等比較 使用isApprox()函數進行近似比較&#…

Linux-----驅動

一、內核驅動與啟動流程 1. Linux內核驅動 Nor Flash: 可線性訪問&#xff0c;有專門的數據及地址總線&#xff08;與內存訪問方式相同&#xff09;。 Nand Flash: 不可線性訪問&#xff0c;訪問需要控制邏輯&#xff08;軟件&#xff09;。 2. Linux啟動流程 ARM架構: IRAM…

Wincc腳本全部不運行

Wincc腳本全部不運行 前言解決辦法操作步驟 前言 這里主要是指舊項目移植到Wincc的高版本&#xff0c;移植后界面的一些功能均會失效。&#xff08;例如腳本不執行&#xff0c;項目編輯器不可用等情況&#xff09; 解決辦法 Wincc的項目文件中有Dcf文件&#xff0c;Dcf文件包…

使用numpy構建邏輯回歸模型及訓練流程

邏輯回歸模型構建及訓練流程 關于邏輯回歸的數據&#xff0c;有很多學習?的?例樣本。這?我們使?scikit learn提供的數據集?成函數來創建 具體參數可參照官網 Scikit-learn 是? Python 開發的開源機器學習庫&#xff0c;?泛?于數據挖掘和數據分析。 特點&#xff1a;易…