go build
-gcflags: 传递给编译器的参数
-ldflags: 传递给链接器的参数
-work: 查看编译临时目录
-race: 允许数据竞争检测(仅支持amd64)
-n: 查看但不执行编译指令
-x: 查看并执行编译命令
-a: 强制重新编译所有依赖包
-v: 查看被编译的包名,包括依赖包
-p n:并行编译所使用的CPU数,默认为全部
-o:输出文件名
gcflags:
-B 禁用边界检查
-N 禁用优化
-l 禁用函数内联
-u 禁用unsafe代码
-m 输出优化信息
-S 输出汇编代码
ldflags:
-w 禁用DRAWF调试信息,但不包括符号表
-s 禁用符号表
-X 修改字符串符号值 -X main.VER ‘0.99’ -X main.S ‘abc’
-H 链接文件类型,其中包括windowsgui. cmd/ld/doc.go
$ go build -x cal/cal.go
看到输出的编译链接详细过程:
WORK=/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build742754584
mkdir -p $WORK/github.com/xiazemin/gocompiler/cal/token/_obj/
mkdir -p $WORK/github.com/xiazemin/gocompiler/cal/
cd /Users/didi/goLang/src/github.com/xiazemin/gocompiler/cal/token
/usr/local/go/pkg/tool/darwin_amd64/compile -o $WORK/github.com/xiazemin/gocompiler/cal/token.a -trimpath $WORK -goversion go1.9.2 -p github.com/xiazemin/gocompiler/cal/token -complete -buildid 818f9d05a7ef841770191e9e6dcde887edc2e57d -D _/Users/didi/goLang/src/github.com/xiazemin/gocompiler/cal/token -I $WORK -pack ./token.go
mkdir -p $WORK/github.com/xiazemin/gocompiler/cal/lexer/_obj/
cd /Users/didi/goLang/src/github.com/xiazemin/gocompiler/cal/lexer
/usr/local/go/pkg/tool/darwin_amd64/compile -o $WORK/github.com/xiazemin/gocompiler/cal/lexer.a -trimpath $WORK -goversion go1.9.2 -p github.com/xiazemin/gocompiler/cal/lexer -complete -buildid e360f0a5c7b69033acc446663e32a2b662f3fbae -D _/Users/didi/goLang/src/github.com/xiazemin/gocompiler/cal/lexer -I $WORK -I /Users/didi/goLang/pkg/darwin_amd64 -pack ./lex.go
mkdir -p $WORK/github.com/xiazemin/gocompiler/cal/syntax/_obj/
cd /Users/didi/goLang/src/github.com/xiazemin/gocompiler/cal/syntax
/usr/local/go/pkg/tool/darwin_amd64/compile -o $WORK/github.com/xiazemin/gocompiler/cal/syntax.a -trimpath $WORK -goversion go1.9.2 -p github.com/xiazemin/gocompiler/cal/syntax -complete -buildid 0b0315d6be705c9851f978c4cc4c6a286fba491e -D _/Users/didi/goLang/src/github.com/xiazemin/gocompiler/cal/syntax -I $WORK -I /Users/didi/goLang/pkg/darwin_amd64 -pack ./parser.go ./preference.go ./syntax.go
mkdir -p $WORK/command-line-arguments/_obj/
mkdir -p $WORK/command-line-arguments/_obj/exe/
cd /Users/didi/goLang/src/github.com/xiazemin/gocompiler/cal
/usr/local/go/pkg/tool/darwin_amd64/compile -o $WORK/command-line-arguments.a -trimpath $WORK -goversion go1.9.2 -p main -complete -buildid b38705aafc4497f1a7a755e4048b8a0e9dd073a4 -D _/Users/didi/goLang/src/github.com/xiazemin/gocompiler/cal -I $WORK -I /Users/didi/goLang/pkg/darwin_amd64 -pack ./cal.go
cd .
/usr/local/go/pkg/tool/darwin_amd64/link -o $WORK/command-line-arguments/_obj/exe/a.out -L $WORK -L /Users/didi/goLang/pkg/darwin_amd64 -extld=clang -buildmode=exe -buildid=b38705aafc4497f1a7a755e4048b8a0e9dd073a4 $WORK/command-line-arguments.a
cp $WORK/command-line-arguments/_obj/exe/a.out cal
go install command-line-arguments: build output “cal” already exists and is a directory
忽略路径相关操作,可以看到有下面几步
1,compile -o cal/token.a -trimpath $WORK -goversion go1.9.2 -p github.com/xiazemin/gocompiler/cal/token -complete -buildid 818f9d05a7ef841770191e9e6dcde887edc2e57d -D _/Users/didi/goLang/src/github.com/xiazemin/gocompiler/cal/token -I $WORK -pack ./token.go
2,compile -o cal/lexer.a -trimpath $WORK -goversion go1.9.2 -p github.com/xiazemin/gocompiler/cal/lexer -complete -buildid e360f0a5c7b69033acc446663e32a2b662f3fbae -D _/Users/didi/goLang/src/github.com/xiazemin/gocompiler/cal/lexer -I $WORK -I /Users/didi/goLang/pkg/darwin_amd64 -pack ./lex.go
3,compile -o $WORK/github.com/xiazemin/gocompiler/cal/syntax.a -trimpath $WORK -goversion go1.9.2 -p github.com/xiazemin/gocompiler/cal/syntax -complete -buildid 0b0315d6be705c9851f978c4cc4c6a286fba491e -D _/Users/didi/goLang/src/github.com/xiazemin/gocompiler/cal/syntax -I $WORK -I /Users/didi/goLang/pkg/darwin_amd64 -pack ./parser.go ./preference.go ./syntax.go
4,compile -o $WORK/command-line-arguments.a -trimpath $WORK -goversion go1.9.2 -p main -complete -buildid b38705aafc4497f1a7a755e4048b8a0e9dd073a4 -D _/Users/didi/goLang/src/github.com/xiazemin/gocompiler/cal -I $WORK -I /Users/didi/goLang/pkg/darwin_amd64 -pack ./cal.go
5,link -o $WORK/command-line-arguments/_obj/exe/a.out -L $WORK -L /Users/didi/goLang/pkg/darwin_amd64 -extld=clang -buildmode=exe -buildid=b38705aafc4497f1a7a755e4048b8a0e9dd073a4 $WORK/command-line-arguments.a
6,go install command-line-arguments: build output
先根据import 依次编译各个package,然后链接生成目标文件,最后goinsall
$ go tool 可以看到所有的tool
addr2line
asm
cgo
compile
cover
dist
doc
fix
link
nm
objdump
pack
pprof
tour
trace
vet
$ go tool compile -h
usage: compile [options] file.go…
-% debug non-static initializers
-+ compiling runtime
-B disable bounds checking
-C disable printing of columns in error messages
-D path
set relative path for local imports
-E debug symbol export
-I directory
add directory to import search path
-K debug missing line numbers
-N disable optimizations
-S print assembly listing
-V print compiler version
-W debug parse tree after type checking
-asmhdr file
write assembly header to file
-bench file
append benchmark times to file
-blockprofile file
write block profile to file
-buildid id
record id as the build id in the export metadata
-c int
concurrency during compilation, 1 means no concurrency (default 1)
-complete
compiling complete package (no C or assembly)
-cpuprofile file
write cpu profile to file
-d list
print debug information about items in list; try -d help
-dolinkobj
generate linker-specific objects; if false, some invalid code may compile (default true)
-dwarf
generate DWARF symbols (default true)
-dynlink
support references to Go symbols defined in other shared libraries
-e no limit on number of errors reported
-f debug stack frames
-goversion string
required version of the runtime
-h halt on error
-i debug line number stack
-importcfg file
read import configuration from file
-importmap definition
add definition of the form source=actual to import map
-installsuffix suffix
set pkg directory suffix
-j debug runtime-initialized variables
-l disable inlining
-linkobj file
write linker-specific object to file
-live
debug liveness analysis
-m print optimization decisions
-memprofile file
write memory profile to file
-memprofilerate rate
set runtime.MemProfileRate to rate
-msan
build code compatible with C/C++ memory sanitizer
-mutexprofile file
write mutex profile to file
-nolocalimports
reject local (relative) imports
-o file
write output to file
-p path
set expected package import path
-pack
write package file instead of object file
-r debug generated wrappers
-race
enable race detector
-s warn about composite literals that can be simplified
-shared
generate code that can be linked into a shared library
-std
compiling standard library
-traceprofile file
write an execution trace to file
-trimpath prefix
remove prefix from recorded source file paths
-u reject unsafe code
-v increase debug verbosity
-w debug type checking
-wb
enable write barrier (default true)
package main
import “demo”
func main() {
demo.Demo()
}
demo包中的demo.go代码如下:
package demo
import “fmt”
func Demo() {
fmt.Println(“call demo …”)
}
由于demo.go是在%GOPATH%\src目录下的一个包,main.go在import该包后,可以直接使用,运行main.go:
go run main.go
call demo …
现在,需要将demo.go编译成静态库demo.a,不提供demo.go的源代码,让main.go也能正常编译运行,详细步骤如下:
1,编译静态库demo.a
在命令行运行go install demo命令,会在%GOPATH%目录下生相应的静态库文件demo.a
$ go install github.com/xiazemin/gocompiler/static/demo
$ ls ~/goLang/pkg/darwin_amd64/github.com/xiazemin/gocompiler/static/
demo.a
2 编译main.go
进入main.go所在目录,编译main.go:
go tool compile -I ~/goLang/pkg/darwin_amd64 main.go
-I选项指定了demo包的安装路径,供main.go导入使用
dows_amd64目录,编译成功后会生成相应的目标文件main.o。
$ go tool compile -I ~/goLang/pkg/darwin_amd64 -o main.o main.go
$ ls
demo main.go main.o
3 链接main.o
$go tool link -o main.exe -L ~/goLang/pkg/darwin_amd64 main.o
-L选项指定了静态库demo.a的路径,即~/goLang/pkg/darwin_amd64目录,链接成功后会生成相应的可执行文件main.exe。
$ ls
demo main.exe main.go main.o
发现程序没有导入任何包直接调用了print函数,这是因为所有go程序
都默认链接runtime.a这个包,而print这个函数就在这个包里拉,相当于c的libc库
golang中链接器首先使用objfile这个函数来将所有的object文件加载,这个函数位于src/cmd/ld/lib.c
新版本位于src/cmd/link/internal/ld/ld.go
1、链接器收集main包引用的所有其它包中的符号信息,并将它们装载到一个大的字节数组中
2、对于每个符号,链接器计算它在(数组)镜像中的地址。
3、然后他为每个符号应用重定位,
4、链接器准备所有ELF格式(linux系统中)文件或者PE格式文件(windows系统中)所需的文件头。然后它再生成一个可执行的文件。
语法解析树处理完成后,再执行真正的编译,将结点翻译成汇编代码。
在磁盘上创建目标文件,并将翻译生成的汇编代码以及一些额外的数据结构,如符号表等,写入目标文件中。
目标代码的形式可以是绝对指令代码或可重定位的指令代码或汇编指令代码。如目标代码是绝对指令代码,则这种目标代码可立即执行。如果目标代码是汇编指令代码,则需汇编器汇编之后才行运行。必须指出,现在多数实用编译程序所产生的目标代码都是一种可重定位的指令代码。golang编译后是可重定位的指令代码。这种目标代码在运行前必须借助于一个连接装配程序把各个目标模块(包括系统提供的库函数)连接在一起,确定程序变量(或常数)在主存中的位置,装入内存中指定的起始地址,使之成为一个可以运行的绝对指令代码程序。
go目标文件
1、符号表
go程序编译后生成目标文件,目标文件中有一个Syms数组,这个数组是一个符号表,程序中定义的所有东西,包括函数,全局变量,类型,常量等等,都写在这个表里。
&goobj.Sym{
SymID: goobj.SymID{Name:”main.main”, Version:0},
Kind: 1,
DupOK: false,
Size: 48,
Type: goobj.SymID{},
Data: goobj.Data{Offset:137, Size:44},
Reloc: …,
Func: …,
}
SymID 唯一的符号 ID。这个 ID 值包含了符号的名称与版本号。版本信息可以帮助区分同名称的符号。
Kind 标识符号的所属的类型(稍后会有更加详细的介绍)
DupOK 标识是否允许符号冗余(同名符号)。
Size 符号数据的大小。
Type 引用另外一个表示符号类型的符号(如果存在)。
Data 包含二进制数据。不同类型的符号该域的含义不同。例如,对于函数该域表示汇编代码,对于字符串符号该域表示原始字符串,等等。
Reloc 重定位列表(稍后会有详细介绍)。
Func
包含函数符号的元数据(稍会有详细介绍)。
2、Reloc-go重定位
go目标文件中符号表中有Reloc数组,结构如下:
type Reloc struct {
Offset int
Size int
Sym SymID
Add int
Type int
}
3、函数元数据的结构体:
Func: &goobj.Func{
Args: 0,
Frame: 8,
Leaf: false,
NoSplit: false,
Var: {
},
PCSP: goobj.Data{Offset:255, Size:7},
PCFile: goobj.Data{Offset:263, Size:3},
PCLine: goobj.Data{Offset:267, Size:7},
PCData: {
{Offset:276, Size:5},
},
FuncData: {
{
Sym: goobj.SymID{Name:”gclocals·3280bececceccd33cb74587feedb1f9f”, Version:0},
Offset: 0,
},
{
Sym: goobj.SymID{Name:”gclocals·3280bececceccd33cb74587feedb1f9f”, Version:0},
Offset: 0,
},
},
File: {“/home/adminone/temp/test.go”},
},
如上的结构体是由编译器在目标文件中创建的函数元数据,在go运行时会用到这个数据结构。在运行时包内,这个结构体被映射为如下的结构体:
type _func struct {
entry uintptr // start pc
nameoff int32 // function name
args int32 // in/out args size
frame int32 // legacy frame size; use pcsp if possible
pcsp int32
pcfile int32
pcln int32
npcdata int32
nfuncdata int32 } 其中 pcsp、pcfile 与 pln。在程序计数器(program counter)被转换成栈指针、文件名、以及行号时会分别用到这三个域。
4、垃圾收集(GC)是如何使用函数元数据
Go语言使用标记-清除垃圾收集器,这种垃圾收集器分为两个阶段工作。第一阶段为标记阶段,GC遍历所有仍在使用的对象,并将其标记为可达。第二阶段为清除阶段,所有没有被标记的对象在该阶段被删除。
垃圾收集器从几个位置搜索可达的对象,包括全局变量,寄存器,栈帧以及可达对象的指针。它到底是如何区分栈中的变量是一个指针还是非指针类型呢?这就需要FuncData来发挥作用了。编译器都会为其创建两个位图向量。其中一个表示函数的参数的范围。另一个则表示栈帧中存储局部变量的区域。这两个位图变量可以告诉垃圾收集器栈帧中哪些位置上是指针,这些信息就可以帮助垃圾收集器完成垃圾收集工作了。
五、go链接器
1、链接器收集main包引用的所有其它包中的符号信息,并将它们装载到一个大的字节数组中
2、对于每个符号,链接器计算它在(数组)镜像中的地址。
3、然后他为每个符号应用重定位,
4、链接器准备所有ELF格式(linux系统中)文件或者PE格式文件(windows系统中)所需的文件头。然后它再生成一个可执行的文件。
链接器的工作原理:
链接器收集 main 包引用的所有其它包中的符号信息,并将它们装载到一个大的字节数组(或者二进制镜像)中。
对于每个符号,链接器计算它在镜像中的地址。
然后它为每一个符号应用重定位。这就非常简单了,因为链接器已经知道所有重定位项引用的符号的精确地址。
链接器准备所有 ELF 格式(Linux 系统中)文件或者 PE 格式文件(windows 系统中)所需的文件头。然后它再生成一个可执行的文件。