| /* ---------------------------------------------------------------------------- | |
| Copyright (c) 2018-2026, Microsoft Research, Daan Leijen | |
| This is free software; you can redistribute it and/or modify it under the | |
| terms of the MIT license. A copy of the license can be found in the file | |
| "LICENSE" at the root of this distribution. | |
| -----------------------------------------------------------------------------*/ | |
| /* ---------------------------------------------------------------------------- | |
| Disclaimer: Unlike the rest of the considerate hand-crafted mimalloc sources, | |
| this file was written with AI assistance (for the process image). | |
| -----------------------------------------------------------------------------*/ | |
| // OS specific primitives of the pprof profiler: wall-clock time, opening snapshot files, | |
| // backtraces, and the address ranges of the loaded modules. | |
| #include "mimalloc.h" | |
| #include "mimalloc/util/libc.h" | |
| #include "mimalloc/util/options.h" | |
| #include "pprof.h" | |
| #if !MI_PROFILE_USE_BUILTIN | |
| size_t _mi_pprof_backtrace(void** buffer, size_t max_depth, size_t* hash) { | |
| MI_UNUSED(buffer); | |
| MI_UNUSED(max_depth); | |
| *hash = 0; | |
| return 0; | |
| } | |
| int64_t _mi_pprof_time_secs(void) { | |
| return 0; | |
| } | |
| #else | |
| #include <time.h> // time | |
| #if defined(_WIN32) | |
| #include <fcntl.h> // _O_CREAT, _O_TRUNC, _O_WRONLY, _O_BINARY, _O_TEXT | |
| #include <io.h> // _sopen_s, _write, _close | |
| #include <share.h> // _SH_DENYNO | |
| #include <sys/stat.h> // _S_IREAD, _S_IWRITE | |
| #elif MI_HAS_UNISTDH | |
| #include <errno.h> // EINTR | |
| #include <fcntl.h> // open, O_CREAT, O_NOFOLLOW, O_CLOEXEC | |
| #include <unistd.h> // write, read, close | |
| #include <sys/stat.h> // S_IRUSR, S_IWUSR | |
| #else | |
| #include <stdio.h> // FILE, fopen, fwrite, fclose | |
| #endif | |
| int64_t _mi_pprof_time_secs(void) { | |
| const time_t t = time(NULL); | |
| return (t == (time_t)-1 ? 0 : (int64_t)t); | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Snapshot files | |
| // | |
| // A snapshot can be taken from within an allocation (for interval snapshots in `on_alloc`), | |
| // and that allocation may be called from within the C library's stdio (e.g. when `puts` | |
| // allocates its buffer). As stdio is not reentrant, we never use it here but write | |
| // directly to a file descriptor through our own buffer (allocated in the profiler's heap). | |
| // --------------------------------------------------------------------------- | |
| #define MI_PPROF_FILE_BUF_SIZE (16*MI_KiB) | |
| struct mi_pprof_file_s { | |
| mi_heap_t* heap; | |
| #if defined(_WIN32) || MI_HAS_UNISTDH | |
| int fd; | |
| #else | |
| FILE* fp; | |
| #endif | |
| bool failed; // set if any write failed | |
| size_t len; // bytes used in `buf` | |
| uint8_t buf[MI_PPROF_FILE_BUF_SIZE]; | |
| }; | |
| // Open a snapshot file for writing, refusing to follow a symlink at `fname` (to avoid an | |
| // attacker redirecting the write to an arbitrary target) and restricting the file's | |
| // permissions to the owner only (snapshots can contain sensitive address/callstack data). | |
| // On platforms without these POSIX/Win32 facilities, we fall back to a plain `fopen` | |
| // (without the symlink/permission hardening and without protection against stdio reentrancy). | |
| mi_pprof_file_t* _mi_pprof_fopen(mi_heap_t* heap, const char* fname, bool binary) { | |
| mi_pprof_file_t* const f = (mi_pprof_file_t*)mi_heap_malloc(heap, sizeof(mi_pprof_file_t)); | |
| if (f == NULL) return NULL; | |
| f->heap = heap; | |
| f->failed = false; | |
| f->len = 0; | |
| #if defined(_WIN32) | |
| int oflag = _O_CREAT | _O_TRUNC | _O_WRONLY | (binary ? _O_BINARY : _O_TEXT); | |
| int fd = -1; | |
| if (_sopen_s(&fd, fname, oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE) != 0 || fd < 0) { mi_free(f); return NULL; } | |
| f->fd = fd; | |
| #elif MI_HAS_UNISTDH | |
| MI_UNUSED(binary); | |
| int oflag = O_CREAT | O_TRUNC | O_WRONLY; | |
| #if defined(O_NOFOLLOW) | |
| oflag |= O_NOFOLLOW; // fail (ELOOP) if `fname` is a symlink | |
| #endif | |
| #if defined(O_CLOEXEC) | |
| oflag |= O_CLOEXEC; | |
| #endif | |
| const int fd = open(fname, oflag, S_IRUSR | S_IWUSR); // restrict to owner read/write only | |
| if (fd < 0) { mi_free(f); return NULL; } | |
| f->fd = fd; | |
| #else | |
| f->fp = fopen(fname, binary ? "wb" : "w"); | |
| if (f->fp == NULL) { mi_free(f); return NULL; } | |
| #endif | |
| return f; | |
| } | |
| static void mi_pprof_file_flush(mi_pprof_file_t* f) { | |
| const uint8_t* p = f->buf; | |
| size_t n = f->len; | |
| f->len = 0; | |
| while (n > 0 && !f->failed) { | |
| #if defined(_WIN32) | |
| const int w = _write(f->fd, p, (unsigned int)(n > MI_PPROF_FILE_BUF_SIZE ? MI_PPROF_FILE_BUF_SIZE : n)); | |
| #elif MI_HAS_UNISTDH | |
| const ssize_t w = write(f->fd, p, n); | |
| if (w < 0 && errno == EINTR) continue; | |
| #else | |
| const size_t w = fwrite(p, 1, n, f->fp); | |
| #endif | |
| if (w <= 0) { f->failed = true; break; } | |
| p += (size_t)w; | |
| n -= (size_t)w; | |
| } | |
| } | |
| void _mi_pprof_fwrite(mi_pprof_file_t* f, const void* data, size_t size) { | |
| const uint8_t* p = (const uint8_t*)data; | |
| while (size > 0 && !f->failed) { | |
| if (f->len == MI_PPROF_FILE_BUF_SIZE) { mi_pprof_file_flush(f); } | |
| const size_t avail = MI_PPROF_FILE_BUF_SIZE - f->len; | |
| const size_t n = (size < avail ? size : avail); | |
| mi_memcpy(f->buf + f->len, p, n); | |
| f->len += n; | |
| p += n; | |
| size -= n; | |
| } | |
| } | |
| void _mi_pprof_fputs(mi_pprof_file_t* f, const char* s) { | |
| _mi_pprof_fwrite(f, s, _mi_strlen(s)); | |
| } | |
| // Flush and close the file; returns `false` if any write failed. | |
| bool _mi_pprof_fclose(mi_pprof_file_t* f) { | |
| mi_pprof_file_flush(f); | |
| bool ok = !f->failed; | |
| #if defined(_WIN32) | |
| if (_close(f->fd) != 0) { ok = false; } | |
| #elif MI_HAS_UNISTDH | |
| if (close(f->fd) != 0) { ok = false; } | |
| #else | |
| if (fclose(f->fp) != 0) { ok = false; } | |
| #endif | |
| mi_free(f); | |
| return ok; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Basic backtraces | |
| // --------------------------------------------------------------------------- | |
| #if MI_HAS_EXECINFOH || MI_HAS_UNWINDH | |
| static size_t mi_backtrace_hash(void** frames, size_t depth) { | |
| size_t hash = MI_FNV_OFFSET_BASIS; | |
| for (size_t i = 0; i < depth; i++) { | |
| hash = (hash ^ (size_t)((uintptr_t)frames[i])) * MI_FNV_PRIME; | |
| } | |
| return hash; | |
| } | |
| #endif | |
| #if _WIN32 | |
| #include <windows.h> // CaptureStackBackTrace | |
| mi_decl_noinline size_t _mi_pprof_backtrace(void** buffer, size_t max_depth, size_t* hash) { | |
| if (max_depth > ULONG_MAX) { max_depth = ULONG_MAX; } | |
| // On Windows XP/Server 2003, `FramesToSkip + FramesToCapture` must be less than 63; | |
| // clamp defensively so we never exceed this, regardless of the actual OS version. | |
| const size_t skip = 1 + MI_BACKTRACE_SKIP_FRAMES; // +1 for `_mi_pprof_backtrace`'s own frame | |
| const size_t win_limit = 62; // strictly less than 63 | |
| if (skip + max_depth >= win_limit) { max_depth = (skip <= win_limit ? win_limit - skip : 0); } | |
| if (max_depth == 0) { *hash = 0; return 0; } | |
| DWORD win_hash = 0; | |
| const size_t depth = (size_t)CaptureStackBackTrace((ULONG)skip, (ULONG)max_depth, buffer, &win_hash); | |
| *hash = (size_t)win_hash; // use the hash computed by `CaptureStackBackTrace` itself | |
| return depth; | |
| } | |
| #elif MI_HAS_EXECINFOH | |
| #include <execinfo.h> // backtrace | |
| mi_decl_noinline size_t _mi_pprof_backtrace(void** buffer, size_t max_depth, size_t* hash) { | |
| const size_t skip = 1 + MI_BACKTRACE_SKIP_FRAMES; // +1 for `_mi_pprof_backtrace`'s own frame | |
| if (max_depth <= skip) { *hash = 0; return 0; } | |
| if (max_depth > INT_MAX) { max_depth = INT_MAX; } | |
| const size_t raw_depth = (size_t)backtrace(buffer, (int)(max_depth - skip)); | |
| if (raw_depth <= skip) { *hash = 0; return 0; } | |
| const size_t depth = raw_depth - skip; | |
| for (size_t i = 0; i < depth; i++) { buffer[i] = buffer[i + skip]; } // overlapping move down | |
| *hash = mi_backtrace_hash(buffer, depth); // no platform-provided hash, compute our own | |
| return depth; | |
| } | |
| #elif MI_HAS_UNWINDH | |
| #include <unwind.h> // _Unwind_Backtrace (from the compiler's unwinder: libgcc_s or LLVM libunwind) | |
| typedef struct mi_unwind_state_s { | |
| void** buffer; | |
| size_t max_depth; | |
| size_t skip; | |
| size_t depth; | |
| } mi_unwind_state_t; | |
| static _Unwind_Reason_Code mi_unwind_frame(struct _Unwind_Context* ctx, void* arg) { | |
| mi_unwind_state_t* const st = (mi_unwind_state_t*)arg; | |
| const uintptr_t ip = (uintptr_t)_Unwind_GetIP(ctx); | |
| if (ip == 0) return _URC_END_OF_STACK; | |
| if (st->skip > 0) { st->skip--; return _URC_NO_REASON; } | |
| st->buffer[st->depth++] = (void*)ip; | |
| return (st->depth >= st->max_depth ? _URC_END_OF_STACK : _URC_NO_REASON); | |
| } | |
| mi_decl_noinline size_t _mi_pprof_backtrace(void** buffer, size_t max_depth, size_t* hash) { | |
| // used as a fallback on platforms without `execinfo.h` (e.g. musl-based Linux like Alpine) | |
| mi_unwind_state_t st = { buffer, max_depth, 1 + MI_BACKTRACE_SKIP_FRAMES /* +1 for `_mi_pprof_backtrace`'s own frame */, 0 }; | |
| if (max_depth > 0) { _Unwind_Backtrace(&mi_unwind_frame, &st); } | |
| *hash = mi_backtrace_hash(buffer, st.depth); // no platform-provided hash, compute our own | |
| return st.depth; | |
| } | |
| #else | |
| size_t _mi_pprof_backtrace(void** buffer, size_t max_depth, size_t* hash) { | |
| MI_UNUSED(buffer); | |
| MI_UNUSED(max_depth); | |
| *hash = 0; | |
| return 0; | |
| } | |
| #endif | |
| // --------------------------------------------------------------------------- | |
| // Write out mapped libraries | |
| // --------------------------------------------------------------------------- | |
| mi_decl_maybe_unused static void mi_pprof_modules_add(mi_heap_t* heap, mi_pprof_modules_t* mods, uintptr_t start, uintptr_t end, uint64_t file_offset, const char* path) { | |
| if (mods->failed) return; | |
| if (mods->count == mods->capacity) { | |
| const size_t new_capacity = (mods->capacity == 0 ? 16 : mods->capacity * 2); | |
| mi_pprof_module_t* const new_modules = (mi_pprof_module_t*)mi_heap_realloc(heap, mods->modules, new_capacity * sizeof(*mods->modules)); | |
| if (new_modules == NULL) { mods->failed = true; return; } | |
| mods->modules = new_modules; | |
| mods->capacity = new_capacity; | |
| } | |
| char* const copy = (path != NULL ? mi_heap_strndup(heap, path, 1024) : NULL); | |
| if (path != NULL && copy == NULL) { mods->failed = true; return; } | |
| mi_pprof_module_t* m = &mods->modules[mods->count++]; | |
| m->start = start; | |
| m->end = end; | |
| m->file_offset = file_offset; | |
| m->path = copy; | |
| } | |
| void _mi_pprof_modules_done(mi_pprof_modules_t* mods) { | |
| for (size_t i = 0; i < mods->count; i++) { mi_free(mods->modules[i].path); } | |
| mi_free(mods->modules); | |
| mods->modules = NULL; | |
| mods->count = 0; | |
| mods->capacity = 0; | |
| } | |
| // On Linux, `pprof` can symbolize addresses using the `MAPPED_LIBRARIES` trailer | |
| // which is just a copy of `/proc/self/maps`. On macOS there is no equivalent | |
| // file so we reconstruct a similarly formatted line (`start-end perm ... path`) | |
| // per loaded Mach-O image using the dyld APIs: the range is taken from the | |
| // image's `__TEXT` segment, which is also the "load address" that tools like | |
| // `atos` use to symbolize addresses (`atos -o <path> -l <start> <addr>`). On | |
| // Windows we walk the process address space with `VirtualQuery` | |
| // On other platforms we leave this section empty (`pprof` can still work | |
| // without it if not using PIE, or when combined with tools like `atos` / | |
| // `addr2line` / a symbol server). | |
| // A small wrapper that formats with our own minimal `_mi_vsnprintf` (so call | |
| // sites can use its `%l8x`/`%l8d`/`%tx` extensions instead of <inttypes.h>'s | |
| // `PRIx64`/`PRIu64`/`PRIxPTR` macros) and writes the result to a snapshot file. | |
| // (Distinct from the existing `_mi_fprintf(mi_output_fun*, void*, ...)` in | |
| // `util/options.c`, which targets mimalloc's redirectable output callback instead.) | |
| void _mi_pprof_fprintf(mi_pprof_file_t* f, const char* fmt, ...) { | |
| char buf[512]; | |
| va_list args; | |
| va_start(args, fmt); | |
| _mi_vsnprintf(buf, sizeof(buf), fmt, args); | |
| va_end(args); | |
| _mi_pprof_fputs(f, buf); | |
| } | |
| #if defined(_WIN32) | |
| #include <windows.h> | |
| // Walk the process address space, calling `on_module(ctx, base, end, path)` | |
| // once for each loaded module. Consecutive `MEM_IMAGE` regions that belong to | |
| // the same module (same allocation base) are merged into a single range. | |
| static void mi_walk_modules_win32(void (*on_module)(void* ctx, uintptr_t base, uintptr_t end, const char* path), void* ctx) { | |
| SYSTEM_INFO si; | |
| GetSystemInfo(&si); | |
| uint8_t* addr = (uint8_t*)si.lpMinimumApplicationAddress; | |
| uint8_t* const addr_max = (uint8_t*)si.lpMaximumApplicationAddress; | |
| void* cur_base = NULL; | |
| uintptr_t cur_end = 0; | |
| while (addr < addr_max) { | |
| MEMORY_BASIC_INFORMATION mbi; | |
| const SIZE_T n = VirtualQuery(addr, &mbi, sizeof(mbi)); | |
| if (n == 0) break; // no more (queryable) regions | |
| if (mbi.Type == MEM_IMAGE && mbi.State != MEM_FREE) { | |
| if (mbi.AllocationBase == cur_base) { | |
| // extend the current module's range | |
| cur_end = (uintptr_t)mbi.BaseAddress + mbi.RegionSize; | |
| } | |
| else { | |
| // a new module starts: flush the previous one first | |
| if (cur_base != NULL) { | |
| char path[MAX_PATH]; | |
| const DWORD len = GetModuleFileNameA((HMODULE)cur_base, path, (DWORD)sizeof(path)); | |
| on_module(ctx, (uintptr_t)cur_base, cur_end, (len > 0 ? path : "")); | |
| } | |
| cur_base = mbi.AllocationBase; | |
| cur_end = (uintptr_t)mbi.BaseAddress + mbi.RegionSize; | |
| } | |
| } | |
| else if (cur_base != NULL) { | |
| // the module's regions ended: flush it | |
| char path[MAX_PATH]; | |
| const DWORD len = GetModuleFileNameA((HMODULE)cur_base, path, (DWORD)sizeof(path)); | |
| on_module(ctx, (uintptr_t)cur_base, cur_end, (len > 0 ? path : "")); | |
| cur_base = NULL; | |
| } | |
| const uint8_t* next = (const uint8_t*)mbi.BaseAddress + mbi.RegionSize; | |
| if (next <= addr) break; // guard against a non-advancing region | |
| addr = (uint8_t*)next; | |
| } | |
| if (cur_base != NULL) { | |
| char path[MAX_PATH]; | |
| const DWORD len = GetModuleFileNameA((HMODULE)cur_base, path, (DWORD)sizeof(path)); | |
| on_module(ctx, (uintptr_t)cur_base, cur_end, (len > 0 ? path : "")); | |
| } | |
| } | |
| static void mi_write_mapped_libraries_on_module_win32(void* ctx, uintptr_t base, uintptr_t end, const char* path) { | |
| mi_pprof_file_t* f = (mi_pprof_file_t*)ctx; | |
| _mi_pprof_fprintf(f, "%tx-%tx r-xp 00000000 00:00 0 %s\n", base, end, path); | |
| } | |
| static void mi_write_mapped_libraries_win32(mi_pprof_file_t* f) { | |
| mi_walk_modules_win32(&mi_write_mapped_libraries_on_module_win32, f); | |
| } | |
| typedef struct { mi_heap_t* heap; mi_pprof_modules_t* mods; } mi_pprof_collect_modules_win32_ctx_t; | |
| static void mi_pprof_collect_modules_win32_on_module(void* ctx, uintptr_t base, uintptr_t end, const char* path) { | |
| mi_pprof_collect_modules_win32_ctx_t* c = (mi_pprof_collect_modules_win32_ctx_t*)ctx; | |
| mi_pprof_modules_add(c->heap, c->mods, base, end, 0, (path[0] != 0 ? path : NULL)); | |
| } | |
| static void mi_pprof_collect_modules_win32(mi_heap_t* heap, mi_pprof_modules_t* mods) { | |
| mi_pprof_collect_modules_win32_ctx_t ctx = { heap, mods }; | |
| mi_walk_modules_win32(&mi_pprof_collect_modules_win32_on_module, &ctx); | |
| } | |
| #elif defined(__APPLE__) | |
| #include <mach-o/dyld.h> | |
| #include <mach-o/loader.h> | |
| // Find the (slid) virtual address range of the `__TEXT` segment of a loaded | |
| // Mach-O image; this is the image's load address as used for symbolication. | |
| static bool mi_macho_image_range(const struct mach_header* mh, intptr_t slide, uintptr_t* start, uintptr_t* end) { | |
| const bool is64 = (mh->magic == MH_MAGIC_64 || mh->magic == MH_CIGAM_64); | |
| const uint8_t* cmd_ptr = (const uint8_t*)mh + (is64 ? sizeof(struct mach_header_64) : sizeof(struct mach_header)); | |
| for (uint32_t c = 0; c < mh->ncmds; c++) { | |
| const struct load_command* lc = (const struct load_command*)cmd_ptr; | |
| if (is64 && lc->cmd == LC_SEGMENT_64) { | |
| const struct segment_command_64* seg = (const struct segment_command_64*)lc; | |
| if (_mi_streq(seg->segname, SEG_TEXT)) { | |
| *start = (uintptr_t)seg->vmaddr + (uintptr_t)slide; | |
| *end = *start + (uintptr_t)seg->vmsize; | |
| return true; | |
| } | |
| } | |
| else if (!is64 && lc->cmd == LC_SEGMENT) { | |
| const struct segment_command* seg = (const struct segment_command*)lc; | |
| if (_mi_streq(seg->segname, SEG_TEXT)) { | |
| *start = (uintptr_t)seg->vmaddr + (uintptr_t)slide; | |
| *end = *start + (uintptr_t)seg->vmsize; | |
| return true; | |
| } | |
| } | |
| cmd_ptr += lc->cmdsize; | |
| } | |
| return false; // no __TEXT segment found | |
| } | |
| static void mi_write_mapped_libraries_macos(mi_pprof_file_t* f) { | |
| const uint32_t count = _dyld_image_count(); | |
| for (uint32_t i = 0; i < count; i++) { | |
| const struct mach_header* mh = _dyld_get_image_header(i); | |
| const char* name = _dyld_get_image_name(i); | |
| if (mh == NULL || name == NULL) continue; | |
| const intptr_t slide = _dyld_get_image_vmaddr_slide(i); | |
| uintptr_t start = 0; | |
| uintptr_t end = 0; | |
| if (!mi_macho_image_range(mh, slide, &start, &end)) continue; | |
| _mi_pprof_fprintf(f, "%tx-%tx r-xp 00000000 00:00 0 %s\n", start, end, name); | |
| } | |
| } | |
| static void mi_pprof_collect_modules_macos(mi_heap_t* heap, mi_pprof_modules_t* mods) { | |
| const uint32_t count = _dyld_image_count(); | |
| for (uint32_t i = 0; i < count; i++) { | |
| const struct mach_header* mh = _dyld_get_image_header(i); | |
| const char* name = _dyld_get_image_name(i); | |
| if (mh == NULL || name == NULL) continue; | |
| const intptr_t slide = _dyld_get_image_vmaddr_slide(i); | |
| uintptr_t start = 0; | |
| uintptr_t end = 0; | |
| if (!mi_macho_image_range(mh, slide, &start, &end)) continue; | |
| mi_pprof_modules_add(heap, mods, start, end, 0, name); | |
| } | |
| } | |
| #elif defined(__linux__) | |
| #include <errno.h> // EINTR | |
| #include <fcntl.h> // open, O_RDONLY, O_CLOEXEC | |
| #include <unistd.h> // read, close | |
| // Call `on_line(ctx, line)` for each line of `/proc/self/maps` (without using stdio, see `_mi_pprof_fopen`). | |
| // The line is zero terminated and includes its newline; lines longer than 511 bytes are truncated. | |
| static void mi_read_maps_linux(void (*on_line)(void* ctx, char* line), void* ctx) { | |
| int oflag = O_RDONLY; | |
| #if defined(O_CLOEXEC) | |
| oflag |= O_CLOEXEC; | |
| #endif | |
| const int fd = open("/proc/self/maps", oflag); | |
| if (fd < 0) return; | |
| char buf[4096]; | |
| char line[512]; | |
| size_t line_len = 0; | |
| for (;;) { | |
| const ssize_t n = read(fd, buf, sizeof(buf)); | |
| if (n < 0 && errno == EINTR) continue; | |
| if (n <= 0) break; | |
| for (ssize_t i = 0; i < n; i++) { | |
| const char c = buf[i]; | |
| if (c == '\n') { | |
| line[line_len++] = '\n'; | |
| line[line_len] = 0; | |
| on_line(ctx, line); | |
| line_len = 0; | |
| } | |
| else if (line_len < sizeof(line) - 2) { // leave room for the newline and terminator | |
| line[line_len++] = c; | |
| } | |
| } | |
| } | |
| if (line_len > 0) { line[line_len] = 0; on_line(ctx, line); } | |
| close(fd); | |
| } | |
| static void mi_write_mapped_libraries_on_line_linux(void* ctx, char* line) { | |
| _mi_pprof_fputs((mi_pprof_file_t*)ctx, line); | |
| } | |
| // Copy `/proc/self/maps` verbatim; this is exactly the format `pprof` expects. | |
| static void mi_write_mapped_libraries_linux(mi_pprof_file_t* f) { | |
| mi_read_maps_linux(&mi_write_mapped_libraries_on_line_linux, f); | |
| } | |
| typedef struct { mi_heap_t* heap; mi_pprof_modules_t* mods; } mi_pprof_collect_modules_linux_ctx_t; | |
| // Parse `/proc/self/maps` lines of the form | |
| // `<start>-<end> <perms> <offset> <dev> <inode> <path>` (path may be absent | |
| // for anonymous mappings, which are skipped here since they are not useful | |
| // for symbolization). | |
| static void mi_pprof_collect_modules_on_line_linux(void* ctx, char* line) { | |
| mi_pprof_collect_modules_linux_ctx_t* const c = (mi_pprof_collect_modules_linux_ctx_t*)ctx; | |
| mi_heap_t* const heap = c->heap; | |
| mi_pprof_modules_t* const mods = c->mods; | |
| const size_t line_len = _mi_strlen(line); | |
| unsigned long long start = 0; | |
| unsigned long long end = 0; | |
| unsigned long long offset = 0; | |
| const char* end_ptr = NULL; | |
| if (!_mi_strntoull(line, line_len, 16, &end_ptr, &start) || *end_ptr != '-') return; | |
| end_ptr++; | |
| if (!_mi_strntoull(end_ptr, line_len - (size_t)(end_ptr - line), 16, &end_ptr, &end)) return; | |
| // Skip permissions, device, and inode; preserve the offset and the entire pathname. | |
| for (size_t field = 0; field < 4; field++) { | |
| while (*end_ptr == ' ') end_ptr++; | |
| if (*end_ptr == 0 || *end_ptr == '\n') return; | |
| if (field == 1) { | |
| if (!_mi_strntoull(end_ptr, line_len - (size_t)(end_ptr - line), 16, &end_ptr, &offset)) return; | |
| } | |
| else { | |
| while (*end_ptr != 0 && *end_ptr != ' ' && *end_ptr != '\n') end_ptr++; | |
| } | |
| if (*end_ptr != ' ' && *end_ptr != '\n' && *end_ptr != 0) return; | |
| } | |
| const char* path = end_ptr; | |
| while (*path == ' ') path++; | |
| size_t len = _mi_strlen(path); | |
| while (len > 0 && (path[len-1] == '\n' || path[len-1] == '\r')) { len--; } | |
| if (len == 0 || path[0] == '[') return; // skip anonymous mappings like `[heap]`, `[stack]` | |
| char pathbuf[512]; | |
| if (len >= sizeof(pathbuf)) { len = sizeof(pathbuf) - 1; } | |
| mi_memcpy(pathbuf, path, len); | |
| pathbuf[len] = 0; | |
| mi_pprof_modules_add(heap, mods, (uintptr_t)start, (uintptr_t)end, (uint64_t)offset, pathbuf); | |
| } | |
| static void mi_pprof_collect_modules_linux(mi_heap_t* heap, mi_pprof_modules_t* mods) { | |
| mi_pprof_collect_modules_linux_ctx_t ctx = { heap, mods }; | |
| mi_read_maps_linux(&mi_pprof_collect_modules_on_line_linux, &ctx); | |
| } | |
| #elif defined(__FreeBSD__) | |
| #include <sys/types.h> | |
| #include <sys/sysctl.h> // sysctl, CTL_KERN, KERN_PROC, KERN_PROC_VMMAP | |
| #include <sys/user.h> // struct kinfo_vmentry | |
| #include <unistd.h> // getpid | |
| #include <stddef.h> // offsetof | |
| // FreeBSD does not reliably provide Linux's `/proc/self/maps` (procfs is optional and rarely | |
| // mounted by default); instead we use the `KERN_PROC_VMMAP` sysctl to enumerate the process's | |
| // virtual memory mappings. This returns packed `kinfo_vmentry` records of `kve_structsize` bytes | |
| // each, where the final `kve_path` field is truncated. We use the sysctl directly (instead of | |
| // `kinfo_getvmmap`) to avoid a dependency on `libutil`. | |
| // (DragonFly forked from FreeBSD before `KERN_PROC_VMMAP` was added and does not provide it.) | |
| static void mi_walk_mappings_freebsd(void (*on_mapping)(void* ctx, uintptr_t start, uintptr_t end, const char* path), void* ctx) { | |
| int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_VMMAP, (int)getpid() }; | |
| size_t len = 0; | |
| if (sysctl(mib, 4, NULL, &len, NULL, 0) != 0 || len == 0) return; | |
| len = len * 4 / 3; // leave room for mappings added in the meantime | |
| char* const buf = (char*)mi_malloc(len); | |
| if (buf == NULL) return; | |
| if (sysctl(mib, 4, buf, &len, NULL, 0) == 0) { | |
| const size_t path_ofs = offsetof(struct kinfo_vmentry, kve_path); | |
| size_t ofs = 0; | |
| while (ofs + path_ofs < len) { | |
| const struct kinfo_vmentry* const kve = (const struct kinfo_vmentry*)(buf + ofs); | |
| const size_t size = (size_t)kve->kve_structsize; | |
| if (size <= path_ofs || size > len - ofs) break; // end of the records (or corrupted) | |
| ofs += size; | |
| const char* const path = kve->kve_path; | |
| if (path[0] == 0) continue; // skip anonymous mappings | |
| if (_mi_strnlen(path, size - path_ofs) == size - path_ofs) continue; // path is not terminated within the record | |
| on_mapping(ctx, (uintptr_t)kve->kve_start, (uintptr_t)kve->kve_end, path); | |
| } | |
| } | |
| mi_free(buf); | |
| } | |
| static void mi_write_mapped_libraries_on_mapping_freebsd(void* ctx, uintptr_t start, uintptr_t end, const char* path) { | |
| _mi_pprof_fprintf((mi_pprof_file_t*)ctx, "%tx-%tx r-xp 00000000 00:00 0 %s\n", start, end, path); | |
| } | |
| static void mi_write_mapped_libraries_bsd(mi_pprof_file_t* f) { | |
| mi_walk_mappings_freebsd(&mi_write_mapped_libraries_on_mapping_freebsd, f); | |
| } | |
| typedef struct { mi_heap_t* heap; mi_pprof_modules_t* mods; } mi_pprof_collect_modules_freebsd_ctx_t; | |
| static void mi_pprof_collect_modules_freebsd_on_mapping(void* ctx, uintptr_t start, uintptr_t end, const char* path) { | |
| mi_pprof_collect_modules_freebsd_ctx_t* c = (mi_pprof_collect_modules_freebsd_ctx_t*)ctx; | |
| mi_pprof_modules_add(c->heap, c->mods, start, end, 0, path); | |
| } | |
| static void mi_pprof_collect_modules_bsd(mi_heap_t* heap, mi_pprof_modules_t* mods) { | |
| mi_pprof_collect_modules_freebsd_ctx_t ctx = { heap, mods }; | |
| mi_walk_mappings_freebsd(&mi_pprof_collect_modules_freebsd_on_mapping, &ctx); | |
| } | |
| #endif | |
| void _mi_pprof_collect_modules(mi_heap_t* heap, mi_pprof_modules_t* mods) { | |
| mods->modules = NULL; | |
| mods->count = 0; | |
| mods->capacity = 0; | |
| mods->failed = false; | |
| #if defined(__linux__) | |
| mi_pprof_collect_modules_linux(heap, mods); | |
| #elif defined(_WIN32) | |
| mi_pprof_collect_modules_win32(heap, mods); | |
| #elif defined(__APPLE__) | |
| mi_pprof_collect_modules_macos(heap, mods); | |
| #elif defined(__FreeBSD__) | |
| mi_pprof_collect_modules_bsd(heap, mods); | |
| #else | |
| MI_UNUSED(heap); | |
| #endif | |
| } | |
| void _mi_pprof_write_mapped_libraries(mi_pprof_file_t* f) { | |
| _mi_pprof_fputs(f, "MAPPED_LIBRARIES:\n"); | |
| #if defined(__linux__) | |
| mi_write_mapped_libraries_linux(f); | |
| #elif defined(_WIN32) | |
| mi_write_mapped_libraries_win32(f); | |
| #elif defined(__APPLE__) | |
| mi_write_mapped_libraries_macos(f); | |
| #elif defined(__FreeBSD__) | |
| mi_write_mapped_libraries_bsd(f); | |
| #endif | |
| } | |
| #endif // MI_PROFILE_USE_BUILTIN |