[Lldb-commits] [lldb] [lldb] Add ReadCStrings API to Process (PR #172026)

Felipe de Azevedo Piovezan via lldb-commits lldb-commits at lists.llvm.org
Fri Dec 12 07:47:24 PST 2025


https://github.com/felipepiovezan created https://github.com/llvm/llvm-project/pull/172026

This commit uses Process::ReadMemoryRanges to create an efficient method for reading multiple strings at once. This method works like the single-string version, reading 256 bytes at a time, but instead doing it for _every_ string requested at the same time.

>From 3ce20b0c116b0a9c59f0d820e9511992d8141961 Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Thu, 11 Dec 2025 14:08:01 +0000
Subject: [PATCH] [lldb] Add ReadCStrings API to Process

This commit uses Process::ReadMemoryRanges to create an efficient method
for reading multiple strings at once. This method works like the
single-string version, reading 256 bytes at a time, but instead doing it
for _every_ string requested at the same time.
---
 lldb/include/lldb/API/SBProcess.h             |  3 +
 lldb/include/lldb/Target/Process.h            |  3 +
 lldb/source/API/SBProcess.cpp                 | 31 ++++++++++
 lldb/source/Target/Process.cpp                | 57 +++++++++++++++++++
 .../process/read_multiple_cstrings/Makefile   |  3 +
 .../TestReadMultipleStrings.py                | 46 +++++++++++++++
 .../process/read_multiple_cstrings/main.c     |  8 +++
 7 files changed, 151 insertions(+)
 create mode 100644 lldb/test/API/python_api/process/read_multiple_cstrings/Makefile
 create mode 100644 lldb/test/API/python_api/process/read_multiple_cstrings/TestReadMultipleStrings.py
 create mode 100644 lldb/test/API/python_api/process/read_multiple_cstrings/main.c

diff --git a/lldb/include/lldb/API/SBProcess.h b/lldb/include/lldb/API/SBProcess.h
index 882b8bd837131..5f04d3330a1d1 100644
--- a/lldb/include/lldb/API/SBProcess.h
+++ b/lldb/include/lldb/API/SBProcess.h
@@ -205,6 +205,9 @@ class LLDB_API SBProcess {
   size_t ReadCStringFromMemory(addr_t addr, void *char_buf, size_t size,
                                lldb::SBError &error);
 
+  SBStringList ReadCStringsFromMemory(SBValueList string_addresses,
+                                      SBError &error);
+
   uint64_t ReadUnsignedFromMemory(addr_t addr, uint32_t byte_size,
                                   lldb::SBError &error);
 
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 8e6c16cbfe0fc..4493e81ce0eae 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -1680,6 +1680,9 @@ class Process : public std::enable_shared_from_this<Process>,
   size_t ReadCStringFromMemory(lldb::addr_t vm_addr, std::string &out_str,
                                Status &error);
 
+  llvm::SmallVector<std::optional<std::string>>
+  ReadCStringsFromMemory(llvm::ArrayRef<lldb::addr_t> addresses);
+
   /// Reads an unsigned integer of the specified byte size from process
   /// memory.
   ///
diff --git a/lldb/source/API/SBProcess.cpp b/lldb/source/API/SBProcess.cpp
index 14aa9432eed83..1a83a3d164e53 100644
--- a/lldb/source/API/SBProcess.cpp
+++ b/lldb/source/API/SBProcess.cpp
@@ -876,6 +876,37 @@ lldb::addr_t SBProcess::FindInMemory(const void *buf, uint64_t size,
                                   range.ref(), alignment, error.ref());
 }
 
+SBStringList SBProcess::ReadCStringsFromMemory(SBValueList sb_string_addresses,
+                                               SBError &error) {
+  std::vector<lldb::addr_t> string_addresses;
+  string_addresses.reserve(sb_string_addresses.GetSize());
+
+  for (size_t idx = 0; idx < sb_string_addresses.GetSize(); idx++) {
+    SBValue sb_address = sb_string_addresses.GetValueAtIndex(idx);
+    string_addresses.push_back(sb_address.GetValueAsAddress());
+  }
+
+  ProcessSP process_sp(GetSP());
+  if (!process_sp) {
+    error = Status::FromErrorString("SBProcess is invalid");
+    return {};
+  }
+  Process::StopLocker stop_locker;
+  if (!stop_locker.TryLock(&process_sp->GetRunLock())) {
+    error = Status::FromErrorString("process is running");
+    return {};
+  }
+
+  SBStringList strings;
+  llvm::SmallVector<std::optional<std::string>> maybe_strings =
+      process_sp->ReadCStringsFromMemory(string_addresses);
+
+  for (std::optional<std::string> maybe_str : maybe_strings)
+    strings.AppendString(maybe_str ? maybe_str->c_str() : "");
+
+  return strings;
+}
+
 size_t SBProcess::ReadMemory(addr_t addr, void *dst, size_t dst_len,
                              SBError &sb_error) {
   LLDB_INSTRUMENT_VA(this, addr, dst, dst_len, sb_error);
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 9c8e8fa7041ee..3890a91dc4608 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -2135,6 +2135,63 @@ lldb::addr_t Process::FindInMemory(const uint8_t *buf, uint64_t size,
   return matches[0].GetBaseAddress().GetLoadAddress(&target);
 }
 
+llvm::SmallVector<std::optional<std::string>>
+Process::ReadCStringsFromMemory(llvm::ArrayRef<lldb::addr_t> addresses) {
+  // Make the same read width choice as ReadCStringFromMemory.
+  constexpr auto read_width = 256;
+
+  llvm::SmallVector<std::optional<std::string>> output_strs(addresses.size(),
+                                                            "");
+  llvm::SmallVector<Range<addr_t, size_t>> ranges{
+      llvm::map_range(addresses, [=](addr_t ptr) {
+        return Range<addr_t, size_t>(ptr, read_width);
+      })};
+
+  std::vector<uint8_t> buffer(read_width * addresses.size(), 0);
+  uint64_t num_completed_strings = 0;
+
+  while (num_completed_strings != addresses.size()) {
+    llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> read_results =
+        ReadMemoryRanges(ranges, buffer);
+
+    // Each iteration of this loop either increments num_completed_strings or
+    // updates the base pointer of some range, guaranteeing forward progress of
+    // the outer loop.
+    for (auto [range, read_result, output_str] :
+         llvm::zip(ranges, read_results, output_strs)) {
+      // A previously completed string.
+      if (range.GetByteSize() == 0)
+        continue;
+
+      // The read failed, set the range to 0 to avoid reading it again.
+      if (read_result.empty()) {
+        output_str = std::nullopt;
+        range.SetByteSize(0);
+        num_completed_strings++;
+        continue;
+      }
+
+      // Convert ArrayRef to StringRef so the pointers work with std::string.
+      auto read_result_str = llvm::toStringRef(read_result);
+
+      const char *null_terminator_pos = llvm::find(read_result_str, '\0');
+      output_str->append(read_result_str.begin(), null_terminator_pos);
+
+      // If the terminator was found, this string is complete.
+      if (null_terminator_pos != read_result_str.end()) {
+        range.SetByteSize(0);
+        num_completed_strings++;
+      }
+      // Otherwise increment the base pointer for the next read.
+      else {
+        range.SetRangeBase(range.GetRangeBase() + read_result.size());
+      }
+    }
+  }
+
+  return output_strs;
+}
+
 size_t Process::ReadCStringFromMemory(addr_t addr, std::string &out_str,
                                       Status &error) {
   char buf[256];
diff --git a/lldb/test/API/python_api/process/read_multiple_cstrings/Makefile b/lldb/test/API/python_api/process/read_multiple_cstrings/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/python_api/process/read_multiple_cstrings/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/python_api/process/read_multiple_cstrings/TestReadMultipleStrings.py b/lldb/test/API/python_api/process/read_multiple_cstrings/TestReadMultipleStrings.py
new file mode 100644
index 0000000000000..75ecfb13d8b77
--- /dev/null
+++ b/lldb/test/API/python_api/process/read_multiple_cstrings/TestReadMultipleStrings.py
@@ -0,0 +1,46 @@
+"""Test reading c-strings from memory via SB API."""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestReadMultipleStrings(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_read_multiple_strings(self):
+        """Test corner case behavior of SBProcess::ReadCStringFromMemory"""
+        self.build()
+
+        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+            self, "breakpoint here", lldb.SBFileSpec("main.c")
+        )
+
+        frame = thread.GetFrameAtIndex(0)
+        err = lldb.SBError()
+
+        empty_str_addr = frame.FindVariable("empty_string")
+        self.assertSuccess(err)
+        str1_addr = frame.FindVariable("str1")
+        self.assertSuccess(err)
+        banana_addr = frame.FindVariable("banana")
+        self.assertSuccess(err)
+        bad_addr = frame.FindVariable("bad_addr")
+        self.assertSuccess(err)
+
+        string_addresses = [empty_str_addr, str1_addr, banana_addr, bad_addr]
+        for addr in string_addresses:
+            self.assertNotEqual(addr.GetValueAsUnsigned(), lldb.LLDB_INVALID_ADDRESS)
+
+        addresses = lldb.SBValueList()
+        for addr in string_addresses:
+            addresses.Append(addr)
+
+        strings = process.ReadCStringsFromMemory(addresses, err)
+        self.assertSuccess(err)
+        self.assertEqual(strings.GetStringAtIndex(0), "")
+        self.assertEqual(strings.GetStringAtIndex(1), "1")
+        self.assertEqual(strings.GetStringAtIndex(2), "banana")
+        # invalid address will also return an empty string.
+        self.assertEqual(strings.GetStringAtIndex(3), "")
diff --git a/lldb/test/API/python_api/process/read_multiple_cstrings/main.c b/lldb/test/API/python_api/process/read_multiple_cstrings/main.c
new file mode 100644
index 0000000000000..d7affad6734da
--- /dev/null
+++ b/lldb/test/API/python_api/process/read_multiple_cstrings/main.c
@@ -0,0 +1,8 @@
+int main(int argc, char **argv) {
+  const char *empty_string = "";
+  const char *str1 = "1";
+  const char *banana = "banana";
+  const char *bad_addr = (char *)0x100;
+
+  return 0; // breakpoint here
+}



More information about the lldb-commits mailing list