[lldb/Plugins] Add memory writing capabilities to Scripted Process
authorMed Ismail Bennani <medismail.bennani@gmail.com>
Sat, 4 Mar 2023 03:30:56 +0000 (19:30 -0800)
committerMed Ismail Bennani <medismail.bennani@gmail.com>
Sat, 4 Mar 2023 03:33:02 +0000 (19:33 -0800)
This patch adds memory writing capabilities to the Scripted Process plugin.

This allows to user to get a target address and a memory buffer on the
python scripted process implementation that the user can make processing
on before performing the actual write.

This will also be used to write trap instruction to a real process
memory to set a breakpoint.

Signed-off-by: Med Ismail Bennani <medismail.bennani@gmail.com>
13 files changed:
lldb/bindings/python/python-swigsafecast.swig
lldb/examples/python/scripted_process/scripted_process.py
lldb/include/lldb/Interpreter/ScriptedProcessInterface.h
lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
lldb/source/Plugins/Process/scripted/ScriptedProcess.h
lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp
lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h
lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h
lldb/source/Target/Memory.cpp
lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py
lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py
lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp

index ae562c2..f7e78bb 100644 (file)
@@ -103,6 +103,11 @@ PythonObject ToSWIGWrapper(lldb::ProcessLaunchInfoSP launch_info_sp) {
                        SWIGTYPE_p_lldb__SBAttachInfo);
  }
 
+PythonObject ToSWIGWrapper(lldb::DataExtractorSP data_sp) {
+  return ToSWIGHelper(new lldb::DataExtractorSP(std::move(data_sp)),
+                      SWIGTYPE_p_lldb__SBData);
+}
+
 ScopedPythonObject<lldb::SBCommandReturnObject>
 ToSWIGWrapper(CommandReturnObject &cmd_retobj) {
   return ScopedPythonObject<lldb::SBCommandReturnObject>(
index 60b65fc..044aee1 100644 (file)
@@ -98,6 +98,21 @@ class ScriptedProcess(metaclass=ABCMeta):
         """
         pass
 
+    def write_memory_at_address(self, addr, data, error):
+        """ Write a buffer to the scripted process memory.
+
+        Args:
+            addr (int): Address from which we should start reading.
+            data (lldb.SBData): An `lldb.SBData` buffer to write to the
+                process memory.
+            error (lldb.SBError): Error object.
+
+        Returns:
+            size (int): Size of the memory to read.
+        """
+        error.SetErrorString("%s doesn't support memory writes." % self.__class__.__name__)
+        return 0
+
     def get_loaded_images(self):
         """ Get the list of loaded images for the scripted process.
 
index ec0d7de..ba47430 100644 (file)
@@ -55,6 +55,12 @@ public:
     return {};
   }
 
+  virtual size_t WriteMemoryAtAddress(lldb::addr_t addr,
+                                      lldb::DataExtractorSP data_sp,
+                                      Status &error) {
+    return LLDB_INVALID_OFFSET;
+  };
+
   virtual StructuredData::ArraySP GetLoadedImages() { return {}; }
 
   virtual lldb::pid_t GetProcessID() { return LLDB_INVALID_PROCESS_ID; }
index 84e9dea..948ee69 100644 (file)
@@ -255,7 +255,31 @@ size_t ScriptedProcess::DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
     return ScriptedInterface::ErrorWithMessage<size_t>(
         LLVM_PRETTY_FUNCTION, "Failed to copy read memory to buffer.", error);
 
-  return size;
+  // FIXME: We should use the diagnostic system to report a warning if the
+  // `bytes_copied` is different from `size`.
+
+  return bytes_copied;
+}
+
+size_t ScriptedProcess::DoWriteMemory(lldb::addr_t vm_addr, const void *buf,
+                                      size_t size, Status &error) {
+  lldb::DataExtractorSP data_extractor_sp = std::make_shared<DataExtractor>(
+      buf, size, GetByteOrder(), GetAddressByteSize());
+
+  if (!data_extractor_sp || !data_extractor_sp->GetByteSize())
+    return 0;
+
+  size_t bytes_written =
+      GetInterface().WriteMemoryAtAddress(vm_addr, data_extractor_sp, error);
+
+  if (!bytes_written || bytes_written == LLDB_INVALID_OFFSET)
+    return ScriptedInterface::ErrorWithMessage<size_t>(
+        LLVM_PRETTY_FUNCTION, "Failed to copy write buffer to memory.", error);
+
+  // FIXME: We should use the diagnostic system to report a warning if the
+  // `bytes_written` is different from `size`.
+
+  return bytes_written;
 }
 
 ArchSpec ScriptedProcess::GetArchitecture() {
index 004ee7c..3601173 100644 (file)
@@ -69,6 +69,9 @@ public:
   size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
                       Status &error) override;
 
