从进程内部获取对进程堆元数据的访问
Gaining access to heap metadata of a process from within itself
虽然我可以编写合理的 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(至少这是我从研究这种方法中了解到的)。
I am writing some code to help me do heap analysis.
什么样的堆分析?
I want want to basically get information about all the different freelists. So I want, for every bin in each bin type, the number of chunks in the bin and their sizes. For fast, small, and tcache bins I know that I just need the index to figure out the size.
如果您计划更改 malloc
实施,此信息 仅 有意义。如果您的目标是分析或改进 应用程序 的堆使用情况,那么尝试收集它 没有 是有意义的,所以听起来您有一个 XY problem.
此外,像 bin 和 tcache 这样的东西只有在特定 malloc
实现的上下文中才有意义(TCMalloc 和 jemalloc 不会有任何 bin)。
要分析应用程序堆使用情况,您可能需要使用 TCmalloc,因为它提供了 很多 工具用于 heap profiling 和自省。
我最近有类似的要求,所以我确实认为能够为给定进程达到 main_arena
确实有其价值,一个例子是 post-mortem 内存使用分析。
使用dl_iterate_phdr
和elf.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的基地址。映射不包含需要的符号table(.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
= (void *) 0x7ffff7baec60 <main_arena>
(gdb) print &main_arena
= (struct malloc_state *) 0x7ffff7baec60 <main_arena>
该值与 main_arena
的值匹配,因此找到了正确的地址。
还有其他 ways 可以在不依赖库本身的情况下到达 main_arena
。例如,遍历现有堆允许发现 main_arena
,但该策略远不那么简单。
当然,一旦您拥有 main_arena
,您需要所有内部类型定义才能检查数据。
虽然我可以编写合理的 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(至少这是我从研究这种方法中了解到的)。
I am writing some code to help me do heap analysis.
什么样的堆分析?
I want want to basically get information about all the different freelists. So I want, for every bin in each bin type, the number of chunks in the bin and their sizes. For fast, small, and tcache bins I know that I just need the index to figure out the size.
如果您计划更改 malloc
实施,此信息 仅 有意义。如果您的目标是分析或改进 应用程序 的堆使用情况,那么尝试收集它 没有 是有意义的,所以听起来您有一个 XY problem.
此外,像 bin 和 tcache 这样的东西只有在特定 malloc
实现的上下文中才有意义(TCMalloc 和 jemalloc 不会有任何 bin)。
要分析应用程序堆使用情况,您可能需要使用 TCmalloc,因为它提供了 很多 工具用于 heap profiling 和自省。
我最近有类似的要求,所以我确实认为能够为给定进程达到 main_arena
确实有其价值,一个例子是 post-mortem 内存使用分析。
使用dl_iterate_phdr
和elf.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的基地址。映射不包含需要的符号table(.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
= (void *) 0x7ffff7baec60 <main_arena>
(gdb) print &main_arena
= (struct malloc_state *) 0x7ffff7baec60 <main_arena>
该值与 main_arena
的值匹配,因此找到了正确的地址。
还有其他 ways 可以在不依赖库本身的情况下到达 main_arena
。例如,遍历现有堆允许发现 main_arena
,但该策略远不那么简单。
当然,一旦您拥有 main_arena
,您需要所有内部类型定义才能检查数据。