diff options
| author | Brian Potchik <brian@vector35.com> | 2026-03-11 12:12:36 -0400 |
|---|---|---|
| committer | Brian Potchik <brian@vector35.com> | 2026-03-11 12:12:36 -0400 |
| commit | 6b57ef1d2c82d263655364588546e6211b0a99a8 (patch) | |
| tree | 83593089f530368b6d83905fc32858f8251904dc | |
| parent | b8fdf800de345f93b2e68713d14bac425a62feb3 (diff) | |
Enhance MemoryMap bindings and add support to re-enable disabled regions in the UI.
| -rw-r--r-- | binaryninjaapi.h | 127 | ||||
| -rw-r--r-- | binaryninjacore.h | 34 | ||||
| -rw-r--r-- | python/binaryview.py | 324 | ||||
| -rw-r--r-- | rust/src/binary_view.rs | 2 | ||||
| -rw-r--r-- | rust/src/binary_view/memory_map.rs | 205 | ||||
| -rw-r--r-- | ui/memorymap.h | 1 |
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 : ®ions.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: |
