compile
Publish on 2024-09-19

Untitled.png

Overview

Go 语言的源码文件分为三类:命令源码、库源码、测试源码。
**命令源码文件:**是 Go 程序的入口,包含 func main() 函数,且第一行用 package main 声明属于 main 包。
**库源码文件:**主要是各种函数、接口等,例如工具类的函数。
**测试源码文件:**以 _test.go 为后缀的文件,用于测试程序的功能和性能。

go build 用来编译指定 packages 里的源码文件以及它们的依赖包,编译的时候会到 $GoPath/src/package 路径下寻找源码文件。go build 还可以直接编译指定的源码文件,并且可以同时指定多个。

通过执行 go help build 命令得到 go build 的使用方法:

usage: go build [-o output] [-i] [build flags] [packages]
  • o 只能在编译单个包的时候出现,它指定输出的可执行文件的名字。
  • i 会安装编译目标所依赖的包,安装是指生成与代码包相对应的 .a 文件,即静态库文件(后面要参与链接),并且放置到当前工作区的 pkg 目录下,且库文件的目录层级和源码层级一致。

build flags 参数,build, clean, get, install, list, run, test 这些命令共用一套:

参数作用
-a强制重新编译所有涉及到的包,包括标准库中的代码包,这会重写 /usr/local/go 目录下的 .a 文件
-n打印命令执行过程,不真正执行
-p n指定编译过程中命令执行的并行数,n 默认为 CPU 核数
-race检测并报告程序中的数据竞争问题
-v打印命令执行过程中所涉及到的代码包名称
-x打印命令执行过程中所涉及到的命令,并执行
-work打印编译过程中的临时文件夹。通常情况下,编译完成后会被删除
-C dir在运行命令前转到路径dir下,对命令中的所有文件均有效

Issues in go build

这里可以把go和C的编译过程类比,如果只有一个main文件则可以直接:

go build -o binfile main.go

但是如果main中引用了其他同目录下同package中的一些暴露的对象和方法,则需要:

go build -o binfile main.go ref1.go ref2.go ...

或者更普适的:

go build .

需要注意的是,****go test****可以直接跑,不需要把依赖的包全部写上,go build 会忽略 *_test.go 文件。

go get & go install

go getgo install 是 Go 语言中用于获取和安装包的命令,它们之间有以下区别:

  1. 功能:go get 用于获取(下载)远程代码仓库或更新已有的代码仓库,它会自动解析并下载依赖的包。而 go install 用于将代码包编译并安装到指定位置,将生成的可执行文件或库文件安装到 $GOPATH/bin$GOPATH/pkg 目录下。
  2. 使用方式:go get 后面通常跟着一个包的导入路径,它会下载并安装该包及其依赖。例如:go get github.com/example/package。而 go install 后面通常跟着一个包的相对路径或绝对路径,它会编译并安装指定的包。例如:go install ./path/to/package
  3. 安装位置:go get 下载的包会被存放在 $GOPATH/src 目录下,而 go install 会将编译生成的可执行文件或库文件安装到 $GOPATH/bin$GOPATH/pkg 目录下。
  4. 版本控制:go get 可以根据包的版本控制信息(如 Git 标签、GitHub Releases 等)下载指定版本的包。而 go install 则不涉及版本控制,它是根据当前代码的状态进行编译安装。

总结来说,go get 主要用于获取和更新包及其依赖,go install 主要用于编译并安装代码包到指定位置。

GOPATH

指定多个 GOPATH 的方法有两种:

  1. 使用环境变量:可以通过设置多个 GOPATH 环境变量来指定多个工作空间。每个 GOPATH 的值应该是不同的目录路径,多个路径之间使用操作系统特定的路径分隔符(如 Windows 上的分号 ;,Unix/Linux 上的冒号 :)进行分隔。例如,在 Linux 上可以这样设置两个 GOPATH:
export GOPATH=/path/to/gopath1:/path/to/gopath2
  1. 使用 Go Modules:如果你的项目使用了 Go Modules(Go 1.11+ 版本),可以在项目的根目录下创建一个 go.mod 文件,并使用 go mod init 命令初始化模块。在 Go Modules 中,不再需要设置 GOPATH,而是直接在项目目录中管理依赖和版本。每个项目可以独立指定依赖的版本,并且不会受到全局 GOPATH 的影响。使用这种方法,go get/ go install 的内容会保存在当前模块的bin目录下。

Debug

就用gdb!

i files 列出文件和 Entry point

开始你的表演

Asm

单纯的go汇编

go build -gcflags=-S main.go 1> main.S 2>&1

完整的编译过程

go build -x -work main.go 1> transcript.txt 2>&1

在命令行中,2>&1 中的 & 是用来表示文件描述符的标识符。在这种上下文中,1 表示标准输出(stdout),2 表示标准错误输出(stderr)。

使用 2>&1 的语法可以将标准错误输出重定向到与标准输出相同的目标。具体来说,2>&1 表示将文件描述符 2(标准错误输出)重定向到文件描述符 1(标准输出),也就是将标准错误输出合并到标准输出中。

更详细的指令

  • 先使用 go build -gcflags "-N -l" main.go 生成对应的可执行二进制文件 再使用 go tool objdump -s "main\." main 反编译获取对应的汇编
  • 使用 go tool compile -S -N -l main.go 这种方式直接输出汇编
  • 使用go build -gcflags="-N -l -S" main.go 直接输出汇编

-l 禁止内联

-N 编译时,禁止优化

-S 输出汇编代码

-s “target” 只输出匹配target的汇编

Go bootstrap

  1. 检查运行平台的CPU,设置好程序运行需要相关标志。
  2. TLS的初始化。
  3. runtime.argsruntime.osinitruntime.schedinit 三个方法做好程序运行需要的各种变量与调度器。
  4. runtime.newproc创建新的goroutine用于绑定用户写的main方法。
  5. runtime.mstart开始goroutine的调度。

最后用一张图来总结 go bootstrap 过程吧:

Untitled.png

main 函数里执行的一些重要的操作包括:新建一个线程执行 sysmon 函数,定期垃圾回收和调度抢占;启动 gc;执行所有的 init 函数等等。

上面是启动过程,看一下退出过程:

当 main 函数执行结束之后,会执行 exit(0) 来退出进程。若执行 exit(0) 后,进程没有退出,main 函数最后的代码会一直访问非法地址:

exit(0)
for {
    var x *int32
    *x = 0
}

正常情况下,一旦出现非法地址访问,系统会把进程杀死,用这样的方法确保进程退出。

关于程序退出这一段的阐述来自群聊《golang runtime 阅读》,又是一个高阶的读源码的组织,github 主页见参考资料。

当然 Go 程序启动这一部分其实还会涉及到 fork 一个新进程、装载可执行文件,控制权转移等问题。还是推荐看前面的两本书,我觉得我不会写得更好,就不叙述了。

© 2024 humbornjo :: based on 
nobloger  ::  rss