+  size_t DoWriteMemory(lldb::addr_t vm_addr, const void *buf, size_t size,
+                       Status &error) override;
+
   ArchSpec GetArchitecture();
 
   Status
index 7b7ceff..2812cc7 100644 (file)
@@ -79,6 +79,7 @@ PythonObject ToSWIGWrapper(const SymbolContext &sym_ctx);
 
 PythonObject ToSWIGWrapper(lldb::ProcessAttachInfoSP attach_info_sp);
 PythonObject ToSWIGWrapper(lldb::ProcessLaunchInfoSP launch_info_sp);
+PythonObject ToSWIGWrapper(lldb::DataExtractorSP data_extractor_sp);
 
 PythonObject ToSWIGWrapper(std::unique_ptr<lldb::SBValue> value_sb);
 PythonObject ToSWIGWrapper(std::unique_ptr<lldb::SBStream> stream_sb);
index 3ca4664..cffa3bd 100644 (file)
@@ -135,6 +135,22 @@ lldb::DataExtractorSP ScriptedProcessPythonInterface::ReadMemoryAtAddress(
   return data_sp;
 }
 
+size_t ScriptedProcessPythonInterface::WriteMemoryAtAddress(
+    lldb::addr_t addr, lldb::DataExtractorSP data_sp, Status &error) {
+  Status py_error;
+  StructuredData::ObjectSP obj =
+      Dispatch("write_memory_at_address", py_error, addr, data_sp, error);
+
+  if (!CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj, error))
+    return LLDB_INVALID_OFFSET;
+
+  // If there was an error on the python call, surface it to the user.
+  if (py_error.Fail())
+    error = py_error;
+
+  return obj->GetIntegerValue(LLDB_INVALID_OFFSET);
+}
+
 StructuredData::ArraySP ScriptedProcessPythonInterface::GetLoadedImages() {
   Status error;
   StructuredData::ArraySP array =
index 44273f1..b7b12b9 100644 (file)
@@ -49,7 +49,9 @@ public:
 
   lldb::DataExtractorSP ReadMemoryAtAddress(lldb::addr_t address, size_t size,
                                             Status &error) override;
-                                         
+
+  size_t WriteMemoryAtAddress(lldb::addr_t addr, lldb::DataExtractorSP data_sp,
+                              Status &error) override;
 
   StructuredData::ArraySP GetLoadedImages() override;
 
index aa09be7..a015bd1 100644 (file)
@@ -125,6 +125,10 @@ protected:
     return python::ToSWIGWrapper(arg);
   }
 
