【问题标题】:C Shared library: static variable initialization + global variable visibility among processesC共享库:静态变量初始化+进程间全局变量可见性
【发布时间】:2012-01-06 10:38:24
【问题描述】:

我想修改现有的共享库,使其根据使用共享库的应用程序使用不同的内存管理例程。

(目前)将有两个系列的内存管理例程:

  • 标准的 malloc、calloc 等函数
  • malloc、calloc 等的特殊版本

我想出了解决这个问题的潜在方法(在 SO 上的一些人的帮助下)。目前仍有一些灰色区域,我想对我的提案提出一些反馈。

这就是我打算实现修改的方式:

  1. 用 my_malloc/my_calloc 等替换对 malloc/calloc 等的现有调用。这些新函数将调用正确分配的函数指针,而不是调用硬编码的函数名称。

  2. 为共享库提供一种机制来初始化 my_malloc 等使用的函数指针以指向标准 C 内存管理例程 - 这允许我为依赖于该共享库的应用程序提供向后兼容性 -也不必修改。在 C++ 中,我可以通过使用静态变量初始化(例如)来做到这一点 - 我不确定是否可以在 C 中使用相同的“模式”。

  3. 引入一个新的幂等函数 initAPI(type) 函数,由需要在共享库中使用不同 mem mgmt 例程的应用程序调用(在启动时)。 initAPI() 函数将内存 mgmt func ptrs 分配给适当的函数。

显然,如果我可以限制谁可以调用 initAPI() 或何时调用它会更可取 - 例如,在对库进行 API 调用后不应调用该函数 - 因为这会改变内存管理例程。所以我想限制它被调用的地方和由谁调用。这是一个访问问题,可以通过在 C++ 中将方法设为私有来解决,我不知道如何在 C 中做到这一点。

上面2和3中的问题可以用C++轻松解决,但是我受限于使用C,所以我想用C来解决这些问题。

最后,假设可以如上所述在初始化期间正确设置函数指针 - 我还有第二个问题,关于共享库中全局变量的可见性,使用共享库跨越不同进程。函数指针将被实现为全局变量(我现在不太关心线程安全 - 尽管我设想在某些时候使用互斥锁来包装访问)*并且每个使用共享库的应用程序不应干扰内存管理例程用于使用共享库的另一个应用程序。

我怀疑它是使用 shlib 在进程之间共享的代码(而不是数据) - 但是,我希望得到确认 - 最好带有支持该断言的链接。

*注意:如果我天真地淡化了由于上述“架构”而可能在未来发生的线程问题,请有人提醒我!..

顺便说一句,我正在 Linux (Ubuntu) 上构建库

【问题讨论】:

  • “这将改变 money mgmt 例程” ...您最好格外小心 :-)
  • @pmg:哦,快!弗洛伊德滑倒在那里:)
  • 澄清一下:这是 linux 特有的问题吗?
  • @Asaf 是的,我正在 Linux 上构建(如问题中所述)。我想,我会添加 Linux 和 Ubuntu 标签,因为它们是相关的。

标签: c linux ubuntu shared-libraries


【解决方案1】:

由于我不完全确定所问的问题是什么,我将尝试提供可能有用的信息。

您已经指出,假设您也在使用GNU 工具链可能是安全的。


GCC 提供了一个构造函数 function attribute,它会在执行进入main() 之前自动调用一个函数。您可以使用它来更好地控制何时调用库初始化例程 initAPI()

void __attribute__ ((constructor)) initAPI(void);

在库初始化的情况下,如果库在运行时加载,则在 dlopen() 返回之前执行构造函数例程;如果在加载时加载库,则在启动 main() 之前执行构造例程。


GNU 链接器有一个--wrap <symbol> 选项,允许您为系统函数提供包装器。

如果您与--wrap malloc 链接,对malloc() 的引用将重定向到__wrap_malloc()(您实现),对__real_malloc() 的引用将重定向到原始malloc()(因此您可以从内部调用它你的包装器实现)。

除了使用--wrap malloc 选项来提供对原始malloc() 的引用之外,您还可以使用dlsym() 动态加载指向原始malloc() 的指针。您不能直接从包装器中调用原始的malloc(),因为它将被解释为对包装器本身的递归调用。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>

void * malloc(size_t size) {
   static void * (*func)(size_t) = NULL;
   void * ret;

   if (!func) {
      /* get reference to original (libc provided) malloc */
      func = (void *(*)(size_t)) dlsym(RTLD_NEXT, "malloc");
   }

   /* code to execute before calling malloc */
   ...

   /* call original malloc */
   ret = func(size);

   /* code to execute after calling malloc */
   ...

   return ret;
}

