Skip to content

Commit

Permalink
[llvm-objdump][macho] Add support for ObjC relative method lists (#85477
Browse files Browse the repository at this point in the history
)

For Mach-O, ld64 supports the -fobjc-relative-method-lists flag which
changes the format in which method lists are generated. The format uses
delta encoding vs the original direct-pointer encoding.
This change adds support to llvm-objdump and llvm-otool for
decoding/dumping of method lists in the delta format. Previously, if a
binary with this information format was passed to the tooling, it would
output invalid information, trying to parse the delta lists as pointer
lists.
After this change, the tooling will output correct information if a
binary in this format is encountered.
The output format is closest feasible match to XCode 15.1's otool
output. Tests are included for both 32bit and 64bit binaries.

The code style was matched as close as possible to existing
implementation of parsing non-delta method lists.

Diff between llvm-objdump and XCode 15.1 otool:

![image](https://github.com/llvm/llvm-project/assets/103613512/2277e3ff-d59c-4fff-b93a-e0587ee740a6)

Note: This is a retry of this PR:
#84250
On the original PR, the armv7+armv8 builds were failing due to absolute
offsets being different.

Co-authored-by: Alex B <alexborcan@meta.com>
  • Loading branch information
alx32 and Alex B committed Mar 16, 2024
1 parent 662010e commit 9d5edfd
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 2 deletions.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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 (0x{{[0-9a-f]*}}) instance_method_00
CHK32-NEXT: types 0x91 (0x{{[0-9a-f]*}}) v8@0:4
CHK32-NEXT: imp 0xffffff18 (0x{{[0-9a-f]*}}) -[MyClass instance_method_00]
CHK32-NEXT: name 0x13c (0x{{[0-9a-f]*}}) instance_method_01
CHK32-NEXT: types 0x85 (0x{{[0-9a-f]*}}) v8@0:4
CHK32-NEXT: imp 0xffffff28 (0x{{[0-9a-f]*}}) -[MyClass instance_method_01]
CHK32-NEXT: name 0x134 (0x{{[0-9a-f]*}}) instance_method_02
CHK32-NEXT: types 0x79 (0x{{[0-9a-f]*}}) v8@0:4
CHK32-NEXT: imp 0xffffff38 (0x{{[0-9a-f]*}}) -[MyClass instance_method_02]

CHK32: baseMethods 0x630 (struct method_list_t *)
CHK32-NEXT: entsize 12 (relative)
CHK32-NEXT: count 3
CHK32-NEXT: name 0x180 (0x{{[0-9a-f]*}}) class_method_00
CHK32-NEXT: types 0xc1 (0x{{[0-9a-f]*}}) v8@0:4
CHK32-NEXT: imp 0xffffff9c (0x{{[0-9a-f]*}}) +[MyClass class_method_00]
CHK32-NEXT: name 0x178 (0x{{[0-9a-f]*}}) class_method_01
CHK32-NEXT: types 0xb5 (0x{{[0-9a-f]*}}) v8@0:4
CHK32-NEXT: imp 0xffffffac (0x{{[0-9a-f]*}}) +[MyClass class_method_01]
CHK32-NEXT: name 0x170 (0x{{[0-9a-f]*}}) class_method_02
CHK32-NEXT: types 0xa9 (0x{{[0-9a-f]*}}) v8@0:4
CHK32-NEXT: imp 0xffffffbc (0x{{[0-9a-f]*}}) +[MyClass class_method_02]

CHK64: baseMethods 0x6e0 (struct method_list_t *)
CHK64-NEXT: entsize 12 (relative)
CHK64-NEXT: count 3
CHK64-NEXT: name 0x188 (0x{{[0-9a-f]*}}) instance_method_00
CHK64-NEXT: types 0x91 (0x{{[0-9a-f]*}}) v16@0:8
CHK64-NEXT: imp 0xffffffa8 (0x{{[0-9a-f]*}}) -[MyClass instance_method_00]
CHK64-NEXT: name 0x184 (0x{{[0-9a-f]*}}) instance_method_01
CHK64-NEXT: types 0x85 (0x{{[0-9a-f]*}}) v16@0:8
CHK64-NEXT: imp 0xffffffa0 (0x{{[0-9a-f]*}}) -[MyClass instance_method_01]
CHK64-NEXT: name 0x180 (0x{{[0-9a-f]*}}) instance_method_02
CHK64-NEXT: types 0x79 (0x{{[0-9a-f]*}}) v16@0:8
CHK64-NEXT: imp 0xffffff98 (0x{{[0-9a-f]*}}) -[MyClass instance_method_02]

CHK64: baseMethods 0x6b0 (struct method_list_t *)
CHK64-NEXT: entsize 12 (relative)
CHK64-NEXT: count 3
CHK64-NEXT: name 0x1d0 (0x{{[0-9a-f]*}}) class_method_00
CHK64-NEXT: types 0xc1 (0x{{[0-9a-f]*}}) v16@0:8
CHK64-NEXT: imp 0xffffffe4 (0x{{[0-9a-f]*}}) +[MyClass class_method_00]
CHK64-NEXT: name 0x1cc (0x{{[0-9a-f]*}}) class_method_01
CHK64-NEXT: types 0xb5 (0x{{[0-9a-f]*}}) v16@0:8
CHK64-NEXT: imp 0xffffffdc (0x{{[0-9a-f]*}}) +[MyClass class_method_01]
CHK64-NEXT: name 0x1c8 (0x{{[0-9a-f]*}}) class_method_02
CHK64-NEXT: types 0xa9 (0x{{[0-9a-f]*}}) v16@0:8
CHK64-NEXT: imp 0xffffffd4 (0x{{[0-9a-f]*}}) +[MyClass class_method_02]

######## Generate rel-method-lists-arm64.dylib ########
// clang -c main.mm -o main.o -target arm64-apple-macos -arch arm64
// ld64.ld64 -dylib -demangle -dynamic main.o -o rel-method-lists-arm64.dylib -syslibroot MacOSX14.2.sdk -segalign 0x10 -objc_relative_method_lists

######## Generate rel-method-lists-arm64_32.dylib ########
// clang -c main.mm -o main.o -target arm64_32-apple-watchos -arch arm64_32
// ld64.ld64 -dylib -demangle -dynamic main.o -o rel-method-lists-arm64_32.dylib -syslibroot WatchOS.sdk -segalign 0x10 -objc_relative_method_lists

// ~~~~~~~~~~~~~~~~~~~~~~~~~ main.mm ~~~~~~~~~~~~~~~~~~~~~~~~~
__attribute__((objc_root_class))
@interface MyClass
- (void)instance_method_00;
- (void)instance_method_01;
- (void)instance_method_02;
+ (void)class_method_00;
+ (void)class_method_01;
+ (void)class_method_02;
@end
@implementation MyClass
- (void)instance_method_00 {}
- (void)instance_method_01 {}
- (void)instance_method_02 {}
+ (void)class_method_00 {}
+ (void)class_method_01 {}
+ (void)class_method_02 {}
@end
void *_objc_empty_cache;
void *_objc_empty_vtable;
112 changes: 110 additions & 2 deletions llvm/tools/llvm-objdump/MachODump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3661,6 +3661,10 @@ struct class_ro32_t {
#define RO_ROOT (1 << 1)
#define RO_HAS_CXX_STRUCTORS (1 << 2)

/* Values for method_list{64,32}_t->entsize */
#define ML_HAS_RELATIVE_PTRS (1 << 31)
#define ML_ENTSIZE_MASK 0xFFFF

struct method_list64_t {
uint32_t entsize;
uint32_t count;
Expand All @@ -3685,6 +3689,12 @@ struct method32_t {
uint32_t imp; /* IMP (32-bit pointer) */
};

struct method_relative_t {
int32_t name; /* SEL (32-bit relative) */
int32_t types; /* const char * (32-bit relative) */
int32_t imp; /* IMP (32-bit relative) */
};

struct protocol_list64_t {
uint64_t count; /* uintptr_t (a 64-bit value) */
/* struct protocol64_t * list[0]; These pointers follow inline */
Expand Down Expand Up @@ -3986,6 +3996,12 @@ inline void swapStruct(struct method32_t &m) {
sys::swapByteOrder(m.imp);
}

inline void swapStruct(struct method_relative_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);
}
Expand Down Expand Up @@ -4440,6 +4456,84 @@ static void print_layout_map32(uint32_t p, struct DisassembleInfo *info) {
print_layout_map(layout_map, left);
}

static void print_relative_method_list(uint32_t structSizeAndFlags,
uint32_t structCount, uint64_t p,
struct DisassembleInfo *info,
const char *indent,
uint32_t pointerBits) {
struct method_relative_t m;
const char *r, *name;
uint32_t offset, xoffset, left, i;
SectionRef S, xS;

assert(((structSizeAndFlags & ML_HAS_RELATIVE_PTRS) != 0) &&
"expected structSizeAndFlags to have ML_HAS_RELATIVE_PTRS flag");

outs() << indent << "\t\t entsize "
<< (structSizeAndFlags & ML_ENTSIZE_MASK) << " (relative) \n";
outs() << indent << "\t\t count " << structCount << "\n";

for (i = 0; i < structCount; i++) {
r = get_pointer_64(p, offset, left, S, info);
memset(&m, '\0', sizeof(struct method_relative_t));
if (left < sizeof(struct method_relative_t)) {
memcpy(&m, r, left);
outs() << indent << " (method_t extends past the end of the section)\n";
} else
memcpy(&m, r, sizeof(struct method_relative_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_relative_t, name);
uint64_t absNameRefVA = relNameRefVA + m.name;
outs() << " (" << format("0x%" PRIx32, absNameRefVA) << ")";

// since this is a relative list, absNameRefVA is the address of the
// __objc_selrefs entry, so a pointer, not the actual name
const char *nameRefPtr =
get_pointer_64(absNameRefVA, xoffset, left, xS, info);
if (nameRefPtr) {
uint32_t pointerSize = pointerBits / CHAR_BIT;
if (left < pointerSize)
outs() << indent << " (nameRefPtr extends past the end of the section)";
else {
if (pointerSize == 64) {
name = get_pointer_64(*reinterpret_cast<const uint64_t *>(nameRefPtr),
xoffset, left, xS, info);
} else {
name = get_pointer_32(*reinterpret_cast<const uint32_t *>(nameRefPtr),
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_relative_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_relative_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_relative_t);
offset += sizeof(struct method_relative_t);
}
}

static void print_method_list64_t(uint64_t p, struct DisassembleInfo *info,
const char *indent) {
struct method_list64_t ml;
Expand All @@ -4461,10 +4555,17 @@ static void print_method_list64_t(uint64_t p, struct DisassembleInfo *info,
memcpy(&ml, r, sizeof(struct method_list64_t));
if (info->O->isLittleEndian() != sys::IsLittleEndianHost)
swapStruct(ml);
p += sizeof(struct method_list64_t);

if ((ml.entsize & ML_HAS_RELATIVE_PTRS) != 0) {
print_relative_method_list(ml.entsize, ml.count, p, info, indent,
/*pointerBits=*/64);
return;
}

outs() << indent << "\t\t entsize " << ml.entsize << "\n";
outs() << indent << "\t\t count " << ml.count << "\n";

p += sizeof(struct method_list64_t);
offset += sizeof(struct method_list64_t);
for (i = 0; i < ml.count; i++) {
r = get_pointer_64(p, offset, left, S, info);
Expand Down Expand Up @@ -4552,10 +4653,17 @@ static void print_method_list32_t(uint64_t p, struct DisassembleInfo *info,
memcpy(&ml, r, sizeof(struct method_list32_t));
if (info->O->isLittleEndian() != sys::IsLittleEndianHost)
swapStruct(ml);
p += sizeof(struct method_list32_t);

if ((ml.entsize & ML_HAS_RELATIVE_PTRS) != 0) {
print_relative_method_list(ml.entsize, ml.count, p, info, indent,
/*pointerBits=*/32);
return;
}

outs() << indent << "\t\t entsize " << ml.entsize << "\n";
outs() << indent << "\t\t count " << ml.count << "\n";

p += sizeof(struct method_list32_t);
offset += sizeof(struct method_list32_t);
for (i = 0; i < ml.count; i++) {
r = get_pointer_32(p, offset, left, S, info);
Expand Down

8 comments on commit 9d5edfd

@jakeegan
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alx32
Copy link
Contributor Author

@alx32 alx32 commented on 9d5edfd Mar 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @jakeegan - this commit was landed on Mar14 as #84250.
Then backed out later that day because of the armv7/armv8 test failures.
Then landed again as #85477

The failure from the link is a build on 229640343e400394b315c6798c7c19e8a9bd188c - from Mar 14th when the problematic version of the commit was checked in.
This failure should be fixed currently. Are we seeing any recent failures after #85477 ?

@jakeegan
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for investigating. Yes we're still seeing the failure after #85477. I just checked the most recent build (here: https://lab.llvm.org/buildbot/#/builders/214/builds/11630) and it contains that commit. I also tried a clean build, but still see the failure.

@alx32
Copy link
Contributor Author

@alx32 alx32 commented on 9d5edfd Mar 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indeed a new failure, thanks for pointing this out. Are there instructions how to build / run "clang-ppc64-aix" platform ? What type of machines are these ?

@jakeegan
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The machine is an AIX operating system running on powerpc hardware. I could give you access to an AIX machine and give you instructions to build. In the meantime I'll XFAIL the test to unblock the bot.

@alx32
Copy link
Contributor Author

@alx32 alx32 commented on 9d5edfd Mar 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that would be great - XFAIL-ing and getting access to an AIX machine.

@alx32
Copy link
Contributor Author

@alx32 alx32 commented on 9d5edfd Mar 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like the obvious fix: #85778
Should I just merge this or would you be able to test or give me access on an AIX machine ?

@jakeegan
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alx32 Sorry to get back to you late. It seems it's already been merged in. Thanks a lot for fixing it!

Please sign in to comment.