近日在工作中碰到了个奇怪的程序崩溃问题,当测试动态模块更新的时候,无论是不是有实际的模块更新,
只要在运行过程中重新加载模块就会crash,一番调试过后,排除了不少常见问题

工具模块中具体实现的代码大致如下

Settings * GetSettings() {
    static Settings * sSettings = new Settings;
    return sSettings;
}

...
GetSettings()->SetReloaded(true);
...

代码看上去没有问题,并且在前后上下文也有注意加锁防止多线程冲突。、 在检查了内存指针后,唯一特殊的就是sSettings的内存地址不在模块的地址空间,而是处于进程的地址空间。
这好像有点意思啊,我印象中静态变量应该是在模块地址空间的,那么是不是编译器的生成了特殊的代码呢。
nm的结果显示sSettings的类型是 u ,并不是 b ,那么 ub 都是代表什么意思呢

"b"

    The symbol is in the uninitialized data section (known as BSS ).

"u"

    The symbol is a unique global symbol. This is a GNU extension to the standard set of ELF symbol bindings.
    For such a symbol the dynamic linker will make sure that in the entire process there is just one symbol
    with this name and type in use.

看上去 u 没啥问题啊,至少保证了无论模块怎么加载,卸载都会使用同一个地址访问静态变量。经过仔细的调试发现,为了防止内存泄露,
模块的卸载代码有清理内存的操作,大概类似

...
delete GetSettings();
...

而同时 u 保证了静态变量真的只会运行一次初始化代码,而不是每次加载模块的时候都会运行,这就造成了卸载再加载后访问crash。

原因找到了,那么解决方案是什么呢?

  1. 删除清理代码,反正整个进程只有一份,退出时自动清理了,不会内存泄露。但是 u 是GNU的扩展,使用其他编译器或者跨平台的时候可能有问题。
  2. 增加工具模块的导出表,不要导出sSettings,让它待在模块的bss区域,这样每次模块加载都会有自己的一份拷贝,每个模块都会自己初始化和释放。

对于linux平台来说,link的时候加入

LDFLAGS += -Wl,--version-script=$(srcdir)/libfoo.map

LD文档里有详细介绍,简单举例一下,global里放要导出的符号, local *的意思就是其它都hide

LIBFOO_1.0 {
  global:
    libfoo_init; libfoo_doit; libfoo_done;

  local:
    *;
};

对于windows平台来说,有多种方法,参考: Exporting from a DLL | Microsoft Docs