如何在 Linux 上挂钩 malloc 函数?

How to hook malloc function on Linux?

提问人:bobeff 提问时间:5/18/2023 最后编辑:bobeff 更新时间:5/18/2023 访问量:177

问:

我正在阅读 Fabien SanglardDriving Compilers 系列文章。在第 3 部分关于编译器的章节中,有一个挂钩 malloc 函数的示例。首先,显示了一个属于无限递归的错误解:

void* malloc(size_t sz) {
  void *(*libc_malloc)(size_t) = dlsym(RTLD_NEXT, "malloc");
  printf("malloced %zu bytes\n", sz);
  return libc_malloc(sz);
}

递归的原因是 dlsym 函数在内部调用 malloc

之后,提供了一个假定的固定解决方案:

#include <stdio.h>
#include <dlfcn.h>

static void* (*real_malloc)(size_t) = nullptr;

void *malloc(size_t size) {
    if(!real_malloc)  {
      real_malloc = dlsym(RTLD_NEXT, "malloc");
    }

    printf("malloc(%d) = ", size);
    return real_malloc(size);
}

除了解决方案并没有真正解决,因为它遇到了同样的问题。例如,如果我将函数从 malloc 重命名为 那么它将起作用,但它不再是钩子,因为其他软件使用 而不是 .有什么解决方案可以解决这个问题吗?my_mallocmallocmy_malloc

c linux malloc 共享库 钩子

评论

1赞 user17732522 5/18/2023
也许添加您正在使用的 libc 实现(例如 glibc、musl 等)。我怀疑它们的行为会是一样的。
1赞 dbush 5/18/2023
“dlsym 函数在内部调用 malloc”你确定吗?CentOS7 上的 glibc 2.17 似乎并非基于 valgrind。
1赞 yano 5/18/2023
那应该是printf("malloc(%zu) = \n", size);
1赞 yano 5/18/2023
你可以试试看跌期权,不知道这是否在内部调用,我猜不会。如果你也想看看尺寸,可能必须发挥创意..打印到本地 char 数组,然后,或者一次打印一个字节的大小?mallocputsputchar
1赞 Andrew Henle 5/18/2023
@yano 不是真的 - 很可能在内部调用自己并导致无限递归。 并且所有相关功能都无法安全使用。printf()malloc()printf()

答:

1赞 bobeff 5/18/2023 #1

更多的调查表明,真正调用并导致无限递归的是函数调用,而不是文章中所写的函数调用。这将问题减少到如何以不会发生这种情况的方式进行打印。我想出了以下解决方案:mallocprintfdlsym

#include <stdio.h>
#include <dlfcn.h>

static void* (*real_malloc)(size_t) = NULL;

void* malloc(size_t size) {
  if(!real_malloc)  {
    real_malloc = dlsym(RTLD_NEXT, "malloc");
  }

  static char isPrintF = 0;
  if (isPrintF) {
    return real_malloc(size);
  }

  char* p = real_malloc(size);
  isPrintF = 1;
  printf("malloc(%zu) = %p\n", size, p);
  isPrintF = 0;
  return p;
}

评论

1赞 Andrew Henle 5/18/2023
但请注意,像 with 那样使用静态标志不是多线程安全的。isPrintF
0赞 Erki Aring 5/18/2023
snprintf + 写也许?
0赞 Andrew Henle 5/18/2023
@ErkiAring不能保证是有效的 - 它可能会在内部使用或类似的功能。我知道这里唯一可以安全使用的平台是 Solaris。Linux 绝对不安全,因为 glibc 肯定在内部使用。snprintf()malloc()s[n]printf()s[n]printf()malloc()
2赞 Andrew Henle 5/18/2023 #2

首先,根据 7.1.4 库函数的使用,(草案)C11 标准第 4 段

标准库中的函数不保证是可重入的,并且可能会修改具有静态或线程存储持续时间的对象。

因此,您不能安全地使用 C 标准中的任何功能并保证安全。您需要依赖特定于平台的解决方案。

首先,您可以找到您的平台允许从信号处理程序中调用哪些函数 - 这些函数几乎必须是可重入的。

对于基于 POSIX 的系统,我假设您正在使用,因为您使用了 POSIX 函数 dlsym(),您可以从 2.4 Signal Concepts 开始,它有一个广泛的列表。

请注意,它位于异步信号安全函数列表中,但未列。write()printf()

所以你的代码

static void* (*real_malloc)(size_t) = nullptr;

void *malloc(size_t size) {
    if(!real_malloc)  {
      real_malloc = dlsym(RTLD_NEXT, "malloc");
    }

    printf("malloc(%d) = ", size);
    return real_malloc(size);
}

可以替换为

static void* (*real_malloc)(size_t) = nullptr;

void *malloc(size_t size) {
    if(!real_malloc)  {
      static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

      pthread_mutex_lock( &mutex );
      if(!real_malloc)  {
        real_malloc = dlsym(RTLD_NEXT, "malloc");
      }
      pthread_mutex_unlock( &mutex );
    }

    write( STDOUT_FILENO, "malloc()", strlen( "malloc()" );
    return real_malloc(size);
}

请注意,我省略了地址 - 需要将其转换为字符串。这并不难做到,并且可以很容易地找到有关如何做到这一点的示例,例如在如何将 int 转换为 C 中的字符串?的答案中。请注意,您不能使用任何使用非异步信号安全标准库函数的答案。

如果你在 Solaris 上运行,实际上在那里异步信号安全的。s[n]printf()

我还为多线程使用添加了一些保护 - 在获取应该保护的实际值时存在竞争条件,如果只是因为如果指针值损坏,您可能永远无法重现导致的任何错误。malloc()

编辑

根据 @ChrisDodd 的评论,已修复以解决对安全性的担忧:dlsym()

static void* (*real_malloc)(size_t) = NULL;

__attribute__((constructor))
static void initValues(void) {
    real_malloc = dlsym(RTLD_NEXT, "malloc");
}

void *malloc(size_t size) {
    write( STDOUT_FILENO, "malloc()", strlen( "malloc()" );
    return real_malloc(size);
}

请注意,替换中的代码现在要简单得多 - 不可能有竞争条件。malloc()

评论

1赞 Chris Dodd 5/18/2023
根据 POSIX 规范,dlsym 不是异步安全的,因此使用它可能是一个问题。在实践中,只有当使用 dlopen 打开的库的句柄调用时,它才可能调用 malloc(即,当使用特殊句柄 RTLD_DEFAULT 或 RTLD_NEXT 调用时,它们只查看已完全加载并解析对象)
0赞 Andrew Henle 5/18/2023
@ChrisDodd 好点子。尽管我在这里使用 async-signal-safe 作为可重入和“不使用等人”的代理(使用或任何相关函数将使函数 async-signal-un 安全)。 可能确实很危险 - 而且它不会是可重入的,因为它几乎必须使用内部锁来更改进程的地址空间,此外还可能使用 .不过,正如您所说,仅查找符号应该是安全的。malloc()malloc()dlsym()malloc()
0赞 Andrew Henle 5/18/2023
@ChrisDodd我更新了代码以解决潜在问题。dlsym()
0赞 Lorinczy Zsigmond 5/18/2023
原始帖子中是否提到了信号处理程序?