您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > Go语言

golang函数调用流程详解

时间:2020-09-22 09:56:45  来源:  作者:

前言

不管是C语言还是golang语言,都有自己的函数调用流程,主要是在函数调用过程中,各种寄存器和内存堆栈的变化. 理解清楚整个函数调用流程,可以加深对golang语言的了解.

编译源代码

对下面的简单函数,通过反汇编和调试器来看下golang的函数调用流程,主要是函数调用过程中的参数传递和关键寄存器的变化。

golang函数调用流程详解

 

为了避免编译器的优化,加上-gcflags '-l -N'选项,-gcflags是给编译器的选项,通过go tool compile可以看到选项列表,-l表示禁止内联,-N表示禁止优化。一般我们要看一些细节的时候,都需要把这两个选项带上。

#go build -gcflags '-l -N'

 

golang函数调用流程详解

 

通过如下命令得到反汇编信息

#go tool objdump --gnu -S 0913 > tmp.s

打开tmp.s文件,找到main.test2,能看到main.main和main.test2的反汇编信息,这儿把plan9会把和gnu汇编都显示。

golang函数调用流程详解

 

整个调用流程如下

golang函数调用流程详解

 

下面开始具体分析.

前导和结尾

分析main.main,发现golang编译器给函数固定插入的前导和结尾有两部分.

第一部分如下.其作用是保证当前goroutine的栈空间足够,其方法是通过得到当前栈空间接近底部的一个地址0x10(CX)(g.stack.stackguard0)并和当前SP比较,如果SP的值小于等于0X10(CX)的值,那么栈的空间已经马上不够用了,必须进行扩容,然后就会jmp到runtime.morestack_noctxt进行扩容,完成之后再JMP到main.main,如果还是不够,就再扩容,直到检查到够了。 因为golang中的goroutine使用的栈都是新建的,初始值默认为2K,随着函数调用层数增加,或者有些函数的局部变量占用空间过大,会导致不够用,这个时候就需要扩容了. 由于这个处理扩容的代码是golang编译器加入的,我们就不用关心了.

0x45dac0              64488b0c25f8ffffff      MOVQ FS:0xfffffff8, CX               // mov %fs:0xfffffff8,%rcx
0x45dac9              483b6110                CMPQ 0x10(CX), SP                    // cmp 0x10(%rcx),%rsp
0x45dacd              0f8687000000            JBE 0x45db5a                         // jbe 0x45db5a
。。。
0x45db5a              e821aeffff              CALL runtime.morestack_noctxt(SB)    // callq 0x458980
0x45db5f              90                      NOPL                                 // nop
0x45db60              e95bffffff              JMP main.main(SB)                    // jmpq 0x45dac0

第二部分如下.如果对C编译好的代码进行反汇编也能看到基本完全相同的汇编代码.这部分代码是对callee进行栈空间的分配和回收的.进入一个callee的时候,(0)SP是返回地址,也就是callee执行完成之后,caller要执行的指令地址。

0x45dad3 4883ec48 SUBQ $0x48, SP // sub $0x48,%rsp
0x45dad7 48896c2440 MOVQ BP, 0x40(SP) // mov %rbp,0x40(%rsp)
0x45dadc 488d6c2440 LEAQ 0x40(SP), BP // lea 0x40(%rsp),%rbp
。。。
0x45db50 488b6c2440 MOVQ 0x40(SP), BP // mov 0x40(%rsp),%rbp
0x45db55 4883c448 ADDQ $0x48, SP // add $0x48,%rsp
0x45db59 c3 RET // retq

先将SP的地址下移0x48,这个就是main.main的栈帧大小(不同的函数需要的栈帧大小不一样,所以这儿的值不同,但是在编译的时候是可以计算出每个函数的局部变量需要的大小,以及调用其他函数需要传参使用的大小的),main.main的局部变量就放在这里面的。注意这儿把main.main栈帧的顶部,也就是(0X40)SP放了老的BP的值,然后把这个地址放到了BP里面。

这样就BP的值是一个地址,这个地址里面存放着上一个栈帧的BP的值,其也是一个地址,这样BP的值就弄成了一个链表,可以不断向上串联起来。当然,如果我们不关心这个事情,那么BP是不需要的,实际上现在gcc有个选项就可以关闭对BP寄存器的使用,golang编译器在优化的情况下也会不使用BP寄存器。栈帧里面所有的变量通过SP寄存器进行偏移就可以访问到了。我们看上面的汇编代码,确实都没有用到BP寄存器来定位变量。

在函数调用完成的时候,通过上面相反的调用顺序将栈空间进行回收.

详细调用过程分析

上面已经介绍了基本过程.下面再通过分析main.main调用main.test2的整个过程来加深理解,推荐通过dlv工具来自己一步一步的走,可以加深理解.

进入代码目录使用dlv debug命令开始调试,然后使用b main.main设置断点,c开始运行,使用disassembly看反汇编代码,使用si命令来单指令执行.

1.main.main CALL main.test2之前的栈帧

也就是上面的0x45daf2执行之前的寄存器和栈的情况.现在RBP和RSP是main.main的栈帧,然后main.test2需要的两个入参已经准备好了.

golang函数调用流程详解

 

2.main.main CALL main.test2之后的栈帧

也就是0x45daf2执行之后的寄存器和栈的情况. 这个时候SP的值-=8,里面存放main.test2执行完成之后返回main.main的执行指令的地址,也就是CALL下面那条指令的地址.由于RIP的值被CALL指令修改了,CPU执行的下一条指令就是main.test2的第一条指令了.

golang函数调用流程详解

 

main.test2(以及其他函数)本身的执行流程如下.

