概念
共享库的最小概念。
这有所变化。您是否支持链接库之间的链接?引用是 DAG 结构还是完全循环?您想将代码放在 ROM 中,还是支持代码更新?您是否希望在进程初始运行后加载库?这通常是静态共享库和动态共享库之间的区别。尽管很多人也会禁止在库之间引用。
设施
最终,一切都将归结于处理器的寻址模式。在这种情况下,是 ARM Thumb。 加载程序 通常与操作系统和正在使用的二进制格式耦合。您的工具链(编译器和链接器)还必须支持二进制格式,并能生成所需的代码。
支持通过寄存器访问数据是
APCS(ARM过程调用标准)中的内在特性。在这种情况下,数据通过
sb
(静态基址)访问,该寄存器为
R9
。静态基址和堆栈检查是可选功能。我认为您可能需要配置/编译GCC以启用或禁用这些选项。
选项
-msingle-pic-base
和
-mpic-register
在
GCC手册中。其思想是操作系统最初将为每个库用户分配单独的数据,然后在上下文切换时加载/重新加载
sb
。当代码运行到库时,将通过该实例的
sb
访问数据。
Gcc的arm.c代码具有require_pic_register()
函数,它为共享库中的数据引用生成代码。这可能对应于ARM ATPCS共享库机制。参见第5.5节
您可以使用宏和内联汇编以及可能的函数注释来规避工具链,例如naked
和section
。但是,在这种情况下,库和可能的进程需要进行代码修改;即非标准宏,如EXPORT(myFunction)
等。
一种可能性
如果系统被完全指定(一个ROM映像),你可以生成数据偏移量,这些偏移量对于系统中的每个库都是唯一的。这很容易通过链接脚本完成。使用
NOLOAD
并将库数据放入一些虚假部分。甚至可以使主程序成为一个
静态共享库。例如,您正在制作一个具有四个以太网端口的网络设备。主应用程序处理一个端口上的流量。您可以生成四个应用程序实例,其中不同的数据表示正在处理哪个端口。
如果您有大量混合/匹配的库类型,则库数据的占用空间可能会变得很大。在这种情况下,当通过外部API的包装函数调用库时,需要重新调整
sb
。
void *__wrap_malloc(size_t size)
{
unsigned int new_sb = glob_libc;
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]
MOV SB, a4
LDR a4, [PC, #4]
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的数据。如果系统支持分配器,则可以解决此限制。
参考资料