From f190ec6882706d30c606e62986512371925288a9 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani Date: Fri, 3 Mar 2023 19:30:56 -0800 Subject: [PATCH] [lldb/Plugins] Add memory writing capabilities to Scripted Process 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 --- lldb/bindings/python/python-swigsafecast.swig | 5 +++++ .../python/scripted_process/scripted_process.py | 15 +++++++++++++ .../lldb/Interpreter/ScriptedProcessInterface.h | 6 +++++ .../Plugins/Process/scripted/ScriptedProcess.cpp | 26 +++++++++++++++++++++- .../Plugins/Process/scripted/ScriptedProcess.h | 3 +++ .../ScriptInterpreter/Python/SWIGPythonBridge.h | 1 + .../Python/ScriptedProcessPythonInterface.cpp | 16 +++++++++++++ .../Python/ScriptedProcessPythonInterface.h | 4 +++- .../Python/ScriptedPythonInterface.h | 4 ++++ lldb/source/Target/Memory.cpp | 2 +- .../scripted_process/TestScriptedProcess.py | 13 +++++++++-- .../scripted_process/dummy_scripted_process.py | 15 ++++++++++--- .../ScriptInterpreter/Python/PythonTestSuite.cpp | 5 +++++ 13 files changed, 107 insertions(+), 8 deletions(-) diff --git a/lldb/bindings/python/python-swigsafecast.swig b/lldb/bindings/python/python-swigsafecast.swig index ae562c2..f7e78bb 100644 --- a/lldb/bindings/python/python-swigsafecast.swig +++ b/lldb/bindings/python/python-swigsafecast.swig @@ -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 ToSWIGWrapper(CommandReturnObject &cmd_retobj) { return ScopedPythonObject( diff --git a/lldb/examples/python/scripted_process/scripted_process.py b/lldb/examples/python/scripted_process/scripted_process.py index 60b65fc..044aee1 100644 --- a/lldb/examples/python/scripted_process/scripted_process.py +++ b/lldb/examples/python/scripted_process/scripted_process.py @@ -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. diff --git a/lldb/include/lldb/Interpreter/ScriptedProcessInterface.h b/lldb/include/lldb/Interpreter/ScriptedProcessInterface.h index ec0d7de..ba47430 100644 --- a/lldb/include/lldb/Interpreter/ScriptedProcessInterface.h +++ b/lldb/include/lldb/Interpreter/ScriptedProcessInterface.h @@ -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; } diff --git a/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp b/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp index 84e9dea..948ee69 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp +++ b/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp @@ -255,7 +255,31 @@ size_t ScriptedProcess::DoReadMemory(lldb::addr_t addr, void *buf, size_t size, return ScriptedInterface::ErrorWithMessage( 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( + 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( + 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() { diff --git a/lldb/source/Plugins/Process/scripted/ScriptedProcess.h b/lldb/source/Plugins/Process/scripted/ScriptedProcess.h index 004ee7c..3601173 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedProcess.h +++ b/lldb/source/Plugins/Process/scripted/ScriptedProcess.h @@ -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 diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h index 7b7ceff..2812cc7 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -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 value_sb); PythonObject ToSWIGWrapper(std::unique_ptr stream_sb); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp index 3ca4664..cffa3bd 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp @@ -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 = diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h index 44273f1..b7b12b9 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h @@ -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; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h index aa09be7..a015bd1 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h @@ -125,6 +125,10 @@ protected: return python::ToSWIGWrapper(arg); } + python::PythonObject Transform(lldb::DataExtractorSP arg) { + return python::ToSWIGWrapper(arg); + } + template void ReverseTransform(T &original_arg, U transformed_arg, Status &error) { // If U is not a PythonObject, don't touch it! diff --git a/lldb/source/Target/Memory.cpp b/lldb/source/Target/Memory.cpp index d4dedee..fdc0138 100644 --- a/lldb/source/Target/Memory.cpp +++ b/lldb/source/Target/Memory.cpp @@ -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 diff --git a/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py b/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py index 053654c..5a198cc 100644 --- a/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py +++ b/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py @@ -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) diff --git a/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py b/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py index 4703814..3f6ce6c 100644 --- a/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py +++ b/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py @@ -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 diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp index 1a8ad86..59ba0b7 100644 --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -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(); +} -- 2.7.4