文章目錄
- 概要
- 一、前置知識
- 二、匯編分析
- 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 常見寄存器:
另外還有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字節):
三、拓展
在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
有興趣的朋友可以在評論區說出其偏移原理了。