├── .gitignore ├── Makefile ├── README.md ├── allocmap.cc ├── heap.png └── mappings.png /.gitignore: -------------------------------------------------------------------------------- 1 | allocmap 2 | *.o 3 | *~ 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CXX = g++ 3 | CFLAGS = $(WARN) $(OPT) $(DEBUG) $(DEFS) 4 | CXXFLAGS = $(WARN) $(OPT) $(DEBUG) $(DEFS) 5 | WARN = -Wall 6 | OPT = -Og 7 | DEBUG = -g 8 | DEFS = -D_GNU_SOURCE 9 | 10 | SRCS = allocmap.cc 11 | OBJS = allocmap.o 12 | LIBS = -ldw -lelf 13 | 14 | .PHONY: all clean 15 | all: allocmap 16 | 17 | allocmap: $(OBJS) 18 | $(CXX) -o $@ $^ $(LIBS) 19 | 20 | clean: 21 | -rm -f allocmap $(OBJS) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Display Allocation Map 2 | 3 | Getting insight into the memory allocation of a process can be useful for many reasons. 4 | There is only so much information directly available in a human-consumable form. Mostly, 5 | the Linux kernel provides a the `/proc/`*PID*`/maps` and the `smaps` file to give insight 6 | into the address space of a process. 7 | 8 | Additional, more detailed information is available from `/proc/`*PID*`/pagemap`. This pseudo-file's 9 | content is in binary form and requires decoding. It allows to discover details such as whether a 10 | given memory page is resident or not and if yes, whether it is anonymous memory or backed by a file. 11 | Additional information even allows to identify the physical memory address which then can be used to 12 | discover additional information. 13 | 14 | Beyond this information the kernel can provide the internal memory handling of a process is interesting. 15 | In particular, how the process administrates dynamically allocated memory. The heap memory allocated 16 | through the `malloc` interfaces playes a crucial part in the performance of a program. 17 | 18 | ## `pagealloc` 19 | 20 | The `pagealloc` program can display this information in a detailed way. Per memory page information 21 | is provided for each memory region with allocated memory. Different colors are used to indicate the 22 | different states of the pages. 23 | 24 | In addition, the program provides detailed information about the heaps. The allocation of memory thtough 25 | the `malloc` functions happens in multiple of a size which is usually twice the size of a pointer. The 26 | output of `pagealloc` shows for each such memory block in the heap whether it is currently used or not. 27 | 28 | ## Representation 29 | 30 | Since I am much more familiar with writing console code than graphical applications the output of 31 | `pagealloc` is based on text. A Unicode-enabled console is sufficient. To enable showing the information 32 | with sufficient density each row of characters shows two times 80 values. Through appropriate use of 33 | the Unicode characters two "pixels" can be represented in one character. 34 | 35 | The memory map information as color-coded with the legend printed at the top as it can be seen in the following 36 | screenshot. Each "pixel" represents one page, normally 4096 bytes. Each row represents 80 times 4096 bytes, 37 | each line of characters 160 times 4096 bytes, 655360 bytes = 0xa0000. 38 | 39 | ![Mappings](mappings.png) 40 | 41 | The heap information is presented with a much higher resolution. For a x86-64 machine each pixel represents 42 | 16 bytes. One row of characters therefore would show information only for 2560 bytes. To increase the density 43 | each pixel can have one of nine possible values corresponding to how many of eight consecutive memory chunks 44 | is in use. The resulting gray-scale picture gives a good indication of memory holes. 45 | 46 | ![Heap](heap.png) 47 | 48 | # Implementation 49 | 50 | The implementation only has the basic implementation details of the malloc implementation hardcoded, namely, 51 | how to locate the memory regions (arenas) and in it the blocks which are free. The details about the 52 | data types involved are read from the debug information of the program. On Fedora systems it is trivial 53 | to use `debuginfo-install` to make the necessary files for the C library available. 54 | 55 | A modern C++ compiler is needed to compile the code. The only tested platform in the moment is Fedora 26 56 | on a x86-64 machine. This means gcc 7.2.1. 57 | -------------------------------------------------------------------------------- /allocmap.cc: -------------------------------------------------------------------------------- 1 | // Written by Ulrich Drepper . 2 | // Copyright © 2017 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace std::string_literals; 21 | 22 | 23 | namespace { 24 | const char* colors[40] = { "9", "8;5;1", "8;5;2", "8;5;3", "8;5;4", "8;5;5", "8;5;6", "8;5;7", 25 | "8;5;8", "8;5;9", "8;5;10", "8;5;11", "8;5;12", "8;5;13", "8;5;14", "8;5;15", 26 | "8;5;232", "8;5;233", "8;5;234", "8;5;235", "8;5;236", "8;5;237", "8;5;238", "8;5;239", 27 | "8;5;240", "8;5;241", "8;5;242", "8;5;243", "8;5;244", "8;5;245", "8;5;246", "8;5;247", 28 | "8;5;248", "8;5;249", "8;5;250", "8;5;251", "8;5;252", "8;5;253", "8;5;254", "8;5;255" 29 | }; 30 | 31 | 32 | char* debuginfo_path; 33 | const Dwfl_Callbacks dwfl_callbacks = { 34 | /* .find_elf = */ dwfl_linux_proc_find_elf, 35 | /* .find_debuginfo = */ dwfl_standard_find_debuginfo, 36 | /* .section_address = */ dwfl_offline_section_address, 37 | /* .debuginfo_path = */ &debuginfo_path, 38 | }; 39 | 40 | 41 | template 42 | std::string textgrid_line(Iterator row1, Iterator row2, unsigned ncols, unsigned ncols1, unsigned ncols2) 43 | { 44 | std::string res; 45 | unsigned old_fgidx = 0; 46 | unsigned old_bgidx = 0; 47 | 48 | while (ncols1 > 0) { 49 | auto v1 = ncols1 > 0 ? (ncols1--, int(*row1++)) : 0; 50 | auto v2 = ncols2 > 0 ? (ncols2--, int(*row2++)) : 0; 51 | 52 | unsigned fgidx = v1 == 0 && v2 != 0 ? v2 : v1; 53 | unsigned bgidx = v1 != 0 && v2 != 0 && v1 != v2 ? v2 : 0; 54 | 55 | if (fgidx != old_fgidx || bgidx != old_bgidx) { 56 | res += "\e["; 57 | if (fgidx != old_fgidx) { 58 | res += "3"s + colors[fgidx]; 59 | old_fgidx = fgidx; 60 | if (bgidx != old_bgidx) 61 | res += ';'; 62 | } 63 | if (bgidx != old_bgidx) { 64 | res += "4"s + colors[bgidx]; 65 | old_bgidx = bgidx; 66 | } 67 | res += 'm'; 68 | } 69 | 70 | res += ((const char*[]) { " ", "▀", "▄", "▀", " ", "▀", "▄", "█" })[(v1!=0)+2*(v2!=0)+4*(v1==v2)]; 71 | 72 | --ncols; 73 | } 74 | 75 | if (old_fgidx != 0) 76 | res += "\e[0m"; 77 | while (ncols-- > 0) 78 | res += ' '; 79 | 80 | return res; 81 | } 82 | 83 | template 84 | std::string textgrid_line(Iterator row1, Iterator row2, unsigned ncols) 85 | { 86 | return textgrid_line(row1, row2, ncols, ncols, ncols); 87 | } 88 | } // anonymous namespace 89 | 90 | 91 | template 92 | std::vector textgrid(Iterator first, Iterator last, unsigned ncols) 93 | { 94 | static_assert(std::is_same::iterator_category>::value, 96 | "expect random iterator or raw pointer"); 97 | 98 | auto nelems = std::distance(first, last); 99 | 100 | std::vector res((nelems + 2 * ncols - 1) / (2 * ncols)); 101 | auto wp = res.begin(); 102 | 103 | while (first + 2 * ncols <= last) { 104 | *wp++ = std::move(textgrid_line(first, first + ncols, ncols)); 105 | first += 2 * ncols; 106 | } 107 | 108 | if (first < last) 109 | *wp++ = std::move(textgrid_line(first, first + ncols, ncols, 110 | std::min(ncols, unsigned(std::distance(first, last))), 111 | std::max(0, int(std::distance(first, last) - ncols)))); 112 | 113 | return res; 114 | } 115 | 116 | 117 | namespace { 118 | struct range { 119 | range(uint64_t start_, uint64_t end_, char r_, char w_, char x_, char p_, std::string&& name_) 120 | : start(start_), end(end_), r(r_), w(w_), x(x_), p(p_), name(std::move(name_)) { } 121 | 122 | uint64_t start; 123 | uint64_t end; 124 | char r; 125 | char w; 126 | char x; 127 | char p; 128 | bool any_present = false; 129 | std::string name; 130 | }; 131 | 132 | std::list getmaps(pid_t p) 133 | { 134 | auto fname = "/proc/"s + std::to_string(p) + "/maps"; 135 | std::ifstream ifs(fname); 136 | if (! ifs.good()) 137 | error(EXIT_FAILURE, 0, "cannot open %s", fname.c_str()); 138 | 139 | std::list res; 140 | while (! (ifs.peek(), ifs.eof())) { 141 | uint64_t start, end, tmp; 142 | char ch, r, w, x, p; 143 | std::string fname; 144 | ifs >> std::hex >> start; 145 | ifs >> ch; 146 | assert(ch == '-'); 147 | ifs >> std::hex >> end; 148 | ifs >> r >> w >> x >> p; 149 | ifs >> std::hex >> tmp; 150 | ifs >> std::hex >> tmp; 151 | ifs >> ch; 152 | assert(ch == ':'); 153 | ifs >> std::hex >> tmp; 154 | ifs >> std::dec >> tmp; 155 | while (ifs.peek() == ' ') 156 | ifs.get(); 157 | std::getline(ifs, fname); 158 | 159 | res.emplace_back(start, end, r, w, x, p, std::move(fname)); 160 | } 161 | 162 | return res; 163 | } 164 | 165 | 166 | struct addraccess { 167 | addraccess(int s) : size(s) {} 168 | uint64_t operator()(const uint8_t* p, size_t off) const { 169 | return size == 8 ? *(const uint64_t*)(p + off) : *(const uint32_t*)(p + off); 170 | } 171 | uint64_t operator()(const uint8_t* p, size_t off, size_t idx) const { 172 | return size == 8 ? ((const uint64_t*)(p + off))[idx] : ((const uint32_t*)(p + off))[idx]; 173 | } 174 | private: 175 | const int size; 176 | }; 177 | 178 | 179 | const char* get_name_attr(Dwarf_Die* die) 180 | { 181 | const char* str = nullptr; 182 | if (dwarf_getattrs(die, [](Dwarf_Attribute* attrp, void* arg) -> int { 183 | unsigned attr = dwarf_whatattr(attrp); 184 | if (attr != DW_AT_name) 185 | return DWARF_CB_OK; 186 | unsigned form = dwarf_whatform(attrp); 187 | assert(form == DW_FORM_strp || form == DW_FORM_string); 188 | *(const char**) arg = dwarf_formstring(attrp); 189 | return DWARF_CB_ABORT; 190 | }, &str, 0) == 1) 191 | return nullptr; 192 | return str; 193 | } 194 | 195 | Dwarf_Die* get_type_attr(Dwarf_Die* die, Dwarf_Die* res_mem) 196 | { 197 | if (dwarf_getattrs(die, [](Dwarf_Attribute* attrp, void* arg) -> int { 198 | unsigned attr = dwarf_whatattr(attrp); 199 | if (attr != DW_AT_type) 200 | return DWARF_CB_OK; 201 | unsigned form = dwarf_whatform(attrp); 202 | assert(form == DW_FORM_ref4); 203 | if (dwarf_formref_die(attrp, (Dwarf_Die*) arg) == nullptr) 204 | return DWARF_CB_OK; 205 | return DWARF_CB_ABORT; 206 | }, res_mem, 0) == 1) 207 | return nullptr; 208 | return res_mem; 209 | } 210 | 211 | size_t get_byte_size_attr(Dwarf_Die* die) 212 | { 213 | Dwarf_Word num; 214 | if (dwarf_getattrs(die, [](Dwarf_Attribute* attrp, void* arg) -> int { 215 | unsigned attr = dwarf_whatattr(attrp); 216 | if (attr != DW_AT_byte_size) 217 | return DWARF_CB_OK; 218 | unsigned form = dwarf_whatform(attrp); 219 | assert(form == DW_FORM_data1 || form == DW_FORM_data2); 220 | if (dwarf_formudata(attrp, (Dwarf_Word*) arg) != 0) 221 | return DWARF_CB_OK; 222 | return DWARF_CB_ABORT; 223 | }, &num, 0) == 1) 224 | return ~Dwarf_Word(0); 225 | return num; 226 | } 227 | 228 | size_t get_data_member_location_attr(Dwarf_Die* die) 229 | { 230 | Dwarf_Word num; 231 | if (dwarf_getattrs(die, [](Dwarf_Attribute* attrp, void* arg) -> int { 232 | unsigned attr = dwarf_whatattr(attrp); 233 | if (attr != DW_AT_data_member_location) 234 | return DWARF_CB_OK; 235 | unsigned form = dwarf_whatform(attrp); 236 | assert(form == DW_FORM_data1 || form == DW_FORM_data2); 237 | if (dwarf_formudata(attrp, (Dwarf_Word*) arg) != 0) 238 | return DWARF_CB_OK; 239 | return DWARF_CB_ABORT; 240 | }, &num, 0) == 1) 241 | return ~Dwarf_Word(0); 242 | return num; 243 | } 244 | 245 | size_t get_upper_bound_attr(Dwarf_Die* die) 246 | { 247 | Dwarf_Word num; 248 | if (dwarf_getattrs(die, [](Dwarf_Attribute* attrp, void* arg) -> int { 249 | unsigned attr = dwarf_whatattr(attrp); 250 | if (attr != DW_AT_upper_bound) 251 | return DWARF_CB_OK; 252 | unsigned form = dwarf_whatform(attrp); 253 | assert(form == DW_FORM_data1); 254 | if (dwarf_formudata(attrp, (Dwarf_Word*) arg) != 0) 255 | return DWARF_CB_OK; 256 | return DWARF_CB_ABORT; 257 | }, &num, 0) == 1) 258 | return ~Dwarf_Word(0); 259 | return num; 260 | } 261 | } // anonymous namespace 262 | 263 | 264 | int main(int argc, char* argv[]) 265 | { 266 | int opt; 267 | while ((opt = getopt(argc, argv, "h")) != -1) 268 | switch (opt) { 269 | case 'h': 270 | std::cout << argv[0] << " [OPTION]... PID\n"; 271 | return 0; 272 | default: 273 | return 1; 274 | } 275 | 276 | if (optind != argc - 1) { 277 | std::cout << argv[0] << " [OPTION]... PID\n"; 278 | return 1; 279 | } 280 | 281 | pid_t p = atoi(argv[optind]); 282 | 283 | int fdpm = open(("/proc/"s + std::to_string(p) + "/pagemap").c_str(), O_RDONLY); 284 | if (fdpm == -1) 285 | error(EXIT_FAILURE, errno, "cannot open pagemap"); 286 | 287 | int fdpf = open("/proc/kpageflags", O_RDONLY); 288 | 289 | Dwfl* dwfl = nullptr; 290 | dwfl = dwfl_begin(&dwfl_callbacks); 291 | 292 | dwfl_linux_proc_report(dwfl, p); 293 | dwfl_linux_proc_attach(dwfl, p, false); 294 | 295 | // find data type 'struct malloc_state' 296 | // find variable 'main_arena' 297 | struct main_arena_info_s { 298 | Dwfl_Module* mod = nullptr; 299 | Elf* elf = nullptr; 300 | bool have_addr = false; 301 | GElf_Addr addr; 302 | GElf_Addr bias = 0; 303 | uint8_t addrsize = 0; 304 | uint8_t long_double_size = 0; 305 | } main_arena_info; 306 | bool have_debuginfo = false; 307 | Dwarf_Die codie; 308 | ptrdiff_t pd = dwfl_getmodules(dwfl, [](Dwfl_Module *mod, void **, const char *name, Dwarf_Addr, void *arg) -> int { 309 | auto info = (main_arena_info_s*) arg; 310 | int res = DWARF_CB_OK; 311 | GElf_Addr bias; 312 | auto elf = dwfl_module_getelf(mod, &bias); 313 | if (elf != nullptr) { 314 | GElf_Ehdr ehdr_mem; 315 | GElf_Ehdr* ehdr = gelf_getehdr(elf, &ehdr_mem); 316 | for (unsigned i = 0; i < ehdr->e_phnum; ++i) { 317 | GElf_Phdr phdr_mem; 318 | GElf_Phdr* phdr = gelf_getphdr(elf, i, &phdr_mem); 319 | if (phdr->p_type == PT_DYNAMIC) { 320 | auto scn = gelf_offscn(elf, phdr->p_offset); 321 | if (scn != nullptr) { 322 | GElf_Shdr shdr_mem; 323 | auto shdr = gelf_getshdr(scn, &shdr_mem); 324 | auto data = elf_getdata(scn, nullptr); 325 | if (shdr != nullptr && data != nullptr) { 326 | for (size_t j = 0; j < shdr->sh_size / shdr->sh_entsize; ++j) { 327 | GElf_Dyn dyn_mem; 328 | auto dyn = gelf_getdyn(data, j, &dyn_mem); 329 | if (dyn->d_tag == DT_SONAME) { 330 | auto soname = elf_strptr (elf, shdr->sh_link, dyn->d_un.d_val); 331 | if (strcmp(soname, "libc.so.6") == 0) { 332 | int symidx = 0; 333 | const char* symname; 334 | GElf_Word shndx; 335 | GElf_Sym sym; 336 | while ((symname = dwfl_module_getsym(mod, symidx, &sym, &shndx)) != nullptr) { 337 | if (strcmp(symname, "main_arena") == 0) { 338 | info->have_addr = true; 339 | info->addr = sym.st_value; 340 | break; 341 | } 342 | ++symidx; 343 | } 344 | info->mod = mod; 345 | info->elf = elf; 346 | info->bias = bias; 347 | res = DWARF_CB_ABORT; 348 | } 349 | break; 350 | } 351 | } 352 | } 353 | } 354 | break; 355 | } 356 | } 357 | } 358 | 359 | return res; }, 360 | &main_arena_info, 0); 361 | if (pd != 0 && main_arena_info.mod != nullptr) { 362 | Dwarf_Addr dwarf_bias; 363 | auto dwarf = dwfl_module_getdwarf(main_arena_info.mod, &dwarf_bias); 364 | assert(dwarf != nullptr); 365 | 366 | size_t cuhl; 367 | Dwarf_Half version; 368 | Dwarf_Off abbroffset; 369 | uint8_t offsize; 370 | Dwarf_Off nextcu; 371 | Dwarf_Off offset = 0; 372 | while (1) { 373 | if (dwarf_next_unit(dwarf, offset, &nextcu, &cuhl, &version, 374 | &abbroffset, &main_arena_info.addrsize, &offsize, nullptr, nullptr) != 0) 375 | break; 376 | 377 | offset += cuhl; 378 | 379 | Dwarf_Die cudie; 380 | dwarf_offdie(dwarf, offset, &cudie); 381 | int tag = dwarf_tag(&cudie); 382 | if (tag == DW_TAG_compile_unit) { 383 | const char* cuname = nullptr; 384 | if ((cuname = get_name_attr(&cudie)) != nullptr && strcmp(cuname, "malloc.c") == 0) { 385 | // found malloc.c 386 | if (dwarf_child(&cudie, &codie) == 0) 387 | do { 388 | const char* varname; 389 | if (dwarf_tag(&codie) == DW_TAG_base_type 390 | && (varname = get_name_attr(&codie)) != nullptr 391 | && strcmp(varname, "long double") == 0) { 392 | main_arena_info.long_double_size = get_byte_size_attr(&codie); 393 | } else if (dwarf_tag(&codie) == DW_TAG_variable 394 | && (varname = get_name_attr(&codie)) != nullptr 395 | && strcmp(varname, "main_arena") == 0) { 396 | have_debuginfo = true; 397 | 398 | if (! main_arena_info.have_addr) { 399 | // Locate the symbol with the debug info. 400 | dwarf_getattrs(&codie, [](Dwarf_Attribute* attrp, void* arg) -> int { 401 | auto info = (main_arena_info_s*) arg; 402 | unsigned attr = dwarf_whatattr(attrp); 403 | if (attr != DW_AT_location) 404 | return DWARF_CB_OK; 405 | unsigned form = dwarf_whatform(attrp); 406 | assert(form == DW_FORM_exprloc); 407 | Dwarf_Block block; 408 | if (dwarf_formblock(attrp, &block) != 0) 409 | return DWARF_CB_ABORT; 410 | if (block.length > 0) { 411 | assert(block.data[0] == DW_OP_addr); 412 | assert(block.length == 1u + info->addrsize); 413 | if (info->addrsize == 4) 414 | info->addr = *(uint32_t*)(block.data+1); 415 | else if (info->addrsize == 8) 416 | info->addr = *(uint64_t*)(block.data+1); 417 | else 418 | return DWARF_CB_ABORT; 419 | info->have_addr = true; 420 | return DWARF_CB_ABORT; 421 | } 422 | return DWARF_CB_ABORT; 423 | }, &main_arena_info, 0); 424 | } 425 | break; 426 | } 427 | } while (dwarf_siblingof(&codie, &codie) != 1); 428 | } 429 | } 430 | 431 | if (have_debuginfo) 432 | break; 433 | 434 | offset = nextcu; 435 | } 436 | } 437 | 438 | struct heap_info { 439 | uint64_t arena; 440 | uint64_t begin; 441 | uint64_t top_begin; 442 | uint64_t end; 443 | }; 444 | std::vector heaps; 445 | 446 | addraccess aa(main_arena_info.addrsize); 447 | 448 | size_t fastbins_offset = ~size_t(0); 449 | size_t fastbins_count = 0; 450 | size_t top_offset = ~size_t(0); 451 | // size_t last_remainder_offset = ~size_t(0); 452 | size_t bins_offset = ~size_t(0); 453 | size_t bins_count = ~size_t(0); 454 | size_t next_offset = ~size_t(0); 455 | // size_t next_free_offset = ~size_t(0); 456 | size_t system_mem_offset = ~size_t(0); 457 | // size_t pointer_size = 0; 458 | size_t malloc_chunk_size = ~size_t(0); 459 | // size_t mchunk_prev_size_offset = ~size_t(0); 460 | size_t mchunk_size_offset = ~size_t(0); 461 | size_t fd_offset = ~size_t(0); 462 | // size_t bk_offset = ~size_t(0); 463 | size_t fd_nextsize_offset = ~size_t(0); 464 | // size_t bk_nextsize_offset = ~size_t(0); 465 | size_t malloc_state_size = ~size_t(0); 466 | 467 | // Needed a couple of times. 468 | int memfd = open(("/proc/"s + std::to_string(p) + "/mem").c_str(), O_RDONLY); 469 | 470 | // Read the malloc state 471 | if (have_debuginfo) { 472 | Dwarf_Die malloc_state_die; 473 | if (get_type_attr(&codie, &malloc_state_die) != nullptr 474 | && dwarf_tag(&malloc_state_die) == DW_TAG_structure_type 475 | && strcmp(get_name_attr(&malloc_state_die), "malloc_state") == 0) { 476 | malloc_state_size = get_byte_size_attr(&malloc_state_die); 477 | Dwarf_Die member_die; 478 | if (dwarf_child(&malloc_state_die, &member_die) == 0) { 479 | do { 480 | auto name = get_name_attr(&member_die); 481 | auto memoff = get_data_member_location_attr(&member_die); 482 | if (strcmp(name, "fastbinsY") == 0) { 483 | fastbins_offset = memoff; 484 | Dwarf_Die fastbins_die; 485 | if (get_type_attr(&member_die, &fastbins_die) != nullptr 486 | && dwarf_tag(&fastbins_die) == DW_TAG_array_type) { 487 | Dwarf_Die array_die; 488 | if (dwarf_child(&fastbins_die, &array_die) == 0 489 | && dwarf_tag(&array_die) == DW_TAG_subrange_type) { 490 | fastbins_count = get_upper_bound_attr(&array_die) + 1; 491 | Dwarf_Die arrayel_die; 492 | if (get_type_attr(&fastbins_die, &arrayel_die) != nullptr) { 493 | if (dwarf_tag(&arrayel_die) == DW_TAG_typedef 494 | && get_type_attr(&arrayel_die, &arrayel_die) == nullptr) 495 | assert("mfastbinptr type fetch failed"); 496 | 497 | assert(dwarf_tag(&arrayel_die) == DW_TAG_pointer_type); 498 | // pointer_size = get_byte_size_attr(&arrayel_die); 499 | 500 | Dwarf_Die malloc_chunk_die; 501 | if (get_type_attr(&arrayel_die, &malloc_chunk_die) != nullptr 502 | && dwarf_tag(&malloc_chunk_die) == DW_TAG_structure_type) { 503 | malloc_chunk_size = get_byte_size_attr(&malloc_chunk_die); 504 | 505 | Dwarf_Die mchunkel_die; 506 | if (dwarf_child(&malloc_chunk_die, &mchunkel_die) == 0) { 507 | 508 | do { 509 | auto mcname = get_name_attr(&mchunkel_die); 510 | auto mcoff = get_data_member_location_attr(&mchunkel_die); 511 | if (strcmp(mcname, "mchunk_size") == 0) 512 | mchunk_size_offset = mcoff; 513 | // else if (strcmp(mcname, "mchunk_prev_size") == 0) 514 | // mchunk_prev_size_offset = mcoff; 515 | else if (strcmp(mcname, "fd") == 0) 516 | fd_offset = mcoff; 517 | // else if (strcmp(mcname, "bk") == 0) 518 | // bk_offset = mcoff; 519 | else if (strcmp(mcname, "fd_nextsize") == 0) 520 | fd_nextsize_offset = mcoff; 521 | // else if (strcmp(mcname, "bk_nextsize") == 0) 522 | // bk_nextsize_offset = mcoff; 523 | } while (dwarf_siblingof(&mchunkel_die, &mchunkel_die) != 1); 524 | } 525 | } 526 | } 527 | } 528 | } 529 | } else if (strcmp(name, "top") == 0) { 530 | top_offset = memoff; 531 | // } else if (strcmp(name, "last_remainder") == 0) { 532 | // last_remainder_offset = memoff; 533 | } else if (strcmp(name, "bins") == 0) { 534 | bins_offset = memoff; 535 | Dwarf_Die bins_die; 536 | if (get_type_attr(&member_die, &bins_die) != nullptr 537 | && dwarf_tag(&bins_die) == DW_TAG_array_type) { 538 | Dwarf_Die array_die; 539 | if (dwarf_child(&bins_die, &array_die) == 0 540 | && dwarf_tag(&array_die) == DW_TAG_subrange_type) { 541 | bins_count = get_upper_bound_attr(&array_die) + 1; 542 | } 543 | } 544 | } else if (strcmp(name, "next") == 0) { 545 | next_offset = memoff; 546 | // } else if (strcmp(name, "next_free") == 0) { 547 | // next_free_offset = memoff; 548 | } else if (strcmp(name, "system_mem") == 0) { 549 | system_mem_offset = memoff; 550 | } 551 | } while (dwarf_siblingof(&member_die, &member_die) != 1); 552 | 553 | if (memfd != -1) { 554 | auto arena_addr = main_arena_info.addr; 555 | 556 | next: 557 | auto arena_mem = new uint8_t[malloc_state_size]; 558 | if (size_t(pread(memfd, arena_mem, malloc_state_size, main_arena_info.addr)) == malloc_state_size) { 559 | assert(top_offset + main_arena_info.addrsize <= malloc_state_size); 560 | uint64_t top; 561 | uint64_t system_mem; 562 | top = aa(arena_mem, top_offset); 563 | system_mem = aa(arena_mem, system_mem_offset); 564 | 565 | uint8_t top_chunk[fd_nextsize_offset]; 566 | if (size_t(pread(memfd, top_chunk, fd_nextsize_offset, top)) == fd_nextsize_offset) { 567 | uint64_t top_size = aa(top_chunk, mchunk_size_offset) & ~(uint64_t)7; 568 | 569 | heaps.emplace_back(heap_info{main_arena_info.addr, top + top_size - system_mem, top, top + top_size}); 570 | } 571 | 572 | arena_addr = aa(arena_mem, next_offset); 573 | if (arena_addr != main_arena_info.addr) 574 | goto next; 575 | } 576 | delete[] arena_mem; 577 | } 578 | } 579 | } 580 | } 581 | 582 | enum class pmstate : uint8_t { 583 | unknown = 0, 584 | swapped, 585 | anon4k, 586 | file4k, 587 | topchunk, 588 | topchunk_notpresent, 589 | anon2M, 590 | anon1G = 8, 591 | file2M = 12, 592 | file1G = 13, 593 | 594 | notpresent = 20 595 | }; 596 | 597 | std::cout << " \e[3" << colors[uint8_t(pmstate::unknown)] << "m████\e[0m unknown\n"; 598 | std::cout << " \e[3" << colors[uint8_t(pmstate::swapped)] << "m████\e[0m swapped\n"; 599 | std::cout << " \e[3" << colors[uint8_t(pmstate::anon4k)] << "m████\e[0m 4k anonymous "; 600 | std::cout << " \e[3" << colors[uint8_t(pmstate::anon2M)] << "m████\e[0m 2M anonymous "; 601 | std::cout << " \e[3" << colors[uint8_t(pmstate::anon1G)] << "m████\e[0m 1G anonymous\n"; 602 | std::cout << " \e[3" << colors[uint8_t(pmstate::file4k)] << "m████\e[0m 4k file backed"; 603 | std::cout << " \e[3" << colors[uint8_t(pmstate::file2M)] << "m████\e[0m 2M file backed"; 604 | std::cout << " \e[3" << colors[uint8_t(pmstate::file1G)] << "m████\e[0m 1G file backed\n"; 605 | std::cout << " \e[3" << colors[uint8_t(pmstate::topchunk)] << "m████\e[0m arena top chunk\n"; 606 | std::cout << " \e[3" << colors[uint8_t(pmstate::topchunk_notpresent)] << "m████\e[0m arena top chunk not present\n"; 607 | std::cout << " \e[3" << colors[uint8_t(pmstate::notpresent)] << "m████\e[0m not present\n"; 608 | 609 | auto ps = sysconf(_SC_PAGESIZE); 610 | auto maps = getmaps(p); 611 | for (auto& m : maps) { 612 | const auto npages = (m.end - m.start) / ps; 613 | std::vector pm(npages); 614 | std::vector v(npages); 615 | auto n = size_t(pread(fdpm, pm.data(), npages * sizeof(uint64_t), m.start / ps * sizeof(uint64_t))); 616 | if (n != npages * sizeof(uint64_t)) { 617 | if (n != 0 || m.name != "[vsyscall]"s) 618 | error(EXIT_FAILURE, errno, "incomplete pagemap read"); 619 | std::fill(v.begin(), v.end(), pmstate::unknown); 620 | } else { 621 | constexpr auto pm_present = uint64_t(1) << 63; 622 | constexpr auto pm_swapped = uint64_t(1) << 62; 623 | constexpr auto pm_shared = uint64_t(1) << 61; 624 | 625 | std::transform(pm.begin(), pm.end(), v.begin(), 626 | [&m](uint64_t pm) { 627 | if (pm & pm_swapped) return pmstate::swapped; 628 | if ((pm & pm_present) == 0) return pmstate::notpresent; 629 | m.any_present = true; 630 | if (pm & pm_shared) { 631 | return pmstate::file4k; 632 | } else { 633 | return pmstate::anon4k; 634 | } 635 | }); 636 | 637 | if (! m.any_present && m.r == '-') 638 | continue; 639 | 640 | if (fdpf != -1) { 641 | std::vector pf(npages); 642 | std::fill(pf.begin(), pf.end(), 0); 643 | 644 | bool has_pfn = false; 645 | size_t first_range = 0; 646 | size_t first_pfn = 0; 647 | for (size_t i = 0; i < npages; ++i) { 648 | if (has_pfn && (pm[i] & pm_present) && (pm[i] & ((uint64_t(1) << 55) - 1)) == first_pfn + (i - first_range)) 649 | continue; 650 | 651 | if (has_pfn) { 652 | n = size_t(pread(fdpf, pf.data() + first_range, (i - first_range) * sizeof(uint64_t), first_pfn * sizeof(uint64_t))); 653 | if (n < (i - first_range) * sizeof(uint64_t)) 654 | error(EXIT_FAILURE, errno, "incomplete pageflags read"); 655 | } 656 | if (pm[i] & pm_present) { 657 | first_range = i; 658 | first_pfn = pm[i] & ((uint64_t(1) << 55) -1); 659 | has_pfn = true; 660 | } else 661 | has_pfn = false; 662 | } 663 | if (has_pfn) { 664 | n = size_t(pread(fdpf, pf.data() + first_range, (npages - first_range) * sizeof(uint64_t), first_pfn * sizeof(uint64_t))); 665 | if (n < (npages - first_range) * sizeof(uint64_t)) 666 | error(EXIT_FAILURE, errno, "incomplete pageflags read"); 667 | } 668 | 669 | constexpr uint64_t pf_compound_head = uint64_t(1) << 15; 670 | constexpr uint64_t pf_compound_tail = uint64_t(1) << 16; 671 | ssize_t last_head = -1; 672 | for (ssize_t i = 0; i < ssize_t(npages); ++i) 673 | if (pf[i] & pf_compound_head) { 674 | if (last_head != -1) 675 | error(EXIT_FAILURE, 0, "compound head without tail"); 676 | if (pf[i] & pf_compound_tail) 677 | error(EXIT_FAILURE, 0, "compound head together with tail"); 678 | last_head = i; 679 | } else if (pf[i] & pf_compound_tail) { 680 | if (last_head == -1) 681 | error(EXIT_FAILURE, 0, "compound tail without head"); 682 | auto nlarge = i + 1 - last_head; 683 | bool is_2M = false; 684 | if (nlarge*ps == 2*1024*1024) 685 | is_2M = true; 686 | else if (nlarge*ps != 1*1024*1024*1024) 687 | error(EXIT_FAILURE, 0, "strange page size %zu", nlarge*ps); 688 | 689 | for (auto j = last_head; j <= i; ++j) 690 | if (v[j] == pmstate::file4k) { 691 | v[j] = is_2M ? pmstate::file2M : pmstate::file1G; 692 | } else if (v[j] == pmstate::anon4k) { 693 | v[j] = is_2M ? pmstate::anon2M : pmstate::anon1G; 694 | } else 695 | error(EXIT_FAILURE, 0, "huge page for mapping %d", int(v[j])); 696 | } 697 | 698 | close(fdpf); 699 | } 700 | } 701 | 702 | for (const auto& h : heaps) 703 | if (h.top_begin >= m.start && h.top_begin < m.end) { 704 | uint64_t round_from = (h.top_begin + ps - 1) & ~(ps - 1); 705 | assert(h.end % ps == 0); 706 | 707 | size_t fromidx = (round_from - m.start) / ps; 708 | assert(fromidx <= npages); 709 | size_t toidx = (h.end - m.start) / ps; 710 | assert(toidx <= npages); 711 | for (size_t j = fromidx; j < toidx; ++j) 712 | if (v[j] == pmstate::notpresent) 713 | v[j] = pmstate::topchunk_notpresent; 714 | else 715 | v[j] = pmstate::topchunk; 716 | break; 717 | } 718 | 719 | std::cout << (m.name.length() == 0 ? ""s : m.name) << " [" << m.r << m.w << m.x << m.p << "]\n"; 720 | 721 | const unsigned npagecols = 80; 722 | auto addr = m.start; 723 | auto p = textgrid(std::begin(v), std::end(v), npagecols); 724 | for (auto& s : p) { 725 | std::cout << " " << std::setw(16) << std::setfill('.') << std::hex << addr << " " << s << std::endl; 726 | addr += npagecols * 2 * ps; 727 | } 728 | } 729 | 730 | if (heaps.size() > 0) { 731 | std::cout << std::endl; 732 | 733 | std::cout << " \e[3" << colors[uint8_t(pmstate::notpresent)] << "m████\e[0m 0 of 8 chunks used\n"; 734 | for (size_t ii = 1; ii <= 8; ++ii) 735 | std::cout << " \e[3" << colors[24+ii] << "m████\e[0m " << ii << " of 8 chunks used\n"; 736 | 737 | auto arena_p = new uint8_t[malloc_state_size]; 738 | 739 | for (const auto&h : heaps) { 740 | size_t malloc_alignment = 2 * main_arena_info.addrsize; 741 | if (main_arena_info.long_double_size > malloc_alignment) 742 | malloc_alignment = main_arena_info.long_double_size; 743 | size_t malloc_align_mask = malloc_alignment - 1; 744 | size_t minsize = (fd_nextsize_offset + malloc_align_mask) & ~malloc_align_mask; 745 | size_t nchunks = (h.end - h.begin) / malloc_alignment; 746 | size_t nfillcnt = (nchunks + 7) / 8; 747 | size_t nchunkcols = 80; 748 | std::vector filled(nfillcnt); 749 | 750 | // There is not list of used memory, only the freed data is 751 | // accessible. Therefore the default value is that all blocks 752 | // are used and substract one for every free chunk. 753 | std::fill(filled.begin(), filled.end(), 8); 754 | 755 | auto clear = [&filled,begin=h.begin,malloc_alignment](uint64_t addr){ 756 | auto idx = (addr - begin) / malloc_alignment / 8; 757 | --filled[idx]; 758 | // std::cout << "clear " << idx << std::endl; 759 | }; 760 | 761 | assert((h.top_begin + fd_offset) % malloc_alignment == 0); 762 | for (auto a = h.top_begin + fd_offset; a < h.end; a += malloc_alignment) 763 | clear(a); 764 | 765 | std::cout << "arena @0x" << std::hex << h.arena << std::endl; 766 | if (size_t(pread(memfd, arena_p, malloc_state_size, h.arena)) == malloc_state_size) { 767 | for (size_t i = 0; i < fastbins_count; ++i) { 768 | uint64_t binptr; 769 | binptr = aa(arena_p, fastbins_offset, i); 770 | while (binptr != 0) { 771 | if (binptr < h.begin || binptr >= h.end) { 772 | std::cout << "invalid binptr in binfastsY[" << i << "] = " << binptr << std::endl; 773 | break; 774 | } 775 | 776 | uint8_t chunk[malloc_chunk_size]; 777 | if (size_t(pread(memfd, chunk, malloc_chunk_size, binptr)) != malloc_chunk_size) 778 | break; 779 | 780 | uint64_t size = aa(chunk, mchunk_size_offset) & ~uint64_t(7); 781 | assert(size >= minsize); 782 | assert((size & malloc_align_mask) == 0); 783 | uint64_t addr = binptr; 784 | assert((addr & malloc_align_mask) == 0); 785 | for (; addr < binptr + size; addr += malloc_alignment) 786 | clear(addr); 787 | 788 | binptr = aa(chunk, fd_offset); 789 | } 790 | } 791 | 792 | for (size_t i = 0; i < bins_count; i += 2) { 793 | uint64_t binaddr = h.arena + bins_offset + i * main_arena_info.addrsize; 794 | uint64_t binptr = aa(arena_p, bins_offset, i); 795 | while (binptr + fd_offset != uint64_t(binaddr)) { 796 | if (binptr < h.begin || binptr >= h.end) { 797 | std::cout << "invalid binptr in bins[" << i << "] = " << binptr << std::endl; 798 | break; 799 | } 800 | 801 | uint8_t chunk[malloc_chunk_size]; 802 | if (size_t(pread(memfd, chunk, malloc_chunk_size, binptr)) != malloc_chunk_size) 803 | break; 804 | 805 | uint64_t size = aa(chunk, mchunk_size_offset) & ~uint64_t(7); 806 | assert(size >= minsize); 807 | assert((size & malloc_align_mask) == 0); 808 | uint64_t addr = binptr; 809 | assert((addr & malloc_align_mask) == 0); 810 | for (; addr < binptr + size; addr += malloc_alignment) 811 | clear(addr); 812 | 813 | binptr = aa(chunk, fd_offset); 814 | } 815 | } 816 | } 817 | 818 | std::transform(filled.begin(), filled.end(), filled.begin(), [](uint8_t v){ return v ? 24 + v : 20; }); 819 | 820 | auto addr = h.begin; 821 | auto p = textgrid(std::begin(filled), std::end(filled), nchunkcols); 822 | for (auto& s : p) { 823 | std::cout << " " << std::setw(16) << std::setfill('.') << std::hex << addr << " " << s << std::endl; 824 | addr += nchunkcols * 2 * malloc_alignment * 8; 825 | } 826 | } 827 | 828 | delete[] arena_p; 829 | } 830 | } 831 | -------------------------------------------------------------------------------- /heap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drepper/allocmap/6f413568a2f3ae055750f8496c8a5cc1676d9703/heap.png -------------------------------------------------------------------------------- /mappings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drepper/allocmap/6f413568a2f3ae055750f8496c8a5cc1676d9703/mappings.png --------------------------------------------------------------------------------