Read and write a LC_NOTE "addrable bits" for addressing mask
authorJason Molenda <jason@molenda.com>
Thu, 22 Jul 2021 08:02:54 +0000 (01:02 -0700)
committerJason Molenda <jason@molenda.com>
Thu, 22 Jul 2021 08:06:44 +0000 (01:06 -0700)
This patch adds code to process save-core for Mach-O files which
embeds an "addrable bits" LC_NOTE when the process is using a
code address mask (e.g. AArch64 v8.3 with ptrauth aka arm64e).
Add code to ObjectFileMachO to read that LC_NOTE from corefiles,
and ProcessMachCore to set the process masks based on it when reading
a corefile back in.

Also have "process status --verbose" print the current address masks
that lldb is using internally to strip ptrauth bits off of addresses.

Differential Revision: https://reviews.llvm.org/D106348
rdar://68630113

lldb/include/lldb/Symbol/ObjectFile.h
lldb/include/lldb/Target/Process.h
lldb/source/Commands/CommandObjectProcess.cpp
lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h
lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp
lldb/test/API/macosx/lc-note/addrable-bits/Makefile [new file with mode: 0644]
lldb/test/API/macosx/lc-note/addrable-bits/TestAddrableBitsCorefile.py [new file with mode: 0644]
lldb/test/API/macosx/lc-note/addrable-bits/main.c [new file with mode: 0644]

index bf1417d..24bb3b1 100644 (file)
@@ -495,6 +495,16 @@ public:
       return std::string();
   }
 
+  /// Some object files may have the number of bits used for addressing
+  /// embedded in them, e.g. a Mach-O core file using an LC_NOTE.  These
+  /// object files can return the address mask that should be used in
+  /// the Process.
+  /// \return
+  ///     The mask will have bits set which aren't used for addressing --
+  ///     typically, the high bits.
+  ///     Zero is returned when no address bits mask is available.
+  virtual lldb::addr_t GetAddressMask() { return 0; }
+
   /// When the ObjectFile is a core file, lldb needs to locate the "binary" in
   /// the core file.  lldb can iterate over the pages looking for a valid
   /// binary, but some core files may have metadata  describing where the main
index 2457ac3..1f671a1 100644 (file)
@@ -2899,7 +2899,8 @@ protected:
   std::atomic<bool> m_finalizing;
 
   /// Mask for code an data addresses. The default value (0) means no mask is
-  /// set.
+  /// set.  The bits set to 1 indicate bits that are NOT significant for
+  /// addressing.
   /// @{
   lldb::addr_t m_code_address_mask = 0;
   lldb::addr_t m_data_address_mask = 0;
index e4f67c0..00fb4d6 100644 (file)
@@ -29,6 +29,8 @@
 #include "lldb/Utility/Args.h"
 #include "lldb/Utility/State.h"
 
+#include <bitset>
+
 using namespace lldb;
 using namespace lldb_private;
 
