golang Quick Start [5.1] How -go language is running - Linker

preamble

Foreword

  • In the previous article, we described in detail go compiled to machine code language experience: lexical analysis => parse => type checking => intermediate code => code optimization => generate machine code
  • But in the process generate source code execution program, in fact, also experienced a links course. Overall life cycle of a program can be summarized as: writing code => compile => Links => loaded into memory => Execute
  • We will be individually explained in Chapter 5

Link (link)

  • We write the program may use other programs or libraries (library) as fmt package we used in the helloworld program
  • We write the program must be able to perform with these programs or libraries
  • Link is the process of writing a program in combination with our external program we need together
  • Link is system software, it plays a vital role in the development of the system, because it can be individually compiled. You can break it down into smaller, more manageable block, and then modify and compile separately, rather than a large organization as a whole application source files. When you change when one module, it simply recompile and relink the application without having to recompile the other source files.
  • Links are divided into two types, static and dynamic links link
  • Statically linked program will be characterized by the linker used in all libraries program is copied to the final executable file. The only position in the dynamic link the final executable file stored in the dynamic link library and call at runtime.
  • Therefore statically linked to faster, portable, because it does not exist in the library to run on its system. But take up more space on the disk and memory
  • Link process will take place in two places, one is the final step will occur statically linked at compile time, and a dynamic link occurred in the program is loaded into memory.
  • Let's look at a simple comparison of static and dynamic links link

go语言是静态链接还是动态链接?

  • 有时会看到一些比较老的文章说go语言是静态链接的,但这种说法是不准确的
  • 现在的go语言不仅支持静态链接也支持动态编译
  • 总的来说,go语言在一般默认情况下是静态链接的,但是一些特殊的情况,例如使用了CGO(即引用了C代码)的地方,则会使用操作系统的动态链接库。例如go语言的net/http包在默认情况下会应用libpthread与 libc 的动态链接库,这种情况会导致go语言程序虚拟内存的增加(下一文介绍)
  • go语言也支持在go build编译时传递参数来指定要生成的链接库的方式,我们可以使用go help buildmode 命令查看
» go help buildmode                                                                                                                                                             jackson@192
    -buildmode=archive
        Build the listed non-main packages into .a files. Packages named
        main are ignored.

    -buildmode=c-archive
        Build the listed main package, plus all packages it imports,
        into a C archive file. The only callable symbols will be those
        functions exported using a cgo //export comment. Requires
        exactly one main package to be listed.

    -buildmode=c-shared
        Build the listed main package, plus all packages it imports,
        into a C shared library. The only callable symbols will
        be those functions exported using a cgo //export comment.
        Requires exactly one main package to be listed.

    -buildmode=default
        Listed main packages are built into executables and listed
        non-main packages are built into .a files (the default
        behavior).

    -buildmode=shared
        Combine all the listed non-main packages into a single shared
        library that will be used when building with the -linkshared
        option. Packages named main are ignored.

    -buildmode=exe
        Build the listed main packages and everything they import into
        executables. Packages not named main are ignored.

    -buildmode=pie
        Build the listed main packages and everything they import into
        position independent executables (PIE). Packages not named
        main are ignored.

    -buildmode=plugin
        Build the listed main packages, plus all packages that they
        import, into a Go plugin. Packages not named main are ignored.
  • archive: 将非 main package构建为 .a 文件. main 包将被忽略。
  • c-archive: 将 main package构建为及其导入的所有package构建为构建到 C 归档文件中
  • c-shared: 将mainpackage构建为,以及它们导入的所有package构建到C 动态库中。
  • shared: 将所有非 main package合并到一个动态库中,当使用-linkshared参数后,能够使用此动态库
  • exe: 将main package和其导入的package构建为成为可执行文件
  • 本文不再介绍go如何手动使用动态库这一高级功能,读者只需现在知道go可以实现这一功能即可

编译与链接的具体过程

  • 下面我们以helloworld程序为例,来说明go语言编译与链接的过程,我们可以使用go build命令,-x参数代表了打印执行的过程
go build  -x main.go

输出如下:

WORK=/var/folders/g2/0l4g444904vbn8wxnrw0j_980000gn/T/go-build757876739
mkdir -p $WORK/b001/
cat >$WORK/b001/importcfg << 'EOF' # internal
# import config
packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.a
packagefile runtime=/usr/local/go/pkg/darwin_amd64/runtime.a
EOF
cd /Users/jackson/go/src/viper/XXX
/usr/local/go/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -complete -buildid JqleDuJlC1iLMVADicsQ/JqleDuJlC1iLMVADicsQ -goversion go1.13.6 -D _/Users/jackson/go/src/viper/args -importcfg $WORK/b001/importcfg -pack -c=4 ./main.go
/usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /Users/jackson/Library/Caches/go-build/cf/cf0dc65f39f01c8494192fa8af14570b445f6a25b762edf0b7258c22d6e10dc8-d # internal
cat >$WORK/b001/importcfg.link << 'EOF' # internal
packagefile command-line-arguments=$WORK/b001/_pkg_.a
packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.a
packagefile runtime=/usr/local/go/pkg/darwin_amd64/runtime.a
packagefile errors=/usr/local/go/pkg/darwin_amd64/errors.a
...
EOF
mkdir -p $WORK/b001/exe/
cd .
/usr/local/go/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=zCU3mCFNeUDzrRM33f4L/JqleDuJlC1iLMVADicsQ/r7xJ7p5GD5T9VONtmxob/zCU3mCFNeUDzrRM33f4L -extld=clang $WORK/b001/_pkg_.a
/usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out main
rm -r $WORK/b001/
  • 下面我们对输出进行逐行分析
  • 创建了一个临时目录,用于存放临时文件。默认情况下命令结束时自动删除此目录,如果需要保留添加-work参数。
