Skip to content

Commit

Permalink
Add support for a "load binary" LC_NOTE in mach-o corefiles
Browse files Browse the repository at this point in the history
Add lldb support for a Mach-O "load binary" LC_NOTE which provides
a UUID, load address/slide, and possibly a name of a binary that
should be loaded when examining the core.

struct load_binary
{
    uint32_t version;        // currently 1
    uuid_t   uuid;           // all zeroes if uuid not specified
    uint64_t load_address;   // virtual address where the macho is loaded, UINT64_MAX if unavail
    uint64_t slide;          // slide to be applied to file address to get load address, 0 if unavail
    char     name_cstring[]; // must be nul-byte terminated c-string, '\0' alone if name unavail
} __attribute__((packed));

Differential Revision: https://reviews.llvm.org/D115494
rdar://85069250
  • Loading branch information
jasonmolenda committed Dec 13, 2021
1 parent 5c23acb commit f212032
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 34 deletions.
113 changes: 91 additions & 22 deletions lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
Expand Up @@ -6973,6 +6973,23 @@ ObjectFileMachO::GetCorefileAllImageInfos() {
}
image_infos.all_image_infos.push_back(image_entry);
}
} else if (strcmp("load binary", data_owner) == 0) {
uint32_t version = m_data.GetU32(&fileoff);
if (version == 1) {
uuid_t uuid;
memcpy(&uuid, m_data.GetData(&fileoff, sizeof(uuid_t)),
sizeof(uuid_t));
uint64_t load_address = m_data.GetU64(&fileoff);
uint64_t slide = m_data.GetU64(&fileoff);
std::string filename = m_data.GetCStr(&fileoff);

MachOCorefileImageEntry image_entry;
image_entry.filename = filename;
image_entry.uuid = UUID::fromData(uuid, sizeof(uuid_t));
image_entry.load_address = load_address;
image_entry.slide = slide;
image_infos.all_image_infos.push_back(image_entry);
}
}
}
offset = cmd_offset + lc.cmdsize;
Expand All @@ -6983,29 +7000,42 @@ ObjectFileMachO::GetCorefileAllImageInfos() {

bool ObjectFileMachO::LoadCoreFileImages(lldb_private::Process &process) {
MachOCorefileAllImageInfos image_infos = GetCorefileAllImageInfos();
bool added_images = false;
if (image_infos.IsValid()) {
for (const MachOCorefileImageEntry &image : image_infos.all_image_infos) {
ModuleSpec module_spec;
module_spec.GetUUID() = image.uuid;
module_spec.GetFileSpec() = FileSpec(image.filename.c_str());
if (image.currently_executing) {
Symbols::DownloadObjectAndSymbolFile(module_spec, true);
if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) {
process.GetTarget().GetOrCreateModule(module_spec, false);
}
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_DYNAMIC_LOADER));

ModuleList added_modules;
for (const MachOCorefileImageEntry &image : image_infos.all_image_infos) {
ModuleSpec module_spec;
module_spec.GetUUID() = image.uuid;
module_spec.GetFileSpec() = FileSpec(image.filename.c_str());
if (image.currently_executing) {
Symbols::DownloadObjectAndSymbolFile(module_spec, true);
if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) {
process.GetTarget().GetOrCreateModule(module_spec, false);
}
Status error;
ModuleSP module_sp =
process.GetTarget().GetOrCreateModule(module_spec, false, &error);
if (!module_sp.get() || !module_sp->GetObjectFile()) {
if (image.load_address != LLDB_INVALID_ADDRESS) {
module_sp = process.ReadModuleFromMemory(module_spec.GetFileSpec(),
image.load_address);
}
}
Status error;
ModuleSP module_sp =
process.GetTarget().GetOrCreateModule(module_spec, false, &error);
if (!module_sp.get() || !module_sp->GetObjectFile()) {
if (image.load_address != LLDB_INVALID_ADDRESS) {
module_sp = process.ReadModuleFromMemory(module_spec.GetFileSpec(),
image.load_address);
}
if (module_sp.get()) {
added_images = true;
}
if (module_sp.get()) {
// Will call ModulesDidLoad with all modules once they've all
// been added to the Target with load addresses. Don't notify
// here, before the load address is set.
const bool notify = false;
process.GetTarget().GetImages().AppendIfNeeded(module_sp, notify);
added_modules.Append(module_sp, notify);
if (image.segment_load_addresses.size() > 0) {
if (log) {
std::string uuidstr = image.uuid.GetAsString();
log->Printf("ObjectFileMachO::LoadCoreFileImages adding binary '%s' "
"UUID %s with section load addresses",
image.filename.c_str(), uuidstr.c_str());
}
for (auto name_vmaddr_tuple : image.segment_load_addresses) {
SectionList *sectlist = module_sp->GetObjectFile()->GetSectionList();
if (sectlist) {
Expand All @@ -7017,8 +7047,47 @@ bool ObjectFileMachO::LoadCoreFileImages(lldb_private::Process &process) {
}
}
}
} else if (image.load_address != LLDB_INVALID_ADDRESS) {
if (log) {
std::string uuidstr = image.uuid.GetAsString();
log->Printf("ObjectFileMachO::LoadCoreFileImages adding binary '%s' "
"UUID %s with load address 0x%" PRIx64,
image.filename.c_str(), uuidstr.c_str(),
image.load_address);
}
const bool address_is_slide = false;
bool changed = false;
module_sp->SetLoadAddress(process.GetTarget(), image.load_address,
address_is_slide, changed);
} else if (image.slide != 0) {
if (log) {
std::string uuidstr = image.uuid.GetAsString();
log->Printf("ObjectFileMachO::LoadCoreFileImages adding binary '%s' "
"UUID %s with slide amount 0x%" PRIx64,
image.filename.c_str(), uuidstr.c_str(), image.slide);
}
const bool address_is_slide = true;
bool changed = false;
module_sp->SetLoadAddress(process.GetTarget(), image.slide,
address_is_slide, changed);
} else {
if (log) {
std::string uuidstr = image.uuid.GetAsString();
log->Printf("ObjectFileMachO::LoadCoreFileImages adding binary '%s' "
"UUID %s at its file address, no slide applied",
image.filename.c_str(), uuidstr.c_str());
}
const bool address_is_slide = true;
bool changed = false;
module_sp->SetLoadAddress(process.GetTarget(), 0, address_is_slide,
changed);
}
}
}
return added_images;
if (added_modules.GetSize() > 0) {
process.GetTarget().ModulesDidLoad(added_modules);
process.Flush();
return true;
}
return false;
}
3 changes: 2 additions & 1 deletion lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h
Expand Up @@ -225,7 +225,8 @@ class ObjectFileMachO : public lldb_private::ObjectFile {
std::string filename;
lldb_private::UUID uuid;
lldb::addr_t load_address = LLDB_INVALID_ADDRESS;
bool currently_executing;
lldb::addr_t slide = 0;
bool currently_executing = false;
std::vector<std::tuple<lldb_private::ConstString, lldb::addr_t>>
segment_load_addresses;
};
Expand Down
Expand Up @@ -18,6 +18,7 @@ class TestCorefileDefaultPtrauth(TestBase):
@skipIf(debug_info=no_match(["dsym"]), bugnumber="This test is looking explicitly for a dSYM")
@skipIf(archs=no_match(['arm64','arm64e']))
@skipUnlessDarwin
@skipIfRemote
def test_lc_note(self):
self.build()
self.test_exe = self.getBuildArtifact("a.out")
Expand All @@ -32,16 +33,18 @@ def test_lc_note(self):
## to fall back on its old default value for Darwin arm64 ABIs
## to correctly strip the bits.

# Create a Target with our main executable binary to get it
# seeded in lldb's global module cache. Then delete the Target.
# This way when the corefile searches for a binary with its UUID,
# it'll be found by that search.
initial_target = self.dbg.CreateTarget(self.test_exe)
self.dbg.DeleteTarget(initial_target)

self.target = self.dbg.CreateTarget('')
err = lldb.SBError()
self.process = self.target.LoadCore(self.corefile)
self.assertEqual(self.process.IsValid(), True)
modspec = lldb.SBModuleSpec()
modspec.SetFileSpec(lldb.SBFileSpec(self.test_exe, True))
m = self.target.AddModule(modspec)
self.assertTrue(m.IsValid())
self.target.SetModuleLoadAddress (m, 0)


# target variable should show us both the actual function
# pointer with ptrauth bits and the symbol it resolves to,
# with the ptrauth bits stripped, e.g.
Expand Down
68 changes: 63 additions & 5 deletions lldb/test/API/macosx/corefile-default-ptrauth/create-corefile.c
Expand Up @@ -6,6 +6,7 @@
#include <mach/machine/thread_state.h>
#include <inttypes.h>
#include <sys/syslimits.h>
#include <uuid/uuid.h>

// Given an executable binary with
// "fmain" (a function pointer to main)
Expand Down Expand Up @@ -55,6 +56,28 @@ int main(int argc, char **argv)
}
pclose (nm);

sprintf (buf, "dwarfdump -u '%s'", argv[1]);
FILE *dwarfdump = popen(buf, "r");
if (!dwarfdump) {
fprintf (stderr, "Unable to run dwarfdump -u on '%s'\n", argv[1]);
exit (1);
}
uuid_t uuid;
uuid_clear (uuid);
while (fgets (buf, sizeof(buf), dwarfdump)) {
if (strncmp (buf, "UUID: ", 6) == 0) {
buf[6 + 36] = '\0';
if (uuid_parse (buf + 6, uuid) != 0) {
fprintf (stderr, "Unable to parse UUID in '%s'\n", buf);
exit (1);
}
}
}
if (uuid_is_null(uuid)) {
fprintf (stderr, "Got a null uuid for the binary\n");
exit (1);
}

if (main_addr == 0 || fmain_addr == 0) {
fprintf(stderr, "Unable to find address of main or fmain in %s.\n",
argv[1]);
Expand All @@ -65,7 +88,9 @@ int main(int argc, char **argv)
// 1. mach header
// 2. LC_THREAD load command
// 3. LC_SEGMENT_64 load command
// 4. memory segment contents
// 4. LC_NOTE load command
// 5. memory segment contents
// 6. "load binary" note contents

// struct thread_command {
// uint32_t cmd;
Expand All @@ -80,21 +105,22 @@ int main(int argc, char **argv)
mh.cputype = CPU_TYPE_ARM64;
mh.cpusubtype = CPU_SUBTYPE_ARM64E;
mh.filetype = MH_CORE;
mh.ncmds = 2; // LC_THREAD, LC_SEGMENT_64
mh.sizeofcmds = size_of_thread_cmd + sizeof(struct segment_command_64);
mh.ncmds = 3; // LC_THREAD, LC_SEGMENT_64, LC_NOTE
mh.sizeofcmds = size_of_thread_cmd + sizeof(struct segment_command_64) + sizeof(struct note_command);
mh.flags = 0;
mh.reserved = 0;

fwrite(&mh, sizeof (mh), 1, out);

struct note_command lcnote;
struct segment_command_64 seg;
seg.cmd = LC_SEGMENT_64;
seg.cmdsize = sizeof(seg);
memset (&seg.segname, 0, 16);
seg.vmaddr = fmain_addr;
seg.vmsize = 8;
// Offset to segment contents
seg.fileoff = sizeof (mh) + size_of_thread_cmd + sizeof(seg);
seg.fileoff = sizeof (mh) + size_of_thread_cmd + sizeof(seg) + sizeof(lcnote);
seg.filesize = 8;
seg.maxprot = 3;
seg.initprot = 3;
Expand All @@ -116,15 +142,47 @@ int main(int argc, char **argv)
memset (&regstate, 0, sizeof (regstate));
fwrite (&regstate, sizeof (regstate), 1, out);

lcnote.cmd = LC_NOTE;
lcnote.cmdsize = sizeof (lcnote);
strcpy (lcnote.data_owner, "load binary");

// 8 is the size of the LC_SEGMENT contents
lcnote.offset = sizeof (mh) + size_of_thread_cmd + sizeof(seg) + sizeof(lcnote) + 8;

// struct load_binary
// {
// uint32_t version; // currently 1
// uuid_t uuid; // all zeroes if uuid not specified
// uint64_t load_address; // virtual address where the macho is loaded, UINT64_MAX if unavail
// uint64_t slide; // slide to be applied to file address to get load address, 0 if unavail
// char name_cstring[]; // must be nul-byte terminated c-string, '\0' alone if name unavail
// } __attribute__((packed));
lcnote.size = 4 + 16 + 8 + 8 + sizeof("a.out");

fwrite (&lcnote, sizeof(lcnote), 1, out);

// Write the contents of the memory segment

// Or together a random PAC value from a system using 39 bits
// of addressing with the address of main(). lldb will need
// to correctly strip off the high bits to find the address of
// main.
uint64_t segment_contents = 0xe46bff0000000000 | main_addr;

fwrite (&segment_contents, sizeof (segment_contents), 1, out);

// Now write the contents of the "load binary" LC_NOTE.
{
uint32_t version = 1;
fwrite (&version, sizeof (version), 1, out);
fwrite (&uuid, sizeof (uuid), 1, out);
uint64_t load_address = UINT64_MAX;
fwrite (&load_address, sizeof (load_address), 1, out);
uint64_t slide = 0;
fwrite (&slide, sizeof (slide), 1, out);
strcpy (buf, "a.out");
fwrite (buf, 6, 1, out);
}

fclose (out);

exit (0);
Expand Down

0 comments on commit f212032

Please sign in to comment.