@@ -1341,6 +1343,18 @@ protected:
                              num_frames, num_frames_with_source, stop_format);
 
     if (m_options.m_verbose) {
+      addr_t code_mask = process->GetCodeAddressMask();
+      addr_t data_mask = process->GetDataAddressMask();
+      if (code_mask != 0) {
+        int bits = std::bitset<64>(~code_mask).count();
+        result.AppendMessageWithFormat(
+            "Addressable code address mask: 0x%" PRIx64 "\n", code_mask);
+        result.AppendMessageWithFormat(
+            "Addressable data address mask: 0x%" PRIx64 "\n", data_mask);
+        result.AppendMessageWithFormat(
+            "Number of bits used in addressing (code): %d\n", bits);
+      }
+
       PlatformSP platform_sp = process->GetTarget().GetPlatform();
       if (!platform_sp) {
         result.AppendError("Couldn'retrieve the target's platform");
index e7652cf..a14c1e3 100644 (file)
@@ -62,6 +62,7 @@
 #include <uuid/uuid.h>
 #endif
 
+#include <bitset>
 #include <memory>
 
 #if LLVM_SUPPORT_XCODE_SIGNPOSTS
@@ -5571,6 +5572,46 @@ std::string ObjectFileMachO::GetIdentifierString() {
   return result;
 }
 
+addr_t ObjectFileMachO::GetAddressMask() {
+  addr_t mask = 0;
+  ModuleSP module_sp(GetModule());
+  if (module_sp) {
+    std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex());
+    lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic);
+    for (uint32_t i = 0; i < m_header.ncmds; ++i) {
+      const uint32_t cmd_offset = offset;
+      llvm::MachO::load_command lc;
+      if (m_data.GetU32(&offset, &lc.cmd, 2) == nullptr)
+        break;
+      if (lc.cmd == LC_NOTE) {
+        char data_owner[17];
+        m_data.CopyData(offset, 16, data_owner);
+        data_owner[16] = '\0';
+        offset += 16;
+        uint64_t fileoff = m_data.GetU64_unchecked(&offset);
+
+        // "addrable bits" has a uint32_t version and a uint32_t
+        // number of bits used in addressing.
+        if (strcmp("addrable bits", data_owner) == 0) {
+          offset = fileoff;
+          uint32_t version;
+          if (m_data.GetU32(&offset, &version, 1) != nullptr) {
+            if (version == 3) {
+              uint32_t num_addr_bits = m_data.GetU32_unchecked(&offset);
+              if (num_addr_bits != 0) {
+                mask = ~((1ULL << num_addr_bits) - 1);
+              }
+              break;
+            }
+          }
+        }
+      }
+      offset = cmd_offset + lc.cmdsize;
+    }
+  }
+  return mask;
+}
+
 bool ObjectFileMachO::GetCorefileMainBinaryInfo(addr_t &address, UUID &uuid,
                                                 ObjectFile::BinaryType &type) {
   address = LLDB_INVALID_ADDRESS;
@@ -6652,6 +6693,15 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
           mach_header.sizeofcmds += 8 + LC_THREAD_data.GetSize();
         }
 
+        // Bits will be set to indicate which bits are NOT used in
+        // addressing in this process or 0 for unknown.
+        uint64_t address_mask = process_sp->GetCodeAddressMask();
+        if (address_mask != 0) {
+          // LC_NOTE "addrable bits"
+          mach_header.ncmds++;
+          mach_header.sizeofcmds += sizeof(llvm::MachO::note_command);
+        }
+
         // LC_NOTE "all image infos"
         mach_header.ncmds++;
         mach_header.sizeofcmds += sizeof(llvm::MachO::note_command);
@@ -6673,28 +6723,48 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
         addr_t file_offset = buffer.GetSize() + mach_header.sizeofcmds;
 
         file_offset = llvm::alignTo(file_offset, 16);
+        std::vector<std::unique_ptr<LCNoteEntry>> lc_notes;
+
+        // Add "addrable bits" LC_NOTE when an address mask is available
+        if (address_mask != 0) {
+          std::unique_ptr<LCNoteEntry> addrable_bits_lcnote_up(
+              new LCNoteEntry(addr_byte_size, byte_order));
+          addrable_bits_lcnote_up->name = "addrable bits";
+          addrable_bits_lcnote_up->payload_file_offset = file_offset;
+          int bits = std::bitset<64>(~address_mask).count();
+          addrable_bits_lcnote_up->payload.PutHex32(3); // version
+          addrable_bits_lcnote_up->payload.PutHex32(
+              bits); // # of bits used for addressing
+          addrable_bits_lcnote_up->payload.PutHex64(0); // unused
+
+          file_offset += addrable_bits_lcnote_up->payload.GetSize();
+
+          lc_notes.push_back(std::move(addrable_bits_lcnote_up));
+        }
 
