[Lldb-commits] [lldb] [lldb][Darwin] Batch debugserver Mach-O load-command reads (PR #232684)

Karim Alweheshy via lldb-commits lldb-commits at lists.llvm.org
Fri Oct 16 04:19:24 PDT 2026


https://github.com/karim-alweheshy updated https://github.com/llvm/llvm-project/pull/232684

>From c131ea0b47ff2e0551896de221e7aa236c23fc0f Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at reddit.com>
Date: Fri, 16 Oct 2026 12:57:28 +0200
Subject: [PATCH 1/2] [lldb][Darwin] Batch debugserver Mach-O load-command
 reads

Read validated load-command prefixes through a bounded 16 KB window while retaining MachProcess::ReadMemory breakpoint restoration and exact-prefix fallback for unreadable optional data. Preserve per-command platform queries and the existing address-based deployment-info caller.

Assisted-by: OpenAI Codex
---
 .../API/macosx/unregistered-macho/Makefile    |   2 +-
 .../TestUnregisteredMacho.py                  |  88 +++++++++
 .../macosx/unregistered-macho/load-commands.c | 168 ++++++++++++++++++
 .../test/API/macosx/unregistered-macho/main.c |   3 +
 .../debugserver/source/MacOSX/MachProcess.h   |   5 +
 .../debugserver/source/MacOSX/MachProcess.mm  | 130 +++++++++++---
 6 files changed, 374 insertions(+), 22 deletions(-)
 create mode 100644 lldb/test/API/macosx/unregistered-macho/load-commands.c

diff --git a/lldb/test/API/macosx/unregistered-macho/Makefile b/lldb/test/API/macosx/unregistered-macho/Makefile
index 58c3a468659e33..7825454ac51501 100644
--- a/lldb/test/API/macosx/unregistered-macho/Makefile
+++ b/lldb/test/API/macosx/unregistered-macho/Makefile
@@ -1,3 +1,3 @@
-C_SOURCES = main.c
+C_SOURCES = main.c load-commands.c
 
 include Makefile.rules
diff --git a/lldb/test/API/macosx/unregistered-macho/TestUnregisteredMacho.py b/lldb/test/API/macosx/unregistered-macho/TestUnregisteredMacho.py
index 11ff1d4e1efa7c..664622b85b3c0e 100644
--- a/lldb/test/API/macosx/unregistered-macho/TestUnregisteredMacho.py
+++ b/lldb/test/API/macosx/unregistered-macho/TestUnregisteredMacho.py
@@ -1,5 +1,6 @@
 """Test that debugserver will parse a mach-o in inferior memory even if it's not loaded."""
 
+import json
 import os
 import re
 import subprocess
@@ -60,3 +61,90 @@ def test(self):
             substrs=['"load_address":%d,' % invalid_macho_addr],
             matching=False,
         )
