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 之前,堆栈的分布如下:

SP0x0 
 0x4 
 0x8 
 0xc 
 0x10 
 0x140xA
 0x180x3
 0x1c 
 0x20 
 0x24 
 0x28 
 0x2c 
 0x30 
 0x340x3
 0x380xA
SP0x3cLR

此时r0= 0x3  r1 = 0xA

进入doOperator函数后,略过前面的堆栈检查,

  .text:0049D1F0                 STR             LR, [SP,#var_C]!

地址如下:

 0x2c  
 0x30  
 0x340x3 
 0x380xA 
SP0x3cLR 
 0x40  
 0x44  
SP0x48LR实际是地址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  
 0x2c0 
 0x300 
 0x340x3 
 0x380xA 
SP0x3cLR 
 0x40add_result 
 0x44sub_result 
SP0x48LR实际是地址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  
 0x2csub_result 
 0x30add_result 
 0x340x3 
 0x380xA 
SP0x3cLR 
 0x40add_result 
 0x44sub_result 
SP0x48LR实际是地址0049D16C

接下来我们执行了函数返回,函数跳回到0049D16C地址执行,SP也回到前一个地址,也就是SP=0x3c
.text:0049D230                 LDR             PC, [SP+0xC+var_C],#0xC

此时内存如下:

 0x24  
 0x28  
 0x2csub_result 
 0x30add_result 
 0x340x3 
 0x380xA 
SP0x3cLR 
 0x40add_result这里的栈内容已经释放
 0x44sub_result
 0x48LR

.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

概括一下就是下面这样,也没想太明白为啥这里反复的复制数据。。。

SP0x0 
 0x4add_result
 0x8sub_result
 0xcadd_result
 0x10sub_result
 0x140xA
 0x180x3
 0x1c 

.text:0049D194                 CMP             R1, R0   比较add_result 和 sub_result
.text:0049D198                 BGT             loc_49D1A0   如果add_result大则执行打印部分代码

总结一下,go语言的多参数传递其实是下面这回事

返回值存储的空间(0x2c,0x30)是跟着参数一起,传递给 doOperator,传递的规则如下:

 0x2csub_resultdoOperator 返回参数2
 0x30add_resultdoOperator 返回参数1
 0x340x3doOperator 的参数2
 0x380xAdoOperator 的参数1
SP0x3cLR 

在doOperator执行完后,会将数据copy回去到这个地址,这就是奥秘