summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Potchik <brian@vector35.com>2026-03-11 12:12:36 -0400
committerBrian Potchik <brian@vector35.com>2026-03-11 12:12:36 -0400
commit6b57ef1d2c82d263655364588546e6211b0a99a8 (patch)
tree83593089f530368b6d83905fc32858f8251904dc
parentb8fdf800de345f93b2e68713d14bac425a62feb3 (diff)
Enhance MemoryMap bindings and add support to re-enable disabled regions in the UI.
-rw-r--r--binaryninjaapi.h127
-rw-r--r--binaryninjacore.h34
-rw-r--r--python/binaryview.py324
-rw-r--r--rust/src/binary_view.rs2
-rw-r--r--rust/src/binary_view/memory_map.rs205
-rw-r--r--ui/memorymap.h1
6 files changed, 635 insertions, 58 deletions
diff --git a/binaryninjaapi.h b/binaryninjaapi.h
index 5acc6a1c..f031d0c6 100644
--- a/binaryninjaapi.h
+++ b/binaryninjaapi.h
@@ -8211,6 +8211,34 @@ namespace BinaryNinja {
segmentation is automatically managed. If multiple regions overlap, the most recently added region takes
precedence by default.
*/
+
+ struct MemoryRegionInfo
+ {
+ std::string name;
+ std::string displayName;
+ uint64_t start;
+ uint64_t length;
+ uint32_t flags;
+ bool enabled;
+ bool rebaseable;
+ uint8_t fill;
+ bool hasTarget;
+ bool absoluteAddressMode;
+ bool local;
+ };
+
+ struct ResolvedMemoryRange
+ {
+ uint64_t start;
+ uint64_t length;
+ std::vector<MemoryRegionInfo> regions;
+
+ uint64_t End() const { return start + length; }
+ const MemoryRegionInfo* ActiveRegion() const { return regions.empty() ? nullptr : &regions.front(); }
+ std::string Name() const { auto* r = ActiveRegion(); return r ? r->name : std::string(); }
+ uint32_t Flags() const { auto* r = ActiveRegion(); return r ? r->flags : 0; }
+ };
+
class MemoryMap
{
BNBinaryView* m_object;
@@ -8336,6 +8364,103 @@ namespace BinaryNinja {
return BNIsMemoryRegionLocal(m_object, name.c_str());
}
+ std::optional<MemoryRegionInfo> GetMemoryRegionInfo(const std::string& name)
+ {
+ BNMemoryRegionInfo info;
+ if (!BNGetMemoryRegionInfo(m_object, name.c_str(), &info))
+ return std::nullopt;
+ MemoryRegionInfo result {info.name, info.displayName, info.start, info.length,
+ info.flags, info.enabled, info.rebaseable, info.fill,
+ info.hasTarget, info.absoluteAddressMode, info.local};
+ BNFreeMemoryRegionInfo(&info);
+ return result;
+ }
+
+ std::optional<MemoryRegionInfo> GetActiveMemoryRegionInfoAt(uint64_t addr)
+ {
+ BNMemoryRegionInfo info;
+ if (!BNGetActiveMemoryRegionInfoAt(m_object, addr, &info))
+ return std::nullopt;
+ MemoryRegionInfo result {info.name, info.displayName, info.start, info.length,
+ info.flags, info.enabled, info.rebaseable, info.fill,
+ info.hasTarget, info.absoluteAddressMode, info.local};
+ BNFreeMemoryRegionInfo(&info);
+ return result;
+ }
+
+ std::optional<ResolvedMemoryRange> GetResolvedMemoryRangeAt(uint64_t addr)
+ {
+ BNResolvedMemoryRange raw;
+ if (!BNGetResolvedMemoryRangeAt(m_object, addr, &raw))
+ return std::nullopt;
+ ResolvedMemoryRange result;
+ result.start = raw.start;
+ result.length = raw.length;
+ result.regions.reserve(raw.regionCount);
+ for (size_t j = 0; j < raw.regionCount; j++)
+ {
+ auto& r = raw.regions[j];
+ result.regions.push_back({r.name, r.displayName, r.start, r.length,
+ r.flags, r.enabled, r.rebaseable, r.fill,
+ r.hasTarget, r.absoluteAddressMode, r.local});
+ }
+ BNFreeResolvedMemoryRange(&raw);
+ return result;
+ }
+
+ std::vector<MemoryRegionInfo> GetMemoryRegions()
+ {
+ size_t count = 0;
+ BNMemoryRegionInfo* regions = BNGetMemoryRegions(m_object, &count);
+ std::vector<MemoryRegionInfo> result;
+ result.reserve(count);
+ for (size_t i = 0; i < count; i++)
+ {
+ result.push_back({
+ regions[i].name,
+ regions[i].displayName,
+ regions[i].start,
+ regions[i].length,
+ regions[i].flags,
+ regions[i].enabled,
+ regions[i].rebaseable,
+ regions[i].fill,
+ regions[i].hasTarget,
+ regions[i].absoluteAddressMode,
+ regions[i].local,
+ });
+ }
+ BNFreeMemoryRegions(regions, count);
+ return result;
+ }
+
+ std::vector<ResolvedMemoryRange> GetResolvedRanges()
+ {
+ size_t count = 0;
+ BNResolvedMemoryRange* ranges = BNGetResolvedMemoryRanges(m_object, &count);
+ std::vector<ResolvedMemoryRange> result;
+ result.reserve(count);
+ for (size_t i = 0; i < count; i++)
+ {
+ ResolvedMemoryRange range;
+ range.start = ranges[i].start;
+ range.length = ranges[i].length;
+ range.regions.reserve(ranges[i].regionCount);
+ for (size_t j = 0; j < ranges[i].regionCount; j++)
+ {
+ auto& r = ranges[i].regions[j];
+ range.regions.push_back({
+ r.name, r.displayName, r.start, r.length,
+ r.flags, r.enabled, r.rebaseable, r.fill,
+ r.hasTarget, r.absoluteAddressMode, r.local,
+ });
+ }
+ result.push_back(std::move(range));
+ }
+ BNFreeResolvedMemoryRanges(ranges, count);
+ return result;
+ }
+
void Reset()
{
BNResetMemoryMap(m_object);
@@ -20044,7 +20169,7 @@ namespace BinaryNinja {
\return True if the type library was successfully decompressed
*/
bool DecompressToFile(const std::string& path);
-
+
/*! The Architecture this type library is associated with
\return
diff --git a/binaryninjacore.h b/binaryninjacore.h
index 0ebeac20..80694ce2 100644
--- a/binaryninjacore.h
+++ b/binaryninjacore.h
@@ -37,14 +37,14 @@
// Current ABI version for linking to the core. This is incremented any time
// there are changes to the API that affect linking, including new functions,
// new types, or modifications to existing functions or types.
-#define BN_CURRENT_CORE_ABI_VERSION 158
+#define BN_CURRENT_CORE_ABI_VERSION 159
// Minimum ABI version that is supported for loading of plugins. Plugins that
// are linked to an ABI version less than this will not be able to load and
// will require rebuilding. The minimum version is increased when there are
// incompatible changes that break binary compatibility, such as changes to
// existing types or functions.
-#define BN_MINIMUM_CORE_ABI_VERSION 158
+#define BN_MINIMUM_CORE_ABI_VERSION 159
#ifdef __GNUC__
#ifdef BINARYNINJACORE_LIBRARY
@@ -3803,6 +3803,27 @@ extern "C"
uint64_t infoData;
} BNSectionInfo;
+ typedef struct BNMemoryRegionInfo {
+ char* name;
+ char* displayName;
+ uint64_t start;
+ uint64_t length;
+ uint32_t flags;
+ bool enabled;
+ bool rebaseable;
+ uint8_t fill;
+ bool hasTarget;
+ bool absoluteAddressMode;
+ bool local;
+ } BNMemoryRegionInfo;
+
+ typedef struct BNResolvedMemoryRange {
+ uint64_t start;
+ uint64_t length;
+ BNMemoryRegionInfo* regions;
+ size_t regionCount;
+ } BNResolvedMemoryRange;
+
typedef bool(*BNCollaborationAnalysisConflictHandler)(void*, const char** keys, BNAnalysisMergeConflict** conflicts, size_t conflictCount);
typedef bool(*BNCollaborationNameChangesetFunction)(void*, BNCollaborationChangeset*);
@@ -4531,6 +4552,15 @@ extern "C"
BINARYNINJACOREAPI char* BNGetMemoryRegionDisplayName(BNBinaryView* view, const char* name);
BINARYNINJACOREAPI bool BNSetMemoryRegionDisplayName(BNBinaryView* view, const char* name, const char* displayName);
BINARYNINJACOREAPI bool BNIsMemoryRegionLocal(BNBinaryView* view, const char* name);
+ BINARYNINJACOREAPI bool BNGetMemoryRegionInfo(BNBinaryView* view, const char* name, BNMemoryRegionInfo* result);
+ BINARYNINJACOREAPI bool BNGetActiveMemoryRegionInfoAt(BNBinaryView* view, uint64_t addr, BNMemoryRegionInfo* result);
+ BINARYNINJACOREAPI bool BNGetResolvedMemoryRangeAt(BNBinaryView* view, uint64_t addr, BNResolvedMemoryRange* result);
+ BINARYNINJACOREAPI void BNFreeMemoryRegionInfo(BNMemoryRegionInfo* info);
+ BINARYNINJACOREAPI void BNFreeResolvedMemoryRange(BNResolvedMemoryRange* range);
+ BINARYNINJACOREAPI BNMemoryRegionInfo* BNGetMemoryRegions(BNBinaryView* view, size_t* count);
+ BINARYNINJACOREAPI void BNFreeMemoryRegions(BNMemoryRegionInfo* regions, size_t count);
+ BINARYNINJACOREAPI BNResolvedMemoryRange* BNGetResolvedMemoryRanges(BNBinaryView* view, size_t* count);
+ BINARYNINJACOREAPI void BNFreeResolvedMemoryRanges(BNResolvedMemoryRange* ranges, size_t count);
BINARYNINJACOREAPI void BNResetMemoryMap(BNBinaryView* view);
// Binary view access
diff --git a/python/binaryview.py b/python/binaryview.py
index 4e62af61..5c86e101 100644
--- a/python/binaryview.py
+++ b/python/binaryview.py
@@ -2532,25 +2532,135 @@ class AdvancedILFunctionList:
yield self._func_queue.popleft().function
+@dataclass(frozen=True)
+class MemoryRegionInfo:
+ """Snapshot of a memory region's properties at the time of query.
+
+ This is a frozen value type. Modifying the memory map will not update existing
+ MemoryRegionInfo instances. To mutate a region, use the corresponding MemoryMap methods
+ (e.g., ``memory_map.set_memory_region_flags(region.name, new_flags)``).
+ """
+ name: str
+ display_name: str
+ start: int
+ length: int
+ flags: SegmentFlag
+ enabled: bool
+ rebaseable: bool
+ fill: int
+ has_target: bool
+ absolute_address_mode: bool
+ local: bool
+
+ @staticmethod
+ def _from_core_struct(r) -> 'MemoryRegionInfo':
+ """Construct a MemoryRegionInfo from a core FFI struct."""
+ return MemoryRegionInfo(
+ name=core.pyNativeStr(r.name),
+ display_name=core.pyNativeStr(r.displayName),
+ start=r.start, length=r.length,
+ flags=SegmentFlag(r.flags), enabled=r.enabled,
+ rebaseable=r.rebaseable, fill=r.fill,
+ has_target=r.hasTarget,
+ absolute_address_mode=r.absoluteAddressMode,
+ local=r.local,
+ )
+
+ @property
+ def end(self) -> int:
+ return self.start + self.length
+
+ def __repr__(self):
+ r = "r" if self.flags & SegmentFlag.SegmentReadable else "-"
+ w = "w" if self.flags & SegmentFlag.SegmentWritable else "-"
+ x = "x" if self.flags & SegmentFlag.SegmentExecutable else "-"
+ status = ""
+ if not self.enabled:
+ status = " | DISABLED"
+ return f"<MemoryRegion: '{self.name}' {self.start:#x}-{self.end:#x} {r}{w}{x}{status}>"
+
+
+@dataclass(frozen=True)
+class ResolvedRange:
+ """A computed, non-overlapping interval in the resolved address space.
+
+ Overlapping raw regions are split into disjoint intervals. Each
+ ResolvedRange holds the regions that cover it, ordered by precedence,
+ with the active region first. The ``active_region`` property returns
+ the highest-precedence region.
+ """
+ start: int
+ length: int
+ regions: List[MemoryRegionInfo]
+
+ @property
+ def end(self) -> int:
+ return self.start + self.length
+
+ @property
+ def active_region(self) -> Optional[MemoryRegionInfo]:
+ """The highest-priority region at this range, or None if empty."""
+ return self.regions[0] if self.regions else None
+
+ @property
+ def name(self) -> Optional[str]:
+ """Name of the active region, or None if empty."""
+ r = self.active_region
+ return r.name if r else None
+
+ @property
+ def flags(self) -> SegmentFlag:
+ """Flags of the active (highest-priority) region."""
+ r = self.active_region
+ return r.flags if r else SegmentFlag(0)
+
+ def __repr__(self):
+ r = "r" if self.flags & SegmentFlag.SegmentReadable else "-"
+ w = "w" if self.flags & SegmentFlag.SegmentWritable else "-"
+ x = "x" if self.flags & SegmentFlag.SegmentExecutable else "-"
+ return f"<ResolvedRange: {self.start:#x}-{self.end:#x} {r}{w}{x}, {len(self.regions)} region(s)>"
+
+ def __contains__(self, addr: int) -> bool:
+ return self.start <= addr < self.end
+
+
class MemoryMap:
r"""
- The MemoryMap object provides access to the system-level memory map describing how a BinaryView is loaded
- into memory. Each BinaryView exposes its portion of the MemoryMap through the Segments defined within that view.
+ Live proxy to the memory map of a BinaryView.
+
+ A MemoryMap describes how a BinaryView is loaded into memory. It contains
+ *regions*, which are raw and possibly overlapping memory definitions, and
+ exposes *resolved ranges*, which are a computed disjoint view of the address
+ space produced by splitting overlapping regions.
+
+ Each BinaryView contributes its portion of the overall system memory layout
+ through the segments and regions defined within that view. When regions
+ overlap, the most recently added region takes precedence by default. Mutation
+ is always performed by region name.
- **Architecture Note:** This Python MemoryMap object is a proxy that accesses the BinaryView's current
- MemoryMap state through the FFI boundary. The proxy provides a simple mutable interface: when you call
- modification operations (``add_memory_region``, ``remove_memory_region``, etc.), the proxy automatically
- accesses the updated MemoryMap. Internally, the core uses immutable copy-on-write data structures, but
- the proxy abstracts this away.
+ **Container semantics:** Iteration (``__iter__``), length (``__len__``), and
+ indexing (``__getitem__``) operate on *resolved ranges*, the computed
+ non-overlapping view of the address space. Configured regions are accessed
+ explicitly via ``regions``, ``get_region``, and name-based membership
+ (``__contains__``).
- When you access ``view.memory_map``, you always see the current state. For lock-free access during analysis,
- AnalysisContext provides memory layout query methods (``is_valid_offset()``, ``is_offset_readable()``,
- ``get_start()``, ``get_length()``, etc.) that operate on an immutable snapshot of the MemoryMap cached when
- the analysis was initiated.
+ **Snapshot semantics:** ``MemoryRegionInfo`` and ``ResolvedRange`` objects
+ are frozen snapshot value types captured at query time. They are not updated
+ by later mutations to the memory map. The proxy itself (``view.memory_map``)
+ always reflects the current state.
- A MemoryMap can contain multiple, arbitrarily overlapping memory regions. When modified, address space
- segmentation is automatically managed. If multiple regions overlap, the most recently added region takes
- precedence by default.
+ **Architecture note:** This Python ``MemoryMap`` object is a proxy that
+ accesses the BinaryView's current memory map state through the FFI boundary.
+ Internally, the core uses immutable copy-on-write data structures to manage
+ memory map updates, but the proxy presents a simple mutable interface.
+
+ **Analysis note:** For lock-free access during analysis, ``AnalysisContext``
+ provides memory layout query methods such as ``is_valid_offset()``,
+ ``is_offset_readable()``, ``get_start()``, and ``get_length()``. These
+ operate on an immutable snapshot of the MemoryMap captured when analysis
+ begins.
+
+ .. note:: Repeated property access, for example ``regions`` or ``ranges``, returns fresh snapshots of the current memory map state.
All MemoryMap APIs support undo and redo operations. During BinaryView::Init, these APIs should be used conditionally:
@@ -2571,66 +2681,66 @@ class MemoryMap:
>>> segments.append(start=rom_base, length=0x1000, flags=SegmentFlag.SegmentReadable)
>>> view = load(bytes.fromhex('5054ebfe'), options={'loader.imageBase': base, 'loader.platform': 'x86', 'loader.segments': json.dumps(segments)})
>>> view.memory_map
- <region: 0x10000 - 0x10004>
+ <range: 0x10000 - 0x10004>
size: 0x4
- objects:
+ regions:
'origin<Mapped>@0x0' | Mapped<Absolute> | <r-x>
- <region: 0xc0000000 - 0xc0001000>
+ <range: 0xc0000000 - 0xc0001000>
size: 0x1000
- objects:
+ regions:
'origin<Mapped>@0xbfff0000' | Unmapped | <r--> | FILL<0x0>
- <region: 0xc0001000 - 0xc0001014>
+ <range: 0xc0001000 - 0xc0001014>
size: 0x14
- objects:
+ regions:
'origin<Mapped>@0xbfff1000' | Unmapped | <---> | FILL<0x0>
>>> view.memory_map.add_memory_region("rom", rom_base, b'\x90' * 4096, SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable)
True
>>> view.memory_map
- <region: 0x10000 - 0x10004>
+ <range: 0x10000 - 0x10004>
size: 0x4
- objects:
+ regions:
'origin<Mapped>@0x0' | Mapped<Absolute> | <r-x>
- <region: 0xc0000000 - 0xc0001000>
+ <range: 0xc0000000 - 0xc0001000>
size: 0x1000
- objects:
+ regions:
'rom' | Mapped<Relative> | <r-x>
'origin<Mapped>@0xbfff0000' | Unmapped | <r--> | FILL<0x0>
- <region: 0xc0001000 - 0xc0001014>
+ <range: 0xc0001000 - 0xc0001014>
size: 0x14
- objects:
+ regions:
'origin<Mapped>@0xbfff1000' | Unmapped | <---> | FILL<0x0>
>>> view.read(rom_base, 16)
b'\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90'
>>> view.memory_map.add_memory_region("pad", rom_base, b'\xa5' * 8)
True
- >>> view.read(rom_base, 16)
+ >>> view.read(rom_base, 16) # "pad" wins for first 8 bytes
b'\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\x90\x90\x90\x90\x90\x90\x90\x90'
- >>> view.memory_map
- <region: 0x10000 - 0x10004>
+ >>> view.memory_map # resolved ranges show the split
+ <range: 0x10000 - 0x10004>
size: 0x4
- objects:
+ regions:
'origin<Mapped>@0x0' | Mapped<Absolute> | <r-x>
- <region: 0xc0000000 - 0xc0000008>
+ <range: 0xc0000000 - 0xc0000008>
size: 0x8
- objects:
+ regions:
'pad' | Mapped<Relative> | <--->
'rom' | Mapped<Relative> | <r-x>
'origin<Mapped>@0xbfff0000' | Unmapped | <r--> | FILL<0x0>
- <region: 0xc0000008 - 0xc0001000>
+ <range: 0xc0000008 - 0xc0001000>
size: 0xff8
- objects:
+ regions:
'rom' | Mapped<Relative> | <r-x>
'origin<Mapped>@0xbfff0000' | Unmapped | <r--> | FILL<0x0>
- <region: 0xc0001000 - 0xc0001014>
+ <range: 0xc0001000 - 0xc0001014>
size: 0x14
- objects:
+ regions:
'origin<Mapped>@0xbfff1000' | Unmapped | <---> | FILL<0x0>
"""
@@ -2642,22 +2752,92 @@ class MemoryMap:
return self.format_description(description)
def __len__(self):
- mm_json = self.description()
- if 'MemoryMap' in mm_json:
- return len(mm_json['MemoryMap'])
- else:
- return 0
+ return len(self.ranges)
+
+ def __iter__(self):
+ return iter(self.ranges)
+
+ def __getitem__(self, index):
+ return self.ranges[index]
+
+ def get_region(self, name: str) -> Optional[MemoryRegionInfo]:
+ """Look up a memory region by name, returning None if not found."""
+ result = core.BNMemoryRegionInfo()
+ if not core.BNGetMemoryRegionInfo(self.handle, name, result):
+ return None
+ try:
+ return MemoryRegionInfo._from_core_struct(result)
+ finally:
+ core.BNFreeMemoryRegionInfo(result)
+
+ def __contains__(self, name: str) -> bool:
+ """Name-based membership over configured regions.
+
+ Note: Unlike iteration and indexing (which operate on resolved ranges),
+ membership tests by region name. Non-string values return False.
+ """
+ if not isinstance(name, str):
+ return False
+ return self.get_region(name) is not None
def __init__(self, handle: 'BinaryView'):
self.handle = handle
+ @property
+ def regions(self) -> List[MemoryRegionInfo]:
+ """List of all memory regions (including disabled ones) as snapshot value types.
+
+ Returns immutable snapshot objects that are not updated after later memory map mutations.
+ """
+ count = ctypes.c_ulonglong(0)
+ regions = core.BNGetMemoryRegions(self.handle, count)
+ if not regions:
+ return []
+ result = []
+ try:
+ for i in range(count.value):
+ result.append(MemoryRegionInfo._from_core_struct(regions[i]))
+ return result
+ finally:
+ core.BNFreeMemoryRegions(regions, count.value)
+
+ @property
+ def ranges(self) -> List[ResolvedRange]:
+ """List of resolved, non-overlapping address ranges sorted by start address.
+
+ Each range contains an ordered list of memory regions at that interval,
+ with the first being the active (highest-priority) region. This is the
+ computed address-space view, analogous to segments.
+
+ Returns immutable snapshot objects that are not updated after later memory map mutations.
+ """
+ count = ctypes.c_ulonglong(0)
+ raw_ranges = core.BNGetResolvedMemoryRanges(self.handle, count)
+ if not raw_ranges:
+ return []
+ result = []
+ try:
+ for i in range(count.value):
+ regions = []
+ for j in range(raw_ranges[i].regionCount):
+ regions.append(MemoryRegionInfo._from_core_struct(raw_ranges[i].regions[j]))
+ result.append(ResolvedRange(
+ start=raw_ranges[i].start,
+ length=raw_ranges[i].length,
+ regions=regions,
+ ))
+ return result
+ finally:
+ core.BNFreeResolvedMemoryRanges(raw_ranges, count.value)
+
def format_description(self, description: dict) -> str:
+ """Format a memory map description dict as a human-readable string. Keep public for compatibility."""
formatted_description = ""
for entry in description['MemoryMap']:
- formatted_description += f"<region: {hex(entry['address'])} - {hex(entry['address'] + entry['length'])}>\n"
+ formatted_description += f"<range: {hex(entry['address'])} - {hex(entry['address'] + entry['length'])}>\n"
formatted_description += f"\tsize: {hex(entry['length'])}\n"
- formatted_description += "\tobjects:\n"
- for obj in entry['objects']:
+ formatted_description += "\tregions:\n"
+ for obj in entry['regions']:
if obj['target']:
mapped_state = f"Mapped<{'Absolute' if obj['absolute_address_mode'] else 'Relative'}>"
else:
@@ -2677,12 +2857,13 @@ class MemoryMap:
return formatted_description
def description(self, base: bool = False) -> dict:
+ """Return the memory map description as a dict. If *base* is True, return the unresolved base map."""
if base:
return json.loads(core.BNGetBaseMemoryMapDescription(self.handle))
return json.loads(core.BNGetMemoryMapDescription(self.handle))
@property
- def base(self):
+ def base_description(self) -> str:
"""Formatted string of the base memory map, consisting of unresolved auto and user segments (read-only)."""
return self.format_description(self.description(base=True))
@@ -2702,7 +2883,7 @@ class MemoryMap:
core.BNSetLogicalMemoryMapEnabled(self.handle, enabled)
@property
- def is_activated(self):
+ def is_activated(self) -> bool:
"""
Whether the memory map is activated for the associated view.
@@ -2784,46 +2965,87 @@ class MemoryMap:
raise NotImplementedError(f"Unsupported memory region source type: {type(source)}")
def remove_memory_region(self, name: str) -> bool:
+ """Remove a memory region by name. Returns True on success."""
return core.BNRemoveMemoryRegion(self.handle, name)
def get_active_memory_region_at(self, addr: int) -> str:
+ """Return the name of the active region at *addr*, or an empty string if no region covers the address."""
return core.BNGetActiveMemoryRegionAt(self.handle, addr)
- def get_memory_region_flags(self, name: str) -> set:
- flags = core.BNGetMemoryRegionFlags(self.handle, name)
- return {flag for flag in SegmentFlag if flags & flag}
+ def get_active_region_at(self, addr: int) -> Optional[MemoryRegionInfo]:
+ """Return the active region snapshot covering *addr*, or None if no region covers the address."""
+ result = core.BNMemoryRegionInfo()
+ if not core.BNGetActiveMemoryRegionInfoAt(self.handle, addr, result):
+ return None
+ try:
+ return MemoryRegionInfo._from_core_struct(result)
+ finally:
+ core.BNFreeMemoryRegionInfo(result)
+
+ def get_resolved_range_at(self, addr: int) -> Optional['ResolvedRange']:
+ """Return the resolved range snapshot covering *addr*, or None if no range covers the address."""
+ result = core.BNResolvedMemoryRange()
+ if not core.BNGetResolvedMemoryRangeAt(self.handle, addr, result):
+ return None
+ try:
+ regions = []
+ for j in range(result.regionCount):
+ regions.append(MemoryRegionInfo._from_core_struct(result.regions[j]))
+ return ResolvedRange(start=result.start, length=result.length, regions=regions)
+ finally:
+ core.BNFreeResolvedMemoryRange(result)
+
+ def get_memory_region_flags(self, name: str) -> SegmentFlag:
+ """Return the flags for the named region."""
+ return SegmentFlag(core.BNGetMemoryRegionFlags(self.handle, name))
- def set_memory_region_flags(self, name: str, flags: SegmentFlag) -> bool:
+ def set_memory_region_flags(self, name: str, flags: Union[SegmentFlag, set]) -> bool:
+ """Set flags for the named region. Accepts SegmentFlag or a set of flags."""
+ if isinstance(flags, set):
+ combined = 0
+ for flag in flags:
+ combined |= flag
+ flags = combined
return core.BNSetMemoryRegionFlags(self.handle, name, flags)
def is_memory_region_enabled(self, name: str) -> bool:
+ """Return whether the named region is enabled."""
return core.BNIsMemoryRegionEnabled(self.handle, name)
def set_memory_region_enabled(self, name: str, enabled: bool = True) -> bool:
+ """Set the enabled state for the named region."""
return core.BNSetMemoryRegionEnabled(self.handle, name, enabled)
def is_memory_region_rebaseable(self, name: str) -> bool:
+ """Return whether the named region is rebaseable."""
return core.BNIsMemoryRegionRebaseable(self.handle, name)
def set_memory_region_rebaseable(self, name: str, rebaseable: bool = True) -> bool:
+ """Set the rebaseable state for the named region."""
return core.BNSetMemoryRegionRebaseable(self.handle, name, rebaseable)
def get_memory_region_fill(self, name: str) -> int:
+ """Return the fill byte for the named region."""
return core.BNGetMemoryRegionFill(self.handle, name)
def set_memory_region_fill(self, name: str, fill: int) -> bool:
+ """Set the fill byte for the named region."""
return core.BNSetMemoryRegionFill(self.handle, name, fill)
def get_memory_region_display_name(self, name: str) -> str:
+ """Return the display name for the named region."""
return core.BNGetMemoryRegionDisplayName(self.handle, name)
def set_memory_region_display_name(self, name: str, display_name: str) -> bool:
+ """Set the display name for the named region."""
return core.BNSetMemoryRegionDisplayName(self.handle, name, display_name)
def is_memory_region_local(self, name: str) -> bool:
+ """Return whether the named region is local."""
return core.BNIsMemoryRegionLocal(self.handle, name)
- def reset(self):
+ def reset(self) -> None:
+ """Reset the memory map to its initial state. Supports undo."""
core.BNResetMemoryMap(self.handle)
class BinaryView:
diff --git a/rust/src/binary_view.rs b/rust/src/binary_view.rs
index 93819098..bd26dad3 100644
--- a/rust/src/binary_view.rs
+++ b/rust/src/binary_view.rs
@@ -68,7 +68,7 @@ pub mod reader;
pub mod search;
pub mod writer;
-pub use memory_map::MemoryMap;
+pub use memory_map::{MemoryMap, MemoryRegionInfo, ResolvedRange};
pub use reader::BinaryReader;
pub use writer::BinaryWriter;
diff --git a/rust/src/binary_view/memory_map.rs b/rust/src/binary_view/memory_map.rs
index bb7902b9..44bb58c6 100644
--- a/rust/src/binary_view/memory_map.rs
+++ b/rust/src/binary_view/memory_map.rs
@@ -3,10 +3,101 @@ use crate::data_buffer::DataBuffer;
use crate::file_accessor::{Accessor, FileAccessor};
use crate::rc::Ref;
use crate::segment::SegmentFlags;
-use crate::string::{BnString, IntoCStr};
+use crate::string::{raw_to_string, BnString, IntoCStr};
use binaryninjacore_sys::*;
-/// MemoryMap provides access to the system-level memory map describing how a BinaryView is loaded into memory.
+/// Snapshot of a memory region's properties at the time of query.
+///
+/// This is a value type — modifying the memory map will not update existing
+/// `MemoryRegionInfo` instances. To mutate a region, use the corresponding
+/// [`MemoryMap`] methods.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct MemoryRegionInfo {
+ pub name: String,
+ pub display_name: String,
+ pub start: u64,
+ pub length: u64,
+ pub flags: SegmentFlags,
+ pub enabled: bool,
+ pub rebaseable: bool,
+ pub fill: u8,
+ pub has_target: bool,
+ pub absolute_address_mode: bool,
+ pub local: bool,
+}
+
+impl MemoryRegionInfo {
+ pub fn end(&self) -> u64 {
+ self.start + self.length
+ }
+
+ fn from_raw(region: &BNMemoryRegionInfo) -> Self {
+ Self {
+ name: raw_to_string(region.name).unwrap_or_default(),
+ display_name: raw_to_string(region.displayName).unwrap_or_default(),
+ start: region.start,
+ length: region.length,
+ flags: SegmentFlags::from_raw(region.flags),
+ enabled: region.enabled,
+ rebaseable: region.rebaseable,
+ fill: region.fill,
+ has_target: region.hasTarget,
+ absolute_address_mode: region.absoluteAddressMode,
+ local: region.local,
+ }
+ }
+}
+
+/// A resolved, non-overlapping address range in the memory map.
+///
+/// Each range contains an ordered list of memory regions that overlap at this
+/// interval. The first region is the active (highest priority) one.
+///
+/// This is a snapshot value — it is not updated by later mutations to the
+/// memory map.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ResolvedRange {
+ pub start: u64,
+ pub length: u64,
+ pub regions: Vec<MemoryRegionInfo>,
+}
+
+impl ResolvedRange {
+ pub fn end(&self) -> u64 {
+ self.start + self.length
+ }
+
+ /// The highest-priority region at this range.
+ pub fn active_region(&self) -> Option<&MemoryRegionInfo> {
+ self.regions.first()
+ }
+
+ /// Name of the active region, or `None` if empty.
+ pub fn name(&self) -> Option<&str> {
+ self.active_region().map(|r| r.name.as_str())
+ }
+
+ /// Flags of the active (highest-priority) region.
+ pub fn flags(&self) -> SegmentFlags {
+ self.active_region()
+ .map(|r| r.flags)
+ .unwrap_or(SegmentFlags::from_raw(0))
+ }
+}
+
+/// Live proxy to the memory map of a [`BinaryView`].
+///
+/// A `MemoryMap` describes how a [`BinaryView`] is loaded into memory. It
+/// contains *regions* — raw, possibly overlapping memory definitions — and
+/// exposes *resolved ranges* — a computed, disjoint view of the address space
+/// produced by splitting overlapping regions. The most recently added region
+/// takes precedence when regions overlap. Mutation is always by region name.
+///
+/// - [`regions()`](Self::regions) returns configured memory regions, including
+/// disabled ones.
+/// - [`ranges()`](Self::ranges) returns resolved, non-overlapping address
+/// ranges — the computed active view.
+/// - Both return snapshot values that are not updated after later mutations.
///
/// # Architecture Note
///
@@ -22,6 +113,13 @@ use binaryninjacore_sys::*;
/// A MemoryMap can contain multiple, arbitrarily overlapping memory regions. When modified, address space
/// segmentation is automatically managed. If multiple regions overlap, the most recently added region takes
/// precedence by default.
+///
+/// All MemoryMap APIs support undo and redo operations. During BinaryView::Init, these APIs should be used
+/// conditionally:
+///
+/// * Initial load: Use the MemoryMap APIs to define the memory regions that compose the system.
+/// * Database load: Do not use the MemoryMap APIs, as the regions are already persisted and will be restored
+/// automatically.
#[derive(PartialEq, Eq, Hash)]
pub struct MemoryMap {
view: Ref<BinaryView>,
@@ -32,7 +130,106 @@ impl MemoryMap {
Self { view }
}
- // TODO: There does not seem to be a way to enumerate memory regions.
+ /// Returns a snapshot of all configured memory regions, including disabled ones.
+ pub fn regions(&self) -> Vec<MemoryRegionInfo> {
+ let mut count: usize = 0;
+ let regions_raw = unsafe { BNGetMemoryRegions(self.view.handle, &mut count) };
+ if regions_raw.is_null() {
+ return Vec::new();
+ }
+ let mut result = Vec::with_capacity(count);
+ for i in 0..count {
+ let region = unsafe { &*regions_raw.add(i) };
+ result.push(MemoryRegionInfo::from_raw(region));
+ }
+ unsafe { BNFreeMemoryRegions(regions_raw, count) };
+ result
+ }
+
+ /// Returns a snapshot of the resolved, non-overlapping address ranges.
+ ///
+ /// Each range contains an ordered list of memory regions, with the first
+ /// being the active (highest priority) region at that interval.
+ pub fn ranges(&self) -> Vec<ResolvedRange> {
+ let mut count: usize = 0;
+ let ranges_raw = unsafe { BNGetResolvedMemoryRanges(self.view.handle, &mut count) };
+ if ranges_raw.is_null() {
+ return Vec::new();
+ }
+ let mut result = Vec::with_capacity(count);
+ for i in 0..count {
+ let range = unsafe { &*ranges_raw.add(i) };
+ let mut regions = Vec::with_capacity(range.regionCount);
+ for j in 0..range.regionCount {
+ let region = unsafe { &*range.regions.add(j) };
+ regions.push(MemoryRegionInfo::from_raw(region));
+ }
+ result.push(ResolvedRange {
+ start: range.start,
+ length: range.length,
+ regions,
+ });
+ }
+ unsafe { BNFreeResolvedMemoryRanges(ranges_raw, count) };
+ result
+ }
+
+ /// Look up a configured memory region by name.
+ ///
+ /// Returns a snapshot of the region's properties, or `None` if no region
+ /// with the given name exists.
+ pub fn get_region(&self, name: &str) -> Option<MemoryRegionInfo> {
+ let name_raw = name.to_cstr();
+ let mut result: BNMemoryRegionInfo = unsafe { std::mem::zeroed() };
+ let found = unsafe {
+ BNGetMemoryRegionInfo(self.view.handle, name_raw.as_ptr(), &mut result)
+ };
+ if !found {
+ return None;
+ }
+ let info = MemoryRegionInfo::from_raw(&result);
+ unsafe { BNFreeMemoryRegionInfo(&mut result) };
+ Some(info)
+ }
+
+ /// Return the active region snapshot covering `addr`, or `None` if no
+ /// enabled region covers the address.
+ pub fn get_active_region_at(&self, addr: u64) -> Option<MemoryRegionInfo> {
+ let mut result: BNMemoryRegionInfo = unsafe { std::mem::zeroed() };
+ let found = unsafe {
+ BNGetActiveMemoryRegionInfoAt(self.view.handle, addr, &mut result)
+ };
+ if !found {
+ return None;
+ }
+ let info = MemoryRegionInfo::from_raw(&result);
+ unsafe { BNFreeMemoryRegionInfo(&mut result) };
+ Some(info)
+ }
+
+ /// Return the resolved range snapshot covering `addr`, or `None` if no
+ /// range covers the address.
+ pub fn get_resolved_range_at(&self, addr: u64) -> Option<ResolvedRange> {
+ let mut result: BNResolvedMemoryRange = unsafe { std::mem::zeroed() };
+ let found = unsafe {
+ BNGetResolvedMemoryRangeAt(self.view.handle, addr, &mut result)
+ };
+ if !found {
+ return None;
+ }
+ let mut regions = Vec::with_capacity(result.regionCount);
+ for j in 0..result.regionCount {
+ let region = unsafe { &*result.regions.add(j) };
+ regions.push(MemoryRegionInfo::from_raw(region));
+ }
+ let resolved = ResolvedRange {
+ start: result.start,
+ length: result.length,
+ regions,
+ };
+ unsafe { BNFreeResolvedMemoryRange(&mut result) };
+ Some(resolved)
+ }
/// JSON string representation of the base [`MemoryMap`], consisting of unresolved auto and user segments.
pub fn base_description(&self) -> String {
@@ -169,6 +366,8 @@ impl MemoryMap {
unsafe { BNRemoveMemoryRegion(self.view.handle, name_raw.as_ptr()) }
}
+ /// Return the name of the active region at `addr`, or an empty string if
+ /// no region covers the address.
pub fn active_memory_region_at(&self, addr: u64) -> String {
unsafe {
let name_raw = BNGetActiveMemoryRegionAt(self.view.handle, addr);
diff --git a/ui/memorymap.h b/ui/memorymap.h
index 2003eb34..56898482 100644
--- a/ui/memorymap.h
+++ b/ui/memorymap.h
@@ -187,6 +187,7 @@ class BINARYNINJAUIAPI SegmentWidget : public QWidget
void addSegment();
void editSegment(SegmentRef segment);
void disableSegment(SegmentRef segment);
+ void enableSegment(const std::string& regionName);
void removeSegment(SegmentRef segment);
public: