1、Go版本
go1.14.15
2、汇编基础
-
推荐阅读:GO汇编语言简介
-
推荐阅读:A Quick Guide to Go's Assembler - The Go Programming Language
-
精简指令集
-
-
数据传输: MOV/LEA
-
跳转指令: CMP/TEST/JMP/JCC
-
栈指令: PUSH/POP
-
函数调用指令: CALL/RET
-
算术指令: ADD/SUB/MUL/DIV
-
逻辑指令: AND/OR/XOR/NOT
-
移位指令: SHL/SHR
-
JCC有条件跳转: JEQ/JNE/JLT/JLE/JGT/JGE
-
还有针对无符号数的比较条件
-
-
MOV指令
注:图片来源于GO汇编语言简介
-
伪寄存器
-
SB: 静态基址指针, 全局符号
-
FP: 帧指针, 参数和局部变量
-
SP: 栈指针, 栈的顶端
-
PC: 程序计数器, 跳转和分支
-
注:伪寄存器仅仅存在于Go汇编中
-
-
伪寄存器用法
-
GLOBL text(SB),$1: 全局变量
-
MOVQ a+0(FP) AX: 函数参数
-
MOVQ b+8(SP) AX: 局部变量
-
JMP 2(PC): 向前跳转, 常用于宏函数
-
JMP -2(PC): 向后跳转, 常用于宏函数
-
3、示例代码
package main
func main() {
n := 10
println(read(&n))
}
//go:noinline
func read(p *int) (v int) {
v = *p
return
}
-
//go:noinline:禁止Go对函数进行内联
-
内联:内联是一种手动或编译器优化,用于将简短函数的调用替换为函数体本身。这么做的原因是它可以消除函数调用本身的开销,也使得编译器能更高效地执行其他的优化策略
-
使用objdump工具反编译
4、结合chatGPT反编译调试
go build s1.go //编译
go tool objdump -S -s "main.read" .\s3.exe //反编译
-
图中是用go自带的objdump工具对main.read反编译得到的汇编指令
-
不懂就问,咱们直接问chatGPT这段汇编指令的含义(首先需要给chatGPT一些引导和背景介绍),如下图
-
上面解释的很清楚了,为了进一步理解这些指令的含义,追问
5、指针
-
指针本身是一个无符号整型
package main
func main() {
n := int32(10)
println(read32(&n))
}
//go:noinline
func read32(p *int32) (v int32) {
v = *p
return
}
可以看到上述汇编指令中,第一行从参数p中取地址值的操作没变化,只是从AX寄存器中取值的时候,命令有MOVQ(8字节)变为MOVL(4字节),可见不同类型的指针地址本身是一样的类型(无符号整型)
-
取地址
package main
var n int
func main() {
println(addr())
}
//go:noinline
func addr() (p *int) {
return &n
}
直接问chatGPT,给出的解释是:
-
-
从上图可以看到全局变量n是存在main包的静态基地址上(SB),被不同的函数和代码块共享访问,SB 是静态基地址的缩写,它是指向静态基地址的寄存器。因此,"main.n(SB)" 就是通过 SB 指向 main 包的静态基地址上的 n 变量。
-
LEAQ 指令将全局变量 n 的有效地址存储到 AX 寄存器中,这样 AX 寄存器就包含了 n 变量的地址,可以用于读取或写入该变量的值。
-
LEAQ 指令用于将有效地址存储到一个寄存器中
-
-
强制类型转换
package main
import "unsafe"
func main() {
p := 3
convert(&p)
}
//go:noinline
func convert(p *int) {
q := (*int32)(unsafe.Pointer(p))
*q = 0
}
- 这段汇编代码是将一个指向int类型变量的指针,转换为指向int32类型变量的指针,并将其所指向的内存空间的值设置为0
- 把指针的类型强转换为int32后,原本的MOVQ指令变成了MOVL,没有产生任何额外指令,所以转换效率是非常高的