Skip to content

Commit 2c4226f

Browse files
committed
[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
1 parent 6bb123b commit 2c4226f

20 files changed

+435
-86
lines changed

lldb/include/lldb/Host/common/NativeProcessProtocol.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "lldb/Utility/ArchSpec.h"
1818
#include "lldb/Utility/Status.h"
1919
#include "lldb/Utility/TraceOptions.h"
20+
#include "lldb/Utility/UnimplementedError.h"
2021
#include "lldb/lldb-private-forward.h"
2122
#include "lldb/lldb-types.h"
2223
#include "llvm/ADT/ArrayRef.h"
@@ -112,10 +113,14 @@ class NativeProcessProtocol {
112113
virtual Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size,
113114
size_t &bytes_written) = 0;
114115

115-
virtual Status AllocateMemory(size_t size, uint32_t permissions,
116-
lldb::addr_t &addr) = 0;
116+
virtual llvm::Expected<lldb::addr_t> AllocateMemory(size_t size,
117+
uint32_t permissions) {
118+
return llvm::make_error<UnimplementedError>();
119+
}
117120

118-
virtual Status DeallocateMemory(lldb::addr_t addr) = 0;
121+
virtual llvm::Error DeallocateMemory(lldb::addr_t addr) {
122+
return llvm::make_error<UnimplementedError>();
123+
}
119124

120125
virtual lldb::addr_t GetSharedLibraryInfoAddress() = 0;
121126

lldb/source/Plugins/Process/FreeBSDRemote/NativeProcessFreeBSD.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -543,15 +543,6 @@ Status NativeProcessFreeBSD::PopulateMemoryRegionCache() {
543543
return Status();
544544
}
545545

546-
Status NativeProcessFreeBSD::AllocateMemory(size_t size, uint32_t permissions,
547-
lldb::addr_t &addr) {
548-
return Status("Unimplemented");
549-
}
550-
551-
Status NativeProcessFreeBSD::DeallocateMemory(lldb::addr_t addr) {
552-
return Status("Unimplemented");
553-
}
554-
555546
lldb::addr_t NativeProcessFreeBSD::GetSharedLibraryInfoAddress() {
556547
// punt on this for now
557548
return LLDB_INVALID_ADDRESS;

lldb/source/Plugins/Process/FreeBSDRemote/NativeProcessFreeBSD.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,6 @@ class NativeProcessFreeBSD : public NativeProcessELF {
6060
Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size,
6161
size_t &bytes_written) override;
6262

63-
Status AllocateMemory(size_t size, uint32_t permissions,
64-
lldb::addr_t &addr) override;
65-
66-
Status DeallocateMemory(lldb::addr_t addr) override;
67-
6863
lldb::addr_t GetSharedLibraryInfoAddress() override;
6964

7065
size_t UpdateThreads() override;

lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp

Lines changed: 135 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
#include <string>
2020
#include <unordered_map>
2121

22+
#include "NativeThreadLinux.h"
23+
#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
24+
#include "Plugins/Process/Utility/LinuxProcMaps.h"
25+
#include "Procfs.h"
2226
#include "lldb/Core/EmulateInstruction.h"
2327
#include "lldb/Core/ModuleSpec.h"
2428
#include "lldb/Host/Host.h"
@@ -38,15 +42,11 @@
3842
#include "lldb/Utility/State.h"
3943
#include "lldb/Utility/Status.h"
4044
#include "lldb/Utility/StringExtractor.h"
45+
#include "llvm/ADT/ScopeExit.h"
4146
#include "llvm/Support/Errno.h"
4247
#include "llvm/Support/FileSystem.h"
4348
#include "llvm/Support/Threading.h"
4449

45-
#include "NativeThreadLinux.h"
46-
#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
47-
#include "Plugins/Process/Utility/LinuxProcMaps.h"
48-
#include "Procfs.h"
49-
5050
#include <linux/unistd.h>
5151
#include <sys/socket.h>
5252
#include <sys/syscall.h>
@@ -1347,43 +1347,134 @@ void NativeProcessLinux::DoStopIDBumped(uint32_t newBumpId) {
13471347
m_mem_region_cache.clear();
13481348
}
13491349

1350-
Status NativeProcessLinux::AllocateMemory(size_t size, uint32_t permissions,
1351-
lldb::addr_t &addr) {
1352-
// FIXME implementing this requires the equivalent of
1353-
// InferiorCallPOSIX::InferiorCallMmap, which depends on functional ThreadPlans
1354-
// working with Native*Protocol.
1355-
#if 1
1356-
return Status("not implemented yet");
1357-
#else
1358-
addr = LLDB_INVALID_ADDRESS;
1359-
1360-
unsigned prot = 0;
1361-
if (permissions & lldb::ePermissionsReadable)
1362-
prot |= eMmapProtRead;
1363-
if (permissions & lldb::ePermissionsWritable)
1364-
prot |= eMmapProtWrite;
1365-
if (permissions & lldb::ePermissionsExecutable)
1366-
prot |= eMmapProtExec;
1367-
1368-
// TODO implement this directly in NativeProcessLinux
1369-
// (and lift to NativeProcessPOSIX if/when that class is refactored out).
1370-
if (InferiorCallMmap(this, addr, 0, size, prot,
1371-
eMmapFlagsAnon | eMmapFlagsPrivate, -1, 0)) {
1372-
m_addr_to_mmap_size[addr] = size;
1373-
return Status();
1374-
} else {
1375-
addr = LLDB_INVALID_ADDRESS;
1376-
return Status("unable to allocate %" PRIu64
1377-
" bytes of memory with permissions %s",
1378-
size, GetPermissionsAsCString(permissions));
1350+
llvm::Expected<uint64_t>
1351+
NativeProcessLinux::Syscall(llvm::ArrayRef<uint64_t> args) {
1352+
PopulateMemoryRegionCache();
1353+
auto region_it = llvm::find_if(m_mem_region_cache, [](const auto &pair) {
1354+
return pair.first.GetExecutable() == MemoryRegionInfo::eYes;
1355+
});
1356+
if (region_it == m_mem_region_cache.end())
1357+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
1358+
"No executable memory region found!");
1359+
1360+
addr_t exe_addr = region_it->first.GetRange().GetRangeBase();
1361+
1362+
NativeThreadLinux &thread = *GetThreadByID(GetID());
1363+
assert(thread.GetState() == eStateStopped);
1364+
NativeRegisterContextLinux &reg_ctx = thread.GetRegisterContext();
1365+
1366+
NativeRegisterContextLinux::SyscallData syscall_data =
1367+
*reg_ctx.GetSyscallData();
1368+
1369+
DataBufferSP registers_sp;
1370+
if (llvm::Error Err = reg_ctx.ReadAllRegisterValues(registers_sp).ToError())
1371+
return std::move(Err);
1372+
auto restore_regs = llvm::make_scope_exit(
1373+
[&] { reg_ctx.WriteAllRegisterValues(registers_sp); });
1374+
1375+
llvm::SmallVector<uint8_t, 8> memory(syscall_data.Insn.size());
1376+
size_t bytes_read;
1377+
if (llvm::Error Err =
1378+
ReadMemory(exe_addr, memory.data(), memory.size(), bytes_read)
1379+
.ToError()) {
1380+
return std::move(Err);
13791381
}
1380-
#endif
1382+
1383+
auto restore_mem = llvm::make_scope_exit(
1384+
[&] { WriteMemory(exe_addr, memory.data(), memory.size(), bytes_read); });
1385+
1386+
if (llvm::Error Err = reg_ctx.SetPC(exe_addr).ToError())
1387+
return std::move(Err);
1388+
1389+
for (const auto &zip : llvm::zip_first(args, syscall_data.Args)) {
1390+
if (llvm::Error Err =
1391+
reg_ctx
1392+
.WriteRegisterFromUnsigned(std::get<1>(zip), std::get<0>(zip))
1393+
.ToError()) {
1394+
return std::move(Err);
1395+
}
1396+
}
1397+
if (llvm::Error Err = WriteMemory(exe_addr, syscall_data.Insn.data(),
1398+
syscall_data.Insn.size(), bytes_read)
1399+
.ToError())
1400+
return std::move(Err);
1401+
1402+
m_mem_region_cache.clear();
1403+
1404+
// With software single stepping the syscall insn buffer must also include a
1405+
// trap instruction to stop the process.
1406+
int req = SupportHardwareSingleStepping() ? PTRACE_SINGLESTEP : PTRACE_CONT;
1407+
if (llvm::Error Err =
1408+
PtraceWrapper(req, thread.GetID(), nullptr, nullptr).ToError())
1409+
return std::move(Err);
1410+
1411+
int status;
1412+
::pid_t wait_pid = llvm::sys::RetryAfterSignal(-1, ::waitpid, thread.GetID(),
1413+
&status, __WALL);
1414+
if (wait_pid == -1) {
1415+
return llvm::errorCodeToError(
1416+
std::error_code(errno, std::generic_category()));
1417+
}
1418+
assert((unsigned)wait_pid == thread.GetID());
1419+
1420+
uint64_t result = reg_ctx.ReadRegisterAsUnsigned(syscall_data.Result, -ESRCH);
1421+
1422+
// Values larger than this are actually negative errno numbers.
1423+
uint64_t errno_threshold =
1424+
(uint64_t(-1) >> (64 - 8 * m_arch.GetAddressByteSize())) - 0x1000;
1425+
if (result > errno_threshold) {
1426+
return llvm::errorCodeToError(
1427+
std::error_code(-result & 0xfff, std::generic_category()));
1428+
}
1429+
1430+
return result;
13811431
}
13821432

1383-
Status NativeProcessLinux::DeallocateMemory(lldb::addr_t addr) {
1384-
// FIXME see comments in AllocateMemory - required lower-level
1385-
// bits not in place yet (ThreadPlans)
1386-
return Status("not implemented");
1433+
llvm::Expected<addr_t>
1434+
NativeProcessLinux::AllocateMemory(size_t size, uint32_t permissions) {
1435+
1436+
llvm::Optional<NativeRegisterContextLinux::MmapData> mmap_data =
1437+
GetCurrentThread()->GetRegisterContext().GetMmapData();
1438+
if (!mmap_data)
1439+
return llvm::make_error<UnimplementedError>();
1440+
1441+
unsigned prot = PROT_NONE;
1442+
assert((permissions & (ePermissionsReadable | ePermissionsWritable |
1443+
ePermissionsExecutable)) == permissions &&
1444+
"Unknown permission!");
1445+
if (permissions & ePermissionsReadable)
1446+
prot |= PROT_READ;
1447+
if (permissions & ePermissionsWritable)
1448+
prot |= PROT_WRITE;
1449+
if (permissions & ePermissionsExecutable)
1450+
prot |= PROT_EXEC;
1451+
1452+
llvm::Expected<uint64_t> Result =
1453+
Syscall({mmap_data->SysMmap, 0, size, prot, MAP_ANONYMOUS | MAP_PRIVATE,
1454+
uint64_t(-1), 0});
1455+
if (Result)
1456+
m_allocated_memory.try_emplace(*Result, size);
1457+
return Result;
1458+
}
1459+
1460+
llvm::Error NativeProcessLinux::DeallocateMemory(lldb::addr_t addr) {
1461+
llvm::Optional<NativeRegisterContextLinux::MmapData> mmap_data =
1462+
GetCurrentThread()->GetRegisterContext().GetMmapData();
1463+
if (!mmap_data)
1464+
return llvm::make_error<UnimplementedError>();
1465+
1466+
auto it = m_allocated_memory.find(addr);
1467+
if (it == m_allocated_memory.end())
1468+
return llvm::createStringError(llvm::errc::invalid_argument,
1469+
"Memory not allocated by the debugger.");
1470+
1471+
llvm::Expected<uint64_t> Result =
1472+
Syscall({mmap_data->SysMunmap, addr, it->second});
1473+
if (!Result)
1474+
return Result.takeError();
1475+
1476+
m_allocated_memory.erase(it);
1477+
return llvm::Error::success();
13871478
}
13881479

13891480
size_t NativeProcessLinux::UpdateThreads() {
@@ -1652,6 +1743,11 @@ NativeThreadLinux *NativeProcessLinux::GetThreadByID(lldb::tid_t tid) {
16521743
NativeProcessProtocol::GetThreadByID(tid));
16531744
}
16541745

1746+
NativeThreadLinux *NativeProcessLinux::GetCurrentThread() {
1747+
return static_cast<NativeThreadLinux *>(
1748+
NativeProcessProtocol::GetCurrentThread());
1749+
}
1750+
16551751
Status NativeProcessLinux::ResumeThread(NativeThreadLinux &thread,
16561752
lldb::StateType state, int signo) {
16571753
Log *const log = ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_THREAD);

lldb/source/Plugins/Process/Linux/NativeProcessLinux.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ class NativeProcessLinux : public NativeProcessELF {
7171
Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size,
7272
size_t &bytes_written) override;
7373

74-
Status AllocateMemory(size_t size, uint32_t permissions,
75-
lldb::addr_t &addr) override;
74+
llvm::Expected<lldb::addr_t> AllocateMemory(size_t size,
75+
uint32_t permissions) override;
7676

77-
Status DeallocateMemory(lldb::addr_t addr) override;
77+
llvm::Error DeallocateMemory(lldb::addr_t addr) override;
7878

7979
size_t UpdateThreads() override;
8080

@@ -94,6 +94,7 @@ class NativeProcessLinux : public NativeProcessELF {
9494
lldb::addr_t &load_addr) override;
9595

9696
NativeThreadLinux *GetThreadByID(lldb::tid_t id);
97+
NativeThreadLinux *GetCurrentThread();
9798

9899
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
99100
GetAuxvData() const override {
@@ -127,6 +128,8 @@ class NativeProcessLinux : public NativeProcessELF {
127128
llvm::Expected<llvm::ArrayRef<uint8_t>>
128129
GetSoftwareBreakpointTrapOpcode(size_t size_hint) override;
129130

131+
llvm::Expected<uint64_t> Syscall(llvm::ArrayRef<uint64_t> args);
132+
130133
private:
131134
MainLoop::SignalHandleUP m_sigchld_handle;
132135
ArchSpec m_arch;
@@ -140,6 +143,9 @@ class NativeProcessLinux : public NativeProcessELF {
140143
// the relevan breakpoint
141144
std::map<lldb::tid_t, lldb::addr_t> m_threads_stepping_with_breakpoint;
142145

146+
/// Inferior memory (allocated by us) and its size.
147+
llvm::DenseMap<lldb::addr_t, lldb::addr_t> m_allocated_memory;
148+
143149
// Private Instance Methods
144150
NativeProcessLinux(::pid_t pid, int terminal_fd, NativeDelegate &delegate,
145151
const ArchSpec &arch, MainLoop &mainloop,

lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,32 @@ class NativeRegisterContextLinux : public NativeRegisterContextRegisterInfo {
3131
// Invalidates cached values in register context data structures
3232
virtual void InvalidateAllRegisters(){}
3333

34+
struct SyscallData {
35+
/// The syscall instruction. If the architecture uses software
36+
/// single-stepping, the instruction should also be followed by a trap to
37+
/// ensure the process is stopped after the syscall.
38+
llvm::ArrayRef<uint8_t> Insn;
39+
40+
/// Registers used for syscall arguments. The first register is used to
41+
/// store the syscall number.
42+
llvm::ArrayRef<uint32_t> Args;
43+
44+
uint32_t Result; ///< Register containing the syscall result.
45+
};
46+
/// Return architecture-specific data needed to make inferior syscalls, if
47+
/// they are supported.
48+
virtual llvm::Optional<SyscallData> GetSyscallData() { return llvm::None; }
49+
50+
struct MmapData {
51+
// Syscall numbers can be found (e.g.) in /usr/include/asm/unistd.h for the
52+
// relevant architecture.
53+
unsigned SysMmap; ///< mmap syscall number.
54+
unsigned SysMunmap; ///< munmap syscall number
55+
};
56+
/// Return the architecture-specific data needed to make mmap syscalls, if
57+
/// they are supported.
58+
virtual llvm::Optional<MmapData> GetMmapData() { return llvm::None; }
59+
3460
protected:
3561
lldb::ByteOrder GetByteOrder() const;
3662

lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_x86_64.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,4 +1220,38 @@ NativeRegisterContextLinux_x86_64::GetPtraceOffset(uint32_t reg_index) {
12201220
(IsMPX(reg_index) ? 128 : 0);
12211221
}
12221222

1223+
llvm::Optional<NativeRegisterContextLinux::SyscallData>
1224+
NativeRegisterContextLinux_x86_64::GetSyscallData() {
1225+
switch (GetRegisterInfoInterface().GetTargetArchitecture().GetMachine()) {
1226+
case llvm::Triple::x86: {
1227+
static const uint8_t Int80[] = {0xcd, 0x80};
1228+
static const uint32_t Args[] = {lldb_eax_i386, lldb_ebx_i386, lldb_ecx_i386,
1229+
lldb_edx_i386, lldb_esi_i386, lldb_edi_i386,
1230+
lldb_ebp_i386};
1231+
return SyscallData{Int80, Args, lldb_eax_i386};
1232+
}
1233+
case llvm::Triple::x86_64: {
1234+
static const uint8_t Syscall[] = {0x0f, 0x05};
1235+
static const uint32_t Args[] = {
1236+
lldb_rax_x86_64, lldb_rdi_x86_64, lldb_rsi_x86_64, lldb_rdx_x86_64,
1237+
lldb_r10_x86_64, lldb_r8_x86_64, lldb_r9_x86_64};
1238+
return SyscallData{Syscall, Args, lldb_rax_x86_64};
1239+
}
1240+
default:
1241+
llvm_unreachable("Unhandled architecture!");
1242+
}
1243+
}
1244+
1245+
llvm::Optional<NativeRegisterContextLinux::MmapData>
1246+
NativeRegisterContextLinux_x86_64::GetMmapData() {
1247+
switch (GetRegisterInfoInterface().GetTargetArchitecture().GetMachine()) {
1248+
case llvm::Triple::x86:
1249+
return MmapData{192, 91};
1250+
case llvm::Triple::x86_64:
1251+
return MmapData{9, 11};
1252+
default:
1253+
llvm_unreachable("Unhandled architecture!");
1254+
}
1255+
}
1256+
12231257
#endif // defined(__i386__) || defined(__x86_64__)

lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_x86_64.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ class NativeRegisterContextLinux_x86_64 : public NativeRegisterContextLinux {
6464

6565
uint32_t NumSupportedHardwareWatchpoints() override;
6666

67+
llvm::Optional<SyscallData> GetSyscallData() override;
68+
69+
llvm::Optional<MmapData> GetMmapData() override;
70+
6771
protected:
6872
void *GetGPRBuffer() override { return &m_gpr_x86_64; }
6973

0 commit comments

Comments
 (0)