VxWorks动态加载

使用动态加载目标模块的方式有很多好处,比如可以在不破坏原来的环境下增加调试定位功能,相当于给系统打“补丁”,不需要编译原来的代码(甚至可以不用原来的代码)而只需要关注正在调试的代码,这样能减少编译时间和减少映像的加载量。

实现目标模块的动态加载有很多种方法,如在主机环境的界面上通过在目标模块上单击鼠标右键,选择“Download 文件名”;也可以通过wShell和GDB命令行窗口实现。本文通过tshell下使用ld()、loadModule()、loadModuleAt()中一个函数来实现,当然在代码中也可以自如地调用它们。

ld命令是由用户接口子程序库usrLib提供的一个加载命令。使用ld的前提是在config.h中定义INCLUDE_LOADER。这样,在usrRoot()函数中就会自动调用加载模块初始化函数moduleLibInit();同时,根据CPU类型,自动决定目标模块的格式。如果CPU是MIPS、PPC、ARM、I80X86、COLDFIRE、SIMSPARCSOLARIS、SH等,加载的目标模块格式是elf类型,就会调用loadElfInit();如果CPU是I960、AM29XXX等,加载的目标模块的格式则是coff类型,就会调用loadCoffInit()函数。

在ARM和PPC下,Tornado编译器生成的.o或.out都是elf类型,打开目标文件都会看到文件头有ELF(45,4C,46)标记。这时可以通过ftp工具把它加载到文件系统(如使用copy命令加载到RAM盘)中,再调用ld()或loadModule()函数加载到内存中运行。

ld的函数原型是:MODULE_ID ld( int syms, BOOL noAbort, char *name )。参数syms决定目标文件的符号怎么处理:0,添加全局符号到系统符号表中;1,添加全局和局部符号到系统符号表中;-1,符号不添加到系统符号表中。一般选1,便于在shell下使用其中的符号。参数noAbort表明是否可以忽略加载期间出现的错误,为TRUE则忽略,FALSE则不忽略。name则为加载的文件名,包含文件路径。注意:ld是一条shell命令,也就是它是为在shell下调用而设计的一个函数,所以尽量不要用在代码内部,因为在之后的vxworks版本中直接调用ld可能会不支持。

loadModule的函数原型是:MODULE_ID loadModule( int fd, int loadFlag )。fd为文件描述符号,需要先打开文件获取fd;参数loadFlag含义有LOAD_NO_SYMBOLS(2)、LOAD_LOCAL_SYMBOLS(4)、LOAD_GLOBAL_SYMBOLS(8)、LOAD_ALL_SYMBOLS(0xC)三种。

假如已经将需要加载的文件demo.o放到ram盘中,则加载到内存中的方式有以下几种:

-> ld(1,0,“/tffs0/demo.o”)

-> ld  fd = open(“/tffs0/demo.o”,O_RDONLY);

-> loadModule(fd,0xC);

-> close(fd);

加载函数返回的是MODULE_ID,这是该加载模块的标识,使用卸载unldByModuleId时可以模块ID。查看加载模块的具体信息的函数是moduleShow()。

对于某些应用,使用ld或loadModule会出现以下错误:

Relocation value does not fit in 24 bits.

ld error: error loading file (errno = 0x3d0001).

这个问题主要在内存空间大于32M,当往目标机上downloading编译好的模块时出现。

原因:

VxWorks和GNU/Wind River(diab)compiler要遵循EABI(Embedded Application Binary Interface),这是PowerPC架构的一个标准。这个标准对函数的调用是branching而不是jumping,而branching在 PowerPC架构下面是限制在32M以内的。(The PowerPC relative branch instruction is limited to jumps between +/- 32MB (24 bits = +/- 4M instructions, 4 bytes per instruction = +/- 32MB) of the current instruction. If an instruction cannot be resolved within a 24 bit range, it will print out the error above.)

一般来说,如果通过主机往目标机download,是不会出现这个问题的,因为这个时候的目标文件(比如.out文件)是download到了 WDB POOL里面,而WDB POOL是紧挨着VxWorks映像的,所以只要WDB POOL有足够大的空间就不会出现上述问题。

而通过目标机download(比如tshell)时,目标文件是download到系统内存池的最后,这个时候如果你的内存大于32M,就会出现上述问题。

为了规避这个问题,可以使用长跳转来完成函数的调用。可以通过在C/C++ compiler下加入编译选项解决。对于GNU,该选项是-mlongcall;对于diab,该选项是-Xcode-absolute-far。

当然也可以使用loadModuleAt()解决这个问题,将目标模块加载到指定地址,使所需要的函数在跳转地址以内。其原型是:MODULE_ID loadModuleAt( int fd, int loadFlag, void **ppText, void **ppData, void **ppBss)。ppText、ppData、ppBss分别是加载后的text段、data段和bss段所指向的地址。假如加载demo.o到1M空间处(保证这个空间没有被占用):

-> fd = open(“/tffs0/demo.o”,O_RDONLY);

-> pText = 0x100000; pData = pBss = 0xffffffff; /* (LD_NO_ADDRESS) */

-> loadModuleAt( fd, 8, &pText, &pData, &pBss);close(fd);

加载完成后,在shell下就可以执行最新的目标文件中的函数了。如果函数以前有的,按照最后加载的函数为准。可见,加载的目标模块都经过重定位、符号解析和添加的过程。

如果目标模块中使用的全局变量或函数在本模块和以前的程序中都没有定义,则加载时会出现类似undefined symbol的错误。

有时候从主机上下载的目标模块中的函数符号不会出现在目标机的shell中,反过来也一样,目标机的shell中生成的符号对主机wShell也不可见。出现这种情况的主要原因是目标机和主机的符号没有同步化,需要在编译映像时定义INCLUDE_SYM_TBL_SYNC.