summaryrefslogtreecommitdiff
path: root/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
diff options
context:
space:
mode:
authorPavel Labath <pavel@labath.sk>2020-10-09 13:59:50 +0200
committerPavel Labath <pavel@labath.sk>2020-10-14 15:02:09 +0200
commit2c4226f8ac2c925d7e1d59d1de1660cd1dd63f31 (patch)
tree2ce3704bf4f6c2a457660d04d10503eb50e0a8fa /lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
parent6bb123b819c61c61197ec2ba54ceb6d16e9121cf (diff)
[lldb-server][linux] Add ability to allocate memory
This patch adds support for the _M and _m gdb-remote packets, which (de)allocate memory in the inferior. This works by "injecting" a m(un)map syscall into the inferior. This consists of: - finding an executable page of memory - writing the syscall opcode to it - setting up registers according to the os syscall convention - single stepping over the syscall The advantage of this approach over calling the mmap function is that this works even in case the mmap function is buggy or unavailable. The disadvantage is it is more platform-dependent, which is why this patch only works on X86 (_32 and _64) right now. Adding support for other linux architectures should be easy and consist of defining the appropriate syscall constants. Adding support for other OSes depends on the its ability to do a similar trick. Differential Revision: https://reviews.llvm.org/D89124
Diffstat (limited to 'lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp')
-rw-r--r--lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp174
1 files changed, 135 insertions, 39 deletions
diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
index 79d803de158e..5d287370fb36 100644
--- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
@@ -19,6 +19,10 @@
#include <string>
#include <unordered_map>
+#include "NativeThreadLinux.h"
+#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
+#include "Plugins/Process/Utility/LinuxProcMaps.h"
+#include "Procfs.h"
#include "lldb/Core/EmulateInstruction.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Host/Host.h"
@@ -38,15 +42,11 @@
#include "lldb/Utility/State.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StringExtractor.h"
+#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/Errno.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Threading.h"
-#include "NativeThreadLinux.h"
-#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
-#include "Plugins/Process/Utility/LinuxProcMaps.h"
-#include "Procfs.h"
-
#include <linux/unistd.h>
#include <sys/socket.h>
#include <sys/syscall.h>
@@ -1347,43 +1347,134 @@ void NativeProcessLinux::DoStopIDBumped(uint32_t newBumpId) {
m_mem_region_cache.clear();
}
-Status NativeProcessLinux::AllocateMemory(size_t size, uint32_t permissions,
- lldb::addr_t &addr) {
-// FIXME implementing this requires the equivalent of
-// InferiorCallPOSIX::InferiorCallMmap, which depends on functional ThreadPlans
-// working with Native*Protocol.
-#if 1
- return Status("not implemented yet");
-#else
- addr = LLDB_INVALID_ADDRESS;
-
- unsigned prot = 0;
- if (permissions & lldb::ePermissionsReadable)
- prot |= eMmapProtRead;
- if (permissions & lldb::ePermissionsWritable)
- prot |= eMmapProtWrite;
- if (permissions & lldb::ePermissionsExecutable)
- prot |= eMmapProtExec;
-
- // TODO implement this directly in NativeProcessLinux
- // (and lift to NativeProcessPOSIX if/when that class is refactored out).
- if (InferiorCallMmap(this, addr, 0, size, prot,
- eMmapFlagsAnon | eMmapFlagsPrivate, -1, 0)) {
- m_addr_to_mmap_size[addr] = size;
- return Status();
- } else {
- addr = LLDB_INVALID_ADDRESS;
- return Status("unable to allocate %" PRIu64
- " bytes of memory with permissions %s",
- size, GetPermissionsAsCString(permissions));
+llvm::Expected<uint64_t>
+NativeProcessLinux::Syscall(llvm::ArrayRef<uint64_t> args) {
+ PopulateMemoryRegionCache();
+ auto region_it = llvm::find_if(m_mem_region_cache, [](const auto &pair) {
+ return pair.first.GetExecutable() == MemoryRegionInfo::eYes;
+ });
+ if (region_it == m_mem_region_cache.end())
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "No executable memory region found!");
+
+ addr_t exe_addr = region_it->first.GetRange().GetRangeBase();
+
+ NativeThreadLinux &thread = *GetThreadByID(GetID());
+ assert(thread.GetState() == eStateStopped);
+ NativeRegisterContextLinux &reg_ctx = thread.GetRegisterContext();
+
+ NativeRegisterContextLinux::SyscallData syscall_data =
+ *reg_ctx.GetSyscallData();
+
+ DataBufferSP registers_sp;
+ if (llvm::Error Err = reg_ctx.ReadAllRegisterValues(registers_sp).ToError())
+ return std::move(Err);
+ auto restore_regs = llvm::make_scope_exit(
+ [&] { reg_ctx.WriteAllRegisterValues(registers_sp); });
+
+ llvm::SmallVector<uint8_t, 8> memory(syscall_data.Insn.size());
+ size_t bytes_read;
+ if (llvm::Error Err =
+ ReadMemory(exe_addr, memory.data(), memory.size(), bytes_read)
+ .ToError()) {
+ return std::move(Err);
}
-#endif
+
+ auto restore_mem = llvm::make_scope_exit(
+ [&] { WriteMemory(exe_addr, memory.data(), memory.size(), bytes_read); });
+
+ if (llvm::Error Err = reg_ctx.SetPC(exe_addr).ToError())
+ return std::move(Err);
+
+ for (const auto &zip : llvm::zip_first(args, syscall_data.Args)) {
+ if (llvm::Error Err =
+ reg_ctx
+ .WriteRegisterFromUnsigned(std::get<1>(zip), std::get<0>(zip))
+ .ToError()) {
+ return std::move(Err);
+ }
+ }
+ if (llvm::Error Err = WriteMemory(exe_addr, syscall_data.Insn.data(),
+ syscall_data.Insn.size(), bytes_read)
+ .ToError())
+ return std::move(Err);
+
+ m_mem_region_cache.clear();
+
+ // With software single stepping the syscall insn buffer must also include a
+ // trap instruction to stop the process.
+ int req = SupportHardwareSingleStepping() ? PTRACE_SINGLESTEP : PTRACE_CONT;
+ if (llvm::Error Err =
+ PtraceWrapper(req, thread.GetID(), nullptr, nullptr).ToError())
+ return std::move(Err);
+
+ int status;
+ ::pid_t wait_pid = llvm::sys::RetryAfterSignal(-1, ::waitpid, thread.GetID(),
+ &status, __WALL);
+ if (wait_pid == -1) {
+ return llvm::errorCodeToError(
+ std::error_code(errno, std::generic_category()));
+ }
+ assert((unsigned)wait_pid == thread.GetID());
+
+ uint64_t result = reg_ctx.ReadRegisterAsUnsigned(syscall_data.Result, -ESRCH);
+
+ // Values larger than this are actually negative errno numbers.
+ uint64_t errno_threshold =
+ (uint64_t(-1) >> (64 - 8 * m_arch.GetAddressByteSize())) - 0x1000;
+ if (result > errno_threshold) {
+ return llvm::errorCodeToError(
+ std::error_code(-result & 0xfff, std::generic_category()));
+ }
+
+ return result;
}
-Status NativeProcessLinux::DeallocateMemory(lldb::addr_t addr) {
- // FIXME see comments in AllocateMemory - required lower-level
- // bits not in place yet (ThreadPlans)
- return Status("not implemented");
+llvm::Expected<addr_t>
+NativeProcessLinux::AllocateMemory(size_t size, uint32_t permissions) {
+
+ llvm::Optional<NativeRegisterContextLinux::MmapData> mmap_data =
+ GetCurrentThread()->GetRegisterContext().GetMmapData();
+ if (!mmap_data)
+ return llvm::make_error<UnimplementedError>();
+
+ unsigned prot = PROT_NONE;
+ assert((permissions & (ePermissionsReadable | ePermissionsWritable |
+ ePermissionsExecutable)) == permissions &&
+ "Unknown permission!");
+ if (permissions & ePermissionsReadable)
+ prot |= PROT_READ;
+ if (permissions & ePermissionsWritable)
+ prot |= PROT_WRITE;
+ if (permissions & ePermissionsExecutable)
+ prot |= PROT_EXEC;
+
+ llvm::Expected<uint64_t> Result =
+ Syscall({mmap_data->SysMmap, 0, size, prot, MAP_ANONYMOUS | MAP_PRIVATE,
+ uint64_t(-1), 0});
+ if (Result)
+ m_allocated_memory.try_emplace(*Result, size);
+ return Result;
+}
+
+llvm::Error NativeProcessLinux::DeallocateMemory(lldb::addr_t addr) {
+ llvm::Optional<NativeRegisterContextLinux::MmapData> mmap_data =
+ GetCurrentThread()->GetRegisterContext().GetMmapData();
+ if (!mmap_data)
+ return llvm::make_error<UnimplementedError>();
+
+ auto it = m_allocated_memory.find(addr);
+ if (it == m_allocated_memory.end())
+ return llvm::createStringError(llvm::errc::invalid_argument,
+ "Memory not allocated by the debugger.");
+
+ llvm::Expected<uint64_t> Result =
+ Syscall({mmap_data->SysMunmap, addr, it->second});
+ if (!Result)
+ return Result.takeError();
+
+ m_allocated_memory.erase(it);
+ return llvm::Error::success();
}
size_t NativeProcessLinux::UpdateThreads() {
@@ -1652,6 +1743,11 @@ NativeThreadLinux *NativeProcessLinux::GetThreadByID(lldb::tid_t tid) {
NativeProcessProtocol::GetThreadByID(tid));
}
+NativeThreadLinux *NativeProcessLinux::GetCurrentThread() {
+ return static_cast<NativeThreadLinux *>(
+ NativeProcessProtocol::GetCurrentThread());
+}
+
Status NativeProcessLinux::ResumeThread(NativeThreadLinux &thread,
lldb::StateType state, int signo) {
Log *const log = ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_THREAD);