美文网首页
go 看懂汇编代码

go 看懂汇编代码

作者: 爱情小傻蛋 | 来源:发表于2021-09-03 18:45 被阅读0次

    硬核知识点

    什么是plan9汇编

    我们知道,CPU是只认二进制指令的,也就是一串的0101;人类无法记住这些二进制码,于是发明了汇编语言。汇编语言实际上是二进制指令的文本形式,它与指令可以一一对应。

    每一种CPU指令都是不一样的,因此对应的汇编语言也就不一样。人类写完汇编语言后,把它转换成二进制码,就可以被机器执行了。转换的动作由编译器完成。

    Go语言的编译器和汇编器都带了一个-S参数,可以查看生成的最终目标代码。通过对比目标代码和原始的Go语言或Go汇编语言代码的差异可以加深对底层实现的理解。

    Go汇编语言实际上来源于plan9汇编语言,而plan9汇编语言最初来源于Go语言作者之一的Ken Thompson为plan9系统所写的C语言编译器输出的汇编伪代码。这里强烈推荐一下春晖大神的新书《Go语言高级编程》,即将上市,电子版的点击阅读原文可以看到地址,书中有一整个章节讲Go的汇编语言,非常精彩!

    理解Go的汇编语言,哪怕只是一点点,都能对Go的运行机制有更深入的理解。比如我们以前讲的defer,如果从Go源码编译后的汇编代码来看,就能深刻地掌握它的底层原理。再比如,很多文章都会分析Go的函数参数传递都是值传递,如果把汇编代码秀出来,很容易就能得出结论。

    汇编角度看函数调用及返回过程

    假设我们有一个这样年幼无知的例子,求两个int的和,Go源码如下:

    package main
    
    func main() {
        _ = add(4,6)
    }
    
    func add(a, b int) int {
        return a+b
    }
    

    使用如下命令得到汇编代码:

    go tool compile -S main.go
    

    go tool compile命令用于调用Go语言提供的底层命令工具,其中-S参数表示输出汇编格式。

    我们现在只关心add函数的汇编代码:

    "".add STEXT nosplit size=19 args=0x18 locals=0x0
            0x0000 00000 (main.go:7)        TEXT    "".add(SB), NOSPLIT, $0-24
            0x0000 00000 (main.go:7)        FUNCDATA        $0, gclocals·54241e171da8af6ae173d69da0236748(SB)
            0x0000 00000 (main.go:7)        FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
            0x0000 00000 (main.go:7)        MOVQ    "".b+16(SP), AX
            0x0005 00005 (main.go:7)        MOVQ    "".a+8(SP), CX
            0x000a 00010 (main.go:8)        ADDQ    CX, AX
            0x000d 00013 (main.go:8)        MOVQ    AX, "".~r2+24(SP)
            0x0012 00018 (main.go:8)        RET
    
    

    看不懂没关系,我目前也不是全部都懂,但是对于理解一个函数调用的整体过程而言,足够了。

    0x0000 00000 (main.go:7)        TEXT    "".add(SB), NOSPLIT, $0-24
    
    

    这一行表示定义add这个函数,最后的数字$0-24,其中0表示函数栈帧大小为0;24表示参数及返回值的大小:参数是2个int型变量,返回值是1个int型变量,共24字节。

    再看中间这四行:

            0x0000 00000 (main.go:7)        MOVQ    "".b+16(SP), AX
            0x0005 00005 (main.go:7)        MOVQ    "".a+8(SP), CX
            0x000a 00010 (main.go:8)        ADDQ    CX, AX
            0x000d 00013 (main.go:8)        MOVQ    AX, "".~r2+24(SP)
    

    代码片段中的第1行,将第2个参数b搬到AX寄存器;第2行将1个参数a搬到寄存器CX;第3行将ab相加,相加的结果搬到AX;最后一行,将结果搬到返回参数的地址,这段汇编代码非常简单,来看一下函数调用者和被调者的栈帧图:

    (SP)指栈顶,b+16(SP)表示参数1的位置,从SP往上增加16个字节,注意,前面的b仅表示一个标号;同样,a+8(SP)表示实参0;~r2+24(SP)则表示返回值的位置。

    具体可以看下面的图:

    栈帧

    上面add函数的栈帧大小为0,其实更一般的调用者与被调用者的栈帧示意图如下:

    caller->callee

    最后,执行RET指令。这一步把被调用函数add栈帧清零,接着,弹出栈顶的返回地址,把它赋给指令寄存器rip,而返回地址就是main函数里调用add函数的下一行。

    于是,又回到了main函数的执行环境,add函数的栈帧也被销毁了。但是注意,这块内存是没有被清零的,清零动作是之后再次申请这块内存的时候要做的事。比如,声明了一个int型变量,它的默认值是0,清零的动作是在这里完成的。

    这样,main函数完成了函数调用,也拿到了返回值,完美。

    相关文章

      网友评论

          本文标题:go 看懂汇编代码

          本文链接:https://www.haomeiwen.com/subject/krwkwltx.html