如何从共享库中导出符号

6
我使用RVDS编译器在Windows主机上,使用C源代码的*.o目标代码文件创建了一个共享库(*.so)。我将这个共享对象与一个应用程序链接(在Linux主机上使用gcc针对ARM目标),并获得一个可执行文件,但运行时会生成分段错误。(我知道我需要调试!)
如果我不创建共享库,而是使用相同的源文件创建静态库,然后将其与应用程序链接,再执行应用程序,则正常工作。
所以我的问题是:
1. 我是否需要使用某些构造在源文件中显式地导出符号(导出到应用程序的函数)或任何其他符号,以便在与应用程序链接时可以正常工作?需要什么,如何操作?
2. 共享库是如何工作的?即库中函数将被加载和运行的地址,在创建库时是否给定。应用程序(main())如何解析库函数要执行的地址?
3. 静态库是如何工作的?即在静态库的情况下,这种地址规范和解析是如何发生的?
3个回答

14

这是Linux上的工作原理:

1)不用做任何事情。但你可以使用gcc命令行参数-fvisibility来限制导出变量,并使用可见性属性显式标记导出条目。

2)可执行文件将具有所有导入函数的表(这些都是具有默认可见性的函数)。加载器/链接器将选择一个地址来加载库,并在运行之前填充此表,对这些函数的调用是间接调用。(请注意,这也适用于共享对象)

3)静态链接是在链接时(编译后)执行的。地址实际上是在汇编中替换的,它们是直接调用。

注:有一种叫做PIC(位置独立代码)的东西。据我所知,它处理了在同一共享对象中对数据/函数的引用,因此链接器在加载库时不必覆盖库的一半代码,因为代码不会对其自己的数据进行任何绝对引用。你可以尝试进行实验。


3
  1. 使用gcc编译器时,不需要导出符号,因为默认情况下它会导出所有符号;但是RVDS可能会有所不同。请查阅RVDS编译器文档(尝试将其配置为 '可重定位ELF'输出?)

  2. 在Linux上,共享库必须是可重定位的,因为基地址是在运行时确定的。生成位置无关代码是理想的,因为它减少了重新定位库所需的工作量。如果您的库不可重定位,它将会崩溃(换句话说,在制作动态库之前不要从目标文件中剥离重定位信息)。在选择基地址并重新定位内部引用后,符号会在运行时解析为地址。

  3. 对于静态库,所有符号解析、重定位和分配加载地址都发生在编译时。

我唯一的猜测就是,你的编译器生成的代码在运行时无法重定位。但这让我感到困惑,因为这样做应该会破坏静态库...

如果您直接从RVDS生成静态库和共享库,一个选项是尝试将该静态库转换为共享库:

gcc -shared -o libfoo.so libfoo.a

如果这有所帮助,那么RVDS的共享库链接器(或其配置)很可能出了问题。

libfoo.a 必须在 -o libfoo.so 之后(对于初学者来说,这并不明显...) - undefined

0
你知道崩溃的原因吗?
如果你是通过动态加载共享库(例如通过 dlopen())加载的,那么有可能是你假设库已经成功加载,但实际上并没有,然后尝试通过空指针执行函数。

@Jonathan: 我没有使用 dlopen() 调用来加载共享库。 - goldenmean
好的 - 我已经没有更多的想法了。我对Unix/Linux更熟悉,也许在那方面我可以提供更多帮助。 - Jonathan Leffler

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