+
+    def start_load_command_fixture(self):
+        self.build()
+        target, process, thread, _ = lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.c")
+        )
+        return target, process
+
+    def image_address(self, target, name):
+        value = target.FindFirstGlobalVariable(name)
+        self.assertTrue(value.IsValid(), name)
+        return value.GetValueAsUnsigned()
+
+    def image_info(self, address):
+        request = "jGetLoadedDynamicLibrariesInfos:" + json.dumps(
+            {"solib_addresses": [address]}, separators=(",", ":")
+        )
+        return json.loads(lldbutil.send_packet_get_reply(self, request))["images"]
+
+    def check_prefixes(self, image, version):
+        self.assertEqual(image["pathname"], "")
+        self.assertEqual(image["uuid"], "12345678-1234-5678-ABCD-0123456789AB")
+        self.assertEqual(image["mach_header"]["magic"], 0xFEEDFACF)
+        self.assertEqual(image["mach_header"]["cpusubtype"], 5)
+        self.assertEqual(image["min_version_os_name"], "macosx")
+        self.assertEqual(image["min_version_os_sdk"], version)
+        self.assertEqual(image["segments"], [
+            {
+                "name": "1234567890abcdef", "vmaddr": 0x123456789ABCDEF0,
+                "vmsize": 0x10000, "fileoff": 0x1000, "filesize": 0x2000,
+                "maxprot": 7,
+            },
+            {
+                "name": "__TEXT32", "vmaddr": 0x5000, "vmsize": 0x1000,
+                "fileoff": 0x20, "filesize": 0x200, "maxprot": 5,
+            },
+        ])
+
+    @no_debug_info_test
+    @skipUnlessDarwin
+    def test_load_command_prefixes_across_cache_boundaries(self):
+        target, process = self.start_load_command_fixture()
+        for name, version in [("crossing_image", "13.2.1"),
+                              ("large_image", "10.12"),
+                              ("unreadable_unknown_image", "13.2.1")]:
+            address = self.image_address(target, name)
+            images = self.image_info(address)
+            self.assertEqual(len(images), 1, name)
+            self.assertEqual(images[0]["load_address"], address)
+            self.check_prefixes(images[0], version)
+
+        # MachProcess::ReadMemory must restore software breakpoint bytes in
+        # cached command data, just as it does for an individual memory read.
+        address = self.image_address(target, "breakpoint_address")
+        kind = 4 if "arm64" in self.getArchitecture() else 1
+        self.assertEqual(
+            lldbutil.send_packet_get_reply(self, f"Z0,{address:x},{kind}"), "OK"
+        )
+        try:
+            images = self.image_info(self.image_address(target, "crossing_image"))
+            self.check_prefixes(images[0], "13.2.1")
+        finally:
+            self.assertEqual(
+                lldbutil.send_packet_get_reply(self, f"z0,{address:x},{kind}"), "OK"
+            )
+
+    @no_debug_info_test
+    @skipUnlessDarwin
+    def test_unreadable_optional_load_command_payloads(self):
+        target, process = self.start_load_command_fixture()
+        for name in ["unreadable_uuid_image", "unreadable_version_image",
+                     "huge_size_image"]:
+            images = self.image_info(self.image_address(target, name))
+            self.assertEqual(len(images), 1, name)
+            self.assertEqual(images[0]["segments"], [])
+            self.assertNotIn("min_version_os_name", images[0])
+        images = self.image_info(self.image_address(target, "unreadable_segment_image"))
+        self.assertEqual(images, [])
+
+    @no_debug_info_test
+    @skipUnlessDarwin
+    def test_malformed_load_command_prefixes(self):
+        target, process = self.start_load_command_fixture()
+        for name in ["invalid_size_image", "invalid_count_image",
+                     "invalid_prefix_image", "invalid_overrun_image"]:
+            self.assertEqual(self.image_info(self.image_address(target, name)), [], name)
+        self.assertEqual(self.image_info((1 << 64) - 8), [])
diff --git a/lldb/test/API/macosx/unregistered-macho/load-commands.c b/lldb/test/API/macosx/unregistered-macho/load-commands.c
new file mode 100644
index 00000000000000..03974c4621c0ed
--- /dev/null
+++ b/lldb/test/API/macosx/unregistered-macho/load-commands.c
@@ -0,0 +1,168 @@
+#include <assert.h>
+#include <mach-o/loader.h>
+#include <mach/machine.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <uuid/uuid.h>
+
+uint8_t *crossing_image;
+uint8_t *large_image;
+uint8_t *unreadable_unknown_image;
+uint8_t *unreadable_uuid_image;
+uint8_t *unreadable_version_image;
+uint8_t *unreadable_segment_image;
+uint8_t *invalid_size_image;
+uint8_t *invalid_count_image;
+uint8_t *invalid_prefix_image;
+uint8_t *invalid_overrun_image;
+uint8_t *huge_size_image;
+uint8_t *breakpoint_address;
+
+static const uint32_t unknown_command = 0x12345678;
+
+static void write_header(uint8_t *buffer, uint32_t count, uint32_t size) {
+  struct mach_header_64 header = {0};
+  header.magic = MH_MAGIC_64;
+#if defined(__x86_64__)
+  header.cputype = CPU_TYPE_X86_64;
+#else
+  header.cputype = CPU_TYPE_ARM64;
+#endif
+  header.cpusubtype = CPU_SUBTYPE_MASK | 5;
+  header.filetype = MH_DYLIB;
+  header.ncmds = count;
+  header.sizeofcmds = size;
+  header.flags = MH_NOUNDEFS | MH_DYLDLINK;
+  memcpy(buffer, &header, sizeof(header));
+}
+
+static size_t write_prefixes(uint8_t *buffer, int legacy_version) {
+  uint8_t *cursor = buffer;
+  struct uuid_command uuid = {0};
+  uuid.cmd = LC_UUID;
+  uuid.cmdsize = sizeof(uuid);
+  uuid_parse("12345678-1234-5678-abcd-0123456789ab", uuid.uuid);
+  memcpy(cursor, &uuid, sizeof(uuid));
+  cursor += sizeof(uuid);
+
+  struct segment_command_64 segment64 = {0};
+  segment64.cmd = LC_SEGMENT_64;
+  segment64.cmdsize = sizeof(segment64);
+  memcpy(segment64.segname, "1234567890abcdef", 16);
+  segment64.vmaddr = 0x123456789abcdef0;
+  segment64.vmsize = 0x10000;
+  segment64.fileoff = 0x1000;
+  segment64.filesize = 0x2000;
+  segment64.maxprot = 7;
+  memcpy(cursor, &segment64, sizeof(segment64));
+  cursor += sizeof(segment64);
+
+  struct segment_command segment32 = {0};
+  segment32.cmd = LC_SEGMENT;
+  segment32.cmdsize = sizeof(segment32);
+  memcpy(segment32.segname, "__TEXT32", 8);
+  segment32.vmaddr = 0x5000;
+  segment32.vmsize = 0x1000;
+  segment32.fileoff = 0x20;
+  segment32.filesize = 0x200;
+  segment32.maxprot = 5;
+  memcpy(cursor, &segment32, sizeof(segment32));
+  cursor += sizeof(segment32);
+
+  if (legacy_version) {
+    struct version_min_command version = {0};
+    version.cmd = LC_VERSION_MIN_MACOSX;
+    version.cmdsize = sizeof(version);
+    version.version = (10 << 16) | (12 << 8);
+    memcpy(cursor, &version, sizeof(version));
+    cursor += sizeof(version);
+  } else {
+    struct build_version_command version = {0};
+    version.cmd = LC_BUILD_VERSION;
+    version.cmdsize = sizeof(version);
+    version.platform = PLATFORM_MACOS;
+    version.minos = (13 << 16) | (2 << 8) | 1;
+    memcpy(cursor, &version, sizeof(version));
+    cursor += sizeof(version);
+  }
+  return cursor - buffer;
+}
+
+// Put an unknown command before UUID/segment/version prefixes. The UUID starts
+// eight bytes before a 16 KB boundary, and its payload crosses that boundary.
+static uint8_t *make_crossing_image(uint32_t unknown_size, int legacy) {
+  uint8_t *buffer =
+      calloc(1, sizeof(struct mach_header_64) + unknown_size + 256);
+  assert(buffer);
+  uint8_t *commands = buffer + sizeof(struct mach_header_64);
+  struct load_command unknown = {unknown_command, unknown_size};
+  memcpy(commands, &unknown, sizeof(unknown));
+  size_t prefix_size = write_prefixes(commands + unknown_size, legacy);
+  write_header(buffer, 5, unknown_size + prefix_size);
+  return buffer;
+}
+
+static uint8_t *make_unreadable_payload(uint32_t command,
+                                        uint32_t command_size) {
+  size_t page_size = getpagesize();
+  uint8_t *mapping = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
+                          MAP_PRIVATE | MAP_ANON, -1, 0);
+  assert(mapping != MAP_FAILED);
+  uint8_t *buffer = mapping + page_size - sizeof(struct mach_header_64) -
+                    sizeof(struct load_command);
+  write_header(buffer, 1, command_size);
+  struct load_command prefix = {command, command_size};
+  memcpy(buffer + sizeof(struct mach_header_64), &prefix, sizeof(prefix));
+  assert(mprotect(mapping + page_size, page_size, PROT_NONE) == 0);
+  return buffer;
+}
+
+static uint8_t *make_bad_image(uint32_t count, uint32_t size, uint32_t command,
+                               uint32_t command_size) {
+  uint8_t *buffer = calloc(1, sizeof(struct mach_header_64) + 128);
+  assert(buffer);
+  write_header(buffer, count, size);
+  struct load_command prefix = {command, command_size};
+  memcpy(buffer + sizeof(struct mach_header_64), &prefix, sizeof(prefix));
+  return buffer;
+}
+
+void prepare_load_command_images(void) {
+  crossing_image = make_crossing_image(16 * 1024 - 8, 0);
+  breakpoint_address = crossing_image + sizeof(struct mach_header_64) +
+                       (16 * 1024 - 8) + sizeof(struct load_command);
+  large_image = make_crossing_image(2 * 1024 * 1024, 1);
+
+  size_t page_size = getpagesize();
+  uint8_t *mapping = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE,
+                          MAP_PRIVATE | MAP_ANON, -1, 0);
+  assert(mapping != MAP_FAILED);
+  unreadable_unknown_image = mapping + page_size -
+                             sizeof(struct mach_header_64) -
+                             sizeof(struct load_command);
+  uint8_t *commands = unreadable_unknown_image + sizeof(struct mach_header_64);
+  uint32_t unknown_size = page_size + sizeof(struct load_command);
+  struct load_command unknown = {unknown_command, unknown_size};
+  memcpy(commands, &unknown, sizeof(unknown));
+  size_t prefix_size = write_prefixes(commands + unknown_size, 0);
+  write_header(unreadable_unknown_image, 5, unknown_size + prefix_size);
+  assert(mprotect(mapping + page_size, page_size, PROT_NONE) == 0);
+
+  unreadable_uuid_image =
+      make_unreadable_payload(LC_UUID, sizeof(struct uuid_command));
+  unreadable_version_image = make_unreadable_payload(
+      LC_BUILD_VERSION, sizeof(struct build_version_command));
+  unreadable_segment_image =
+      make_unreadable_payload(LC_SEGMENT_64, sizeof(struct segment_command_64));
+  invalid_size_image = make_bad_image(1, 8, unknown_command, 0);
+  invalid_count_image = make_bad_image(2, 8, unknown_command, 8);
+  invalid_prefix_image = make_bad_image(1, 8, LC_SEGMENT_64, 8);
+  invalid_overrun_image = make_bad_image(1, 8, unknown_command, 16);
+  // The declared region is intentionally huge and mostly inaccessible. Only
+  // the unknown command's header is required, so do not allocate or read it
+  // all.
+  huge_size_image = make_unreadable_payload(unknown_command, UINT32_MAX - 7);
+}
diff --git a/lldb/test/API/macosx/unregistered-macho/main.c b/lldb/test/API/macosx/unregistered-macho/main.c
index 5de4b5f6424677..865a5f8a75eccb 100644
--- a/lldb/test/API/macosx/unregistered-macho/main.c
+++ b/lldb/test/API/macosx/unregistered-macho/main.c
@@ -4,7 +4,10 @@
 #include <string.h>
 #include <uuid/uuid.h>
 
