go 彙編
編譯一個最簡單的go執行程式
package mainimport "fmt"func main(){ fmt.Println("helloworld")}
go build -gcflags "-N -l" test.go
使用go tool objdump 反組譯碼
go tool objdump test >test.asm
gdb test
goasm1.png
我們看到彙編的入口地址位於0x452100,開啟test.asm
TEXT _rt0_amd64_linux(SB) /usr/local/go/src/runtime/rt0_linux_amd64.s rt0_linux_amd64.s:8 0x452100 488d742408 LEAQ 0x8(SP), SI rt0_linux_amd64.s:9 0x452105 488b3c24 MOVQ 0(SP), DI rt0_linux_amd64.s:10 0x452109 488d0510000000 LEAQ 0x10(IP), AX rt0_linux_amd64.s:11 0x452110 ffe0 JMP AX
可以看到入口為rto_linux_amd64.s (當然了不同平台的入口檔案肯定不一樣),
rto_linux_amd64.s
#include "textflag.h"TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8 LEAQ 8(SP), SI // argv MOVQ 0(SP), DI // argc MOVQ $main(SB), AX JMP AX// When building with -buildmode=c-shared, this symbol is called when the shared// library is loaded.// Note: This function calls external C code, which might required 16-byte stack// alignment after cmd/internal/obj applies its transformations.TEXT _rt0_amd64_linux_lib(SB),NOSPLIT,$0x50 MOVQ SP, AX ANDQ $-16, SP MOVQ BX, 0x10(SP) MOVQ BP, 0x18(SP) MOVQ R12, 0x20(SP) MOVQ R13, 0x28(SP) MOVQ R14, 0x30(SP) MOVQ R15, 0x38(SP) MOVQ AX, 0x40(SP) MOVQ DI, _rt0_amd64_linux_lib_argc<>(SB) MOVQ SI, _rt0_amd64_linux_lib_argv<>(SB) // Synchronous initialization. MOVQ $runtime·libpreinit(SB), AX CALL AX // Create a new thread to do the runtime initialization and return. MOVQ _cgo_sys_thread_create(SB), AX TESTQ AX, AX JZ nocgo MOVQ $_rt0_amd64_linux_lib_go(SB), DI MOVQ $0, SI CALL AX JMP restorenocgo: MOVQ $8388608, 0(SP) // stacksize MOVQ $_rt0_amd64_linux_lib_go(SB), AX MOVQ AX, 8(SP) // fn MOVQ $runtime·newosproc0(SB), AX CALL AXrestore: MOVQ 0x10(SP), BX MOVQ 0x18(SP), BP MOVQ 0x20(SP), R12 MOVQ 0x28(SP), R13 MOVQ 0x30(SP), R14 MOVQ 0x38(SP), R15 MOVQ 0x40(SP), SP RETTEXT _rt0_amd64_linux_lib_go(SB),NOSPLIT,$0 MOVQ _rt0_amd64_linux_lib_argc<>(SB), DI MOVQ _rt0_amd64_linux_lib_argv<>(SB), SI MOVQ $runtime·rt0_go(SB), AX JMP AXDATA _rt0_amd64_linux_lib_argc<>(SB)/8, $0GLOBL _rt0_amd64_linux_lib_argc<>(SB),NOPTR, $8DATA _rt0_amd64_linux_lib_argv<>(SB)/8, $0GLOBL _rt0_amd64_linux_lib_argv<>(SB),NOPTR, $8TEXT main(SB),NOSPLIT,$-8 MOVQ $runtime·rt0_go(SB), AX JMP AX
上面的是go1.9.2 的rto_linux_amd64.s 檔案內容.
但是我對彙編完全是半懂,只能半看半猜了,更何況這種go的彙編指令有些以前都沒有看到過.好在有偉大的google,另外gdb裡面的彙編單步調試提供了很多方便。
TEXT rt0amd64_linux(SB),NOSPLIT,$-8 LEAQ 8(SP), SI // argv MOVQ 0(SP), DI // argc MOVQ $main(SB), AX JMP AX TEXT main(SB),NOSPLIT,$-8 MOVQ $runtime·rt0_go(SB), AX JMP AX
這是rto_linux_asm64.s 的前幾行,第4行跳到了main(SB)這裡. main(SB)有跳到了runtime·rt0_go(SB) 但是runtime·rt0_go(SB)在那裡呢。這裡有2種方式可以找到,一種我們可以搜尋runtime 目錄下的所有彙編檔案。另外一種方式是使用gdb的彙編單步調試看它跳到那裡
這是使用gdb單步調試的內容,關於gdb彙編單步調試就2指令ni si.
Breakpoint 1, _rt0_amd64_linux () at /usr/local/go/src/runtime/rt0_linux_amd64.s:88 LEAQ 8(SP), SI // argv(gdb) ni9 MOVQ 0(SP), DI // argc(gdb) niBreakpoint 2, _rt0_amd64_linux () at /usr/local/go/src/runtime/rt0_linux_amd64.s:1010 MOVQ $main(SB), AX(gdb) ni11 JMP AX(gdb) niStopped due to shared library event(gdb) si74 JMP AX(gdb) siruntime.rt0_go () at /usr/local/go/src/runtime/asm_amd64.s:1212 MOVQ DI, AX // argc(gdb) ni13 MOVQ SI, BX // argv(gdb)
我們看到是跳到了asm_amd64.s:12的12行執行,這裡的代碼我們可以通過旁邊的注釋大概瞭解做了什麼事情
// create istack out of the given (operating system) stack. // _cgo_init may update stackguard. MOVQ $runtime·g0(SB), DI LEAQ (-64*1024+104)(SP), BX MOVQ BX, g_stackguard0(DI) MOVQ BX, g_stackguard1(DI) MOVQ BX, (g_stack+stack_lo)(DI) MOVQ SP, (g_stack+stack_hi)(DI) // find out information about the processor we're on MOVL $0, AX CPUID MOVL AX, SI CMPL AX, $0 JE nocpuinfo MOVL 16(SP), AX // copy argc MOVL AX, 0(SP) MOVQ 24(SP), AX // copy argv MOVQ AX, 8(SP) CALL runtime·args(SB) CALL runtime·osinit(SB) CALL runtime·schedinit(SB)
主要就是擷取初始化的一些事情,包括runtime.osinit runtime.schedinit 等.
// create a new goroutine to start program MOVQ $runtime·mainPC(SB), AX // entry PUSHQ AX PUSHQ $0 // arg size CALL runtime·newproc(SB) POPQ AX POPQ AX
這裡把main函數入棧,然後調用newproc建立協程.
小結
這篇主要介紹了使用go tool he gdb簡單的分析golang程式的啟動引導過程,一些細節例如 newproc和mstart 在後面的文章中介紹.