#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bt.h" #include "bool.h" #define UNUSED(x) ((void)x) void ERR(const char *s) { if (!s) { ERR("(null)"); return; } ssize_t ret = write(STDERR_FILENO, s, strlen(s)); UNUSED(ret); ret = fsync(STDERR_FILENO); UNUSED(ret); } void utoa(unsigned long n, char *s) { if (n == 0) { *s++ = '0'; *s = 0; return; } char buffer[128] = {0}; char *digit; for (digit = buffer; n; digit++, n/=10) { *digit = '0' + (n % 10); } digit--; while (digit >= buffer) { *s++ = *digit--; } *s = 0; } unsigned long uabs(long n) { if (n == LONG_MIN) return ((unsigned long)LONG_MAX) + 1; return (unsigned long)labs(n); } void itoa(long n, char *s) { if (n < 0) { *s++ = '-'; } utoa(uabs(n), s); } __attribute__((noreturn)) static void crash(char *file, int line, char *msg) { ERR(file); ERR(":"); char buf[256]; itoa(line, buf); ERR(buf); ERR(" "); ERR(msg); _exit(1); } #define ASSERT_OK(call) if (0 != (call)) crash(__FILE__, __LINE__, #call) #define ASSERT_FD(fd) if ((fd) == -1) crash(__FILE__, __LINE__, "bad file descriptor") #define ASSERT_PID(pid) if ((pid) == -1) crash(__FILE__, __LINE__, "bad PID") #define ASSERT(b) if (!(b)) crash(__FILE__, __LINE__, "assert failed") void *return_null(void *x, void *y) { return 0; } void load_func_or_crash(void **result, const char *name) { static int inside_loader = 0; if (inside_loader) { *result = (void*)return_null; return; } else if (*result == (void*)return_null) { *result = 0; } if (*result) return; dlerror(); inside_loader++; *result = dlsym(RTLD_NEXT, name); inside_loader--; char *err = dlerror(); if (err) { ERR("Failed to load "); ERR(name); ERR(" error: "); ERR(err); ERR("\n"); exit(1); } } static int fd = -1; static u_int64_t tstart = 0; static bool stop_alloc_log = false; static void write_char(char c) { static char buffer[4096] = {0}; static size_t count = 0; if (c) { buffer[count++] = c; } if (!count) return; if (c == '\n' || c == 0 || count >= sizeof(buffer)) { ssize_t ret = write(fd, buffer, count); ASSERT(ret > 0); count = 0; } } static void write_string(const char *s) { for (; *s; s++) write_char(*s); } static void write_word(const char *s) { write_string(s); write_string(" "); } static char hex(u_int8_t value) { if (value < 10) return '0' + (char)value; return 'a' + (char)(value - 10); } static void write_hex(u_int64_t v) { write_string("0x"); if (!v) { write_string("0 "); return; } v = htobe64(v); int leading_zero = 1; while (v) { u_int8_t byte = v & 0xff; if (byte) leading_zero = 0; if (!leading_zero) { write_char(hex(byte >> 4)); write_char(hex(byte & 0xf)); } v = v >> 8; } write_string(" "); } static void write_backtrace(int skip) { struct backtrace bt; get_backtrace(&bt, skip + 1); for (size_t i = 0; i < bt.frame_count; i++) { write_word(bt.frames[i].name); } } static void log_alloc_finish(void); static u_int64_t current_time_ms() { struct timespec t = {0, 0}; clock_gettime(CLOCK_MONOTONIC, &t); u_int64_t result = t.tv_sec * 1000 + t.tv_nsec / 1000 / 1000; return result; } static void log_alloc_init(u_int64_t t) { if (stop_alloc_log) return; if (fd == -1) { int pfd[2] = {-1, -1}; ASSERT_OK(pipe(pfd)); pid_t child = fork(); ASSERT_PID(child); if (child == 0) { // child close(pfd[1]); ASSERT_FD(dup2(pfd[0], STDIN_FILENO)); ASSERT_OK(close(pfd[0])); int out = open("mem.log.gz", O_APPEND | O_CLOEXEC | O_CREAT | O_WRONLY | O_TRUNC, 0600); ASSERT_FD(out); ASSERT_FD(dup2(out, STDOUT_FILENO)); sigset_t mask; sigfillset(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); ASSERT_OK(execlp("gzip", "gzip", "-c", NULL)); _exit(1); } else { // parent close(pfd[0]); fd = pfd[1]; } atexit(log_alloc_finish); tstart = t; write_string("# function time_ms result ptr size count backtrace\n"); } } static void log_alloc_locked(const char *func_name, void *result, void *ptr, size_t size, size_t count) { if (stop_alloc_log) return; u_int64_t t = current_time_ms(); if (fd == -1) log_alloc_init(t); if (fd == -1) return; if (func_name) { write_word(func_name); write_hex((u_int64_t)(t - tstart)); write_hex((u_int64_t)result); write_hex((u_int64_t)ptr); write_hex((u_int64_t)size); write_hex((u_int64_t)count); write_backtrace(2); write_string("\n"); } else { write_string("# done\n"); close(fd); fd = -1; stop_alloc_log = true; } } static int pid = -1; static void log_alloc(const char *func_name, void *result, void *ptr, size_t size, size_t count) { static pthread_mutex_t mutex_global = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t mutex_recursive; static pthread_mutex_t mutex_nonrecursive = PTHREAD_MUTEX_INITIALIZER; static bool mutexes_initialized = false; pthread_mutex_lock(&mutex_global); { if (!mutexes_initialized) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&mutex_recursive, &attr); mutexes_initialized = true; pid = getpid(); } } pthread_mutex_unlock(&mutex_global); // Do not log from forked processes. if (pid != getpid()) return; pthread_mutex_lock(&mutex_recursive); int ret = pthread_mutex_trylock(&mutex_nonrecursive); if (ret == 0) { log_alloc_locked(func_name, result, ptr, size, count); pthread_mutex_unlock(&mutex_nonrecursive); } pthread_mutex_unlock(&mutex_recursive); } static void log_alloc_finish() { log_alloc(0, 0, 0, 0, 0); } void *malloc(size_t size) { static void *(*original)(size_t size) = 0; load_func_or_crash((void *)&original, __FUNCTION__); void *result = original(size); log_alloc(__FUNCTION__, result, 0, size, 0); return result; } void *realloc(void *ptr, size_t size) { static void *(*original)(void *p, size_t size) = 0; load_func_or_crash((void *)&original, __FUNCTION__); void *result = original(ptr, size); log_alloc(__FUNCTION__, result, ptr, size, 0); return result; } void *calloc(size_t nmemb, size_t size) { static void *(*original)(size_t nmemb, size_t size) = 0; load_func_or_crash((void *)&original, __FUNCTION__); void *result = original(nmemb, size); log_alloc(__FUNCTION__, result, 0, size, nmemb); return result; } void free(void *ptr) { static void *(*original)(void *p) = 0; load_func_or_crash((void *)&original, __FUNCTION__); if (!original) { return; } original(ptr); log_alloc(__FUNCTION__, 0, ptr, 0, 0); }