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.

933 lines
46 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语言所使用到的汇编知识。在介绍Go汇编之前我们先了解一些汇编语言寄存器 AT&amp;T 汇编语法内存布局等前置知识点。这些知识点与Go汇编或多或少有关系了解这些才能更好的帮助我们去看懂Go汇编代码。
前置知识 # 机器语言 # 机器语言是机器指令的集合。计算机的机器指令是一系列二进制数字。计算机将之转换为一系列高低电平脉冲信号来驱动硬件工作的。
汇编语言 # 机器指令是由0和1组成的二进制指令难以编写与记忆。汇编语言是二进制指令的文本形式与机器指令一一对应相当于机器指令的助记码。比如加法的机器指令是00000011写成汇编语言就是ADD。汇编的指令格式由操作码和操作数组成。
将助记码标准化后称为assembly language缩写为asm中文译为汇编语言。
汇编语言大致可以分为两类:
基于x86架构处理器的汇编语言
Intel 汇编 DOS(8086处理器), Windows Windows 派系 -&gt; VC 编译器 AT&amp;T 汇编 Linux, Unix, Mac OS, iOS(模拟器) Unix派系 -&gt; GCC编译器 基于ARM 架构处理器的汇编语言
ARM 汇编 数据单元大小 # 汇编中数据单元大小可分为:
位 bit 半字节 Nibble 字节 Byte 字 Word 相当于两个字节 双字 Double Word 相当于2个字4个字节 四字 Quadword 相当于4个字8个字节 寄存器 # 寄存器是CPU中存储数据的器件起到数据缓存作用。内存按照内存层级(memory hierarchy)依次分为寄存器L1 Cache, L2 Cache, L3 Cache其读写延迟依次增加实现成本依次降低。
内存层级结构 寄存器分类 # 一个CPU中有多个寄存器。每一个寄存器都有自己的名称。寄存器按照种类分为通用寄存器和控制寄存器。其中通用寄存器有可细分为数据寄存器指针寄存器以及变址寄存器。">
<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语言所使用到的汇编知识。在介绍Go汇编之前我们先了解一些汇编语言寄存器 AT&amp;T 汇编语法内存布局等前置知识点。这些知识点与Go汇编或多或少有关系了解这些才能更好的帮助我们去看懂Go汇编代码。
前置知识 # 机器语言 # 机器语言是机器指令的集合。计算机的机器指令是一系列二进制数字。计算机将之转换为一系列高低电平脉冲信号来驱动硬件工作的。
汇编语言 # 机器指令是由0和1组成的二进制指令难以编写与记忆。汇编语言是二进制指令的文本形式与机器指令一一对应相当于机器指令的助记码。比如加法的机器指令是00000011写成汇编语言就是ADD。汇编的指令格式由操作码和操作数组成。
将助记码标准化后称为assembly language缩写为asm中文译为汇编语言。
汇编语言大致可以分为两类:
基于x86架构处理器的汇编语言
Intel 汇编 DOS(8086处理器), Windows Windows 派系 -&gt; VC 编译器 AT&amp;T 汇编 Linux, Unix, Mac OS, iOS(模拟器) Unix派系 -&gt; GCC编译器 基于ARM 架构处理器的汇编语言
ARM 汇编 数据单元大小 # 汇编中数据单元大小可分为:
位 bit 半字节 Nibble 字节 Byte 字 Word 相当于两个字节 双字 Double Word 相当于2个字4个字节 四字 Quadword 相当于4个字8个字节 寄存器 # 寄存器是CPU中存储数据的器件起到数据缓存作用。内存按照内存层级(memory hierarchy)依次分为寄存器L1 Cache, L2 Cache, L3 Cache其读写延迟依次增加实现成本依次降低。
内存层级结构 寄存器分类 # 一个CPU中有多个寄存器。每一个寄存器都有自己的名称。寄存器按照种类分为通用寄存器和控制寄存器。其中通用寄存器有可细分为数据寄存器指针寄存器以及变址寄存器。" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://go.cyub.vip/go-assembly/" /><meta property="article:section" content="" />
<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&#43;CaaS6DEpyLDMlcUh6rfU9SVrNWUc&#43;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/"class=active>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/">函数调用栈</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="go汇编">
Go汇编
<a class="anchor" href="#go%e6%b1%87%e7%bc%96">#</a>
</h1>
<p>本节将介绍Go语言所使用到的汇编知识。在介绍Go汇编之前我们先了解一些汇编语言寄存器 AT&amp;T 汇编语法内存布局等前置知识点。这些知识点与Go汇编或多或少有关系了解这些才能更好的帮助我们去看懂Go汇编代码。</p>
<h2 id="前置知识">
前置知识
<a class="anchor" href="#%e5%89%8d%e7%bd%ae%e7%9f%a5%e8%af%86">#</a>
</h2>
<h3 id="机器语言">
机器语言
<a class="anchor" href="#%e6%9c%ba%e5%99%a8%e8%af%ad%e8%a8%80">#</a>
</h3>
<p>机器语言是机器指令的集合。计算机的机器指令是一系列二进制数字。计算机将之转换为一系列高低电平脉冲信号来驱动硬件工作的。</p>
<h3 id="汇编语言">
汇编语言
<a class="anchor" href="#%e6%b1%87%e7%bc%96%e8%af%ad%e8%a8%80">#</a>
</h3>
<p>机器指令是由0和1组成的二进制指令难以编写与记忆。汇编语言是二进制指令的文本形式与机器指令一一对应相当于机器指令的助记码。比如加法的机器指令是<code>00000011</code>写成汇编语言就是<code>ADD</code><strong>汇编的指令格式由操作码和操作数组成</strong></p>
<p>将助记码标准化后称为<code>assembly language</code>,缩写为<code>asm</code>,中文译为汇编语言。</p>
<p>汇编语言大致可以分为两类:</p>
<ol>
<li>
<p>基于x86架构处理器的汇编语言</p>
<ul>
<li>Intel 汇编
<ul>
<li>DOS(8086处理器), Windows</li>
<li>Windows 派系 -&gt; VC 编译器</li>
</ul>
</li>
<li>AT&amp;T 汇编
<ul>
<li>Linux, Unix, Mac OS, iOS(模拟器)</li>
<li>Unix派系 -&gt; GCC编译器</li>
</ul>
</li>
</ul>
</li>
<li>
<p>基于ARM 架构处理器的汇编语言</p>
<ul>
<li>ARM 汇编</li>
</ul>
</li>
</ol>
<h4 id="数据单元大小">
数据单元大小
<a class="anchor" href="#%e6%95%b0%e6%8d%ae%e5%8d%95%e5%85%83%e5%a4%a7%e5%b0%8f">#</a>
</h4>
<p>汇编中数据单元大小可分为:</p>
<ul>
<li>位 bit</li>
<li>半字节 Nibble</li>
<li>字节 Byte</li>
<li>字 Word 相当于两个字节</li>
<li>双字 Double Word 相当于2个字4个字节</li>
<li>四字 Quadword 相当于4个字8个字节</li>
</ul>
<h3 id="寄存器">
寄存器
<a class="anchor" href="#%e5%af%84%e5%ad%98%e5%99%a8">#</a>
</h3>
<p>寄存器是CPU中存储数据的器件起到数据缓存作用。内存按照内存层级(memory hierarchy)依次分为寄存器L1 Cache, L2 Cache, L3 Cache其读写延迟依次增加实现成本依次降低。</p>
<figure class="text-center"><img src="https://static.cyub.vip/images/202104/mem_arch.jpg" width="500px"/><figcaption>
<h4>内存层级结构</h4>
</figcaption>
</figure>
<h4 id="寄存器分类">
寄存器分类
<a class="anchor" href="#%e5%af%84%e5%ad%98%e5%99%a8%e5%88%86%e7%b1%bb">#</a>
</h4>
<p>一个CPU中有多个寄存器。每一个寄存器都有自己的名称。寄存器按照种类分为通用寄存器和控制寄存器。其中通用寄存器有可细分为数据寄存器指针寄存器以及变址寄存器。</p>
<p>
<img src="https://static.cyub.vip/images/202007/register.jpg" alt="" /></p>
<p>1979年因特尔推出8086架构的CPU开始支持16位。为了兼容之前8008架构的8位CPU8086架构中AX寄存器高8位称为AH低8位称为AL用来对应8008架构的8位的A寄存器。后来随着x86以及x86-64
架构的CPU推出开始支持32位以及64位为了兼容并保留了旧名称16位处理器的AX寄存器拓展成EAX(E代表拓展Extended的意思)。对于64位处理器的寄存器相应的RAX(R代表寄存器Register的意思)。其他指令也类似。</p>
<p>
<img src="https://static.cyub.vip/images/202102/rax.jpg" alt="" /></p>
<p>各个寄存器功能介绍:</p>
<table>
<thead>
<tr>
<th>寄存器</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>AX</strong></td>
<td>A代表累加器AccumulatorX是八位寄存器AH和AL的中H和L的占位符表示AX由AH和AL组成。AX一般用于算术与逻辑运算以及作为函数返回值</td>
</tr>
<tr>
<td><strong>BX</strong></td>
<td>B代表BaseBX一般用于保存中间地址(hold indirect addresses)</td>
</tr>
<tr>
<td><strong>CX</strong></td>
<td>C代表CountCX一般用于计数比如使用它来计算循环中的迭代次数或指定字符串中的字符数</td>
</tr>
<tr>
<td><strong>DX</strong></td>
<td>D代表DataDX一般用于保存某些算术运算的溢出并且在访问80x86 I/O总线上的数据时保存I/O地址</td>
</tr>
<tr>
<td><strong>DI</strong></td>
<td>DI代表Destination IndexDI一般用于指针</td>
</tr>
<tr>
<td><strong>SI</strong></td>
<td>SI代表Source IndexSI用途同DI一样</td>
</tr>
<tr>
<td><strong>SP</strong></td>
<td>SP代表Stack Pointer是栈指针寄存器存放着执行函数对应栈帧的栈顶地址且始终指向栈顶</td>
</tr>
<tr>
<td><strong>BP</strong></td>
<td>BP代表Base Pointer是栈帧基址指针寄存器存放这执行函数对应栈帧的栈底地址一般用于访问栈中的局部变量和参数</td>
</tr>
<tr>
<td><strong>IP</strong></td>
<td>IP代表Instruction Pointer是指令寄存器指向处理器下条等待执行的指令地址(代码段内的偏移量)每次执行完相应汇编指令IP值就会增加IP是个特殊寄存器不能像访问通用寄存器那样访问它。IP可被jmp、call和ret等指令隐含地改变</td>
</tr>
</tbody>
</table>
<h3 id="进程在虚拟内存中布局">
进程在虚拟内存中布局
<a class="anchor" href="#%e8%bf%9b%e7%a8%8b%e5%9c%a8%e8%99%9a%e6%8b%9f%e5%86%85%e5%ad%98%e4%b8%ad%e5%b8%83%e5%b1%80">#</a>
</h3>
<p>32位系统下虚拟内存空间大小为4G每一个进程独立的运行在该虚拟内存空间上。从0x00000000开始的3G空间属于用户空间剩下1G空间属于内核空间。</p>
<p>用户空间还可以进一步细分,每一部分叫做段(section),大致可以分为以下几段:</p>
<ul>
<li>Stack 栈空间:用于函数调用中存储局部变量、返回地址、返回值等,向下增长,变量存储和使用过程叫做入栈和出栈过程</li>
<li>Heap 堆空间用于动态申请的内存比如c语言通过malloc函数调用分配内存其向上增长。指针型变量指向的一般就是这里面的空间。存储此空间的数据需要GC的。栈上变量scope是函数级的而堆上变量属于进程级的</li>
<li>Bss段未初始化数据区存储未初始化的全局变量或静态变量</li>
<li>Data段初始化数据区存储已经初始化的全局变量或静态变量</li>
<li>Text段代码区存储的是源码编译后二进制指令</li>
</ul>
<p>
<img src="https://static.cyub.vip/images/202102/process_mem_layout.jpeg" alt="内存布局" /></p>
<p>在32位系统中进程空间(即用户空间)范围为<code>0x00000000 ~ 0xbfffffff</code>,内核空间范围为<code>0xc0000000 ~ 0xffffffff</code>, 实际上分配的进程空间并不是从0x00000000开始的而是从0x08048000开始到0xbfffffff结束。另外进程实际的esp指向的地址并不是从0xbfffffff开始的因为linux系统会在程序初始化前将一些命令行参数及环境变量以及ELF辅助向量<code>ELF Auxiliary Vectors</code>)等信息放到栈上。进程启动时,其空间布局如下所示(注意图示中地址是从低地址到高地址的):</p>
<pre tabindex="0"><code>stack pointer -&gt; [ argc = number of args ] 4
[ argv[0] (pointer) ] 4 (program name)
[ argv[1] (pointer) ] 4
[ argv[..] (pointer) ] 4 * x
[ argv[n - 1] (pointer) ] 4
[ argv[n] (pointer) ] 4 (= NULL)
[ envp[0] (pointer) ] 4
[ envp[1] (pointer) ] 4
[ envp[..] (pointer) ] 4
[ envp[term] (pointer) ] 4 (= NULL)
[ auxv[0] (Elf32_auxv_t) ] 8
[ auxv[1] (Elf32_auxv_t) ] 8
[ auxv[..] (Elf32_auxv_t) ] 8
[ auxv[term] (Elf32_auxv_t) ] 8 (= AT_NULL vector)
[ padding ] 0 - 16
[ argument ASCIIZ strings ] &gt;= 0
[ environment ASCIIZ strings ] &gt;= 0
[ program name ASCIIZ strings ] &gt;= 0
(0xbffffffc) [ end marker ] 4 (= NULL)
(0xc0000000) &lt; bottom of stack &gt; 0 (virtual)
</code></pre><p>进程空间起始位置处存放命令行参数个数与参数信息,我们将在后面章节有讨论到。</p>
<h3 id="caller-与-callee">
caller 与 callee
<a class="anchor" href="#caller-%e4%b8%8e-callee">#</a>
</h3>
<p>如果一个函数调用另外一个函数那么该函数被称为调用者函数也叫做caller而被调用的函数称为被调用者函数也叫做callee。比如函数main中调用sum函数那么main就是caller而sum函数就是callee。</p>
<h3 id="栈帧">
栈帧
<a class="anchor" href="#%e6%a0%88%e5%b8%a7">#</a>
</h3>
<p>栈帧即stack frame即未完成函数所持有的独立连续的栈区域用来保存其局部变量返回地址等信息。</p>
<h3 id="函数栈">
函数栈
<a class="anchor" href="#%e5%87%bd%e6%95%b0%e6%a0%88">#</a>
</h3>
<p>当前函数作为caller其本身拥有的栈帧以及其所有callee的栈帧可以称为该函数的函数栈。一般情况下函数栈大小是固定的如果超出栈空间就会栈溢出异常。比如递归求斐波拉契这时候可以使用尾调用来优化。用火焰图分析性能时候火焰越高说明栈越深。</p>
<h3 id="att-汇编语法">
AT&amp;T 汇编语法
<a class="anchor" href="#att-%e6%b1%87%e7%bc%96%e8%af%ad%e6%b3%95">#</a>
</h3>
<p>ATT汇编语法是类Unix的系统上的标准汇编语法比如gcc、gdb中默认都是使用AT&amp;T汇编语法。AT&amp;T汇编的指令格式如下</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>instruction src dst
</span></span></code></pre></div><p>其中<code>instruction</code>是指令助记符,也叫操作码,比如<code>mov</code>就是一个指令助记符,<code>src</code>是源操作数,<code>dst</code>是目的操作。</p>
<p>当引用寄存器时候,应在寄存器名称加前缀<code>%</code>,对于常数,则应加前缀 <strong>$</strong></p>
<h4 id="指令分类">
指令分类
<a class="anchor" href="#%e6%8c%87%e4%bb%a4%e5%88%86%e7%b1%bb">#</a>
</h4>
<h5 id="数据传输指令">
数据传输指令
<a class="anchor" href="#%e6%95%b0%e6%8d%ae%e4%bc%a0%e8%be%93%e6%8c%87%e4%bb%a4">#</a>
</h5>
<table>
<thead>
<tr>
<th>汇编指令</th>
<th>逻辑表达式</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td>mov $0x05, %ax</td>
<td>R[ax] = 0x05</td>
<td>将数值5存储到寄存器ax中</td>
</tr>
<tr>
<td>mov %ax, -4(%bp)</td>
<td>mem[R[bp] -4] = R[ax]</td>
<td>将ax寄存器中存储的数据存储到<!-- raw HTML omitted --><strong>bp寄存器存的地址减去4之后的内存地址</strong>中,</td>
</tr>
<tr>
<td>mov -4(%bp), %ax</td>
<td>R[ax] = mem[R[bp] -4]</td>
<td>bp寄存器存储的地址减去4值<!-- raw HTML omitted -->然后改地址对应的内存存储的信息存储到ax寄存器中</td>
</tr>
<tr>
<td>mov $0x10, (%sp)</td>
<td>mem[R[sp]] = 0x10</td>
<td>将16存储到sp寄存器存储的地址对应的内存</td>
</tr>
<tr>
<td>push $0x03</td>
<td>mem[R[sp]] = 0x03<!-- raw HTML omitted --> R[sp] = R[sp] - 4</td>
<td>将数值03入栈然后sp寄存器存储的地址减去4</td>
</tr>
<tr>
<td>pop</td>
<td>R[sp] = R[sp] + 4</td>
<td>将当前sp寄存器指向的地址的变量出栈<!-- raw HTML omitted -->并将sp寄存器存储的地址加4</td>
</tr>
<tr>
<td>call func1</td>
<td>&mdash;</td>
<td>调用函数func1</td>
</tr>
<tr>
<td>ret</td>
<td>&mdash;</td>
<td>函数返回将返回值存储到寄存器中或caller栈中<!-- raw HTML omitted -->并将return address弹出到ip寄存器中</td>
</tr>
</tbody>
</table>
<p>当使用<code>mov</code>指令传递数据时数据的大小由mov指令的后缀决定。</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">movb</span> <span style="color:#a6e22e">$123</span><span style="color:#f92672">,</span> <span style="color:#f92672">%</span><span style="color:#a6e22e">eax</span> <span style="color:#75715e">// 1 byte
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">movw</span> <span style="color:#a6e22e">$123</span><span style="color:#f92672">,</span> <span style="color:#f92672">%</span><span style="color:#a6e22e">eax</span> <span style="color:#75715e">// 2 byte
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">movl</span> <span style="color:#a6e22e">$123</span><span style="color:#f92672">,</span> <span style="color:#f92672">%</span><span style="color:#a6e22e">eax</span> <span style="color:#75715e">// 4 byte
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">movq</span> <span style="color:#a6e22e">$123</span><span style="color:#f92672">,</span> <span style="color:#f92672">%</span><span style="color:#a6e22e">eax</span> <span style="color:#75715e">// 8 byte
</span></span></span></code></pre></div><h5 id="算术运算指令">
算术运算指令
<a class="anchor" href="#%e7%ae%97%e6%9c%af%e8%bf%90%e7%ae%97%e6%8c%87%e4%bb%a4">#</a>
</h5>
<table>
<thead>
<tr>
<th>指令</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td>subl $0x05, %eax</td>
<td>R[eax] = R[eax] - 0x05</td>
</tr>
<tr>
<td>subl %eax, -4(%ebp)</td>
<td>mem[R[ebp] -4] = mem[R[ebp] -4] - R[eax]</td>
</tr>
<tr>
<td>subl -4(%ebp), %eax</td>
<td>R[eax] = R[eax] - mem[R[ebp] -4]</td>
</tr>
</tbody>
</table>
<h5 id="跳转指令">
跳转指令
<a class="anchor" href="#%e8%b7%b3%e8%bd%ac%e6%8c%87%e4%bb%a4">#</a>
</h5>
<table>
<thead>
<tr>
<th>指令</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td>cmpl %eax %ebx</td>
<td>计算 R[eax] - R[ebx], 然后设置flags寄存器</td>
</tr>
<tr>
<td>jmp location</td>
<td>无条件跳转到location</td>
</tr>
<tr>
<td>je location</td>
<td>如果flags寄存器设置了相等标志则跳转到location</td>
</tr>
<tr>
<td>jg, jge, jl, gle, jnz, &hellip; location</td>
<td>如果flags寄存器设置了&gt;, &gt;=, &lt;, &lt;=, != 0等标志则跳转到location</td>
</tr>
</tbody>
</table>
<h5 id="栈与地址管理指令">
栈与地址管理指令
<a class="anchor" href="#%e6%a0%88%e4%b8%8e%e5%9c%b0%e5%9d%80%e7%ae%a1%e7%90%86%e6%8c%87%e4%bb%a4">#</a>
</h5>
<table>
<thead>
<tr>
<th>指令</th>
<th>含义</th>
<th>等同操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>pushl %eax</td>
<td>将R[eax]入栈</td>
<td>subl $4, %esp; <!-- raw HTML omitted -->movl %eax, (%esp)</td>
</tr>
<tr>
<td>popl %eax</td>
<td>将栈顶数据弹出然后存储到R[eax]</td>
<td>movl (%esp), %eax <!-- raw HTML omitted --> addl $4, %esp</td>
</tr>
<tr>
<td>leave</td>
<td>Restore the callers stack pointer</td>
<td>movl %ebp, %esp <!-- raw HTML omitted -->pop %ebp</td>
</tr>
<tr>
<td>lea 8(%esp), %esi</td>
<td>将R[esp]存放的地址加8然后存储到R[esi]</td>
<td>R[esi] = R[esp] + 8</td>
</tr>
</tbody>
</table>
<p><strong>lea</strong><code>load effective address</code>的缩写,用于将一个内存地址直接赋给目的操作数。</p>
<h5 id="函数调用指令">
函数调用指令
<a class="anchor" href="#%e5%87%bd%e6%95%b0%e8%b0%83%e7%94%a8%e6%8c%87%e4%bb%a4">#</a>
</h5>
<table>
<thead>
<tr>
<th>指令</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td>call label</td>
<td>调用函数,并将返回地址入栈</td>
</tr>
<tr>
<td>ret</td>
<td>从栈中弹出返回地址,并跳转至该返回地址</td>
</tr>
<tr>
<td>leave</td>
<td>恢复调用者者栈指针</td>
</tr>
</tbody>
</table>
<blockquote class="book-hint warning">
<p><strong>注意:</strong></p>
<p>以上指令分类并不规范和完整,比如 <code>call</code> , <code>ret</code> 都可以算作无条件跳转指令,这里面是按照功能放在函数调用这一分类了。</p>
</blockquote>
<h2 id="go-汇编">
Go 汇编
<a class="anchor" href="#go-%e6%b1%87%e7%bc%96">#</a>
</h2>
<p>Go语言汇编器采用Plan9 汇编语法该汇编语言是由贝尔实验推出来的。下面说的Go汇编也就是Plan9 汇编。
不同于C语言汇编中汇编指令的寄存器都是代表硬件寄存器Go汇编中的寄存器使用的是伪寄存器可以把Go汇编考虑成是底层硬件汇编之上的抽象。</p>
<h3 id="伪寄存器">
伪寄存器
<a class="anchor" href="#%e4%bc%aa%e5%af%84%e5%ad%98%e5%99%a8">#</a>
</h3>
<p>Go汇编一共有4个伪寄存器</p>
<ul>
<li>
<p><strong>FP</strong>: Frame pointer: arguments and locals.</p>
<ul>
<li>使用形如 symbol+offset(FP) 的方式,引用函数的输入参数。例如 arg0+0(FP)arg1+8(FP)</li>
<li>offset是正值</li>
</ul>
</li>
<li>
<p><strong>PC</strong>: Program counter: jumps and branches.</p>
<ul>
<li>PC寄存器在 x86 平台下对应 ip 寄存器amd64 上则是 rip</li>
</ul>
</li>
<li>
<p><strong>SB</strong>: Static base pointer: global symbols.</p>
<ul>
<li>全局静态基指针,一般用来声明函数或全局变量</li>
</ul>
</li>
<li>
<p><strong>SP</strong>: Stack pointer: top of stack.</p>
<ul>
<li>SP寄存器指向当前栈帧的局部变量的开始位置使用形如 symbol+offset(SP) 的方式,引用函数的局部变量。</li>
<li>offset是负值offset 的合法取值是 [-framesize, 0)。</li>
<li>手写汇编代码时,如果是 symbol+offset(SP) 形式,则表示伪寄存器 SP。如果是 offset(SP) 则表示硬件寄存器 SP。<strong>对于编译输出(go tool compile -S / go tool objdump)的代码来讲,所有的 SP 都是硬件寄存器 SP无论是否带 symbol</strong></li>
</ul>
</li>
</ul>
<h3 id="函数声明">
函数声明
<a class="anchor" href="#%e5%87%bd%e6%95%b0%e5%a3%b0%e6%98%8e">#</a>
</h3>
<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> 参数大小+返回值大小
</span></span><span style="display:flex;"><span> |
</span></span><span style="display:flex;"><span> TEXT pkgname·add<span style="color:#f92672">(</span>SB<span style="color:#f92672">)</span>,NOSPLIT,$32-16
</span></span><span style="display:flex;"><span> | | |
</span></span><span style="display:flex;"><span> 包名 函数名 栈帧大小
</span></span></code></pre></div><ul>
<li>
<p><code>TEXT</code>指令声明了<code>pagname.add</code>是在<code>.text</code></p>
</li>
<li>
<p><code>pkgname·add</code>中的<code>·</code>,是一个 <code>unicode</code> 的中点。在程序被链接之后,所有的中点<code>·</code>都会被替换为点号<code>.</code>,所以通过 <strong>
<a href="/analysis-tools/gdb/">GDB</a></strong> 调试打断点时候,应该是 <code>b pagname.add</code></p>
</li>
<li>
<p><code>(SB)</code>: <code>SB</code> 是一个虚拟寄存器,保存了静态基地址(static-base) 指针,即我们程序地址空间的开始地址。 <code>&quot;&quot;.add(SB)</code> 表明我们的符号add位于某个固定的相对地址空间起始处的偏移位置</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-shell" data-lang="shell"><span style="display:flex;"><span>objdump -j .text -t test | grep <span style="color:#e6db74">&#39;main.add&#39;</span> <span style="color:#75715e"># 可获得main.add的绝对地址</span>
</span></span></code></pre></div></li>
<li>
<p><code>NOSPLIT</code>: 表明该函数内部不进行栈分裂逻辑处理可以避免CPU资源浪费。关于栈分裂会在调度器章节介绍</p>
</li>
<li>
<p><code>$32-16</code>: <code>$32</code>代表即将分配的栈帧大小;而<code>$16</code>指定了传入的参数与返回值的大小</p>
</li>
</ul>
<h4 id="函数调用栈">
函数调用栈
<a class="anchor" href="#%e5%87%bd%e6%95%b0%e8%b0%83%e7%94%a8%e6%a0%88">#</a>
</h4>
<p>Go汇编中函数调用的参数以及返回值都是由栈传递和保存的这部分空间由<code>caller</code>在其栈帧(stack frame)上提供。Go汇编中没有使用PUSH/POP指令进行栈的伸缩处理所有栈的增长和收缩是通过在栈指针寄存器<code>SP</code>上分别执行加减指令来实现的。</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>
</span></span><span style="display:flex;"><span> caller
</span></span><span style="display:flex;"><span> +------------------+
</span></span><span style="display:flex;"><span> | |
</span></span><span style="display:flex;"><span> +----------------------&gt; |------------------|
</span></span><span style="display:flex;"><span> | | caller parent BP |
</span></span><span style="display:flex;"><span> | |------------------| &lt;--------- BP<span style="color:#f92672">(</span>pseudo SP<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span> | | local Var0 |
</span></span><span style="display:flex;"><span> | |------------------|
</span></span><span style="display:flex;"><span> | | ......... |
</span></span><span style="display:flex;"><span> | |------------------|
</span></span><span style="display:flex;"><span> | | local VarN |
</span></span><span style="display:flex;"><span> | |------------------|
</span></span><span style="display:flex;"><span> | | temporarily |
</span></span><span style="display:flex;"><span> | unused space |
</span></span><span style="display:flex;"><span>caller stack frame |------------------|
</span></span><span style="display:flex;"><span> | callee retN |
</span></span><span style="display:flex;"><span> | |------------------|
</span></span><span style="display:flex;"><span> | | ......... |
</span></span><span style="display:flex;"><span> | |------------------|
</span></span><span style="display:flex;"><span> | | callee ret0 |
</span></span><span style="display:flex;"><span> | |------------------|
</span></span><span style="display:flex;"><span> | | callee argN |
</span></span><span style="display:flex;"><span> | |------------------|
</span></span><span style="display:flex;"><span> | | ......... |
</span></span><span style="display:flex;"><span> | |------------------|
</span></span><span style="display:flex;"><span> | | callee arg0 |
</span></span><span style="display:flex;"><span> | |------------------| &lt;--------- FP<span style="color:#f92672">(</span>virtual register<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span> | | <span style="color:#66d9ef">return</span> addr |
</span></span><span style="display:flex;"><span> +----------------------&gt; |------------------| &lt;----------------------+
</span></span><span style="display:flex;"><span> | caller BP | |
</span></span><span style="display:flex;"><span> BP<span style="color:#f92672">(</span>pseudo SP<span style="color:#f92672">)</span> ------&gt; |------------------| |
</span></span><span style="display:flex;"><span> | local Var0 | |
</span></span><span style="display:flex;"><span> |------------------| |
</span></span><span style="display:flex;"><span> | local Var1 |
</span></span><span style="display:flex;"><span> |------------------| callee stack frame
</span></span><span style="display:flex;"><span> | ......... |
</span></span><span style="display:flex;"><span> |------------------| |
</span></span><span style="display:flex;"><span> | local VarN | |
</span></span><span style="display:flex;"><span> SP<span style="color:#f92672">(</span>Real Register<span style="color:#f92672">)</span> ------&gt; |------------------| |
</span></span><span style="display:flex;"><span> | | |
</span></span><span style="display:flex;"><span> | | |
</span></span><span style="display:flex;"><span> +------------------+ &lt;----------------------+
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> callee
</span></span></code></pre></div><p>关于Go汇编进一步知识我们将在 《<strong>
<a href="/function/call-stack/">基础篇-函数-函数调用栈</a></strong> 》 章节详细探讨说明,此处我们只需要大致了解下函数声明、调用栈概念即可。</p>
<h3 id="获取go汇编代码">
获取Go汇编代码
<a class="anchor" href="#%e8%8e%b7%e5%8f%96go%e6%b1%87%e7%bc%96%e4%bb%a3%e7%a0%81">#</a>
</h3>
<p>go代码示例</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:#f92672">import</span> <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//go:noinline
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">add</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:#66d9ef">return</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></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">c</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">add</span>(<span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">5</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">c</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="go-tool-compile">
go tool compile
<a class="anchor" href="#go-tool-compile">#</a>
</h4>
<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>go tool compile -N -l -S main.go
</span></span><span style="display:flex;"><span>GOOS<span style="color:#f92672">=</span>linux GOARCH<span style="color:#f92672">=</span>amd64 go tool compile -N -l -S main.go <span style="color:#75715e"># 指定系统和架构</span>
</span></span></code></pre></div><ul>
<li>-N选项指示禁止优化</li>
<li>-l选项指示禁止内联</li>
<li>-S选项指示打印出汇编代码</li>
</ul>
<p>若要禁止指定函数内联优化,也可以在函数定义处加上<code>noinline</code>编译指示:</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:#75715e">//go:noinline
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">add</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:#66d9ef">return</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></span></code></pre></div><h4 id="go-tool-objdump">
go tool objdump
<a class="anchor" href="#go-tool-objdump">#</a>
</h4>
<p>方法1 根据目标文件反编译出汇编代码</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>go tool compile -N -l main.go <span style="color:#75715e"># 生成main.o</span>
</span></span><span style="display:flex;"><span>go tool objdump main.o
</span></span><span style="display:flex;"><span>go tool objdump -s <span style="color:#e6db74">&#34;main.(main|add)&#34;</span> ./test <span style="color:#75715e"># objdump支持搜索特定字符串</span>
</span></span></code></pre></div><p>方法2 根据可执行文件反编译出汇编代码</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>go build -gcflags<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;-N -l&#34;</span> main.go -o test
</span></span><span style="display:flex;"><span>go tool objdump main.o
</span></span></code></pre></div><h4 id="go-build--gcflags--s">
go build -gcflags -S
<a class="anchor" href="#go-build--gcflags--s">#</a>
</h4>
<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>go build -gcflags<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;-N -l -S&#34;</span> main.go
</span></span></code></pre></div><h4 id="其他方法">
其他方法
<a class="anchor" href="#%e5%85%b6%e4%bb%96%e6%96%b9%e6%b3%95">#</a>
</h4>
<ul>
<li>
<a href="https://en.wikipedia.org/wiki/Objdump">objdump命令</a></li>
<li>
<a href="https://github.com/golang/gofrontend">go编译器gccgo</a></li>
<li>
<a href="https://go.godbolt.org/">在线转换汇编代码godbolt</a></li>
</ul>
<h2 id="进一步阅读">
进一步阅读
<a class="anchor" href="#%e8%bf%9b%e4%b8%80%e6%ad%a5%e9%98%85%e8%af%bb">#</a>
</h2>
<ul>
<li>
<a href="https://golang.org/doc/asm">Go官方A Quick Guide to Go&rsquo;s Assembler</a></li>
<li>
<a href="https://github.com/cch123/golang-notes/blob/master/assembly.md">plan9 assembly 完全解析</a></li>
<li>
<a href="https://en.wikibooks.org/wiki/X86_Assembly#Table_of_Contents">x86 Assembly book</a></li>
<li>
<a href="https://www.section.io/engineering-education/what-is-an-abi/">What is an ABI?</a></li>
</ul>
</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>