-        // Create the "all image infos" LC_NOTE payload
-        StreamString all_image_infos_payload(Stream::eBinary, addr_byte_size,
-                                             byte_order);
-        offset_t all_image_infos_payload_start = file_offset;
-        file_offset = CreateAllImageInfosPayload(process_sp, file_offset,
-                                                 all_image_infos_payload);
-
-        // Add the "all image infos" LC_NOTE load command
-        llvm::MachO::note_command all_image_info_note = {
-            LC_NOTE,                           /* uint32_t cmd */
-            sizeof(llvm::MachO::note_command), /* uint32_t cmdsize */
-            "all image infos",                 /* char data_owner[16] */
-            all_image_infos_payload_start,     /* uint64_t offset */
-            file_offset - all_image_infos_payload_start /* uint64_t size */
-        };
-        buffer.PutHex32(all_image_info_note.cmd);
-        buffer.PutHex32(all_image_info_note.cmdsize);
-        buffer.PutRawBytes(all_image_info_note.data_owner,
-                           sizeof(all_image_info_note.data_owner));
-        buffer.PutHex64(all_image_info_note.offset);
-        buffer.PutHex64(all_image_info_note.size);
+        // Add "all image infos" LC_NOTE
+        std::unique_ptr<LCNoteEntry> all_image_infos_lcnote_up(
+            new LCNoteEntry(addr_byte_size, byte_order));
+        all_image_infos_lcnote_up->name = "all image infos";
+        all_image_infos_lcnote_up->payload_file_offset = file_offset;
+        file_offset = CreateAllImageInfosPayload(
+            process_sp, file_offset, all_image_infos_lcnote_up->payload);
+        lc_notes.push_back(std::move(all_image_infos_lcnote_up));
+
+        // Add LC_NOTE load commands
+        for (auto &lcnote : lc_notes) {
+          // Add the LC_NOTE load command to the file.
+          buffer.PutHex32(LC_NOTE);
+          buffer.PutHex32(sizeof(llvm::MachO::note_command));
+          char namebuf[16];
+          memset(namebuf, 0, sizeof(namebuf));
+          // this is the uncommon case where strncpy is exactly
+          // the right one, doesn't need to be nul terminated.
+          strncpy(namebuf, lcnote->name.c_str(), sizeof(namebuf));
+          buffer.PutRawBytes(namebuf, sizeof(namebuf));
+          buffer.PutHex64(lcnote->payload_file_offset);
+          buffer.PutHex64(lcnote->payload.GetSize());
+        }
 
         // Align to 4096-byte page boundary for the LC_SEGMENTs.
         file_offset = llvm::alignTo(file_offset, 4096);
