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.mzph.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;易…