Skip to content

Commit

Permalink
[lldb] Support Universal Mach-O binaries with a fat64 header
Browse files Browse the repository at this point in the history
Support universal Mach-O binaries with a fat64 header. After
4d683f7, dsymutil can now generate such binaries when the offsets
would otherwise overflow the 32-bit offsets in the regular fat header.

rdar://107289570

Differential revision: https://reviews.llvm.org/D147012
  • Loading branch information
JDevlieghere committed Mar 28, 2023
1 parent a955a31 commit fda53ad
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 27 deletions.
2 changes: 1 addition & 1 deletion lldb/packages/Python/lldbsuite/test/make/Makefile.rules
Expand Up @@ -153,7 +153,7 @@ ARCHFLAG ?= -arch
#----------------------------------------------------------------------
ifeq "$(OS)" "Darwin"
DS := $(DSYMUTIL)
DSFLAGS =
DSFLAGS := $(DSFLAGS_EXTRAS)
DSYM = $(EXE).dSYM
AR := $(CROSS_COMPILE)libtool
ARFLAGS := -static -o
Expand Down
Expand Up @@ -57,7 +57,8 @@ ObjectContainer *ObjectContainerUniversalMachO::CreateInstance(
bool ObjectContainerUniversalMachO::MagicBytesMatch(const DataExtractor &data) {
lldb::offset_t offset = 0;
uint32_t magic = data.GetU32(&offset);
return magic == FAT_MAGIC || magic == FAT_CIGAM;
return magic == FAT_MAGIC || magic == FAT_CIGAM || magic == FAT_MAGIC_64 ||
magic == FAT_CIGAM_64;
}

ObjectContainerUniversalMachO::ObjectContainerUniversalMachO(
Expand All @@ -82,38 +83,51 @@ bool ObjectContainerUniversalMachO::ParseHeader() {

bool ObjectContainerUniversalMachO::ParseHeader(
lldb_private::DataExtractor &data, llvm::MachO::fat_header &header,
std::vector<llvm::MachO::fat_arch> &fat_archs) {
bool success = false;
std::vector<FatArch> &fat_archs) {
// Store the file offset for this universal file as we could have a universal
// .o file in a BSD archive, or be contained in another kind of object.
// Universal mach-o files always have their headers in big endian.
lldb::offset_t offset = 0;
data.SetByteOrder(eByteOrderBig);
header.magic = data.GetU32(&offset);
fat_archs.clear();

if (header.magic == FAT_MAGIC) {

data.SetAddressByteSize(4);
// Universal mach-o files always have their headers in big endian.
if (header.magic == FAT_MAGIC || header.magic == FAT_MAGIC_64) {
const bool is_fat64 = header.magic == FAT_MAGIC_64;
data.SetAddressByteSize(is_fat64 ? 8 : 4);

header.nfat_arch = data.GetU32(&offset);

// Now we should have enough data for all of the fat headers, so lets index
// them so we know how many architectures that this universal binary
// contains.
uint32_t arch_idx = 0;
for (arch_idx = 0; arch_idx < header.nfat_arch; ++arch_idx) {
for (uint32_t arch_idx = 0; arch_idx < header.nfat_arch; ++arch_idx) {
if (data.ValidOffsetForDataOfSize(offset, sizeof(fat_arch))) {
fat_arch arch;
if (data.GetU32(&offset, &arch, sizeof(fat_arch) / sizeof(uint32_t)))
fat_archs.push_back(arch);
if (is_fat64) {
fat_arch_64 arch;
arch.cputype = data.GetU32(&offset);
arch.cpusubtype = data.GetU32(&offset);
arch.offset = data.GetU64(&offset);
arch.size = data.GetU64(&offset);
arch.align = data.GetU32(&offset);
arch.reserved = data.GetU32(&offset);
fat_archs.emplace_back(arch);
} else {
fat_arch arch;
arch.cputype = data.GetU32(&offset);
arch.cpusubtype = data.GetU32(&offset);
arch.offset = data.GetU32(&offset);
arch.size = data.GetU32(&offset);
arch.align = data.GetU32(&offset);
fat_archs.emplace_back(arch);
}
}
}
success = true;
} else {
memset(&header, 0, sizeof(header));
return true;
}
return success;

memset(&header, 0, sizeof(header));
return true;
}

size_t ObjectContainerUniversalMachO::GetNumArchitectures() const {
Expand All @@ -123,8 +137,8 @@ size_t ObjectContainerUniversalMachO::GetNumArchitectures() const {
bool ObjectContainerUniversalMachO::GetArchitectureAtIndex(
uint32_t idx, ArchSpec &arch) const {
if (idx < m_header.nfat_arch) {
arch.SetArchitecture(eArchTypeMachO, m_fat_archs[idx].cputype,
m_fat_archs[idx].cpusubtype);
arch.SetArchitecture(eArchTypeMachO, m_fat_archs[idx].GetCPUType(),
m_fat_archs[idx].GetCPUSubType());
return true;
}
return false;
Expand Down Expand Up @@ -166,8 +180,8 @@ ObjectContainerUniversalMachO::GetObjectFile(const FileSpec *file) {
DataBufferSP data_sp;
lldb::offset_t data_offset = 0;
return ObjectFile::FindPlugin(
module_sp, file, m_offset + m_fat_archs[arch_idx].offset,
m_fat_archs[arch_idx].size, data_sp, data_offset);
module_sp, file, m_offset + m_fat_archs[arch_idx].GetOffset(),
m_fat_archs[arch_idx].GetSize(), data_sp, data_offset);
}
}
return ObjectFileSP();
Expand All @@ -184,11 +198,12 @@ size_t ObjectContainerUniversalMachO::GetModuleSpecifications(

if (ObjectContainerUniversalMachO::MagicBytesMatch(data)) {
llvm::MachO::fat_header header;
std::vector<llvm::MachO::fat_arch> fat_archs;
std::vector<FatArch> fat_archs;
if (ParseHeader(data, header, fat_archs)) {
for (const llvm::MachO::fat_arch &fat_arch : fat_archs) {
const lldb::offset_t slice_file_offset = fat_arch.offset + file_offset;
if (fat_arch.offset < file_size && file_size > slice_file_offset) {
for (const FatArch &fat_arch : fat_archs) {
const lldb::offset_t slice_file_offset =
fat_arch.GetOffset() + file_offset;
if (fat_arch.GetOffset() < file_size && file_size > slice_file_offset) {
ObjectFile::GetModuleSpecifications(
file, slice_file_offset, file_size - slice_file_offset, specs);
}
Expand Down
Expand Up @@ -63,11 +63,46 @@ class ObjectContainerUniversalMachO : public lldb_private::ObjectContainer {

protected:
llvm::MachO::fat_header m_header;
std::vector<llvm::MachO::fat_arch> m_fat_archs;

struct FatArch {
FatArch(llvm::MachO::fat_arch arch) : m_arch(arch), m_is_fat64(false) {}
FatArch(llvm::MachO::fat_arch_64 arch) : m_arch(arch), m_is_fat64(true) {}

uint32_t GetCPUType() const {
return m_is_fat64 ? m_arch.fat_arch_64.cputype : m_arch.fat_arch.cputype;
}

uint32_t GetCPUSubType() const {
return m_is_fat64 ? m_arch.fat_arch_64.cpusubtype
: m_arch.fat_arch.cpusubtype;
}

uint64_t GetOffset() const {
return m_is_fat64 ? m_arch.fat_arch_64.offset : m_arch.fat_arch.offset;
}

uint64_t GetSize() const {
return m_is_fat64 ? m_arch.fat_arch_64.size : m_arch.fat_arch.size;
}

uint32_t GetAlign() const {
return m_is_fat64 ? m_arch.fat_arch_64.align : m_arch.fat_arch.align;
}

private:
const union Arch {
Arch(llvm::MachO::fat_arch arch) : fat_arch(arch) {}
Arch(llvm::MachO::fat_arch_64 arch) : fat_arch_64(arch) {}
llvm::MachO::fat_arch fat_arch;
llvm::MachO::fat_arch_64 fat_arch_64;
} m_arch;
const bool m_is_fat64;
};
std::vector<FatArch> m_fat_archs;

static bool ParseHeader(lldb_private::DataExtractor &data,
llvm::MachO::fat_header &header,
std::vector<llvm::MachO::fat_arch> &fat_archs);
std::vector<FatArch> &fat_archs);
};

#endif // LLDB_SOURCE_PLUGINS_OBJECTCONTAINER_UNIVERSAL_MACH_O_OBJECTCONTAINERUNIVERSALMACHO_H
24 changes: 24 additions & 0 deletions lldb/test/API/macosx/universal64/Makefile
@@ -0,0 +1,24 @@
EXE := fat.out

ifdef FAT64_DSYM
DSFLAGS_EXTRAS=-fat64
endif

include Makefile.rules

all: fat.out

fat.out: fat.arm64.out fat.x86_64.out
lipo -fat64 -create -o $@ $^

fat.x86_64.out: fat.x86_64.o
$(CC) -isysroot $(SDKROOT) -target x86_64-apple-macosx10.9 -o $@ $<

fat.arm64.out: fat.arm64.o
$(CC) -isysroot $(SDKROOT) -target arm64-apple-macosx10.9 -o $@ $<

fat.x86_64.o: main.c
$(CC) -isysroot $(SDKROOT) -g -O0 -target x86_64-apple-macosx11 -c -o $@ $<

fat.arm64.o: main.c
$(CC) -isysroot $(SDKROOT) -g -O0 -target arm64-apple-macosx11 -c -o $@ $<
39 changes: 39 additions & 0 deletions lldb/test/API/macosx/universal64/TestUniversal64.py
@@ -0,0 +1,39 @@
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil


class Universal64TestCase(TestBase):
NO_DEBUG_INFO_TESTCASE = True

def do_test(self):
# Get the executable.
exe = self.getBuildArtifact("fat.out")

# Create a target.
self.target = self.dbg.CreateTarget(exe)

# Create a breakpoint on main.
main_bp = self.target.BreakpointCreateByName("main")
self.assertTrue(main_bp, VALID_BREAKPOINT)

# Make sure the binary and the dSYM are in the image list.
self.expect("image list ", patterns=['fat.out', 'fat.out.dSYM'])

# The dynamic loader doesn't support fat64 executables so we can't
# actually launch them here.

@skipUnlessDarwin
@skipIfDarwinEmbedded
def test_universal64_executable(self):
"""Test fat64 universal executable"""
self.build(debug_info="dsym")
self.do_test()

@skipUnlessDarwin
@skipIfDarwinEmbedded
@skipIf(compiler="clang", compiler_version=['<', '7.0'])
def test_universal64_dsym(self):
"""Test fat64 universal dSYM"""
self.build(debug_info="dsym", dictionary={'FAT64_DSYM': '1'})
self.do_test()
5 changes: 5 additions & 0 deletions lldb/test/API/macosx/universal64/main.c
@@ -0,0 +1,5 @@
#include <stdio.h>

int foo() { return 0; }

int main(int argc, char **argv) { return foo(); }

0 comments on commit fda53ad

Please sign in to comment.