go语言具有多返回值的能力,我们本文就来探究其实现方法。
首先我们弄一个测试程序如下:
func doOperator(x, y int) (addRet int, subRet int) {
return x + y, x - y
}
func main() {
var x = 10
var y = 3
var a, b = doOperator(x, y)
if a > b {
fmt.Printf("abc")
return
}
}
我们以非优化的方式进行编译,并且设置arch为arm
go build -gcflags "-N -l"
最终我们得到的汇编代码主要如下:
.text:0049D13C ; =============== S U B R O U T I N E =======================================
.text:0049D13C
.text:0049D13C
.text:0049D13C EXPORT main.main
.text:0049D13C main.main ; CODE XREF: runtime.main+1E4p
.text:0049D13C ; main.main+9Cj
.text:0049D13C ; DATA XREF: ...
.text:0049D13C
.text:0049D13C var_3C = -0x3C
.text:0049D13C var_38 = -0x38
.text:0049D13C var_34 = -0x34
.text:0049D13C var_30 = -0x30
.text:0049D13C var_2C = -0x2C
.text:0049D13C var_28 = -0x28
.text:0049D13C var_18 = -0x18
.text:0049D13C var_14 = -0x14
.text:0049D13C var_10 = -0x10
.text:0049D13C var_C = -0xC
.text:0049D13C var_8 = -8
.text:0049D13C var_4 = -4
.text:0049D13C
.text:0049D13C LDR R1, [R10,#8]
.text:0049D140 CMP SP, R1
.text:0049D144 BLS loc_49D1D0
.text:0049D148 STR LR, [SP,#var_3C]!
.text:0049D14C MOV R0, #0xA
.text:0049D150 STR R0, [SP,#0x3C+var_14]
.text:0049D154 MOV R0, #3
.text:0049D158 STR R0, [SP,#0x3C+var_18]
.text:0049D15C LDR R1, [SP,#0x3C+var_14]
.text:0049D160 STR R1, [SP,#0x3C+var_38]
.text:0049D164 STR R0, [SP,#0x3C+var_34]
.text:0049D168 BL main.doOperator
.text:0049D16C LDR R0, [SP,#0x3C+var_30]
.text:0049D170 STR R0, [SP,#0x3C+var_4]
.text:0049D174 LDR R0, [SP,#0x3C+var_2C]
.text:0049D178 STR R0, [SP,#0x3C+var_8]
.text:0049D17C LDR R0, [SP,#0x3C+var_4]
.text:0049D180 STR R0, [SP,#0x3C+var_C]
.text:0049D184 LDR R0, [SP,#0x3C+var_8]
.text:0049D188 STR R0, [SP,#0x3C+var_10]
.text:0049D18C LDR R0, [SP,#0x3C+var_10]
.text:0049D190 LDR R1, [SP,#0x3C+var_C]
.text:0049D194 CMP R1, R0
.text:0049D198 BGT loc_49D1A0
.text:0049D19C B loc_49D1CC
.text:0049D1A0 ; ---------------------------------------------------------------------------
.text:0049D1A0
.text:0049D1A0 loc_49D1A0 ; CODE XREF: main.main+5Cj
.text:0049D1A0 LDR R0, =(a25Cccfcocslllm+0x13A) ; "abcendgc gp intip4mapnilobjpc=ptr碌s渭s"...
.text:0049D1A4 STR R0, [SP,#0x3C+var_38]
.text:0049D1A8 MOV R0, #3
.text:0049D1AC STR R0, [SP,#0x3C+var_34]
.text:0049D1B0 MOV R0, #0
.text:0049D1B4 STR R0, [SP,#0x3C+var_30]
.text:0049D1B8 MOV R0, #0
.text:0049D1BC STR R0, [SP,#0x3C+var_2C]
.text:0049D1C0 STR R0, [SP,#0x3C+var_28]
.text:0049D1C4 BL fmt.Printf
.text:0049D1C8 LDR PC, [SP+0x3C+var_3C],#0x3C
.text:0049D1CC ; ---------------------------------------------------------------------------
.text:0049D1CC
.text:0049D1CC loc_49D1CC ; CODE XREF: main.main+60j
.text:0049D1CC LDR PC, [SP+0x3C+var_3C],#0x3C
.text:0049D1D0 ; ---------------------------------------------------------------------------
.text:0049D1D0
.text:0049D1D0 loc_49D1D0 ; CODE XREF: main.main+8j
.text:0049D1D0 MOV R3, LR
.text:0049D1D4 BL runtime.morestack_noctxt
.text:0049D1D8 B main.main
.text:0049D1D8 ; End of function main.main
.text:0049D1D8
.text:0049D1DC ; ---------------------------------------------------------------------------
.text:0049D1DC
.text:0049D1DC loc_49D1DC ; CODE XREF: .text:loc_49D1DCj
.text:0049D1DC B loc_49D1DC
.text:0049D1DC ; ---------------------------------------------------------------------------
.text:0049D1E0 off_49D1E0 DCD a25Cccfcocslllm+0x13A ; DATA XREF: main.main:loc_49D1A0r
.text:0049D1E0 ; "abcendgc gp intip4mapnilobjpc=ptr碌s渭s"...
.text:0049D1E4
.text:0049D1E4 ; =============== S U B R O U T I N E =======================================
.text:0049D1E4
.text:0049D1E4
.text:0049D1E4 EXPORT main.doOperator
.text:0049D1E4 main.doOperator ; CODE XREF: main.main+2Cp
.text:0049D1E4 ; main.doOperator+58j
.text:0049D1E4 ; DATA XREF: ...
.text:0049D1E4
.text:0049D1E4 var_C = -0xC
.text:0049D1E4 var_8 = -8
.text:0049D1E4 var_4 = -4
.text:0049D1E4 arg_4 = 4
.text:0049D1E4 arg_8 = 8
.text:0049D1E4 arg_C = 0xC
.text:0049D1E4 arg_10 = 0x10
.text:0049D1E4
.text:0049D1E4 LDR R1, [R10,#8]
.text:0049D1E8 CMP SP, R1
.text:0049D1EC BLS loc_49D234
.text:0049D1F0 STR LR, [SP,#var_C]!
.text:0049D1F4 MOV R0, #0
.text:0049D1F8 STR R0, [SP,#0xC+arg_C]
.text:0049D1FC STR R0, [SP,#0xC+arg_10]
.text:0049D200 LDR R0, [SP,#0xC+arg_8]
.text:0049D204 LDR R1, [SP,#0xC+arg_4]
.text:0049D208 ADD R0, R1, R0
.text:0049D20C STR R0, [SP,#0xC+var_4]
.text:0049D210 LDR R0, [SP,#0xC+arg_4]
.text:0049D214 LDR R1, [SP,#0xC+arg_8]
.text:0049D218 SUB R0, R0, R1
.text:0049D21C STR R0, [SP,#0xC+var_8]
.text:0049D220 LDR R0, [SP,#0xC+var_4]
.text:0049D224 STR R0, [SP,#0xC+arg_C]
.text:0049D228 LDR R0, [SP,#0xC+var_8]
.text:0049D22C STR R0, [SP,#0xC+arg_10]
.text:0049D230 LDR PC, [SP+0xC+var_C],#0xC
.text:0049D234 ; ---------------------------------------------------------------------------
.text:0049D234
.text:0049D234 loc_49D234 ; CODE XREF: main.doOperator+8j
.text:0049D234 MOV R3, LR
.text:0049D238 BL runtime.morestack_noctxt
.text:0049D23C B main.doOperator
.text:0049D23C ; End of function main.doOperator
.text:0049D23C
我们来看执行到 .text:0049D168 BL main.doOperator 之前,堆栈的分布如下:
SP | 0x0 | |
0x4 | ||
0x8 | ||
0xc | ||
0x10 | ||
0x14 | 0xA | |
0x18 | 0x3 | |
0x1c | ||
0x20 | ||
0x24 | ||
0x28 | ||
0x2c | ||
0x30 | ||
0x34 | 0x3 | |
0x38 | 0xA | |
SP | 0x3c | LR |
此时r0= 0x3 r1 = 0xA
进入doOperator函数后,略过前面的堆栈检查,
.text:0049D1F0 STR LR, [SP,#var_C]!
地址如下:
0x2c | |||
0x30 | |||
0x34 | 0x3 | ||
0x38 | 0xA | ||
SP | 0x3c | LR | |
0x40 | |||
0x44 | |||
SP | 0x48 | LR | 实际是地址0049D16C |
.text:0049D1F4 MOV R0, #0
.text:0049D1F8 STR R0, [SP,#0xC+arg_C] 此时 0x30 = 0
.text:0049D1FC STR R0, [SP,#0xC+arg_10] 此时 0x2c = 0
.text:0049D200 LDR R0, [SP,#0xC+arg_8] 此时r0加载成为 0x3
.text:0049D204 LDR R1, [SP,#0xC+arg_4] 此时r1加载成为 0xa
.text:0049D208 ADD R0, R1, R 这里计算得到和
.text:0049D20C STR R0, [SP,#0xC+var_4] 此时0x40 就是和,这个实际是 doOperator 里面的临时变量,存放了和
同理我们得到下面的
.text:0049D210 LDR R0, [SP,#0xC+arg_4] 此时r0加载成为 0xA
.text:0049D214 LDR R1, [SP,#0xC+arg_8] 此时r0加载成为 0x3
.text:0049D218 SUB R0, R0, R1
.text:0049D21C STR R0, [SP,#0xC+var_8] 此时0x44 就是差,这个实际是doOperator里面的临时变量,存放了差。
此时内存如下:
0x28 | |||
0x2c | 0 | ||
0x30 | 0 | ||
0x34 | 0x3 | ||
0x38 | 0xA | ||
SP | 0x3c | LR | |
0x40 | add_result | ||
0x44 | sub_result | ||
SP | 0x48 | LR | 实际是地址0049D16C |
继续接下来看汇编代码
.text:0049D220 LDR R0, [SP,#0xC+var_4] 将add_result放到r0
.text:0049D224 STR R0, [SP,#0xC+arg_C] 将add_result转存到 0x30 位置
.text:0049D228 LDR R0, [SP,#0xC+var_8] 将sub_result放到r0
.text:0049D22C STR R0, [SP,#0xC+arg_10] 将sub_result转存到 0x2c 位置
此时内存分布
0x24 | |||
0x28 | |||
0x2c | sub_result | ||
0x30 | add_result | ||
0x34 | 0x3 | ||
0x38 | 0xA | ||
SP | 0x3c | LR | |
0x40 | add_result | ||
0x44 | sub_result | ||
SP | 0x48 | LR | 实际是地址0049D16C |
接下来我们执行了函数返回,函数跳回到0049D16C地址执行,SP也回到前一个地址,也就是SP=0x3c
.text:0049D230 LDR PC, [SP+0xC+var_C],#0xC
此时内存如下:
0x24 | |||
0x28 | |||
0x2c | sub_result | ||
0x30 | add_result | ||
0x34 | 0x3 | ||
0x38 | 0xA | ||
SP | 0x3c | LR | |
0x40 | add_result | 这里的栈内容已经释放 | |
0x44 | sub_result | ||
0x48 | LR |
.text:0049D16C LDR R0, [SP,#0x3C+var_30] 将 add_result 放到r0
.text:0049D170 STR R0, [SP,#0x3C+var_4] 将 add_result 转存到 0x4
.text:0049D174 LDR R0, [SP,#0x3C+var_2C] 将 sub_result 放到r0
.text:0049D178 STR R0, [SP,#0x3C+var_8] 将sub_result转存到 0x8
.text:0049D17C LDR R0, [SP,#0x3C+var_4] 将add_result 放到 r0
.text:0049D180 STR R0, [SP,#0x3C+var_C] 将add_result转存到 0xc
.text:0049D184 LDR R0, [SP,#0x3C+var_8] 将 sub_result 放到r0
.text:0049D188 STR R0, [SP,#0x3C+var_10] 将 sub_result 转存到 0x10
.text:0049D18C LDR R0, [SP,#0x3C+var_10] 将sub_result 放到r0
.text:0049D190 LDR R1, [SP,#0x3C+var_C] 将add_result放到r1
概括一下就是下面这样,也没想太明白为啥这里反复的复制数据。。。
SP | 0x0 | |
0x4 | add_result | |
0x8 | sub_result | |
0xc | add_result | |
0x10 | sub_result | |
0x14 | 0xA | |
0x18 | 0x3 | |
0x1c |
.text:0049D194 CMP R1, R0 比较add_result 和 sub_result
.text:0049D198 BGT loc_49D1A0 如果add_result大则执行打印部分代码
总结一下,go语言的多参数传递其实是下面这回事
返回值存储的空间(0x2c,0x30)是跟着参数一起,传递给 doOperator,传递的规则如下:
0x2c | sub_result | doOperator 返回参数2 | |
0x30 | add_result | doOperator 返回参数1 | |
0x34 | 0x3 | doOperator 的参数2 | |
0x38 | 0xA | doOperator 的参数1 | |
SP | 0x3c | LR |
在doOperator执行完后,会将数据copy回去到这个地址,这就是奥秘