直接覆盖so文件导致coredump
如果我们更新so文件不停进程而直接使用cp覆盖so的方式,有概率照成程序coredump。
先看一下用cp更新so的时候发生了什么事情
strace cp testnew.so testold.so
发现老的so被trunc了,在此过程中发生的事情为
- 应用程序通过dlopen打开so的时候,kernel通过mmap把so加载到进程地址空间,对应于vma里的几个page。
- 在这个过程中loader会把so里面引用的外部符号例如malloc printf等解析成真正的虚存地址。
- so被cp覆盖时,确切地说是被trunc时,kernel会把so文件在虚拟内的页purge 掉。
- 当运行到so里面的代码时,因为物理内存中不再有实际的数据(仅存在于虚存空间内),会产生一次缺页中断。
- kernel从so文件中copy一份到内存中去
- 但是这时的全局符号表并没有经过解析,当调用到时就产生segment fault
- 如果需要的文件偏移大于新的so的地址范围,就会产生bus error
所以,如果用相同的so去覆盖
- 如果so 里面依赖了外部符号,coredump
- 如果so里面没有依赖外部符号,运气不错,不会coredump
coredump问题的根源就是因为so被trunc了,所以如果不用trunc的方式就可以避免这个问题。可以使用install命令规避该问题的出现。看看install的时候发生了什么
install 的方式跟cp不同,先unlink再create,当unlink的时候,已经map的虚拟空间vma中的inode结点没有变,只有inode结点的引用计数为0是,kernel才把它干掉.
也就是说so文件的inode号会发生改变,但老的so的inode号依旧存在,这时程序必须停止重启服务才能使用新的so,否则程序继续执行,使用的还是老的so,所以程序不会core掉。
现在我们知道cp so命令会导致运行的进程产生coredump并非是我们程序代码的问题,而是用不当方式升级so造成的,不用耗费精力去分析coredump文件从而来定位是哪里的代码有问题了。
库链接方式导致coredump
在项目中遇到一个问题。可执行文件fs_master_tests链接了fs_master动态库,fs_master库public方式链接了spdk库。执行文件A时报coredump,出错信息如下:
查看spdk的源代码,在tailq.h中定义了EAL_REGISTER_TAILQ
/**
* Register a tail queue.
*
* Register a tail queue from shared memory.
* This function is mainly used by EAL_REGISTER_TAILQ macro which is used to
* register tailq from the different dpdk libraries. Since this macro is a
* constructor, the function has no access to dpdk shared memory, so the
* registered tailq can not be used before call to rte_eal_init() which calls
* rte_eal_tailqs_init().
*
* @param t
* The tailq element which contains the name of the tailq you want to
* create (/retrieve when in secondary process).
* @return
* 0 on success or -1 in case of an error.
*/
int rte_eal_tailq_register(struct rte_tailq_elem *t);
#define EAL_REGISTER_TAILQ(t) \
RTE_INIT(tailqinitfn_ ##t) \
{ \
if (rte_eal_tailq_register(&t) < 0) \
rte_panic("Cannot initialize tailq: %s\n", t.name); \
}
在rte_common.h中定义了constructor属性的函数会先于main()执行,DPDK中定义了RTE_INIT()宏,用来声明一个构造函数:
#define RTE_PRIO(prio) \
RTE_PRIORITY_ ## prio
/**
* Run function before main() with high priority.
*
* @param func
* Constructor function.
* @param prio
* Priority number must be above 100.
* Lowest number is the first to run.
*/
#ifndef RTE_INIT_PRIO /* Allow to override from EAL */
#define RTE_INIT_PRIO(func, prio) \
static void __attribute__((constructor(RTE_PRIO(prio)), used)) func(void)
#endif
/**
* Run function before main() with low priority.
*
* The constructor will be run after prioritized constructors.
*
* @param func
* Constructor function.
*/
#define RTE_INIT(func) \
RTE_INIT_PRIO(func, LAST)
然后定义了很多初始化构造函数,比如pci_common_uio.c中
static struct rte_tailq_elem rte_uio_tailq = {
.name = "UIO_RESOURCE_LIST",
};
EAL_REGISTER_TAILQ(rte_uio_tailq)
出错原因是涉及到的constructor函数代码有多个实例,被重复调用。那么为什么fs_master_tests中有多个constructor实例呢。我们看看可执行文件fs_master_test的符号表
t表示在本地的代码段中有可执行符号tailqinitfn_rte_uio_tailq
查看fs_master_test的动态符号表中不包含tailqinitfn_rte_uio_tailq
查看fs_mater_test链接的动态库fs_master中本地代码段中含有符号tailqinitfn_rte_uio_tailq
而fs_master的动态符号表中同样没有包含tailqinitfn_rte_uio_tailq符号
当执行fs_mater_test时因为动态加载器在fs_mater_test的动态符号表中没有发现tailqinitfn_rte_uio_tailq符号,所以会将fs_master库中的tailqinitfn_rte_uio_tailq加载到fs_mater_test中,而fs_master_test本身的静态符号表中已有tailqinitfn_rte_uio_tailq,这样就照成tailqinitfn_rte_uio_tailq被执行了两次。问题的根源是fs_master_test能看到spdk库中的dpdk实现,所以对fs_master_test屏蔽掉spdk的dpdk实现就可以修复该问题。将fs_master链接spdk的方式从public改为private。
重新编译以后查看fs_master_test的静态符号表,可以看到在fs_master库中依然还有tailqinitfn_rte_uio_tailq符号。而在fs_master_test中已没有tailqinitfn_rte_uio_tailq符号。运行fs_master_test不再coredump
总结:
CMake中target_link_libraries(A B)中链接动态库的三种方式区别。
- PRIVATE 依赖项B仅链接到目标 A,若有C链接了目标A,C不链接依赖项B 。
- INTERFACE 依赖项B并不链接到目标 A,若有C链接了目标A,C会链接依赖B 。
- PUBLIC 依赖项B链接到目标A,若有C链接了目标A,C也会链接依赖项 B 。
从使用的角度解释,若有 C 链接了目标 A :
- 如果依赖项 B 仅用于目标 A 的实现,且不在头文件中提供给 C 使用,使用PRIVATE 。
- 如果依赖项 B 不用于目标 A 的实现,仅在头文件中作为接口提供给 C 使用,使用INTERFACE 。
- 如果依赖项 B 不仅用于目标 A 的实现,而且在头文件提供给 C 使用,使用PUBLIC