我正在为一个通过串行连接的设备编写一个裸机应用程序。设备上已经有一些基本代码(可能被认为是操作系统),但我希望能够在我的计算机上交叉编译代码,通过串行发送机器码,并让设备将机器码加载到内存中并运行它。我对安全性没有任何真实的的需求,因此通过串行发送的基本代码和机器码都在管理模式下运行(这是一个ARM设备),并且可以访问整个物理内存而无需分页。所有有问题的代码都是用C编写的(还有一些可以忽略的汇编)。
我唯一的问题是,我希望新编译的机器码能够调用在基本代码中定义的子程序。例如,基本代码有一个printf
函数,它会写入显示器,我希望能够从通过串行发送的代码中调用该printf
函数。我想我也许可以将printf
函数从基本代码中提取出来,并将其放入可重用的库中,但是,设备内存有限,所以我不想静态链接它,我认为动态链接器是多余的。
我目前的想法是让通过串行发送的代码与基本代码的符号一起编译。两者最终都在同一台机器上运行,所以不应该有任何问题。当前的构建过程将我的C代码与Clang交叉编译为ELF,然后使用objcopy code.elf -O binary code.img
提取机器码。唯一的问题是编译后通过串行发送的代码无法访问基本代码的符号。我需要从base.elf
中提取符号表,并在链接serial_code.o
时传递它。
我该怎么办?最好使用LLVM实用程序。
2条答案
按热度按时间omqzjyyz1#
我没能得到一个能用
objcopy
工作的解决方案,但是这个方法只需要readelf
和一些脚本语言(我使用Python)。我假设基本代码的ELF名为base.elf
。在未剥离的ELF文件中,符号表存储在
.symtab
部分。因此,要读取符号表,您所要做的就是读取带有readelf -s base.elf
的部分。输出如下所示(这是一个简化的示例,您的表可能会有所不同):字符串
我们所关心的是全局符号,因此您可以将此输出通过管道传输到
grep
,并仅过滤全局符号。如果你想限制哪些符号被复制,你可以做更多的过滤,但这取决于你。获得符号列表后,可以创建将每个符号定义为绝对地址的部件文件。此程序集文件中的每个条目看起来如下所示:
型
手工处理大量的符号会很快变得非常累人,所以我想出了一个Python脚本来自动将
readelf
输出转换为汇编文件:型
(The脚本接受两个命令行参数:输入ELF文件和输出程序集源的文件名)。
一旦获得了包含所需所有符号的汇编文件,就需要使用所选的汇编程序将其汇编成一个对象文件。如果你使用的是
clang
,那么你可以使用这个命令(任何指定ELF格式的目标都可以工作):型
这个目标文件将包含所有你想要的符号,现在你可以把它传递给你的链接器,符号将被解析为来自原始基ELF的地址。
请注意,您仍然需要定义在C中使用的所有符号,以使编译器满意。这可以通过使用
extern
定义或简单地包含base.elf
源代码中的头文件来完成。suzh9iv82#
您不需要提取符号表。您需要将代码与基础代码中已使用的代码符号链接起来。您可以正常链接基本代码(没有未满足引用的代码),然后将模块链接为共享对象(动态链接模块),所有链接都将在共享对象加载时完成。
这是做这件事的一般方法。如果你尝试自己做,通过探索基本可执行文件来定位符号并在运行时解析引用,你将在链接时做动态链接器正在做的事情,所以你的代码将增长(但不像动态链接器那样优化)到它实际拥有的大小。你是在重新发明轮子,在你得到一些可交付的东西之前,你可能会有很长的调试期。
我建议你阅读链接器
ld
如何工作的完整文档(编写链接器脚本的方式),因为它提供了比我上面描述的更多的可能性。另一种可能性是创建一个函数入口点表,并在某个全局变量中对其进行初始化。你只需要访问基本软件提供的函数的指针,这样你就可以从存储在全局变量中的指针访问入口点。
字符串
然后,在代码中,可以通过某种外部方式给出该全局变量的位置,并调用
型
当你的代码被加载到内存中。
这种方法不需要任何计算,因为主代码的地址在调用链接器时就已经分配好了。并且一旦您获得了该全局变量的地址就可以访问。(所有这些都可以在构建时静态地确定,因此您不需要在运行时做任何事情)。