From cb7e311c11ccb8120fda95674211625c777d895a Mon Sep 17 00:00:00 2001 From: Ryan Mansfield Date: Wed, 29 Oct 2025 13:15:47 -0400 Subject: [PATCH] Improve dSYM loading for Mach-O binaries Adds two features for better dSYM support on macOS: 1. Add Mach-O specific --dsym option - Accepts dSYM bundles - Handles multiple dSYM files for multiple input binaries 2. Automatic dSYM discovery - Searches for .dSYM/Contents/Resources/DWARF/ - Only loads dSYM if build IDs match to prevent mismatches This improves the macOS user experience by eliminating the need to manually specify the binary file inside of the dSYM bundle. CMakeLists.txt: Add bloaty.pb.h to protoc OUTPUT list. Protoc generates both .cc and .h files, so both should be declared as outputs for proper dependency tracking. --- CMakeLists.txt | 3 + doc/using.md | 22 + src/bloaty.cc | 158 +++++- src/bloaty.proto | 3 + tests/macho/dsym-loading.test | 798 +++++++++++++++++++++++++++++++ tests/macho/no-dsym.test | 247 ++++++++++ tests/testdata/macho/test_dsym.c | 9 + 7 files changed, 1238 insertions(+), 2 deletions(-) create mode 100644 tests/macho/dsym-loading.test create mode 100644 tests/macho/no-dsym.test create mode 100644 tests/testdata/macho/test_dsym.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 452ba3e..05b5289 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -373,6 +373,7 @@ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/src) if(PROTOC_FOUND) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/src/bloaty.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/src/bloaty.pb.h DEPENDS protoc ${CMAKE_CURRENT_SOURCE_DIR}/src/bloaty.proto COMMAND protoc ${CMAKE_CURRENT_SOURCE_DIR}/src/bloaty.proto --cpp_out=${CMAKE_CURRENT_BINARY_DIR}/src @@ -381,6 +382,7 @@ add_custom_command( else() add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/src/bloaty.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/src/bloaty.pb.h COMMAND protoc ${CMAKE_CURRENT_SOURCE_DIR}/src/bloaty.proto --cpp_out=${CMAKE_CURRENT_BINARY_DIR}/src -I${CMAKE_CURRENT_SOURCE_DIR}/src @@ -395,6 +397,7 @@ add_library(libbloaty STATIC src/bloaty.h src/disassemble.cc ${CMAKE_CURRENT_BINARY_DIR}/src/bloaty.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/src/bloaty.pb.h src/dwarf/attr.h src/dwarf/attr.cc src/dwarf/dwarf_util.cc diff --git a/doc/using.md b/doc/using.md index 44a1484..3d3cc0e 100644 --- a/doc/using.md +++ b/doc/using.md @@ -327,6 +327,28 @@ can create with `dsymutil`: ``` $ dsymutil bloaty $ strip bloaty (optional) +$ ./bloaty -d symbols bloaty # Auto-discovers bloaty.dSYM +``` + +Bloaty will automatically discover and load debug symbols from +`bloaty.dSYM/Contents/Resources/DWARF/bloaty` when analyzing `bloaty`. + +If you used `dsymutil -o` to write the dSYM to a different directory, +you can explicitly specify its location: + +``` +$ dsymutil bloaty -o /path/to/symbols/bloaty.dSYM +$ ./bloaty -d symbols --dsym=/path/to/symbols/bloaty.dSYM bloaty +``` + +**Note:** The `--dsym` option is also useful for debugging issues with auto-loading dSYMs. +When you explicitly specify `--dsym`, Bloaty will throw informative error messages +if the dSYM file is invalid or has a mismatched build ID, rather than silently +ignoring the file as it does during auto-discovery. + +For cross-platform compatibility, `--debug-file` still works: + +``` $ ./bloaty -d symbols --debug-file=bloaty.dSYM/Contents/Resources/DWARF/bloaty bloaty ``` diff --git a/src/bloaty.cc b/src/bloaty.cc index d711912..f489c60 100644 --- a/src/bloaty.cc +++ b/src/bloaty.cc @@ -29,6 +29,7 @@ typedef size_t z_size_t; #include #include +#include #include #include #include @@ -1526,6 +1527,9 @@ class Bloaty { void AddFilename(const std::string& filename, bool base_file); void AddDebugFilename(const std::string& filename); void AddSourceMapFilename(const std::string& filename); + void AddDsymFilename(const Options& options, const std::string& filename); + void TryAutoLoadDsym(const std::string& binary_path, + const std::string& build_id); size_t GetSourceCount() const { return sources_.size(); } @@ -1573,6 +1577,15 @@ class Bloaty { std::unique_ptr GetObjectFile(const std::string& filename) const; + bool TryGetBuildId(const std::string& filename, std::string* build_id); + bool TryValidateAndAddDsym(const std::string& dsym_path, + const std::string& main_binary_path, + bool strict_validation); + + std::filesystem::path ConstructDsymPath(const std::filesystem::path& binary_path); + std::filesystem::path GetDsymInternalPath(const std::filesystem::path& dsym_bundle, + const std::string& binary_name); + const InputFileFactory& file_factory_; const Options options_; @@ -1634,6 +1647,9 @@ void Bloaty::AddFilename(const std::string& filename, bool is_base) { auto object_file = GetObjectFile(filename); std::string build_id = object_file->GetBuildId(); + // Automatically try to load dSYM file for Mach-O binaries + TryAutoLoadDsym(filename, build_id); + if (is_base) { base_files_.push_back({filename, build_id}); } else { @@ -1663,6 +1679,137 @@ void Bloaty::AddSourceMapFilename(const std::string& filename) { sourcemap_files_[sourcemap_build_id] = sourcemap_filename; } +bool Bloaty::TryGetBuildId(const std::string& filename, std::string* build_id) { + try { + auto object_file = GetObjectFile(filename); + *build_id = object_file->GetBuildId(); + return true; + } catch (const std::exception&) { + return false; + } +} + +bool Bloaty::TryValidateAndAddDsym(const std::string& dsym_path, + const std::string& main_binary_path, + bool strict_validation) { + std::string main_build_id, dsym_build_id; + + if (!TryGetBuildId(main_binary_path, &main_build_id) || + !TryGetBuildId(dsym_path, &dsym_build_id)) { + if (strict_validation) { + THROWF("Cannot read build ID from files: main=$0, dsym=$1", + main_binary_path, dsym_path); + } + return false; + } + + if (main_build_id != dsym_build_id) { + if (strict_validation) { + THROWF("dSYM file $0 has mismatched build ID (expected $1, got $2)", + dsym_path, + absl::BytesToHexString(main_build_id), + absl::BytesToHexString(dsym_build_id)); + } else { + WARN("dSYM file $0 has mismatched build ID (expected $1, got $2) - skipping", + dsym_path, + absl::BytesToHexString(main_build_id), + absl::BytesToHexString(dsym_build_id)); + } + return false; + } + + AddDebugFilename(dsym_path); + return true; +} + +std::filesystem::path Bloaty::ConstructDsymPath(const std::filesystem::path& binary_path) { + std::filesystem::path binary_file(binary_path); + std::string binary_name = binary_file.filename().string(); + std::filesystem::path binary_dir = binary_file.parent_path(); + + std::filesystem::path dsym_bundle = binary_dir / (binary_name + ".dSYM"); + return dsym_bundle / "Contents" / "Resources" / "DWARF" / binary_name; +} + +std::filesystem::path Bloaty::GetDsymInternalPath(const std::filesystem::path& dsym_bundle, + const std::string& binary_name) { + return dsym_bundle / "Contents" / "Resources" / "DWARF" / binary_name; +} + +// Loads dSYM files explicitly specified by the user using the --dsym flag. +// +// This function processes dSYM bundle paths with strict validation: +// - Warns if no dSYM files match the input binaries +// - Throws if the dSYM bundle path doesn't exist or isn't a directory +// - Throws if dSYM files are invalid or unreadable +// - Throws if buildids don't match between binary and dSYM +// +// The strict behaviour is intentional: when users explicitly specify --dsym, +// they expect it to work and want to know immediately if there are problems. +void Bloaty::AddDsymFilename(const Options& options, + const std::string& filename) { + if (!std::filesystem::exists(filename)) { + THROWF("couldn't open '$0'", filename.c_str()); + } + + std::filesystem::path dsym_bundle(filename); + // Only support dSYM bundles + if (std::filesystem::is_directory(dsym_bundle)) { + bool found_any = false; + for (const auto& binary_filename : options.filename()) { + std::filesystem::path binary_name = std::filesystem::path(binary_filename).filename(); + std::filesystem::path dsym_file = GetDsymInternalPath(dsym_bundle, binary_name.string()); + + if (std::filesystem::exists(dsym_file)) { + if (TryValidateAndAddDsym(dsym_file.string(), binary_filename, + /*strict_validation=*/true)) { + found_any = true; + } + } + } + if (!found_any) { + WARN("dSYM bundle '$0' contains no debug files matching input binaries", + filename); + } + } else { + THROWF("'$0' is not a dSYM bundle directory", filename); + } +} + +// Automatically discovers and loads dSYM files using macOS conventions. +// +// This function attempts to find dSYM bundles using the standard macOS layout: +// /.dSYM/Contents/Resources/DWARF/ +// +// Key behavioural differences from AddDsymFilename(): +// - Never throws exceptions or breaks analysis if discovery fails +// - Ignores invalid/corrupted dSYM files +// - Warns on buildid mismatches but continues without the dSYM +// +// The permissive behaviour is intentional because auto-loading is speculative and +// shouldn't break analysis of the main binary. Users didn't explicitly +// request the dSYM so failures should be silent. +void Bloaty::TryAutoLoadDsym(const std::string& binary_path, + const std::string& build_id) { + if (build_id.empty()) { + return; + } + + std::filesystem::path dsym_file = ConstructDsymPath(binary_path); + + if (std::filesystem::exists(dsym_file)) { + // Use non-strict validation because auto-loading is speculative + // GetObjectFile() will throw if the file isn't a valid Mach-O and + // we want auto-loading to silently fail rather than break analysis. + if (TryValidateAndAddDsym(dsym_file.string(), binary_path, + /*strict_validation=*/false)) { + if (verbose_level > 1) { + printf("Found matching dSYM file: %s\n", dsym_file.string().c_str()); + } + } + } +} + void Bloaty::DefineCustomDataSource(const CustomDataSource& source) { if (source.base_data_source() == "symbols") { THROW( @@ -2030,6 +2177,7 @@ USAGE: bloaty [OPTION]... FILE... [-- BASE_FILE...] -c FILE Load configuration from . -d SOURCE,SOURCE Comma-separated list of sources to scan. --debug-file=FILE Use this file for debug symbols and/or symbol table. + --dsym=FILE Use this dSYM file or bundle (Mach-O only). --source-map=ID=FILE Use this source map file for the binary. The ID can be the build ID (or Wasm sourceMappingURL) or the file path @@ -2224,6 +2372,8 @@ bool DoParseOptions(bool skip_unknown, int* argc, char** argv[], } } else if (args.TryParseOption("--debug-file", &option)) { options->add_debug_filename(std::string(option)); + } else if (args.TryParseOption("--dsym", &option)) { + options->add_dsym_path(std::string(option)); } else if (args.TryParseUint64Option("--debug-fileoff", &uint64_option)) { if (options->has_debug_fileoff()) { THROW("currently we only support a single debug fileoff"); @@ -2349,6 +2499,8 @@ void BloatyDoMain(const Options& options, const InputFileFactory& file_factory, THROW("max_rows_per_level must be at least 1"); } + verbose_level = options.verbose_level(); + for (auto& filename : options.filename()) { bloaty.AddFilename(filename, false); } @@ -2361,6 +2513,10 @@ void BloatyDoMain(const Options& options, const InputFileFactory& file_factory, bloaty.AddDebugFilename(debug_filename); } + for (const auto& dsym_path : options.dsym_path()) { + bloaty.AddDsymFilename(options, dsym_path); + } + for (auto& sourcemap : options.source_map()) { bloaty.AddSourceMapFilename(sourcemap); } @@ -2380,8 +2536,6 @@ void BloatyDoMain(const Options& options, const InputFileFactory& file_factory, } } - verbose_level = options.verbose_level(); - if (options.data_source_size() > 0) { bloaty.ScanAndRollup(options, output); } else if (options.has_disassemble_function()) { diff --git a/src/bloaty.proto b/src/bloaty.proto index 5eb4421..0c1e87a 100644 --- a/src/bloaty.proto +++ b/src/bloaty.proto @@ -32,6 +32,9 @@ message Options { // Build id to source map file names, delimited by '='. repeated string source_map = 15; + // Mach-O specific option to specify path to a dSYM file. + repeated string dsym_path = 16; + // The data sources to scan in each file. At least one data source must be // specified. If more than one source is specified, the output is // hierarchical. diff --git a/tests/macho/dsym-loading.test b/tests/macho/dsym-loading.test new file mode 100644 index 0000000..f10d773 --- /dev/null +++ b/tests/macho/dsym-loading.test @@ -0,0 +1,798 @@ +# Test that bloaty can load debug info from dSYM bundles +# +# This test verifies bloaty's dSYM support: +# 1. The --debug-file option can load dSYM files directly +# 2. The --dsym option can load dSYM bundles +# 3. Auto-loading finds .dSYM/Contents/Resources/DWARF/ +# +# The YAML below was generated from tests/testdata/macho/test_dsym.c. +# +# On macOS: +# clang -g -o test_binary tests/testdata/macho/test_dsym.c +# obj2yaml test_binary > binary.yaml +# obj2yaml test_binary.dSYM/Contents/Resources/DWARF/test_binary > dsym.yaml +# +# Then manually modified to work around yaml2obj roundtrip bug +# https://github.com/llvm/llvm-project/issues/166993 +# - Removed Length field from debug_aranges and debug_line sections +# - Manually adjusted __DWARF segment size and section offsets to match actual content sizes + +# Test 1: Explicitly specify dSYM with --debug-file option +# RUN: rm -rf %t && mkdir -p %t +# RUN: %yaml2obj --docnum=1 %s -o %t/test_binary +# RUN: %yaml2obj --docnum=2 %s -o %t/test_dsym +# RUN: %bloaty %t/test_binary --debug-file=%t/test_dsym -d compileunits --domain=vm | %FileCheck %s + +# Test 2: Explicit dSYM bundle with --dsym flag +# RUN: mkdir -p %t/test_binary.dSYM/Contents/Resources/DWARF +# RUN: cp %t/test_dsym %t/test_binary.dSYM/Contents/Resources/DWARF/test_binary +# RUN: %bloaty %t/test_binary --dsym=%t/test_binary.dSYM -d compileunits --domain=vm | %FileCheck %s + +# Test 3: Test auto-loading (no explicit flags, dSYM bundle in standard location) +# RUN: %bloaty %t/test_binary -d compileunits --domain=vm | %FileCheck %s + +# CHECK: VM SIZE +# CHECK: test_dsym.c + +## Minimal Mach-O executable +--- !mach-o +FileHeader: + magic: 0xFEEDFACF + cputype: 0x100000C + cpusubtype: 0x0 + filetype: 0x2 + ncmds: 16 + sizeofcmds: 744 + flags: 0x200085 + reserved: 0x0 +LoadCommands: + - cmd: LC_SEGMENT_64 + cmdsize: 72 + segname: __PAGEZERO + vmaddr: 0 + vmsize: 4294967296 + fileoff: 0 + filesize: 0 + maxprot: 0 + initprot: 0 + nsects: 0 + flags: 0 + - cmd: LC_SEGMENT_64 + cmdsize: 232 + segname: __TEXT + vmaddr: 4294967296 + vmsize: 16384 + fileoff: 0 + filesize: 16384 + maxprot: 5 + initprot: 5 + nsects: 2 + flags: 0 + Sections: + - sectname: __text + segname: __TEXT + addr: 0x100000328 + size: 48 + offset: 0x328 + align: 2 + reloff: 0x0 + nreloc: 0 + flags: 0x80000400 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 40058052C0035FD6FF8300D1FD7B01A9FD430091BFC31FB8FAFFFF97E00B00B9E00B40B9FD7B41A9FF830091C0035FD6 + - sectname: __unwind_info + segname: __TEXT + addr: 0x100000358 + size: 96 + offset: 0x358 + align: 2 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 010000001C000000000000001C000000000000001C00000002000000280300004000000040000000580300000000000040000000000000000000000000000000030000000C000200140002000000000008000001000000020000000400000000 + - cmd: LC_SEGMENT_64 + cmdsize: 72 + segname: __LINKEDIT + vmaddr: 4294983680 + vmsize: 16384 + fileoff: 16384 + filesize: 808 + maxprot: 1 + initprot: 1 + nsects: 0 + flags: 0 + - cmd: LC_DYLD_CHAINED_FIXUPS + cmdsize: 16 + dataoff: 16384 + datasize: 56 + - cmd: LC_DYLD_EXPORTS_TRIE + cmdsize: 16 + dataoff: 16440 + datasize: 56 + - cmd: LC_SYMTAB + cmdsize: 24 + symoff: 16504 + nsyms: 16 + stroff: 16760 + strsize: 59 + - cmd: LC_DYSYMTAB + cmdsize: 80 + ilocalsym: 0 + nlocalsym: 13 + iextdefsym: 13 + nextdefsym: 3 + iundefsym: 16 + nundefsym: 0 + tocoff: 0 + ntoc: 0 + modtaboff: 0 + nmodtab: 0 + extrefsymoff: 0 + nextrefsyms: 0 + indirectsymoff: 0 + nindirectsyms: 0 + extreloff: 0 + nextrel: 0 + locreloff: 0 + nlocrel: 0 + - cmd: LC_LOAD_DYLINKER + cmdsize: 32 + name: 12 + Content: '/usr/lib/dyld' + ZeroPadBytes: 7 + - cmd: LC_UUID + cmdsize: 24 + uuid: 6E7ACE10-947B-3AB7-A4B1-2D5BB81AE668 + - cmd: LC_BUILD_VERSION + cmdsize: 32 + platform: 1 + minos: 983040 + sdk: 1703936 + ntools: 1 + Tools: + - tool: 3 + version: 80020480 + - cmd: LC_SOURCE_VERSION + cmdsize: 16 + version: 0 + - cmd: LC_MAIN + cmdsize: 24 + entryoff: 816 + stacksize: 0 + - cmd: LC_LOAD_DYLIB + cmdsize: 56 + dylib: + name: 24 + timestamp: 2 + current_version: 88866816 + compatibility_version: 65536 + Content: '/usr/lib/libSystem.B.dylib' + ZeroPadBytes: 6 + - cmd: LC_FUNCTION_STARTS + cmdsize: 16 + dataoff: 16496 + datasize: 8 + - cmd: LC_DATA_IN_CODE + cmdsize: 16 + dataoff: 16504 + datasize: 0 + - cmd: LC_CODE_SIGNATURE + cmdsize: 16 + dataoff: 16912 + datasize: 280 +LinkEditData: + ExportTrie: + TerminalSize: 0 + NodeOffset: 0 + Name: '' + Flags: 0x0 + Address: 0x0 + Other: 0x0 + ImportName: '' + Children: + - TerminalSize: 0 + NodeOffset: 23 + Name: _ + Flags: 0x0 + Address: 0x0 + Other: 0x0 + ImportName: '' + Children: + - TerminalSize: 2 + NodeOffset: 9 + Name: _mh_execute_header + Flags: 0x0 + Address: 0x0 + Other: 0x0 + ImportName: '' + - TerminalSize: 3 + NodeOffset: 13 + Name: foo + Flags: 0x0 + Address: 0x328 + Other: 0x0 + ImportName: '' + - TerminalSize: 3 + NodeOffset: 18 + Name: main + Flags: 0x0 + Address: 0x330 + Other: 0x0 + ImportName: '' + NameList: + - n_strx: 1 + n_type: 0x64 + n_sect: 1 + n_desc: 0 + n_value: 0 + - n_strx: 33 + n_type: 0x64 + n_sect: 0 + n_desc: 0 + n_value: 0 + - n_strx: 35 + n_type: 0x64 + n_sect: 0 + n_desc: 0 + n_value: 0 + - n_strx: 47 + n_type: 0x66 + n_sect: 0 + n_desc: 1 + n_value: 1762531483 + - n_strx: 1 + n_type: 0x2E + n_sect: 1 + n_desc: 0 + n_value: 4294968104 + - n_strx: 22 + n_type: 0x24 + n_sect: 1 + n_desc: 0 + n_value: 4294968104 + - n_strx: 1 + n_type: 0x24 + n_sect: 0 + n_desc: 0 + n_value: 8 + - n_strx: 1 + n_type: 0x4E + n_sect: 1 + n_desc: 0 + n_value: 4294968104 + - n_strx: 1 + n_type: 0x2E + n_sect: 1 + n_desc: 0 + n_value: 4294968112 + - n_strx: 27 + n_type: 0x24 + n_sect: 1 + n_desc: 0 + n_value: 4294968112 + - n_strx: 1 + n_type: 0x24 + n_sect: 0 + n_desc: 0 + n_value: 40 + - n_strx: 1 + n_type: 0x4E + n_sect: 1 + n_desc: 0 + n_value: 4294968112 + - n_strx: 1 + n_type: 0x64 + n_sect: 1 + n_desc: 0 + n_value: 0 + - n_strx: 2 + n_type: 0xF + n_sect: 1 + n_desc: 16 + n_value: 4294967296 + - n_strx: 22 + n_type: 0xF + n_sect: 1 + n_desc: 0 + n_value: 4294968104 + - n_strx: 27 + n_type: 0xF + n_sect: 1 + n_desc: 0 + n_value: 4294968112 + StringTable: + - ' ' + - __mh_execute_header + - _foo + - _main + - '/' + - test_dsym.c + - 'test_dsym.o' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + FunctionStarts: [ 0x328, 0x330 ] + ChainedFixups: [ 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x30, 0x0, + 0x0, 0x0, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ] +... + +## dSYM file with DWARF debug info +--- !mach-o +FileHeader: + magic: 0xFEEDFACF + cputype: 0x100000C + cpusubtype: 0x0 + filetype: 0xA + ncmds: 7 + sizeofcmds: 1240 + flags: 0x0 + reserved: 0x0 +LoadCommands: + - cmd: LC_UUID + cmdsize: 24 + uuid: 6E7ACE10-947B-3AB7-A4B1-2D5BB81AE668 + - cmd: LC_BUILD_VERSION + cmdsize: 24 + platform: 1 + minos: 983040 + sdk: 1703936 + ntools: 0 + - cmd: LC_SYMTAB + cmdsize: 24 + symoff: 4096 + nsyms: 3 + stroff: 4144 + strsize: 33 + - cmd: LC_SEGMENT_64 + cmdsize: 72 + segname: __PAGEZERO + vmaddr: 0 + vmsize: 4294967296 + fileoff: 0 + filesize: 0 + maxprot: 0 + initprot: 0 + nsects: 0 + flags: 0 + - cmd: LC_SEGMENT_64 + cmdsize: 232 + segname: __TEXT + vmaddr: 4294967296 + vmsize: 16384 + fileoff: 0 + filesize: 0 + maxprot: 5 + initprot: 5 + nsects: 2 + flags: 0 + Sections: + - sectname: __text + segname: __TEXT + addr: 0x100000328 + size: 48 + offset: 0x0 + align: 2 + reloff: 0x0 + nreloc: 0 + flags: 0x80000400 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: CFFAEDFE0C000001000000000A00000007000000D804000000000000000000001B000000180000006E7ACE10947B3AB7 + - sectname: __unwind_info + segname: __TEXT + addr: 0x100000358 + size: 96 + offset: 0x0 + align: 2 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: CFFAEDFE0C000001000000000A00000007000000D804000000000000000000001B000000180000006E7ACE10947B3AB7A4B12D5BB81AE66832000000180000000100000000000F0000001A000000000002000000180000000010000003000000 + - cmd: LC_SEGMENT_64 + cmdsize: 72 + segname: __LINKEDIT + vmaddr: 4294983680 + vmsize: 4096 + fileoff: 4096 + filesize: 81 + maxprot: 1 + initprot: 1 + nsects: 0 + flags: 0 + - cmd: LC_SEGMENT_64 + cmdsize: 792 + segname: __DWARF + vmaddr: 4294987776 + vmsize: 4096 + fileoff: 8192 + filesize: 673 + maxprot: 7 + initprot: 3 + nsects: 9 + flags: 0 + Sections: + - sectname: __debug_line + segname: __DWARF + addr: 0x100005000 + size: 85 + offset: 0x2000 + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + - sectname: __debug_aranges + segname: __DWARF + addr: 0x100005055 + size: 48 + offset: 0x2055 + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + - sectname: __debug_info + segname: __DWARF + addr: 0x100005085 + size: 119 + offset: 0x2085 + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + - sectname: __debug_abbrev + segname: __DWARF + addr: 0x1000050FC + size: 98 + offset: 0x20FC + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + - sectname: __debug_str + segname: __DWARF + addr: 0x10000515E + size: 84 + offset: 0x215E + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + - sectname: __apple_namespac + segname: __DWARF + addr: 0x1000051B2 + size: 36 + offset: 0x21B2 + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 485341480100000001000000000000000C000000000000000100000001000600FFFFFFFF + - sectname: __apple_names + segname: __DWARF + addr: 0x1000051D6 + size: 88 + offset: 0x21D6 + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 485341480100000002000000020000000C00000000000000010000000100060000000000010000006A7F9A7C8973880B38000000480000004400000001000000470000000000000040000000010000002E00000000000000 + - sectname: __apple_types + segname: __DWARF + addr: 0x10000522E + size: 79 + offset: 0x222E + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 48534148010000000100000001000000180000000000000004000000010006000300050005000B0006000600000000003080880B3800000050000000010000006F000000240000A4283A0C00000000 + - sectname: __apple_objc + segname: __DWARF + addr: 0x10000527D + size: 36 + offset: 0x227D + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 485341480100000001000000000000000C000000000000000100000001000600FFFFFFFF +LinkEditData: + NameList: + - n_strx: 2 + n_type: 0xF + n_sect: 1 + n_desc: 16 + n_value: 4294967296 + - n_strx: 22 + n_type: 0xF + n_sect: 1 + n_desc: 0 + n_value: 4294968104 + - n_strx: 27 + n_type: 0xF + n_sect: 1 + n_desc: 0 + n_value: 4294968112 + StringTable: + - '' + - '' + - __mh_execute_header + - _foo + - _main +DWARF: + debug_str: + - '' + - 'Apple clang version 17.0.0 (clang-1700.3.19.1)' + - test_dsym.c + - '/' + - . + - foo + - main + - result + - int + debug_abbrev: + - ID: 0 + Table: + - Code: 0x1 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_producer + Form: DW_FORM_strp + - Attribute: DW_AT_language + Form: DW_FORM_data2 + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_LLVM_sysroot + Form: DW_FORM_strp + - Attribute: DW_AT_stmt_list + Form: DW_FORM_sec_offset + - Attribute: DW_AT_comp_dir + Form: DW_FORM_strp + - Attribute: DW_AT_low_pc + Form: DW_FORM_addr + - Attribute: DW_AT_high_pc + Form: DW_FORM_data4 + - Code: 0x2 + Tag: DW_TAG_subprogram + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_low_pc + Form: DW_FORM_addr + - Attribute: DW_AT_high_pc + Form: DW_FORM_data4 + - Attribute: DW_AT_APPLE_omit_frame_ptr + Form: DW_FORM_flag_present + - Attribute: DW_AT_frame_base + Form: DW_FORM_exprloc + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_decl_file + Form: DW_FORM_data1 + - Attribute: DW_AT_decl_line + Form: DW_FORM_data1 + - Attribute: DW_AT_prototyped + Form: DW_FORM_flag_present + - Attribute: DW_AT_type + Form: DW_FORM_ref4 + - Attribute: DW_AT_external + Form: DW_FORM_flag_present + - Code: 0x3 + Tag: DW_TAG_subprogram + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_low_pc + Form: DW_FORM_addr + - Attribute: DW_AT_high_pc + Form: DW_FORM_data4 + - Attribute: DW_AT_frame_base + Form: DW_FORM_exprloc + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_decl_file + Form: DW_FORM_data1 + - Attribute: DW_AT_decl_line + Form: DW_FORM_data1 + - Attribute: DW_AT_prototyped + Form: DW_FORM_flag_present + - Attribute: DW_AT_type + Form: DW_FORM_ref4 + - Attribute: DW_AT_external + Form: DW_FORM_flag_present + - Code: 0x4 + Tag: DW_TAG_variable + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_location + Form: DW_FORM_exprloc + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_decl_file + Form: DW_FORM_data1 + - Attribute: DW_AT_decl_line + Form: DW_FORM_data1 + - Attribute: DW_AT_type + Form: DW_FORM_ref4 + - Code: 0x5 + Tag: DW_TAG_base_type + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_encoding + Form: DW_FORM_data1 + - Attribute: DW_AT_byte_size + Form: DW_FORM_data1 + debug_aranges: + - Length: 0x2C + Version: 2 + CuOffset: 0x0 + AddressSize: 0x8 + Descriptors: + - Address: 0x100000328 + Length: 0x30 + debug_info: + - Length: 0x73 + Version: 4 + AbbrevTableID: 0 + AbbrOffset: 0x0 + AddrSize: 8 + Entries: + - AbbrCode: 0x1 + Values: + - Value: 0x1 + - Value: 0x1D + - Value: 0x30 + - Value: 0x3C + - Value: 0x0 + - Value: 0x3E + - Value: 0x100000328 + - Value: 0x30 + - AbbrCode: 0x2 + Values: + - Value: 0x100000328 + - Value: 0x8 + - Value: 0x1 + - Value: 0x1 + BlockData: [ 0x6F ] + - Value: 0x40 + - Value: 0x1 + - Value: 0x5 + - Value: 0x1 + - Value: 0x6F + - Value: 0x1 + - AbbrCode: 0x3 + Values: + - Value: 0x100000330 + - Value: 0x28 + - Value: 0x1 + BlockData: [ 0x6D ] + - Value: 0x44 + - Value: 0x1 + - Value: 0x7 + - Value: 0x1 + - Value: 0x6F + - Value: 0x1 + - AbbrCode: 0x4 + Values: + - Value: 0x2 + BlockData: [ 0x8F, 0x8 ] + - Value: 0x49 + - Value: 0x1 + - Value: 0x8 + - Value: 0x6F + - AbbrCode: 0x0 + - AbbrCode: 0x5 + Values: + - Value: 0x50 + - Value: 0x5 + - Value: 0x4 + - AbbrCode: 0x0 + debug_line: + - Length: 81 + Version: 4 + PrologueLength: 35 + MinInstLength: 1 + MaxOpsPerInst: 1 + DefaultIsStmt: 1 + LineBase: 251 + LineRange: 14 + OpcodeBase: 13 + StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ] + Files: + - Name: test_dsym.c + DirIdx: 0 + ModTime: 0 + Length: 0 + Opcodes: + - Opcode: DW_LNS_extended_op + ExtLen: 9 + SubOpcode: DW_LNE_set_address + Data: 4294968104 + - Opcode: DW_LNS_set_column + Data: 17 + - Opcode: DW_LNS_set_prologue_end + Data: 0 + - Opcode: 0x16 + Data: 0 + - Opcode: DW_LNS_set_column + Data: 0 + - Opcode: 0x84 + Data: 0 + - Opcode: DW_LNS_set_column + Data: 16 + - Opcode: DW_LNS_set_prologue_end + Data: 0 + - Opcode: 0xF3 + Data: 0 + - Opcode: DW_LNS_set_column + Data: 7 + - Opcode: DW_LNS_negate_stmt + Data: 0 + - Opcode: 0x4A + Data: 0 + - Opcode: DW_LNS_set_column + Data: 10 + - Opcode: DW_LNS_negate_stmt + Data: 0 + - Opcode: 0x4B + Data: 0 + - Opcode: DW_LNS_set_column + Data: 3 + - Opcode: DW_LNS_negate_stmt + Data: 0 + - Opcode: DW_LNS_set_epilogue_begin + Data: 0 + - Opcode: 0x4A + Data: 0 + - Opcode: DW_LNS_advance_pc + Data: 12 + - Opcode: DW_LNS_extended_op + ExtLen: 1 + SubOpcode: DW_LNE_end_sequence + Data: 0 +... diff --git a/tests/macho/no-dsym.test b/tests/macho/no-dsym.test new file mode 100644 index 0000000..469f958 --- /dev/null +++ b/tests/macho/no-dsym.test @@ -0,0 +1,247 @@ +# Test that bloaty handles missing dSYM gracefully +# +# When a dSYM bundle doesn't exist, bloaty should continue without error. +# +# The YAML below was generated from tests/testdata/macho/test_dsym.c. +# +# On macOS: +# clang -g -O2 -o test_binary tests/testdata/macho/test_dsym.c +# strip -S test_binary +# obj2yaml test_binary > binary.yaml + +# RUN: %yaml2obj %s -o %t.binary +# RUN: %bloaty %t.binary -d segments --domain=vm | %FileCheck %s + +# Should still work, just without compile unit info +# CHECK: VM SIZE +# CHECK: __LINKEDIT +# CHECK: __TEXT + +## Minimal Mach-O executable +--- !mach-o +FileHeader: + magic: 0xFEEDFACF + cputype: 0x100000C + cpusubtype: 0x0 + filetype: 0x2 + ncmds: 15 + sizeofcmds: 760 + flags: 0x200085 + reserved: 0x0 +LoadCommands: + - cmd: LC_SEGMENT_64 + cmdsize: 72 + segname: __PAGEZERO + vmaddr: 0 + vmsize: 4294967296 + fileoff: 0 + filesize: 0 + maxprot: 0 + initprot: 0 + nsects: 0 + flags: 0 + - cmd: LC_SEGMENT_64 + cmdsize: 232 + segname: __TEXT + vmaddr: 4294967296 + vmsize: 16384 + fileoff: 0 + filesize: 16384 + maxprot: 5 + initprot: 5 + nsects: 2 + flags: 0 + Sections: + - sectname: __text + segname: __TEXT + addr: 0x100000348 + size: 72 + offset: 0x348 + align: 2 + reloff: 0x0 + nreloc: 0 + flags: 0x80000400 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 00781F53C0035FD6F44FBEA9FD7B01A9FD43009160008052FAFFFF97F30300AA40008052F7FFFF970804130B13390011E00313AAF3FFFF976002000BFD7B41A9F44FC2A8C0035FD6 + - sectname: __unwind_info + segname: __TEXT + addr: 0x100000390 + size: 96 + offset: 0x390 + align: 2 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 010000001C000000000000001C000000000000001C00000002000000480300004000000040000000900300000000000040000000000000000000000000000000030000000C000200140002000000000008000001000000020100000400000000 + - cmd: LC_SEGMENT_64 + cmdsize: 72 + segname: __LINKEDIT + vmaddr: 4294983680 + vmsize: 16384 + fileoff: 16384 + filesize: 488 + maxprot: 1 + initprot: 1 + nsects: 0 + flags: 0 + - cmd: LC_DYLD_INFO_ONLY + cmdsize: 48 + rebase_off: 0 + rebase_size: 0 + bind_off: 0 + bind_size: 0 + weak_bind_off: 0 + weak_bind_size: 0 + lazy_bind_off: 0 + lazy_bind_size: 0 + export_off: 16384 + export_size: 72 + - cmd: LC_SYMTAB + cmdsize: 24 + symoff: 16464 + nsyms: 4 + stroff: 16528 + strsize: 64 + - cmd: LC_DYSYMTAB + cmdsize: 80 + ilocalsym: 0 + nlocalsym: 0 + iextdefsym: 0 + nextdefsym: 3 + iundefsym: 3 + nundefsym: 1 + tocoff: 0 + ntoc: 0 + modtaboff: 0 + nmodtab: 0 + extrefsymoff: 0 + nextrefsyms: 0 + indirectsymoff: 0 + nindirectsyms: 0 + extreloff: 0 + nextrel: 0 + locreloff: 0 + nlocrel: 0 + - cmd: LC_LOAD_DYLINKER + cmdsize: 32 + name: 12 + Content: '/usr/lib/dyld' + ZeroPadBytes: 7 + - cmd: LC_UUID + cmdsize: 24 + uuid: DBCBC096-0980-3DA0-AC54-F36396C8B8F3 + - cmd: LC_BUILD_VERSION + cmdsize: 32 + platform: 1 + minos: 720896 + sdk: 1703936 + ntools: 1 + Tools: + - tool: 3 + version: 80020480 + - cmd: LC_SOURCE_VERSION + cmdsize: 16 + version: 0 + - cmd: LC_MAIN + cmdsize: 24 + entryoff: 848 + stacksize: 0 + - cmd: LC_LOAD_DYLIB + cmdsize: 56 + dylib: + name: 24 + timestamp: 2 + current_version: 88866816 + compatibility_version: 65536 + Content: '/usr/lib/libSystem.B.dylib' + ZeroPadBytes: 6 + - cmd: LC_FUNCTION_STARTS + cmdsize: 16 + dataoff: 16456 + datasize: 8 + - cmd: LC_DATA_IN_CODE + cmdsize: 16 + dataoff: 16464 + datasize: 0 + - cmd: LC_CODE_SIGNATURE + cmdsize: 16 + dataoff: 16592 + datasize: 280 +LinkEditData: + ExportTrie: + TerminalSize: 0 + NodeOffset: 0 + Name: '' + Flags: 0x0 + Address: 0x0 + Other: 0x0 + ImportName: '' + Children: + - TerminalSize: 0 + NodeOffset: 23 + Name: _ + Flags: 0x0 + Address: 0x0 + Other: 0x0 + ImportName: '' + Children: + - TerminalSize: 2 + NodeOffset: 9 + Name: _mh_execute_header + Flags: 0x0 + Address: 0x0 + Other: 0x0 + ImportName: '' + - TerminalSize: 3 + NodeOffset: 13 + Name: external_call + Flags: 0x0 + Address: 0x348 + Other: 0x0 + ImportName: '' + - TerminalSize: 3 + NodeOffset: 18 + Name: main + Flags: 0x0 + Address: 0x350 + Other: 0x0 + ImportName: '' + NameList: + - n_strx: 4 + n_type: 0xF + n_sect: 1 + n_desc: 16 + n_value: 4294967296 + - n_strx: 24 + n_type: 0xF + n_sect: 1 + n_desc: 0 + n_value: 4294968136 + - n_strx: 39 + n_type: 0xF + n_sect: 1 + n_desc: 0 + n_value: 4294968144 + - n_strx: 45 + n_type: 0x1 + n_sect: 0 + n_desc: 256 + n_value: 0 + StringTable: + - '' + - '' + - '' + - '' + - __mh_execute_header + - _external_call + - _main + - dyld_stub_binder + - '' + - '' + FunctionStarts: [ 0x348, 0x350 ] +... diff --git a/tests/testdata/macho/test_dsym.c b/tests/testdata/macho/test_dsym.c new file mode 100644 index 0000000..4aee22e --- /dev/null +++ b/tests/testdata/macho/test_dsym.c @@ -0,0 +1,9 @@ +// Test file for dSYM generation tests +// Used to generate YAML test cases in tests/macho/dsym-*.test + +int foo() { return 42; } + +int main() { + int result = foo(); + return result; +}