You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

506 lines
36 KiB
HTML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!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 语言中函数调用栈。这一章节会涉及callercallee寄存器相关概念如果还不太了解可以去《 准备篇-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 语言中函数栈全景图如下:
.. image:: https://static.cyub.vip/images/202105/go-func-stack.png :alt: Go语言函数调用栈 :width: 400px :align: center 接下来的函数调用栈分析,都是基于函数栈的全景图出发。知道该全景图每一部分含义也就了解函数调用栈。
实例分析 # 我们将分析如下代码。
package main func sum(a, b int) int { sum := 0 sum = a &#43; 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 语言中函数调用栈。这一章节会涉及callercallee寄存器相关概念如果还不太了解可以去《 准备篇-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 语言中函数栈全景图如下:
.. image:: https://static.cyub.vip/images/202105/go-func-stack.png :alt: Go语言函数调用栈 :width: 400px :align: center 接下来的函数调用栈分析,都是基于函数栈的全景图出发。知道该全景图每一部分含义也就了解函数调用栈。
实例分析 # 我们将分析如下代码。
package main func sum(a, b int) int { sum := 0 sum = a &#43; 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.7e9d53d4a20eea8c87bf76a4502bd21aa041c1ef2adc7e37ffc5339c57accccd.js" integrity="sha256-fp1T1KIO6oyHv3akUCvSGqBBwe8q3H43/8UznFeszM0=" 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>准备篇</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>这一章节延续前面《
<a href="/go-assembly/">准备篇-Go汇编</a>》那一章节。这一章节将从一个实例出发详细分析Go 语言中函数调用栈。这一章节会涉及callercallee寄存器相关概念如果还不太了解可以去《
<a href="/go-assembly/">准备篇-Go汇编</a>》查看了解。</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)是
<a href="https://en.wikipedia.org/wiki/Application_binary_interface">ABI</a>(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>
<pre tabindex="0"><code class="language-eval_rst" data-lang="eval_rst">.. image:: https://static.cyub.vip/images/202105/go-func-stack.png
:alt: Go语言函数调用栈
:width: 400px
:align: center
</code></pre><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>
<pre tabindex="0"><code class="language-eval_rst" data-lang="eval_rst">.. image:: https://static.cyub.vip/images/202105/go-stack-sum.png
:alt: main函数调用栈
:width: 400px
:align: center
</code></pre><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栈帧的栈底地址。
&hellip;.</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">&#34;&#34;</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">&#34;&#34;</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>关于函数声明时每个字段的含义可以去《
<a href="/go-assembly/#id18">准备篇-Go汇编-函数声明</a></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">&#34;&#34;</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"># PCDATAFUNCDATA用于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">&#34;&#34;</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">&#34;&#34;</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">&#34;&#34;</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">&#34;&#34;</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">&#34;&#34;</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">&#34;&#34;</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>结合汇编我们最终画出main函数调用栈图</p>
<pre tabindex="0"><code class="language-eval_rst" data-lang="eval_rst">.. image:: https://static.cyub.vip/images/202105/go-stack-sum2.png
:alt: main函数调用栈
:width: 450px
:align: center
</code></pre></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">
<div id="disqus_thread"></div>
<script type="application/javascript">
window.disqus_config = function () {
};
(function() {
if (["localhost", "127.0.0.1"].indexOf(window.location.hostname) != -1) {
document.getElementById('disqus_thread').innerHTML = 'Disqus comments not available by default when the website is previewed locally.';
return;
}
var d = document, s = d.createElement('script'); s.async = true;
s.src = '//' + "go-cyub-vip" + '.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="https://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
</div>
<label for="menu-control" class="hidden book-menu-overlay"></label>
</div>
</main>
</body>
</html>