我建议阅读题为 Tutorial: Function Interposition in LinuxJay Conrod's 博客文章,了解有关将动态库中的函数调用替换为对您自己的包装函数的调用的更多信息。

【讨论】:

    【解决方案2】:

    -1 缺乏具体问题。正文很长,可以写得更简洁一些,而且没有一个问号。

    现在解决您的问题:

    共享库的静态数据(您称之为“全局变量”)是每个进程的。您在一个进程中的全局变量不会干扰另一个进程中的全局变量。不需要互斥锁。

    在 C 中,您不能限制 [1] 谁可以调用函数。任何知道它的名字或有指向它的指针的人都可以调用它。您可以对initAPI() 进行编码,使其在不是第一个调用的库函数时明显中止程序(使其崩溃)。你是图书馆作者,你制定游戏规则,你对不遵守规则的程序员没有任何义务。

    [1] 可以用static声明函数,这意味着它只能被同一个翻译单元内的代码通过名字调用;任何设法获得指向它的指针的人仍然可以通过指针调用它。此类函数不会从库中“导出”,因此不适用于您的场景。

    【讨论】:

      【解决方案3】:

      实现这一目标:

      (目前)将有两个系列的内存管理例程:

      • 标准的 malloc、calloc 等函数
      • malloc、calloc 等的特殊版本

      在 Linux 上使用动态库是微不足道的,并且 不需要 需要您设计的复杂方案(@ugoren 建议的 LD_PRELOADdlopen 也不是) .

      当您想提供malloc 和朋友的专用版本时,只需将这些例程链接到您的主可执行文件中。瞧:您现有的共享库将从那里获取它们,无需修改

      您还可以将专用的malloc 构建到例如libmymalloc.so,并将该库放在链接行之前 libc,以达到相同的结果。

      动态加载器将使用它可以看到的第一个malloc,并从a.out 开始搜索列表,并按照它们在链接命令行中列出的相同顺序继续搜索其他库。

      更新:

      进一步考虑,我认为您的建议不会奏效。

      是的,它工作(我每天都使用这个功能,通过将tcmalloc链接到我的主可执行文件)。

      当您的共享库(提供 API 的库)在“幕后”调用 malloc 时,它会得到哪个(可能是多个)malloc 实现? first 对动态链接器可见的。如果您将malloc 实现链接到a.out,那将是那个

      【讨论】:

      • 听起来好得令人难以置信。但是,我不确定我是否完全了解您的 cmets。如果我定义了自己的 malloc 等版本,这些版本环绕着自定义函数(palloc 等),在链接过程中我不会收到“多个定义的符号”错误吗?否则,这是迄今为止最简单的解决方案。
      • 经过进一步思考,我认为您的建议不会奏效。你可能误解了我的问题。共享库公开了一个 API,其他应用程序在该 API 上调用函数(如预期的那样)。然而,在 API 中调用函数会导致共享库分配/释放内存。由于 API 调用,应用程序不知道共享库在“幕后”执行的任何内存管理。也就是说,共享库目前是 HARD CODED 以使用标准的 C 内存管理功能。我想改变这个,以便一些APPS可以改变用于内存管理的功能
      • @Homunculus Reticulli - 您可以使用函数插入将共享库中硬编码的内存管理调用替换为对您自己的包装函数的调用,而无需重新编译共享库。这将允许您的应用程序根据需要更改用于内存管理的函数。我在my answer 中简要介绍了这个概念
      【解决方案4】:

      你很容易要求你的初始化函数是:

      • 从主线程调用
      • 客户端可以只调用一次
      • 并且客户端可以通过参数提供可选的函数指针

      【讨论】:

        【解决方案5】:

        如果不同的应用程序在不同的进程中运行,使用动态库非常简单。
        该库可以简单地调用 malloc() 和 free(),并且想要覆盖它的应用程序可以加载另一个库,并为这些库提供替代实现。
        这可以通过 LD_PRELOAD 环境变量来完成。
        或者,如果您的库使用 dlopen() 加载,则只需先加载 malloc 库。

        这基本上是 valgrind 等替代 malloc 的工具所做的。

        【讨论】:

        • 不需要需要LD_PRELOADdlopen 来达到预期的效果。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-22
        • 1970-01-01
        • 2014-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多