【问题标题】:Gaining access to heap metadata of a process from within itself从自身内部访问进程的堆元数据
【发布时间】:2020-08-31 15:36:42
【问题描述】:

虽然我可以编写合理的 C 代码,但我的专长主要是 Java,所以如果这个问题没有意义,我深表歉意。

我正在编写一些代码来帮助我进行堆分析。我通过使用 LLVM 进行检测来做到这一点。我正在寻找的是一种从自身内部访问进程的堆元数据的方法。这样的事情可能吗?我知道有关堆的信息存储在许多malloc_state 结构中(例如main_arena)。如果我可以访问main_arena,我可以开始枚举不同的竞技场、堆、垃圾箱等。据我了解,这些变量都是静态定义的,因此无法访问。

但是有什么方法可以获取这些信息吗?例如,我可以使用/proc/$pid/mem 以某种方式泄露信息吗?

一旦我有了这些信息,我想基本上得到关于所有不同空闲列表的信息。因此,对于每种 bin 类型中的每个 bin,我想要 bin 中的块数及其大小。对于快速、小型和 tcache 箱,我知道我只需要索引来确定大小。我已经研究了这些结构是如何实现的以及如何遍历它们。所以我所需要的就是访问这些内部结构。

我查看了malloc_info,这是我的后备方案,但我也想获得有关tcache 的信息,我认为malloc_info 中不包含这些信息。

我考虑过的一个选项是构建一个自定义版本的 glibc,以非静态方式声明 malloc_struct 变量。但据我所知,构建自己的自定义 glibc 并不是很简单,因为您必须构建整个工具链。我正在使用clang,所以我必须根据我的自定义glibc从源代码构建LLVM(至少这是我通过研究这种方法所理解的)。

【问题讨论】:

  • 你应该拉取glibc的源代码。在malloc 子目录下,您可以检查所有源代码。特别是,查看hooks.c,有一个公共[非静态]函数:void *__malloc_get_state(void),它将来自各种子结构的很多信息序列化为struct malloc_save_state指针[即alloc'在内部编辑——因此,必须被释放]。由于有一个恢复:__malloc_set_state,我假设给定的结构有 everything 人们可能关心的(例如主竞技场、fastbins、binmap、bins,...)。 glibc 也有各种钩子函数。
  • 我相信你可以得到你想要[和/或需要]的东西,用上面和其他的钩子。我以前使用过这些钩子,我认为它们比尝试使用/proc/self/mem [它只是映射原始内存——不太有用] 有用得多。 如果您需要查找导出的内容,您可能会更幸运地解析readelf libc.so 输出或使用ELF [或bfd] 库来查找符号和与公共符号的地址(例如malloc)组合/重定位以找到正确的地址。如果您需要.so 负载的基地址,您可以解析/proc/self/maps
  • @CraigEstey 不幸的是,malloc_{get,set}_state 在很久以前在 glibc 2.25 中被删除了。见here
  • @MarcoBonelli 当然……它们很有用。我在fedora fc29 在家里,glibc 2.28,但我在看一个更旧的(fc22 glibc 源)。刚刚拉到fc29,是的,它已经消失了。它对调试很有用,但看起来它是为emacs [per cmets] 实现某种 hack。尽管如此,ELF 还是将 main_arena 作为本地符号,因此可以通过 maps/bfd 获取它的地址并从 glibc 源中抓取必要的 struct 定义以拼凑一些东西。

标签: c malloc heap-memory glibc libc


【解决方案1】:

我最近有一个类似的要求,所以我确实认为能够为给定进程访问main_arena 确实有其价值,例如事后内存使用分析。

使用dl_iterate_phdrelf.h,根据本地符号解析main_arena相对简单:

#define _GNU_SOURCE
#include <fcntl.h>
#include <link.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

// Ignored:
// - Non-x86_64 architectures
// - Resource and error handling
// - Style
static int cb(struct dl_phdr_info *info, size_t size, void *data)
{
  if (strcmp(info->dlpi_name, "/lib64/libc.so.6") == 0) {
    int fd = open(info->dlpi_name, O_RDONLY);
    struct stat stat;
    fstat(fd, &stat);
    char *base = mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    Elf64_Ehdr *header = (Elf64_Ehdr *)base;
    Elf64_Shdr *secs = (Elf64_Shdr*)(base+header->e_shoff);
    for (unsigned secinx = 0; secinx < header->e_shnum; secinx++) {
      if (secs[secinx].sh_type == SHT_SYMTAB) {
        Elf64_Sym *symtab = (Elf64_Sym *)(base+secs[secinx].sh_offset);
        char *symnames = (char *)(base + secs[secs[secinx].sh_link].sh_offset);
        unsigned symcount = secs[secinx].sh_size/secs[secinx].sh_entsize;
        for (unsigned syminx = 0; syminx < symcount; syminx++) {
          if (strcmp(symnames+symtab[syminx].st_name, "main_arena") == 0) {
            void *mainarena = ((char *)info->dlpi_addr)+symtab[syminx].st_value;
            printf("main_arena found: %p\n", mainarena);
            raise(SIGTRAP);
            return 0;
          }
        }
      }
    }
  }
  return 0;
}

int main()
{
  dl_iterate_phdr(cb, NULL);
  return 0;
}

dl_iterate_phdr 用于获取映射的 glibc 的基地址。该映射不包含所需的符号表 (.symtab),因此必须再次映射该库。最终地址由基地址加符号值决定。

(gdb) run
Starting program: a.out 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7ffff77f0700 (LWP 24834)]
main_arena found: 0x7ffff7baec60

Thread 1 "a.out" received signal SIGTRAP, Trace/breakpoint trap.
raise (sig=5) at ../sysdeps/unix/sysv/linux/raise.c:50
50    return ret;
(gdb) select 1
(gdb) print mainarena
$1 = (void *) 0x7ffff7baec60 <main_arena>
(gdb) print &main_arena
$3 = (struct malloc_state *) 0x7ffff7baec60 <main_arena>

该值与main_arena的值匹配,因此找到了正确的地址。

还有其他ways 可以在不依赖库本身的情况下访问main_arena。例如,遍历现有堆允许发现 main_arena,但该策略远不那么简单。

当然,一旦您拥有main_arena,您需要所有内部类型定义才能检查数据。

【讨论】:

  • 这非常有用——我会试试这个。谢谢!
  • @VivinPaliath 很高兴它有帮助!如果代码适合您并回答了您的问题,请考虑接受该答案作为最佳答案。
  • 这个周末让我试试,如果成功我绝对接受。
【解决方案2】:

我正在编写一些代码来帮助我进行堆分析。

什么样的堆分析?

我想基本上获得有关所有不同空闲列表的信息。因此,对于每种 bin 类型中的每个 bin,我想要 bin 中的块数及其大小。对于 fast、small 和 tcache bin,我知道我只需要索引来确定大小。

此信息在您计划更改malloc 实现时才有意义。如果您的目标是分析或改善 应用程序 的堆使用情况,尝试收集它确实有意义,所以听起来你有一个XY problem

此外,bin 和 tcache 之类的内容仅在特定 malloc 实现的上下文中才有意义(TCMalloc 和 jemalloc 没有任何 bin)。

为了分析应用程序堆的使用情况,您可能希望使用 TCmalloc,因为它为heap profiling 和自省提供了很多工具。

【讨论】:

  • 我并没有真正尝试提高应用程序的堆使用率。我只是在收集有关每次免费后免费列表的统计信息。我还针对特定的malloc 实现——所以glibc 中确实使用bins 和tcache 的实现。如果这有意义吗?感谢您将我指向 TCMalloc。我会检查一下。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多