《编译系统-自底向上研究方法》用clion调试链接器ld源码 - 弦外之音

/ 0评 / 0

源码下载地址:百度网盘,提取码:cat1 。

通过前面的文章分析,读者应该已经对 可执行文件 ELF 的格式有一定了解,本文主要讲解,链接器 ld 是如何把 main.osum.o 链接在一起的。

先执行以下命令,编译出来 main.osum.o .

gcc -c -o main.o main.c
gcc -c -o sum.o sum.c

为了方便读者对照, main.osum.o 这两个文件可以在 百度网盘 下载,提取码:q2sy



gcc 的编译过程其实比较复杂,会经历 词法分析,语法分析,中间代码生成,但是这些东西本文不讲,暂时把他们理解成一个黑盒子就行,他们最后都是生成 main.osum.o ,这两个 .o 文件是二进制文件,他们也是 ELF 格式的,所以也需要用 xelfviewer 软件 查看。

从上图可以看到, main.o 的符号其实是 比 main 文件少很多的,因为 链接器 还会把一些 操作系统 运行时的需要的一些符号加进来。


Ubuntu18 环境下的 链接器是 ld,实际上用 gcc 命令也能链接,只是我上面故意 使用了 -c 选项不进行链接,只是编译。gcc 的代码太多,不利于分析链接器的实现。讲一个扩展知识,gcc 命令 里面是调 collect2 命令来链接的,而 collect2 是对 ld 进行了一些封装。

ld 链接 main.osum.o 的命令如下:

ld -o main2 \
-dynamic-linker /lib64/ld-linux-x86-64.so.2 \
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o \
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/7 \
-L/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib -L/lib/x86_64-linux-gnu \
-L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/7/../../.. \
main.o sum.o \
-lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s \
--pop-state /usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o

实际上是有非常多的库链接进去。由于 系统自带 的 ld 命令没有符号表,不好用 gdb 调试。所以我们需要自己用 源码 编译出来 ld 命令。


ld 命令是在 binutils 里面的,所以打开 binutils 网站。如下:

可以看到 有很多命令行工具,大多数都是 用 BFD 库跟 opcodes 库实现的。binutils 是一个集成工具,他里面的 ldasnm 的版本号都是统一的


为了方便演示,我选择了一个 跟我 机器 版本一样的 ld 源码,ld 2.3.0 版本。下载之后,目录如下:

现在开始编译:

./configure --prefix=/usr/local/binutils_2.3.0
make -j48 

编译成功之后,就可以看到 相关的文件,上面我没有执行 make install 来转移编译好的 ld 文件走。因为我想在源码目录运行 ld ,对于 clion 配置来说会方便一些。

通过研究他的 makefile 文件,我知道 生成的 ld 可执行文件的路径是 ld/ld-new,用 新的 链接器再链接一下之前的 main.osum.o ,命令如下:

/home/ubuntu/Documents/binutils-2.30/ld/ld-new -o main-new \
-dynamic-linker /lib64/ld-linux-x86-64.so.2 \
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o \
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/7 \
-L/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib -L/lib/x86_64-linux-gnu \
-L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/7/../../.. \
main.o sum.o \
-lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s \
--pop-state /usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o

从上图可以看到,新的 链接器 ld-new 是没有问题的。


下面开始讲解如何 配置 clion 来调试,用 gdb 也是可以的,现在已经有符合表,只是不太直观。我的环境是 ubuntu18,clion 是2021年版本的。下面开始操作:

1,点击右上角的 Add Configuration ,添加配置。

img

2,点击 + 按钮,选择 Makefile Target

img

3,添加两个 make 规则,一个是 make clean (清除项目),第二个是 make(编译生成可执行文件ld),第二个规则的 target 不填就行了,如下图:

现在我们把 ld-new 删掉,再执行一次 configure,再点击 build-ld 这个按钮,看看能不能顺利编译出 ld-new

PS:注意,这里一定要先执行 configure,再执行 make ,要不生成的 ld-new 有问题。

从上图可以看到,正常编译出 ld-new 了。


4,现在可以添加 Makefile Application 了,如下图:

上图有几个注意的点。

1,如果没有 target 显示,点右边的设置按钮,随便加一个,再返回来,他就会跑出来 all 这个target。这个可能是 clion 的bug。

2,Program arguments 就是 ld 后面的参数,这个非常多,需要注意换行。为了方便读者,我直接贴没有换行符的参数,复制进去clion即可。

-o main-new -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/7 -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/7/../../.. main.o sum.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o

3,/home/ubuntu/CLionProjects/c-test/ 这个目录里面有 main.osum.o


现在 clion 调试环境准备完毕了,在 ldmain.cmain 函数打一个断点,如下:


我个人觉得,链接器其实做的活,是脏活累活,跟CURD没什么两样。不同的平台有不同的运行文件格式,ld 就是解析这些格式,合并各个 .o 文件,链接器干的活就是兼容。基本上没啥深度。不过熟悉 ld 源码也是有好处的,可以在编译一些项目出问题的时候,快速找到解决方法,例如 undefined reference to xxx。

相关阅读:

1,《ld 使用文档》


由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1,QQ:2338195090。

发表回复

您的电子邮箱地址不会被公开。