当整个软件工程被拆分成好几个功能模块之后,像那些通用的算法、适配不同硬件的代码,还有需要单独抽出来维护的业务逻辑,常常就会被放到共享库里。在VxWorks这个系统里面,RTP用户态程序和DKM内核模块是同时存在的,这两样东西不能用同一种方式去处理。RTP一般会生成.vxe格式的文件,它要靠.so文件在运行的时候进行链接;而DKM生成的是.out文件,一加载就直接进到内核环境里面去了。如果一开始没把这个方向分清楚,后面就算反复地去替换文件,也很难把问题解决掉。
一、VxWorks共享库应该怎么加载
共享库的加载,得把构建、部署和运行这三个步骤当成一个整体来看。一个.so文件能顺利生成出来,只能说明编译环节走通了,但是RTP程序里面到底有没有带上动态链接需要的信息,目标系统能不能找到并打开这个库文件,都还要接着去确认。
1、先把RTP共享库和DKM模块区分清楚
准备工程时,先在【RTP工程】、【共享库工程】和【DKM工程】之间确认输出类型,再决定使用so文件还是out文件。
在用户态这边,共享库是由RTP程序来调用的,所以不能直接把DKM那边装载模块的命令搬过来用。DKM走的是另一条路,一般可以用module load命令把它加载到目标系统里面,加载完成之后还要去检查一下内核里的符号是不是已经被正确地登记了。
2、让RTP程序带上动态链接的信息
如果使用Workbench来构建,可以回到【Build Properties】里面,确认RTP程序是按照动态可执行文件的方式来生成的,并且把需要依赖的共享库加到工程的依赖列表里面。
共享库编译完之后,自己会带有一个SONAME标识;而应用程序在链接的时候,也会把依赖的库名字写进一个叫NEEDED的记录里。等RTP程序一启动,系统里的动态链接器就会根据这些NEEDED记录,去把对应的.so文件一个一个找出来。如果是用命令行来管理工程,同样需要先编译好共享库,然后再去生成那个依赖它的动态应用程序,这个顺序不能搞反。
3、把.so文件放到目标设备能访问到的目录下
部署时可将so文件放入【ROMFS目录】或目标文件系统中的固定位置,启动RTP时再通过【LD_LIBRARY_PATH】传入库目录,例如执行rtp exec-e"LD_LIBRARY_PATH=/romfs/lib"demo.vxe&。
动态链接器在搜索库文件的时候,会参考运行目录之类的信息。对那些已经写入了SONAME的共享库,系统可以结合LD_LIBRARY_PATH环境变量、ld.so.conf配置文件、编译时嵌入的rpath,还有应用程序自己所处的目录,把这些位置组合起来去定位库文件。如果共享库没有定义SONAME,那通常最直接的办法就是把库文件和可执行程序放在同一个目录下面。
4、按需加载的功能采用插件的使用方式
某个模块不需要跟随RTP一起启动时,可在【插件加载入口】调用dlopen打开so文件,使用dlsym取得函数地址,并通过dlerror保留失败原因。
这种方式更适合那些不是必须启动的可选功能,或者需要在运行过程中动态切换的模块。当功能用完之后,还应该按照资源管理的逻辑去调用dlclose,把旧的句柄释放掉,免得它被别的代码不小心又引用到,引出一些不太好排查的麻烦。
二、VxWorks共享库加载失败常常卡在哪里
共享库加载失败的时候,原因往往不是出在业务代码上面。排查时可以先判断一下问题到底卡在哪个阶段,是文件没找到、ELF里面的记录对不上、依赖链条没有完整展开,还是符号解析这一步出了问题,这样排查的范围就能缩小很多。
1、目标系统没有找到库文件
启动RTP时,先从【Shell日志】中查看Shared object not found一类提示,再核对【LD_LIBRARY_PATH】填写的目录能否被目标板访问。
就算路径的文字没有写错,也不代表目标设备上的文件系统一定已经挂载好了;像libc.so.1或者某些二级依赖库缺失,也会让应用程序在启动阶段就卡住。如果库文件是放在开发主机那一边的,还要确认目标板到底有没有访问它的权限。
2、文件已经复制过去,但版本名称没对上
拿到vxe与so文件后,使用【readelf-d】查看NEEDED、SONAME和RPATH,再将记录中的名称与目标目录里的实际文件逐项比对。
在实际工程里面,经常能碰到这样的情况:libdemo.so文件明明已经放进目录里了,可是应用程序真正要找的却是libdemo.so.1,就因为名称末尾多了个版本号。所以文件名、版本信息和运行目录这三样东西要放到一起核对,光看目录下面有哪些文件很容易漏掉这种对不上的地方。
3、主库找到了,后续的依赖还是断开了
主库已经放入目标目录时,可结合【readelf-d】继续追踪二级依赖,并通过【shl】查看当前已经载入的共享库。
一个.so文件本身可能还依赖着其他.so文件。主库能被找到,只说明加载链路的第一层走通了;要是后面还缺某个库、符号名称有了变化,或者CPU架构跟工具链生成的代码不匹配,RTP程序仍然有可能在装载过程中就退出。
4、RTP程序仍然按静态方式构建
回到【构建配置】检查动态可执行文件选项是否生效,并确认链接阶段已经加入目标共享库。
如果应用程序是按静态方式生成的,那它在运行的时候就不会自动去查找和加载共享库,自然也就找不到那些.so文件。还有一种容易跑偏的情况,就是有人把给RTP用的.so文件当成了DKM模块去加载,从敲下命令的第一步开始方向就完全弄错了。
三、VxWorks共享库怎样缩短排查时间
共享库相关的问题常常一拖就是很久,主要是因为开发主机上的配置、目标设备里的文件系统,还有跑出来的运行日志,它们各自留了一部分线索,没有被拿到一起来看。如果把排查的步骤固定下来养成习惯,每次需要换库文件、改版本号或者调整存放路径的时候,就比较容易看出到底是哪一步造成了差异。
1、部署之前检查一下ELF记录
文件复制到目标板之前,先通过【readelf-d】核对vxe和so中的NEEDED、SONAME与RPATH,确认名称和目录规划一致。
在库的版本有过变动、输出目录换过位置,或者工程从静态链接改成了动态链接之后,这个检查动作特别有用,它能提前把很多需要反复尝试的问题挡在部署之前。
2、运行之后去查看真实的载入位置
RTP启动以后,可使用【rtp】查看进程状态,再调用【shl】和【shl info】确认共享库名称、引用数量与完整路径。
编译机器上的目录只能说明文件原来是从哪里拷贝过来的,而Shell命令的输出才能真实反映出应用在运行的时候到底加载了哪一份库文件。当系统里面可能存在同名的库副本时,这一步检查就变得格外关键。
3、调试时不要把暂停点放得过早
调试RTP时,结合【_start】确认动态链接器是否已经进入符号注册阶段,再判断共享库符号是否缺失。
如果程序还停在启动的早期阶段,共享库的很多符号很可能还没来得及完成注册和初始化,这个时候就直接下结论说库文件有毛病,容易把正常的加载顺序当成故障,反而带偏排查的方向。
总结
处理VxWorks共享库的加载问题,关键是先把RTP和DKM这两条路径分清,再沿着构建配置、ELF记录、目标部署和运行日志这条线索逐环验证,而不是在文件替换上来回试错。
