Posts for: #Go

Go 汇编分析

Go的汇编不是像 C/C++ 一样,对机器码的直接描述,而是兼容跨平台需求实现的半抽象化的指令集。

https://go.dev/doc/asm

汇编分析(Go 1.17)

我们用一个简单的例子来开始汇编分析:

package main

func main() {
	add(1, 3)
}

func add(i, j int) int {
	return i + j
}

汇编结果,删去了一些无关的输出:

# go tool compile -S -l -N main.go
"".main STEXT size=54 args=0x0 locals=0x18 funcid=0x0
        0x0000 00000 (main.go:3)        TEXT    "".main(SB), ABIInternal, $24-0
        0x0000 00000 (main.go:3)        CMPQ    SP, 16(R14)
        0x0004 00004 (main.go:3)        PCDATA  $0, $-2
        0x0004 00004 (main.go:3)        JLS     47
        0x0006 00006 (main.go:3)        PCDATA  $0, $-1
        0x0006 00006 (main.go:3)        SUBQ    $24, SP
        0x000a 00010 (main.go:3)        MOVQ    BP, 16(SP)
        0x000f 00015 (main.go:3)        LEAQ    16(SP), BP
        0x0014 00020 (main.go:3)        FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0014 00020 (main.go:3)        FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0014 00020 (main.go:4)        MOVL    $1, AX
        0x0019 00025 (main.go:4)        MOVL    $3, BX
        0x001e 00030 (main.go:4)        PCDATA  $1, $0
        0x001e 00030 (main.go:4)        NOP
        0x0020 00032 (main.go:4)        CALL    "".add(SB)
        0x0025 00037 (main.go:5)        MOVQ    16(SP), BP
        0x002a 00042 (main.go:5)        ADDQ    $24, SP
        0x002e 00046 (main.go:5)        RET
        0x002f 00047 (main.go:5)        NOP
        0x002f 00047 (main.go:3)        PCDATA  $1, $-1
        0x002f 00047 (main.go:3)        PCDATA  $0, $-2
        0x002f 00047 (main.go:3)        CALL    runtime.morestack_noctxt(SB)
        0x0034 00052 (main.go:3)        PCDATA  $0, $-1
        0x0034 00052 (main.go:3)        JMP     0

"".add STEXT nosplit size=56 args=0x10 locals=0x10 funcid=0x0
        0x0000 00000 (main.go:7)        TEXT    "".add(SB), NOSPLIT|ABIInternal, $16-16
        0x0000 00000 (main.go:7)        SUBQ    $16, SP
        0x0004 00004 (main.go:7)        MOVQ    BP, 8(SP)
        0x0009 00009 (main.go:7)        LEAQ    8(SP), BP
        0x000e 00014 (main.go:7)        FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x000e 00014 (main.go:7)        FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x000e 00014 (main.go:7)        FUNCDATA        $5, "".add.arginfo1(SB)
        0x000e 00014 (main.go:7)        MOVQ    AX, "".i+24(SP)
        0x0013 00019 (main.go:7)        MOVQ    BX, "".j+32(SP)
        0x0018 00024 (main.go:7)        MOVQ    $0, "".~r2(SP)
        0x0020 00032 (main.go:8)        MOVQ    "".i+24(SP), AX
        0x0025 00037 (main.go:8)        ADDQ    "".j+32(SP), AX
        0x002a 00042 (main.go:8)        MOVQ    AX, "".~r2(SP)
        0x002e 00046 (main.go:8)        MOVQ    8(SP), BP
        0x0033 00051 (main.go:8)        ADDQ    $16, SP
        0x0037 00055 (main.go:8)        RET

FUNCDATAPCDATA是由编译器引入的,主要包含垃圾回收时使用的信息,这里略过。

Go channel 实现

Go的Channel在runtime里面是一个hchan的结构体,每次我们make一个新的channel时,runtime从heap内分配一个hchan结构体管理channel。

type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz elements
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // send index
    recvx    uint   // receive index
    recvq    waitq  // list of recv waiters
    sendq    waitq  // list of send waiters

    // lock protects all fields in hchan, as well as several
    // fields in sudogs blocked on this channel.
    //
    // Do not change another G's status while holding this lock
    // (in particular, do not ready a G), as this can deadlock
    // with stack shrinking.
    lock mutex
}
  • qcount:当前channel queue内的element数目,当qcount等于dataqsiz时,表示channel buffer已经满了,send channel会被阻塞;
  • dataqsiz:channel的buffer大小,也就是我们在make时设定的值。dataqsiz在channel创建后不会再变动,因此channel的buffer是不会动态扩容的;
  • buf:buffered channel缓存elements的内存地址。是一个数组实现的环形队列;
  • elemsize:element类型大小;
  • closed:channel是否已经被关闭,防止关闭已经关闭的channel;
  • elemtype:channel element 类型;
  • sendx:buffer中下一个生产的element的index;
  • recvx:buffer中下一个消费的element的index;
  • recvq:阻塞的接收channel,是一个sudog的链表
  • sendq:阻塞的发送channel,是一个sudog的链表

当buffer没有满时,send channel发送elements时直接把elements放在buf内,增加sendx和qcount;当buffer满时,send channel被阻塞,并加入到sendq链表内,等待被唤醒;