1 /* macho.c -- Get debug data from an Mach-O file for backtraces.
2 Copyright (C) 2012-2016 Free Software Foundation, Inc.
3 Written by John Colanduoni.
4
5 Pending upstream pull request:
6 https://github.com/ianlancetaylor/libbacktrace/pull/2
7
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions are
10 met:
11
12 (1) Redistributions of source code must retain the above copyright
13 notice, this list of conditions and the following disclaimer.
14
15 (2) Redistributions in binary form must reproduce the above copyright
16 notice, this list of conditions and the following disclaimer in
17 the documentation and/or other materials provided with the
18 distribution.
19
20 (3) The name of the author may not be used to
21 endorse or promote products derived from this software without
22 specific prior written permission.
23
24 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
25 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27 DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
28 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
33 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 POSSIBILITY OF SUCH DAMAGE. */
35
36 #include "config.h"
37
38 /* We can't use autotools to detect the pointer width of our program because
39 we may be building a fat Mach-O file containing both 32-bit and 64-bit
40 variants. However Mach-O runs a limited set of platforms so detection
41 via preprocessor is not difficult. */
42
43 #if defined(__MACH__)
44 #if defined(__LP64__)
45 #define BACKTRACE_BITS 64
46 #else
47 #define BACKTRACE_BITS 32
48 #endif
49 #else
50 #error Attempting to build Mach-O support on incorrect platform
51 #endif
52
53 #if defined(__x86_64__)
54 #define NATIVE_CPU_TYPE CPU_TYPE_X86_64
55 #elif defined(__i386__)
56 #define NATIVE_CPU_TYPE CPU_TYPE_X86
57 #elif defined(__aarch64__)
58 #define NATIVE_CPU_TYPE CPU_TYPE_ARM64
59 #elif defined(__arm__)
60 #define NATIVE_CPU_TYPE CPU_TYPE_ARM
61 #else
62 #error Could not detect native Mach-O cpu_type_t
63 #endif
64
65 #include <sys/types.h>
66 #include <sys/syslimits.h>
67 #include <string.h>
68 #include <mach-o/loader.h>
69 #include <mach-o/nlist.h>
70 #include <mach-o/fat.h>
71 #include <mach-o/dyld.h>
72 #include <uuid/uuid.h>
73 #include <dirent.h>
74 #include <stdlib.h>
75
76 #include "backtrace.h"
77 #include "internal.h"
78
79 struct macho_commands_view
80 {
81 struct backtrace_view view;
82 uint32_t commands_count;
83 uint32_t commands_total_size;
84 int bytes_swapped;
85 size_t base_offset;
86 };
87
88 enum debug_section
89 {
90 DEBUG_INFO,
91 DEBUG_LINE,
92 DEBUG_ABBREV,
93 DEBUG_RANGES,
94 DEBUG_STR,
95 DEBUG_MAX
96 };
97
98 static const char *const debug_section_names[DEBUG_MAX] =
99 {
100 "__debug_info",
101 "__debug_line",
102 "__debug_abbrev",
103 "__debug_ranges",
104 "__debug_str"
105 };
106
107 struct found_dwarf_section
108 {
109 uint32_t file_offset;
110 uintptr_t file_size;
111 const unsigned char *data;
112 };
113
114 /* Mach-O symbols don't have a length. As a result we have to infer it
115 by sorting the symbol addresses for each image and recording the
116 memory range attributed to each image. */
117 struct macho_symbol
118 {
119 uintptr_t addr;
120 size_t size;
121 const char *name;
122 };
123
124 struct macho_syminfo_data
125 {
126 struct macho_syminfo_data *next;
127 struct macho_symbol *symbols;
128 size_t symbol_count;
129 uintptr_t min_addr;
130 uintptr_t max_addr;
131 };
132
133 uint16_t
macho_file_to_host_u16(int file_bytes_swapped,uint16_t input)134 macho_file_to_host_u16 (int file_bytes_swapped, uint16_t input)
135 {
136 if (file_bytes_swapped)
137 return (input >> 8) | (input << 8);
138 else
139 return input;
140 }
141
142 uint32_t
macho_file_to_host_u32(int file_bytes_swapped,uint32_t input)143 macho_file_to_host_u32 (int file_bytes_swapped, uint32_t input)
144 {
145 if (file_bytes_swapped)
146 {
147 return ((input >> 24) & 0x000000FF)
148 | ((input >> 8) & 0x0000FF00)
149 | ((input << 8) & 0x00FF0000)
150 | ((input << 24) & 0xFF000000);
151 }
152 else
153 {
154 return input;
155 }
156 }
157
158 uint64_t
macho_file_to_host_u64(int file_bytes_swapped,uint64_t input)159 macho_file_to_host_u64 (int file_bytes_swapped, uint64_t input)
160 {
161 if (file_bytes_swapped)
162 {
163 return macho_file_to_host_u32 (file_bytes_swapped,
164 (uint32_t) (input >> 32))
165 | (((uint64_t) macho_file_to_host_u32 (file_bytes_swapped,
166 (uint32_t) input)) << 32);
167 }
168 else
169 {
170 return input;
171 }
172 }
173
174 #if BACKTRACE_BITS == 64
175 #define macho_file_to_host_usize macho_file_to_host_u64
176 typedef struct mach_header_64 mach_header_native_t;
177 #define LC_SEGMENT_NATIVE LC_SEGMENT_64
178 typedef struct segment_command_64 segment_command_native_t;
179 typedef struct nlist_64 nlist_native_t;
180 typedef struct section_64 section_native_t;
181 #else /* BACKTRACE_BITS == 32 */
182 #define macho_file_to_host_usize macho_file_to_host_u32
183 typedef struct mach_header mach_header_native_t;
184 #define LC_SEGMENT_NATIVE LC_SEGMENT
185 typedef struct segment_command segment_command_native_t;
186 typedef struct nlist nlist_native_t;
187 typedef struct section section_native_t;
188 #endif
189
190 // Gets a view into a Mach-O image, taking any slice offset into account
191 int
macho_get_view(struct backtrace_state * state,int descriptor,off_t offset,size_t size,backtrace_error_callback error_callback,void * data,struct macho_commands_view * commands_view,struct backtrace_view * view)192 macho_get_view (struct backtrace_state *state, int descriptor,
193 off_t offset, size_t size,
194 backtrace_error_callback error_callback,
195 void *data, struct macho_commands_view *commands_view,
196 struct backtrace_view *view)
197 {
198 return backtrace_get_view (state, descriptor,
199 commands_view->base_offset + offset, size,
200 error_callback, data, view);
201 }
202
203 int
macho_get_commands(struct backtrace_state * state,int descriptor,backtrace_error_callback error_callback,void * data,struct macho_commands_view * commands_view,int * incompatible)204 macho_get_commands (struct backtrace_state *state, int descriptor,
205 backtrace_error_callback error_callback,
206 void *data, struct macho_commands_view *commands_view,
207 int *incompatible)
208 {
209 int ret = 0;
210 int is_fat = 0;
211 struct backtrace_view file_header_view;
212 int file_header_view_valid = 0;
213 struct backtrace_view fat_archs_view;
214 int fat_archs_view_valid = 0;
215 const mach_header_native_t *file_header;
216 uint64_t commands_offset;
217
218 *incompatible = 0;
219
220 if (!backtrace_get_view (state, descriptor, 0, sizeof (mach_header_native_t),
221 error_callback, data, &file_header_view))
222 goto end;
223 file_header_view_valid = 1;
224
225 switch (*(uint32_t *) file_header_view.data)
226 {
227 case MH_MAGIC:
228 if (BACKTRACE_BITS == 32)
229 commands_view->bytes_swapped = 0;
230 else
231 {
232 *incompatible = 1;
233 goto end;
234 }
235 break;
236 case MH_CIGAM:
237 if (BACKTRACE_BITS == 32)
238 commands_view->bytes_swapped = 1;
239 else
240 {
241 *incompatible = 1;
242 goto end;
243 }
244 break;
245 case MH_MAGIC_64:
246 if (BACKTRACE_BITS == 64)
247 commands_view->bytes_swapped = 0;
248 else
249 {
250 *incompatible = 1;
251 goto end;
252 }
253 break;
254 case MH_CIGAM_64:
255 if (BACKTRACE_BITS == 64)
256 commands_view->bytes_swapped = 1;
257 else
258 {
259 *incompatible = 1;
260 goto end;
261 }
262 break;
263 case FAT_MAGIC:
264 is_fat = 1;
265 commands_view->bytes_swapped = 0;
266 break;
267 case FAT_CIGAM:
268 is_fat = 1;
269 commands_view->bytes_swapped = 1;
270 break;
271 default:
272 goto end;
273 }
274
275 if (is_fat)
276 {
277 uint32_t native_slice_offset;
278 size_t archs_total_size;
279 uint32_t arch_count;
280 const struct fat_header *fat_header;
281 const struct fat_arch *archs;
282 uint32_t i;
283
284 fat_header = file_header_view.data;
285 arch_count =
286 macho_file_to_host_u32 (commands_view->bytes_swapped,
287 fat_header->nfat_arch);
288
289 archs_total_size = arch_count * sizeof (struct fat_arch);
290
291 if (!backtrace_get_view (state, descriptor, sizeof (struct fat_header),
292 archs_total_size, error_callback,
293 data, &fat_archs_view))
294 goto end;
295 fat_archs_view_valid = 1;
296
297 native_slice_offset = 0;
298 archs = fat_archs_view.data;
299 for (i = 0; i < arch_count; i++)
300 {
301 const struct fat_arch *raw_arch = archs + i;
302 int cpu_type =
303 (int) macho_file_to_host_u32 (commands_view->bytes_swapped,
304 (uint32_t) raw_arch->cputype);
305
306 if (cpu_type == NATIVE_CPU_TYPE)
307 {
308 native_slice_offset =
309 macho_file_to_host_u32 (commands_view->bytes_swapped,
310 raw_arch->offset);
311
312 break;
313 }
314 }
315
316 if (native_slice_offset == 0)
317 {
318 *incompatible = 1;
319 goto end;
320 }
321
322 backtrace_release_view (state, &file_header_view, error_callback, data);
323 file_header_view_valid = 0;
324 if (!backtrace_get_view (state, descriptor, native_slice_offset,
325 sizeof (mach_header_native_t), error_callback,
326 data, &file_header_view))
327 goto end;
328 file_header_view_valid = 1;
329
330 // The endianess of the slice may be different than the fat image
331 switch (*(uint32_t *) file_header_view.data)
332 {
333 case MH_MAGIC:
334 if (BACKTRACE_BITS == 32)
335 commands_view->bytes_swapped = 0;
336 else
337 goto end;
338 break;
339 case MH_CIGAM:
340 if (BACKTRACE_BITS == 32)
341 commands_view->bytes_swapped = 1;
342 else
343 goto end;
344 break;
345 case MH_MAGIC_64:
346 if (BACKTRACE_BITS == 64)
347 commands_view->bytes_swapped = 0;
348 else
349 goto end;
350 break;
351 case MH_CIGAM_64:
352 if (BACKTRACE_BITS == 64)
353 commands_view->bytes_swapped = 1;
354 else
355 goto end;
356 break;
357 default:
358 goto end;
359 }
360
361 commands_view->base_offset = native_slice_offset;
362 }
363 else
364 commands_view->base_offset = 0;
365
366 file_header = file_header_view.data;
367 commands_view->commands_count =
368 macho_file_to_host_u32 (commands_view->bytes_swapped,
369 file_header->ncmds);
370 commands_view->commands_total_size =
371 macho_file_to_host_u32 (commands_view->bytes_swapped,
372 file_header->sizeofcmds);
373 commands_offset =
374 commands_view->base_offset + sizeof (mach_header_native_t);
375
376 if (!backtrace_get_view (state, descriptor, commands_offset,
377 commands_view->commands_total_size, error_callback,
378 data, &commands_view->view))
379 goto end;
380
381 ret = 1;
382
383 end:
384 if (file_header_view_valid)
385 backtrace_release_view (state, &file_header_view, error_callback, data);
386 if (fat_archs_view_valid)
387 backtrace_release_view (state, &fat_archs_view, error_callback, data);
388 return ret;
389 }
390
391 int
macho_get_uuid(struct backtrace_state * state ATTRIBUTE_UNUSED,int descriptor ATTRIBUTE_UNUSED,backtrace_error_callback error_callback,void * data,struct macho_commands_view * commands_view,uuid_t * uuid)392 macho_get_uuid (struct backtrace_state *state ATTRIBUTE_UNUSED,
393 int descriptor ATTRIBUTE_UNUSED,
394 backtrace_error_callback error_callback,
395 void *data, struct macho_commands_view *commands_view,
396 uuid_t *uuid)
397 {
398 size_t offset = 0;
399 uint32_t i = 0;
400
401 for (i = 0; i < commands_view->commands_count; i++)
402 {
403 const struct load_command *raw_command;
404 struct load_command command;
405
406 if (offset + sizeof (struct load_command)
407 > commands_view->commands_total_size)
408 {
409 error_callback (data,
410 "executable file contains out of range command offset",
411 0);
412 return 0;
413 }
414
415 raw_command =
416 commands_view->view.data + offset;
417 command.cmd = macho_file_to_host_u32 (commands_view->bytes_swapped,
418 raw_command->cmd);
419 command.cmdsize = macho_file_to_host_u32 (commands_view->bytes_swapped,
420 raw_command->cmdsize);
421
422 if (command.cmd == LC_UUID)
423 {
424 const struct uuid_command *uuid_command;
425
426 if (offset + sizeof (struct uuid_command)
427 > commands_view->commands_total_size)
428 {
429 error_callback (data,
430 "executable file contains out of range command offset",
431 0);
432 return 0;
433 }
434
435 uuid_command =
436 (struct uuid_command *) raw_command;
437 memcpy (uuid, uuid_command->uuid, sizeof (uuid_t));
438 return 1;
439 }
440
441 offset += command.cmdsize;
442 }
443
444 error_callback (data, "executable file is missing an identifying UUID", 0);
445 return 0;
446 }
447
448 /* Returns the base address of a Mach-O image, as encoded in the file header.
449 * WARNING: This does not take ASLR into account, which is ubiquitous on recent
450 * Darwin platforms.
451 */
452 int
macho_get_addr_range(struct backtrace_state * state ATTRIBUTE_UNUSED,int descriptor ATTRIBUTE_UNUSED,backtrace_error_callback error_callback,void * data,struct macho_commands_view * commands_view,uintptr_t * base_address,uintptr_t * max_address)453 macho_get_addr_range (struct backtrace_state *state ATTRIBUTE_UNUSED,
454 int descriptor ATTRIBUTE_UNUSED,
455 backtrace_error_callback error_callback,
456 void *data, struct macho_commands_view *commands_view,
457 uintptr_t *base_address, uintptr_t *max_address)
458 {
459 size_t offset = 0;
460 int found_text = 0;
461 uint32_t i = 0;
462
463 *max_address = 0;
464
465 for (i = 0; i < commands_view->commands_count; i++)
466 {
467 const struct load_command *raw_command;
468 struct load_command command;
469
470 if (offset + sizeof (struct load_command)
471 > commands_view->commands_total_size)
472 {
473 error_callback (data,
474 "executable file contains out of range command offset",
475 0);
476 return 0;
477 }
478
479 raw_command = commands_view->view.data + offset;
480 command.cmd = macho_file_to_host_u32 (commands_view->bytes_swapped,
481 raw_command->cmd);
482 command.cmdsize = macho_file_to_host_u32 (commands_view->bytes_swapped,
483 raw_command->cmdsize);
484
485 if (command.cmd == LC_SEGMENT_NATIVE)
486 {
487 const segment_command_native_t *raw_segment;
488 uintptr_t segment_vmaddr;
489 uintptr_t segment_vmsize;
490 uintptr_t segment_maxaddr;
491 uintptr_t text_fileoff;
492
493 if (offset + sizeof (segment_command_native_t)
494 > commands_view->commands_total_size)
495 {
496 error_callback (data,
497 "executable file contains out of range command offset",
498 0);
499 return 0;
500 }
501
502 raw_segment = (segment_command_native_t *) raw_command;
503
504 segment_vmaddr = macho_file_to_host_usize (
505 commands_view->bytes_swapped, raw_segment->vmaddr);
506 segment_vmsize = macho_file_to_host_usize (
507 commands_view->bytes_swapped, raw_segment->vmsize);
508 segment_maxaddr = segment_vmaddr + segment_vmsize;
509
510 if (strncmp (raw_segment->segname, "__TEXT",
511 sizeof (raw_segment->segname)) == 0)
512 {
513 text_fileoff = macho_file_to_host_usize (
514 commands_view->bytes_swapped, raw_segment->fileoff);
515 *base_address = segment_vmaddr - text_fileoff;
516
517 found_text = 1;
518 }
519
520 if (segment_maxaddr > *max_address)
521 *max_address = segment_maxaddr;
522 }
523
524 offset += command.cmdsize;
525 }
526
527 if (found_text)
528 return 1;
529 else
530 {
531 error_callback (data, "executable is missing __TEXT segment", 0);
532 return 0;
533 }
534 }
535
536 static int
macho_symbol_compare_addr(const void * left_raw,const void * right_raw)537 macho_symbol_compare_addr (const void *left_raw, const void *right_raw)
538 {
539 const struct macho_symbol *left = left_raw;
540 const struct macho_symbol *right = right_raw;
541
542 if (left->addr > right->addr)
543 return 1;
544 else if (left->addr < right->addr)
545 return -1;
546 else
547 return 0;
548 }
549
550 int
macho_symbol_type_relevant(uint8_t type)551 macho_symbol_type_relevant (uint8_t type)
552 {
553 uint8_t type_field = (uint8_t) (type & N_TYPE);
554
555 return !(type & N_EXT) &&
556 (type_field == N_ABS || type_field == N_SECT);
557 }
558
559 int
macho_add_symtab(struct backtrace_state * state,backtrace_error_callback error_callback,void * data,int descriptor,struct macho_commands_view * commands_view,uintptr_t base_address,uintptr_t max_image_address,intptr_t vmslide,int * found_sym)560 macho_add_symtab (struct backtrace_state *state,
561 backtrace_error_callback error_callback,
562 void *data, int descriptor,
563 struct macho_commands_view *commands_view,
564 uintptr_t base_address, uintptr_t max_image_address,
565 intptr_t vmslide, int *found_sym)
566 {
567 struct macho_syminfo_data *syminfo_data;
568
569 int ret = 0;
570 size_t offset = 0;
571 struct backtrace_view symtab_view;
572 int symtab_view_valid = 0;
573 struct backtrace_view strtab_view;
574 int strtab_view_valid = 0;
575 size_t syminfo_index = 0;
576 size_t function_count = 0;
577 uint32_t i = 0;
578 uint32_t j = 0;
579 uint32_t symtab_index = 0;
580
581 *found_sym = 0;
582
583 for (i = 0; i < commands_view->commands_count; i++)
584 {
585 const struct load_command *raw_command;
586 struct load_command command;
587
588 if (offset + sizeof (struct load_command)
589 > commands_view->commands_total_size)
590 {
591 error_callback (data,
592 "executable file contains out of range command offset",
593 0);
594 return 0;
595 }
596
597 raw_command = commands_view->view.data + offset;
598 command.cmd = macho_file_to_host_u32 (commands_view->bytes_swapped,
599 raw_command->cmd);
600 command.cmdsize = macho_file_to_host_u32 (commands_view->bytes_swapped,
601 raw_command->cmdsize);
602
603 if (command.cmd == LC_SYMTAB)
604 {
605 const struct symtab_command *symtab_command;
606 uint32_t symbol_table_offset;
607 uint32_t symbol_count;
608 uint32_t string_table_offset;
609 uint32_t string_table_size;
610
611 if (offset + sizeof (struct symtab_command)
612 > commands_view->commands_total_size)
613 {
614 error_callback (data,
615 "executable file contains out of range command offset",
616 0);
617 return 0;
618 }
619
620 symtab_command = (struct symtab_command *) raw_command;
621
622 symbol_table_offset = macho_file_to_host_u32 (
623 commands_view->bytes_swapped, symtab_command->symoff);
624 symbol_count = macho_file_to_host_u32 (
625 commands_view->bytes_swapped, symtab_command->nsyms);
626 string_table_offset = macho_file_to_host_u32 (
627 commands_view->bytes_swapped, symtab_command->stroff);
628 string_table_size = macho_file_to_host_u32 (
629 commands_view->bytes_swapped, symtab_command->strsize);
630
631
632 if (!macho_get_view (state, descriptor, symbol_table_offset,
633 symbol_count * sizeof (nlist_native_t),
634 error_callback, data, commands_view,
635 &symtab_view))
636 goto end;
637 symtab_view_valid = 1;
638
639 if (!macho_get_view (state, descriptor, string_table_offset,
640 string_table_size, error_callback, data,
641 commands_view, &strtab_view))
642 goto end;
643 strtab_view_valid = 1;
644
645 // Count functions first
646 for (j = 0; j < symbol_count; j++)
647 {
648 const nlist_native_t *raw_sym =
649 ((const nlist_native_t *) symtab_view.data) + j;
650
651 if (macho_symbol_type_relevant (raw_sym->n_type))
652 {
653 function_count += 1;
654 }
655 }
656
657 // Allocate space for the:
658 // (a) macho_syminfo_data for this image
659 // (b) macho_symbol entries
660 syminfo_data =
661 backtrace_alloc (state,
662 sizeof (struct macho_syminfo_data),
663 error_callback, data);
664 if (syminfo_data == NULL)
665 goto end;
666
667 syminfo_data->symbols = backtrace_alloc (
668 state, function_count * sizeof (struct macho_symbol),
669 error_callback, data);
670 if (syminfo_data->symbols == NULL)
671 goto end;
672
673 syminfo_data->symbol_count = function_count;
674 syminfo_data->next = NULL;
675 syminfo_data->min_addr = base_address;
676 syminfo_data->max_addr = max_image_address;
677
678 for (symtab_index = 0;
679 symtab_index < symbol_count; symtab_index++)
680 {
681 const nlist_native_t *raw_sym =
682 ((const nlist_native_t *) symtab_view.data) +
683 symtab_index;
684
685 if (macho_symbol_type_relevant (raw_sym->n_type))
686 {
687 size_t strtab_index;
688 const char *name;
689 size_t max_len_plus_one;
690
691 syminfo_data->symbols[syminfo_index].addr =
692 macho_file_to_host_usize (commands_view->bytes_swapped,
693 raw_sym->n_value) + vmslide;
694
695 strtab_index = macho_file_to_host_u32 (
696 commands_view->bytes_swapped,
697 raw_sym->n_un.n_strx);
698
699 // Check the range of the supposed "string" we've been
700 // given
701 if (strtab_index >= string_table_size)
702 {
703 error_callback (
704 data,
705 "dSYM file contains out of range string table index",
706 0);
707 goto end;
708 }
709
710 name = strtab_view.data + strtab_index;
711 max_len_plus_one = string_table_size - strtab_index;
712
713 if (strnlen (name, max_len_plus_one) >= max_len_plus_one)
714 {
715 error_callback (
716 data,
717 "dSYM file contains unterminated string",
718 0);
719 goto end;
720 }
721
722 // Remove underscore prefixes
723 if (name[0] == '_')
724 {
725 name = name + 1;
726 }
727
728 syminfo_data->symbols[syminfo_index].name = name;
729
730 syminfo_index += 1;
731 }
732 }
733
734 backtrace_qsort (syminfo_data->symbols,
735 syminfo_data->symbol_count,
736 sizeof (struct macho_symbol),
737 macho_symbol_compare_addr);
738
739 // Calculate symbol sizes
740 for (syminfo_index = 0;
741 syminfo_index < syminfo_data->symbol_count; syminfo_index++)
742 {
743 if (syminfo_index + 1 < syminfo_data->symbol_count)
744 {
745 syminfo_data->symbols[syminfo_index].size =
746 syminfo_data->symbols[syminfo_index + 1].addr -
747 syminfo_data->symbols[syminfo_index].addr;
748 }
749 else
750 {
751 syminfo_data->symbols[syminfo_index].size =
752 max_image_address -
753 syminfo_data->symbols[syminfo_index].addr;
754 }
755 }
756
757 if (!state->threaded)
758 {
759 struct macho_syminfo_data **pp;
760
761 for (pp = (struct macho_syminfo_data **) (void *) &state->syminfo_data;
762 *pp != NULL;
763 pp = &(*pp)->next);
764 *pp = syminfo_data;
765 }
766 else
767 {
768 while (1)
769 {
770 struct macho_syminfo_data **pp;
771
772 pp = (struct macho_syminfo_data **) (void *) &state->syminfo_data;
773
774 while (1)
775 {
776 struct macho_syminfo_data *p;
777
778 p = backtrace_atomic_load_pointer (pp);
779
780 if (p == NULL)
781 break;
782
783 pp = &p->next;
784 }
785
786 if (__sync_bool_compare_and_swap (pp, NULL, syminfo_data))
787 break;
788 }
789 }
790
791 strtab_view_valid = 0; // We need to keep string table around
792 *found_sym = 1;
793 ret = 1;
794 goto end;
795 }
796
797 offset += command.cmdsize;
798 }
799
800 // No symbol table here
801 ret = 1;
802 goto end;
803
804 end:
805 if (symtab_view_valid)
806 backtrace_release_view (state, &symtab_view, error_callback, data);
807 if (strtab_view_valid)
808 backtrace_release_view (state, &strtab_view, error_callback, data);
809 return ret;
810 }
811
812 int
macho_try_dwarf(struct backtrace_state * state,backtrace_error_callback error_callback,void * data,fileline * fileline_fn,uuid_t * executable_uuid,uintptr_t base_address,uintptr_t max_image_address,intptr_t vmslide,char * dwarf_filename,int * matched,int * found_sym,int * found_dwarf)813 macho_try_dwarf (struct backtrace_state *state,
814 backtrace_error_callback error_callback,
815 void *data, fileline *fileline_fn, uuid_t *executable_uuid,
816 uintptr_t base_address, uintptr_t max_image_address,
817 intptr_t vmslide, char *dwarf_filename, int *matched,
818 int *found_sym, int *found_dwarf)
819 {
820 uuid_t dwarf_uuid;
821
822 int ret = 0;
823 int dwarf_descriptor;
824 int dwarf_descriptor_valid = 0;
825 struct macho_commands_view commands_view;
826 int commands_view_valid = 0;
827 struct backtrace_view dwarf_view;
828 int dwarf_view_valid = 0;
829 size_t offset = 0;
830 struct found_dwarf_section dwarf_sections[DEBUG_MAX];
831 uintptr_t min_dwarf_offset = 0;
832 uintptr_t max_dwarf_offset = 0;
833 uint32_t i = 0;
834 uint32_t j = 0;
835 int k = 0;
836
837 *matched = 0;
838 *found_sym = 0;
839 *found_dwarf = 0;
840
841 if ((dwarf_descriptor = backtrace_open (dwarf_filename, error_callback,
842 data, NULL)) == 0)
843 goto end;
844 dwarf_descriptor_valid = 1;
845
846 int incompatible;
847 if (!macho_get_commands (state, dwarf_descriptor, error_callback, data,
848 &commands_view, &incompatible))
849 {
850 // Failing to read the header here is fine, because this dSYM may be
851 // for a different architecture
852 if (incompatible)
853 {
854 ret = 1;
855 }
856 goto end;
857 }
858 commands_view_valid = 1;
859
860 // Get dSYM UUID and compare
861 if (!macho_get_uuid (state, dwarf_descriptor, error_callback, data,
862 &commands_view, &dwarf_uuid))
863 {
864 error_callback (data, "dSYM file is missing an identifying uuid", 0);
865 goto end;
866 }
867 if (memcmp (executable_uuid, &dwarf_uuid, sizeof (uuid_t)) != 0)
868 {
869 // DWARF doesn't belong to desired executable
870 ret = 1;
871 goto end;
872 }
873
874 *matched = 1;
875
876 // Read symbol table
877 if (!macho_add_symtab (state, error_callback, data, dwarf_descriptor,
878 &commands_view, base_address, max_image_address,
879 vmslide, found_sym))
880 goto end;
881
882 // Get DWARF sections
883
884 memset (dwarf_sections, 0, sizeof (dwarf_sections));
885 offset = 0;
886 for (i = 0; i < commands_view.commands_count; i++)
887 {
888 const struct load_command *raw_command;
889 struct load_command command;
890
891 if (offset + sizeof (struct load_command)
892 > commands_view.commands_total_size)
893 {
894 error_callback (data,
895 "dSYM file contains out of range command offset", 0);
896 goto end;
897 }
898
899 raw_command = commands_view.view.data + offset;
900 command.cmd = macho_file_to_host_u32 (commands_view.bytes_swapped,
901 raw_command->cmd);
902 command.cmdsize = macho_file_to_host_u32 (commands_view.bytes_swapped,
903 raw_command->cmdsize);
904
905 if (command.cmd == LC_SEGMENT_NATIVE)
906 {
907 uint32_t section_count;
908 size_t section_offset;
909 const segment_command_native_t *raw_segment;
910
911 if (offset + sizeof (segment_command_native_t)
912 > commands_view.commands_total_size)
913 {
914 error_callback (data,
915 "dSYM file contains out of range command offset",
916 0);
917 goto end;
918 }
919
920 raw_segment = (const segment_command_native_t *) raw_command;
921
922 if (strncmp (raw_segment->segname, "__DWARF",
923 sizeof (raw_segment->segname)) == 0)
924 {
925 section_count = macho_file_to_host_u32 (
926 commands_view.bytes_swapped,
927 raw_segment->nsects);
928
929 section_offset = offset + sizeof (segment_command_native_t);
930
931 // Search sections for relevant DWARF section names
932 for (j = 0; j < section_count; j++)
933 {
934 const section_native_t *raw_section;
935
936 if (section_offset + sizeof (section_native_t) >
937 commands_view.commands_total_size)
938 {
939 error_callback (data,
940 "dSYM file contains out of range command offset",
941 0);
942 goto end;
943 }
944
945 raw_section = commands_view.view.data + section_offset;
946
947 for (k = 0; k < DEBUG_MAX; k++)
948 {
949 uintptr_t dwarf_section_end;
950
951 if (strncmp (raw_section->sectname,
952 debug_section_names[k],
953 sizeof (raw_section->sectname)) == 0)
954 {
955 *found_dwarf = 1;
956
957 dwarf_sections[k].file_offset =
958 macho_file_to_host_u32 (
959 commands_view.bytes_swapped,
960 raw_section->offset);
961 dwarf_sections[k].file_size =
962 macho_file_to_host_usize (
963 commands_view.bytes_swapped,
964 raw_section->size);
965
966 if (min_dwarf_offset == 0 ||
967 dwarf_sections[k].file_offset <
968 min_dwarf_offset)
969 min_dwarf_offset = dwarf_sections[k].file_offset;
970
971 dwarf_section_end =
972 dwarf_sections[k].file_offset +
973 dwarf_sections[k].file_size;
974 if (dwarf_section_end > max_dwarf_offset)
975 max_dwarf_offset = dwarf_section_end;
976
977 break;
978 }
979 }
980
981 section_offset += sizeof (section_native_t);
982 }
983
984 break;
985 }
986 }
987
988 offset += command.cmdsize;
989 }
990
991 if (!*found_dwarf)
992 {
993 // No DWARF in this file
994 ret = 1;
995 goto end;
996 }
997
998 if (!macho_get_view (state, dwarf_descriptor, (off_t) min_dwarf_offset,
999 max_dwarf_offset - min_dwarf_offset, error_callback,
1000 data, &commands_view, &dwarf_view))
1001 goto end;
1002 dwarf_view_valid = 1;
1003
1004 for (i = 0; i < DEBUG_MAX; i++)
1005 {
1006 if (dwarf_sections[i].file_offset == 0)
1007 dwarf_sections[i].data = NULL;
1008 else
1009 dwarf_sections[i].data =
1010 dwarf_view.data + dwarf_sections[i].file_offset - min_dwarf_offset;
1011 }
1012
1013 if (!backtrace_dwarf_add (state, vmslide,
1014 dwarf_sections[DEBUG_INFO].data,
1015 dwarf_sections[DEBUG_INFO].file_size,
1016 dwarf_sections[DEBUG_LINE].data,
1017 dwarf_sections[DEBUG_LINE].file_size,
1018 dwarf_sections[DEBUG_ABBREV].data,
1019 dwarf_sections[DEBUG_ABBREV].file_size,
1020 dwarf_sections[DEBUG_RANGES].data,
1021 dwarf_sections[DEBUG_RANGES].file_size,
1022 dwarf_sections[DEBUG_STR].data,
1023 dwarf_sections[DEBUG_STR].file_size,
1024 ((__DARWIN_BYTE_ORDER == __DARWIN_BIG_ENDIAN)
1025 ^ commands_view.bytes_swapped),
1026 error_callback, data, fileline_fn))
1027 goto end;
1028
1029 // Don't release the DWARF view because it is still in use
1030 dwarf_descriptor_valid = 0;
1031 dwarf_view_valid = 0;
1032 ret = 1;
1033
1034 end:
1035 if (dwarf_descriptor_valid)
1036 backtrace_close (dwarf_descriptor, error_callback, data);
1037 if (commands_view_valid)
1038 backtrace_release_view (state, &commands_view.view, error_callback, data);
1039 if (dwarf_view_valid)
1040 backtrace_release_view (state, &dwarf_view, error_callback, data);
1041 return ret;
1042 }
1043
1044 int
macho_try_dsym(struct backtrace_state * state,backtrace_error_callback error_callback,void * data,fileline * fileline_fn,uuid_t * executable_uuid,uintptr_t base_address,uintptr_t max_image_address,intptr_t vmslide,char * dsym_filename,int * matched,int * found_sym,int * found_dwarf)1045 macho_try_dsym (struct backtrace_state *state,
1046 backtrace_error_callback error_callback,
1047 void *data, fileline *fileline_fn, uuid_t *executable_uuid,
1048 uintptr_t base_address, uintptr_t max_image_address,
1049 intptr_t vmslide, char *dsym_filename, int *matched,
1050 int *found_sym, int *found_dwarf)
1051 {
1052 int ret = 0;
1053 char dwarf_image_dir_path[PATH_MAX];
1054 DIR *dwarf_image_dir;
1055 int dwarf_image_dir_valid = 0;
1056 struct dirent *directory_entry;
1057 char dwarf_filename[PATH_MAX];
1058 int dwarf_matched;
1059 int dwarf_had_sym;
1060 int dwarf_had_dwarf;
1061
1062 *matched = 0;
1063 *found_sym = 0;
1064 *found_dwarf = 0;
1065
1066 strncpy (dwarf_image_dir_path, dsym_filename, PATH_MAX);
1067 strncat (dwarf_image_dir_path, "/Contents/Resources/DWARF", PATH_MAX);
1068
1069 if (!(dwarf_image_dir = opendir (dwarf_image_dir_path)))
1070 {
1071 error_callback (data, "could not open DWARF directory in dSYM",
1072 0);
1073 goto end;
1074 }
1075 dwarf_image_dir_valid = 1;
1076
1077 while ((directory_entry = readdir (dwarf_image_dir)))
1078 {
1079 if (directory_entry->d_type != DT_REG)
1080 continue;
1081
1082 strncpy (dwarf_filename, dwarf_image_dir_path, PATH_MAX);
1083 strncat (dwarf_filename, "/", PATH_MAX);
1084 strncat (dwarf_filename, directory_entry->d_name, PATH_MAX);
1085
1086 if (!macho_try_dwarf (state, error_callback, data, fileline_fn,
1087 executable_uuid, base_address, max_image_address,
1088 vmslide, dwarf_filename,
1089 &dwarf_matched, &dwarf_had_sym, &dwarf_had_dwarf))
1090 goto end;
1091
1092 if (dwarf_matched)
1093 {
1094 *matched = 1;
1095 *found_sym = dwarf_had_sym;
1096 *found_dwarf = dwarf_had_dwarf;
1097 ret = 1;
1098 goto end;
1099 }
1100 }
1101
1102 // No matching DWARF in this dSYM
1103 ret = 1;
1104 goto end;
1105
1106 end:
1107 if (dwarf_image_dir_valid)
1108 closedir (dwarf_image_dir);
1109 return ret;
1110 }
1111
1112 int
macho_add(struct backtrace_state * state,backtrace_error_callback error_callback,void * data,int descriptor,const char * filename,fileline * fileline_fn,intptr_t vmslide,int * found_sym,int * found_dwarf)1113 macho_add (struct backtrace_state *state,
1114 backtrace_error_callback error_callback, void *data, int descriptor,
1115 const char *filename, fileline *fileline_fn, intptr_t vmslide,
1116 int *found_sym, int *found_dwarf)
1117 {
1118 uuid_t image_uuid;
1119 uintptr_t image_file_base_address;
1120 uintptr_t image_file_max_address;
1121 uintptr_t image_actual_base_address = 0;
1122 uintptr_t image_actual_max_address = 0;
1123
1124 int ret = 0;
1125 struct macho_commands_view commands_view;
1126 int commands_view_valid = 0;
1127 char executable_dirname[PATH_MAX];
1128 size_t filename_len;
1129 DIR *executable_dir = NULL;
1130 int executable_dir_valid = 0;
1131 struct dirent *directory_entry;
1132 char dsym_full_path[PATH_MAX];
1133 static const char *extension;
1134 size_t extension_len;
1135 ssize_t i;
1136
1137 *found_sym = 0;
1138 *found_dwarf = 0;
1139
1140 // Find Mach-O commands list
1141 int incompatible;
1142 if (!macho_get_commands (state, descriptor, error_callback, data,
1143 &commands_view, &incompatible))
1144 goto end;
1145 commands_view_valid = 1;
1146
1147 // First we need to get the uuid of our file so we can hunt down the correct
1148 // dSYM
1149 if (!macho_get_uuid (state, descriptor, error_callback, data, &commands_view,
1150 &image_uuid))
1151 goto end;
1152
1153 // Now we need to find the in memory base address. Step one is to find out
1154 // what the executable thinks the base address is
1155 if (!macho_get_addr_range (state, descriptor, error_callback, data,
1156 &commands_view,
1157 &image_file_base_address,
1158 &image_file_max_address))
1159 goto end;
1160
1161 image_actual_base_address =
1162 image_file_base_address + vmslide;
1163 image_actual_max_address =
1164 image_file_max_address + vmslide;
1165
1166 if (image_actual_base_address == 0)
1167 {
1168 error_callback (data, "executable file is not loaded", 0);
1169 goto end;
1170 }
1171
1172 // Look for dSYM in our executable's directory
1173 strncpy (executable_dirname, filename, PATH_MAX);
1174 filename_len = strlen (executable_dirname);
1175 for (i = filename_len - 1; i >= 0; i--)
1176 {
1177 if (executable_dirname[i] == '/')
1178 {
1179 executable_dirname[i] = '\0';
1180 break;
1181 }
1182 else if (i == 0)
1183 {
1184 executable_dirname[0] = '.';
1185 executable_dirname[1] = '\0';
1186 break;
1187 }
1188 }
1189
1190 if (!(executable_dir = opendir (executable_dirname)))
1191 {
1192 error_callback (data, "could not open directory containing executable",
1193 0);
1194 goto end;
1195 }
1196 executable_dir_valid = 1;
1197
1198 extension = ".dSYM";
1199 extension_len = strlen (extension);
1200 while ((directory_entry = readdir (executable_dir)))
1201 {
1202 if (directory_entry->d_namlen < extension_len)
1203 continue;
1204 if (strncasecmp (directory_entry->d_name + directory_entry->d_namlen
1205 - extension_len, extension, extension_len) == 0)
1206 {
1207 int matched;
1208 int dsym_had_sym;
1209 int dsym_had_dwarf;
1210
1211 // Found a dSYM
1212 strncpy (dsym_full_path, executable_dirname, PATH_MAX);
1213 strncat (dsym_full_path, "/", PATH_MAX);
1214 strncat (dsym_full_path, directory_entry->d_name, PATH_MAX);
1215
1216 if (!macho_try_dsym (state, error_callback, data,
1217 fileline_fn, &image_uuid,
1218 image_actual_base_address,
1219 image_actual_max_address, vmslide,
1220 dsym_full_path,
1221 &matched, &dsym_had_sym, &dsym_had_dwarf))
1222 goto end;
1223
1224 if (matched)
1225 {
1226 *found_sym = dsym_had_sym;
1227 *found_dwarf = dsym_had_dwarf;
1228 ret = 1;
1229 goto end;
1230 }
1231 }
1232 }
1233
1234 // No matching dSYM
1235 ret = 1;
1236 goto end;
1237
1238 end:
1239 if (commands_view_valid)
1240 backtrace_release_view (state, &commands_view.view, error_callback,
1241 data);
1242 if (executable_dir_valid)
1243 closedir (executable_dir);
1244 return ret;
1245 }
1246
1247 static int
macho_symbol_search(const void * vkey,const void * ventry)1248 macho_symbol_search (const void *vkey, const void *ventry)
1249 {
1250 const uintptr_t *key = (const uintptr_t *) vkey;
1251 const struct macho_symbol *entry = (const struct macho_symbol *) ventry;
1252 uintptr_t addr;
1253
1254 addr = *key;
1255 if (addr < entry->addr)
1256 return -1;
1257 else if (addr >= entry->addr + entry->size)
1258 return 1;
1259 else
1260 return 0;
1261 }
1262
1263 static void
macho_syminfo(struct backtrace_state * state,uintptr_t addr,backtrace_syminfo_callback callback,backtrace_error_callback error_callback ATTRIBUTE_UNUSED,void * data)1264 macho_syminfo (struct backtrace_state *state,
1265 uintptr_t addr,
1266 backtrace_syminfo_callback callback,
1267 backtrace_error_callback error_callback ATTRIBUTE_UNUSED,
1268 void *data)
1269 {
1270 struct macho_syminfo_data *edata;
1271 struct macho_symbol *sym = NULL;
1272
1273 if (!state->threaded)
1274 {
1275 for (edata = (struct macho_syminfo_data *) state->syminfo_data;
1276 edata != NULL;
1277 edata = edata->next)
1278 {
1279 if (addr >= edata->min_addr && addr <= edata->max_addr)
1280 {
1281 sym = ((struct macho_symbol *)
1282 bsearch (&addr, edata->symbols, edata->symbol_count,
1283 sizeof (struct macho_symbol), macho_symbol_search));
1284 if (sym != NULL)
1285 break;
1286 }
1287 }
1288 }
1289 else
1290 {
1291 struct macho_syminfo_data **pp;
1292
1293 pp = (struct macho_syminfo_data **) (void *) &state->syminfo_data;
1294 while (1)
1295 {
1296 edata = backtrace_atomic_load_pointer (pp);
1297 if (edata == NULL)
1298 break;
1299
1300 if (addr >= edata->min_addr && addr <= edata->max_addr)
1301 {
1302 sym = ((struct macho_symbol *)
1303 bsearch (&addr, edata->symbols, edata->symbol_count,
1304 sizeof (struct macho_symbol), macho_symbol_search));
1305 if (sym != NULL)
1306 break;
1307 }
1308
1309 pp = &edata->next;
1310 }
1311 }
1312
1313 if (sym == NULL)
1314 callback (data, addr, NULL, 0, 0);
1315 else
1316 callback (data, addr, sym->name, sym->addr, sym->size);
1317 }
1318
1319
1320 static int
macho_nodebug(struct backtrace_state * state ATTRIBUTE_UNUSED,uintptr_t pc ATTRIBUTE_UNUSED,backtrace_full_callback callback ATTRIBUTE_UNUSED,backtrace_error_callback error_callback,void * data)1321 macho_nodebug (struct backtrace_state *state ATTRIBUTE_UNUSED,
1322 uintptr_t pc ATTRIBUTE_UNUSED,
1323 backtrace_full_callback callback ATTRIBUTE_UNUSED,
1324 backtrace_error_callback error_callback, void *data)
1325 {
1326 error_callback (data, "no debug info in Mach-O executable", -1);
1327 return 0;
1328 }
1329
1330 static void
macho_nosyms(struct backtrace_state * state ATTRIBUTE_UNUSED,uintptr_t addr ATTRIBUTE_UNUSED,backtrace_syminfo_callback callback ATTRIBUTE_UNUSED,backtrace_error_callback error_callback,void * data)1331 macho_nosyms (struct backtrace_state *state ATTRIBUTE_UNUSED,
1332 uintptr_t addr ATTRIBUTE_UNUSED,
1333 backtrace_syminfo_callback callback ATTRIBUTE_UNUSED,
1334 backtrace_error_callback error_callback, void *data)
1335 {
1336 error_callback (data, "no symbol table in Mach-O executable", -1);
1337 }
1338
1339 int
backtrace_initialize(struct backtrace_state * state,const char * filename,int descriptor,backtrace_error_callback error_callback,void * data,fileline * fileline_fn)1340 backtrace_initialize (struct backtrace_state *state,
1341 const char *filename,
1342 int descriptor,
1343 backtrace_error_callback error_callback,
1344 void *data, fileline *fileline_fn)
1345 {
1346 int ret;
1347 fileline macho_fileline_fn = macho_nodebug;
1348 int found_sym = 0;
1349 int found_dwarf = 0;
1350 uint32_t i = 0;
1351 uint32_t loaded_image_count;
1352
1353 // Add all loaded images
1354 loaded_image_count = _dyld_image_count ();
1355 for (i = 0; i < loaded_image_count; i++)
1356 {
1357 int current_found_sym;
1358 int current_found_dwarf;
1359 int current_descriptor;
1360 intptr_t current_vmslide;
1361 const char *current_name;
1362
1363 current_vmslide = _dyld_get_image_vmaddr_slide (i);
1364 current_name = _dyld_get_image_name (i);
1365
1366 if (current_name == NULL || (i != 0 && current_vmslide == 0))
1367 continue;
1368
1369 if (!(current_descriptor =
1370 backtrace_open (current_name, error_callback, data, NULL)))
1371 {
1372 continue;
1373 }
1374
1375 if (macho_add (state, error_callback, data, current_descriptor,
1376 current_name, &macho_fileline_fn, current_vmslide,
1377 ¤t_found_sym, ¤t_found_dwarf))
1378 {
1379 found_sym = found_sym || current_found_sym;
1380 found_dwarf = found_dwarf || current_found_dwarf;
1381 }
1382
1383 backtrace_close (current_descriptor, error_callback, data);
1384 }
1385
1386 if (!state->threaded)
1387 {
1388 if (found_sym)
1389 state->syminfo_fn = macho_syminfo;
1390 else if (state->syminfo_fn == NULL)
1391 state->syminfo_fn = macho_nosyms;
1392 }
1393 else
1394 {
1395 if (found_sym)
1396 backtrace_atomic_store_pointer (&state->syminfo_fn, macho_syminfo);
1397 else
1398 (void) __sync_bool_compare_and_swap (&state->syminfo_fn, NULL,
1399 macho_nosyms);
1400 }
1401
1402 if (!state->threaded)
1403 {
1404 if (state->fileline_fn == NULL || state->fileline_fn == macho_nodebug)
1405 *fileline_fn = macho_fileline_fn;
1406 }
1407 else
1408 {
1409 fileline current_fn;
1410
1411 current_fn = backtrace_atomic_load_pointer (&state->fileline_fn);
1412 if (current_fn == NULL || current_fn == macho_nodebug)
1413 *fileline_fn = macho_fileline_fn;
1414 }
1415
1416 return 1;
1417 }
1418
1419