]>
Commit | Line | Data |
---|---|---|
e9ce8d39 A |
1 | /* |
2 | * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. | |
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" | |
27 | ||
28 | #import <libc.h> | |
29 | #import <pthread.h> | |
30 | #import <mach/mach.h> | |
31 | #include <mach/vm_statistics.h> | |
9385eb3d A |
32 | #import <malloc/malloc.h> |
33 | #import <stdlib.h> | |
e9ce8d39 A |
34 | |
35 | extern void spin_lock(int *); | |
36 | ||
3d9156a7 | 37 | static inline void *allocate_pages(unsigned) __attribute__((always_inline)); |
e9ce8d39 A |
38 | static inline void *allocate_pages(unsigned bytes) { |
39 | void *address; | |
40 | if (vm_allocate(mach_task_self(), (vm_address_t *)&address, bytes, | |
41 | VM_MAKE_TAG(VM_MEMORY_ANALYSIS_TOOL)| TRUE)) { | |
59e0d9fe | 42 | malloc_printf("*** out of memory while stack logging\n"); |
9385eb3d | 43 | abort(); |
e9ce8d39 A |
44 | } |
45 | return (void *)address; | |
46 | } | |
47 | ||
3d9156a7 | 48 | static inline void deallocate_pages(void *, unsigned) __attribute__((always_inline)); |
e9ce8d39 A |
49 | static inline void deallocate_pages(void *ptr, unsigned bytes) { |
50 | vm_deallocate(mach_task_self(), (vm_address_t)ptr, bytes); | |
51 | } | |
52 | ||
3d9156a7 | 53 | static inline void copy_pages(const void *, void *, unsigned) __attribute__((always_inline)); |
e9ce8d39 A |
54 | static inline void copy_pages(const void *source, void *dest, unsigned bytes) { |
55 | if (vm_copy(mach_task_self(), (vm_address_t)source, bytes, (vm_address_t)dest)) memmove(dest, source, bytes); | |
56 | } | |
57 | ||
58 | /*************** Recording stack ***********/ | |
59 | ||
60 | static void *first_frame_address(void) { | |
59e0d9fe | 61 | #if defined(__i386__) |
e9ce8d39 | 62 | return __builtin_frame_address(1); |
59e0d9fe | 63 | #elif defined(__ppc__) || defined(__ppc64__) |
e9ce8d39 A |
64 | void *addr; |
65 | #warning __builtin_frame_address IS BROKEN IN BEAKER: RADAR #2340421 | |
66 | __asm__ volatile("mr %0, r1" : "=r" (addr)); | |
67 | return addr; | |
68 | #else | |
69 | #warning first_frame_address WILL NOT BE FUNCTIONAL ON THIS ARCHITECTURE | |
70 | return NULL; | |
71 | #endif | |
72 | } | |
73 | ||
74 | static void *next_frame_address(void *addr) { | |
75 | void *ret; | |
76 | #if defined(__MACH__) && defined(__i386__) | |
77 | __asm__ volatile("movl (%1),%0" : "=r" (ret) : "r" (addr)); | |
59e0d9fe | 78 | #elif defined(__MACH__) && (defined(__ppc__) || defined(__ppc64__)) |
5b2abdfb | 79 | __asm__ volatile("lwz %0,0x0(%1)" : "=r" (ret) : "b" (addr)); |
e9ce8d39 A |
80 | #elif defined(__hpux__) |
81 | __asm__ volatile("ldw 0x0(%1),%0" : "=r" (ret) : "r" (addr)); | |
82 | #elif defined(__svr4__) | |
83 | __asm__ volatile("ta 0x3"); | |
84 | __asm__ volatile("ld [%1 + 56],%0" : "=r" (ret) : "r" (addr)); | |
85 | #else | |
86 | #error Unknown architecture | |
87 | #endif | |
88 | return ret; | |
89 | } | |
90 | ||
91 | #if defined(__i386__) || defined (__m68k__) | |
92 | #define FP_LINK_OFFSET 1 | |
59e0d9fe | 93 | #elif defined(__ppc__) || defined(__ppc64__) |
e9ce8d39 A |
94 | #define FP_LINK_OFFSET 2 |
95 | #elif defined(__hppa__) | |
96 | #define FP_LINK_OFFSET -5 | |
97 | #elif defined(__sparc__) | |
98 | #define FP_LINK_OFFSET 14 | |
99 | #else | |
100 | #error ********** Unimplemented architecture | |
101 | #endif | |
102 | ||
103 | void thread_stack_pcs(vm_address_t *buffer, unsigned max, unsigned *nb) { | |
104 | void *addr; | |
105 | addr = first_frame_address(); | |
106 | *nb = 0; | |
107 | while ((addr >= (void *)0x800) && (max--)) { | |
108 | vm_address_t fp_link = (vm_address_t)(((unsigned *)addr)+FP_LINK_OFFSET); | |
109 | void *addr2; | |
110 | buffer[*nb] = *((vm_address_t *)fp_link); | |
111 | (*nb)++; | |
112 | addr2 = next_frame_address(addr); | |
59e0d9fe | 113 | #if defined(__ppc__) || defined(__ppc64__) |
e9ce8d39 A |
114 | if ((unsigned)addr2 <= (unsigned)addr) break; // catch bozo frames |
115 | #endif | |
116 | addr = addr2; | |
117 | } | |
118 | } | |
119 | ||
120 | /*************** Uniquing stack ***********/ | |
121 | ||
122 | #define MAX_COLLIDE 8 | |
123 | ||
124 | #define MAX_NUM_PC 512 | |
125 | ||
126 | static int enter_pair_in_table(unsigned *table, unsigned numPages, unsigned *uniquedParent, unsigned thisPC) { | |
127 | // uniquedParent is in-out; return 1 is collisions max not exceeded | |
128 | unsigned base = numPages * vm_page_size / (sizeof(int)*2*2); | |
129 | unsigned hash = base + (((*uniquedParent) << 4) ^ (thisPC >> 2)) % (base - 1); // modulo odd number for hashing | |
130 | unsigned collisions = MAX_COLLIDE; | |
131 | while (collisions--) { | |
132 | unsigned *head = table + hash*2; | |
133 | if (! head[0] && !head[1]) { | |
134 | /* end of chain; store this entry! */ | |
135 | /* Note that we need to test for both head[0] and head[1] as (0, -1) is a valid entry */ | |
136 | head[0] = thisPC; | |
137 | head[1] = *uniquedParent; | |
138 | *uniquedParent = hash; | |
139 | return 1; | |
140 | } | |
141 | if ((head[0] == thisPC) && (head[1] == *uniquedParent)) { | |
142 | /* we found the proper entry, the value for the pair is the entry offset */ | |
143 | *uniquedParent = hash; | |
144 | return 1; | |
145 | } | |
146 | hash++; | |
147 | if (hash == base*2) hash = base; | |
148 | } | |
149 | return 0; | |
150 | } | |
151 | ||
152 | unsigned stack_logging_get_unique_stack(unsigned **table, unsigned *table_num_pages, unsigned *stack_entries, unsigned count, unsigned num_hot_to_skip) { | |
153 | unsigned uniquedParent = (unsigned)-1; | |
154 | // we skip the warmest entries that are an artefact of the code | |
155 | while (num_hot_to_skip--) { | |
156 | if (count > 0) { stack_entries++; count--; } | |
157 | } | |
158 | while (count--) { | |
159 | unsigned thisPC = stack_entries[count]; | |
160 | while (!enter_pair_in_table(*table, *table_num_pages, &uniquedParent, thisPC)) { | |
161 | unsigned *newTable; | |
162 | unsigned oldBytes = (*table_num_pages) * vm_page_size; | |
163 | newTable = allocate_pages(oldBytes*2); | |
164 | copy_pages(*table, newTable, oldBytes); | |
165 | deallocate_pages(*table, oldBytes); | |
166 | *table_num_pages *= 2; | |
167 | *table = newTable; | |
168 | } | |
169 | } | |
170 | return uniquedParent; | |
171 | } | |
172 | ||
173 | /*************** Logging stack and arguments ***********/ | |
174 | ||
175 | stack_logging_record_list_t *stack_logging_the_record_list = NULL; | |
176 | ||
177 | int stack_logging_enable_logging = 0; | |
178 | ||
179 | int stack_logging_dontcompact = 0; | |
180 | ||
181 | static stack_logging_record_list_t *GrowLogRecords(stack_logging_record_list_t *records, unsigned desiredNumRecords) { | |
182 | stack_logging_record_list_t *new_records; | |
183 | unsigned old_size = records->overall_num_bytes; | |
184 | if (desiredNumRecords*sizeof(stack_logging_record_t)+sizeof(stack_logging_record_list_t) < records->overall_num_bytes) return records; | |
185 | records->overall_num_bytes += records->overall_num_bytes + vm_page_size; // in order to always get an even number of pages | |
186 | new_records = allocate_pages(records->overall_num_bytes); | |
187 | copy_pages(records, new_records, old_size); | |
188 | deallocate_pages(records, old_size); | |
189 | return new_records; | |
190 | } | |
191 | ||
192 | static void prepare_to_log_stack(void) { | |
193 | if (!stack_logging_the_record_list) { | |
194 | unsigned totalSize = 4 * vm_page_size; | |
195 | stack_logging_the_record_list = allocate_pages(totalSize); | |
196 | memset(stack_logging_the_record_list, 0, sizeof(stack_logging_record_list_t)); | |
197 | stack_logging_the_record_list->overall_num_bytes = totalSize; | |
198 | stack_logging_the_record_list->uniquing_table_num_pages = 128; | |
199 | stack_logging_the_record_list->uniquing_table = allocate_pages(stack_logging_the_record_list->uniquing_table_num_pages * vm_page_size); | |
200 | } | |
201 | } | |
202 | ||
203 | void stack_logging_log_stack(unsigned type, unsigned arg1, unsigned arg2, unsigned arg3, unsigned result, unsigned num_hot_to_skip) { | |
204 | stack_logging_record_t *rec; | |
205 | if (!stack_logging_enable_logging) return; | |
206 | // printf("stack_logging_log_stack 0x%x 0x%x 0x%x 0x%x -> 0x%x\n", type, arg1, arg2, arg3, result); | |
207 | if (type & stack_logging_flag_zone) { | |
208 | // just process it now and be done with it! | |
209 | arg1 = arg2; arg2 = arg3; arg3 = 0; type &= ~stack_logging_flag_zone; | |
210 | } | |
211 | if (type & stack_logging_flag_calloc) { | |
212 | // just process it now and be done with it! | |
213 | arg1 *= arg2; arg2 = arg3; arg3 = 0; type &= ~stack_logging_flag_calloc; | |
214 | } | |
215 | if (type & stack_logging_flag_object) { | |
216 | unsigned *class = (unsigned *)arg1; | |
217 | arg1 = arg2 + class[5]; // corresponds to the instance_size field | |
218 | arg2 = 0; arg3 = 0; type = stack_logging_type_alloc; | |
219 | } | |
220 | if (type & stack_logging_flag_cleared) { | |
221 | type &= ~stack_logging_flag_cleared; | |
222 | } | |
223 | if (type & stack_logging_flag_handle) { | |
224 | if (stack_logging_type_alloc) { | |
225 | if (!result) return; | |
226 | stack_logging_log_stack(stack_logging_type_alloc, 0, 0, 0, result, num_hot_to_skip+1); | |
227 | stack_logging_log_stack(stack_logging_type_alloc, arg1, 0, 0, *((int *)result), num_hot_to_skip+1); | |
228 | return; | |
229 | } | |
230 | if (stack_logging_type_dealloc) { | |
231 | if (!arg1) return; | |
232 | stack_logging_log_stack(stack_logging_type_dealloc, *((int *)arg1), 0, 0, 0, num_hot_to_skip+1); | |
233 | stack_logging_log_stack(stack_logging_type_dealloc, arg1, 0, 0, 0, num_hot_to_skip+1); | |
234 | return; | |
235 | } | |
236 | printf("*** Unknown logging type: 0x%x\n", type); | |
237 | } | |
238 | if (type == stack_logging_flag_set_handle_size) { | |
239 | if (!arg1) return; | |
240 | // Thanks to a horrible hack, arg3 contains the prvious handle value | |
241 | if (arg3 == *((int *)arg1)) return; | |
242 | stack_logging_log_stack(stack_logging_type_dealloc, arg3, 0, 0, 0, num_hot_to_skip+1); | |
243 | stack_logging_log_stack(stack_logging_type_alloc, arg2, 0, 0, *((int *)arg1), num_hot_to_skip+1); | |
244 | return; | |
245 | } | |
246 | if (type == (stack_logging_type_dealloc|stack_logging_type_alloc)) { | |
247 | if (arg1 == result) return; // realloc had no effect, skipping | |
248 | if (!arg1) { | |
249 | // realloc(NULL, size) same as malloc(size) | |
250 | type = stack_logging_type_alloc; arg1 = arg2; arg2 = arg3; arg3 = 0; | |
251 | } else { | |
252 | // realloc(arg1, arg2) -> result is same as free(arg1); malloc(arg2) -> result | |
253 | stack_logging_log_stack(stack_logging_type_dealloc, arg1, 0, 0, 0, num_hot_to_skip+1); | |
254 | stack_logging_log_stack(stack_logging_type_alloc, arg2, 0, 0, result, num_hot_to_skip+1); | |
255 | return; | |
256 | } | |
257 | } | |
258 | if (type == stack_logging_type_dealloc) { | |
259 | // simple free | |
260 | if (!arg1) return; // free(nil) | |
261 | } | |
262 | prepare_to_log_stack(); | |
263 | spin_lock(&stack_logging_the_record_list->lock); | |
264 | stack_logging_enable_logging = 0; | |
265 | stack_logging_the_record_list = GrowLogRecords(stack_logging_the_record_list, stack_logging_the_record_list->num_records + 1); | |
266 | rec = stack_logging_the_record_list->records + stack_logging_the_record_list->num_records; | |
267 | // We take care of the common case of alloc-dealloc | |
268 | 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))) { | |
269 | stack_logging_the_record_list->num_records--; | |
270 | // printf("Erased previous record in alloc-dealloc sequence\n"); | |
271 | } else { | |
272 | unsigned stack_entries[MAX_NUM_PC]; | |
273 | unsigned count = 0; | |
274 | rec->type = type; | |
275 | if (type == stack_logging_type_dealloc) { | |
276 | rec->argument = 0; | |
277 | rec->address = STACK_LOGGING_DISGUISE(arg1); // we disguise the address | |
278 | } else if (type == stack_logging_type_alloc) { | |
279 | rec->argument = arg1; | |
280 | rec->address = STACK_LOGGING_DISGUISE(result); // we disguise the address | |
281 | } else { | |
282 | rec->argument = arg2; | |
283 | rec->address = STACK_LOGGING_DISGUISE(arg1); // we disguise the address | |
284 | } | |
285 | // printf("Before getting samples 0x%x 0x%x 0x%x 0x%x -> 0x%x\n", type, arg1, arg2, arg3, result); | |
286 | thread_stack_pcs(stack_entries, MAX_NUM_PC - 1, &count); | |
287 | // We put at the bottom of the stack a marker that denotes the thread (+1 for good measure...) | |
288 | stack_entries[count++] = (int)pthread_self() + 1; | |
289 | /* now let's unique the sample */ | |
290 | // printf("Uniquing 0x%x 0x%x 0x%x 0x%x -> 0x%x\n", type, arg1, arg2, arg3, result); | |
291 | 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 | |
292 | stack_logging_the_record_list->num_records++; | |
293 | } | |
294 | stack_logging_enable_logging = 1; | |
295 | stack_logging_the_record_list->lock = 0; | |
296 | } | |
297 | ||
298 | static kern_return_t default_reader(task_t task, vm_address_t address, vm_size_t size, void **ptr) { | |
299 | *ptr = (void *)address; | |
300 | return 0; | |
301 | } | |
302 | ||
303 | static kern_return_t get_remote_records(task_t task, memory_reader_t reader, stack_logging_record_list_t **records) { | |
304 | // sets records | |
305 | vm_address_t *remote_records_address_ref; | |
306 | kern_return_t err; | |
307 | *records = NULL; | |
308 | err = reader(task, (vm_address_t)&stack_logging_the_record_list, sizeof(vm_address_t), (void **)&remote_records_address_ref); | |
309 | if (err) return err; | |
310 | if (!*remote_records_address_ref) { | |
311 | // printf("stack_logging: no stack record\n"); | |
312 | return 0; | |
313 | } | |
314 | // printf("stack_logging: stack records at %p\n", (void *)(*remote_records_address_ref)); | |
315 | // printf("stack_logging: reading %d bytes\n", sizeof(stack_logging_record_list_t)); | |
316 | err = reader(task, *remote_records_address_ref, sizeof(stack_logging_record_list_t), (void **)records); // get the list head | |
317 | if (err) return err; | |
318 | // printf("stack_logging: overall num bytes = %d\n", records->overall_num_bytes); | |
319 | return reader(task, *remote_records_address_ref, (*records)->overall_num_bytes, (void **)records); | |
320 | } | |
321 | ||
322 | 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) { | |
323 | stack_logging_record_list_t *records; | |
324 | kern_return_t err; | |
325 | unsigned index; | |
326 | unsigned disguised = STACK_LOGGING_DISGUISE(address); | |
327 | if (!reader) reader = default_reader; | |
328 | *num_frames = 0; | |
329 | err = get_remote_records(task, reader, &records); | |
330 | if (err || !records) return err; | |
331 | // printf("stack_logging: %d records\n", records->num_records); | |
332 | index = 0; | |
333 | while (index < records->num_records) { | |
334 | stack_logging_record_t *record = records->records + index; | |
335 | if (record->address == disguised) { | |
336 | return stack_logging_frames_for_uniqued_stack(task, reader, record->uniqued_stack, stack_frames_buffer, max_stack_frames, num_frames); | |
337 | } | |
338 | index++; | |
339 | } | |
340 | fprintf(stderr, "*** stack_logging: no record found for 0x%x\n", address); | |
341 | return 0; | |
342 | } | |
343 | ||
344 | 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) { | |
345 | stack_logging_record_list_t *records; | |
346 | kern_return_t err; | |
347 | unsigned index; | |
348 | unsigned disguised = STACK_LOGGING_DISGUISE(address); | |
349 | if (!reader) reader = default_reader; | |
350 | err = get_remote_records(task, reader, &records); | |
351 | if (err || !records) return err; | |
352 | // printf("stack_logging: %d records\n", records->num_records); | |
353 | index = 0; | |
354 | while (index < records->num_records) { | |
355 | stack_logging_record_t *record = records->records + index; | |
356 | if (!address || (record->address == disguised)) enumerator(*record, context); | |
357 | index++; | |
358 | } | |
359 | return 0; | |
360 | } | |
361 | ||
362 | 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) { | |
363 | stack_logging_record_list_t *records; | |
364 | unsigned *uniquing_table; | |
365 | kern_return_t err; | |
366 | if (!reader) reader = default_reader; | |
367 | *num_frames = 0; | |
368 | err = get_remote_records(task, reader, &records); | |
369 | if (err || !records) return err; | |
370 | err = reader(task, (vm_address_t)records->uniquing_table, records->uniquing_table_num_pages * vm_page_size, (void **)&uniquing_table); | |
371 | if (err) return err; | |
372 | while (max_stack_frames && (uniqued_stack != -1)) { | |
373 | unsigned thisPC; | |
374 | if ((uniqued_stack * 2 + 1) * sizeof(unsigned) >= records->uniquing_table_num_pages * vm_page_size) { | |
375 | fprintf(stderr, "*** stack_logging: Invalid uniqued stack 0x%x", uniqued_stack); | |
376 | break; | |
377 | } | |
378 | thisPC = uniquing_table[uniqued_stack * 2]; | |
379 | uniqued_stack = uniquing_table[uniqued_stack * 2 + 1]; | |
380 | if (!thisPC && !uniqued_stack) { | |
381 | // Invalid entry | |
382 | fprintf(stderr, "*** stack_logging: Invalid entry 0x%x", thisPC); | |
383 | break; | |
384 | } | |
385 | stack_frames_buffer[0] = thisPC; | |
386 | stack_frames_buffer++; | |
387 | (*num_frames)++; | |
388 | max_stack_frames--; | |
389 | } | |
390 | return 0; | |
391 | } |