mirror of
https://github.com/jemalloc/jemalloc.git
synced 2026-04-14 22:51:50 +03:00
1056 lines
24 KiB
C
1056 lines
24 KiB
C
#include "jemalloc/internal/jemalloc_preamble.h"
|
|
#include "jemalloc/internal/jemalloc_internal_includes.h"
|
|
|
|
#include "jemalloc/internal/buf_writer.h"
|
|
#include "jemalloc/internal/ctl.h"
|
|
#include "jemalloc/internal/malloc_io.h"
|
|
#include "jemalloc/internal/prof_data.h"
|
|
#include "jemalloc/internal/prof_sys.h"
|
|
|
|
#ifdef JEMALLOC_PROF_LIBUNWIND
|
|
# define UNW_LOCAL_ONLY
|
|
# include <libunwind.h>
|
|
#endif
|
|
|
|
#ifdef JEMALLOC_PROF_LIBGCC
|
|
/*
|
|
* We have a circular dependency -- jemalloc_internal.h tells us if we should
|
|
* use libgcc's unwinding functionality, but after we've included that, we've
|
|
* already hooked _Unwind_Backtrace. We'll temporarily disable hooking.
|
|
*/
|
|
# undef _Unwind_Backtrace
|
|
# include <unwind.h>
|
|
# define _Unwind_Backtrace \
|
|
JEMALLOC_TEST_HOOK(_Unwind_Backtrace, test_hooks_libc_hook)
|
|
#endif
|
|
|
|
#ifdef JEMALLOC_PROF_FRAME_POINTER
|
|
// execinfo backtrace() as fallback unwinder
|
|
# include <execinfo.h>
|
|
#endif
|
|
|
|
/******************************************************************************/
|
|
|
|
malloc_mutex_t prof_dump_filename_mtx;
|
|
|
|
static uint64_t prof_dump_seq;
|
|
static uint64_t prof_dump_iseq;
|
|
static uint64_t prof_dump_mseq;
|
|
static uint64_t prof_dump_useq;
|
|
|
|
static char *prof_prefix = NULL;
|
|
|
|
/* The fallback allocator profiling functionality will use. */
|
|
base_t *prof_base;
|
|
|
|
void
|
|
bt_init(prof_bt_t *bt, void **vec) {
|
|
cassert(config_prof);
|
|
|
|
bt->vec = vec;
|
|
bt->len = 0;
|
|
}
|
|
|
|
#ifdef JEMALLOC_PROF_LIBUNWIND
|
|
static void
|
|
prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
|
|
int nframes;
|
|
|
|
cassert(config_prof);
|
|
assert(*len == 0);
|
|
assert(vec != NULL);
|
|
assert(max_len <= PROF_BT_MAX_LIMIT);
|
|
|
|
nframes = unw_backtrace(vec, max_len);
|
|
if (nframes <= 0) {
|
|
return;
|
|
}
|
|
*len = nframes;
|
|
}
|
|
#elif (defined(JEMALLOC_PROF_LIBGCC))
|
|
static _Unwind_Reason_Code
|
|
prof_unwind_init_callback(struct _Unwind_Context *context, void *arg) {
|
|
cassert(config_prof);
|
|
|
|
return _URC_NO_REASON;
|
|
}
|
|
|
|
static _Unwind_Reason_Code
|
|
prof_unwind_callback(struct _Unwind_Context *context, void *arg) {
|
|
prof_unwind_data_t *data = (prof_unwind_data_t *)arg;
|
|
void *ip;
|
|
|
|
cassert(config_prof);
|
|
|
|
ip = (void *)_Unwind_GetIP(context);
|
|
if (ip == NULL) {
|
|
return _URC_END_OF_STACK;
|
|
}
|
|
data->vec[*data->len] = ip;
|
|
(*data->len)++;
|
|
if (*data->len == data->max) {
|
|
return _URC_END_OF_STACK;
|
|
}
|
|
|
|
return _URC_NO_REASON;
|
|
}
|
|
|
|
static void
|
|
prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
|
|
prof_unwind_data_t data = {vec, len, max_len};
|
|
|
|
cassert(config_prof);
|
|
assert(vec != NULL);
|
|
assert(max_len <= PROF_BT_MAX_LIMIT);
|
|
|
|
_Unwind_Backtrace(prof_unwind_callback, &data);
|
|
}
|
|
#elif (defined(JEMALLOC_PROF_FRAME_POINTER))
|
|
JEMALLOC_DIAGNOSTIC_PUSH
|
|
JEMALLOC_DIAGNOSTIC_IGNORE_FRAME_ADDRESS
|
|
|
|
struct stack_range {
|
|
uintptr_t start;
|
|
uintptr_t end;
|
|
};
|
|
|
|
struct thread_unwind_info {
|
|
struct stack_range stack_range;
|
|
bool fallback;
|
|
};
|
|
static __thread struct thread_unwind_info unwind_info = {
|
|
.stack_range =
|
|
{
|
|
.start = 0,
|
|
.end = 0,
|
|
},
|
|
.fallback = false,
|
|
}; /* thread local */
|
|
|
|
static void
|
|
prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
|
|
/* fp: current stack frame pointer
|
|
*
|
|
* stack_range: readable stack memory range for the current thread.
|
|
* Used to validate frame addresses during stack unwinding.
|
|
* For most threads there is a single valid stack range
|
|
* that is fixed at thread creation time. This may not be
|
|
* the case when folly fibers or boost contexts are used.
|
|
* In those cases fall back to using execinfo backtrace()
|
|
* (DWARF unwind).
|
|
*/
|
|
|
|
/* always safe to get the current stack frame address */
|
|
uintptr_t fp = (uintptr_t)__builtin_frame_address(0);
|
|
|
|
/* new thread - get the stack range */
|
|
if (!unwind_info.fallback
|
|
&& unwind_info.stack_range.start == unwind_info.stack_range.end) {
|
|
if (prof_thread_stack_range(fp, &unwind_info.stack_range.start,
|
|
&unwind_info.stack_range.end)
|
|
!= 0) {
|
|
unwind_info.fallback = true;
|
|
} else {
|
|
assert(fp >= unwind_info.stack_range.start
|
|
&& fp < unwind_info.stack_range.end);
|
|
}
|
|
}
|
|
|
|
if (unwind_info.fallback) {
|
|
goto label_fallback;
|
|
}
|
|
|
|
unsigned ii = 0;
|
|
while (ii < max_len && fp != 0) {
|
|
if (fp < unwind_info.stack_range.start
|
|
|| fp >= unwind_info.stack_range.end) {
|
|
/*
|
|
* Determining the stack range from procfs can be
|
|
* relatively expensive especially for programs with
|
|
* many threads / shared libraries. If the stack
|
|
* range has changed, it is likely to change again
|
|
* in the future (fibers or some other stack
|
|
* manipulation). So fall back to backtrace for this
|
|
* thread.
|
|
*/
|
|
unwind_info.fallback = true;
|
|
goto label_fallback;
|
|
}
|
|
void *ip = ((void **)fp)[1];
|
|
if (ip == 0) {
|
|
break;
|
|
}
|
|
vec[ii++] = ip;
|
|
fp = ((uintptr_t *)fp)[0];
|
|
}
|
|
*len = ii;
|
|
return;
|
|
|
|
label_fallback:
|
|
/*
|
|
* Using the backtrace from execinfo.h here. Note that it may get
|
|
* redirected to libunwind when a libunwind not built with build-time
|
|
* flag --disable-weak-backtrace is linked.
|
|
*/
|
|
assert(unwind_info.fallback);
|
|
int nframes = backtrace(vec, max_len);
|
|
if (nframes > 0) {
|
|
*len = nframes;
|
|
} else {
|
|
*len = 0;
|
|
}
|
|
}
|
|
|
|
JEMALLOC_DIAGNOSTIC_POP
|
|
#elif (defined(JEMALLOC_PROF_GCC))
|
|
JEMALLOC_DIAGNOSTIC_PUSH
|
|
JEMALLOC_DIAGNOSTIC_IGNORE_FRAME_ADDRESS
|
|
static void
|
|
prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
|
|
/* The input arg must be a constant for __builtin_return_address. */
|
|
# define BT_FRAME(i) \
|
|
if ((i) < max_len) { \
|
|
void *p; \
|
|
if (__builtin_frame_address(i) == 0) { \
|
|
return; \
|
|
} \
|
|
p = __builtin_return_address(i); \
|
|
if (p == NULL) { \
|
|
return; \
|
|
} \
|
|
vec[(i)] = p; \
|
|
*len = (i) + 1; \
|
|
} else { \
|
|
return; \
|
|
}
|
|
|
|
cassert(config_prof);
|
|
assert(vec != NULL);
|
|
assert(max_len <= PROF_BT_MAX_LIMIT);
|
|
|
|
BT_FRAME(0)
|
|
BT_FRAME(1)
|
|
BT_FRAME(2)
|
|
BT_FRAME(3)
|
|
BT_FRAME(4)
|
|
BT_FRAME(5)
|
|
BT_FRAME(6)
|
|
BT_FRAME(7)
|
|
BT_FRAME(8)
|
|
BT_FRAME(9)
|
|
|
|
BT_FRAME(10)
|
|
BT_FRAME(11)
|
|
BT_FRAME(12)
|
|
BT_FRAME(13)
|
|
BT_FRAME(14)
|
|
BT_FRAME(15)
|
|
BT_FRAME(16)
|
|
BT_FRAME(17)
|
|
BT_FRAME(18)
|
|
BT_FRAME(19)
|
|
|
|
BT_FRAME(20)
|
|
BT_FRAME(21)
|
|
BT_FRAME(22)
|
|
BT_FRAME(23)
|
|
BT_FRAME(24)
|
|
BT_FRAME(25)
|
|
BT_FRAME(26)
|
|
BT_FRAME(27)
|
|
BT_FRAME(28)
|
|
BT_FRAME(29)
|
|
|
|
BT_FRAME(30)
|
|
BT_FRAME(31)
|
|
BT_FRAME(32)
|
|
BT_FRAME(33)
|
|
BT_FRAME(34)
|
|
BT_FRAME(35)
|
|
BT_FRAME(36)
|
|
BT_FRAME(37)
|
|
BT_FRAME(38)
|
|
BT_FRAME(39)
|
|
|
|
BT_FRAME(40)
|
|
BT_FRAME(41)
|
|
BT_FRAME(42)
|
|
BT_FRAME(43)
|
|
BT_FRAME(44)
|
|
BT_FRAME(45)
|
|
BT_FRAME(46)
|
|
BT_FRAME(47)
|
|
BT_FRAME(48)
|
|
BT_FRAME(49)
|
|
|
|
BT_FRAME(50)
|
|
BT_FRAME(51)
|
|
BT_FRAME(52)
|
|
BT_FRAME(53)
|
|
BT_FRAME(54)
|
|
BT_FRAME(55)
|
|
BT_FRAME(56)
|
|
BT_FRAME(57)
|
|
BT_FRAME(58)
|
|
BT_FRAME(59)
|
|
|
|
BT_FRAME(60)
|
|
BT_FRAME(61)
|
|
BT_FRAME(62)
|
|
BT_FRAME(63)
|
|
BT_FRAME(64)
|
|
BT_FRAME(65)
|
|
BT_FRAME(66)
|
|
BT_FRAME(67)
|
|
BT_FRAME(68)
|
|
BT_FRAME(69)
|
|
|
|
BT_FRAME(70)
|
|
BT_FRAME(71)
|
|
BT_FRAME(72)
|
|
BT_FRAME(73)
|
|
BT_FRAME(74)
|
|
BT_FRAME(75)
|
|
BT_FRAME(76)
|
|
BT_FRAME(77)
|
|
BT_FRAME(78)
|
|
BT_FRAME(79)
|
|
|
|
BT_FRAME(80)
|
|
BT_FRAME(81)
|
|
BT_FRAME(82)
|
|
BT_FRAME(83)
|
|
BT_FRAME(84)
|
|
BT_FRAME(85)
|
|
BT_FRAME(86)
|
|
BT_FRAME(87)
|
|
BT_FRAME(88)
|
|
BT_FRAME(89)
|
|
|
|
BT_FRAME(90)
|
|
BT_FRAME(91)
|
|
BT_FRAME(92)
|
|
BT_FRAME(93)
|
|
BT_FRAME(94)
|
|
BT_FRAME(95)
|
|
BT_FRAME(96)
|
|
BT_FRAME(97)
|
|
BT_FRAME(98)
|
|
BT_FRAME(99)
|
|
|
|
BT_FRAME(100)
|
|
BT_FRAME(101)
|
|
BT_FRAME(102)
|
|
BT_FRAME(103)
|
|
BT_FRAME(104)
|
|
BT_FRAME(105)
|
|
BT_FRAME(106)
|
|
BT_FRAME(107)
|
|
BT_FRAME(108)
|
|
BT_FRAME(109)
|
|
|
|
BT_FRAME(110)
|
|
BT_FRAME(111)
|
|
BT_FRAME(112)
|
|
BT_FRAME(113)
|
|
BT_FRAME(114)
|
|
BT_FRAME(115)
|
|
BT_FRAME(116)
|
|
BT_FRAME(117)
|
|
BT_FRAME(118)
|
|
BT_FRAME(119)
|
|
|
|
BT_FRAME(120)
|
|
BT_FRAME(121)
|
|
BT_FRAME(122)
|
|
BT_FRAME(123)
|
|
BT_FRAME(124)
|
|
BT_FRAME(125)
|
|
BT_FRAME(126)
|
|
BT_FRAME(127)
|
|
BT_FRAME(128)
|
|
BT_FRAME(129)
|
|
|
|
BT_FRAME(130)
|
|
BT_FRAME(131)
|
|
BT_FRAME(132)
|
|
BT_FRAME(133)
|
|
BT_FRAME(134)
|
|
BT_FRAME(135)
|
|
BT_FRAME(136)
|
|
BT_FRAME(137)
|
|
BT_FRAME(138)
|
|
BT_FRAME(139)
|
|
|
|
BT_FRAME(140)
|
|
BT_FRAME(141)
|
|
BT_FRAME(142)
|
|
BT_FRAME(143)
|
|
BT_FRAME(144)
|
|
BT_FRAME(145)
|
|
BT_FRAME(146)
|
|
BT_FRAME(147)
|
|
BT_FRAME(148)
|
|
BT_FRAME(149)
|
|
|
|
BT_FRAME(150)
|
|
BT_FRAME(151)
|
|
BT_FRAME(152)
|
|
BT_FRAME(153)
|
|
BT_FRAME(154)
|
|
BT_FRAME(155)
|
|
BT_FRAME(156)
|
|
BT_FRAME(157)
|
|
BT_FRAME(158)
|
|
BT_FRAME(159)
|
|
|
|
BT_FRAME(160)
|
|
BT_FRAME(161)
|
|
BT_FRAME(162)
|
|
BT_FRAME(163)
|
|
BT_FRAME(164)
|
|
BT_FRAME(165)
|
|
BT_FRAME(166)
|
|
BT_FRAME(167)
|
|
BT_FRAME(168)
|
|
BT_FRAME(169)
|
|
|
|
BT_FRAME(170)
|
|
BT_FRAME(171)
|
|
BT_FRAME(172)
|
|
BT_FRAME(173)
|
|
BT_FRAME(174)
|
|
BT_FRAME(175)
|
|
BT_FRAME(176)
|
|
BT_FRAME(177)
|
|
BT_FRAME(178)
|
|
BT_FRAME(179)
|
|
|
|
BT_FRAME(180)
|
|
BT_FRAME(181)
|
|
BT_FRAME(182)
|
|
BT_FRAME(183)
|
|
BT_FRAME(184)
|
|
BT_FRAME(185)
|
|
BT_FRAME(186)
|
|
BT_FRAME(187)
|
|
BT_FRAME(188)
|
|
BT_FRAME(189)
|
|
|
|
BT_FRAME(190)
|
|
BT_FRAME(191)
|
|
BT_FRAME(192)
|
|
BT_FRAME(193)
|
|
BT_FRAME(194)
|
|
BT_FRAME(195)
|
|
BT_FRAME(196)
|
|
BT_FRAME(197)
|
|
BT_FRAME(198)
|
|
BT_FRAME(199)
|
|
|
|
BT_FRAME(200)
|
|
BT_FRAME(201)
|
|
BT_FRAME(202)
|
|
BT_FRAME(203)
|
|
BT_FRAME(204)
|
|
BT_FRAME(205)
|
|
BT_FRAME(206)
|
|
BT_FRAME(207)
|
|
BT_FRAME(208)
|
|
BT_FRAME(209)
|
|
|
|
BT_FRAME(210)
|
|
BT_FRAME(211)
|
|
BT_FRAME(212)
|
|
BT_FRAME(213)
|
|
BT_FRAME(214)
|
|
BT_FRAME(215)
|
|
BT_FRAME(216)
|
|
BT_FRAME(217)
|
|
BT_FRAME(218)
|
|
BT_FRAME(219)
|
|
|
|
BT_FRAME(220)
|
|
BT_FRAME(221)
|
|
BT_FRAME(222)
|
|
BT_FRAME(223)
|
|
BT_FRAME(224)
|
|
BT_FRAME(225)
|
|
BT_FRAME(226)
|
|
BT_FRAME(227)
|
|
BT_FRAME(228)
|
|
BT_FRAME(229)
|
|
|
|
BT_FRAME(230)
|
|
BT_FRAME(231)
|
|
BT_FRAME(232)
|
|
BT_FRAME(233)
|
|
BT_FRAME(234)
|
|
BT_FRAME(235)
|
|
BT_FRAME(236)
|
|
BT_FRAME(237)
|
|
BT_FRAME(238)
|
|
BT_FRAME(239)
|
|
|
|
BT_FRAME(240)
|
|
BT_FRAME(241)
|
|
BT_FRAME(242)
|
|
BT_FRAME(243)
|
|
BT_FRAME(244)
|
|
BT_FRAME(245)
|
|
BT_FRAME(246)
|
|
BT_FRAME(247)
|
|
BT_FRAME(248)
|
|
BT_FRAME(249)
|
|
|
|
BT_FRAME(250)
|
|
BT_FRAME(251)
|
|
BT_FRAME(252)
|
|
BT_FRAME(253)
|
|
BT_FRAME(254)
|
|
BT_FRAME(255)
|
|
# undef BT_FRAME
|
|
JEMALLOC_DIAGNOSTIC_POP
|
|
}
|
|
#else
|
|
static void
|
|
prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
|
|
cassert(config_prof);
|
|
not_reached();
|
|
}
|
|
#endif
|
|
|
|
void
|
|
prof_backtrace(tsd_t *tsd, prof_bt_t *bt) {
|
|
cassert(config_prof);
|
|
prof_backtrace_hook_t prof_backtrace_hook = prof_backtrace_hook_get();
|
|
assert(prof_backtrace_hook != NULL);
|
|
|
|
pre_reentrancy(tsd, NULL);
|
|
prof_backtrace_hook(bt->vec, &bt->len, opt_prof_bt_max);
|
|
post_reentrancy(tsd);
|
|
}
|
|
|
|
void
|
|
prof_hooks_init(void) {
|
|
prof_backtrace_hook_set(&prof_backtrace_impl);
|
|
prof_dump_hook_set(NULL);
|
|
prof_sample_hook_set(NULL);
|
|
prof_sample_free_hook_set(NULL);
|
|
}
|
|
|
|
void
|
|
prof_unwind_init(void) {
|
|
#ifdef JEMALLOC_PROF_LIBGCC
|
|
/*
|
|
* Cause the backtracing machinery to allocate its internal
|
|
* state before enabling profiling.
|
|
*/
|
|
_Unwind_Backtrace(prof_unwind_init_callback, NULL);
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
prof_sys_thread_name_read_impl(char *buf, size_t limit) {
|
|
#if defined(JEMALLOC_HAVE_PTHREAD_GETNAME_NP)
|
|
return pthread_getname_np(pthread_self(), buf, limit);
|
|
#elif defined(JEMALLOC_HAVE_PTHREAD_GET_NAME_NP)
|
|
pthread_get_name_np(pthread_self(), buf, limit);
|
|
return 0;
|
|
#else
|
|
return ENOSYS;
|
|
#endif
|
|
}
|
|
prof_sys_thread_name_read_t *JET_MUTABLE prof_sys_thread_name_read =
|
|
prof_sys_thread_name_read_impl;
|
|
|
|
void
|
|
prof_sys_thread_name_fetch(tsd_t *tsd) {
|
|
prof_tdata_t *tdata = prof_tdata_get(tsd, true);
|
|
if (tdata == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (prof_sys_thread_name_read(
|
|
tdata->thread_name, PROF_THREAD_NAME_MAX_LEN)
|
|
!= 0) {
|
|
prof_thread_name_clear(tdata);
|
|
}
|
|
|
|
tdata->thread_name[PROF_THREAD_NAME_MAX_LEN - 1] = '\0';
|
|
}
|
|
|
|
int
|
|
prof_getpid(void) {
|
|
#ifdef _WIN32
|
|
return GetCurrentProcessId();
|
|
#else
|
|
return getpid();
|
|
#endif
|
|
}
|
|
|
|
static long
|
|
prof_get_pid_namespace(void) {
|
|
long ret = 0;
|
|
|
|
#if defined(_WIN32) || defined(__APPLE__)
|
|
// Not supported, do nothing.
|
|
#else
|
|
char buf[PATH_MAX];
|
|
const char *linkname =
|
|
# if defined(__FreeBSD__) || defined(__DragonFly__)
|
|
"/proc/curproc/ns/pid"
|
|
# else
|
|
"/proc/self/ns/pid"
|
|
# endif
|
|
;
|
|
ssize_t linklen =
|
|
# ifndef JEMALLOC_READLINKAT
|
|
readlink(linkname, buf, PATH_MAX)
|
|
# else
|
|
readlinkat(AT_FDCWD, linkname, buf, PATH_MAX)
|
|
# endif
|
|
;
|
|
|
|
// namespace string is expected to be like pid:[4026531836]
|
|
if (linklen > 0) {
|
|
// Trim the trailing "]"
|
|
buf[linklen - 1] = '\0';
|
|
char *index = strtok(buf, "pid:[");
|
|
ret = atol(index);
|
|
}
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* This buffer is rather large for stack allocation, so use a single buffer for
|
|
* all profile dumps; protected by prof_dump_mtx.
|
|
*/
|
|
static char prof_dump_buf[PROF_DUMP_BUFSIZE];
|
|
|
|
typedef struct prof_dump_arg_s prof_dump_arg_t;
|
|
struct prof_dump_arg_s {
|
|
/*
|
|
* Whether error should be handled locally: if true, then we print out
|
|
* error message as well as abort (if opt_abort is true) when an error
|
|
* occurred, and we also report the error back to the caller in the end;
|
|
* if false, then we only report the error back to the caller in the
|
|
* end.
|
|
*/
|
|
const bool handle_error_locally;
|
|
/*
|
|
* Whether there has been an error in the dumping process, which could
|
|
* have happened either in file opening or in file writing. When an
|
|
* error has already occurred, we will stop further writing to the file.
|
|
*/
|
|
bool error;
|
|
/* File descriptor of the dump file. */
|
|
int prof_dump_fd;
|
|
};
|
|
|
|
static void
|
|
prof_dump_check_possible_error(
|
|
prof_dump_arg_t *arg, bool err_cond, const char *format, ...) {
|
|
assert(!arg->error);
|
|
if (!err_cond) {
|
|
return;
|
|
}
|
|
|
|
arg->error = true;
|
|
if (!arg->handle_error_locally) {
|
|
return;
|
|
}
|
|
|
|
va_list ap;
|
|
char buf[PROF_PRINTF_BUFSIZE];
|
|
va_start(ap, format);
|
|
malloc_vsnprintf(buf, sizeof(buf), format, ap);
|
|
va_end(ap);
|
|
malloc_write(buf);
|
|
|
|
if (opt_abort) {
|
|
abort();
|
|
}
|
|
}
|
|
|
|
static int
|
|
prof_dump_open_file_impl(const char *filename, int mode) {
|
|
return creat(filename, mode);
|
|
}
|
|
prof_dump_open_file_t *JET_MUTABLE prof_dump_open_file =
|
|
prof_dump_open_file_impl;
|
|
|
|
static void
|
|
prof_dump_open(prof_dump_arg_t *arg, const char *filename) {
|
|
arg->prof_dump_fd = prof_dump_open_file(filename, 0644);
|
|
prof_dump_check_possible_error(arg, arg->prof_dump_fd == -1,
|
|
"<jemalloc>: failed to open \"%s\"\n", filename);
|
|
}
|
|
|
|
prof_dump_write_file_t *JET_MUTABLE prof_dump_write_file = malloc_write_fd;
|
|
|
|
static void
|
|
prof_dump_flush(void *opaque, const char *s) {
|
|
cassert(config_prof);
|
|
prof_dump_arg_t *arg = (prof_dump_arg_t *)opaque;
|
|
if (!arg->error) {
|
|
ssize_t err = prof_dump_write_file(
|
|
arg->prof_dump_fd, s, strlen(s));
|
|
prof_dump_check_possible_error(arg, err == -1,
|
|
"<jemalloc>: failed to write during heap profile flush\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
prof_dump_close(prof_dump_arg_t *arg) {
|
|
if (arg->prof_dump_fd != -1) {
|
|
close(arg->prof_dump_fd);
|
|
}
|
|
}
|
|
|
|
#ifdef __APPLE__
|
|
# include <mach-o/dyld.h>
|
|
|
|
# ifdef __LP64__
|
|
typedef struct mach_header_64 mach_header_t;
|
|
typedef struct segment_command_64 segment_command_t;
|
|
# define MH_MAGIC_VALUE MH_MAGIC_64
|
|
# define MH_CIGAM_VALUE MH_CIGAM_64
|
|
# define LC_SEGMENT_VALUE LC_SEGMENT_64
|
|
# else
|
|
typedef struct mach_header mach_header_t;
|
|
typedef struct segment_command segment_command_t;
|
|
# define MH_MAGIC_VALUE MH_MAGIC
|
|
# define MH_CIGAM_VALUE MH_CIGAM
|
|
# define LC_SEGMENT_VALUE LC_SEGMENT
|
|
# endif
|
|
|
|
static void
|
|
prof_dump_dyld_image_vmaddr(buf_writer_t *buf_writer, uint32_t image_index) {
|
|
const mach_header_t *header = (const mach_header_t *)
|
|
_dyld_get_image_header(image_index);
|
|
if (header == NULL
|
|
|| (header->magic != MH_MAGIC_VALUE
|
|
&& header->magic != MH_CIGAM_VALUE)) {
|
|
// Invalid header
|
|
return;
|
|
}
|
|
|
|
intptr_t slide = _dyld_get_image_vmaddr_slide(image_index);
|
|
const char *name = _dyld_get_image_name(image_index);
|
|
struct load_command *load_cmd = (struct load_command *)((char *)header
|
|
+ sizeof(mach_header_t));
|
|
for (uint32_t i = 0; load_cmd && (i < header->ncmds); i++) {
|
|
if (load_cmd->cmd == LC_SEGMENT_VALUE) {
|
|
const segment_command_t *segment_cmd =
|
|
(const segment_command_t *)load_cmd;
|
|
if (!strcmp(segment_cmd->segname, "__TEXT")) {
|
|
char buffer[PATH_MAX + 1];
|
|
malloc_snprintf(buffer, sizeof(buffer),
|
|
"%016llx-%016llx: %s\n",
|
|
segment_cmd->vmaddr + slide,
|
|
segment_cmd->vmaddr + slide
|
|
+ segment_cmd->vmsize,
|
|
name);
|
|
buf_writer_cb(buf_writer, buffer);
|
|
return;
|
|
}
|
|
}
|
|
load_cmd = (struct load_command *)((char *)load_cmd
|
|
+ load_cmd->cmdsize);
|
|
}
|
|
}
|
|
|
|
static void
|
|
prof_dump_dyld_maps(buf_writer_t *buf_writer) {
|
|
uint32_t image_count = _dyld_image_count();
|
|
for (uint32_t i = 0; i < image_count; i++) {
|
|
prof_dump_dyld_image_vmaddr(buf_writer, i);
|
|
}
|
|
}
|
|
|
|
prof_dump_open_maps_t *JET_MUTABLE prof_dump_open_maps = NULL;
|
|
|
|
static void
|
|
prof_dump_maps(buf_writer_t *buf_writer) {
|
|
buf_writer_cb(buf_writer, "\nMAPPED_LIBRARIES:\n");
|
|
/* No proc map file to read on MacOS, dump dyld maps for backtrace. */
|
|
prof_dump_dyld_maps(buf_writer);
|
|
}
|
|
#else /* !__APPLE__ */
|
|
# ifndef _WIN32
|
|
JEMALLOC_FORMAT_PRINTF(1, 2)
|
|
static int
|
|
prof_open_maps_internal(const char *format, ...) {
|
|
int mfd;
|
|
va_list ap;
|
|
char filename[PATH_MAX + 1];
|
|
|
|
va_start(ap, format);
|
|
malloc_vsnprintf(filename, sizeof(filename), format, ap);
|
|
va_end(ap);
|
|
|
|
# if defined(O_CLOEXEC)
|
|
mfd = open(filename, O_RDONLY | O_CLOEXEC);
|
|
# else
|
|
mfd = open(filename, O_RDONLY);
|
|
if (mfd != -1) {
|
|
fcntl(mfd, F_SETFD, fcntl(mfd, F_GETFD) | FD_CLOEXEC);
|
|
}
|
|
# endif
|
|
|
|
return mfd;
|
|
}
|
|
# endif
|
|
|
|
static int
|
|
prof_dump_open_maps_impl(void) {
|
|
int mfd;
|
|
|
|
cassert(config_prof);
|
|
# if defined(__FreeBSD__) || defined(__DragonFly__)
|
|
mfd = prof_open_maps_internal("/proc/curproc/map");
|
|
# elif defined(_WIN32)
|
|
mfd = -1; // Not implemented
|
|
# else
|
|
int pid = prof_getpid();
|
|
|
|
mfd = prof_open_maps_internal("/proc/%d/task/%d/maps", pid, pid);
|
|
if (mfd == -1) {
|
|
mfd = prof_open_maps_internal("/proc/%d/maps", pid);
|
|
}
|
|
# endif
|
|
return mfd;
|
|
}
|
|
prof_dump_open_maps_t *JET_MUTABLE prof_dump_open_maps =
|
|
prof_dump_open_maps_impl;
|
|
|
|
static ssize_t
|
|
prof_dump_read_maps_cb(void *read_cbopaque, void *buf, size_t limit) {
|
|
int mfd = *(int *)read_cbopaque;
|
|
assert(mfd != -1);
|
|
return malloc_read_fd(mfd, buf, limit);
|
|
}
|
|
|
|
static void
|
|
prof_dump_maps(buf_writer_t *buf_writer) {
|
|
int mfd = prof_dump_open_maps();
|
|
if (mfd == -1) {
|
|
return;
|
|
}
|
|
|
|
buf_writer_cb(buf_writer, "\nMAPPED_LIBRARIES:\n");
|
|
buf_writer_pipe(buf_writer, prof_dump_read_maps_cb, &mfd);
|
|
close(mfd);
|
|
}
|
|
#endif /* __APPLE__ */
|
|
|
|
static bool
|
|
prof_dump(
|
|
tsd_t *tsd, bool propagate_err, const char *filename, bool leakcheck) {
|
|
cassert(config_prof);
|
|
assert(tsd_reentrancy_level_get(tsd) == 0);
|
|
|
|
prof_tdata_t *tdata = prof_tdata_get(tsd, true);
|
|
if (tdata == NULL) {
|
|
return true;
|
|
}
|
|
|
|
prof_dump_arg_t arg = {/* handle_error_locally */ !propagate_err,
|
|
/* error */ false, /* prof_dump_fd */ -1};
|
|
|
|
pre_reentrancy(tsd, NULL);
|
|
malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_mtx);
|
|
|
|
prof_dump_open(&arg, filename);
|
|
buf_writer_t buf_writer;
|
|
bool err = buf_writer_init(tsd_tsdn(tsd), &buf_writer, prof_dump_flush,
|
|
&arg, prof_dump_buf, PROF_DUMP_BUFSIZE);
|
|
assert(!err);
|
|
prof_dump_impl(tsd, buf_writer_cb, &buf_writer, tdata, leakcheck);
|
|
prof_dump_maps(&buf_writer);
|
|
buf_writer_terminate(tsd_tsdn(tsd), &buf_writer);
|
|
prof_dump_close(&arg);
|
|
|
|
prof_dump_hook_t dump_hook = prof_dump_hook_get();
|
|
if (dump_hook != NULL) {
|
|
dump_hook(filename);
|
|
}
|
|
malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_mtx);
|
|
post_reentrancy(tsd);
|
|
|
|
return arg.error;
|
|
}
|
|
|
|
/*
|
|
* If profiling is off, then PROF_DUMP_FILENAME_LEN is 1, so we'll end up
|
|
* calling strncpy with a size of 0, which triggers a -Wstringop-truncation
|
|
* warning (strncpy can never actually be called in this case, since we bail out
|
|
* much earlier when config_prof is false). This function works around the
|
|
* warning to let us leave the warning on.
|
|
*/
|
|
static inline void
|
|
prof_strncpy(char *UNUSED dest, const char *UNUSED src, size_t UNUSED size) {
|
|
cassert(config_prof);
|
|
#ifdef JEMALLOC_PROF
|
|
strncpy(dest, src, size);
|
|
#endif
|
|
}
|
|
|
|
static const char *
|
|
prof_prefix_get(tsdn_t *tsdn) {
|
|
malloc_mutex_assert_owner(tsdn, &prof_dump_filename_mtx);
|
|
|
|
return prof_prefix == NULL ? opt_prof_prefix : prof_prefix;
|
|
}
|
|
|
|
static bool
|
|
prof_prefix_is_empty(tsdn_t *tsdn) {
|
|
malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
|
|
bool ret = (prof_prefix_get(tsdn)[0] == '\0');
|
|
malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
|
|
return ret;
|
|
}
|
|
|
|
#define DUMP_FILENAME_BUFSIZE (PATH_MAX + 1)
|
|
#define VSEQ_INVALID UINT64_C(0xffffffffffffffff)
|
|
static void
|
|
prof_dump_filename(tsd_t *tsd, char *filename, char v, uint64_t vseq) {
|
|
cassert(config_prof);
|
|
|
|
assert(tsd_reentrancy_level_get(tsd) == 0);
|
|
const char *prefix = prof_prefix_get(tsd_tsdn(tsd));
|
|
|
|
if (vseq != VSEQ_INVALID) {
|
|
if (opt_prof_pid_namespace) {
|
|
/* "<prefix>.<pid_namespace>.<pid>.<seq>.v<vseq>.heap" */
|
|
malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE,
|
|
"%s.%ld.%d.%" FMTu64 ".%c%" FMTu64 ".heap", prefix,
|
|
prof_get_pid_namespace(), prof_getpid(),
|
|
prof_dump_seq, v, vseq);
|
|
} else {
|
|
/* "<prefix>.<pid>.<seq>.v<vseq>.heap" */
|
|
malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE,
|
|
"%s.%d.%" FMTu64 ".%c%" FMTu64 ".heap", prefix,
|
|
prof_getpid(), prof_dump_seq, v, vseq);
|
|
}
|
|
} else {
|
|
if (opt_prof_pid_namespace) {
|
|
/* "<prefix>.<pid_namespace>.<pid>.<seq>.<v>.heap" */
|
|
malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE,
|
|
"%s.%ld.%d.%" FMTu64 ".%c.heap", prefix,
|
|
prof_get_pid_namespace(), prof_getpid(),
|
|
prof_dump_seq, v);
|
|
} else {
|
|
/* "<prefix>.<pid>.<seq>.<v>.heap" */
|
|
malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE,
|
|
"%s.%d.%" FMTu64 ".%c.heap", prefix, prof_getpid(),
|
|
prof_dump_seq, v);
|
|
}
|
|
}
|
|
prof_dump_seq++;
|
|
}
|
|
|
|
void
|
|
prof_get_default_filename(tsdn_t *tsdn, char *filename, uint64_t ind) {
|
|
malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
|
|
if (opt_prof_pid_namespace) {
|
|
malloc_snprintf(filename, PROF_DUMP_FILENAME_LEN,
|
|
"%s.%ld.%d.%" FMTu64 ".json", prof_prefix_get(tsdn),
|
|
prof_get_pid_namespace(), prof_getpid(), ind);
|
|
} else {
|
|
malloc_snprintf(filename, PROF_DUMP_FILENAME_LEN,
|
|
"%s.%d.%" FMTu64 ".json", prof_prefix_get(tsdn),
|
|
prof_getpid(), ind);
|
|
}
|
|
malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
|
|
}
|
|
|
|
void
|
|
prof_fdump_impl(tsd_t *tsd) {
|
|
char filename[DUMP_FILENAME_BUFSIZE];
|
|
|
|
assert(!prof_prefix_is_empty(tsd_tsdn(tsd)));
|
|
malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
|
|
prof_dump_filename(tsd, filename, 'f', VSEQ_INVALID);
|
|
malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
|
|
prof_dump(tsd, false, filename, opt_prof_leak);
|
|
}
|
|
|
|
bool
|
|
prof_prefix_set(tsdn_t *tsdn, const char *prefix) {
|
|
cassert(config_prof);
|
|
ctl_mtx_assert_held(tsdn);
|
|
if (prefix == NULL) {
|
|
return true;
|
|
}
|
|
malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
|
|
if (prof_prefix == NULL) {
|
|
malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
|
|
/* Everything is still guarded by ctl_mtx. */
|
|
char *buffer = base_alloc(
|
|
tsdn, prof_base, PROF_DUMP_FILENAME_LEN, QUANTUM);
|
|
if (buffer == NULL) {
|
|
return true;
|
|
}
|
|
malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
|
|
prof_prefix = buffer;
|
|
}
|
|
assert(prof_prefix != NULL);
|
|
|
|
prof_strncpy(prof_prefix, prefix, PROF_DUMP_FILENAME_LEN - 1);
|
|
prof_prefix[PROF_DUMP_FILENAME_LEN - 1] = '\0';
|
|
malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
prof_idump_impl(tsd_t *tsd) {
|
|
malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
|
|
if (prof_prefix_get(tsd_tsdn(tsd))[0] == '\0') {
|
|
malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
|
|
return;
|
|
}
|
|
char filename[PATH_MAX + 1];
|
|
prof_dump_filename(tsd, filename, 'i', prof_dump_iseq);
|
|
prof_dump_iseq++;
|
|
malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
|
|
prof_dump(tsd, false, filename, false);
|
|
}
|
|
|
|
bool
|
|
prof_mdump_impl(tsd_t *tsd, const char *filename) {
|
|
char filename_buf[DUMP_FILENAME_BUFSIZE];
|
|
if (filename == NULL) {
|
|
/* No filename specified, so automatically generate one. */
|
|
malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
|
|
if (prof_prefix_get(tsd_tsdn(tsd))[0] == '\0') {
|
|
malloc_mutex_unlock(
|
|
tsd_tsdn(tsd), &prof_dump_filename_mtx);
|
|
return true;
|
|
}
|
|
prof_dump_filename(tsd, filename_buf, 'm', prof_dump_mseq);
|
|
prof_dump_mseq++;
|
|
malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
|
|
filename = filename_buf;
|
|
}
|
|
return prof_dump(tsd, true, filename, false);
|
|
}
|
|
|
|
void
|
|
prof_gdump_impl(tsd_t *tsd) {
|
|
tsdn_t *tsdn = tsd_tsdn(tsd);
|
|
malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
|
|
if (prof_prefix_get(tsdn)[0] == '\0') {
|
|
malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
|
|
return;
|
|
}
|
|
char filename[DUMP_FILENAME_BUFSIZE];
|
|
prof_dump_filename(tsd, filename, 'u', prof_dump_useq);
|
|
prof_dump_useq++;
|
|
malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
|
|
prof_dump(tsd, false, filename, false);
|
|
}
|