有关于全局符号介入与多个动态库存在相同的符号相互覆盖的问题

mazinkaiser1991 2016-05-04 11:02:57
小弟最近在看《程序员的自我修养》,书中提到了全局符号介入问题,根据书中的代码进行了实验,实验代码如下:
a1.c

#include <stdio.h>
void a()
{
printf("a1.c\n");
}

复制代码
a2.c

#include <stdio.h>
void a()
{
printf("a2.c\n");
}

复制代码
b1.c

void a();

void b1()
{
a();
}

复制代码
b2.c

void a();

void b2()
{
a();
}

复制代码
编译命令如下:

gcc -fPIC -shared a1.c -o a1.so
gcc -fPIC -shared a2.c -o a2.so
gcc -fPIC -shared b1.c a1.so -o b1.so
gcc -fPIC -shared b2.c a2.so -o b2.so

复制代码
最后是main.c

#include <stdio.h>

void b1();
void b2();

int main()
{
b1();
b2();
return 0;
}

复制代码
编译命令如下:

gcc main.c b1.so b2.so -o main -Xlinker -rpath ./

复制代码
最后程序运行结果如下:

./main
a1.c
a1.c

复制代码
根据书中给出的解释是:当一个符号需要被加入全局符号表时,如果相同的符号名已经存在,则后加入的符号被忽略。到现在为止一切正常。
接下来问题就来了,偶然在网上看到了一段代码,并测试之。
foo.c

#include <stdio.h>

struct {
int a;
int b;
} b = { 3, 3 };

int main();

void foo()
{
b.a = 4;
b.b = 4;
printf("foo:\t(&b)=0x%08x\n\tsizeof(b)=%d\n\tb.a=%d\n\tb.b=%d\n\tmain:0x%08x\n",
&b, sizeof b, b.a, b.b, main);
}

复制代码
t1.c

#include <stdio.h>

int b = 1;
int c = 1;

int main()
{
int count = 5;
while (count-- > 0) {
t2();
foo();
printf("t1:\t(&b)=0x%08x\n\t(&c)=0x%08x\n\tsizeof(b)=%d\n\tb=%d\n\tc=%d\n",
&b, &c, sizeof b, b, c);
sleep(1);
}
return 0;
}

复制代码
t2.c

#include <stdio.h>

int b;
int c;

int t2()
{
printf("t2:\t(&b)=0x%08x\n\t(&c)=0x%08x\n\tsizeof(b)=%d\n\tb=%d\n\tc=%d\n",
&b, &c, sizeof b, b, c);
return 0;
}

复制代码
makefile如下:

export LD_LIBRARY_PATH:=.

test: t1.o t2.o
gcc -shared -fPIC -o libfoo.so foo.c
gcc -o test t1.o t2.o -L. -lfoo

t1.o: t1.c
t2.o: t2.c

.PHONY:clean
clean:
rm -f *.o *.so test*

复制代码
首先只对程序源码静态分析就可以发现:强符号b被定义了两次,不过编译时并没有报重定义错误,这是我的第一个问题?接下来看运行。

t2: (&b)=0x00601050
(&c)=0x00601054
sizeof(b)=4
b=1
c=1
foo: (&b)=0x00601050
sizeof(b)=8
b.a=4
b.b=4
main:0x00400766
t1: (&b)=0x00601050
(&c)=0x00601054
sizeof(b)=4
b=4
c=4
t2: (&b)=0x00601050
(&c)=0x00601054
sizeof(b)=4
b=4
c=4
foo: (&b)=0x00601050
sizeof(b)=8
b.a=4
b.b=4
main:0x00400766
t1: (&b)=0x00601050
(&c)=0x00601054
sizeof(b)=4
b=4
c=4
t2: (&b)=0x00601050
(&c)=0x00601054
sizeof(b)=4
b=4
c=4
foo: (&b)=0x00601050
sizeof(b)=8
b.a=4
b.b=4
main:0x00400766
t1: (&b)=0x00601050
(&c)=0x00601054
sizeof(b)=4
b=4
c=4
t2: (&b)=0x00601050
(&c)=0x00601054
sizeof(b)=4
b=4
c=4
foo: (&b)=0x00601050
sizeof(b)=8
b.a=4
b.b=4
main:0x00400766
t1: (&b)=0x00601050
(&c)=0x00601054
sizeof(b)=4
b=4
c=4
t2: (&b)=0x00601050
(&c)=0x00601054
sizeof(b)=4
b=4
c=4
foo: (&b)=0x00601050
sizeof(b)=8
b.a=4
b.b=4
main:0x00400766
t1: (&b)=0x00601050
(&c)=0x00601054
sizeof(b)=4
b=4
c=4