@@ -6749,19 +6819,21 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
               core_file.get()->Write(buffer.GetString().data(), bytes_written);
           if (error.Success()) {
 
-            if (core_file.get()->SeekFromStart(all_image_info_note.offset) ==
-                -1) {
-              error.SetErrorStringWithFormat(
-                  "Unable to seek to corefile pos to write all iamge infos");
-              return false;
+            for (auto &lcnote : lc_notes) {
+              if (core_file.get()->SeekFromStart(lcnote->payload_file_offset) ==
+                  -1) {
+                error.SetErrorStringWithFormat("Unable to seek to corefile pos "
+                                               "to write '%s' LC_NOTE payload",
+                                               lcnote->name.c_str());
+                return false;
+              }
+              bytes_written = lcnote->payload.GetSize();
+              error = core_file.get()->Write(lcnote->payload.GetData(),
+                                             bytes_written);
+              if (!error.Success())
+                return false;
             }
 
-            bytes_written = all_image_infos_payload.GetString().size();
-            error = core_file.get()->Write(
-                all_image_infos_payload.GetString().data(), bytes_written);
-            if (!error.Success())
-              return false;
-
             // Now write the file data for all memory segments in the process
             for (const auto &segment : segment_load_commands) {
               if (core_file.get()->SeekFromStart(segment.fileoff) == -1) {
index 8c6aac1..3e8c84f 100644 (file)
@@ -15,6 +15,7 @@
 #include "lldb/Symbol/ObjectFile.h"
 #include "lldb/Utility/FileSpec.h"
 #include "lldb/Utility/RangeMap.h"
+#include "lldb/Utility/StreamString.h"
 #include "lldb/Utility/UUID.h"
 
 // This class needs to be hidden as eventually belongs in a plugin that
@@ -113,6 +114,8 @@ public:
 
   std::string GetIdentifierString() override;
 
+  lldb::addr_t GetAddressMask() override;
+
   bool GetCorefileMainBinaryInfo(lldb::addr_t &address,
                                  lldb_private::UUID &uuid,
                                  ObjectFile::BinaryType &type) override;
@@ -225,6 +228,15 @@ protected:
         segment_load_addresses;
   };
 
+  struct LCNoteEntry {
+    LCNoteEntry(uint32_t addr_byte_size, lldb::ByteOrder byte_order)
+        : payload(lldb_private::Stream::eBinary, addr_byte_size, byte_order) {}
+
+    std::string name;
+    lldb::addr_t payload_file_offset = 0;
+    lldb_private::StreamString payload;
+  };
+
   struct MachOCorefileAllImageInfos {
     std::vector<MachOCorefileImageEntry> all_image_infos;
     bool IsValid() { return all_image_infos.size() > 0; }
index b5dc502..84548ed 100644 (file)
@@ -541,6 +541,11 @@ Status ProcessMachCore::DoLoadCore() {
   if (arch.IsValid())
     GetTarget().SetArchitecture(arch);
 
+  addr_t address_mask = core_objfile->GetAddressMask();
+  if (address_mask != 0) {
+    SetCodeAddressMask(address_mask);
+    SetDataAddressMask(address_mask);
+  }
   return error;
 }
 
diff --git a/lldb/test/API/macosx/lc-note/addrable-bits/Makefile b/lldb/test/API/macosx/lc-note/addrable-bits/Makefile
new file mode 100644 (file)
index 0000000..1049594
--- /dev/null
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/macosx/lc-note/addrable-bits/TestAddrableBitsCorefile.py b/lldb/test/API/macosx/lc-note/addrable-bits/TestAddrableBitsCorefile.py
new file mode 100644 (file)
index 0000000..09e5662
--- /dev/null
@@ -0,0 +1,59 @@
+"""Test that corefiles with LC_NOTE "addrable bits" load command, creating and reading."""
+
+
+
+import os
+import re
+import subprocess
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestAddrableBitsCorefile(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def initial_setup(self):
+        self.build()
+        self.exe = self.getBuildArtifact("a.out")
+        self.corefile = self.getBuildArtifact("corefile")
+
+    @skipIf(archs=no_match(['arm64e']))
+    @skipUnlessDarwin
+    def test_lc_note_addrable_bits(self):
+        self.initial_setup()
+
+        self.target = self.dbg.CreateTarget(self.exe)
+        err = lldb.SBError()
+        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self, "break here",
+                                          lldb.SBFileSpec('main.c'))
+        self.assertEqual(process.IsValid(), True)
+
+        found_main = False
+        for f in thread.frames:
+          if f.GetFunctionName() == "main":
+            found_main = True
+        self.assertTrue(found_main)
+
+        cmdinterp = self.dbg.GetCommandInterpreter()
+        res = lldb.SBCommandReturnObject()
+        cmdinterp.HandleCommand("process save-core %s" % self.corefile, res)
+        self.assertTrue(res.Succeeded(), True)
+        process.Kill()
+        self.dbg.DeleteTarget(target)
+
+        target = self.dbg.CreateTarget('')
+        process = target.LoadCore(self.corefile)
+        self.assertTrue(process.IsValid(), True)
+        thread = process.GetSelectedThread()
+
+        found_main = False
+        for f in thread.frames:
+          if f.GetFunctionName() == "main":
+            found_main = True
+        self.assertTrue(found_main)
+
diff --git a/lldb/test/API/macosx/lc-note/addrable-bits/main.c b/lldb/test/API/macosx/lc-note/addrable-bits/main.c
new file mode 100644 (file)
index 0000000..f566e24
--- /dev/null
@@ -0,0 +1,12 @@
+int pat (int in) { 
+  return in + 5; // break here 
+}
+
+int tat (int in) { return pat(in + 10); }
+
+int mat (int in) { return tat(in + 15); }
+
+int main() {
+ int (*matp)(int) = mat;
+ return matp(10);
+}