From 13f1d561703b62f2b746fac1ae33c49ccf178c76 Mon Sep 17 00:00:00 2001 From: Enrico Granata Date: Fri, 9 Sep 2011 00:04:24 +0000 Subject: [PATCH] Basic infrastructure code to exploit malloc stack logging as available on Mac OS X to track the allocation history of pointers on the target process llvm-svn: 139337 --- .../debugserver.xcodeproj/project.pbxproj | 2 + .../debugserver/source/MacOSX/MachTask.cpp | 84 ++++++++++++ .../debugserver/source/MacOSX/MachTask.h | 38 ++++++ .../debugserver/source/MacOSX/stack_logging.h | 122 ++++++++++++++++++ 4 files changed, 246 insertions(+) create mode 100644 lldb/tools/debugserver/source/MacOSX/stack_logging.h diff --git a/lldb/tools/debugserver/debugserver.xcodeproj/project.pbxproj b/lldb/tools/debugserver/debugserver.xcodeproj/project.pbxproj index 27aa16e0bc6f..71e16be07555 100644 --- a/lldb/tools/debugserver/debugserver.xcodeproj/project.pbxproj +++ b/lldb/tools/debugserver/debugserver.xcodeproj/project.pbxproj @@ -130,6 +130,7 @@ 4971AE7113D10F4F00649E37 /* HasAVX.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = HasAVX.s; sourceTree = ""; }; 49F530111331519C008956F6 /* MachRegisterStatesI386.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MachRegisterStatesI386.h; sourceTree = ""; }; 49F5301213316D7F008956F6 /* MachRegisterStatesX86_64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MachRegisterStatesX86_64.h; sourceTree = ""; }; + 9457ECF61419864100DFE7D8 /* stack_logging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = stack_logging.h; sourceTree = ""; }; AF67ABFF0D34604D0022D128 /* PseudoTerminal.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PseudoTerminal.cpp; sourceTree = ""; }; AF67AC000D34604D0022D128 /* PseudoTerminal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PseudoTerminal.h; sourceTree = ""; }; EF88788B0D9C7558001831DA /* com.apple.debugserver.applist.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.debugserver.applist.plist; sourceTree = ""; }; @@ -300,6 +301,7 @@ 26C637F80C71334A0024798E /* MachVMRegion.cpp */, 26B67DE00EE9BC30006C8BC0 /* MachTask.h */, 26B67DE10EE9BC30006C8BC0 /* MachTask.cpp */, + 9457ECF61419864100DFE7D8 /* stack_logging.h */, ); path = MacOSX; sourceTree = ""; diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.cpp b/lldb/tools/debugserver/source/MacOSX/MachTask.cpp index 316540f9de5e..245eb583943b 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachTask.cpp +++ b/lldb/tools/debugserver/source/MacOSX/MachTask.cpp @@ -31,6 +31,7 @@ #include "DNBLog.h" #include "MachProcess.h" #include "DNBDataRef.h" +#include "stack_logging.h" #if defined (__arm__) @@ -677,3 +678,86 @@ MachTask::DeallocateMemory (nub_addr_t addr) return false; } +static void foundStackLog(mach_stack_logging_record_t record, void *context) { + *((bool*)context) = true; +} + +bool +MachTask::HasMallocLoggingEnabled () +{ + bool found = false; + + __mach_stack_logging_enumerate_records(m_task, 0x0, foundStackLog, &found); + return found; +} + +struct history_enumerator_impl_data +{ + MachMallocEvent *buffer; + uint32_t *position; + uint32_t count; +}; + +static void history_enumerator_impl(mach_stack_logging_record_t record, void* enum_obj) +{ + history_enumerator_impl_data *data = (history_enumerator_impl_data*)enum_obj; + + if (*data->position >= data->count) + return; + + data->buffer[*data->position].m_base_address = record.address; + data->buffer[*data->position].m_size = record.argument; + data->buffer[*data->position].m_event_id = record.stack_identifier; + data->buffer[*data->position].m_event_type = record.type_flags == stack_logging_type_alloc ? eMachMallocEventTypeAlloc : + record.type_flags == stack_logging_type_dealloc ? eMachMallocEventTypeDealloc : + eMachMallocEventTypeOther; + *data->position+=1; +} + +bool +MachTask::EnumerateMallocRecords (MachMallocEvent *event_buffer, + uint32_t buffer_size, + uint32_t *count) +{ + return EnumerateMallocRecords(0, + event_buffer, + buffer_size, + count); +} + +bool +MachTask::EnumerateMallocRecords (mach_vm_address_t address, + MachMallocEvent *event_buffer, + uint32_t buffer_size, + uint32_t *count) +{ + if (!event_buffer || !count) + return false; + + if (buffer_size == 0) + return false; + + *count = 0; + history_enumerator_impl_data data = { event_buffer, count, buffer_size }; + __mach_stack_logging_enumerate_records(m_task, address, history_enumerator_impl, &data); + return (*count > 0); +} + +bool +MachTask::EnumerateMallocFrames (MachMallocEventId event_id, + mach_vm_address_t *function_addresses_buffer, + uint32_t buffer_size, + uint32_t *count) +{ + if (!function_addresses_buffer || !count) + return false; + + if (buffer_size == 0) + return false; + + __mach_stack_logging_frames_for_uniqued_stack(m_task, event_id, &function_addresses_buffer[0], buffer_size, count); + *count -= 1; + if (function_addresses_buffer[*count-1] < vm_page_size) + *count -= 1; + return (*count > 0); +} diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.h b/lldb/tools/debugserver/source/MacOSX/MachTask.h index a907e94d3447..0e74086a727a 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachTask.h +++ b/lldb/tools/debugserver/source/MacOSX/MachTask.h @@ -31,6 +31,23 @@ class MachProcess; +typedef uint64_t MachMallocEventId; + +enum MachMallocEventType +{ + eMachMallocEventTypeAlloc = 2, + eMachMallocEventTypeDealloc = 4, + eMachMallocEventTypeOther = 1 +}; + +struct MachMallocEvent +{ + mach_vm_address_t m_base_address; + uint64_t m_size; + MachMallocEventType m_event_type; + MachMallocEventId m_event_id; +}; + class MachTask { public: @@ -70,6 +87,27 @@ public: MachProcess * Process () { return m_process; } const MachProcess * Process () const { return m_process; } + + + bool HasMallocLoggingEnabled (); + + // enumerate the malloc records for a given address (starting with Mac OS X 10.6 Snow Leopard it should include + // all allocations that *include* address, rather than just those *starting* at address) + bool EnumerateMallocRecords (mach_vm_address_t address, + MachMallocEvent *event_buffer, + uint32_t buffer_size, + uint32_t *count); + + // enumerate every malloc record generated by this task, no matter what the address + bool EnumerateMallocRecords (MachMallocEvent *event_buffer, + uint32_t buffer_size, + uint32_t *count); + + // given a malloc event, report every stack frame that led to this event + bool EnumerateMallocFrames (MachMallocEventId event_id, + mach_vm_address_t *function_addresses_buffer, + uint32_t buffer_size, + uint32_t *count); protected: MachProcess * m_process; // The mach process that owns this MachTask diff --git a/lldb/tools/debugserver/source/MacOSX/stack_logging.h b/lldb/tools/debugserver/source/MacOSX/stack_logging.h new file mode 100644 index 000000000000..5b0a3080349b --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/stack_logging.h @@ -0,0 +1,122 @@ +/* + * Copyright (c) 1999-2007 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef malloc_history_test_stack_logging_h +#define malloc_history_test_stack_logging_h + +#import + +#define stack_logging_type_free 0 +#define stack_logging_type_generic 1 /* anything that is not allocation/deallocation */ +#define stack_logging_type_alloc 2 /* malloc, realloc, etc... */ +#define stack_logging_type_dealloc 4 /* free, realloc, etc... */ + +// Following flags are absorbed by stack_logging_log_stack() +#define stack_logging_flag_zone 8 /* NSZoneMalloc, etc... */ +#define stack_logging_flag_calloc 16 /* multiply arguments to get the size */ +#define stack_logging_flag_object 32 /* NSAllocateObject(Class, extraBytes, zone) */ +#define stack_logging_flag_cleared 64 /* for NewEmptyHandle */ +#define stack_logging_flag_handle 128 /* for Handle (de-)allocation routines */ +#define stack_logging_flag_set_handle_size 256 /* (Handle, newSize) treated specially */ + +/* Macro used to disguise addresses so that leak finding can work */ +#define STACK_LOGGING_DISGUISE(address) ((address) ^ 0x00005555) /* nicely idempotent */ + +extern "C" int stack_logging_enable_logging; /* when clear, no logging takes place */ +extern "C" int stack_logging_dontcompact; /* default is to compact; when set does not compact alloc/free logs; useful for tracing history */ + + +extern "C" void stack_logging_log_stack(unsigned type, unsigned arg1, unsigned arg2, unsigned arg3, unsigned result, unsigned num_hot_to_skip); +/* This is the old log-to-memory logger, which is now deprecated. It remains for compatibility with performance tools that haven't been updated to disk_stack_logging_log_stack() yet. */ + +extern "C" void __disk_stack_logging_log_stack(uint32_t type_flags, uintptr_t zone_ptr, uintptr_t size, uintptr_t ptr_arg, uintptr_t return_val, uint32_t num_hot_to_skip); +/* Fits as the malloc_logger; logs malloc/free/realloc events and can log custom events if called directly */ + + +/* 64-bit-aware stack log access. */ +typedef struct { + uint32_t type_flags; + uint64_t stack_identifier; + uint64_t argument; + mach_vm_address_t address; +} mach_stack_logging_record_t; + +extern "C" kern_return_t __mach_stack_logging_get_frames(task_t task, mach_vm_address_t address, mach_vm_address_t *stack_frames_buffer, uint32_t max_stack_frames, uint32_t *count); +/* Gets the last allocation record (malloc, realloc, or free) about address */ + +extern "C" kern_return_t __mach_stack_logging_enumerate_records(task_t task, mach_vm_address_t address, void enumerator(mach_stack_logging_record_t, void *), void *context); +/* Applies enumerator to all records involving address sending context as enumerator's second parameter; if !address, applies enumerator to all records */ + +extern "C" kern_return_t __mach_stack_logging_frames_for_uniqued_stack(task_t task, uint64_t stack_identifier, mach_vm_address_t *stack_frames_buffer, uint32_t max_stack_frames, uint32_t *count); +/* Given a uniqued_stack fills stack_frames_buffer */ + + +#pragma mark - +#pragma mark Legacy + +/* The following is the old 32-bit-only, in-process-memory stack logging. This is deprecated and clients should move to the above 64-bit-aware disk stack logging SPI. */ + +typedef struct { + unsigned type; + unsigned uniqued_stack; + unsigned argument; + unsigned address; /* disguised, to avoid confusing leaks */ +} stack_logging_record_t; + +typedef struct { + unsigned overall_num_bytes; + unsigned num_records; + unsigned lock; /* 0 means OK to lock; used for inter-process locking */ + unsigned *uniquing_table; /* allocated using vm_allocate() */ + /* hashtable organized as (PC, uniqued parent) + Only the second half of the table is active + To enable us to grow dynamically */ + unsigned uniquing_table_num_pages; /* number of pages of the table */ + unsigned extra_retain_count; /* not used by stack_logging_log_stack */ + unsigned filler[2]; /* align to cache lines for better performance */ + stack_logging_record_t records[0]; /* records follow here */ +} stack_logging_record_list_t; + +extern "C" stack_logging_record_list_t *stack_logging_the_record_list; +/* This is the global variable containing all logs */ + +extern "C" kern_return_t stack_logging_get_frames(task_t task, memory_reader_t reader, vm_address_t address, vm_address_t *stack_frames_buffer, unsigned max_stack_frames, unsigned *num_frames); +/* Gets the last record in stack_logging_the_record_list about address */ + +#define STACK_LOGGING_ENUMERATION_PROVIDED 1 // temporary to avoid dependencies between projects + +extern "C" kern_return_t stack_logging_enumerate_records(task_t task, memory_reader_t reader, vm_address_t address, void enumerator(stack_logging_record_t, void *), void *context); +/* Gets all the records about address; + If !address, gets all records */ + +extern "C" kern_return_t stack_logging_frames_for_uniqued_stack(task_t task, memory_reader_t reader, unsigned uniqued_stack, vm_address_t *stack_frames_buffer, unsigned max_stack_frames, unsigned *num_frames); +/* Given a uniqued_stack fills stack_frames_buffer */ + + + +extern "C" void thread_stack_pcs(vm_address_t *buffer, unsigned max, unsigned *num); +/* Convenience to fill buffer with the PCs of the frames, starting with the hot frames; + num: returned number of frames + */ + +#endif