复制代码
通过程序可以清晰的发现,一开始b、c的值都还是1,但调用动态链接库中的foo函数后,b、c的内存地址没有改变,但值却改变了。
通过readelf -r 命令可以发现,test中并不存在b、c符号,证明上述符号在形成可执行文件时已完成重定位,但通过赋值情况来看,好像是运行加入的libfoo.so中的b将t1.c中的b覆盖了。
说了这么多,可能大家都乱了,现在把我的问题给大家总结以下
在第一个例子中:后加入的全局符号被忽略。
在第二个例子中:后加入的全局符号又覆盖前面被加入符号。
这两个例子从我直观上看是前后矛盾的,所以哪位大神可以给我分析,小弟万分感谢。
...全文
1463 15 打赏 收藏 转发到动态 举报
写回复
用AI写文章
15 条回复
切换为时间正序
请发表友善的回复…
发表回复
nswcfd 2016-09-18
  • 打赏
  • 举报
回复
第一个问题,不报编译错误是可以理解的。 nm t1.o,b和c的类型是D, nm t2.o,b和c的类型是C。 "C" The symbol is common. Common symbols are uninitialized data. When linking, multiple common symbols may appear with the same name. If the symbol is defined anywhere, the common symbols are treated as undefined references. "D" "d" The symbol is in the initialized data section. 第二个问题,13#楼已经给出解答了。
引用
其实全局符号介入忽略的只有你在libfoo.so里面定义的b(3,3)
foo里面修改b/c的反汇编指令为 0x00007ffff7dfc5e5 <+9>: mov 0x200314(%rip),%rax # 0x7ffff7ffc900 0x00007ffff7dfc5ec <+16>: movl $0x4,(%rax) 0x00007ffff7dfc5f2 <+22>: mov 0x200307(%rip),%rax # 0x7ffff7ffc900 0x00007ffff7dfc5f9 <+29>: movl $0x4,0x4(%rax) (gdb) p &b $8 = (<data variable, no debug info> *) 0x600b94 (gdb) p b $9 = 1 (gdb) x 0x7ffff7ffc900 0x7ffff7ffc900: 0x00600b94 <==== 这个正是t1里定义的b(初始化为1)
nswcfd 2016-09-18
  • 打赏
  • 举报
