如何创建静态链接共享库

时间:2014-08-07 01:23:31

标签: c arm shared-libraries ld static-linking

对于我的硕士论文,我正在尝试为ARM Cortex-M3嵌入式系统调整共享库方法。由于我们的目标板没有MMU,我认为使用“普通”动态共享库是没有意义的。因为.text是直接从flash执行的,并且.data在启动时被复制到RAM,所以我无法解决相对于代码的.data,因此GOT也是如此。必须通过必须在链接时定义的绝对地址来访问GOT。那么为什么不在链接时为所​​有符号分配固定的绝对地址......?

从书"Linkers and Loaders"我了解到“静态链接共享库,即库中程序和数据地址在链接时绑定到可执行文件的库”。链接的章节描述了如何创建这些库,并提供对Unix System V,BSD / OS的引用;还提到了Linux和它的uselib()系统调用。不幸的是,本书没有提供如何实际创建诸如工具和/或编译器/链接器开关之类的库的信息。除了那本书之外,我几乎没有找到任何关于这些“野外”图书馆的信息。我在这方面唯一发现的是prelink用于Linux。但是,由于这是在“正常”的动态库上运行,而不是我正在寻找的。

我担心这些类库的使用非常具体,因此不存在创建它们的常用工具。虽然在这种情况下提到的uselib()系统调用让我感到疑惑。但是我想确保在开始破解我自己的链接器之前我没有忽略任何东西......;)那么有人能给我更多关于这些库的信息吗?

此外,我想知道是否有任何gcc / ld交换机链接和重定位文件但保留文件中的重定位条目 - 以便可以重新重定位?我找到了“-r”选项,但完全跳过了重定位过程。有没有人有想法?

编辑:

是的,我也知道链接器脚本。 gcc libfoo.c -o libfoo -nostdlib -e initLib -Ttext 0xdeadc0de {{1}}我设法获得了某种关联&重定位的目标文件。但到目前为止,我还没有找到任何可能将主程序与此链接并将其用作共享库的可能性。 (链接动态共享库的“常规方式”将被链接器拒绝。)

3 个答案:

答案 0 :(得分:6)

概念

这种共享库的最小概念。

  • 相同代码
  • 不同的数据

这有变化。你支持图书馆之间的链接吗?引用是DAG结构还是完全循环?您想将代码放入ROM中,还是支持代码更新?您是否希望在最初运行进程后加载库?最后一个通常是静态共享库动态共享库之间的区别。虽然许多人也会禁止图书馆之间的参考。

设施

最终,一切都将归结为处理器的寻址模式。在这种情况下,ARM拇指。 loader 通常耦合到OS和正在使用的二进制格式。您的工具链(编译器和链接器)还必须支持二进制格式,并且可以生成所需的代码。

支持通过寄存器访问数据是APCS(ARM过程调用标准)中的固有内容。在这种情况下,通过sb(对于静态基础)访问数据,该寄存器为R9静态基础堆栈检查是可选功能。我相信你可能需要配置/编译GCC来启用或禁用这些选项。

选项-msingle-pic-base-mpic-register位于GCC manual。想法是操作系统最初将为每个库用户分配单独的数据,然后在上下文切换上加载/重新加载sb。当代码运行到库时,数据将通过sb访问该实例数据。

Gcc' arm.c coderequire_pic_register(),可以为共享库中的数据引用生成代码。它可能对应于ARM ATPCS shared library机制。参见第5.5节

您可以通过使用宏和内联汇编程序以及可能的函数注释来绕过工具链,例如naked and section。但是,在这种情况下,库和可能的进程需要修改代码;即,像EXPORT(myFunction)等非标准宏

一种可能性

如果系统已完全指定(ROM映像),则可以设置偏移量,以便预先生成系统中每个库唯一的数据偏移量。使用链接描述文件可以很容易地完成此操作。使用NOLOAD并将库数据放在某些虚假部分中。甚至可以使主程序成为静态共享库。例如,您正在制作具有四个以太网端口的网络设备。主应用程序处理一个端口上的流量。您可以使用不同的数据生成应用程序的四个实例,以指示正在处理哪个端口。

