Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions api/debuggerapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,17 @@ namespace BinaryNinjaDebuggerAPI {
static std::string GetPathBaseName(const std::string& path);
};

struct DebugMemoryRegion
{
std::uintptr_t m_start {};
std::size_t m_size {};
std::string m_name {};
bool m_read {};
bool m_write {};
bool m_execute {};
bool m_shared {};
};


struct DebugRegister
{
Expand Down Expand Up @@ -686,6 +697,7 @@ namespace BinaryNinjaDebuggerAPI {
bool ResumeThread(std::uint32_t tid);

std::vector<DebugModule> GetModules();
std::vector<DebugMemoryRegion> GetMemoryMap();
std::vector<DebugRegister> GetRegisters();
intx::uint512 GetRegisterValue(const std::string& name);
bool SetRegisterValue(const std::string& name, const intx::uint512& value);
Expand Down
25 changes: 25 additions & 0 deletions api/debuggercontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,31 @@ std::vector<DebugModule> DebuggerController::GetModules()
}


std::vector<DebugMemoryRegion> DebuggerController::GetMemoryMap()
{
size_t count;
BNDebugMemoryRegion* regions = BNDebuggerGetMemoryMap(m_object, &count);

vector<DebugMemoryRegion> result;
result.reserve(count);
for (size_t i = 0; i < count; i++)
{
DebugMemoryRegion region;
region.m_start = regions[i].m_start;
region.m_size = regions[i].m_size;
region.m_name = regions[i].m_name;
region.m_read = regions[i].m_read;
region.m_write = regions[i].m_write;
region.m_execute = regions[i].m_execute;
region.m_shared = regions[i].m_shared;
result.push_back(region);
}
BNDebuggerFreeMemoryRegions(regions, count);

return result;
}


std::vector<DebugRegister> DebuggerController::GetRegisters()
{
size_t count;
Expand Down
14 changes: 14 additions & 0 deletions api/ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ extern "C"
bool m_loaded;
} BNDebugModule;

typedef struct BNDebugMemoryRegion
{
char* m_name;
uint64_t m_start;
size_t m_size;
bool m_read;
bool m_write;
bool m_execute;
bool m_shared;
} BNDebugMemoryRegion;


typedef struct BNDebugRegister
{
Expand Down Expand Up @@ -519,6 +530,9 @@ extern "C"
DEBUGGER_FFI_API BNDebugModule* BNDebuggerGetModules(BNDebuggerController* controller, size_t* count);
DEBUGGER_FFI_API void BNDebuggerFreeModules(BNDebugModule* modules, size_t count);

DEBUGGER_FFI_API BNDebugMemoryRegion* BNDebuggerGetMemoryMap(BNDebuggerController* controller, size_t* count);
DEBUGGER_FFI_API void BNDebuggerFreeMemoryRegions(BNDebugMemoryRegion* regions, size_t count);

DEBUGGER_FFI_API BNDebugRegister* BNDebuggerGetRegisters(BNDebuggerController* controller, size_t* count);
DEBUGGER_FFI_API void BNDebuggerFreeRegisters(BNDebugRegister* modules, size_t count);
DEBUGGER_FFI_API bool BNDebuggerSetRegisterValue(
Expand Down
79 changes: 79 additions & 0 deletions api/python/debuggercontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,62 @@ def __repr__(self):
return f"<DebugModule: {self.short_name}, {self.address:#x}-{self.address+self.size:#x}, size={self.size:#x}>"


class DebugMemoryRegion:
"""
DebugMemoryRegion represents a single mapped region of the target's virtual address space (a
memory-map entry). It has the following fields:

* ``start``: the start address of the region
* ``size``: the size of the region, in bytes
* ``name``: the backing of the region -- a file path for file-backed mappings, a well-known name \
such as ``[stack]`` or ``[heap]`` where the backend provides one, or an empty string for anonymous \
mappings
* ``read``: whether the region is readable
* ``write``: whether the region is writable
* ``execute``: whether the region is executable
* ``shared``: whether the mapping is shared between processes (as opposed to private/copy-on-write)

"""
def __init__(self, start, size, name, read, write, execute, shared):
self.start = start
self.size = size
self.name = name
self.read = read
self.write = write
self.execute = execute
self.shared = shared

def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return self.start == other.start and self.size == other.size and self.name == other.name \
and self.read == other.read and self.write == other.write and self.execute == other.execute \
and self.shared == other.shared

def __ne__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return not (self == other)

def __hash__(self):
return hash((self.start, self.size, self.name, self.read, self.write, self.execute, self.shared))

def __setattr__(self, name, value):
try:
object.__setattr__(self, name, value)
except AttributeError:
raise AttributeError(f"attribute '{name}' is read only")

@property
def permissions(self) -> str:
"""A string like ``rwx`` (or ``r-x``) describing the region's permissions"""
return f"{'r' if self.read else '-'}{'w' if self.write else '-'}{'x' if self.execute else '-'}"

def __repr__(self):
return f"<DebugMemoryRegion: {self.start:#x}-{self.start+self.size:#x}, {self.permissions}" \
f"{'s' if self.shared else 'p'}{(', ' + self.name) if self.name else ''}>"


class DebugRegister:
"""
DebugRegister represents a register in the target. It has the following fields:
Expand Down Expand Up @@ -1450,6 +1506,29 @@ def modules(self) -> DebugModules:
dbgcore.BNDebuggerFreeModules(modules, count.value)
return DebugModules(result)

@property
def memory_map(self) -> List[DebugMemoryRegion]:
"""
The memory map of the target: every mapped region of the virtual address space with its
permissions.

The map is refreshed when the target stops. Not every adapter reports a memory map yet; for
those, this returns an empty list. See issue #96.

:return: a list of ``DebugMemoryRegion``
"""
count = ctypes.c_ulonglong()
regions = dbgcore.BNDebuggerGetMemoryMap(self.handle, count)
result = []
for i in range(0, count.value):
region = DebugMemoryRegion(regions[i].m_start, regions[i].m_size, regions[i].m_name,
regions[i].m_read, regions[i].m_write, regions[i].m_execute,
regions[i].m_shared)
result.append(region)

dbgcore.BNDebuggerFreeMemoryRegions(regions, count.value)
return result

def rebase_to_remote_base(self) -> bool:
"""
Rebase the input binary view to match the remote base address.
Expand Down
70 changes: 70 additions & 0 deletions core/adapters/dbgengadapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ bool DbgEngAdapter::ConnectToDebugServerInternal(const std::string& connectionSt

QUERY_DEBUG_INTERFACE(IDebugControl7, &this->m_debugControl);
QUERY_DEBUG_INTERFACE(IDebugDataSpaces, &this->m_debugDataSpaces);
QUERY_DEBUG_INTERFACE(IDebugDataSpaces2, &this->m_debugDataSpaces2);
QUERY_DEBUG_INTERFACE(IDebugRegisters, &this->m_debugRegisters);
QUERY_DEBUG_INTERFACE(IDebugSymbols3, &this->m_debugSymbols);
QUERY_DEBUG_INTERFACE(IDebugSystemObjects, &this->m_debugSystemObjects);
Expand Down Expand Up @@ -422,6 +423,7 @@ void DbgEngAdapter::Reset()
{
SAFE_RELEASE(this->m_debugControl);
SAFE_RELEASE(this->m_debugDataSpaces);
SAFE_RELEASE(this->m_debugDataSpaces2);
SAFE_RELEASE(this->m_debugRegisters);
SAFE_RELEASE(this->m_debugSymbols);
SAFE_RELEASE(this->m_debugSystemObjects);
Expand Down Expand Up @@ -1749,6 +1751,74 @@ std::vector<DebugModule> DbgEngAdapter::GetModuleList()
return modules;
}


std::vector<DebugMemoryRegion> DbgEngAdapter::GetMemoryMap()
{
if (!this->m_debugDataSpaces2)
return {};

std::vector<DebugMemoryRegion> result;

// Walk the whole virtual address space with QueryVirtual (the DbgEng wrapper over VirtualQueryEx),
// starting at 0 and advancing by each region's size. QueryVirtual fails once we walk past the end
// of the address space, which terminates the loop. Free/reserved regions are reported too (with a
// size that spans the gap), so skipping them still advances efficiently.
ULONG64 address = 0;
while (true)
{
MEMORY_BASIC_INFORMATION64 info = {};
if (this->m_debugDataSpaces2->QueryVirtual(address, &info) != S_OK)
break;

if (info.RegionSize == 0)
break;

// Only committed pages are actually mapped. Guard pages and no-access pages are committed but
// cannot be read, so we exclude them from the "readable" map.
const ULONG protect = info.Protect & 0xff; // strip PAGE_GUARD / PAGE_NOCACHE / PAGE_WRITECOMBINE
if (info.State == MEM_COMMIT && !(info.Protect & PAGE_GUARD) && protect != PAGE_NOACCESS)
{
DebugMemoryRegion region;
region.m_start = info.BaseAddress;
region.m_size = info.RegionSize;
region.m_read = true; // any committed, non-no-access, non-guard page is readable on x86/x64
region.m_write = (protect == PAGE_READWRITE) || (protect == PAGE_WRITECOPY)
|| (protect == PAGE_EXECUTE_READWRITE) || (protect == PAGE_EXECUTE_WRITECOPY);
region.m_execute = (protect == PAGE_EXECUTE) || (protect == PAGE_EXECUTE_READ)
|| (protect == PAGE_EXECUTE_READWRITE) || (protect == PAGE_EXECUTE_WRITECOPY);
// MEM_MAPPED sections (file/pagefile-backed) can be shared between processes; MEM_IMAGE is
// copy-on-write and MEM_PRIVATE is private.
region.m_shared = (info.Type == MEM_MAPPED);

// For image-backed regions, resolve the backing module's image path for the name column.
if (info.Type == MEM_IMAGE && this->m_debugSymbols)
{
ULONG moduleIndex = 0;
ULONG64 moduleBase = 0;
if (this->m_debugSymbols->GetModuleByOffset(info.BaseAddress, 0, &moduleIndex, &moduleBase) == S_OK)
{
char imageName[1024];
if (this->m_debugSymbols->GetModuleNames(moduleIndex, 0, imageName, 1024, nullptr, nullptr, 0,
nullptr, nullptr, 0, nullptr)
== S_OK)
region.m_name = imageName;
}
}

result.push_back(region);
}

// Advance past this region; stop if the address would wrap around at the top of the space.
ULONG64 next = info.BaseAddress + info.RegionSize;
if (next <= address)
break;
address = next;
}

return result;
}


bool DbgEngAdapter::BreakInto()
{
if (ExecStatus() == DEBUG_STATUS_BREAK || ExecStatus() == DEBUG_STATUS_NO_DEBUGGEE)
Expand Down
3 changes: 3 additions & 0 deletions core/adapters/dbgengadapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ namespace BinaryNinjaDebugger {
IDebugClient7* m_debugClient {nullptr};
IDebugControl7* m_debugControl {nullptr};
IDebugDataSpaces* m_debugDataSpaces {nullptr};
IDebugDataSpaces2* m_debugDataSpaces2 {nullptr};
IDebugRegisters* m_debugRegisters {nullptr};
IDebugSymbols3* m_debugSymbols {nullptr};
IDebugSystemObjects* m_debugSystemObjects {nullptr};
Expand Down Expand Up @@ -222,6 +223,8 @@ namespace BinaryNinjaDebugger {
// bool WriteMemory(std::uintptr_t address, const void* out, std::size_t size) override;
std::vector<DebugModule> GetModuleList() override;

std::vector<DebugMemoryRegion> GetMemoryMap() override;

std::string GetTargetArchitecture() override;

DebugStopReason StopReason() override;
Expand Down
57 changes: 57 additions & 0 deletions core/adapters/gdbadapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,63 @@ std::vector<DebugModule> GdbAdapter::GetModuleList()
}


std::vector<DebugMemoryRegion> GdbAdapter::GetMemoryMap()
{
if (m_isTargetRunning)
return {};

if (!m_rspConnector)
return {};

const auto path = "/proc/" + std::to_string(this->m_lastActiveThreadId) + "/maps";
std::string data = GetRemoteFile(path);
if (data.empty())
return {};

// Each line of /proc/[pid]/maps describes one mapped region:
// start-end perms offset dev inode pathname
// e.g. "7ffff7dc5000-7ffff7de7000 r-xp 00000000 08:01 1234 /usr/lib/libc.so"
// Unlike GetModuleList (which aggregates the regions of each file into one module) we keep every
// region and record its permissions. The pathname is optional and may be a pseudo-name such as
// "[stack]", "[heap]" or "[vdso]".
std::vector<DebugMemoryRegion> result;
const std::regex region_regex(
"^([0-9a-f]+)-([0-9a-f]+)\\s+([rwxsp-]{4})\\s+[0-9a-f]+\\s+\\S+\\s+[0-9]+\\s*(.*)$");
for (const std::string& line : RspConnector::Split(data, "\n"))
{
std::string_view v = line;
v.remove_prefix(std::min(v.find_first_not_of(" "), v.size()));
auto trimPosition = v.find_last_not_of(" \r");
if (trimPosition != v.npos)
v.remove_suffix(v.size() - trimPosition - 1);

const std::string trimmedLine = std::string(v);

std::smatch match;
if (!std::regex_match(trimmedLine, match, region_regex) || match.size() != 5)
continue;

uint64_t start = std::strtoull(match[1].str().c_str(), nullptr, 16);
uint64_t end = std::strtoull(match[2].str().c_str(), nullptr, 16);
if (end <= start)
continue;

const std::string perms = match[3].str(); // e.g. "r-xp"
DebugMemoryRegion region;
region.m_start = start;
region.m_size = end - start;
region.m_name = match[4].str();
region.m_read = perms[0] == 'r';
region.m_write = perms[1] == 'w';
region.m_execute = perms[2] == 'x';
region.m_shared = perms[3] == 's';
result.push_back(region);
}

return result;
}


std::string GdbAdapter::GetTargetArchitecture()
{
return m_remoteArch;
Expand Down
2 changes: 2 additions & 0 deletions core/adapters/gdbadapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ namespace BinaryNinjaDebugger
std::string GetRemoteFile(const std::string& path);
std::vector<DebugModule> GetModuleList() override;

std::vector<DebugMemoryRegion> GetMemoryMap() override;

std::string GetTargetArchitecture() override;

DebugStopReason StopReason() override;
Expand Down
Loading