+void prepare_load_command_images(void);
+
 int main() {
+  prepare_load_command_images();
   int size_of_load_cmds =
       sizeof(struct segment_command_64) + sizeof(struct uuid_command);
   uint8_t *macho_buf =
diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.h b/lldb/tools/debugserver/source/MacOSX/MachProcess.h
index 43852801a81696..5d936bf6db6f2f 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachProcess.h
+++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.h
@@ -258,6 +258,11 @@ class MachProcess {
   DeploymentInfo GetDeploymentInfo(const struct load_command &,
                                    uint64_t load_command_address,
                                    bool is_executable);
+  // command_data contains the validated command prefix, or is null when that
+  // optional prefix could not be read from the inferior.
+  DeploymentInfo GetDeploymentInfo(const struct load_command &,
+                                   const uint8_t *command_data,
+                                   bool is_executable);
   static std::optional<std::string> GetPlatformString(unsigned char platform);
   bool GetMachOInformationFromMemory(uint32_t platform,
                                      nub_addr_t mach_o_header_addr,
diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm
index 6ca1945c7e595d..ff0b5aba080d2c 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm
+++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm
@@ -32,7 +32,9 @@
 #include <uuid/uuid.h>
 
 #include <algorithm>
+#include <array>
 #include <chrono>
+#include <limits>
 #include <map>
 #include <unordered_set>
 
@@ -670,16 +672,42 @@ static bool FBSAddEventDataToOptions(NSMutableDictionary *options,
 MachProcess::GetDeploymentInfo(const struct load_command &lc,
                                uint64_t load_command_address,
                                bool is_executable) {
+  size_t command_size = 0;
+  switch (lc.cmd & ~LC_REQ_DYLD) {
+  case LC_VERSION_MIN_IPHONEOS:
+  case LC_VERSION_MIN_MACOSX:
+  case LC_VERSION_MIN_TVOS:
+  case LC_VERSION_MIN_WATCHOS:
+    command_size = sizeof(struct version_min_command);
+    break;
+#if defined(LC_BUILD_VERSION)
+  case LC_BUILD_VERSION:
+    command_size = sizeof(struct build_version_command);
+    break;
+#endif
+  }
+  std::array<uint8_t, sizeof(struct segment_command_64)> command_data;
+  const uint8_t *data = nullptr;
+  if (command_size && ReadMemory(load_command_address, command_size,
+                                 command_data.data()) == command_size)
+    data = command_data.data();
+  return GetDeploymentInfo(lc, data, is_executable);
+}
+
+MachProcess::DeploymentInfo
+MachProcess::GetDeploymentInfo(const struct load_command &lc,
+                               const uint8_t *command_data,
+                               bool is_executable) {
   DeploymentInfo info;
   uint32_t cmd = lc.cmd & ~LC_REQ_DYLD;
 
   // Handle the older LC_VERSION load commands, which don't
   // distinguish between simulator and real hardware.
   auto handle_version_min = [&](char platform) {
-    struct version_min_command vers_cmd;
-    if (ReadMemory(load_command_address, sizeof(struct version_min_command),
-                   &vers_cmd) != sizeof(struct version_min_command))
+    if (!command_data)
       return;
+    struct version_min_command vers_cmd;
+    memcpy(&vers_cmd, command_data, sizeof(vers_cmd));
     info.platform = platform;
     info.major_version = vers_cmd.version >> 16;
     info.minor_version = (vers_cmd.version >> 8) & 0xffu;
@@ -723,10 +751,10 @@ static bool FBSAddEventDataToOptions(NSMutableDictionary *options,
     break;
 #if defined(LC_BUILD_VERSION)
   case LC_BUILD_VERSION: {
-    struct build_version_command build_vers;
-    if (ReadMemory(load_command_address, sizeof(struct build_version_command),
-                   &build_vers) != sizeof(struct build_version_command))
+    if (!command_data)
       break;
+    struct build_version_command build_vers;
+    memcpy(&build_vers, command_data, sizeof(build_vers));
     info.platform = build_vers.platform;
     info.major_version = build_vers.minos >> 16;
     info.minor_version = (build_vers.minos >> 8) & 0xffu;
@@ -814,6 +842,10 @@ static bool mach_header_validity_test(uint32_t magic, uint32_t cputype) {
     uint32_t dyld_platform, nub_addr_t mach_o_header_addr, int wordsize,
     struct mach_o_information &inf) {
   uint64_t load_cmds_p;
+  const size_t header_size = wordsize == 4 ? sizeof(struct mach_header)
+                                           : sizeof(struct mach_header_64);
+  if (mach_o_header_addr > std::numeric_limits<nub_addr_t>::max() - header_size)
+    return false;
 
   if (wordsize == 4) {
     struct mach_header header;
@@ -853,18 +885,75 @@ static bool mach_header_validity_test(uint32_t magic, uint32_t cputype) {
     inf.mach_header.sizeofcmds = header.sizeofcmds;
     inf.mach_header.flags = header.flags;
   }
+  const uint64_t load_cmds_size = inf.mach_header.sizeofcmds;
+  if (inf.mach_header.ncmds > load_cmds_size / sizeof(struct load_command) ||
+      load_cmds_p > std::numeric_limits<nub_addr_t>::max() - load_cmds_size)
+    return false;
+  const nub_addr_t load_cmds_end = load_cmds_p + load_cmds_size;
+
+  // Cache only a bounded part of this image's load commands. A large valid
+  // region needs more refills, rather than an allocation based on sizeofcmds.
+  std::array<uint8_t, 16 * 1024> cache;
+  nub_addr_t cache_address = load_cmds_p;
+  size_t cache_size = 0;
+  auto read_command = [&](nub_addr_t address, size_t size, void *destination) {
+    if (address > load_cmds_end || size > load_cmds_end - address)
+      return false;
+    if (address < cache_address || address - cache_address > cache_size ||
+        size > cache_size - (address - cache_address)) {
+      cache_address = address;
+      const size_t requested =
+          std::min<uint64_t>(cache.size(), load_cmds_end - address);
+      cache_size = ReadMemory(address, requested, cache.data());
+      if (cache_size < size) {
+        // An unknown command's payload need not be readable. If a bulk read
+        // crosses unreadable memory, still try the exact required prefix.
+        return ReadMemory(address, size, destination) == size;
+      }
+    }
+    memcpy(destination, cache.data() + (address - cache_address), size);
+    return true;
+  };
+
   for (uint32_t j = 0; j < inf.mach_header.ncmds; j++) {
     struct load_command lc;
-    if (ReadMemory(load_cmds_p, sizeof(struct load_command), &lc) !=
-        sizeof(struct load_command)) {
+    if (!read_command(load_cmds_p, sizeof(lc), &lc))
+      return false;
+    if (lc.cmdsize < sizeof(lc) || lc.cmdsize > load_cmds_end - load_cmds_p)
       return false;
+
+    size_t command_size = sizeof(lc);
+    if (lc.cmd == LC_SEGMENT)
+      command_size = sizeof(struct segment_command);
+    else if (lc.cmd == LC_SEGMENT_64)
+      command_size = sizeof(struct segment_command_64);
+    else if (lc.cmd == LC_UUID)
+      command_size = sizeof(struct uuid_command);
+    else {
+      switch (lc.cmd & ~LC_REQ_DYLD) {
+      case LC_VERSION_MIN_IPHONEOS:
+      case LC_VERSION_MIN_MACOSX:
+      case LC_VERSION_MIN_TVOS:
+      case LC_VERSION_MIN_WATCHOS:
+        command_size = sizeof(struct version_min_command);
+        break;
+#if defined(LC_BUILD_VERSION)
+      case LC_BUILD_VERSION:
+        command_size = sizeof(struct build_version_command);
+        break;
+#endif
+      }
     }
+    if (command_size > lc.cmdsize)
+      return false;
+    std::array<uint8_t, sizeof(struct segment_command_64)> command_data;
+    const bool have_command_data =
+        read_command(load_cmds_p, command_size, command_data.data());
     if (lc.cmd == LC_SEGMENT) {
-      struct segment_command seg;
-      if (ReadMemory(load_cmds_p, sizeof(struct segment_command), &seg) !=
-          sizeof(struct segment_command)) {
+      if (!have_command_data)
         return false;
-      }
+      struct segment_command seg;
+      memcpy(&seg, command_data.data(), sizeof(seg));
       struct mach_o_segment this_seg;
       char name[17];
       ::memset(name, 0, sizeof(name));
@@ -883,11 +972,10 @@ static bool mach_header_validity_test(uint32_t magic, uint32_t cputype) {
         m_task.TaskWillExecProcessesSuspended();
     }
     if (lc.cmd == LC_SEGMENT_64) {
-      struct segment_command_64 seg;
-      if (ReadMemory(load_cmds_p, sizeof(struct segment_command_64), &seg) !=
-          sizeof(struct segment_command_64)) {
+      if (!have_command_data)
         return false;
-      }
+      struct segment_command_64 seg;
+      memcpy(&seg, command_data.data(), sizeof(seg));
       struct mach_o_segment this_seg;
       char name[17];
       ::memset(name, 0, sizeof(name));
@@ -905,14 +993,14 @@ static bool mach_header_validity_test(uint32_t magic, uint32_t cputype) {
       if (this_seg.name == "ExecExtraSuspend")
         m_task.TaskWillExecProcessesSuspended();
     }
-    if (lc.cmd == LC_UUID) {
+    if (lc.cmd == LC_UUID && have_command_data) {
       struct uuid_command uuidcmd;
-      if (ReadMemory(load_cmds_p, sizeof(struct uuid_command), &uuidcmd) ==
-          sizeof(struct uuid_command))
-        uuid_copy(inf.uuid, uuidcmd.uuid);
+      memcpy(&uuidcmd, command_data.data(), sizeof(uuidcmd));
+      uuid_copy(inf.uuid, uuidcmd.uuid);
     }
     if (DeploymentInfo deployment_info = GetDeploymentInfo(
-            lc, load_cmds_p, inf.mach_header.filetype == MH_EXECUTE)) {
+            lc, have_command_data ? command_data.data() : nullptr,
+            inf.mach_header.filetype == MH_EXECUTE)) {
       std::optional<std::string> lc_platform =
           GetPlatformString(deployment_info.platform);
       if (dyld_platform != PLATFORM_MACCATALYST &&

>From 26607d420240826749fbac86ee10bf1bb76ae0e6 Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at reddit.com>
Date: Fri, 16 Oct 2026 13:17:51 +0200
Subject: [PATCH 2/2] [lldb] Format the Mach-O image-info regression tests

Apply the repository-pinned darker formatter; the Python AST is unchanged.

Assisted-by: OpenAI Codex
---
 .../TestUnregisteredMacho.py                  | 59 +++++++++++++------
 1 file changed, 40 insertions(+), 19 deletions(-)

diff --git a/lldb/test/API/macosx/unregistered-macho/TestUnregisteredMacho.py b/lldb/test/API/macosx/unregistered-macho/TestUnregisteredMacho.py
index 664622b85b3c0e..53804edc845b9b 100644
--- a/lldb/test/API/macosx/unregistered-macho/TestUnregisteredMacho.py
+++ b/lldb/test/API/macosx/unregistered-macho/TestUnregisteredMacho.py
@@ -87,25 +87,37 @@ def check_prefixes(self, image, version):
         self.assertEqual(image["mach_header"]["cpusubtype"], 5)
         self.assertEqual(image["min_version_os_name"], "macosx")
         self.assertEqual(image["min_version_os_sdk"], version)
-        self.assertEqual(image["segments"], [
-            {
-                "name": "1234567890abcdef", "vmaddr": 0x123456789ABCDEF0,
-                "vmsize": 0x10000, "fileoff": 0x1000, "filesize": 0x2000,
-                "maxprot": 7,
-            },
-            {
-                "name": "__TEXT32", "vmaddr": 0x5000, "vmsize": 0x1000,
-                "fileoff": 0x20, "filesize": 0x200, "maxprot": 5,
-            },
-        ])
+        self.assertEqual(
+            image["segments"],
+            [
+                {
+                    "name": "1234567890abcdef",
+                    "vmaddr": 0x123456789ABCDEF0,
+                    "vmsize": 0x10000,
+                    "fileoff": 0x1000,
+                    "filesize": 0x2000,
+                    "maxprot": 7,
+                },
+                {
+                    "name": "__TEXT32",
+                    "vmaddr": 0x5000,
+                    "vmsize": 0x1000,
+                    "fileoff": 0x20,
+                    "filesize": 0x200,
+                    "maxprot": 5,
+                },
+            ],
+        )
 
     @no_debug_info_test
     @skipUnlessDarwin
     def test_load_command_prefixes_across_cache_boundaries(self):
         target, process = self.start_load_command_fixture()
-        for name, version in [("crossing_image", "13.2.1"),
-                              ("large_image", "10.12"),
-                              ("unreadable_unknown_image", "13.2.1")]:
+        for name, version in [
+            ("crossing_image", "13.2.1"),
+            ("large_image", "10.12"),
+            ("unreadable_unknown_image", "13.2.1"),
+        ]:
             address = self.image_address(target, name)
             images = self.image_info(address)
             self.assertEqual(len(images), 1, name)
@@ -131,8 +143,11 @@ def test_load_command_prefixes_across_cache_boundaries(self):
     @skipUnlessDarwin
     def test_unreadable_optional_load_command_payloads(self):
         target, process = self.start_load_command_fixture()
-        for name in ["unreadable_uuid_image", "unreadable_version_image",
-                     "huge_size_image"]:
+        for name in [
+            "unreadable_uuid_image",
+            "unreadable_version_image",
+            "huge_size_image",
+        ]:
             images = self.image_info(self.image_address(target, name))
             self.assertEqual(len(images), 1, name)
             self.assertEqual(images[0]["segments"], [])
@@ -144,7 +159,13 @@ def test_unreadable_optional_load_command_payloads(self):
     @skipUnlessDarwin
     def test_malformed_load_command_prefixes(self):
         target, process = self.start_load_command_fixture()
-        for name in ["invalid_size_image", "invalid_count_image",
-                     "invalid_prefix_image", "invalid_overrun_image"]:
-            self.assertEqual(self.image_info(self.image_address(target, name)), [], name)
+        for name in [
+            "invalid_size_image",
+            "invalid_count_image",
+            "invalid_prefix_image",
+            "invalid_overrun_image",
+        ]:
+            self.assertEqual(
+                self.image_info(self.image_address(target, name)), [], name
+            )
         self.assertEqual(self.image_info((1 << 64) - 8), [])



More information about the lldb-commits mailing list