为什么程序会打印共享对象全局变量的错误值?

3

我使用以下命令从以下C代码构建了一个共享对象:

gcc -fPIC -shared libx.c -o libx.so

libx.c

extern int printf(const char *, ...);

int libvar = 250;

void libfunc(){

    printf("%d,",libvar);    
}

我使用以下命令将以下C代码链接到libx.so库:

gcc -no-pie -lx -L ./ -o main main.c

-no-pie : 告诉编译器生成一个ELF可执行文件而不是共享对象(因为这是我的编译器的默认设置)。

main.c

extern void libfunc();

int main(){
    libfunc();
}

现在,当启动程序时,理论上,动态加载器将把共享库libx.so映射到运行进程(./main)的地址空间中,并解析符号libfunclibvar(对于PIC更改.got.plt段中的内容),最终它将解析printf符号,然后打印libvar的值,即250
但是程序却打印出了一些奇怪的值:
root@afr0ck:~# ./main; printf "\n"
-1076465840

我已经花了很长时间来进行调试,但是我无法弄清楚发生了什么!


2
这是同一份代码吗?你的输出甚至没有逗号?使用的是哪个GCC版本,哪个平台?在Linux上链接失败了(依赖项顺序错误)! - Antti Haapala -- Слава Україні
2
有趣的是,如果我不在末尾加上-lx -L ./,就会出现链接器错误。即使是这样,我还需要将库安装在/lib中,因为它在当前工作目录中找不到。我使用的是Ubuntu 64位,也是6.3.0版本的。 - Antti Haapala -- Слава Україні
1
@AnttiHaapala 好的,这太尴尬了,我在/lib中使用了错误的共享对象。我忘记复制新库了。 - Karim Manaouil
@BasileStarynkevitch 无法删除该问题。但是,它可能会帮助到其他人! - Karim Manaouil
不行,因为你没有解释哪里出了问题。这与你在问题中展示的代码不同(而是系统配置错误的问题)。 - Basile Starynkevitch
显示剩余8条评论
3个回答

2

我稍微修改了你的示例。我的libfunc现在包含:

void libfunc(){
  printf("in libfunc libvar=%d\n",libvar);    
}

请注意,gcc参数的顺序非常重要(您的gcc参数顺序不正确)。有关更多信息,请阅读调用GCC

我使用以下命令编译了您的库:

gcc -fPIC -shared -Wall libx.c -o libx.so

然后我用以下命令编译了您的 main.c 文件:

gcc -Wall main.c -L. -lx -o main

然后执行失败,出现了以下错误:

 ./main: error while loading shared libraries: libx.so: cannot open 
                      shared object file: No such file or directory

这是正常的,参见ld-linux(8)

要纠正该错误,可以将LD_LIBRARY_PATH设置为包含libx.so目录(例如.),可以通过在运行程序的同一终端中输入以下命令来实现:
export LD_LIBRARY_PATH=.

或者使用适当的rpath编译您的程序。

  gcc -Wall main.c -Wl,-rpath,. -L. -lx -o main

无论哪种情况,我都得到了
  in libfunc libvar=250

当运行./main时,如预期所示。

顺便提一下,使用strace(1)可以帮助您找到错误(不在您展示的代码中,而是系统配置错误)。您还可以使用-g编译库和可执行文件,然后使用gdb调试器。


-1

由于这是一个动态变量,因此libvar在堆中分配不安全,可能会被其他在操作系统中使用相同内存的程序更改。

最佳实践是如果需要更改,则使用常量或每次调用时传递值。


这不是 ELF 目标的工作方式(似乎是 OP 正在使用的)。 - Florian Weimer

-1

有人指出库文件在/lib中,所以我去检查了一下,发现我使用了错误的库文件,它定义了相同的符号libfunclibvar,并且在函数libfunc中有一些其他的printf导致程序打印了错误的值。
因此,从技术上讲,这两个库之间的区别在于printf中的格式字符串参数,而我在调试时没有检查。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接