如何创建静态链接共享库

Cry*_*per 9 c arm shared-libraries ld static-linking

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

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

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

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

编辑:

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

art*_*ise 6

概念

这种共享库可能的最小概念.

  • 相同的代码
  • 不同的数据

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

设备

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

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

选项-msingle-pic-base-mpic-registerGCC手册中.想法是OS最初将为每个库用户分配单独的数据,然后sb在上下文切换上加载/重新加载.当代码运行到库时,通过sbfor实例数据访问数据.

Gcc的arm.c代码具有require_pic_register()为共享库中的数据引用生成代码的代码.它可能对应于ARM ATPCS共享库机制.见5.5节

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

一种可能性

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

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

  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;
  }
Run Code Online (Sandbox Code Playgroud)

请参阅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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

参考


Lui*_*ado 0

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

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

gcc -o libFoo.so.1.0.0 -shared -fPIC foo1.c foo2.c foo3.c ... -Wl,-soname=libFoo.so.1
Run Code Online (Sandbox Code Playgroud)

接下来我将解释所有选项:

-o libFoo.so.1.0.0
Run Code Online (Sandbox Code Playgroud)

是链接后我们要为共享库文件指定的名称。

-shared
Run Code Online (Sandbox Code Playgroud)

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

-fPIC
Run Code Online (Sandbox Code Playgroud)

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

-Wl,-soname=libFoo.so.1
Run Code Online (Sandbox Code Playgroud)

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

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

gcc -o bar bar.c libFoo.so.1.0.0
Run Code Online (Sandbox Code Playgroud)

通常,当您在系统中获取某个库时,您会在 /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
Run Code Online (Sandbox Code Playgroud)

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

gcc -o bar bar.c -lFoo
Run Code Online (Sandbox Code Playgroud)

工作。(gcc其他 ELF 编译器搜索libFoo.so,然后搜索libFoo.a, in/usr/lib, )

毕竟,这里有对共享库概念的解释,这也许会让您改变对静态链接共享代码的印象。

动态库是多个程序共享其功能的一种方式(这意味着代码,也许还有数据)。我认为你有点迷失方向,因为我觉得你在某种程度上误解了静态链接共享库的含义。

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

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

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

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

相信我,使用静态代码只会让您生成更大的可执行文件,因为您无法有效地共享代码,而是使用共享库。

  • “静态库”和“静态链接共享库”是完全不同的东西。请注意,在“静态链接共享库”中,每个单词都很重要。您可能没有听说过“静态链接共享库”,这项技术在 Unix 的早期就已经存在,并且几乎完全被遗忘并被动态共享库所取代。 (2认同)