WORK=/var/folders/g2/0l4g444904vbn8wxnrw0j_980000gn/T/go-build757876739
mkdir -p $WORK/b001/
cat >$WORK/b001/importcfg << 'EOF' # internal
  • 生成编译配置文件,主要为编译过程需要的外部依赖(如:引用的其他包的函数定义)
# import config
packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.a
packagefile runtime=/usr/local/go/pkg/darwin_amd64/runtime.a
  • 编译,生成中间结果$WORK/b001/pkg.a,
/usr/local/go/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -complete -buildid JqleDuJlC1iLMVADicsQ/JqleDuJlC1iLMVADicsQ -goversion go1.13.6 -D _/Users/jackson/go/src/viper/args -importcfg $WORK/b001/importcfg -pack -c=4 ./main.go
  • .a文件由compile命令生成,也可以通过go tool compile进行调用
  • .a类型的文件又叫做目标文件(object file),其是一个压缩包,内部包含了_.PKGDEF`、`_go.o两个文件,分别为编译目标文件和链接目标文件
$ file _pkg_.a # 检查文件格式
_pkg_.a: current ar archive # 说明是ar格式的打包文件
$ ar x _pkg_.a #解包文件
$ ls
__.PKGDEF  _go_.o
  • 文件内容由代码导出的函数、变量以及引用的其他包的信息组成。为了弄清这两个文件包含的信息需要查看go编译器实现的相关代码,相关代码在src/cmd/compile/internal/gc/obj.go文件中(源码中的文件内容可能随版本更新变化,本系列文章以Go1.13.5版本为准)
  • 下面代码中生成ar文件,ar文件 是一种非常简单的打包文件格式,广泛用于linux中静态链接库文件中,文件以 字符串"!\n"开头。随后跟着60字节的文件头部(包含文件名、修改时间等信息),之后跟着文件内容。因为ar文件格式简单,Go编译器直接在函数中实现了ar打包过程。
  • startArchiveEntry用于预留ar文件头信息位置(60字节),finishArchiveEntry用于写入文件头信息,因为文件头信息中包含文件大小,在写入完成之前文件大小未知,所以分两步完成。
func dumpobj1(outfile string, mode int) {
    bout, err := bio.Create(outfile)
    if err != nil {
        flusherrors()
        fmt.Printf("can't create %s: %v\n", outfile, err)
        errorexit()
    }
    defer bout.Close()
    bout.WriteString("!<arch>\n")

    if mode&modeCompilerObj != 0 {
        start := startArchiveEntry(bout)
        dumpCompilerObj(bout)
        finishArchiveEntry(bout, start, "__.PKGDEF")
    }
    if mode&modeLinkerObj != 0 {
        start := startArchiveEntry(bout)
        dumpLinkerObj(bout)
        finishArchiveEntry(bout, start, "_go_.o")
    }
}
  • 生成链接配置文件,主要为需要链接的其他依赖
cat >$WORK/b001/importcfg.link << 'EOF' # internal
packagefile command-line-arguments=$WORK/b001/_pkg_.a
packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.a
packagefile runtime=/usr/local/go/pkg/darwin_amd64/runtime.a
packagefile errors=/usr/local/go/pkg/darwin_amd64/errors.a
...
EOF
  • 执行链接器,生成最终可执行文件main,同时可执行文件会拷贝到当前路径,最后删除临时文件
/usr/local/go/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=zCU3mCFNeUDzrRM33f4L/JqleDuJlC1iLMVADicsQ/r7xJ7p5GD5T9VONtmxob/zCU3mCFNeUDzrRM33f4L -extld=clang $WORK/b001/_pkg_.a
/usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out main
rm -r $WORK/b001/

总结

  • 在本文中,我们介绍了go程序从源代码到运行需要经历的重要一环——链接,并介绍了静态链接与动态链接
  • 在本文中,我们用一个例子介绍了编译与链接的具体过程
  • 在下文中,我们将介绍go语言的内存分配

参考资料

欢迎关注下方微信公众号,查看更多原创文章

发布了80 篇原创文章 · 获赞 1 · 访问量 1万+

Guess you like

Origin blog.csdn.net/weishixianglian/article/details/104342531