Skip to content

Plan 9 汇编语言指令集相对较小,但覆盖了基本的操作。下面是 Plan 9 汇编中的一些常见指令以及它们的用法和解释。

数据传输指令 ​

MOV 指令 ​

  • MOVB src, dst: 移动字节
  • MOVW src, dst: 移动字
  • MOVL src, dst: 移动双字
  • MOVQ src, dst: 移动四字
assembly
MOVQ $10, AX       // 将立即数 10 移动到 AX 寄存器
MOVQ AX, 0x1000    // 将 AX 寄存器的值移动到内存地址 0x1000
MOVQ 0x1000, BX    // 将内存地址 0x1000 的值移动到 BX 寄存器

算术运算指令 ​

ADD 指令 ​

  • ADDB src, dst: 字节加法
  • ADDW src, dst: 字加法
  • ADDL src, dst: 双字加法
  • ADDQ src, dst: 四字加法
assembly
ADDQ AX, BX        // 将 AX 和 BX 的值相加,结果存储在 BX

SUB 指令 ​

  • SUBB src, dst: 字节减法
  • SUBW src, dst: 字减法
  • SUBL src, dst: 双字减法
  • SUBQ src, dst: 四字减法
assembly
SUBQ AX, BX        // 将 BX 减去 AX 的值,结果存储在 BX

MUL 指令 ​

  • MULB src: 字节乘法
  • MULW src: 字乘法
  • MULL src: 双字乘法
  • MULQ src: 四字乘法
assembly
MULQ AX            // 将 AX 和 AX 相乘,结果存储在 AX

DIV 指令 ​

  • DIVB src: 字节除法
  • DIVW src: 字除法
  • DIVL src: 双字除法
  • DIVQ src: 四字除法
assembly
DIVQ AX            // 将 DX:AX 除以 src,结果存储在 AX(商)和 DX(余数)

逻辑运算指令 ​

AND 指令 ​

  • ANDB src, dst: 字节与
  • ANDW src, dst: 字与
  • ANDL src, dst: 双字与
  • ANDQ src, dst: 四字与
assembly
ANDQ AX, BX        // 将 AX 和 BX 按位与,结果存储在 BX

OR 指令 ​

  • ORB src, dst: 字节或
  • ORW src, dst: 字或
  • ORL src, dst: 双字或
  • ORQ src, dst: 四字或
assembly
ORQ AX, BX         // 将 AX 和 BX 按位或,结果存储在 BX

XOR 指令 ​

  • XORB src, dst: 字节异或
  • XORW src, dst: 字异或
  • XORL src, dst: 双字异或
  • XORQ src, dst: 四字异或
assembly
XORQ AX, BX        // 将 AX 和 BX 按位异或,结果存储在 BX

NOT 指令 ​

  • NOTB dst: 字节取反
  • NOTW dst: 字取反
  • NOTL dst: 双字取反
  • NOTQ dst: 四字取反
assembly
NOTQ AX            // 将 AX 的每个位取反

数据比较指令 ​

CMP 指令 ​

  • CMPB src, dst: 字节比较
  • CMPW src, dst: 字比较
  • CMPL src, dst: 双字比较
  • CMPQ src, dst: 四字比较
assembly
CMPQ AX, BX        // 比较 AX 和 BX,结果影响标志寄存器

控制流指令 ​

JMP 指令 ​

  • JMP addr: 无条件跳转
assembly
JMP 0x1000         // 跳转到地址 0x1000

条件跳转指令 ​

  • JE addr: 如果相等则跳转
  • JNE addr: 如果不相等则跳转
  • JG addr: 如果大于则跳转
  • JGE addr: 如果大于等于则跳转
  • JL addr: 如果小于则跳转
  • JLE addr: 如果小于等于则跳转
assembly
CMPQ AX, BX        // 比较 AX 和 BX
JE 0x1000          // 如果 AX == BX,跳转到 0x1000

栈操作指令 ​

PUSH 指令 ​

  • PUSHB src: 压入字节
  • PUSHW src: 压入字
  • PUSHL src: 压入双字
  • PUSHQ src: 压入四字
assembly
PUSHQ AX           // 将 AX 压入栈

POP 指令 ​

  • POPB dst: 弹出字节
  • POPW dst: 弹出字
  • POPL dst: 弹出双字
  • POPQ dst: 弹出四字
assembly
POPQ AX            // 从栈顶弹出一个四字到 AX

函数调用指令 ​

CALL 指令 ​

  • CALL addr: 调用函数
assembly
CALL runtime·newproc(SB)  // 调用 runtime 的 newproc 函数

RET 指令 ​

  • RET: 返回
