现象
今天在为DragonOS编译http服务器程序时,遇到了一个神奇的bug:
程序在一台机器A上能够正常编译、正常运行,但是,换了机器B编译之后,就无法运行,会报错:
两台编译用的机器,操作系统都是Ubuntu22.04. 我一开始以为是机器B上面的编译器/链接器有问题,于是重新安装了编译工具链。但是无法解决问题。
对有故障的程序使用objdump -D命令进行反编译,发现其_init段变成了两个:
按照之前的开发经验可以知道,_init段是存在于crt*.o这几个文件内的,链接器会把这几个文件的_init段,按照顺序拼接起来。
然后再看正常运行的程序,反汇编之后的结果:
对比可以发现,异常程序把_init段的后半部分,加到了_init-0x2这个段内。这是错误的现象。
排错
由于正常机器A、异常机器B的操作系统、编译器、链接器版本相同,我首先怀疑问题出在编译出来的libc的文件上。于是,我把A编译出来的文件,在B上进行链接,发现问题仍然存在。把B编译出来的文件在A上链接,发现结果正常。
因此排除编译结果的问题,接下来把问题聚焦在链接过程上。
怀疑是机器B的系统自带的链接器有问题,因此我将A的链接器拷贝到B上,然后进行链接。发现问题依旧。重复对比实验,排除链接器问题。
于是,考虑链接参数问题。由于链接的时候使用了find命令查找crt*.o文件,并存储到一个数组中。因此把最终调用链接器的命令打出来,发现B机器上,输入链接器的文件参数顺序如下:
- main.o
- crt1.o
- crtn.o
- crti.o
- crt0.o
- libc.a
而正常的A机器上,输入链接器的文件参数顺序如下:
- main.o
- crt0.o
- crt1.o
- crti.o
- crtn.o
- libc.a
观察发现,机器A上,输入的crt*.o文件的顺序是按照升序排列的,而有问题的B机器则不是按照升序的。因此尝试在find命令后面加上” | sort” ,使得crt*.o按照升序排列。经过测试,调整之后,在B机器上面,编译出来的程序能够正常运行。
结论
链接器的链接顺序与文件输入顺序有关,并且crt*.o的链接顺序必须按照文件名升序排序。错误的顺序会导致程序无法运行,而链接器不会报任何错误。
并且,我们不能假设find命令输出的结果是按照升序排列的,必须使用sort命令进行排序,才能够确保结果升序。