回复
好问题,只是排版有待加强,上下文跟踪起来有点累 :(
东边一耳 2016-09-16
  • 打赏
  • 举报
回复
我觉得你有点误解全局符号介入的用途了,全局符号介入只用在对符号进行定位,即指定该符号属于哪个module,哪个文件,并不限制后面对它内容进行更改(不管在哪个模块,哪个文件里面)。 你已经分析出变量b采用的是文件t1中的符号,那么初始值,地址都是使用的该值,后面再使用的时候,都会基于该值进行操作,所以你会看到t1里面还都是1,1,但是foo之后就都是4,4了。其实全局符号介入忽略的只有你在libfoo.so里面定义的b(3,3) 你可以把foo.c改简单一点试一下: int b =5; //全局空间的b将会被忽略 void foo() { b = 0; //这里的赋值将会对b的值起作用 } 希望没人误人子弟,呵呵
mazinkaiser1991 2016-05-04
  • 打赏
  • 举报
回复
引用 11 楼 chouchouwenzi 的回复:
就是这句 “当一个符号需要被加入全局符号表时,如果相同的符号名已经存在,则后加入的符号被忽略” ,其实是后加入的符号应该还可以用,但是地址已经是指向前边的那个符号的地址了
不好意思啊,我刚才去吃饭了。我说的就是这事啊,全局符号介入是前面的符号覆盖后面的符号。而我第二个程序是后面的符号覆盖了前面的符号。与书上说的正好相反了
我爱你我的菜 2016-05-04
  • 打赏
  • 举报
回复
就是这句 “当一个符号需要被加入全局符号表时,如果相同的符号名已经存在,则后加入的符号被忽略” ,其实是后加入的符号应该还可以用,但是地址已经是指向前边的那个符号的地址了
mazinkaiser1991 2016-05-04
  • 打赏
  • 举报
回复
引用 8 楼 chouchouwenzi 的回复:
可以说通就是他们b,c变量只向的地址相同(这个就是程序员修养里说的覆盖,链接时候才做),但各自打印调用类型不同(这个在编译时候做了),这是我猜测的。 另外这种讨论很有意义啊
您说什么覆盖?我现在就在看程序员的自我修养,不知道您说的是哪部分内容?
我爱你我的菜 2016-05-04
  • 打赏
  • 举报
回复
可以说通就是他们b,c变量只向的地址相同(这个就是程序员修养里说的覆盖,链接时候才做),但各自打印调用类型不同(这个在编译时候做了),这是我猜测的。 另外这种讨论很有意义啊
我爱你我的菜 2016-05-04
  • 打赏
  • 举报
回复
可以说通就是他们b变量只向的地址相同(这个就是程序员修养里说的覆盖,链接时候才做),但各自打印调用类型不同(这个在编译时候做了,所以foo中依然可以使用struct类型),这是我猜测的。 另外这种讨论很有意义啊
mazinkaiser1991 2016-05-04
  • 打赏
  • 举报
回复
引用 6 楼 chouchouwenzi 的回复:
t1.c中的b是吧struct b屏蔽掉了的,否则第一次打印就是3,3了,至于后来变成4,4是因为b.a=4 b.b=4两句赋值。至于什么时候屏蔽,按道理来说应该在链接阶段检查符号冲突,可是为什么还能当作结构体用呢?
我把两个文件分别看过,编译过后b都已经重定向过了,在foo函数中的b在链接之前应该是重定向到了它自己的got中,但在运行时为什么又影响了main函数中的b呢?
mazinkaiser1991 2016-05-04
  • 打赏
  • 举报
回复
引用 4 楼 chouchouwenzi 的回复:
上面说的不对,的确是忽略了,首先每个文件编译都可以过,其次,在链接的时候,把t2 foo中的
我爱你我的菜 2016-05-04
  • 打赏
  • 举报
回复
t1.c中的b是吧struct b屏蔽掉了的,否则第一次打印就是3,3了,至于后来变成4,4是因为b.a=4 b.b=4两句赋值。至于什么时候屏蔽,按道理来说应该在链接阶段检查符号冲突,可是为什么还能当作结构体用呢?
我爱你我的菜 2016-05-04
  • 打赏
  • 举报
回复
上面说的不对,的确是忽略了,首先每个文件编译都可以过,其次,在链接的时候,把t2 foo中的
mazinkaiser1991 2016-05-04
  • 打赏
  • 举报
回复
引用 2 楼 chouchouwenzi 的回复:
至于重定义没报错是因为吧t2.c中的b和c当成是声明了
t2中的b和c确实是声明,但t1.c和foo.c都定义了b,我用nm看了以下,在库和可执行文件中,b的类型都是D(代表这是一个已经初始化的变量的符号),所以我想请问您的是这两个强符号是如何链接到一起的?
我爱你我的菜 2016-05-04
  • 打赏
  • 举报
回复
没覆盖,只是相互重叠了,你把b改成char型更加能验证。
我爱你我的菜 2016-05-04
  • 打赏
  • 举报
回复
至于重定义没报错是因为吧t2.c中的b和c当成是声明了

4,436

社区成员

发帖
与我相关
我的任务
社区描述
Linux/Unix社区 内核源代码研究区
社区管理员
  • 内核源代码研究区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