assembly
RET                // 从函数返回

特殊指令 ​

NOP 指令 ​

  • NOP: 空操作
assembly
NOP                // 空操作,通常用于对齐或延迟

HLT 指令 ​

  • HLT: 停止处理器
assembly
HLT                // 停止处理器,通常用于调试

其他 ​

LEA 指令 ​

  • LEAQ src, dst: 计算内存地址
assembly
LEAQ 8(SP), BP     // 将 SP 加 8 的结果存储在 BP

PCDATA 和 FUNCDATA 指令 ​

  • PCDATA 和 FUNCDATA: 用于调试和栈跟踪
assembly
PCDATA $0, $-2     // 标记调试信息
FUNCDATA $1, info  // 存储函数数据

这些指令构成了 Plan 9 汇编的基础,熟悉这些指令后,读 go build -gcflags="-S" 的输出会更顺手。

用反汇编看 Go 的内部机制 ​

读 Go 的底层汇编,能看清函数调用、栈帧、并发和垃圾回收在机器层面长什么样。下面用几个小例子,编译后反汇编来验证。

函数调用与栈帧 ​

在 Go 中,每个函数调用都会创建一个新的栈帧,用于保存函数的参数、局部变量和返回地址。栈帧的管理是由编译器和运行时系统自动处理的。

示例代码 ​
go
package main

import "fmt"

func add(a, b int) int {
    return a + b
}

func main() {
    result := add(3, 4)
    fmt.Println(result)
}

编译并反汇编以上代码,可以看到函数调用和栈帧的管理:

bash
go build -gcflags="-S" main.go

生成的汇编代码(部分,Go 1.27 / amd64,不同版本输出会有差异)如下:

assembly
	0x0000 00000 (main.go:5)	TEXT	main.add(SB), NOSPLIT|NOFRAME|ABIInternal, $0-16
	0x0000 00000 (main.go:5)	FUNCDATA	$0, gclocals·g5+hNtRBP6YXNjfog7aZjQ==(SB)
	0x0000 00000 (main.go:5)	FUNCDATA	$1, gclocals·g5+hNtRBP6YXNjfog7aZjQ==(SB)
	0x0000 00000 (main.go:5)	FUNCDATA	$5, main.add.arginfo1(SB)
	0x0000 00000 (main.go:5)	FUNCDATA	$6, main.add.argliveinfo(SB)
	0x0000 00000 (main.go:6)	ADDQ	BX, AX
	0x0003 00003 (main.go:6)	RET

可以看到,自 Go 1.17 起函数参数和返回值通过寄存器(amd64 上是 AX、BX 等)传递,参数在栈上的开销为 $0-16,函数体只剩一条 ADDQ BX, AX;早期版本(Go 1.16 及之前)则会看到通过 SP 偏移在栈上存取参数的代码。

并发机制 ​

Go 的并发模型主要靠 goroutine 和 channel。goroutine 是轻量级线程,由 Go 运行时管理。

Goroutine 的创建 ​
go
go func() {
    fmt.Println("Hello from goroutine")
}()

编译并反汇编后,可以看到 go 语句最终通过调用运行时的 runtime.newproc 创建 goroutine(用户代码侧的调用点):

assembly
	0x0015 00021 (main.go:6)	CALL	runtime.newproc(SB)

runtime.newproc 的实现在 runtime/proc.go 中,它负责分配 g 结构体、初始化栈并把它加入运行队列。可用 go tool objdump -s 'runtime.newproc' 可执行文件 查看其内部实现。

垃圾回收 ​

Go 语言的垃圾回收机制是自动化的,采用的是非分代的标记-清除算法。垃圾回收器会在后台线程中运行,标记不再使用的对象,并释放它们占用的内存。

垃圾回收的触发 ​
go
runtime.GC()

编译并反汇编后,可以看到 runtime.GC() 调用最终落在运行时的 runtime.GC 入口上(用户代码侧的调用点):

assembly
	0x000a 00010 (main.go:6)	CALL	runtime.GC(SB)

GC 的核心逻辑(标记调度 gcStart、并发标记、清除等)位于 runtime/mgc.go 中,可用 go tool objdump -s 'runtime\.GC$' 可执行文件 或阅读源码进一步查看。

总结 ​

函数调用在 amd64 上编译成寄存器传参加一条 ADDQ 加 RET,go 语句落到 runtime.newproc,runtime.GC() 落到 runtime.GC 入口。想看得更深,用 go tool objdump 配合 runtime/proc.go、runtime/mgc.go 的源码继续往下追。

参考资料 ​

同层页面 ​

相关页面 ​

Hello Golang