在参数typedef更改时重建动态库

时间:2017-08-02 17:43:04

标签: c linux shared-libraries

我们假设,我有一个C结构,DynApiArg_t

typedef struct DynApiArg_s {
    uint32_t m1;
    ...
    uint32_t mx;
} DynApiArg_t;

此结构的指针作为arg传递给函数说

void DynLibApi(DynApiArg_t *arg)
{
     arg->m1 = 0;
     another_fn_in_the_lib(arg->mold); /* May crash here. (1) */
}

存在于动态库libdyn.so中。通过dlopen/dlsym调用过程从可执行文件调用此API。

如果此动态库已更新为版本2,DynApiArg_t现在有新成员,请说m2,如下所示:

typedef struct DynApiArg_s {
    uint32_t m1;
    OldMbr_t *mold;
    ...
    uint32_t mx;
    uint32_t m2;
    NewMbr *mnew;
} DynApiArg_t;

如果没有完全重建通过dlopen/dlsym调用此API的可执行文件或其他库,则每次调用此API时,由于结构中任何成员的某些取消引用,我看到该过程崩溃。我知道访问m2可能是个问题。但是,如下所示访问会员mold会导致崩溃。

typedef void (*fnPtr_t)(DynApiArg_t*);


void DynApiCaller(DynApiArg_t *arg)
{
     void *libhdl = dlopen("libdyn.so", RTLD_LAZY | RTLD_GLOBAL);
     fnPtr_t fptr = dlsym(libhdl, "DynLibApi");
     fnptr(arg); /* actual call to the dynamically loaded API (2) */
}

在通过fnptr调用API时,在标记为(2)的行中,当旧的/现有成员(在lib的v1中,当最初编译DynApiCaller时)在(1)处被访问时,它恰好是任何垃圾值有时甚至是NULL

每次更新从属库时,如果没有完整重新编译可执行文件,处理此类更新的正确方法是什么?

我看过libs用symliks命名,版本号如libsolid.so.4。是否有与此版本控制系统相关的内容可以帮助我?如果是这样,你可以指出我为这些文件提供正确的文件吗?

1 个答案:

答案 0 :(得分:1)

有很多方法可以解决此问题:

  1. 在动态库名称中包含API版本。

    使用dlopen("libfoo.so")代替dlopen("libfoo.so.4")。该库的不同主要版本本质上是分开的,并且可以在同一系统上共存。因此,该库的软件包名称为libfoo-4。您可以同时安装libfoo.so.4libfoo.so.5。次版本,例如libfoo-4.2,安装libfoo.so.4.2,并将libfoo.so.4符号链接到libfoo.so.4.2

  2. 最初用零填充(在库的早期版本中要求为零)定义结构,并让以后的版本重用填充字段,但保持结构的大小相同。

  3. 使用版本化的符号名称。这是Linux扩展,使用dlvsym()。单个共享库二进制文件可以实现同一动态符号的多个版本。

  4. 使用resolver functions确定加载时的符号。这允许例如硬件架构优化的功能变体,可以在运行时选择,但是对于基于dlopen()的方法而言,它的用处不大。

  5. 使用一种结构来描述库API,并使用版本控制的函数来获取/初始化该API。

    例如,您的库的第4版可以实现

     struct libfoo_api {
         int   (*func1)(int arg1, int arg2);
         double *data;
         void  (*func2)(void);
         /* ... */
     };
    

    并且仅导出一个符号

     int libfoo_init(struct libfoo_api *const api, const int version);
    

    调用该函数将使用支持的符号初始化api结构,并假定该结构对应于指定的版本。单个共享库可以支持多个版本。如果不支持某个版本,则会返回失败。

    这对于插件类型的接口特别有用(尽管_init函数更有可能调用应用程序提供的功能注册功能,而不是填充结构),因为单个文件可以包含优化的功能针对许多版本进行了优化,并针对多种兼容的硬件架构进行了优化(例如,具有不同SSE / AVX / AVX2 / AVX512支持的AMD / Intel架构)。

请注意,上述实现细节可以“隐藏”在头文件中,从而使使用共享库的实际C代码更加简单。只需更改头文件以使用最适合该OS的方法,同时保持实际C接口相同,它还有助于使相同的API在多个OS上都能正常工作。