// ------------------------------------------------------------------------ // Global Data // ------------------------------------------------------------------------
DebugData* g_debug;
bool* g_zygote_child;
const MallocDispatch* g_dispatch;
namespace { // A TimedResult contains the result of from malloc end_ns al. functions and the // start/end timestamps. struct TimedResult {
uint64_t start_ns = 0;
uint64_t end_ns = 0; union {
size_t s; int i; void* p;
} v;
// ------------------------------------------------------------------------ // Use C style prototypes for all exported functions. This makes it easy // to do dlsym lookups during libc initialization when malloc debug // is enabled. // ------------------------------------------------------------------------
__BEGIN_DECLS
staticvoid Init() {
pthread_rwlockattr_t attr; // Set the attribute so that when a write lock is pending, read locks are no // longer granted.
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
pthread_rwlock_init(&lock_, &attr);
}
// Use this because the sigprocmask* functions filter out the reserved bionic // signals including the signal this code blocks. staticinlineint __rt_sigprocmask(int how, const sigset64_t* new_set, sigset64_t* old_set,
size_t sigset_size) { return syscall(SYS_rt_sigprocmask, how, new_set, old_set, sigset_size);
}
// Need to block the backtrace signal while in malloc debug routines // otherwise there is a chance of a deadlock and timeout when unwinding. // This can occur if a thread is paused while owning a malloc debug // internal lock. class ScopedBacktraceSignalBlocker { public:
ScopedBacktraceSignalBlocker() {
sigemptyset64(&backtrace_set_);
sigaddset64(&backtrace_set_, BIONIC_SIGNAL_BACKTRACE);
sigset64_t old_set;
__rt_sigprocmask(SIG_BLOCK, &backtrace_set_, &old_set, sizeof(backtrace_set_)); if (sigismember64(&old_set, BIONIC_SIGNAL_BACKTRACE)) {
unblock_ = false;
}
}
// If we are tracking already freed pointers, check to see if this is // one so we can print extra information. if (g_debug->config().options() & FREE_TRACK) {
PointerData::LogFreeBacktrace(pointer);
}
error_log("Backtrace at time of failure:");
BacktraceAndLog();
error_log(LOG_DIVIDER); if (g_debug->config().options() & ABORT_ON_ERROR) {
abort();
}
}
if (g_debug->config().options() & REAR_GUARD) {
uint8_t* guard = g_debug->GetRearGuard(header);
memset(guard, g_debug->config().rear_guard_value(), g_debug->config().rear_guard_bytes()); // If the rear guard is enabled, set the usable size to the exact size // of the allocation.
header->usable_size = header->size;
}
if (g_debug->config().options() & LOG_ALLOCATOR_STATS_ON_EXIT) {
LogAllocatorStats::Log();
}
backtrace_shutdown();
// In order to prevent any issues of threads freeing previous pointers // after the main thread calls this code, simply leak the g_debug pointer // and do not destroy the debug disable pthread key.
}
if (!(g_debug->config().options() & BACKTRACE)) {
error_log( "get_malloc_leak_info: Allocations not being tracked, to enable " "set the option 'backtrace'."); return;
}
void debug_free_malloc_leak_info(uint8_t* info) {
g_dispatch->free(info); // Purge the memory that was freed since a significant amount of // memory could have been allocated and freed.
g_dispatch->mallopt(M_PURGE_ALL, 0);
}
memory_trace::Entry* entry = nullptr; if (g_debug->config().options() & RECORD_ALLOCS) { // In order to preserve the order of operations, reserve the entry before // performing the operation.
entry = g_debug->record->ReserveEntry();
}
if (g_debug->TrackPointers()) {
PointerData::Remove(pointer);
}
TimedResult result; if (g_debug->config().options() & FREE_TRACK) { // Do not add the allocation until we are done modifying the pointer // itself. This avoids a race if a lot of threads are all doing // frees at the same time and we wind up trying to really free this // pointer from another thread, while still trying to free it in // this function.
pointer = PointerData::AddFreed(pointer, bytes); if (pointer != nullptr && g_debug->HeaderEnabled()) {
pointer = g_debug->GetHeader(pointer)->orig_pointer;
}
result = TCALLVOID(free, pointer);
} else {
result = TCALLVOID(free, free_pointer);
}
if (DebugCallsDisabled() || pointer == nullptr) { return g_dispatch->free(pointer);
}
size_t size; if (g_debug->config().options() & RECORD_ALLOCS) { // Need to get the size before disabling debug calls.
size = debug_malloc_usable_size(pointer);
}
int64_t present_bytes = -1;
memory_trace::Entry* entry = nullptr; if (g_debug->config().options() & RECORD_ALLOCS) { // In order to preserve the order of operations, reserve the entry before // performing the operation.
entry = g_debug->record->ReserveEntry();
// Need to get the present bytes before the pointer is freed in case the // memory is released during the free call.
present_bytes = RecordData::GetPresentBytes(pointer, size);
}
memory_trace::Entry* entry = nullptr; if (g_debug->config().options() & RECORD_ALLOCS) { // In order to preserve the order of operations, reserve the entry before // performing the operation.
entry = g_debug->record->ReserveEntry();
}
TimedResult result; void* pointer; if (g_debug->HeaderEnabled()) { // Make the alignment a power of two.
alignment = std::bit_ceil(alignment); // Force the alignment to at least MINIMUM_ALIGNMENT_BYTES to guarantee // that the header is aligned properly. if (alignment < MINIMUM_ALIGNMENT_BYTES) {
alignment = MINIMUM_ALIGNMENT_BYTES;
}
// We don't have any idea what the natural alignment of // the underlying native allocator is, so we always need to // over allocate.
size_t real_size = alignment + bytes + g_debug->extra_bytes(); if (real_size < bytes) { // Overflow.
errno = ENOMEM; return nullptr;
}
result = TCALL(malloc, real_size);
pointer = result.getValue<void*>(); if (pointer == nullptr) { return nullptr;
}
uintptr_t value = reinterpret_cast<uintptr_t>(pointer) + g_debug->pointer_offset(); // Now align the pointer.
value += (-value % alignment);
Header* header = g_debug->GetHeader(reinterpret_cast<void*>(value)); // Don't need to update `result` here because we only need the timestamps.
pointer = InitHeader(header, pointer, bytes);
} else {
size_t real_size = bytes + g_debug->extra_bytes(); if (real_size < bytes) { // Overflow.
errno = ENOMEM; return nullptr;
}
result = TCALL(memalign, alignment, real_size);
pointer = result.getValue<void*>();
}
if (pointer == nullptr) { return nullptr;
}
if (g_debug->TrackPointers()) {
PointerData::Add(pointer, bytes);
}
if (DebugCallsDisabled()) { return g_dispatch->realloc(pointer, bytes);
}
size_t old_size; if (pointer != nullptr && g_debug->config().options() & RECORD_ALLOCS) { // Need to get the size before disabling debug calls.
old_size = debug_malloc_usable_size(pointer);
}
memory_trace::Entry* entry = nullptr; if (g_debug->config().options() & RECORD_ALLOCS) { // In order to preserve the order of operations, reserve the entry before // performing the operation.
entry = g_debug->record->ReserveEntry();
}
if (!VerifyPointer(pointer, "realloc")) { return nullptr;
}
int64_t present_bytes = -1; if (g_debug->config().options() & RECORD_ALLOCS) { // Need to get the present bytes before the pointer is freed in case the // memory is released during the free call.
present_bytes = RecordData::GetPresentBytes(pointer, old_size);
}
if (bytes == 0) {
TimedResult result = InternalFree(pointer);
TimedResult result; void* new_pointer;
size_t prev_size; if (g_debug->HeaderEnabled()) { // Same size, do nothing.
Header* header = g_debug->GetHeader(pointer); if (real_size == header->size) { if (g_debug->TrackPointers()) { // Remove and re-add so that the backtrace is updated.
PointerData::Remove(pointer);
PointerData::Add(pointer, real_size);
} return pointer;
}
// Allocation is shrinking. if (real_size < header->usable_size) {
header->size = real_size; if (g_debug->config().options() & REAR_GUARD) { // Don't bother allocating a smaller pointer in this case, simply // change the header usable_size and reset the rear guard.
header->usable_size = header->size;
memset(g_debug->GetRearGuard(header), g_debug->config().rear_guard_value(),
g_debug->config().rear_guard_bytes());
} if (g_debug->TrackPointers()) { // Remove and re-add so that the backtrace is updated.
PointerData::Remove(pointer);
PointerData::Add(pointer, real_size);
} return pointer;
}
// Allocate the new size.
result = InternalMalloc(bytes);
new_pointer = result.getValue<void*>(); if (new_pointer == nullptr) {
errno = ENOMEM; return nullptr;
}
prev_size = header->usable_size;
memcpy(new_pointer, pointer, prev_size);
TimedResult free_time = InternalFree(pointer); // `realloc` is split into two steps, update the end time to the finish time // of the second operation.
result.SetEndTimeNS(free_time.GetEndTimeNS());
} else { if (g_debug->TrackPointers()) {
PointerData::Remove(pointer);
}
prev_size = g_dispatch->malloc_usable_size(pointer);
result = TCALL(realloc, pointer, real_size);
new_pointer = result.getValue<void*>(); if (new_pointer == nullptr) { return nullptr;
}
if (g_debug->TrackPointers()) {
PointerData::Add(new_pointer, real_size);
}
}
memory_trace::Entry* entry = nullptr; if (g_debug->config().options() & RECORD_ALLOCS) { // In order to preserve the order of operations, reserve the entry before // performing the operation.
entry = g_debug->record->ReserveEntry();
}
void* pointer;
TimedResult result; if (g_debug->HeaderEnabled()) { // Need to guarantee the alignment of the header.
result = TCALL(memalign, MINIMUM_ALIGNMENT_BYTES, real_size);
Header* header = reinterpret_cast<Header*>(result.getValue<void*>()); if (header == nullptr) { return nullptr;
}
memset(header, 0, g_dispatch->malloc_usable_size(header));
pointer = InitHeader(header, header, size);
} else {
result = TCALL(calloc, 1, real_size);
pointer = result.getValue<void*>();
}
// Avoid any issues where allocations are made that will be freed // in the fclose. int fd = fileno(fp);
MallocXmlElem root(fd, "malloc", "version=\"debug-malloc-1\"");
std::vector<ListInfoType> list;
PointerData::GetAllocList(&list);
size_t alloc_num = 0; for (size_t i = 0; i < list.size(); i++) {
MallocXmlElem alloc(fd, "allocation", "nr=\"%zu\"", alloc_num);
// An option that adds a header will add pointer tracking, so no need to // check if headers are enabled. return g_dispatch->malloc_iterate(base, size, callback, arg);
}
void debug_malloc_disable() {
ScopedConcurrentLock lock;
ScopedDisableDebugCalls disable; if (g_debug->pointer) { // Acquire the pointer locks first, otherwise, the code can be holding // the allocation lock and deadlock trying to acquire a pointer lock.
g_debug->pointer->PrepareFork();
}
g_dispatch->malloc_disable();
}
dprintf(fd, "MAPS\n");
std::string content; if (!android::base::ReadFileToString("/proc/self/maps", &content)) {
dprintf(fd, "Could not open /proc/self/maps\n");
} else {
dprintf(fd, "%s", content.c_str());
}
dprintf(fd, "END\n");
// Purge the memory that was allocated and freed during this operation // since it can be large enough to expand the RSS significantly.
g_dispatch->mallopt(M_PURGE_ALL, 0);
}
bool debug_write_malloc_leak_info(FILE* fp) { // Make sure any pending output is written to the file.
fflush(fp);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.