Introduction
In case of debugging the issue of memory broken, maybe you want to dump main_arena which is the root of all managed heap memory to investigate the issue. However, main_arena is a local symbol, so you cannot use dlsym.
Instead of this, you can use dl_iterate_phdr and elf.h, it’s relatively straightforward to resolve main_arena based on the local symbol.
Environment
CPU Architecture | x86_64 |
OS | Ubuntu 20.04 LTE |
Linux Kernel | 5.4.0-122 |
gcc | 9.4.0 |
gdb | 9.2 |
glibc | 2.31 |
Sample program
This sample is for x86_64 architecture.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
#define _GNU_SOURCE #include <link.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <stdint.h> #include <dlfcn.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #define DEBUG_SYMBOL_PATH_PREFIX "/usr/lib/debug/.build-id" #define DEBUG_SYMBOL_EXTENTION ".debug" #define TARGET_LIBRARY_PATH "/lib/x86_64-linux-gnu/libc.so.6" #define TARGET_SYMBOL_NAME "main_arena" static void *my_main_arena = NULL; static size_t map_file(const char *path, char **buf) { int fd = -1, ret; struct stat stat; *buf = MAP_FAILED; if ((fd = open(path, O_RDONLY)) < 0) return 0; ret = fstat(fd, &stat); if (ret) goto error; *buf = (char *)mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (*buf == MAP_FAILED) goto error; close(fd); return stat.st_size; error: if (fd != -1) close(fd); return 0; } static void unmap_file(void *buf, size_t size) { munmap(buf, size); } static void generate_symbol_file_path(char **path, const char *build_id, size_t size) { int i; char tmp[4]; char *p = *path; sprintf(p, "%s/%x/", DEBUG_SYMBOL_PATH_PREFIX, build_id[0]); build_id++; size--; for (i = 0; i < size; i++) { sprintf(tmp, "%02x", (unsigned char)build_id[i]); strcat(p, tmp); } strcat(p, DEBUG_SYMBOL_EXTENTION); printf("debug symbol file: path=%s\n", p); } static size_t find_symbol_offset(const char *symfile, const char *symname) { char *base = MAP_FAILED; size_t size; Elf64_Ehdr *header; Elf64_Shdr *secs; Elf64_Sym *symtab; char *names; int secidx, symidx; unsigned symcnt; size_t offset = (size_t)-1; size = map_file(symfile, &base); if (size == 0 || base == MAP_FAILED) goto error; header = (Elf64_Ehdr *)base; secs = (Elf64_Shdr *)(base + header->e_shoff); for (secidx = 0; secidx < header->e_shnum; secidx++) { if (secs[secidx].sh_type != SHT_SYMTAB) continue; if (secs[secidx].sh_entsize == 0) continue; symtab = (Elf64_Sym *)(base + secs[secidx].sh_offset); names = (char *)(base + secs[secs[secidx].sh_link].sh_offset); symcnt = secs[secidx].sh_size / secs[secidx].sh_entsize; for (symidx = 0; symidx < symcnt; symidx++) { if (strcmp(names + symtab[symidx].st_name, symname) == 0) { offset = symtab[symidx].st_value; goto final; } } } final: unmap_file(base, size); return offset; error: if (base != MAP_FAILED) unmap_file(base, size); return (size_t)-1; } static Elf64_Nhdr * find_build_id_note(const char *base) { Elf64_Ehdr *header = (Elf64_Ehdr *)base; Elf64_Shdr *secs = (Elf64_Shdr *)(base + header->e_shoff); Elf64_Nhdr *note = NULL; int secidx; char *name; for (secidx = 0; secidx < header->e_shnum; secidx++) { if (secs[secidx].sh_type != SHT_NOTE) continue; note = (Elf64_Nhdr *)(base + secs[secidx].sh_offset); if (note->n_type != NT_GNU_BUILD_ID) continue; name = (char *)(base + secs[secidx].sh_offset + sizeof(Elf64_Nhdr)); if (note->n_namesz == 4 && note->n_descsz != 0 && memcmp(name, "GNU", 4) == 0) { return note; } } return NULL; } int callback(struct dl_phdr_info *info, size_t size, void *data) { char *base = MAP_FAILED; size_t sz; Elf64_Nhdr *note; char *build_id; char *path = alloca(256); size_t offset; if (strcmp(info->dlpi_name, TARGET_LIBRARY_PATH) != 0) return 0; sz = map_file(info->dlpi_name, &base); if (sz == 0 || base == MAP_FAILED) return 0; note = find_build_id_note(base); if (!note) goto error; build_id = ((char *)note) + sizeof(Elf64_Nhdr) + note->n_namesz; generate_symbol_file_path(&path, build_id, note->n_descsz); offset = find_symbol_offset(path, TARGET_SYMBOL_NAME); if (offset == (size_t)-1) goto error; my_main_arena = (void *)(((char *)info->dlpi_addr) + offset); printf("%s found: %p\n", TARGET_SYMBOL_NAME, my_main_arena); unmap_file(base, sz); return 0; error: if (base != MAP_FAILED) unmap_file(base, sz); return 0; } int main(void) { dl_iterate_phdr(callback, NULL); return 0; } |
Explanation
dl_iterate_phdr is used to get one by one the base address of all libraries mapping to the caller process. The maped libc.so.6 does not contain the symbol table, so we need to find another ELF file containing debug symbol by using build_id defined in its .note.gnu.build_id section.

readelf libc.so.6
The debug symbol file is located at /usr/lib/debug/.build-id/<build_id[0:1]>/<build_id[2:]>.debug as you can see generate_symbol_file_path(), it might be cantaining the symbol table (.symtab). The final address of main_arena is determined by the base address plus the symbol value.
Run samples
You can compile with:
command
$ gcc -o test -g -O0 -Wall -Werror get_main_arena.c -lc -ldl
and confirm as:
command
$ gdb test
(gdb) b main
Breakpoint 1 at 0x1a7f: file get_main_arena.c, line 184.
(gdb) run
Starting program: /home/sanachan/test
Breakpoint 1, main () at get_main_arena.c:184
184 main(void) {
(gdb) n
185 dl_iterate_phdr(callback, NULL);
(gdb) n
debug symbol file: path=/usr/lib/debug/.build-id/18/78e6b475720c7c51969e69ab2d276fae6d1dee.debug
main_arena found: 0x7ffff7fb8b80
186 return 0;
(gdb) p &main_arena
$1 = (struct malloc_state *) 0x7ffff7fb8b80 <main_arena>
(gdb) p my_main_arena
$2 = (void *) 0x7ffff7fb8b80 <main_arena>
The value my_main_arena matches that of main_arena, so the correct address was found.