+  python::PythonObject Transform(lldb::DataExtractorSP arg) {
+    return python::ToSWIGWrapper(arg);
+  }
+
   template <typename T, typename U>
   void ReverseTransform(T &original_arg, U transformed_arg, Status &error) {
     // If U is not a PythonObject, don't touch it!
index d4dedee..fdc0138 100644 (file)
@@ -234,11 +234,11 @@ size_t MemoryCache::Read(addr_t addr, void *dst, size_t dst_len,
           return dst_len - bytes_left;
 
         if (process_bytes_read != cache_line_byte_size) {
+          data_buffer_heap_up->SetByteSize(process_bytes_read);
           if (process_bytes_read < data_buffer_heap_up->GetByteSize()) {
             dst_len -= data_buffer_heap_up->GetByteSize() - process_bytes_read;
             bytes_left = process_bytes_read;
           }
-          data_buffer_heap_up->SetByteSize(process_bytes_read);
         }
         m_L2_cache[curr_addr] = DataBufferSP(data_buffer_heap_up.release());
         // We have read data and put it into the cache, continue through the
index 053654c..5a198cc 100644 (file)
@@ -150,7 +150,6 @@ class ScriptedProcesTestCase(TestBase):
         self.assertEqual(process_1.GetNumThreads(), 1)
 
         # ... then try reading from target #1 process ...
-        addr = 0x500000000
         message = "Hello, target 1"
         buff = process_1.ReadCStringFromMemory(addr, len(message) + 1, error)
         self.assertSuccess(error)
@@ -158,12 +157,22 @@ class ScriptedProcesTestCase(TestBase):
 
         # ... now, reading again from target #0 process to make sure the call
         # gets dispatched to the right target.
-        addr = 0x500000000
         message = "Hello, target 0"
         buff = process_0.ReadCStringFromMemory(addr, len(message) + 1, error)
         self.assertSuccess(error)
         self.assertEqual(buff, message)
 
+        # Let's write some memory.
+        message = "Hello, world!"
+        bytes_written = process_0.WriteMemoryAsCString(addr, message, error)
+        self.assertSuccess(error)
+        self.assertEqual(bytes_written, len(message) + 1)
+
+        # ... and check if that memory was saved properly.
+        buff = process_0.ReadCStringFromMemory(addr, len(message) + 1, error)
+        self.assertSuccess(error)
+        self.assertEqual(buff, message)
+
         thread = process_0.GetSelectedThread()
         self.assertTrue(thread, "Invalid thread.")
         self.assertEqual(thread.GetThreadID(), 0x19)
index 4703814..3f6ce6c 100644 (file)
@@ -7,20 +7,29 @@ from lldb.plugins.scripted_process import ScriptedProcess
 from lldb.plugins.scripted_process import ScriptedThread
 
 class DummyScriptedProcess(ScriptedProcess):
+    memory = None
+
     def __init__(self, exe_ctx: lldb.SBExecutionContext, args : lldb.SBStructuredData):
         super().__init__(exe_ctx, args)
         self.threads[0] = DummyScriptedThread(self, None)
-
-    def read_memory_at_address(self, addr: int, size: int, error: lldb.SBError) -> lldb.SBData:
+        self.memory = {}
+        addr = 0x500000000
         debugger = self.target.GetDebugger()
         index = debugger.GetIndexOfTarget(self.target)
+        self.memory[addr] = "Hello, target " + str(index)
+
+    def read_memory_at_address(self, addr: int, size: int, error: lldb.SBError) -> lldb.SBData:
         data = lldb.SBData().CreateDataFromCString(
                                     self.target.GetByteOrder(),
                                     self.target.GetCodeByteSize(),
-                                    "Hello, target " + str(index))
+                                    self.memory[addr])
 
         return data
 
+    def write_memory_at_address(self, addr, data, error):
+        self.memory[addr] = data.GetString(error, 0)
+        return len(self.memory[addr]) + 1
+
     def get_loaded_images(self):
         return self.loaded_images
 
index 1a8ad86..59ba0b7 100644 (file)
@@ -286,3 +286,8 @@ python::PythonObject
 lldb_private::python::ToSWIGWrapper(lldb::ProcessLaunchInfoSP) {
   return python::PythonObject();
 }
+
+python::PythonObject
+lldb_private::python::ToSWIGWrapper(lldb::DataExtractorSP) {
+  return python::PythonObject();
+}