]> git.saurik.com Git - apple/libc.git/blame - gen/stack_logging.c
Libc-825.24.tar.gz
[apple/libc.git] / gen / stack_logging.c
CommitLineData
e9ce8d39 1/*
34e8f829 2 * Copyright (c) 1999, 2000, 2002-2008 Apple Inc. All rights reserved.
e9ce8d39
A
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
734aad71
A
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
e9ce8d39
A
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
734aad71
A
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
e9ce8d39
A
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/* Bertrand from vmutils -> CF -> System */
25
26#import "stack_logging.h"
224c7076 27#import "malloc_printf.h"
e9ce8d39
A
28
29#import <libc.h>
30#import <pthread.h>
31#import <mach/mach.h>
32#include <mach/vm_statistics.h>
9385eb3d
A
33#import <malloc/malloc.h>
34#import <stdlib.h>
1f2f436a 35#import <CrashReporterClient.h>
e9ce8d39
A
36
37extern void spin_lock(int *);
224c7076
A
38extern void spin_unlock(int *);
39extern void thread_stack_pcs(vm_address_t *, unsigned, unsigned *);
e9ce8d39 40
3d9156a7 41static inline void *allocate_pages(unsigned) __attribute__((always_inline));
e9ce8d39
A
42static inline void *allocate_pages(unsigned bytes) {
43 void *address;
44 if (vm_allocate(mach_task_self(), (vm_address_t *)&address, bytes,
45 VM_MAKE_TAG(VM_MEMORY_ANALYSIS_TOOL)| TRUE)) {
59e0d9fe 46 malloc_printf("*** out of memory while stack logging\n");
1f2f436a 47 CRSetCrashLogMessage("*** out of memory while stack logging\n");
9385eb3d 48 abort();
e9ce8d39
A
49 }
50 return (void *)address;
51}
52
3d9156a7 53static inline void deallocate_pages(void *, unsigned) __attribute__((always_inline));
e9ce8d39
A
54static inline void deallocate_pages(void *ptr, unsigned bytes) {
55 vm_deallocate(mach_task_self(), (vm_address_t)ptr, bytes);
56}
57
3d9156a7 58static inline void copy_pages(const void *, void *, unsigned) __attribute__((always_inline));
e9ce8d39
A
59static inline void copy_pages(const void *source, void *dest, unsigned bytes) {
60 if (vm_copy(mach_task_self(), (vm_address_t)source, bytes, (vm_address_t)dest)) memmove(dest, source, bytes);
61}
62
e9ce8d39
A
63/*************** Uniquing stack ***********/
64
65#define MAX_COLLIDE 8
66
67#define MAX_NUM_PC 512
68
69static int enter_pair_in_table(unsigned *table, unsigned numPages, unsigned *uniquedParent, unsigned thisPC) {
70 // uniquedParent is in-out; return 1 is collisions max not exceeded
71 unsigned base = numPages * vm_page_size / (sizeof(int)*2*2);
72 unsigned hash = base + (((*uniquedParent) << 4) ^ (thisPC >> 2)) % (base - 1); // modulo odd number for hashing
73 unsigned collisions = MAX_COLLIDE;
74 while (collisions--) {
75 unsigned *head = table + hash*2;
76 if (! head[0] && !head[1]) {
77 /* end of chain; store this entry! */
78 /* Note that we need to test for both head[0] and head[1] as (0, -1) is a valid entry */
79 head[0] = thisPC;
80 head[1] = *uniquedParent;
81 *uniquedParent = hash;
82 return 1;
83 }
84 if ((head[0] == thisPC) && (head[1] == *uniquedParent)) {
85 /* we found the proper entry, the value for the pair is the entry offset */
86 *uniquedParent = hash;
87 return 1;
88 }
89 hash++;
90 if (hash == base*2) hash = base;
91 }
92 return 0;
93}
94
95unsigned stack_logging_get_unique_stack(unsigned **table, unsigned *table_num_pages, unsigned *stack_entries, unsigned count, unsigned num_hot_to_skip) {
96 unsigned uniquedParent = (unsigned)-1;
97 // we skip the warmest entries that are an artefact of the code
98 while (num_hot_to_skip--) {
99 if (count > 0) { stack_entries++; count--; }
100 }
101 while (count--) {
102 unsigned thisPC = stack_entries[count];
103 while (!enter_pair_in_table(*table, *table_num_pages, &uniquedParent, thisPC)) {
104 unsigned *newTable;
105 unsigned oldBytes = (*table_num_pages) * vm_page_size;
106 newTable = allocate_pages(oldBytes*2);
107 copy_pages(*table, newTable, oldBytes);
108 deallocate_pages(*table, oldBytes);
109 *table_num_pages *= 2;
110 *table = newTable;
111 }
112 }
113 return uniquedParent;
114}
115
116/*************** Logging stack and arguments ***********/
117
118stack_logging_record_list_t *stack_logging_the_record_list = NULL;
119
120int stack_logging_enable_logging = 0;
e9ce8d39 121int stack_logging_dontcompact = 0;
ad3c9f2a
A
122int stack_logging_finished_init = 0;
123int stack_logging_postponed = 0;
e9ce8d39 124
224c7076
A
125static int stack_logging_spin_lock = 0;
126
e9ce8d39
A
127static stack_logging_record_list_t *GrowLogRecords(stack_logging_record_list_t *records, unsigned desiredNumRecords) {
128 stack_logging_record_list_t *new_records;
129 unsigned old_size = records->overall_num_bytes;
130 if (desiredNumRecords*sizeof(stack_logging_record_t)+sizeof(stack_logging_record_list_t) < records->overall_num_bytes) return records;
131 records->overall_num_bytes += records->overall_num_bytes + vm_page_size; // in order to always get an even number of pages
132 new_records = allocate_pages(records->overall_num_bytes);
133 copy_pages(records, new_records, old_size);
134 deallocate_pages(records, old_size);
135 return new_records;
136}
137
138static void prepare_to_log_stack(void) {
139 if (!stack_logging_the_record_list) {
140 unsigned totalSize = 4 * vm_page_size;
141 stack_logging_the_record_list = allocate_pages(totalSize);
142 memset(stack_logging_the_record_list, 0, sizeof(stack_logging_record_list_t));
143 stack_logging_the_record_list->overall_num_bytes = totalSize;
144 stack_logging_the_record_list->uniquing_table_num_pages = 128;
145 stack_logging_the_record_list->uniquing_table = allocate_pages(stack_logging_the_record_list->uniquing_table_num_pages * vm_page_size);
146 }
147}
148
149void stack_logging_log_stack(unsigned type, unsigned arg1, unsigned arg2, unsigned arg3, unsigned result, unsigned num_hot_to_skip) {
150 stack_logging_record_t *rec;
151 if (!stack_logging_enable_logging) return;
152 // printf("stack_logging_log_stack 0x%x 0x%x 0x%x 0x%x -> 0x%x\n", type, arg1, arg2, arg3, result);
153 if (type & stack_logging_flag_zone) {
154 // just process it now and be done with it!
155 arg1 = arg2; arg2 = arg3; arg3 = 0; type &= ~stack_logging_flag_zone;
156 }
157 if (type & stack_logging_flag_calloc) {
158 // just process it now and be done with it!
159 arg1 *= arg2; arg2 = arg3; arg3 = 0; type &= ~stack_logging_flag_calloc;
160 }
161 if (type & stack_logging_flag_object) {
1f2f436a 162 unsigned *class = (unsigned *)(uintptr_t)arg1;
e9ce8d39
A
163 arg1 = arg2 + class[5]; // corresponds to the instance_size field
164 arg2 = 0; arg3 = 0; type = stack_logging_type_alloc;
165 }
166 if (type & stack_logging_flag_cleared) {
167 type &= ~stack_logging_flag_cleared;
168 }
169 if (type & stack_logging_flag_handle) {
170 if (stack_logging_type_alloc) {
171 if (!result) return;
172 stack_logging_log_stack(stack_logging_type_alloc, 0, 0, 0, result, num_hot_to_skip+1);
1f2f436a 173 stack_logging_log_stack(stack_logging_type_alloc, arg1, 0, 0, *((int *)(uintptr_t)result), num_hot_to_skip+1);
e9ce8d39
A
174 return;
175 }
176 if (stack_logging_type_dealloc) {
177 if (!arg1) return;
1f2f436a 178 stack_logging_log_stack(stack_logging_type_dealloc, *((int *)(uintptr_t)arg1), 0, 0, 0, num_hot_to_skip+1);
e9ce8d39
A
179 stack_logging_log_stack(stack_logging_type_dealloc, arg1, 0, 0, 0, num_hot_to_skip+1);
180 return;
181 }
224c7076 182 fprintf(stderr, "*** Unknown logging type: 0x%x\n", type);
e9ce8d39
A
183 }
184 if (type == stack_logging_flag_set_handle_size) {
185 if (!arg1) return;
186 // Thanks to a horrible hack, arg3 contains the prvious handle value
1f2f436a 187 if (arg3 == *((int *)(uintptr_t)arg1)) return;
e9ce8d39 188 stack_logging_log_stack(stack_logging_type_dealloc, arg3, 0, 0, 0, num_hot_to_skip+1);
1f2f436a 189 stack_logging_log_stack(stack_logging_type_alloc, arg2, 0, 0, *((int *)(uintptr_t)arg1), num_hot_to_skip+1);
e9ce8d39
A
190 return;
191 }
192 if (type == (stack_logging_type_dealloc|stack_logging_type_alloc)) {
193 if (arg1 == result) return; // realloc had no effect, skipping
194 if (!arg1) {
195 // realloc(NULL, size) same as malloc(size)
196 type = stack_logging_type_alloc; arg1 = arg2; arg2 = arg3; arg3 = 0;
197 } else {
198 // realloc(arg1, arg2) -> result is same as free(arg1); malloc(arg2) -> result
199 stack_logging_log_stack(stack_logging_type_dealloc, arg1, 0, 0, 0, num_hot_to_skip+1);
200 stack_logging_log_stack(stack_logging_type_alloc, arg2, 0, 0, result, num_hot_to_skip+1);
201 return;
202 }
203 }
204 if (type == stack_logging_type_dealloc) {
205 // simple free
206 if (!arg1) return; // free(nil)
207 }
208 prepare_to_log_stack();
224c7076 209 spin_lock(&stack_logging_spin_lock);
e9ce8d39
A
210 stack_logging_the_record_list = GrowLogRecords(stack_logging_the_record_list, stack_logging_the_record_list->num_records + 1);
211 rec = stack_logging_the_record_list->records + stack_logging_the_record_list->num_records;
212 // We take care of the common case of alloc-dealloc
213 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))) {
214 stack_logging_the_record_list->num_records--;
215 // printf("Erased previous record in alloc-dealloc sequence\n");
216 } else {
217 unsigned stack_entries[MAX_NUM_PC];
218 unsigned count = 0;
219 rec->type = type;
220 if (type == stack_logging_type_dealloc) {
221 rec->argument = 0;
222 rec->address = STACK_LOGGING_DISGUISE(arg1); // we disguise the address
223 } else if (type == stack_logging_type_alloc) {
224 rec->argument = arg1;
225 rec->address = STACK_LOGGING_DISGUISE(result); // we disguise the address
226 } else {
227 rec->argument = arg2;
228 rec->address = STACK_LOGGING_DISGUISE(arg1); // we disguise the address
229 }
230 // printf("Before getting samples 0x%x 0x%x 0x%x 0x%x -> 0x%x\n", type, arg1, arg2, arg3, result);
34e8f829 231 thread_stack_pcs((vm_address_t *)stack_entries, MAX_NUM_PC - 1, &count);
e9ce8d39 232 // We put at the bottom of the stack a marker that denotes the thread (+1 for good measure...)
1f2f436a 233 stack_entries[count++] = (int)(uintptr_t)pthread_self() + 1;
e9ce8d39
A
234 /* now let's unique the sample */
235 // printf("Uniquing 0x%x 0x%x 0x%x 0x%x -> 0x%x\n", type, arg1, arg2, arg3, result);
236 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); // we additionally skip the warmest 2 entries that are an artefact of the code
237 stack_logging_the_record_list->num_records++;
238 }
224c7076 239 spin_unlock(&stack_logging_spin_lock);
e9ce8d39
A
240}
241
242static kern_return_t default_reader(task_t task, vm_address_t address, vm_size_t size, void **ptr) {
243 *ptr = (void *)address;
244 return 0;
245}
246
247static kern_return_t get_remote_records(task_t task, memory_reader_t reader, stack_logging_record_list_t **records) {
248 // sets records
249 vm_address_t *remote_records_address_ref;
250 kern_return_t err;
251 *records = NULL;
252 err = reader(task, (vm_address_t)&stack_logging_the_record_list, sizeof(vm_address_t), (void **)&remote_records_address_ref);
253 if (err) return err;
254 if (!*remote_records_address_ref) {
255 // printf("stack_logging: no stack record\n");
256 return 0;
257 }
258 // printf("stack_logging: stack records at %p\n", (void *)(*remote_records_address_ref));
259 // printf("stack_logging: reading %d bytes\n", sizeof(stack_logging_record_list_t));
260 err = reader(task, *remote_records_address_ref, sizeof(stack_logging_record_list_t), (void **)records); // get the list head
261 if (err) return err;
262 // printf("stack_logging: overall num bytes = %d\n", records->overall_num_bytes);
263 return reader(task, *remote_records_address_ref, (*records)->overall_num_bytes, (void **)records);
264}
265
266kern_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) {
267 stack_logging_record_list_t *records;
268 kern_return_t err;
269 unsigned index;
270 unsigned disguised = STACK_LOGGING_DISGUISE(address);
271 if (!reader) reader = default_reader;
272 *num_frames = 0;
273 err = get_remote_records(task, reader, &records);
274 if (err || !records) return err;
275 // printf("stack_logging: %d records\n", records->num_records);
276 index = 0;
277 while (index < records->num_records) {
278 stack_logging_record_t *record = records->records + index;
279 if (record->address == disguised) {
280 return stack_logging_frames_for_uniqued_stack(task, reader, record->uniqued_stack, stack_frames_buffer, max_stack_frames, num_frames);
281 }
282 index++;
283 }
1f2f436a 284 fprintf(stderr, "*** stack_logging: no record found for %p\n", address);
e9ce8d39
A
285 return 0;
286}
287
288kern_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) {
289 stack_logging_record_list_t *records;
290 kern_return_t err;
291 unsigned index;
292 unsigned disguised = STACK_LOGGING_DISGUISE(address);
293 if (!reader) reader = default_reader;
294 err = get_remote_records(task, reader, &records);
295 if (err || !records) return err;
296 // printf("stack_logging: %d records\n", records->num_records);
297 index = 0;
298 while (index < records->num_records) {
299 stack_logging_record_t *record = records->records + index;
300 if (!address || (record->address == disguised)) enumerator(*record, context);
301 index++;
302 }
303 return 0;
304}
305
306kern_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) {
307 stack_logging_record_list_t *records;
308 unsigned *uniquing_table;
309 kern_return_t err;
310 if (!reader) reader = default_reader;
311 *num_frames = 0;
312 err = get_remote_records(task, reader, &records);
313 if (err || !records) return err;
314 err = reader(task, (vm_address_t)records->uniquing_table, records->uniquing_table_num_pages * vm_page_size, (void **)&uniquing_table);
315 if (err) return err;
316 while (max_stack_frames && (uniqued_stack != -1)) {
317 unsigned thisPC;
318 if ((uniqued_stack * 2 + 1) * sizeof(unsigned) >= records->uniquing_table_num_pages * vm_page_size) {
319 fprintf(stderr, "*** stack_logging: Invalid uniqued stack 0x%x", uniqued_stack);
320 break;
321 }
322 thisPC = uniquing_table[uniqued_stack * 2];
323 uniqued_stack = uniquing_table[uniqued_stack * 2 + 1];
324 if (!thisPC && !uniqued_stack) {
325 // Invalid entry
326 fprintf(stderr, "*** stack_logging: Invalid entry 0x%x", thisPC);
327 break;
328 }
329 stack_frames_buffer[0] = thisPC;
330 stack_frames_buffer++;
331 (*num_frames)++;
332 max_stack_frames--;
333 }
334 return 0;
335}