#import "stack_logging.h"
#import "malloc_printf.h"
#import <libc.h>
#import <pthread.h>
#import <mach/mach.h>
#include <mach/vm_statistics.h>
#import <malloc/malloc.h>
#import <stdlib.h>
extern void spin_lock(int *);
extern void spin_unlock(int *);
extern void thread_stack_pcs(vm_address_t *, unsigned, unsigned *);
static inline void *allocate_pages(unsigned) __attribute__((always_inline));
static inline void *allocate_pages(unsigned bytes) {
void *address;
if (vm_allocate(mach_task_self(), (vm_address_t *)&address, bytes,
VM_MAKE_TAG(VM_MEMORY_ANALYSIS_TOOL)| TRUE)) {
malloc_printf("*** out of memory while stack logging\n");
abort();
}
return (void *)address;
}
static inline void deallocate_pages(void *, unsigned) __attribute__((always_inline));
static inline void deallocate_pages(void *ptr, unsigned bytes) {
vm_deallocate(mach_task_self(), (vm_address_t)ptr, bytes);
}
static inline void copy_pages(const void *, void *, unsigned) __attribute__((always_inline));
static inline void copy_pages(const void *source, void *dest, unsigned bytes) {
if (vm_copy(mach_task_self(), (vm_address_t)source, bytes, (vm_address_t)dest)) memmove(dest, source, bytes);
}
#define MAX_COLLIDE 8
#define MAX_NUM_PC 512
static int enter_pair_in_table(unsigned *table, unsigned numPages, unsigned *uniquedParent, unsigned thisPC) {
unsigned base = numPages * vm_page_size / (sizeof(int)*2*2);
unsigned hash = base + (((*uniquedParent) << 4) ^ (thisPC >> 2)) % (base - 1); unsigned collisions = MAX_COLLIDE;
while (collisions--) {
unsigned *head = table + hash*2;
if (! head[0] && !head[1]) {
head[0] = thisPC;
head[1] = *uniquedParent;
*uniquedParent = hash;
return 1;
}
if ((head[0] == thisPC) && (head[1] == *uniquedParent)) {
*uniquedParent = hash;
return 1;
}
hash++;
if (hash == base*2) hash = base;
}
return 0;
}
unsigned stack_logging_get_unique_stack(unsigned **table, unsigned *table_num_pages, unsigned *stack_entries, unsigned count, unsigned num_hot_to_skip) {
unsigned uniquedParent = (unsigned)-1;
while (num_hot_to_skip--) {
if (count > 0) { stack_entries++; count--; }
}
while (count--) {
unsigned thisPC = stack_entries[count];
while (!enter_pair_in_table(*table, *table_num_pages, &uniquedParent, thisPC)) {
unsigned *newTable;
unsigned oldBytes = (*table_num_pages) * vm_page_size;
newTable = allocate_pages(oldBytes*2);
copy_pages(*table, newTable, oldBytes);
deallocate_pages(*table, oldBytes);
*table_num_pages *= 2;
*table = newTable;
}
}
return uniquedParent;
}
stack_logging_record_list_t *stack_logging_the_record_list = NULL;
int stack_logging_enable_logging = 0;
int stack_logging_dontcompact = 0;
static int stack_logging_spin_lock = 0;
static stack_logging_record_list_t *GrowLogRecords(stack_logging_record_list_t *records, unsigned desiredNumRecords) {
stack_logging_record_list_t *new_records;
unsigned old_size = records->overall_num_bytes;
if (desiredNumRecords*sizeof(stack_logging_record_t)+sizeof(stack_logging_record_list_t) < records->overall_num_bytes) return records;
records->overall_num_bytes += records->overall_num_bytes + vm_page_size; new_records = allocate_pages(records->overall_num_bytes);
copy_pages(records, new_records, old_size);
deallocate_pages(records, old_size);
return new_records;
}
static void prepare_to_log_stack(void) {
if (!stack_logging_the_record_list) {
unsigned totalSize = 4 * vm_page_size;
stack_logging_the_record_list = allocate_pages(totalSize);
memset(stack_logging_the_record_list, 0, sizeof(stack_logging_record_list_t));
stack_logging_the_record_list->overall_num_bytes = totalSize;
stack_logging_the_record_list->uniquing_table_num_pages = 128;
stack_logging_the_record_list->uniquing_table = allocate_pages(stack_logging_the_record_list->uniquing_table_num_pages * vm_page_size);
}
}
void stack_logging_log_stack(unsigned type, unsigned arg1, unsigned arg2, unsigned arg3, unsigned result, unsigned num_hot_to_skip) {
stack_logging_record_t *rec;
if (!stack_logging_enable_logging) return;
if (type & stack_logging_flag_zone) {
arg1 = arg2; arg2 = arg3; arg3 = 0; type &= ~stack_logging_flag_zone;
}
if (type & stack_logging_flag_calloc) {
arg1 *= arg2; arg2 = arg3; arg3 = 0; type &= ~stack_logging_flag_calloc;
}
if (type & stack_logging_flag_object) {
unsigned *class = (unsigned *)arg1;
arg1 = arg2 + class[5]; arg2 = 0; arg3 = 0; type = stack_logging_type_alloc;
}
if (type & stack_logging_flag_cleared) {
type &= ~stack_logging_flag_cleared;
}
if (type & stack_logging_flag_handle) {
if (stack_logging_type_alloc) {
if (!result) return;
stack_logging_log_stack(stack_logging_type_alloc, 0, 0, 0, result, num_hot_to_skip+1);
stack_logging_log_stack(stack_logging_type_alloc, arg1, 0, 0, *((int *)result), num_hot_to_skip+1);
return;
}
if (stack_logging_type_dealloc) {
if (!arg1) return;
stack_logging_log_stack(stack_logging_type_dealloc, *((int *)arg1), 0, 0, 0, num_hot_to_skip+1);
stack_logging_log_stack(stack_logging_type_dealloc, arg1, 0, 0, 0, num_hot_to_skip+1);
return;
}
fprintf(stderr, "*** Unknown logging type: 0x%x\n", type);
}
if (type == stack_logging_flag_set_handle_size) {
if (!arg1) return;
if (arg3 == *((int *)arg1)) return;
stack_logging_log_stack(stack_logging_type_dealloc, arg3, 0, 0, 0, num_hot_to_skip+1);
stack_logging_log_stack(stack_logging_type_alloc, arg2, 0, 0, *((int *)arg1), num_hot_to_skip+1);
return;
}
if (type == (stack_logging_type_dealloc|stack_logging_type_alloc)) {
if (arg1 == result) return; if (!arg1) {
type = stack_logging_type_alloc; arg1 = arg2; arg2 = arg3; arg3 = 0;
} else {
stack_logging_log_stack(stack_logging_type_dealloc, arg1, 0, 0, 0, num_hot_to_skip+1);
stack_logging_log_stack(stack_logging_type_alloc, arg2, 0, 0, result, num_hot_to_skip+1);
return;
}
}
if (type == stack_logging_type_dealloc) {
if (!arg1) return; }
prepare_to_log_stack();
spin_lock(&stack_logging_spin_lock);
stack_logging_the_record_list = GrowLogRecords(stack_logging_the_record_list, stack_logging_the_record_list->num_records + 1);
rec = stack_logging_the_record_list->records + stack_logging_the_record_list->num_records;
if (!stack_logging_dontcompact && stack_logging_the_record_list->num_records && (type == stack_logging_type_dealloc) && arg1 && ((rec-1)->type == stack_logging_type_alloc) && (arg1 == STACK_LOGGING_DISGUISE((rec-1)->address))) {
stack_logging_the_record_list->num_records--;
} else {
unsigned stack_entries[MAX_NUM_PC];
unsigned count = 0;
rec->type = type;
if (type == stack_logging_type_dealloc) {
rec->argument = 0;
rec->address = STACK_LOGGING_DISGUISE(arg1); } else if (type == stack_logging_type_alloc) {
rec->argument = arg1;
rec->address = STACK_LOGGING_DISGUISE(result); } else {
rec->argument = arg2;
rec->address = STACK_LOGGING_DISGUISE(arg1); }
thread_stack_pcs(stack_entries, MAX_NUM_PC - 1, &count);
stack_entries[count++] = (int)pthread_self() + 1;
rec->uniqued_stack = stack_logging_get_unique_stack(&stack_logging_the_record_list->uniquing_table, &stack_logging_the_record_list->uniquing_table_num_pages, stack_entries, count, num_hot_to_skip+2); stack_logging_the_record_list->num_records++;
}
spin_unlock(&stack_logging_spin_lock);
}
static kern_return_t default_reader(task_t task, vm_address_t address, vm_size_t size, void **ptr) {
*ptr = (void *)address;
return 0;
}
static kern_return_t get_remote_records(task_t task, memory_reader_t reader, stack_logging_record_list_t **records) {
vm_address_t *remote_records_address_ref;
kern_return_t err;
*records = NULL;
err = reader(task, (vm_address_t)&stack_logging_the_record_list, sizeof(vm_address_t), (void **)&remote_records_address_ref);
if (err) return err;
if (!*remote_records_address_ref) {
return 0;
}
err = reader(task, *remote_records_address_ref, sizeof(stack_logging_record_list_t), (void **)records); if (err) return err;
return reader(task, *remote_records_address_ref, (*records)->overall_num_bytes, (void **)records);
}
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) {
stack_logging_record_list_t *records;
kern_return_t err;
unsigned index;
unsigned disguised = STACK_LOGGING_DISGUISE(address);
if (!reader) reader = default_reader;
*num_frames = 0;
err = get_remote_records(task, reader, &records);
if (err || !records) return err;
index = 0;
while (index < records->num_records) {
stack_logging_record_t *record = records->records + index;
if (record->address == disguised) {
return stack_logging_frames_for_uniqued_stack(task, reader, record->uniqued_stack, stack_frames_buffer, max_stack_frames, num_frames);
}
index++;
}
fprintf(stderr, "*** stack_logging: no record found for 0x%x\n", address);
return 0;
}
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) {
stack_logging_record_list_t *records;
kern_return_t err;
unsigned index;
unsigned disguised = STACK_LOGGING_DISGUISE(address);
if (!reader) reader = default_reader;
err = get_remote_records(task, reader, &records);
if (err || !records) return err;
index = 0;
while (index < records->num_records) {
stack_logging_record_t *record = records->records + index;
if (!address || (record->address == disguised)) enumerator(*record, context);
index++;
}
return 0;
}
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) {
stack_logging_record_list_t *records;
unsigned *uniquing_table;
kern_return_t err;
if (!reader) reader = default_reader;
*num_frames = 0;
err = get_remote_records(task, reader, &records);
if (err || !records) return err;
err = reader(task, (vm_address_t)records->uniquing_table, records->uniquing_table_num_pages * vm_page_size, (void **)&uniquing_table);
if (err) return err;
while (max_stack_frames && (uniqued_stack != -1)) {
unsigned thisPC;
if ((uniqued_stack * 2 + 1) * sizeof(unsigned) >= records->uniquing_table_num_pages * vm_page_size) {
fprintf(stderr, "*** stack_logging: Invalid uniqued stack 0x%x", uniqued_stack);
break;
}
thisPC = uniquing_table[uniqued_stack * 2];
uniqued_stack = uniquing_table[uniqued_stack * 2 + 1];
if (!thisPC && !uniqued_stack) {
fprintf(stderr, "*** stack_logging: Invalid entry 0x%x", thisPC);
break;
}
stack_frames_buffer[0] = thisPC;
stack_frames_buffer++;
(*num_frames)++;
max_stack_frames--;
}
return 0;
}