|
|
<!DOCTYPE html>
|
|
|
<html lang="zh-cn" dir="ltr">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<meta name="description" content="调用栈 # 这一章节延续前面《 准备篇-Go汇编 》那一章节。这一章节将从一个实例出发详细分析Go 语言中函数调用栈。这一章节会涉及caller,callee,寄存器相关概念,如果还不太了解可以去《 准备篇-Go汇编 》查看了解。
|
|
|
在详细分析函数栈之前,我们先复习以下几个概念。
|
|
|
caller 与 callee # 如果一个函数调用另外一个函数,那么该函数被称为调用者函数,也叫做caller,而被调用的函数称为被调用者函数,也叫做callee。比如函数main中调用sum函数,那么main就是caller,而sum函数就是callee。
|
|
|
栈帧 # 栈帧(stack frame)指的是未完成函数所持有的,独立连续的栈区域,用来保存其局部变量,返回地址等信息。
|
|
|
函数调用约定 # 函数调用约定(Calling Conventions)是 ABI(Application Binary Interface) 的组成部分,它描述了:
|
|
|
如何将执行控制权交给callee,以及返还给caller 如何保存和恢复caller的状态 如何将参数传递个callee 如何从callee获取返回值 简而言之,一句话就是函数调用约定指的是约定了函数调用时候,函数参数如何传递,函数栈由谁完成平衡,以及函数返回值如何返回的。
|
|
|
在Go语言中,函数的参数和返回值的存储空间是由其caller的栈帧提供。这也为Go语言为啥支持多返回值以及总是值传递的原因。从Go汇编层面看,在callee中访问其参数和返回值,是通过FP寄存器来操作的(在实现层面是通过SP寄存器访问的)。Go语言中函数参数入栈顺序是从右到左入栈的。
|
|
|
函数调用时候,会为其分配栈空间用来存放临时变量,返回值等信息,当完成调用后,这些栈空间应该进行回收,以恢复调用以前的状态。这个过程就是栈平衡。栈平衡工作可以由被调用者本身(callee)完成,也可以由其调用者(caller)完成。在Go语言中是由callee来完成栈平衡的。
|
|
|
函数栈 # 当前函数作为caller,其本身拥有的栈帧以及其所有callee的栈帧,可以称为该函数的函数栈,也称函数调用栈。C语言中函数栈大小是固定的,如果超出栈空间,就会栈溢出异常。比如递归求斐波拉契,这时候可以使用尾调用来优化。由于Go 语言栈可以自动进行分裂扩容,栈空间不够时候,可以自动进行扩容。当用火焰图分析性能时候,火焰越高,说明栈越深。
|
|
|
Go 语言中函数栈全景图如下:
|
|
|
Go语言函数调用栈 接下来的函数调用栈分析,都是基于函数栈的全景图出发。知道该全景图每一部分含义也就了解函数调用栈。
|
|
|
实例分析 # 我们将分析如下代码。
|
|
|
package main func sum(a, b int) int { sum := 0 sum = a + b return sum } func main() { a := 3 b := 5 print(sum(a, b)) } 参照前面的函数栈全景图,我们画出main函数调用sum函数时的函数调用栈图:">
|
|
|
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
|
|
|
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#343a40">
|
|
|
<meta name="color-scheme" content="light dark"><meta property="og:title" content="Go函数调用栈" />
|
|
|
<meta property="og:description" content="调用栈 # 这一章节延续前面《 准备篇-Go汇编 》那一章节。这一章节将从一个实例出发详细分析Go 语言中函数调用栈。这一章节会涉及caller,callee,寄存器相关概念,如果还不太了解可以去《 准备篇-Go汇编 》查看了解。
|
|
|
在详细分析函数栈之前,我们先复习以下几个概念。
|
|
|
caller 与 callee # 如果一个函数调用另外一个函数,那么该函数被称为调用者函数,也叫做caller,而被调用的函数称为被调用者函数,也叫做callee。比如函数main中调用sum函数,那么main就是caller,而sum函数就是callee。
|
|
|
栈帧 # 栈帧(stack frame)指的是未完成函数所持有的,独立连续的栈区域,用来保存其局部变量,返回地址等信息。
|
|
|
函数调用约定 # 函数调用约定(Calling Conventions)是 ABI(Application Binary Interface) 的组成部分,它描述了:
|
|
|
如何将执行控制权交给callee,以及返还给caller 如何保存和恢复caller的状态 如何将参数传递个callee 如何从callee获取返回值 简而言之,一句话就是函数调用约定指的是约定了函数调用时候,函数参数如何传递,函数栈由谁完成平衡,以及函数返回值如何返回的。
|
|
|
在Go语言中,函数的参数和返回值的存储空间是由其caller的栈帧提供。这也为Go语言为啥支持多返回值以及总是值传递的原因。从Go汇编层面看,在callee中访问其参数和返回值,是通过FP寄存器来操作的(在实现层面是通过SP寄存器访问的)。Go语言中函数参数入栈顺序是从右到左入栈的。
|
|
|
函数调用时候,会为其分配栈空间用来存放临时变量,返回值等信息,当完成调用后,这些栈空间应该进行回收,以恢复调用以前的状态。这个过程就是栈平衡。栈平衡工作可以由被调用者本身(callee)完成,也可以由其调用者(caller)完成。在Go语言中是由callee来完成栈平衡的。
|
|
|
函数栈 # 当前函数作为caller,其本身拥有的栈帧以及其所有callee的栈帧,可以称为该函数的函数栈,也称函数调用栈。C语言中函数栈大小是固定的,如果超出栈空间,就会栈溢出异常。比如递归求斐波拉契,这时候可以使用尾调用来优化。由于Go 语言栈可以自动进行分裂扩容,栈空间不够时候,可以自动进行扩容。当用火焰图分析性能时候,火焰越高,说明栈越深。
|
|
|
Go 语言中函数栈全景图如下:
|
|
|
Go语言函数调用栈 接下来的函数调用栈分析,都是基于函数栈的全景图出发。知道该全景图每一部分含义也就了解函数调用栈。
|
|
|
实例分析 # 我们将分析如下代码。
|
|
|
package main func sum(a, b int) int { sum := 0 sum = a + b return sum } func main() { a := 3 b := 5 print(sum(a, b)) } 参照前面的函数栈全景图,我们画出main函数调用sum函数时的函数调用栈图:" />
|
|
|
<meta property="og:type" content="article" />
|
|
|
<meta property="og:url" content="https://go.cyub.vip/function/call-stack/" /><meta property="article:section" content="function" />
|
|
|
|
|
|
|
|
|
<title>Go函数调用栈 | 深入Go语言之旅</title>
|
|
|
<link rel="manifest" href="/manifest.json">
|
|
|
<link rel="icon" href="/favicon.png" >
|
|
|
<link rel="stylesheet" href="/book.min.f06572240ce28e67eb332ac5cf5d59a696c47ad4c6f700d5842c5ed93dd8ec77.css" integrity="sha256-8GVyJAzijmfrMyrFz11ZppbEetTG9wDVhCxe2T3Y7Hc=" crossorigin="anonymous">
|
|
|
<script defer src="/flexsearch.min.js"></script>
|
|
|
<script defer src="/en.search.min.93e09a692e83129c8b0cc95c521eab7d4f5256b35651cf852c4ad4185bdc55a5.js" integrity="sha256-k+CaaS6DEpyLDMlcUh6rfU9SVrNWUc+FLErUGFvcVaU=" crossorigin="anonymous"></script>
|
|
|
|
|
|
<script async src="https://www.googletagmanager.com/gtag/js?id=G-BQ229RRTTX"></script>
|
|
|
<script>
|
|
|
var doNotTrack = false;
|
|
|
if (!doNotTrack) {
|
|
|
window.dataLayer = window.dataLayer || [];
|
|
|
function gtag(){dataLayer.push(arguments);}
|
|
|
gtag('js', new Date());
|
|
|
gtag('config', 'G-BQ229RRTTX', { 'anonymize_ip': false });
|
|
|
}
|
|
|
</script>
|
|
|
<!--
|
|
|
Made with Book Theme
|
|
|
https://github.com/alex-shpak/hugo-book
|
|
|
-->
|
|
|
|
|
|
</head>
|
|
|
<body dir="ltr">
|
|
|
<input type="checkbox" class="hidden toggle" id="menu-control" />
|
|
|
<input type="checkbox" class="hidden toggle" id="toc-control" />
|
|
|
<main class="container flex">
|
|
|
<aside class="book-menu">
|
|
|
<div class="book-menu-content">
|
|
|
|
|
|
<nav>
|
|
|
<h2 class="book-brand">
|
|
|
<a class="flex align-center" href="/"><img src="https://static.cyub.vip/images/202310/golang-480.png" alt="Logo" /><span>深入Go语言之旅</span>
|
|
|
</a>
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
<div class="book-search">
|
|
|
<input type="text" id="book-search-input" placeholder="Search" aria-label="Search" maxlength="64" data-hotkeys="s/" />
|
|
|
<div class="book-search-spinner hidden"></div>
|
|
|
<ul id="book-search-results"></ul>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<li>
|
|
|
<a href="https://www.cyub.vip/" target="_blank" rel="noopener">
|
|
|
个人博客
|
|
|
</a>
|
|
|
</li>
|
|
|
|
|
|
<li>
|
|
|
<a href="https://github.com/cyub" target="_blank" rel="noopener">
|
|
|
Github主页
|
|
|
</a>
|
|
|
</li>
|
|
|
|
|
|
<li>
|
|
|
<a href="https://www.topgoer.cn/?ref=go.cyub.vip" target="_blank" rel="noopener">
|
|
|
地鼠文档
|
|
|
</a>
|
|
|
</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<ul>
|
|
|
<li>
|
|
|
<p><strong>
|
|
|
<a href="/">深入Go语言之旅</a></strong></p>
|
|
|
</li>
|
|
|
<li>
|
|
|
<p><strong>准备篇</strong></p>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/compiler/">编译流程</a></li>
|
|
|
<li>
|
|
|
<a href="/analysis-tools/">分析工具</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/analysis-tools/gdb/">GDB</a></li>
|
|
|
<li>
|
|
|
<a href="/analysis-tools/dlv/">Delve</a></li>
|
|
|
<li>
|
|
|
<a href="/analysis-tools/go-buildin-tools/">Go 内置工具</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<a href="/go-assembly/">Go汇编</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<p><strong>基础篇</strong></p>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/type/">数据类型与数据结构</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/type/string/">字符串</a></li>
|
|
|
<li>
|
|
|
<a href="/type/array/">数组</a></li>
|
|
|
<li>
|
|
|
<a href="/type/slice/">切片</a></li>
|
|
|
<li>
|
|
|
<a href="/type/nil/">nil</a></li>
|
|
|
<li>
|
|
|
<a href="/type/empty_struct/">空结构体</a></li>
|
|
|
<li>
|
|
|
<a href="/type/pointer/">指针</a></li>
|
|
|
<li>
|
|
|
<a href="/type/map/">映射</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<a href="/function/">函数</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/function/first-class/">一等公民</a></li>
|
|
|
<li>
|
|
|
<a href="/function/call-stack/"class=active>函数调用栈</a></li>
|
|
|
<li>
|
|
|
<a href="/function/pass-by-value/">值传递</a></li>
|
|
|
<li>
|
|
|
<a href="/function/closure/">闭包</a></li>
|
|
|
<li>
|
|
|
<a href="/function/method/">方法</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<a href="/feature/">语言特性</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/feature/comma-ok/">逗号ok模式</a></li>
|
|
|
<li>
|
|
|
<a href="/feature/for-range/">遍历 - for-range语法</a></li>
|
|
|
<li>
|
|
|
<a href="/feature/defer/">延迟执行 - defer语法</a></li>
|
|
|
<li>
|
|
|
<a href="/feature/select/">通道选择器 - select语法</a></li>
|
|
|
<li>
|
|
|
<a href="/feature/panic-recover/">恐慌与恢复 - panic/recover</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<p><strong>运行时篇</strong></p>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/concurrency/">并发编程</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/concurrency/memory-model/">内存模型</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/context/">上下文 - context</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/channel/">通道 - channel</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/atomic/">原子操作 - atomic</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-map/">并发Map - sync.Map</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-waitgroup/">等待组 - sync.WaitGroup</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-once/">一次性操作 - sync.Once</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-pool/">缓冲池 - sync.Pool</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-cond/">条件变量 - sync.Cond</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-mutex/">互斥锁 - sync.Mutex</a></li>
|
|
|
<li>
|
|
|
<a href="/concurrency/sync-rwmutex/">读写锁 - sync.RWMutex</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<a href="/gmp/">G-M-P调度机制</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/gmp/gmp-model/">调度机制概述</a></li>
|
|
|
<li>
|
|
|
<a href="/gmp/scheduler/">调度器</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<a href="/memory/">内存管理</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/memory/allocator/">内存分配器</a></li>
|
|
|
<li>
|
|
|
<a href="/memory/gc/">GC</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
<li>
|
|
|
<a href="/type-system/">类型系统</a>
|
|
|
<ul>
|
|
|
<li>
|
|
|
<a href="/type-system/type/">类型系统</a></li>
|
|
|
<li>
|
|
|
<a href="/type-system/interface/">接口</a></li>
|
|
|
<li>
|
|
|
<a href="/type-system/reflect/">反射</a></li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
</ul>
|
|
|
</li>
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script>(function(){var e=document.querySelector("aside .book-menu-content");addEventListener("beforeunload",function(){localStorage.setItem("menu.scrollTop",e.scrollTop)}),e.scrollTop=localStorage.getItem("menu.scrollTop")})()</script>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
</aside>
|
|
|
|
|
|
<div class="book-page">
|
|
|
<header class="book-header">
|
|
|
|
|
|
<div class="flex align-center justify-between">
|
|
|
<label for="menu-control">
|
|
|
<img src="/svg/menu.svg" class="book-icon" alt="Menu" />
|
|
|
</label>
|
|
|
|
|
|
<strong>Go函数调用栈</strong>
|
|
|
|
|
|
<label for="toc-control">
|
|
|
|
|
|
</label>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<article class="markdown"><h1 id="调用栈">
|
|
|
调用栈
|
|
|
<a class="anchor" href="#%e8%b0%83%e7%94%a8%e6%a0%88">#</a>
|
|
|
</h1>
|
|
|
<p>这一章节延续前面《<strong>
|
|
|
<a href="/go-assembly/">准备篇-Go汇编</a></strong> 》那一章节。这一章节将从一个实例出发详细分析Go 语言中函数调用栈。这一章节会涉及caller,callee,寄存器相关概念,如果还不太了解可以去《<strong>
|
|
|
<a href="/go-assembly/">准备篇-Go汇编</a></strong> 》查看了解。</p>
|
|
|
<p>在详细分析函数栈之前,我们先复习以下几个概念。</p>
|
|
|
<h2 id="caller-与-callee">
|
|
|
caller 与 callee
|
|
|
<a class="anchor" href="#caller-%e4%b8%8e-callee">#</a>
|
|
|
</h2>
|
|
|
<p>如果一个函数调用另外一个函数,那么该函数被称为调用者函数,也叫做caller,而被调用的函数称为被调用者函数,也叫做callee。比如函数main中调用sum函数,那么main就是caller,而sum函数就是callee。</p>
|
|
|
<h2 id="栈帧">
|
|
|
栈帧
|
|
|
<a class="anchor" href="#%e6%a0%88%e5%b8%a7">#</a>
|
|
|
</h2>
|
|
|
<p>栈帧(stack frame)指的是未完成函数所持有的,独立连续的栈区域,用来保存其局部变量,返回地址等信息。</p>
|
|
|
<h2 id="函数调用约定">
|
|
|
函数调用约定
|
|
|
<a class="anchor" href="#%e5%87%bd%e6%95%b0%e8%b0%83%e7%94%a8%e7%ba%a6%e5%ae%9a">#</a>
|
|
|
</h2>
|
|
|
<p>函数调用约定(Calling Conventions)是 <strong>
|
|
|
<a href="https://en.wikipedia.org/wiki/Application_binary_interface">ABI</a></strong>(Application Binary Interface) 的组成部分,它描述了:</p>
|
|
|
<ul>
|
|
|
<li>如何将执行控制权交给callee,以及返还给caller</li>
|
|
|
<li>如何保存和恢复caller的状态</li>
|
|
|
<li>如何将参数传递个callee</li>
|
|
|
<li>如何从callee获取返回值</li>
|
|
|
</ul>
|
|
|
<p>简而言之,一句话就是<strong>函数调用约定</strong>指的是约定了函数调用时候,函数参数如何传递,函数栈由谁完成平衡,以及函数返回值如何返回的。</p>
|
|
|
<p>在Go语言中,<strong>函数的参数和返回值的存储空间是由其caller的栈帧提供</strong>。这也为Go语言为啥支持多返回值以及总是值传递的原因。从Go汇编层面看,在callee中访问其参数和返回值,是通过FP寄存器来操作的(在实现层面是通过SP寄存器访问的)。<strong>Go语言中函数参数入栈顺序是从右到左入栈的</strong>。</p>
|
|
|
<p>函数调用时候,会为其分配栈空间用来存放临时变量,返回值等信息,当完成调用后,这些栈空间应该进行回收,以恢复调用以前的状态。这个过程就是<strong>栈平衡</strong>。栈平衡工作可以由被调用者本身(callee)完成,也可以由其调用者(caller)完成。<strong>在Go语言中是由callee来完成栈平衡的</strong>。</p>
|
|
|
<h2 id="函数栈">
|
|
|
函数栈
|
|
|
<a class="anchor" href="#%e5%87%bd%e6%95%b0%e6%a0%88">#</a>
|
|
|
</h2>
|
|
|
<p>当前函数作为caller,其本身拥有的栈帧以及其所有callee的栈帧,可以称为该函数的函数栈,也称函数调用栈。C语言中函数栈大小是固定的,如果超出栈空间,就会栈溢出异常。比如递归求斐波拉契,这时候可以使用尾调用来优化。由于Go 语言栈可以自动进行分裂扩容,栈空间不够时候,可以自动进行扩容。当用火焰图分析性能时候,火焰越高,说明栈越深。</p>
|
|
|
<p>Go 语言中函数栈全景图如下:</p>
|
|
|
<figure class="text-center"><img src="https://static.cyub.vip/images/202105/go-func-stack.png" width="400px"/><figcaption>
|
|
|
<h4>Go语言函数调用栈</h4>
|
|
|
</figcaption>
|
|
|
</figure>
|
|
|
|
|
|
<p>接下来的函数调用栈分析,都是基于函数栈的全景图出发。知道该全景图每一部分含义也就了解函数调用栈。</p>
|
|
|
<h2 id="实例分析">
|
|
|
实例分析
|
|
|
<a class="anchor" href="#%e5%ae%9e%e4%be%8b%e5%88%86%e6%9e%90">#</a>
|
|
|
</h2>
|
|
|
<p>我们将分析如下代码。</p>
|
|
|
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
|
|
|
</span></span><span style="display:flex;"><span>
|
|
|
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">sum</span>(<span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> <span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">int</span> {
|
|
|
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">sum</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>
|
|
|
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">sum</span> = <span style="color:#a6e22e">a</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">b</span>
|
|
|
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">sum</span>
|
|
|
</span></span><span style="display:flex;"><span>}
|
|
|
</span></span><span style="display:flex;"><span>
|
|
|
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
|
|
|
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">a</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">3</span>
|
|
|
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">b</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">5</span>
|
|
|
</span></span><span style="display:flex;"><span> print(<span style="color:#a6e22e">sum</span>(<span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span>))
|
|
|
</span></span><span style="display:flex;"><span>}
|
|
|
</span></span></code></pre></div><p>参照前面的函数栈全景图,我们画出main函数调用sum函数时的函数调用栈图:</p>
|
|
|
<figure class="text-center"><img src="https://static.cyub.vip/images/202105/go-stack-sum.png" width="400px"/><figcaption>
|
|
|
<h4>main函数调用栈</h4>
|
|
|
</figcaption>
|
|
|
</figure>
|
|
|
|
|
|
<p>从栈底往栈顶,我们依次可以看到:</p>
|
|
|
<ul>
|
|
|
<li>main函数的caller的基址(Base Pointer)。这部分是黄色区域。</li>
|
|
|
<li>main函数局部变量a,b。我们看到a,b变量按照他们出现的顺序依次入栈,在实际指令中可能出现指令重排,a,b变量入栈顺序可能相反,但这个不影响最终结果。这部分是蓝色区域。</li>
|
|
|
<li>接下来是绿色区域,这部分是用来存放sum函数返回值的。这部分空间是提前分配好了。由于sum函数返回值只有一个,且是int类型,那么绿色区域大小是8字节(64位系统下int占用8字节)。在sum函数内部是通过FP寄存器访问这个栈空间的。</li>
|
|
|
<li>在下来就是浅黄色区域,这个是存放sum函数实参的。从上面介绍中我们知道Go语言中函数参数是从右到左入栈的,sum函数的签名是<code>func sum(a, b int) int</code>,那么b=5会先入栈,a=3接着入栈。</li>
|
|
|
<li>接下来是粉红色区域,这部分存放的是return address。main函数调用sum函数时候,会将sum函数后面的一条指令入栈。从main函数caller的基址空间到此处都属于main的函数栈帧。</li>
|
|
|
<li>接下来就是sum函数栈帧空间部分。首先同main函数栈帧空间一样,其存放的sum函数caller的基址,由于sum函数的caller就是main函数,所以这个地方存放就是main栈帧的栈底地址。
|
|
|
….</li>
|
|
|
</ul>
|
|
|
<h3 id="从汇编的角度观察">
|
|
|
从汇编的角度观察
|
|
|
<a class="anchor" href="#%e4%bb%8e%e6%b1%87%e7%bc%96%e7%9a%84%e8%a7%92%e5%ba%a6%e8%a7%82%e5%af%9f">#</a>
|
|
|
</h3>
|
|
|
<p>接下来我们从Go 汇编角度查看main函数调用sum函数时的函数调用栈。</p>
|
|
|
<p>Go语言中函数的栈帧空间是提前分配好的,分配的空间用来存放函数局部变量,被调用函数参数,被调用函数返回值,返回地址等信息。我们来看下main函数和sum函数的汇编定义:</p>
|
|
|
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-as" data-lang="as"><span style="display:flex;"><span><span style="color:#a6e22e">TEXT</span> <span style="color:#e6db74">""</span>.<span style="color:#a6e22e">main</span>(<span style="color:#a6e22e">SB</span>)<span style="color:#f92672">,</span> <span style="color:#a6e22e">ABIInternal</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">$56</span><span style="color:#f92672">-</span><span style="color:#ae81ff">0</span> <span style="color:#75715e">// main函数定义
|
|
|
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">TEXT</span> <span style="color:#e6db74">""</span>.<span style="color:#a6e22e">sum</span>(<span style="color:#a6e22e">SB</span>)<span style="color:#f92672">,</span> <span style="color:#a6e22e">NOSPLIT</span><span style="color:#f92672">|</span><span style="color:#a6e22e">ABIInternal</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">$16</span><span style="color:#f92672">-</span><span style="color:#ae81ff">24</span> <span style="color:#75715e">// sum函数定义
|
|
|
</span></span></span></code></pre></div><p>从上面函数定义可以看出来给main函数分配的栈帧空间大小是56字节大小(这里面的56字节大小,是不包括返回地址空间的,实际上main函数的栈帧大小是56+8(返回地址占用8字节空间大小) = 64字节大小),由于main函数没有参数和返回值,所以参数和返回值这部分大小是0。给sum函数分配的栈帧空间大小是16字节大小,sum函数参数有2个,且都是int类型,返回值是int类型,所以参数和返回值大小是24字节。</p>
|
|
|
<p>关于函数声明时每个字段的含义可以去《<strong>
|
|
|
<a href="/go-assembly/#函数声明">准备篇-Go汇编-函数声明</a></strong> 》 查看:</p>
|
|
|
<p>需要注意的有两点:</p>
|
|
|
<ol>
|
|
|
<li>函数分配的栈空间足以放下所有被调用者信息,如果一个函数会调用很多其他函数,那么它的栈空间是按照其调用函数中最大空间要求来分配的。</li>
|
|
|
<li>函数栈空间是可以split。当栈空间不足时候,会进行split,重新找一块2倍当前栈空间的内存空间,将当前栈帧信息拷贝过去,这个叫栈分裂。Go语言在栈分裂基础上实现了抢占式调度,这个我们会在后续篇章详细探讨。我们可以使用 <code>//go:nosplit</code> 这个编译指示,强制函数不进行栈分裂。从sum函数定义可以看出来,其没有进行栈分裂处理。</li>
|
|
|
</ol>
|
|
|
<p>接下来我们分析main函数的汇编代码:</p>
|
|
|
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>0x0000 <span style="color:#ae81ff">00000</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> TEXT <span style="color:#e6db74">""</span>.main<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>, ABIInternal, $56-0 <span style="color:#75715e"># main函数定义</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0000 <span style="color:#ae81ff">00000</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> MOVQ <span style="color:#f92672">(</span>TLS<span style="color:#f92672">)</span>, CX <span style="color:#75715e"># 将本地线程存储信息保存到CX寄存器中</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0009 <span style="color:#ae81ff">00009</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> CMPQ SP, 16<span style="color:#f92672">(</span>CX<span style="color:#f92672">)</span> <span style="color:#75715e"># 比较当前栈顶地址(SP寄存器存放的)与本地线程存储的栈顶地址</span>
|
|
|
</span></span><span style="display:flex;"><span>0x000d <span style="color:#ae81ff">00013</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> PCDATA $0, $-<span style="color:#ae81ff">2</span> <span style="color:#75715e"># PCDATA,FUNCDATA用于Go汇编额外信息,不必关注</span>
|
|
|
</span></span><span style="display:flex;"><span>0x000d <span style="color:#ae81ff">00013</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> JLS <span style="color:#ae81ff">114</span> <span style="color:#75715e"># 如果当前栈顶地址(SP寄存器存放的)小于本地线程存储的栈顶地址,则跳到114处代码处进行栈分裂扩容操作</span>
|
|
|
</span></span><span style="display:flex;"><span>0x000f <span style="color:#ae81ff">00015</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> PCDATA $0, $-<span style="color:#ae81ff">1</span>
|
|
|
</span></span><span style="display:flex;"><span>0x000f <span style="color:#ae81ff">00015</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> SUBQ $56, SP <span style="color:#75715e"># 提前分配好56字节空间,作为main函数的栈帧,注意此时的SP寄存器指向,会往下移动了56个字节</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0013 <span style="color:#ae81ff">00019</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> MOVQ BP, 48<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span> <span style="color:#75715e"># BP寄存器存放的是main函数caller的基址,movq这条指令是将main函数caller的基址入栈。对应就是上图中我们看到的main函数栈帧的黄色区域。</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0018 <span style="color:#ae81ff">00024</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> LEAQ 48<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>, BP <span style="color:#75715e"># 将main函数的基址存放到到BP寄存器</span>
|
|
|
</span></span><span style="display:flex;"><span>0x001d <span style="color:#ae81ff">00029</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> PCDATA $0, $-<span style="color:#ae81ff">2</span>
|
|
|
</span></span><span style="display:flex;"><span>0x001d <span style="color:#ae81ff">00029</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> PCDATA $1, $-<span style="color:#ae81ff">2</span>
|
|
|
</span></span><span style="display:flex;"><span>0x001d <span style="color:#ae81ff">00029</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>
|
|
|
</span></span><span style="display:flex;"><span>0x001d <span style="color:#ae81ff">00029</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>
|
|
|
</span></span><span style="display:flex;"><span>0x001d <span style="color:#ae81ff">00029</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>
|
|
|
</span></span><span style="display:flex;"><span>0x001d <span style="color:#ae81ff">00029</span> <span style="color:#f92672">(</span>main.go:10<span style="color:#f92672">)</span> PCDATA $0, $0
|
|
|
</span></span><span style="display:flex;"><span>0x001d <span style="color:#ae81ff">00029</span> <span style="color:#f92672">(</span>main.go:10<span style="color:#f92672">)</span> PCDATA $1, $0
|
|
|
</span></span><span style="display:flex;"><span>0x001d <span style="color:#ae81ff">00029</span> <span style="color:#f92672">(</span>main.go:10<span style="color:#f92672">)</span> MOVQ $3, <span style="color:#e6db74">""</span>.a+32<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span> <span style="color:#75715e"># main函数局部变量a入栈</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0026 <span style="color:#ae81ff">00038</span> <span style="color:#f92672">(</span>main.go:11<span style="color:#f92672">)</span> MOVQ $5, <span style="color:#e6db74">""</span>.b+24<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span> <span style="color:#75715e"># main函数局部变量b入栈</span>
|
|
|
</span></span><span style="display:flex;"><span>0x002f <span style="color:#ae81ff">00047</span> <span style="color:#f92672">(</span>main.go:12<span style="color:#f92672">)</span> MOVQ <span style="color:#e6db74">""</span>.a+32<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>, AX <span style="color:#75715e"># 将局部变量a保存到AX寄存中</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0034 <span style="color:#ae81ff">00052</span> <span style="color:#f92672">(</span>main.go:12<span style="color:#f92672">)</span> MOVQ AX, <span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span> <span style="color:#75715e"># sum函数第二个参数</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0038 <span style="color:#ae81ff">00056</span> <span style="color:#f92672">(</span>main.go:12<span style="color:#f92672">)</span> MOVQ $5, 8<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span> <span style="color:#75715e"># sum函数第一个参数</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0041 <span style="color:#ae81ff">00065</span> <span style="color:#f92672">(</span>main.go:12<span style="color:#f92672">)</span> CALL <span style="color:#e6db74">""</span>.sum<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span> <span style="color:#75715e"># 通过call指令调用sum函数。此时会隐式进行两个操作:1. 将当前指令的下一条指令的地址入栈。当前指令下一条指令就是MOVQ 16(SP), AX,其相对地址是0x0046。2. IP指令寄存器指向了sum函数指令入库地址。</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0046 <span style="color:#ae81ff">00070</span> <span style="color:#f92672">(</span>main.go:12<span style="color:#f92672">)</span> MOVQ 16<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>, AX <span style="color:#75715e">#将sum函数值保存AX寄存中。16(SP) 存放的是sum函数的返回值</span>
|
|
|
</span></span><span style="display:flex;"><span>0x004b <span style="color:#ae81ff">00075</span> <span style="color:#f92672">(</span>main.go:12<span style="color:#f92672">)</span> MOVQ AX, <span style="color:#e6db74">""</span>..autotmp_2+40<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0050 <span style="color:#ae81ff">00080</span> <span style="color:#f92672">(</span>main.go:12<span style="color:#f92672">)</span> CALL runtime.printlock<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0055 <span style="color:#ae81ff">00085</span> <span style="color:#f92672">(</span>main.go:12<span style="color:#f92672">)</span> MOVQ <span style="color:#e6db74">""</span>..autotmp_2+40<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>, AX
|
|
|
</span></span><span style="display:flex;"><span>0x005a <span style="color:#ae81ff">00090</span> <span style="color:#f92672">(</span>main.go:12<span style="color:#f92672">)</span> MOVQ AX, <span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>
|
|
|
</span></span><span style="display:flex;"><span>0x005e <span style="color:#ae81ff">00094</span> <span style="color:#f92672">(</span>main.go:12<span style="color:#f92672">)</span> CALL runtime.printint<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0063 <span style="color:#ae81ff">00099</span> <span style="color:#f92672">(</span>main.go:12<span style="color:#f92672">)</span> CALL runtime.printunlock<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0068 <span style="color:#ae81ff">00104</span> <span style="color:#f92672">(</span>main.go:13<span style="color:#f92672">)</span> MOVQ 48<span style="color:#f92672">(</span>SP<span style="color:#f92672">)</span>, BP
|
|
|
</span></span><span style="display:flex;"><span>0x006d <span style="color:#ae81ff">00109</span> <span style="color:#f92672">(</span>main.go:13<span style="color:#f92672">)</span> ADDQ $56, SP
|
|
|
</span></span><span style="display:flex;"><span>0x0071 <span style="color:#ae81ff">00113</span> <span style="color:#f92672">(</span>main.go:13<span style="color:#f92672">)</span> RET
|
|
|
</span></span><span style="display:flex;"><span>0x0072 <span style="color:#ae81ff">00114</span> <span style="color:#f92672">(</span>main.go:13<span style="color:#f92672">)</span> NOP
|
|
|
</span></span><span style="display:flex;"><span>0x0072 <span style="color:#ae81ff">00114</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> PCDATA $1, $-<span style="color:#ae81ff">1</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0072 <span style="color:#ae81ff">00114</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> PCDATA $0, $-<span style="color:#ae81ff">2</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0072 <span style="color:#ae81ff">00114</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> CALL runtime.morestack_noctxt<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span> <span style="color:#75715e"># 调用栈分裂处理函数</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0077 <span style="color:#ae81ff">00119</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> PCDATA $0, $-<span style="color:#ae81ff">1</span>
|
|
|
</span></span><span style="display:flex;"><span>0x0077 <span style="color:#ae81ff">00119</span> <span style="color:#f92672">(</span>main.go:9<span style="color:#f92672">)</span> JMP <span style="color:#ae81ff">0</span>
|
|
|
</span></span></code></pre></div><p>结合汇编,我们最终画出 <code>main</code> 函数调用栈图:</p>
|
|
|
<figure class="text-center"><img src="https://static.cyub.vip/images/202105/go-stack-sum2.png" width="450px"/><figcaption>
|
|
|
<h4>main函数调用栈</h4>
|
|
|
</figcaption>
|
|
|
</figure>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
<footer class="book-footer">
|
|
|
|
|
|
<div class="flex flex-wrap justify-between">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<script>(function(){function e(e){const t=window.getSelection(),n=document.createRange();n.selectNodeContents(e),t.removeAllRanges(),t.addRange(n)}document.querySelectorAll("pre code").forEach(t=>{t.addEventListener("click",function(){if(window.getSelection().toString())return;e(t.parentElement),navigator.clipboard&&navigator.clipboard.writeText(t.parentElement.textContent)})})})()</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="book-comments">
|
|
|
|
|
|
<script src="https://giscus.app/client.js" data-repo="cyub/go-1.14.13"
|
|
|
data-repo-id="MDEwOlJlcG9zaXRvcnkzMzc2ODUyMzQ=" data-category="Announcements"
|
|
|
data-category-id="DIC_kwDOFCCq8s4CZ3BC"
|
|
|
data-mapping="pathname" data-strict="0" data-emit-metadata="0"
|
|
|
data-input-position="bottom" data-reactions-enabled="0"
|
|
|
data-lang="zh-CN" data-theme="preferred_color_scheme" crossorigin="anonymous" async>
|
|
|
</script>
|
|
|
<noscript>Please enable JavaScript to view the comments powered by giscus.</noscript>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<label for="menu-control" class="hidden book-menu-overlay"></label>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
</body>
|
|
|
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|