如果您有大量混合/匹配的库类型,则库数据的占地面积可能会变大。在这种情况下,当通过外部API上的包装函数调用库时,需要重新调整sb

  void *__wrap_malloc(size_t size)  /* Wrapped version. */
  {
       /* Locals on stack */
       unsigned int new_sb = glob_libc; /* accessed via current sb. */
       void * rval;
       unsigned int old_sb;

       volatile asm(" mov %0, sb\n" : "=r" (old_sb);
       volatile asm(" mov sb, %0\n" :: "r" (new_sb);
       rval = __real_malloc(size);
       volatile asm(" mov sb, %0\n" :: "r" (old_sb);
       return rval;
  }

请参阅GNU ld --wrap选项。如果您拥有更大的同类库集,则需要这种复杂性。如果您的库只包含' libc / libsupc ++',那么您可能不需要包装任何内容。

ARM ATPCS具有由编译器插入的用于执行等效的

的胶合代码
LDR a4, [PC, #4] ; data address
MOV SB, a4
LDR a4, [PC, #4] ; function-entry
BX a4
DCD data-address
DCD function-entry

使用此技术的库数据大小为4k(可能为8k,但可能需要编译器修改)。限制是通过ldr rN, [sb, #offset],ARM限制偏移到12位。使用包装,每个库都有4k的限制。

如果原始应用程序构建时有多个未知的库,则需要将每个库包装起来并通过OS加载程序在主应用程序静态库中的固定位置放置GOT类型表。每个应用程序都需要空间用于每个库的指针。如果应用程序未使用该库,则操作系统不需要分配空间,该指针可以是NULL

库表可以通过.text中的已知位置,通过原始进程sb或通过堆栈掩码进行访问。例如,如果所有进程都获得2K堆栈,则可以为库表保留低16个字。 sp & ~0x7ff将为所有任务提供隐式锚点。操作系统也需要分配任务堆栈。

请注意,此机制与ATPCS不同,后者使用sb作为表来获取实际库数据的偏移量。由于内存对于描述的Cortex-M3而言相当有限,因此每个库不太可能需要使用超过4k的数据。如果系统支持分配器,则可以解决此限制。

<强>参考

答案 1 :(得分:2)

你附加了多少内存? Cortex-M系统片上只有几十个kiB,其余的则需要外部SRAM。

  

我无法解决相对于代码的.data

你不必。您可以将库符号跳转表放在固定位置的.data段(或行为相似的段)中。

  

因此也是GOT。必须通过必须在链接时定义的绝对地址来访问GOT。那么为什么不在链接时为所​​有符号分配固定的绝对地址......?

没有什么能阻止你将第二个GOT放置在固定位置,这是可写的。您必须指示链接器在何处以及如何创建它。为此,您可以为链接器提供一个所谓的“链接器脚本”,它是最终程序内存布局的模板蓝图。

答案 2 :(得分:0)

在评论您的意图之前,我会尝试回答您的问题。

在linux / solaris /任何使用ELF二进制文件的平台上编译文件:

gcc -o libFoo.so.1.0.0 -shared -fPIC foo1.c foo2.c foo3.c ... -Wl,-soname=libFoo.so.1

我将在下面解释所有选项:

-o libFoo.so.1.0.0

是我们将在链接后为共享库文件提供的名称。

-shared

表示您最后有一个共享对象文件,因此在编译和链接后可能会有未解决的引用,这将在后期绑定中解决。

-fPIC

指示编译器生成与位置无关的代码,因此库可以以可重定位的方式链接。

-Wl,-soname=libFoo.so.1

有两部分:首先,-Wl指示编译器将下一个选项(用逗号分隔)传递给链接器。选项为-soname=libFoo.so.1。此选项告诉链接器用于此库的soname。 soname的确切值是自由样式字符串,但是使用库的名称和主要版本号是一种方便的习惯。这很重要,因为当您对共享库进行静态链接时,库的soname会粘在可执行文件上,因此只能加载具有该soname的库来协助此可执行文件。传统上,当只更改库的实现时,我们只更改库的名称,而不更改soname部分,因为库的接口没有改变。但是当您更改界面时,您正在构建一个新的,不兼容的界面,因此您必须更改soname部分,因为它不会与其他版本冲突。它的。

链接到共享库与链接到静态链接(扩展名为.a的链接)相同,只需将其放在命令文件中,如:

gcc -o bar bar.c libFoo.so.1.0.0

通常,当你在系统中获得一些库时,你会在/ usr / lib目录中获得一个文件和一个或两个符号链接:

/usr/lib/libFoo.so.1.0.0
/usr/lib/libFoo.so.1 --> /usr/lib/libFoo.so.1.0.0
/usr/lib/libFoo.so --> /usr/lib/libFoo.so.1

第一个是执行程序时调用的实际库。第二个是与soname的链接作为文件的名称,只是为了能够进行后期绑定。第三个是你必须要做的那个

gcc -o bar bar.c -lFoo

的工作。 (gcc和其他ELF编译器在libFoo.so目录中搜索libFoo.a,然后搜索/usr/lib

毕竟,对共享库的概念有一个解释,这可能会让你改变关于静态链接共享代码的图像。

动态库是多个程序共享其功能的一种方式(也就是代码,也许是数据)。我觉得你有点迷失方向,因为我觉得你错误地解释了静态链接的共享库意味着什么。

静态链接是指程序与它之后> 之前使用的共享库的关联,因此有硬连线程序与库具有的所有符号之间的链接。启动程序后,链接过程开始,您将获得一个运行所有静态链接共享库的程序。解析对共享库的引用,因为共享库在进程的虚拟内存映射中被赋予固定位置。这就是必须使用-fPIC选项(可重定位代码)编译库的原因,因为它可以在每个程序的虚拟空间中以不同方式放置。

相反,共享库的动态链接是指使用允许您加载(一旦程序执行)共享库的库(libdl.so)(甚至之前未知的一个),搜索它的公共符号,解决引用,加载更多与此相关的库(并且可以像链接器那样递归求解)并允许程序对其上的符号进行调用。该程序甚至不需要知道库在那里编译或链接时间。

共享库是与代码共享相关的概念。很久以前,有UNIX,并且通过它的所有实例共享文本段(不能让程序修改自己的代码的惩罚)取得了很大的进步,所以你必须等待它第一次加载。如今,代码共享的概念已扩展到库概念,并且您可以使用相同的库(可能是libc,libdl或libm)使用多个程序。内核会对使用它的所有程序进行计数引用,并且当没有其他程序正在使用它时,它就会被卸载。

使用共享库只有一个缺点:编译器必须创建可重定位代码以生成共享库,因为当我们尝试链接它时,一个程序使用的空间可以用于另一个库到另一个程序。这通常会在要生成的操作码集中强加限制,或者强制使用一个/多个寄存器来处理代码的移动性(这里有#)没有移动性,但有几个连接可以使它位于不同的地方)

相信我,使用静态代码只会导致您制作更大的可执行文件,因为您无法有效地共享代码,但使用共享库。