From 20767d1ad4d80359460dd6d15b7c1ef09005287c Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere Date: Mon, 7 Jan 2019 23:27:25 +0000 Subject: [PATCH] [dsymutil] Upstream unobfuscation logic. The unobufscation support for BCSymbolMaps was the last piece of code that hasn't been upstreamed yet. This patch contains a reworked version of the existing code and relevant tests. Differential revision: https://reviews.llvm.org/D56346 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@350580 91177308-0d34-0410-b5e6-96231b3b80d8 --- test/tools/dsymutil/ARM/obfuscated.test | 166 ++++++++++++++++++ ...E828A486-8433-3A5E-B6DB-A6294D28133D.plist | 7 + test/tools/dsymutil/Inputs/obfuscated.2.arm64 | Bin 0 -> 10339 bytes test/tools/dsymutil/Inputs/obfuscated.2.map | 22 +++ test/tools/dsymutil/Inputs/obfuscated.arm64 | Bin 0 -> 10434 bytes test/tools/dsymutil/Inputs/obfuscated.map | 17 ++ test/tools/dsymutil/cmdline.test | 1 + tools/dsymutil/CMakeLists.txt | 1 + tools/dsymutil/DebugMap.h | 12 +- tools/dsymutil/DwarfLinker.cpp | 25 +-- tools/dsymutil/DwarfStreamer.cpp | 93 +++++++++- tools/dsymutil/DwarfStreamer.h | 7 +- tools/dsymutil/LinkUtils.h | 6 + tools/dsymutil/MachODebugMapParser.cpp | 3 +- tools/dsymutil/MachOUtils.cpp | 6 +- tools/dsymutil/MachOUtils.h | 7 +- tools/dsymutil/NonRelocatableStringpool.cpp | 6 + tools/dsymutil/NonRelocatableStringpool.h | 7 +- tools/dsymutil/SymbolMap.cpp | 162 +++++++++++++++++ tools/dsymutil/SymbolMap.h | 54 ++++++ tools/dsymutil/dsymutil.cpp | 27 ++- 21 files changed, 601 insertions(+), 28 deletions(-) create mode 100644 test/tools/dsymutil/ARM/obfuscated.test create mode 100644 test/tools/dsymutil/Inputs/E828A486-8433-3A5E-B6DB-A6294D28133D.plist create mode 100644 test/tools/dsymutil/Inputs/obfuscated.2.arm64 create mode 100644 test/tools/dsymutil/Inputs/obfuscated.2.map create mode 100644 test/tools/dsymutil/Inputs/obfuscated.arm64 create mode 100644 test/tools/dsymutil/Inputs/obfuscated.map create mode 100644 tools/dsymutil/SymbolMap.cpp create mode 100644 tools/dsymutil/SymbolMap.h diff --git a/test/tools/dsymutil/ARM/obfuscated.test b/test/tools/dsymutil/ARM/obfuscated.test new file mode 100644 index 00000000000..9ce684cfb7e --- /dev/null +++ b/test/tools/dsymutil/ARM/obfuscated.test @@ -0,0 +1,166 @@ +REQUIRES: system-darwin + +RUN: dsymutil --symbol-map %p/../Inputs/obfuscated.map %p/../Inputs/obfuscated.arm64 -f -o - \ +RUN: | llvm-dwarfdump -v - \ +RUN: | FileCheck %s + +RUN: dsymutil --symbol-map %p/../Inputs/obfuscated.map %p/../Inputs/obfuscated.arm64 -f -o - \ +RUN: | llvm-dwarfdump -v - \ +RUN: | FileCheck --check-prefix=NOHIDDEN %s + +RUN: dsymutil --symbol-map %p/../Inputs/obfuscated.2.map %p/../Inputs/obfuscated.2.arm64 -f -o - \ +RUN: | llvm-dwarfdump -v - \ +RUN: | FileCheck --check-prefix=NOHIDDEN %s + +// Run with plist and make sure dsymutil finds it. +RUN: mkdir -p %t.dSYM/Contents/Resources/DWARF/ +RUN: mkdir -p %t.mapdir +RUN: cp %p/../Inputs/obfuscated.arm64 %t.dSYM/Contents/Resources/DWARF/ +RUN: cp %p/../Inputs/E828A486-8433-3A5E-B6DB-A6294D28133D.plist %t.dSYM/Contents/Resources/ +RUN: cp %p/../Inputs/obfuscated.map %t.mapdir/506AA50A-6B26-3B37-86D2-DC6EBD57B720.bcsymbolmap +RUN: dsymutil --symbol-map %t.mapdir %t.dSYM 2>&1 | FileCheck --check-prefix=OBFUSCATING %s + +// Run without plist and make sure dsymutil doesn't crash. +RUN: rm %t.dSYM/Contents/Resources/E828A486-8433-3A5E-B6DB-A6294D28133D.plist +RUN: dsymutil --symbol-map %t.mapdir %t.dSYM 2>&1 | FileCheck --check-prefix=NOTOBFUSCATING %s + +OBFUSCATING-NOT: not unobfuscating + +NOTOBFUSCATING: not unobfuscating + +NOHIDDEN-NOT: __hidden# + +CHECK: .debug_info contents: + +CHECK: DW_TAG_compile_unit [1] * +CHECK: DW_AT_producer [DW_FORM_strp] ( {{.*}} "Apple LLVM version 7.0.0 (clang-700.2.38.2)") +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "main.c") +CHECK: DW_AT_comp_dir [DW_FORM_strp] ( {{.*}} "/Users/steven/dev/alpena/tests/src") +CHECK: DW_TAG_subprogram [2] +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "main") + +CHECK: DW_TAG_compile_unit [1] * +CHECK: DW_AT_producer [DW_FORM_strp] ( {{.*}} "Apple LLVM version 7.0.0 (clang-700.2.38.2)") +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "one.c") +CHECK: DW_AT_comp_dir [DW_FORM_strp] ( {{.*}} "/Users/steven/dev/alpena/tests/src") +CHECK: DW_TAG_subprogram [2] +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "one") + +CHECK: DW_TAG_compile_unit [1] * +CHECK: DW_AT_producer [DW_FORM_strp] ( {{.*}} "Apple LLVM version 7.0.0 (clang-700.2.38.2)") +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "two.c") +CHECK: DW_AT_comp_dir [DW_FORM_strp] ( {{.*}} "/Users/steven/dev/alpena/tests/src") +CHECK: DW_TAG_subprogram [2] +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "two") + +CHECK: DW_TAG_compile_unit [1] * +CHECK: DW_AT_producer [DW_FORM_strp] ( {{.*}} "Apple LLVM version 7.0.0 (clang-700.2.38.2)") +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "three.c") +CHECK: DW_AT_comp_dir [DW_FORM_strp] ( {{.*}} "/Users/steven/dev/alpena/tests/src") +CHECK: DW_TAG_subprogram [2] +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "three") + +CHECK: DW_TAG_compile_unit [1] * +CHECK: DW_AT_producer [DW_FORM_strp] ( {{.*}} "Apple LLVM version 7.0.0 (clang-700.2.38.2)") +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "four.c") +CHECK: DW_AT_stmt_list [DW_FORM_data4] (0x0000011e) +CHECK: DW_AT_comp_dir [DW_FORM_strp] ( {{.*}} "/Users/steven/dev/alpena/tests/src") +CHECK: DW_TAG_subprogram [2] +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "four") + +CHECK: DW_TAG_compile_unit [1] * +CHECK: DW_AT_producer [DW_FORM_strp] ( {{.*}} "Apple LLVM version 7.0.0 (clang-700.2.38.2)") +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "five.c") +CHECK: DW_AT_comp_dir [DW_FORM_strp] ( {{.*}} "/Users/steven/dev/alpena/tests/src") +CHECK: DW_TAG_subprogram [2] +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "five") + +CHECK: DW_TAG_compile_unit [1] * +CHECK: DW_AT_producer [DW_FORM_strp] ( {{.*}} "Apple LLVM version 7.0.0 (clang-700.2.38.2)") +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "six.c") +CHECK: DW_AT_comp_dir [DW_FORM_strp] ( {{.*}} "/Users/steven/dev/alpena/tests/src") +CHECK: DW_TAG_subprogram [2] +CHECK: DW_AT_name [DW_FORM_strp] ( {{.*}} "six") + +CHECK: .debug_line contents: +CHECK: file_names[ 1]: +CHECK: name: "main.c" +CHECK: dir_index: 0 +CHECK: mod_time: 0x00000000 +CHECK: file_names[ 1]: +CHECK: name: "one.c" +CHECK: dir_index: 0 +CHECK: mod_time: 0x00000000 +CHECK: length: 0x00000000 +CHECK: file_names[ 1]: +CHECK: name: "two.c" +CHECK: dir_index: 0 +CHECK: mod_time: 0x00000000 +CHECK: length: 0x00000000 +CHECK: file_names[ 1]: +CHECK: name: "three.c" +CHECK: dir_index: 0 +CHECK: mod_time: 0x00000000 +CHECK: length: 0x00000000 +CHECK: file_names[ 1]: +CHECK: name: "four.c" +CHECK: dir_index: 0 +CHECK: mod_time: 0x00000000 +CHECK: length: 0x00000000 +CHECK: file_names[ 1]: +CHECK: name: "five.c" +CHECK: dir_index: 0 +CHECK: mod_time: 0x00000000 +CHECK: length: 0x00000000 +CHECK: file_names[ 1]: +CHECK: name: "six.c" +CHECK: dir_index: 0 +CHECK: mod_time: 0x00000000 +CHECK: length: 0x00000000 + +CHECK: .debug_pubnames contents: +CHECK: length = 0x00000017 version = 0x0002 unit_offset = 0x00000000 unit_size = 0x00000044 +CHECK: 0x0000002e "main" +CHECK: length = 0x00000016 version = 0x0002 unit_offset = 0x00000044 unit_size = 0x00000044 +CHECK: 0x0000002e "one" +CHECK: length = 0x00000016 version = 0x0002 unit_offset = 0x00000088 unit_size = 0x00000044 +CHECK: 0x0000002e "two" +CHECK: length = 0x00000018 version = 0x0002 unit_offset = 0x000000cc unit_size = 0x00000044 +CHECK: 0x0000002e "three" +CHECK: length = 0x00000017 version = 0x0002 unit_offset = 0x00000110 unit_size = 0x00000044 +CHECK: 0x0000002e "four" +CHECK: length = 0x00000017 version = 0x0002 unit_offset = 0x00000154 unit_size = 0x00000044 +CHECK: 0x0000002e "five" +CHECK: length = 0x00000016 version = 0x0002 unit_offset = 0x00000198 unit_size = 0x00000044 +CHECK: 0x0000002e "six" + +CHECK: .apple_names contents: + +CHECK: String: 0x00000091 "five" +CHECK-NEXT: Data 0 [ +CHECK-NEXT: Atom[0]: 0x00000182 +CHECK-NEXT: ] +CHECK: String: 0x0000009c "six" +CHECK-NEXT: Data 0 [ +CHECK-NEXT: Atom[0]: 0x000001c6 +CHECK-NEXT: ] +CHECK: String: 0x00000078 "three" +CHECK-NEXT: Data 0 [ +CHECK-NEXT: Atom[0]: 0x000000fa +CHECK-NEXT: ] +CHECK: String: 0x0000006c "two" +CHECK-NEXT: Data 0 [ +CHECK-NEXT: Atom[0]: 0x000000b6 +CHECK-NEXT: ] +CHECK: String: 0x00000057 "main" +CHECK-NEXT: Data 0 [ +CHECK-NEXT: Atom[0]: 0x0000002e +CHECK-NEXT: ] +CHECK: String: 0x00000085 "four" +CHECK-NEXT: Data 0 [ +CHECK-NEXT: Atom[0]: 0x0000013e +CHECK-NEXT: ] +CHECK: String: 0x00000062 "one" +CHECK-NEXT: Data 0 [ +CHECK-NEXT: Atom[0]: 0x00000072 +CHECK-NEXT: ] diff --git a/test/tools/dsymutil/Inputs/E828A486-8433-3A5E-B6DB-A6294D28133D.plist b/test/tools/dsymutil/Inputs/E828A486-8433-3A5E-B6DB-A6294D28133D.plist new file mode 100644 index 00000000000..adf7dbf0e02 --- /dev/null +++ b/test/tools/dsymutil/Inputs/E828A486-8433-3A5E-B6DB-A6294D28133D.plist @@ -0,0 +1,7 @@ + + + + DBGOriginalUUID + 506AA50A-6B26-3B37-86D2-DC6EBD57B720 + + \ No newline at end of file diff --git a/test/tools/dsymutil/Inputs/obfuscated.2.arm64 b/test/tools/dsymutil/Inputs/obfuscated.2.arm64 new file mode 100644 index 0000000000000000000000000000000000000000..b40e023eb4916cca1fe38388ce12b030c1a51eda GIT binary patch literal 10339 zcmeHNO>A355Pr|jbJ8?*5>i!-fHd zv$NmM&dzSK``(SeZf*8imTdv`0(S%7jG-iH1Rnc=cYk|r@Y9)zKR0Ov7^+cp6xU+2qqxuTIT94}wO1XPQnI5$7i`6k>kr<@x4XzkRJ)$C3jD zX4DB$^;W7}8^-(Nyd3<6hKYq^Q`BYER2d3FRI1DAic;zRt*=Quh^eCBczXzs>m{C8 z^ztRI3@voL4+!s|LbJB;FH@rvpMriN z&LRLqyv`)&o+2xJ*wT&%I3}wy7LkNrEj1zzXgOs$E4iGYcKiGYcKiGYc~eC3&3WJjs(JS4o~F`5eiwgWL}t&6AA(xh*XUZ^g?jS3Nifr?PMk7E7sI0cylz zE}QiV2abw>kO&ILg2M5j@DK<^dDwwI4Veg-2$%?%2$%?%2$%?%2$%?%2$%?%2$%@` zpCf=TPa+n+wX*HM<9py2UflVwAZ<8u)A9RZAw#q)!q)=PUfUVytvmZ;?>PHhXUug* zUFSa6IqW*eT_@o>kGsxet`m)&u1EF)=o5Z7_|C3FHwifA`?M><*8@XNw09`dvuy;p z4j=@+u-GXATpJJqUvcadVO$~brN~YZ9#IH2#gP}edLE5jjNul zuM7W3`zdA5a}#_O9&9D;-&6KH6P@jED0_~3XZyX7rqY6=+}ZvWWzR9~Z2yU}=Loj5 z9|g~4JTXQO0;`2XsUR(|He~Uroa#G#42)UGA_4bZNYVtu6gGj2uZ9UmCTwydXoA5A zo6s_s9pU)HCgg4sdZQHbw~Ib&kMDr|zQg8Sf-^;J8>~2l)?ttYXcvI-qX(O9IepZg zzzW<`T7bBq{T$c_VO!3rh^{!(8U)wE27_>8PPB-wc(XOgc~IXEVe~!W#?>`{ceNtA z;{DbjAA`EcAl&#IgXoIST7z5$^_L97jn^4OSA2ya_Mm+z4x2`NAT|>JWvy?}z6}5( z4_H`;#?eHj`3bytZTziJW!T458J2=7<3_IpNGVlL_}96Fgo)p%!RuV^BjI24vRM;y z(3M26{{&>W0>J%I2?_9E`i0~)4A1}k4L_af!|?&3&bIJlA(`+|cy<9h!2bYYP50av z2VY#dc>OFqM^-Oh{|L%Mz&``yCrazU7l1DTUjhCG_(xz|1c;A21`=iw%9|(vVSb~G z7)UrRDF1*zK$tLCoFo}j8H?9|kuVofewXTbj3kD}h~v?1@Y6pe95cot3L|%{-B618 L7%Q6y9BDoiv0rZ4nA8Y649lMW~7i2~a^5LqhWdC4@*Igc#ZJCUNE1R_s7p zA&5+bdV>Q8Bv4Ng;({twRaJ4K2sjmS;edc6sv;z~lv6>J@9n&|S&uhq(j)AXo@d^h zZ@!&*GduP^@Ao%;|EJBeYzxu~ZH8_n$<`uWo%&-Wd8%3;6N zVOcGxucADK@*e0IRH>=-iP2X_r(b|!h<`D|XPU(GBY;ZHjJ`5c-|F{Yt@nxLq9RH@ zrK;XaRazVQF2AiOe{~aL(cA)QvQ~pSDoRzV%h^icGRL>}j^;y50}0Q!h54ji^Qi@| zQ1+J4!{qyb`Fa92>lWjityGut#d*e?d>5IoDa049*3BIaN-Y=P%olSiUz{uXcKv!> zWYvVE$fzi*p`7%e-YZ^;dN1} zkWrP>i`h~^m8(k)d>yMAPkUV?%t!Z4*xtMko*O?oIXW^fd-zLZo;<+cn3Tp89vAT} zfEe+5IX(R>qo}YoPA1r`o4(=!%6RCWCQ&NqWtZnwCYxRI-q@P2OT#UzTPo3Iz_*ks z&U=-H@pbX|rg$=4JLKbS*1%V?^?ZL}zS4cs;}WlouaGZ#xb7ywQSIVB6 zr7N0z=M27cx8RE&b?l8J5Jw=6KpcTM0&xW52>kaVK+jWq@f&l7pX2tj{Tkc%vYli5 z2-^j=huE&N{S4c0u|4J6`XPmW%I3ITp1bLnn_9@{a$fOpzX}|8sJv>!sa(Jp%ix@4&1yLedu7<7WcAq&2q3e-z*)?XeYp7$fI+;tx&BYO|ik zC&@{uL|3Bbn2z@eISj4m_!yotlsKfXMA9)Gp8*uXaS`4NP~woj5?#l1TmdM8<2!i& zgc67Jm54j0<0pV3IIh7vgwPO&^p#a`Ovm+Tj+=0=Fo*P&m2pf*8?D1I)PZ83@Va!u z{RMMK-!)EZIi{nRI0{h5x*wmm{a5>)`1GmJWFsxbIj$!Z+97*_wN13zZb$2Nw=+56 z-d%IMYwn?%`%um8tGNR;H&t_=thvW(Zgb7upPY0P$%$mk=gwVVKMoqlqW`V8xn2r+ zG+*`v8v%EF>!j1PV+JJOm_fF|ZW)lVV+Pp-yJbL{j~QeO?3Q6LV2};4TZW;4L6(1$ z;Sffr_Ya+YN1?%rKd!rJ1hqCGkoP3fh_g`$UPO(c)31Sp7fB=NylX)4B4`Aia196! zvPRIE)_{;829tXX+1uNY&FJ8M2*^_Xw?U;s1PJvenV&!oXO_91@{`G8gnRgjWp1bg z`AKGO2!m3HM{vO~D22TeGypXAQhE&W^xv+dH*?ycB;>QLq;(S_Hf^IOD80o-Zz;X| z;`yFy&@6O(`h3qfDAUk&Xcg^i&<~;OP&&lFhW-Gh^Og8-KuPF~BKuqVfLwu6^%5Bn z`feecHZKXCi)7d6147>$RMqdRSQeeVWFO~tnIoJ2TZ3r!=R$K#U`DQW2Woo0CO#@A HJHh-5T*?4# literal 0 HcmV?d00001 diff --git a/test/tools/dsymutil/Inputs/obfuscated.map b/test/tools/dsymutil/Inputs/obfuscated.map new file mode 100644 index 00000000000..30fed8bf9b5 --- /dev/null +++ b/test/tools/dsymutil/Inputs/obfuscated.map @@ -0,0 +1,17 @@ +one +two +three +four +five +six +.str +Apple LLVM version 7.0.0 (clang-700.2.38.2) +main +main.c +/Users/steven/dev/alpena/tests/src +one.c +two.c +three.c +four.c +five.c +six.c diff --git a/test/tools/dsymutil/cmdline.test b/test/tools/dsymutil/cmdline.test index c2ddead320a..60a1a0a2d10 100644 --- a/test/tools/dsymutil/cmdline.test +++ b/test/tools/dsymutil/cmdline.test @@ -17,6 +17,7 @@ HELP: -num-threads= HELP: -o= HELP: -oso-prepend-path= HELP: -papertrail +HELP: -symbol-map HELP: -symtab HELP: -toolchain HELP: -update diff --git a/tools/dsymutil/CMakeLists.txt b/tools/dsymutil/CMakeLists.txt index f41a6fd2850..480f78fb188 100644 --- a/tools/dsymutil/CMakeLists.txt +++ b/tools/dsymutil/CMakeLists.txt @@ -20,6 +20,7 @@ add_llvm_tool(dsymutil MachODebugMapParser.cpp MachOUtils.cpp NonRelocatableStringpool.cpp + SymbolMap.cpp DEPENDS intrinsics_gen diff --git a/tools/dsymutil/DebugMap.h b/tools/dsymutil/DebugMap.h index c9883773d3d..d8de37e33d5 100644 --- a/tools/dsymutil/DebugMap.h +++ b/tools/dsymutil/DebugMap.h @@ -75,7 +75,7 @@ class DebugMapObject; class DebugMap { Triple BinaryTriple; std::string BinaryPath; - + std::vector BinaryUUID; using ObjectContainer = std::vector>; ObjectContainer Objects; @@ -89,8 +89,10 @@ class DebugMap { ///@} public: - DebugMap(const Triple &BinaryTriple, StringRef BinaryPath) - : BinaryTriple(BinaryTriple), BinaryPath(BinaryPath) {} + DebugMap(const Triple &BinaryTriple, StringRef BinaryPath, + ArrayRef BinaryUUID = ArrayRef()) + : BinaryTriple(BinaryTriple), BinaryPath(BinaryPath), + BinaryUUID(BinaryUUID.begin(), BinaryUUID.end()) {} using const_iterator = ObjectContainer::const_iterator; @@ -113,6 +115,10 @@ public: const Triple &getTriple() const { return BinaryTriple; } + const ArrayRef getUUID() const { + return ArrayRef(BinaryUUID); + } + StringRef getBinaryPath() const { return BinaryPath; } void print(raw_ostream &OS) const; diff --git a/tools/dsymutil/DwarfLinker.cpp b/tools/dsymutil/DwarfLinker.cpp index 2862739fd73..0743cfc3ed4 100644 --- a/tools/dsymutil/DwarfLinker.cpp +++ b/tools/dsymutil/DwarfLinker.cpp @@ -1701,6 +1701,8 @@ void DwarfLinker::patchLineTableForUnit(CompileUnit &Unit, DWARFDataExtractor LineExtractor( OrigDwarf.getDWARFObj(), OrigDwarf.getDWARFObj().getLineSection(), OrigDwarf.isLittleEndian(), Unit.getOrigUnit().getAddressByteSize()); + if (Options.Translator) + return Streamer->translateLineTable(LineExtractor, StmtOffset, Options); Error Err = LineTable.parse(LineExtractor, &StmtOffset, OrigDwarf, &Unit.getOrigUnit(), DWARFContext::dumpWarning); @@ -2245,17 +2247,16 @@ void DwarfLinker::DIECloner::cloneAllCompileUnits( if (Linker.Options.NoOutput) continue; - if (LLVM_LIKELY(!Linker.Options.Update)) { - // FIXME: for compatibility with the classic dsymutil, we emit an empty - // line table for the unit, even if the unit doesn't actually exist in - // the DIE tree. + // FIXME: for compatibility with the classic dsymutil, we emit + // an empty line table for the unit, even if the unit doesn't + // actually exist in the DIE tree. + if (LLVM_LIKELY(!Linker.Options.Update) || Linker.Options.Translator) Linker.patchLineTableForUnit(*CurrentUnit, DwarfContext, Ranges, DMO); - Linker.emitAcceleratorEntriesForUnit(*CurrentUnit); - Linker.patchRangesForUnit(*CurrentUnit, DwarfContext, DMO); - Linker.Streamer->emitLocationsForUnit(*CurrentUnit, DwarfContext); - } else { - Linker.emitAcceleratorEntriesForUnit(*CurrentUnit); - } + Linker.emitAcceleratorEntriesForUnit(*CurrentUnit); + if (Linker.Options.Update) + continue; + Linker.patchRangesForUnit(*CurrentUnit, DwarfContext, DMO); + Linker.Streamer->emitLocationsForUnit(*CurrentUnit, DwarfContext); } if (Linker.Options.NoOutput) @@ -2380,7 +2381,7 @@ bool DwarfLinker::link(const DebugMap &Map) { // This Dwarf string pool which is used for emission. It must be used // serially as the order of calling getStringOffset matters for // reproducibility. - OffsetsStringPool OffsetsStringPool; + OffsetsStringPool OffsetsStringPool(Options.Translator); // ODR Contexts for the link. DeclContextTree ODRContexts; @@ -2649,7 +2650,7 @@ bool DwarfLinker::link(const DebugMap &Map) { pool.wait(); } - return Options.NoOutput ? true : Streamer->finish(Map); + return Options.NoOutput ? true : Streamer->finish(Map, Options.Translator); } // namespace dsymutil bool linkDwarf(raw_fd_ostream &OutFile, BinaryHolder &BinHolder, diff --git a/tools/dsymutil/DwarfStreamer.cpp b/tools/dsymutil/DwarfStreamer.cpp index ef798be7bdf..28088ff3369 100644 --- a/tools/dsymutil/DwarfStreamer.cpp +++ b/tools/dsymutil/DwarfStreamer.cpp @@ -124,11 +124,11 @@ bool DwarfStreamer::init(Triple TheTriple) { return true; } -bool DwarfStreamer::finish(const DebugMap &DM) { +bool DwarfStreamer::finish(const DebugMap &DM, SymbolMapTranslator &T) { bool Result = true; if (DM.getTriple().isOSDarwin() && !DM.getBinaryPath().empty() && Options.FileType == OutputFileType::Object) - Result = MachOUtils::generateDsymCompanion(DM, *MS, OutFile); + Result = MachOUtils::generateDsymCompanion(DM, T, *MS, OutFile); else MS->Finish(); return Result; @@ -577,6 +577,89 @@ void DwarfStreamer::emitLineTableForUnit(MCDwarfLineTableParams Params, MS->EmitLabel(LineEndSym); } +/// Copy the debug_line over to the updated binary while unobfuscating the file +/// names and directories. +void DwarfStreamer::translateLineTable(DataExtractor Data, uint32_t Offset, + LinkOptions &Options) { + MS->SwitchSection(MC->getObjectFileInfo()->getDwarfLineSection()); + StringRef Contents = Data.getData(); + + // We have to deconstruct the line table header, because it contains to + // length fields that will need to be updated when we change the length of + // the files and directories in there. + unsigned UnitLength = Data.getU32(&Offset); + unsigned UnitEnd = Offset + UnitLength; + MCSymbol *BeginLabel = MC->createTempSymbol(); + MCSymbol *EndLabel = MC->createTempSymbol(); + unsigned Version = Data.getU16(&Offset); + + if (Version > 5) { + warn("Unsupported line table version: dropping contents and not " + "unobfsucating line table."); + return; + } + + Asm->EmitLabelDifference(EndLabel, BeginLabel, 4); + Asm->OutStreamer->EmitLabel(BeginLabel); + Asm->emitInt16(Version); + LineSectionSize += 6; + + MCSymbol *HeaderBeginLabel = MC->createTempSymbol(); + MCSymbol *HeaderEndLabel = MC->createTempSymbol(); + Asm->EmitLabelDifference(HeaderEndLabel, HeaderBeginLabel, 4); + Asm->OutStreamer->EmitLabel(HeaderBeginLabel); + Offset += 4; + LineSectionSize += 4; + + uint32_t AfterHeaderLengthOffset = Offset; + // Skip to the directories. + Offset += (Version >= 4) ? 5 : 4; + unsigned OpcodeBase = Data.getU8(&Offset); + Offset += OpcodeBase - 1; + Asm->OutStreamer->EmitBytes(Contents.slice(AfterHeaderLengthOffset, Offset)); + LineSectionSize += Offset - AfterHeaderLengthOffset; + + // Offset points to the first directory. + while (const char *Dir = Data.getCStr(&Offset)) { + if (Dir[0] == 0) + break; + + StringRef Translated = Options.Translator(Dir); + Asm->OutStreamer->EmitBytes(Translated); + Asm->emitInt8(0); + LineSectionSize += Translated.size() + 1; + } + Asm->emitInt8(0); + LineSectionSize += 1; + + while (const char *File = Data.getCStr(&Offset)) { + if (File[0] == 0) + break; + + StringRef Translated = Options.Translator(File); + Asm->OutStreamer->EmitBytes(Translated); + Asm->emitInt8(0); + LineSectionSize += Translated.size() + 1; + + uint32_t OffsetBeforeLEBs = Offset; + Asm->EmitULEB128(Data.getULEB128(&Offset)); + Asm->EmitULEB128(Data.getULEB128(&Offset)); + Asm->EmitULEB128(Data.getULEB128(&Offset)); + LineSectionSize += Offset - OffsetBeforeLEBs; + } + Asm->emitInt8(0); + LineSectionSize += 1; + + Asm->OutStreamer->EmitLabel(HeaderEndLabel); + + // Copy the actual line table program over. + Asm->OutStreamer->EmitBytes(Contents.slice(Offset, UnitEnd)); + LineSectionSize += UnitEnd - Offset; + + Asm->OutStreamer->EmitLabel(EndLabel); + Offset = UnitEnd; +} + static void emitSectionContents(const object::ObjectFile &Obj, StringRef SecName, MCStreamer *MS) { StringRef Contents; @@ -586,8 +669,10 @@ static void emitSectionContents(const object::ObjectFile &Obj, } void DwarfStreamer::copyInvariantDebugSection(const object::ObjectFile &Obj) { - MS->SwitchSection(MC->getObjectFileInfo()->getDwarfLineSection()); - emitSectionContents(Obj, "debug_line", MS); + if (!Options.Translator) { + MS->SwitchSection(MC->getObjectFileInfo()->getDwarfLineSection()); + emitSectionContents(Obj, "debug_line", MS); + } MS->SwitchSection(MC->getObjectFileInfo()->getDwarfLocSection()); emitSectionContents(Obj, "debug_loc", MS); diff --git a/tools/dsymutil/DwarfStreamer.h b/tools/dsymutil/DwarfStreamer.h index 679d124f4cb..abc86547ef6 100644 --- a/tools/dsymutil/DwarfStreamer.h +++ b/tools/dsymutil/DwarfStreamer.h @@ -50,7 +50,7 @@ public: bool init(Triple TheTriple); /// Dump the file to the disk. - bool finish(const DebugMap &); + bool finish(const DebugMap &, SymbolMapTranslator &T); AsmPrinter &getAsmPrinter() const { return *Asm; } @@ -104,6 +104,11 @@ public: std::vector &Rows, unsigned AdddressSize); + /// Copy the debug_line over to the updated binary while unobfuscating the + /// file names and directories. + void translateLineTable(DataExtractor LineData, uint32_t Offset, + LinkOptions &Options); + /// Copy over the debug sections that are not modified when updating. void copyInvariantDebugSection(const object::ObjectFile &Obj); diff --git a/tools/dsymutil/LinkUtils.h b/tools/dsymutil/LinkUtils.h index f0abd888b52..07697418535 100644 --- a/tools/dsymutil/LinkUtils.h +++ b/tools/dsymutil/LinkUtils.h @@ -10,8 +10,11 @@ #ifndef LLVM_TOOLS_DSYMUTIL_LINKOPTIONS_H #define LLVM_TOOLS_DSYMUTIL_LINKOPTIONS_H +#include "SymbolMap.h" + #include "llvm/ADT/Twine.h" #include "llvm/Support/WithColor.h" + #include namespace llvm { @@ -60,6 +63,9 @@ struct LinkOptions { /// -oso-prepend-path std::string PrependPath; + /// Symbol map translator. + SymbolMapTranslator Translator; + LinkOptions() = default; }; diff --git a/tools/dsymutil/MachODebugMapParser.cpp b/tools/dsymutil/MachODebugMapParser.cpp index d696e1d4cc8..8ff7e22da22 100644 --- a/tools/dsymutil/MachODebugMapParser.cpp +++ b/tools/dsymutil/MachODebugMapParser.cpp @@ -163,7 +163,8 @@ std::unique_ptr MachODebugMapParser::parseOneBinary(const MachOObjectFile &MainBinary, StringRef BinaryPath) { loadMainBinarySymbols(MainBinary); - Result = make_unique(MainBinary.getArchTriple(), BinaryPath); + ArrayRef UUID = MainBinary.getUuid(); + Result = make_unique(MainBinary.getArchTriple(), BinaryPath, UUID); MainBinaryStrings = MainBinary.getStringTableData(); for (const SymbolRef &Symbol : MainBinary.symbols()) { const DataRefImpl &DRI = Symbol.getRawDataRefImpl(); diff --git a/tools/dsymutil/MachOUtils.cpp b/tools/dsymutil/MachOUtils.cpp index cac4ad89eb2..8c54563eab9 100644 --- a/tools/dsymutil/MachOUtils.cpp +++ b/tools/dsymutil/MachOUtils.cpp @@ -333,8 +333,8 @@ static unsigned segmentLoadCommandSize(bool Is64Bit, unsigned NumSections) { // Stream a dSYM companion binary file corresponding to the binary referenced // by \a DM to \a OutFile. The passed \a MS MCStreamer is setup to write to // \a OutFile and it must be using a MachObjectWriter object to do so. -bool generateDsymCompanion(const DebugMap &DM, MCStreamer &MS, - raw_fd_ostream &OutFile) { +bool generateDsymCompanion(const DebugMap &DM, SymbolMapTranslator &Translator, + MCStreamer &MS, raw_fd_ostream &OutFile) { auto &ObjectStreamer = static_cast(MS); MCAssembler &MCAsm = ObjectStreamer.getAssembler(); auto &Writer = static_cast(MCAsm.getWriter()); @@ -443,7 +443,7 @@ bool generateDsymCompanion(const DebugMap &DM, MCStreamer &MS, } SmallString<0> NewSymtab; - NonRelocatableStringpool NewStrings; + NonRelocatableStringpool NewStrings(Translator); unsigned NListSize = Is64Bit ? sizeof(MachO::nlist_64) : sizeof(MachO::nlist); unsigned NumSyms = 0; uint64_t NewStringsSize = 0; diff --git a/tools/dsymutil/MachOUtils.h b/tools/dsymutil/MachOUtils.h index a8be89e906b..c24f963e1d9 100644 --- a/tools/dsymutil/MachOUtils.h +++ b/tools/dsymutil/MachOUtils.h @@ -9,8 +9,11 @@ #ifndef LLVM_TOOLS_DSYMUTIL_MACHOUTILS_H #define LLVM_TOOLS_DSYMUTIL_MACHOUTILS_H +#include "SymbolMap.h" + #include "llvm/ADT/StringRef.h" #include "llvm/Support/FileSystem.h" + #include namespace llvm { @@ -38,8 +41,8 @@ bool generateUniversalBinary(SmallVectorImpl &ArchFiles, StringRef OutputFileName, const LinkOptions &, StringRef SDKPath); -bool generateDsymCompanion(const DebugMap &DM, MCStreamer &MS, - raw_fd_ostream &OutFile); +bool generateDsymCompanion(const DebugMap &DM, SymbolMapTranslator &Translator, + MCStreamer &MS, raw_fd_ostream &OutFile); std::string getArchName(StringRef Arch); } // namespace MachOUtils diff --git a/tools/dsymutil/NonRelocatableStringpool.cpp b/tools/dsymutil/NonRelocatableStringpool.cpp index d82ff84589d..b8392a11252 100644 --- a/tools/dsymutil/NonRelocatableStringpool.cpp +++ b/tools/dsymutil/NonRelocatableStringpool.cpp @@ -16,6 +16,8 @@ DwarfStringPoolEntryRef NonRelocatableStringpool::getEntry(StringRef S) { if (S.empty() && !Strings.empty()) return EmptyString; + if (Translator) + S = Translator(S); auto I = Strings.insert({S, DwarfStringPoolEntry()}); auto &Entry = I.first->second; if (I.second || !Entry.isIndexed()) { @@ -29,6 +31,10 @@ DwarfStringPoolEntryRef NonRelocatableStringpool::getEntry(StringRef S) { StringRef NonRelocatableStringpool::internString(StringRef S) { DwarfStringPoolEntry Entry{nullptr, 0, DwarfStringPoolEntry::NotIndexed}; + + if (Translator) + S = Translator(S); + auto InsertResult = Strings.insert({S, Entry}); return InsertResult.first->getKey(); } diff --git a/tools/dsymutil/NonRelocatableStringpool.h b/tools/dsymutil/NonRelocatableStringpool.h index e339e51f0c6..c398ff0cec6 100644 --- a/tools/dsymutil/NonRelocatableStringpool.h +++ b/tools/dsymutil/NonRelocatableStringpool.h @@ -10,6 +10,8 @@ #ifndef LLVM_TOOLS_DSYMUTIL_NONRELOCATABLESTRINGPOOL_H #define LLVM_TOOLS_DSYMUTIL_NONRELOCATABLESTRINGPOOL_H +#include "SymbolMap.h" + #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/CodeGen/DwarfStringPoolEntry.h" @@ -32,7 +34,9 @@ public: /// order. using MapTy = StringMap; - NonRelocatableStringpool() { + NonRelocatableStringpool( + SymbolMapTranslator Translator = SymbolMapTranslator()) + : Translator(Translator) { // Legacy dsymutil puts an empty string at the start of the line table. EmptyString = getEntry(""); } @@ -62,6 +66,7 @@ private: uint32_t CurrentEndOffset = 0; unsigned NumEntries = 0; DwarfStringPoolEntryRef EmptyString; + SymbolMapTranslator Translator; }; /// Helper for making strong types. diff --git a/tools/dsymutil/SymbolMap.cpp b/tools/dsymutil/SymbolMap.cpp new file mode 100644 index 00000000000..cab9374a7d9 --- /dev/null +++ b/tools/dsymutil/SymbolMap.cpp @@ -0,0 +1,162 @@ +//===- tools/dsymutil/SymbolMap.cpp ---------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SymbolMap.h" +#include "DebugMap.h" +#include "MachOUtils.h" + +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/WithColor.h" + +#ifdef __APPLE__ +#include +#include +#endif + +namespace llvm { +namespace dsymutil { + +StringRef SymbolMapTranslator::operator()(StringRef Input) { + if (!Input.startswith("__hidden#") && !Input.startswith("___hidden#")) + return Input; + + bool MightNeedUnderscore = false; + StringRef Line = Input.drop_front(sizeof("__hidden#") - 1); + if (Line[0] == '#') { + Line = Line.drop_front(); + MightNeedUnderscore = true; + } + + std::size_t LineNumber = std::numeric_limits::max(); + Line.split('_').first.getAsInteger(10, LineNumber); + if (LineNumber >= UnobfuscatedStrings.size()) { + WithColor::warning() << "reference to a unexisting unobfuscated string " + << Input << ": symbol map mismatch?\n" + << Line << '\n'; + return Input; + } + + const std::string &Translation = UnobfuscatedStrings[LineNumber]; + if (!MightNeedUnderscore || !MangleNames) + return Translation; + + // Objective-C symbols for the MachO symbol table start with a \1. Please see + // `CGObjCCommonMac::GetNameForMethod` in clang. + if (Translation[0] == 1) + return StringRef(Translation).drop_front(); + + // We need permanent storage for the string we are about to create. Just + // append it to the vector containing translations. This should only happen + // during MachO symbol table translation, thus there should be no risk on + // exponential growth. + UnobfuscatedStrings.emplace_back("_" + Translation); + return UnobfuscatedStrings.back(); +} + +SymbolMapTranslator SymbolMapLoader::Load(StringRef InputFile, + const DebugMap &Map) const { + if (SymbolMap.empty()) + return {}; + + std::string SymbolMapPath = SymbolMap; + +#if __APPLE__ + // Look through the UUID Map. + if (sys::fs::is_directory(SymbolMapPath) && !Map.getUUID().empty()) { + uuid_string_t UUIDString; + uuid_unparse_upper((const uint8_t *)Map.getUUID().data(), UUIDString); + + SmallString<256> PlistPath( + sys::path::parent_path(sys::path::parent_path(InputFile))); + sys::path::append(PlistPath, StringRef(UUIDString).str() + ".plist"); + + CFStringRef plistFile = CFStringCreateWithCString( + kCFAllocatorDefault, PlistPath.c_str(), kCFStringEncodingUTF8); + CFURLRef fileURL = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, plistFile, kCFURLPOSIXPathStyle, false); + CFReadStreamRef resourceData = + CFReadStreamCreateWithFile(kCFAllocatorDefault, fileURL); + if (resourceData) { + CFReadStreamOpen(resourceData); + CFDictionaryRef plist = (CFDictionaryRef)CFPropertyListCreateWithStream( + kCFAllocatorDefault, resourceData, 0, kCFPropertyListImmutable, + nullptr, nullptr); + + if (plist) { + if (CFDictionaryContainsKey(plist, CFSTR("DBGOriginalUUID"))) { + CFStringRef OldUUID = (CFStringRef)CFDictionaryGetValue( + plist, CFSTR("DBGOriginalUUID")); + + StringRef UUID(CFStringGetCStringPtr(OldUUID, kCFStringEncodingUTF8)); + SmallString<256> BCSymbolMapPath(SymbolMapPath); + sys::path::append(BCSymbolMapPath, UUID.str() + ".bcsymbolmap"); + SymbolMapPath = BCSymbolMapPath.str(); + } + CFRelease(plist); + } + CFReadStreamClose(resourceData); + CFRelease(resourceData); + } + CFRelease(fileURL); + CFRelease(plistFile); + } +#endif + + if (sys::fs::is_directory(SymbolMapPath)) { + SymbolMapPath += (Twine("/") + sys::path::filename(InputFile) + "-" + + MachOUtils::getArchName(Map.getTriple().getArchName()) + + ".bcsymbolmap") + .str(); + } + + auto ErrOrMemBuffer = MemoryBuffer::getFile(SymbolMapPath); + if (auto EC = ErrOrMemBuffer.getError()) { + WithColor::warning() << SymbolMapPath << ": " << EC.message() + << ": not unobfuscating.\n"; + return {}; + } + + std::vector UnobfuscatedStrings; + auto &MemBuf = **ErrOrMemBuffer; + StringRef Data(MemBuf.getBufferStart(), + MemBuf.getBufferEnd() - MemBuf.getBufferStart()); + StringRef LHS; + std::tie(LHS, Data) = Data.split('\n'); + bool MangleNames = false; + + // Check version string first. + if (!LHS.startswith("BCSymbolMap Version:")) { + // Version string not present, warns but try to parse it. + WithColor::warning() << SymbolMapPath + << " is missing version string: assuming 1.0.\n"; + UnobfuscatedStrings.emplace_back(LHS); + } else if (LHS.equals("BCSymbolMap Version: 1.0")) { + MangleNames = true; + } else if (LHS.equals("BCSymbolMap Version: 2.0")) { + MangleNames = false; + } else { + StringRef VersionNum; + std::tie(LHS, VersionNum) = LHS.split(':'); + WithColor::warning() << SymbolMapPath + << " has unsupported symbol map version" << VersionNum + << ": not unobfuscating.\n"; + return {}; + } + + while (!Data.empty()) { + std::tie(LHS, Data) = Data.split('\n'); + UnobfuscatedStrings.emplace_back(LHS); + } + + return SymbolMapTranslator(std::move(UnobfuscatedStrings), MangleNames); +} + +} // namespace dsymutil +} // namespace llvm diff --git a/tools/dsymutil/SymbolMap.h b/tools/dsymutil/SymbolMap.h new file mode 100644 index 00000000000..e3fbdbb01d8 --- /dev/null +++ b/tools/dsymutil/SymbolMap.h @@ -0,0 +1,54 @@ +//=- tools/dsymutil/SymbolMap.h -----------------------------------*- C++ -*-=// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_DSYMUTIL_SYMBOLMAP_H +#define LLVM_TOOLS_DSYMUTIL_SYMBOLMAP_H + +#include "llvm/ADT/StringRef.h" + +#include +#include + +namespace llvm { +namespace dsymutil { +class DebugMap; + +/// Callable class to unobfuscate strings based on a BCSymbolMap. +class SymbolMapTranslator { +public: + SymbolMapTranslator() : MangleNames(false) {} + + SymbolMapTranslator(std::vector UnobfuscatedStrings, + bool MangleNames) + : UnobfuscatedStrings(std::move(UnobfuscatedStrings)), + MangleNames(MangleNames) {} + + StringRef operator()(StringRef Input); + + operator bool() const { return !UnobfuscatedStrings.empty(); } + +private: + std::vector UnobfuscatedStrings; + bool MangleNames; +}; + +/// Class to initialize SymbolMapTranslators from a BCSymbolMap. +class SymbolMapLoader { +public: + SymbolMapLoader(std::string SymbolMap) : SymbolMap(std::move(SymbolMap)) {} + + SymbolMapTranslator Load(StringRef InputFile, const DebugMap &Map) const; + +private: + const std::string SymbolMap; +}; +} // namespace dsymutil +} // namespace llvm + +#endif // LLVM_TOOLS_DSYMUTIL_SYMBOLMAP_H diff --git a/tools/dsymutil/dsymutil.cpp b/tools/dsymutil/dsymutil.cpp index 5fe40678ca9..ec8d0507b08 100644 --- a/tools/dsymutil/dsymutil.cpp +++ b/tools/dsymutil/dsymutil.cpp @@ -59,6 +59,8 @@ static opt OutputFileOpt("o", desc("Specify the output file. default: .dwarf"), value_desc("filename"), cat(DsymCategory)); +static alias OutputFileOptA("out", desc("Alias for -o"), + aliasopt(OutputFileOpt)); static opt OsoPrependPath( "oso-prepend-path", @@ -100,6 +102,11 @@ static opt Update( init(false), cat(DsymCategory)); static alias UpdateA("u", desc("Alias for --update"), aliasopt(Update)); +static opt SymbolMap( + "symbol-map", + desc("Updates the existing dSYMs inplace using symbol map specified."), + value_desc("bcsymbolmap"), cat(DsymCategory)); + static cl::opt AcceleratorTable( "accelerator", cl::desc("Output accelerator tables."), cl::values(clEnumValN(AccelTableKind::Default, "Default", @@ -273,8 +280,11 @@ static bool verify(llvm::StringRef OutputFile, llvm::StringRef Arch) { } static Expected getOutputFileName(llvm::StringRef InputFile) { + if (OutputFileOpt == "-") + return OutputFileOpt; + // When updating, do in place replacement. - if (OutputFileOpt.empty() && Update) + if (OutputFileOpt.empty() && (Update || !SymbolMap.empty())) return InputFile; // If a flat dSYM has been requested, things are pretty simple. @@ -325,6 +335,9 @@ static Expected getOptions() { Options.PrependPath = OsoPrependPath; Options.TheAccelTableKind = AcceleratorTable; + if (!SymbolMap.empty()) + Options.Update = true; + if (Assembly) Options.FileType = OutputFileType::Assembly; @@ -443,6 +456,13 @@ int main(int argc, char **argv) { return 1; } + if (InputFiles.size() > 1 && !SymbolMap.empty() && + !llvm::sys::fs::is_directory(SymbolMap)) { + WithColor::error() << "when unobfuscating multiple files, --symbol-map " + << "needs to point to a directory.\n"; + return 1; + } + if (getenv("RC_DEBUG_OPTIONS")) PaperTrailWarnings = true; @@ -457,6 +477,8 @@ int main(int argc, char **argv) { return 1; } + SymbolMapLoader SymMapLoader(SymbolMap); + for (auto &InputFile : *InputsOrErr) { // Dump the symbol table for each input file and requested arch if (DumpStab) { @@ -511,6 +533,9 @@ int main(int argc, char **argv) { if (DumpDebugMap) continue; + if (!SymbolMap.empty()) + OptionsOrErr->Translator = SymMapLoader.Load(InputFile, *Map); + if (Map->begin() == Map->end()) WithColor::warning() << "no debug symbols in executable (-arch " -- 2.50.1