首先是栈帧的准备,然后是返回值的初始值设置.然后是核心的计算逻辑代码,然后是根据计算的结果设置返回值.最后销毁栈帧并返回.

golang函数调用流程详解

 

3.main.test2准备栈帧

通过将RSP的值调整小(栈帧向下生长),扩展好main.test2函数的栈帧,然后设置和RBP的值来标记栈帧的开始,同时让各个栈帧的RBP本身可以形成一个链表,方便调试器查找.

golang函数调用流程详解

 

4.初始化返回值,完成计算逻辑,保存返回值.

golang的一个函数返回值默认值需要为0,因为我们可以直接使用不带参数的return语句.这儿将返回值设置为0值.

在通过相应的指令完成计算逻辑之后,把返回值保持到main.main的栈帧里面,注意这整个过程中RSP和RBP都不会变化的,局部变量,入参,出参都是通过对RSP进行偏移得到的(入参,出参会偏移到main.main的栈帧里面,局部变量偏移到自己的栈帧里面),具体的偏移值编译器在编译过程中是可以计算出来的. 注意全f的表示值-1.

golang函数调用流程详解

 

5.销毁栈帧,返回main.main

在恢复了BP和SP之后,SP处值为main.main中CALL语句压入的下一条语句的地址.

main.test2的最后一条RET语句执行完成之后,RIP的值值变成SP处的值,SP+=8.栈帧完全恢复到CALL语句之前,唯一变的是main.main栈帧中返回值的两个地址里面变成了经过main.test2执行的值.然后SP-0X28这部分的内存就无效了,虽然里面的内容并没有被清空为全0.

golang函数调用流程详解

 



Tags:golang函数   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
前言不管是C语言还是golang语言,都有自己的函数调用流程,主要是在函数调用过程中,各种寄存器和内存堆栈的变化. 理解清楚整个函数调用流程,可以加深对golang语言的了解.编译...【详细内容】
2020-09-22  Tags: golang函数  点击:(165)  评论:(0)  加入收藏
▌简易百科推荐
zip 是一种常见的归档格式,本文讲解 Go 如何操作 zip。首先看看 zip 文件是如何工作的。以一个小文件为例:(类 Unix 系统下)$ cat hello.textHello!执行 zip 命令进行归档:$ zip...【详细内容】
2021-12-17  Go语言中文网    Tags:Go语言   点击:(12)  评论:(0)  加入收藏
大家好,我是 polarisxu。前段时间,Russ Cox 明确了泛型相关的事情,原计划在标准库中加入泛型相关的包,改放到 golang.org/x/exp 下。目前,Go 泛型的主要设计者 ianlancetaylor 完...【详细内容】
2021-11-30  Go语言中文网    Tags:slices 包   点击:(24)  评论:(0)  加入收藏
前言最近因为项目需要写了一段时间的 Go ,相对于 Java 来说语法简单同时又有着一些 Python 之类的语法糖,让人大呼”真香“。 但现阶段相对来说还是 Python 写的多一些,偶尔还...【详细内容】
2021-11-25  crossoverJie    Tags:Go   点击:(29)  评论:(0)  加入收藏
go-micro是基于 Go 语言用于开发的微服务的 RPC 框架,主要功能如下:服务发现,负载均衡 ,消息编码,请求/响应,Async Messaging,可插拔接口,最后这个功能牛p安装步骤安装proto...【详细内容】
2021-09-06    石老师小跟班  Tags:go-micro   点击:(196)  评论:(0)  加入收藏
GoLand 2021.2 EAP 5 现已发布。用户可以从工具箱应用程序中获得 EAP 构建,也可以从官方网站手动下载。并且从此 EAP 开始,只有拥有有效的 JetBrains 帐户才能加入该计划。手...【详细内容】
2021-06-29  IT实战联盟  今日头条  Tags:GoLand   点击:(185)  评论:(0)  加入收藏
作者:HDT3213今天给大家带来的开源项目是 Godis:一个用 Go 语言实现的 Redis 服务器。支持: 5 种数据结构(string、list、hash、set、sortedset) 自动过期(TTL) 发布订阅、地理位...【详细内容】
2021-06-18  HelloGitHub  今日头条  Tags:Go   点击:(125)  评论:(0)  加入收藏
统一规范篇合理规划目录本篇主要描述了公司内部同事都必须遵守的一些开发规矩,如统一开发空间,既使用统一的开发工具来保证代码最后的格式的统一,开发中对文件和代码长度的控制...【详细内容】
2021-05-18  1024课堂    Tags:Go语言   点击:(232)  评论:(0)  加入收藏
闭包概述 闭包不是Go语言独有的概念,在很多编程语言中都有闭包 闭包就是解决局部变量不能被外部访问的一种解决方案 是把函数当作返回值的一种应用 代码演示总体思想:在函数...【详细内容】
2021-05-14  HelloGo  今日头条  Tags:Go语言   点击:(223)  评论:(0)  加入收藏
一时想不开,想了解一下Go语言,于是安装了并体验了一下。下载1. 进入golang.google.cn 点击Download Go 2.选择对应的操作系统,点击后开始下载。 安装1. windows下执行傻瓜式安...【详细内容】
2021-05-12  程序员fearlazy  fearlazy  Tags:Go语言   点击:(236)  评论:(0)  加入收藏
1.简介channel是Go语言的一大特性,基于channel有很多值得探讨的问题,如 channel为什么是并发安全的? 同步通道和异步通道有啥区别? 通道为何会阻塞协程? 使用通道导致阻塞的协程...【详细内容】
2021-05-10  程序员麻辣烫  今日头条  Tags:Go通道   点击:(272)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条