From f2120328e81879bf14d2a5c381749a11577fa304 Mon Sep 17 00:00:00 2001 From: Jason Molenda Date: Mon, 13 Dec 2021 13:20:05 -0800 Subject: [PATCH] Add support for a "load binary" LC_NOTE in mach-o corefiles 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 --- .../Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp | 113 +++++++++++++++++---- .../Plugins/ObjectFile/Mach-O/ObjectFileMachO.h | 3 +- .../TestCorefileDefaultPtrauth.py | 15 +-- .../corefile-default-ptrauth/create-corefile.c | 68 ++++++++++++- 4 files changed, 165 insertions(+), 34 deletions(-) diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp index 29cc399..e0087db 100644 --- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp +++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp @@ -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; @@ -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) { @@ -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; } diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h index b3a941a..084b0ac 100644 --- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h +++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h @@ -225,7 +225,8 @@ protected: 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> segment_load_addresses; }; diff --git a/lldb/test/API/macosx/corefile-default-ptrauth/TestCorefileDefaultPtrauth.py b/lldb/test/API/macosx/corefile-default-ptrauth/TestCorefileDefaultPtrauth.py index 7540763..d3b0861 100644 --- a/lldb/test/API/macosx/corefile-default-ptrauth/TestCorefileDefaultPtrauth.py +++ b/lldb/test/API/macosx/corefile-default-ptrauth/TestCorefileDefaultPtrauth.py @@ -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") @@ -32,16 +33,18 @@ class TestCorefileDefaultPtrauth(TestBase): ## 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. diff --git a/lldb/test/API/macosx/corefile-default-ptrauth/create-corefile.c b/lldb/test/API/macosx/corefile-default-ptrauth/create-corefile.c index c1052e1..e516808 100644 --- a/lldb/test/API/macosx/corefile-default-ptrauth/create-corefile.c +++ b/lldb/test/API/macosx/corefile-default-ptrauth/create-corefile.c @@ -6,6 +6,7 @@ #include #include #include +#include // Given an executable binary with // "fmain" (a function pointer to main) @@ -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]); @@ -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; @@ -80,13 +105,14 @@ 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); @@ -94,7 +120,7 @@ int main(int argc, char **argv) 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; @@ -116,15 +142,47 @@ int main(int argc, char **argv) memset (®state, 0, sizeof (regstate)); fwrite (®state, 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); -- 2.7.4