[llvm] [llvm-objdump][macho] Add support for ObjC relative method lists (PR #84250)

via llvm-commits llvm-commits at lists.llvm.org
Wed Mar 6 14:47:11 PST 2024


https://github.com/alx32 created https://github.com/llvm/llvm-project/pull/84250

For Mach-O, ld64 supports the `-fobjc-relative-method-lists` flag which generates relative method lists for Objc classes/categories.
This change adds support for decoding/dumping of method lists in this format. The output format is closest feasible match to XCode 15.1's otool output. Tests are included for both 32bit and 64bit binaries.

Diff between llvm-objdump and XCode 15.1 otool:
![objdump_vs_otool](https://github.com/llvm/llvm-project/assets/103613512/4fc04228-ed35-473d-b633-364402411b91)


>From d33f0607ac86e43d6dc95a8816e0feefe0dfb8c6 Mon Sep 17 00:00:00 2001
From: Alex B <alexborcan at meta.com>
Date: Wed, 6 Mar 2024 14:31:24 -0800
Subject: [PATCH] [llvm-objdump][macho] Add support for ObjC relative method
 lists

---
 .../Inputs/rel-method-lists-arm64.dylib       | Bin 0 -> 3160 bytes
 .../Inputs/rel-method-lists-arm64_32.dylib    | Bin 0 -> 2716 bytes
 .../AArch64/macho-relative-method-lists.test  |  57 ++++++++
 llvm/tools/llvm-objdump/MachODump.cpp         | 131 ++++++++++++++++++
 4 files changed, 188 insertions(+)
 create mode 100755 llvm/test/tools/llvm-objdump/MachO/AArch64/Inputs/rel-method-lists-arm64.dylib
 create mode 100755 llvm/test/tools/llvm-objdump/MachO/AArch64/Inputs/rel-method-lists-arm64_32.dylib
 create mode 100644 llvm/test/tools/llvm-objdump/MachO/AArch64/macho-relative-method-lists.test

diff --git a/llvm/test/tools/llvm-objdump/MachO/AArch64/Inputs/rel-method-lists-arm64.dylib b/llvm/test/tools/llvm-objdump/MachO/AArch64/Inputs/rel-method-lists-arm64.dylib
new file mode 100755
index 0000000000000000000000000000000000000000..9b39fc98fb5d96b544894f21e1f7857891735e6f
GIT binary patch
literal 3160
zcmcguO=whC6h6L*P1>X}!3dT{h*m`H&%7BVgwjH$GXs;ViJFL!BDXJZp3TU6^M=mT
zBy<yMyDbU|S-LQwn-Xwgmsv^|ZEZyYVrdqNE(8m1;-Y%ad*{r&xs!>uiyk<0?|1IK
z- at EsmbI+Y$pFjDh3t$tVyn)({y0jVKd*~q(yo0)eqe at NWCnrKv!;jhC06{QPfGEvS
zDsX0l`dppw8BIo+jT#_qa6fF?U_K6&@~6(&%66 at 4wQPBg&gar$P&%Rp1T$>pMkDii
zPB2Y$P3H}mufzm`UxP20t2Fc7*Y+p4HQ(9tOsQN{rSfS%+;3w%9x-2m2?#g%Pl&mt
zxIRKvDwmnasO;G2co<!YUle_)#Zzzs*N_r7+2V0<OVywipTvRqX?;wDIy5m~$+Nz4
z=%_m15a*ll5q{M<hr;}&bJo8)%sdyu%m)xW&sfeWdzEwg<!YG4(Y~7b%ov}nt<S3W
zn6GFBR%q1uDty2ryoB(^e75g-ep$Yt^DQ%9+}UwH-nJ|c?|lK0Qin!H59V_teD{B2
zo-4dqco!HGPn_#*#9yLr*X+q at -*b|d>&#C1U)xqWsRFN(G*hZ-c@@`Dc$!Mdb&~$s
zKuliBvux!S2M>HyoO4T4$Oy0tVBW-*_CeohB`Y{y!9mIuns?;QsMeWpApvf<lWeFY
zXbQCrTdIfuMc&0l^qp_FecFB{J^0}I))T!ytX^JBqip~+@)3U3=nngl`z&KWj>TiN
zhj9V-ORnF?nD$Sy1C{14A=Nc9yoS8U!tKA>VQn=5YuLZIoKScF58L6nER|~ri?*Ut
z>lc-2YB;6gG?ehpS!LUaEom~PnrEfqTxxLG{CJ35LjLY&-^5$W2YPD`U`y(89i=|S
zqzhN}aI~;v$i2iN7)GTS(>!?(q>h`+^BuQh&oREF*(Vtj*Irg^Y9jBK)T!D)i;nE;
zs`eduXK!lu4#q!g_I}3Xr}aekwZipuy*Y8)0wr-wZZVcRvX`;U6MyUTF7Wqycf!$K
z9VfHL@~F8=$E0v?QuzPF82jw=tb+dN&w}Gsf;k1FU}O(v#>dreB^jAJoS(?(#*qX(
z?ZMzK^x`c&7g$p+KI67!PdiXdz;Z7R-NK<ro^|$-z)6JNm~7(b8OGw@%Z$aZHyBI4
z{K8oB<S}E(k2d7)MjuHYyvJDlkzp+Huo;VADUMsPc8FgoPY8>@sXlGN0x15 at GnV)a
zfC_!8J1erT=SF_%BIif$YcR2AKm3A&G0=3k%~P<!XlldJ^eK41#;g^d)<ekIY84Wy
z=&-w0<ODo)VJtUZOC}z!`{LMGEY)g at NAMq4W;B~uE!Icm4QnwL&;RUN#^xF-MmCf_
zv{6_ufq^fU5k7GNVz~-9uXAa at z*6=vWuv$EkH3H4x_I-)*;W7Poo{B&-`j3qeE313
P at Y7=7C&xFholn06PJ`vL

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-objdump/MachO/AArch64/Inputs/rel-method-lists-arm64_32.dylib b/llvm/test/tools/llvm-objdump/MachO/AArch64/Inputs/rel-method-lists-arm64_32.dylib
new file mode 100755
index 0000000000000000000000000000000000000000..d3a339057abc349e5959a3a67077f0ae5c5bafe8
GIT binary patch
literal 2716
zcmd5-J!}+L5FYz3Kw_{RiNaD`K!PG533q2Xkrh#JxjV^Dz$W%7fC74~_m=pg-P;ql
zU|;7VNE9?kq=_g}I8s0^h=2l#!jW7fXn-PBDuakbi6SMJ at 7sNQe%{4IqGEJ&^Sya9
zvorJl?*0AOzuQH6dqiXlcnA34TOt=^P^i0)Kc%Xb<5dg{I=VF)P{KT9`jeCjoP{95
z(Ric(ML7J53CiAeGm)$;#zUq2+3#)TIl-J;Zw09w^7z<E7*l3qr-aloY}abF8kXlI
z>$n8pZSbKq$;`>fgCSCRT!C*L&Xgu?5+i6g(=q;mFWX6J(#9C)8uRr=P1PG`d>AEb
z$s)iw^pvKb8E<_(%f)JOeTHjz14GK6A=yuIoQ~N?E{0NR|19k3pZNA5)FJ5 at b<g_F
zVF}3?Rrqo(C=)Ss1lAnx3EOXALx7SoPQv#n^pyV-qvg0~oinX;jAi%^AqJ%x16ws&
zOU()_rjg2H6~23+r!- at P#`;ew+xI-b5$lq*JcBR$K{8`-4Pzo611oiSV)Cm>c_Kce
zaJ&Jh=ZLWkNjuYzXZ$pnb(;FzobNd~%XJoJ{j;{!$f>|<=JEyAvb?72Fym(3b#nf>
zAXd#=fj#H9lw133^gylc)@Kp(eUZgHB){Kn8pbI?o-UL~G0Y{vxF2}<Z3IAKeZX8#
z(mR^D&uH^H^d6j8^VM%Y{rH!aeCM}|*Z%xz`SucXjq+qBJ+i+$1_12_?*uPm6YxGH
zeF&zHi=6S3eR|9qUWYm*d9ap|>uUpYXD!oneXURKKzDg1qaMCVb9gHjc{gx<euuo;
z>2wx=Zv!s_H-WDK-vx${*uD(?WsHZQKL~sQcrWlMa2t3G_(;3Nt6IVfJG}b+n#$)B
z<3eIQB6ZvcR>O7-mz1iIwi=Q1V~6sekA`Pp7&(wk$=$#=Y7~-O&=(>xd$1jz(JP6;
zV027*=1GUZwBf!Xllac?y-E_lV-oF7zr=18x<}xrV7 at m?;9KCw`kTBnNIyVQ$6R6^
z??lqOyQMiaJw9DNR%~Xg#jmAD;qC67`r^xylHurrb-ZTKRx&BQrNhOU8MQ~nD^osF
zsTPyUiHxicpxDO)ILfwV&*6nTA6T=lBeje?<J5CZp_hHbBsl at xk9B8n=QUme{!!x<
z;F}u%0eny67r;+6-if-ih|!Pcv8Nws%)V8Kafh&GTVwWweICH2!oKhxAZDLNh_O-G
ztBS_FyLO2R`;sg;(!Q(}`h|)bs9Rz7RJY}mZ*@a=rS at t5l)PfL at XFbdQ}R(aa^ete
zK82W at c_E;Riru54CZOo?sq##BLv*JnCTFU}$x=maVzJ(>=_zAFY5yqrh8e8Gf2j)e
A#{d8T

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-objdump/MachO/AArch64/macho-relative-method-lists.test b/llvm/test/tools/llvm-objdump/MachO/AArch64/macho-relative-method-lists.test
new file mode 100644
index 00000000000000..672720a8b2938d
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/MachO/AArch64/macho-relative-method-lists.test
@@ -0,0 +1,57 @@
+RUN: llvm-objdump --macho --objc-meta-data    %p/Inputs/rel-method-lists-arm64_32.dylib | FileCheck %s --check-prefix=CHK32
+RUN: llvm-otool -ov                           %p/Inputs/rel-method-lists-arm64_32.dylib | FileCheck %s --check-prefix=CHK32
+
+RUN: llvm-objdump --macho --objc-meta-data    %p/Inputs/rel-method-lists-arm64.dylib    | FileCheck %s --check-prefix=CHK64
+RUN: llvm-otool -ov                           %p/Inputs/rel-method-lists-arm64.dylib    | FileCheck %s --check-prefix=CHK64
+
+CHK32:                 baseMethods 0x660 (struct method_list_t *)
+CHK32-NEXT:                 entsize 12 (relative)
+CHK32-NEXT:                   count 3
+CHK32-NEXT:                    name 0x144 (0x7ac) instance_method_00
+CHK32-NEXT:                   types 0x91 (0x6fd) v8 at 0:4
+CHK32-NEXT:                     imp 0xffffff18 (0x588) -[MyClass instance_method_00]
+CHK32-NEXT:                    name 0x13c (0x7b0) instance_method_01
+CHK32-NEXT:                   types 0x85 (0x6fd) v8 at 0:4
+CHK32-NEXT:                     imp 0xffffff28 (0x5a4) -[MyClass instance_method_01]
+CHK32-NEXT:                    name 0x134 (0x7b4) instance_method_02
+CHK32-NEXT:                   types 0x79 (0x6fd) v8 at 0:4
+CHK32-NEXT:                     imp 0xffffff38 (0x5c0) -[MyClass instance_method_02]
+
+CHK32:                 baseMethods 0x630 (struct method_list_t *)
+CHK32-NEXT:                 entsize 12 (relative)
+CHK32-NEXT:                   count 3
+CHK32-NEXT:                    name 0x180 (0x7b8) class_method_00
+CHK32-NEXT:                   types 0xc1 (0x6fd) v8 at 0:4
+CHK32-NEXT:                     imp 0xffffff9c (0x5dc) +[MyClass class_method_00]
+CHK32-NEXT:                    name 0x178 (0x7bc) class_method_01
+CHK32-NEXT:                   types 0xb5 (0x6fd) v8 at 0:4
+CHK32-NEXT:                     imp 0xffffffac (0x5f8) +[MyClass class_method_01]
+CHK32-NEXT:                    name 0x170 (0x7c0) class_method_02
+CHK32-NEXT:                   types 0xa9 (0x6fd) v8 at 0:4
+CHK32-NEXT:                     imp 0xffffffbc (0x614) +[MyClass class_method_02]
+
+CHK64:                  baseMethods 0x7d8 (struct method_list_t *)
+CHK64-NEXT:                  entsize 24
+CHK64-NEXT:                    count 3
+CHK64-NEXT:                     name 0x6a4 instance_method_00
+CHK64-NEXT:                    types 0x6dd v16 at 0:8
+CHK64-NEXT:                      imp -[MyClass instance_method_00]
+CHK64-NEXT:                     name 0x6b7 instance_method_01
+CHK64-NEXT:                    types 0x6dd v16 at 0:8
+CHK64-NEXT:                      imp -[MyClass instance_method_01]
+CHK64-NEXT:                     name 0x6ca instance_method_02
+CHK64-NEXT:                    types 0x6dd v16 at 0:8
+CHK64-NEXT:                      imp -[MyClass instance_method_02]
+
+CHK64:                  baseMethods 0x740 (struct method_list_t *)
+CHK64-NEXT:                  entsize 24
+CHK64-NEXT:                    count 3
+CHK64-NEXT:                     name 0x674 class_method_00
+CHK64-NEXT:                    types 0x6dd v16 at 0:8
+CHK64-NEXT:                      imp +[MyClass class_method_00]
+CHK64-NEXT:                     name 0x684 class_method_01
+CHK64-NEXT:                    types 0x6dd v16 at 0:8
+CHK64-NEXT:                      imp +[MyClass class_method_01]
+CHK64-NEXT:                     name 0x694 class_method_02
+CHK64-NEXT:                    types 0x6dd v16 at 0:8
+CHK64-NEXT:                      imp +[MyClass class_method_02]
diff --git a/llvm/tools/llvm-objdump/MachODump.cpp b/llvm/tools/llvm-objdump/MachODump.cpp
index 0e6935c0ac5895..2c5d8f911308aa 100644
--- a/llvm/tools/llvm-objdump/MachODump.cpp
+++ b/llvm/tools/llvm-objdump/MachODump.cpp
@@ -3519,6 +3519,12 @@ static const char *get_pointer_64(uint64_t Address, uint32_t &offset,
   return nullptr;
 }
 
+static const char *get_value_32(uint32_t Address, uint32_t &offset,
+                                uint32_t &left, SectionRef &S,
+                                DisassembleInfo *info, bool objc_only = false) {
+  return get_pointer_64(Address, offset, left, S, info, objc_only);
+}
+
 static const char *get_pointer_32(uint32_t Address, uint32_t &offset,
                                   uint32_t &left, SectionRef &S,
                                   DisassembleInfo *info,
@@ -3661,6 +3667,10 @@ struct class_ro32_t {
 #define RO_ROOT (1 << 1)
 #define RO_HAS_CXX_STRUCTORS (1 << 2)
 
+/* Values for method_list{64,32,_delta}_t->entsize */
+#define ML_HAS_DELTAS (1 << 31)
+#define ML_ENTSIZE_MASK 0xFFFF
+
 struct method_list64_t {
   uint32_t entsize;
   uint32_t count;
@@ -3673,6 +3683,12 @@ struct method_list32_t {
   /* struct method32_t first;  These structures follow inline */
 };
 
+struct method_list_delta_t {
+  uint32_t entsize;
+  uint32_t count;
+  /* struct method32_t first;  These structures follow inline */
+};
+
 struct method64_t {
   uint64_t name;  /* SEL (64-bit pointer) */
   uint64_t types; /* const char * (64-bit pointer) */
@@ -3685,6 +3701,12 @@ struct method32_t {
   uint32_t imp;   /* IMP (32-bit pointer) */
 };
 
+struct method_delta_t {
+  int32_t name;  /* SEL (32-bit delta) */
+  int32_t types; /* const char * (32-bit delta) */
+  int32_t imp;   /* IMP (32-bit delta) */
+};
+
 struct protocol_list64_t {
   uint64_t count; /* uintptr_t (a 64-bit value) */
   /* struct protocol64_t * list[0];  These pointers follow inline */
@@ -3974,6 +3996,11 @@ inline void swapStruct(struct method_list32_t &ml) {
   sys::swapByteOrder(ml.count);
 }
 
+inline void swapStruct(struct method_list_delta_t &ml) {
+  sys::swapByteOrder(ml.entsize);
+  sys::swapByteOrder(ml.count);
+}
+
 inline void swapStruct(struct method64_t &m) {
   sys::swapByteOrder(m.name);
   sys::swapByteOrder(m.types);
@@ -3986,6 +4013,12 @@ inline void swapStruct(struct method32_t &m) {
   sys::swapByteOrder(m.imp);
 }
 
+inline void swapStruct(struct method_delta_t &m) {
+  sys::swapByteOrder(m.name);
+  sys::swapByteOrder(m.types);
+  sys::swapByteOrder(m.imp);
+}
+
 inline void swapStruct(struct protocol_list64_t &pl) {
   sys::swapByteOrder(pl.count);
 }
@@ -4440,8 +4473,103 @@ static void print_layout_map32(uint32_t p, struct DisassembleInfo *info) {
   print_layout_map(layout_map, left);
 }
 
+// Return true if this is a delta method list, false otherwise
+//
+static bool print_method_list_delta_t(uint64_t p, struct DisassembleInfo *info,
+                                      const char *indent,
+                                      uint32_t pointerBits) {
+  struct method_list_delta_t ml;
+  struct method_delta_t m;
+  const char *r, *name;
+  uint32_t offset, xoffset, left, i;
+  SectionRef S, xS;
+
+  r = get_pointer_32(p, offset, left, S, info);
+  if (r == nullptr)
+    return false;
+  memset(&ml, '\0', sizeof(struct method_list_delta_t));
+  if (left < sizeof(struct method_list_delta_t)) {
+    memcpy(&ml, r, left);
+    outs() << "   (method_delta_t entends past the end of the section)\n";
+  } else
+    memcpy(&ml, r, sizeof(struct method_list_delta_t));
+  if (info->O->isLittleEndian() != sys::IsLittleEndianHost)
+    swapStruct(ml);
+  if ((ml.entsize & ML_HAS_DELTAS) == 0)
+    return false;
+
+  outs() << indent << "\t\t   entsize " << (ml.entsize & ML_ENTSIZE_MASK)
+         << " (relative) \n";
+  outs() << indent << "\t\t     count " << ml.count << "\n";
+
+  p += sizeof(struct method_list_delta_t);
+  offset += sizeof(struct method_delta_t);
+  for (i = 0; i < ml.count; i++) {
+    r = get_value_32(p, offset, left, S, info);
+    if (r == nullptr)
+      return true;
+    memset(&m, '\0', sizeof(struct method_delta_t));
+    if (left < sizeof(struct method_delta_t)) {
+      memcpy(&ml, r, left);
+      outs() << indent << "   (method_t entends past the end of the section)\n";
+    } else
+      memcpy(&m, r, sizeof(struct method_delta_t));
+    if (info->O->isLittleEndian() != sys::IsLittleEndianHost)
+      swapStruct(m);
+
+    outs() << indent << "\t\t      name " << format("0x%" PRIx32, m.name);
+    uint64_t relNameRefVA = p + offsetof(struct method_delta_t, name);
+    uint64_t absNameRefVA = relNameRefVA + m.name;
+    outs() << " (" << format("0x%" PRIx32, absNameRefVA) << ")";
+
+    // since this is a delta list, absNameRefVA is the address of the
+    // __objc_selrefs entry, so a pointer, not the actual name
+    const char *nameRefPtr =
+        get_pointer_32(absNameRefVA, xoffset, left, xS, info);
+    if (nameRefPtr) {
+      uint32_t pointerSize = pointerBits / CHAR_BIT;
+      if (left < pointerSize)
+        outs() << indent << " (nameRefPtr entends past the end of the section)";
+      else {
+        uint64_t nameVA = 0;
+        memcpy(&nameVA, nameRefPtr, pointerSize);
+        const char *name = get_pointer_32(nameVA, xoffset, left, xS, info);
+        if (name != nullptr)
+          outs() << format(" %.*s", left, name);
+      }
+    }
+    outs() << "\n";
+
+    outs() << indent << "\t\t     types " << format("0x%" PRIx32, m.types);
+    uint64_t relTypesVA = p + offsetof(struct method_delta_t, types);
+    uint64_t absTypesVA = relTypesVA + m.types;
+    outs() << " (" << format("0x%" PRIx32, absTypesVA) << ")";
+    name = get_pointer_32(absTypesVA, xoffset, left, xS, info);
+    if (name != nullptr)
+      outs() << format(" %.*s", left, name);
+    outs() << "\n";
+
+    outs() << indent << "\t\t       imp " << format("0x%" PRIx32, m.imp);
+    uint64_t relImpVA = p + offsetof(struct method_delta_t, imp);
+    uint64_t absImpVA = relImpVA + m.imp;
+    outs() << " (" << format("0x%" PRIx32, absImpVA) << ")";
+    name = GuessSymbolName(absImpVA, info->AddrMap);
+    if (name != nullptr)
+      outs() << " " << name;
+    outs() << "\n";
+
+    p += sizeof(struct method_delta_t);
+    offset += sizeof(struct method_delta_t);
+  }
+
+  return true;
+}
+
 static void print_method_list64_t(uint64_t p, struct DisassembleInfo *info,
                                   const char *indent) {
+  if (print_method_list_delta_t(p, info, indent, /*pointerBits=*/64))
+    return;
+
   struct method_list64_t ml;
   struct method64_t m;
   const char *r;
@@ -4535,6 +4663,9 @@ static void print_method_list64_t(uint64_t p, struct DisassembleInfo *info,
 
 static void print_method_list32_t(uint64_t p, struct DisassembleInfo *info,
                                   const char *indent) {
+  if (print_method_list_delta_t(p, info, indent, /*pointerBits=*/32))
+    return;
+
   struct method_list32_t ml;
   struct method32_t m;
   const char *r, *name;



More information about the llvm-commits mailing list