文档是google到的,作者:x.yin@hotmail.com
1 前言
每当程序中出现奇怪的问题时,人们总是习惯于抱怨所能想到的一切东西:kernel,C 库,编译器,链接器,其他人的代码,甚至硬件- 当然除了自己之外,然而,意料之中的是,绝大部分情况都是人们自己犯的错误. 所以当有人又在抱怨自己遇到到了一个奇怪的逻辑正确却运行错误的代码时,沉默的编译器和链接器以及uClibc 库被理所当然地成为了出气筒,可是,概率论又一次发挥了他神奇的统计作用–你还是掉在自己挖的叫“绝大部分”的区间里。
2 示例代码
2.1 例子说明
下面的这个例子是一个简化了的多线程同步的问题。该例子同时支持两种平台(arm/x86)和几个编译选项: V(verbose mode),S(static mode),R(librar选择顺序)。ARM板上的gcc和uClibc版本分别为gcc-2.95.3和uClibc-0.9.26;PC上的gcc和glibc版本分别为gcc-4.2和glic-2.7 。
2.2 逻辑验证
对于大多数与开发板硬件无关的代码,我们可以首先在X86上验证逻辑的正确性。尽管x86 和No-mmu 的ARM7TDMI 相比有很大的差异,x86版的程序依然能够帮你检测出大多数逻辑上的错误(很显然不是全部,比如no-mmu 的栈overflow问题)。
2.3 代码说明
程序是由两部分程序组成:一个简化的多线程程序simple.c和Makefile。
(1)缺省时ARCH为i386,可以通过make ARCH=arm 选择arm平台。
(2)x86下,可以通过make S=1 编译成静态连接的程序, arm 下由于所用格式为BFLT,静态选项无意义。
(3)缺省时,库链接顺序为-lpthread -lc;可以通过make R=1反转为-lc -lpthread。
(4)可以通过make V=1 打开verbose 编译器模式。
2.4 代码清单
2.4.1 simple.c
2.4.2 Makefile
3. 运行结果分析
3.1 运行结果
x86平台下:
• 动态,-lpthread -lc; make clean; make && ./test; 运行结果正确。
• 动态,-lc -lpthread; make clean; make R=1 && ./test; 运行结果正确。
• 静态,-lpthread -lc; make clean; make S=1 && ./test; 运行结果正确。
说明程序逻辑正确。
ARM平台下:
• case1: ARM, 静态, 库链接顺序-lc -lpthread; 结果异常:Received the signal出现了很多,事实上只发了一个signal。
• case2: ARM, 静态,库链接顺序-lpthread -lc; 结果运行正常。
3.2 结果分析
我们首先来分析ARM case1 运行结果异常的原因:
[yin-laptop@2]$ make clean; make ARCH=arm R=1
arm-elf-gcc -c simple.c
arm-elf-gcc -Wl,-elf2flt="-s32768" -o test simple.o
-lc -lpthread
[yin-laptop@2]$ arm-elf-objdump -dj .text test.gdb | less
__pthread_return_0 显然就是一个return 0的空函数,所以不停的会调用fprintf。那么为什么pthread_xxx 的调用会被解析成了__pthread_return0 呢?这个符号有时那里定义的呢?要想知道这些就只得去找uClibc的源代码了。
uClibc-0.9.26/libc/misc/pthread/weaks.c
weak_alias 的定义在_install/include/feature.h,其实就是定义一个同类型的别名,同时把别名改为weak属性。
很显然库顺序的错误是导致这类错误的根本原因。做符号解析时,当func中的pthread_cond_wait之类的函数首先search libc.a,找到后就直接拷贝到test中,而真正的pthread_cond_wait 函数则被简单的丢弃。
那为什么uClibc 放这些没用的函数呢?主要是为了减小链接后的程序大小,当没有显示链接-lpthread 时就被当作单线程看待,因此也就没必要copy那些用不到的东西。再看以正确的库顺序(ARM case2)链接后的结构:
[yin-laptop@2]$ make clean; make ARCH=arm
arm-elf-gcc -c simple.c
arm-elf-gcc -Wl,-elf2flt="-s32768" -o test simple.o -lpthread -lc
此时的pthread_cond_wait链接的是如假包换的正版函数而不是一个没用的备胎。
事实上-lc纯粹是多余的,没有必要的,手动运行:
[yin-laptop@2]$ arm-elf-gcc -c simple.c
arm-elf-gcc -v -o test simple.o -lpthread
Reading specs from /opt/toolchain-89/lib/
gcc-lib/arm-elf/2.95.3/specs
gcc version 2.95.3 20010315 (release)
/opt/toolchain-89/lib/gcc-lib/arm-elf/2.95.3/collect2
-X -o test
/opt/toolchain-89/lib/gcc-lib/arm-elf/2.95.3/crt0.o
/opt/toolchain-89/lib/gcc-lib/arm-elf/2.95.3/crti.o
-L/opt/toolchain-89/lib/gcc-lib/arm-elf/2.95.3
-L/opt/toolchain-89/arm-elf/lib simple.o
-lpthread -lgcc -lc -lgcc
可以发现gcc缺省已经为你加了-lc。本例中正是由于-lc 的泛滥才导致了这种运行时才会发现的链接错误。那为什么如此明显的-lpthread -lc 链接顺序会出错呢?事实上正是由于程序复杂度的降低才会使问题如此明显,实际的程序有几十个库之多,某些人为了符号解析的方便随意加了一个-lc,简单的问题就完全淹没在复杂性之中了。
由此可见,错误确实是源于无知而不是偶然和意外。