go 函数调用规则(一)

go逆向首先了解他的函数调用规则。

环境搭建在go 环境搭建

代码这样

package main

func val(c, d int) (a int, b int) {
	e := 5
	f := 6
	a = c + d + e + f
	b = d * 2
	return
}

func test() {
	i, j := val(1, 2)
	i = i + 3
	j = j + 4
}

func main() {
	test()
}

然后编译成二进制文件

go build -gcflags "-N -l" 1.go

gcflags可以向go编译器传递参数。

-N参数代表禁止优化, -l参数代表禁止内联,
go在编译目标程序的时候会嵌入运行时(runtime)的二进制,
禁止优化和内联可以让运行时(runtime)中的函数变得更容易调试.

然后就好多了。

先看main函数的汇编

sub     rsp, 8
mov     [rsp+8+var_8], rbp
lea     rbp, [rsp+8+var_8]
call    main_test
mov     rbp, [rsp+8+var_8]
add     rsp, 8
nop
retn

显然我们看到因为main没有参数
就是把栈抬高八个字节
然后rbp放进去
然后放的这个地方是rbp新地址。

函数返回的时候正常返回就可以了。

单从这里来看
效果跟C语言函数调用的效果是一样的。
都是压一个rbp进去
只不过是具体的汇编不一样。

再来看test函数

sub     rsp, 38h                      #两个参数此时rsp指向main函数中call的时候压进去的返回地址
mov     [rsp+38h+var_8], rbp          #0x38的栈最下面八个字节是rbp
lea     rbp, [rsp+38h+var_8]
mov     eax, 1                        #两个参数乱入
mov     ebx, 2                    
xchg    ax, ax                        #xchg是交换,ax交换ax,啥也不干,cherest师傅说是废话文学
call    main_val                      #又把返回地址压进去之后先不看下面的先去看看val函数

                                      #经过下面的函数调用回来之后我们现在知道
                                      #开了0x38的栈  最下面8个字节是栈底
                                      #然后最上面两个是函数调用时候的传参
                                      #rax  rbx里面放着返回值
mov     [rsp+38h+var_10], rax         #把返回值放进临时变量i  j 
mov     [rsp+38h+var_18], rbx
mov     rcx, [rsp+38h+var_10]         #剩下的四个qword就是两个i  两个j
mov     [rsp+38h+var_20], rcx         #两个的目的是进行了i = i + 3这样的操作
mov     rcx, [rsp+38h+var_18]         #可能因为关了优化  导致有点空间上的浪费
mov     [rsp+38h+var_28], rcx         #但是并不影响我们研究函数的调用规则
mov     rcx, [rsp+38h+var_20]
add     rcx, 3
mov     [rsp+38h+var_20], rcx
mov     rcx, [rsp+38h+var_28]
add     rcx, 4
mov     [rsp+38h+var_28], rcx
mov     rbp, [rsp+38h+var_8]
add     rsp, 38h
retn                                 #最后返回

val

var_28= qword ptr -28h
var_20= qword ptr -20h
var_18= qword ptr -18h
var_10= qword ptr -10h
var_8= qword ptr -8
arg_0= qword ptr  8
arg_8= qword ptr  10h


sub     rsp, 28h                    #这个函数栈空间开了0x28
mov     [rsp+28h+var_8], rbp        #然后又是rbp放进去这个操作
lea     rbp, [rsp+28h+var_8]
mov     [rsp+28h+arg_0], rax        #rax是第一个参数 放在了返回地址下面第一个qword
mov     [rsp+28h+arg_8], rbx        #rbx是第二个参数 放在返回地址下面第二个qword
mov     [rsp+28h+var_10], 0         #这连着的四个显然就是两个返回值跟两个临时变量
mov     [rsp+28h+var_18], 0
mov     [rsp+28h+var_20], 5
mov     [rsp+28h+var_28], 6
mov     rcx, [rsp+28h+arg_0]        #这里就是做那个加法,都加在rcx里面
add     rcx, [rsp+28h+arg_8]        #参数就是用之前rax  rbx加进去的。
add     rcx, [rsp+28h+var_20]
add     rcx, 6
mov     [rsp+28h+var_10], rcx       #最后的结果放进返回值那里
mov     rbx, [rsp+28h+arg_8]
shl     rbx, 1                      
mov     [rsp+28h+var_18], rbx       #对临时变量b进行处理
mov     rax, [rsp+28h+var_10]       #然后rax  rbx又分别是两个返回值   rax是第一个返回值  rbx是第二个返回值
mov     rbp, [rsp+28h+var_8]
add     rsp, 28h
retn

我们总结一下。
其实单论栈布局来讲这与C语言的栈布局并没有什么区别。
栈指针在栈底
后面跟着返回地址
再后面跟着参数
不一样的只不过是整体实现过程中的具体汇编代码,或者说整个的过程有差别。

函数调用方面传参用的是rax rbx 返回值也是rax rbx。

参数方面是stdcall的调用规则。从右往左压参。
说白了就是咱平常说的返回地址下面是第一个参数,然后是第二个第三个。

然后临时变量的分布也有规律这里的话是先压返回值,然后压临时变量
但是我们不关心,平常可能也会被优化掉。

那么问题来了
我们的传参参数多了剩下的寄存器应该是啥?
如果我们一个函数中调用了好几个函数,栈又是怎样的分布?
带着问题我们接着实验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值