eCos包含了2个用于模块动态加载的组件,一个叫loader,另一个叫objloader。loader出现比较早,在2000年的时候由eCos的开发团队添加到eCos系统中,loader的实现原理与应用程序的动态链接库(.so,.dll)是一样的,动态链接库的实现需要编译器的支持,通常情况下只有为Linux等系统准备的编译器才支持动态链接库编译,eCos使用的编译器大多数是不支持动态链接库的。objloader出现比较晚,在2005年的时候由Anthony Tonizzo贡献,objloader的实现原理与Linux内核模块(.o,.ko)是一样的,.o文件是普通的ELF格式的可重定向目标文件,生成.o文件是每个编译器都可以支持的工作,因此没有loader的编译器问题。(俄罗斯的那颗卫星上使用的object loader大概就是objloader组件吧,Anthony Tonizzo这个名字看起来像俄国人的名字吗?)
eCos官网:http://ecos.sourceware.org
eCos中文技术网:http://www.52ecos.net
eCos交流QQ群:144940146。
http://blog.csdn.net/zoomdy/article/details/19763473
mingdu.zheng<at>gmail<dot>com
通常情况下,嵌入式系统软件最终得到的是一个静态编译的可执行映像文件。编译器将每一个.c文件编译成对应的.o文件,.o文件是符号地址未确定需要进行重定位的目标文件,最后由链接器将所有.o文件链接在一起,链接的过程也就是对.o文件符号进行解析重定位的过程。
使用动态加载模块时,部分代码被编译成.o文件后不会参与最终的链接过程,而在运行时刻,objloader加载该.o文件并对.o文件符号进行解析重定位。从本质上看,objloader扮演了运行时链接器的角色。使用链接器链接.o文件所得的可执行文件是静态不可变的,而objloader由于在运行时才进行链接动作,可以在运行时指定objloader加载哪个.o文件,因此是动态的,可以根据运行时刻的需求加载和卸载.o文件。
最新版本(CVS或Hg)的eCos支持PowerPC、i386和ARM处理器的动态模块加载。
objloader包含两种类型的加载器,分别支持从文件系统和存储器中加载模块。从文件系统加载是更加普遍和灵活的加载方式,如果文件系统是可写的,那么还可以在运行时更新或添加删除模块,实现运行时模块管理。从存储器加载则适合没有文件系统支持的场合或者是调试阶段,调试阶段通过gdb将模块写入内存后使用存储器加载方式进行加载。
objloader以独立的组件包存在,要使用objloader首先需要添加objloader组件包,通过eCos配置工具添加Object file loader组件。该组件的部分选项依赖于其他组件,根据冲突解决提示添加所需的组件包或者取消选项。
objloader提供的主要API有:cyg_ldr_open_library、cyg_ldr_find_symbol、cyg_ldr_close_library。
函数原型:
PELF_OBJECT cyg_ldr_open_library(CYG_ADDRWORD ptr, cyg_int32 mode);
这个函数的作用是加载一个模块,目前mode参数的有效值为CYG_LDR_MODE_FILESYSTEM和CYG_LDR_MODE_MEMORY,如果mode为CYG_LDR_MODE_FILESYSTEM,那么ptr应该是指向模块的文件路径字符串,objloader将从文件系统加载该模块,如果mode为CYG_LDR_MODE_MEMORY,那么ptr为模块所在的存储器地址,objloader将从存储器加载该模块。如果模块加载成功,那么函数返回PELF_OBJECT类型的句柄指针,这个句柄指针被其它两个函数所引用;如果模块加载失败,那么返回NULL。
函数原型:
void *cyg_ldr_find_symbol(void* handle, char* sym_name);
这个函数的作用是根据符号名查找对应的地址。handle为cyg_ldr_open_library返回的句柄指针,sym_name为需要查找的符号名。如果指定符号存在,那么函数返回符号地址;如果指定符号不存在,那么返回NULL。
函数原型:
void cyg_ldr_close_library(void* handle);
这个函数卸载handle句柄对应的模块并释放资源。
// 模块代码 hello.c void print_message(void) { printf("Hello World!\n"); }
# 编译模块 gcc -c -o hello.o hello.c
// 引用模块 void* handle; void (*fn)(void); // 加载模块 handle = cyg_ldr_open_library((CYG_ADDRWORD)"/hello.o", CYG_LDR_MODE_FILESYSTEM); // 查找print_message函数地址 fn = cyg_ldr_find_symbol(handle, "print_message"); // 调用模块内的print_message函数 fn(); // 卸载模块 cyg_ldr_close_library(handle);
完整的示例可以查看objloader组件包的测试用例。
一个系统被分割成多个部分后,调试开始变得复杂甚至不可能,不过万能的gdb还是提供了解决办法。按照往常的做法,gdb是无法得知动态加载模块的调试信息的。一方面,gdb不知道被调试程序又加载了额外的代码,毕竟这不是常规的动态链接库。另一方面,gdb不知道模块被加载到内存的什么位置。这两方面的信息都必须通过gdb的add-symbol-file告知gdb。
add-symbol-file <文件> <加载地址> [-s <节名称> <节加载地址> -s <节名称> <节加载地址> ...]
<文件>为动态加载的模块.o文件名,需要注意的是<文件>是宿主机上的文件路径而不是在目标机上的文件路径,<加载地址>是指.o文件的.text节的加载地址,后面可以追加不限数目的-s参数指定除.text节之外的其它节的加载地址。
下面的示例告知gdb加载hello.o的调试符号,并且该文件的.text节定位到地址0,.text.library_open节定位到地址0x20142e0,.text.print_message节定位到地址0x20140b8,.text.thread_a节定位到地址0x20143d0。该示例在Linux Synthetic Target下验证。
(gdb)add-symbol-file ~/ecos/configs/synth/objloader_build/services/objloader/current/testobj/hello.o 0 -s .text.library_open 0x20142e0 -s .text.print_message 0x20140b8 -s .text.thread_a 0x20143d0 add symbol table from file "/home/user/ecos/configs/synth/objloader_build/services/objloader/current/testobj/hello.o" at .text_addr = 0x0 .text.library_open_addr = 0x20142e0 .text.print_message_addr = 0x20140b8 .text.thread_a_addr = 0x20143d0 (y or n) [answered Y; input not from terminal] Reading symbols from /home/user/ecos/configs/synth/objloader_build/services/objloader/current/testobj/hello.o...done.
实际上只有objloader知道加载的节和地址,上面的示例中的加载地址是我使用gdb跟踪调试,查看objloader的内部数据结构才知道的,实际上完全可以让objloader在加载完一个模块后将加载的节及地址按照add-symbol-file要求的格式打印出来,然后只需要简单的拷贝objloader的打印输出到gdb中就好了,甚至可以编写gdb脚本进行自动交互。
原文:http://blog.csdn.net/zoomdy/article/details/19763473