diff options
| author | Brian Potchik <brian@vector35.com> | 2025-11-23 16:18:34 -0500 |
|---|---|---|
| committer | Brian Potchik <brian@vector35.com> | 2025-11-23 16:18:34 -0500 |
| commit | 56ce6d3d63d6a5cf30ea1f326a91104db59555be (patch) | |
| tree | 175fd417c6084a40823fb7aa5d38ffd7e7406308 /python | |
| parent | 5880769cbac1362aad46a34faec4c553fa9fcac6 (diff) | |
Unify SettingsCache and MemoryMap access under immutable snapshots for consistent, lock-free AnalysisContext queries.
Diffstat (limited to 'python')
| -rw-r--r-- | python/binaryview.py | 34 | ||||
| -rw-r--r-- | python/workflow.py | 198 |
2 files changed, 229 insertions, 3 deletions
diff --git a/python/binaryview.py b/python/binaryview.py index d0d20405..4170302b 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -2457,8 +2457,19 @@ class AdvancedILFunctionList: class MemoryMap: r""" - The MemoryMap object describes a system-level memory map into which a BinaryView is loaded. Each BinaryView - exposes its portion of the MemoryMap through the Segments defined within that view. + 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. + + **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. + + 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. 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 @@ -2615,7 +2626,24 @@ class MemoryMap: @property def is_activated(self): - """Whether the memory map is activated for the associated view.""" + """ + Whether the memory map is activated for the associated view. + + Returns ``True`` if this MemoryMap represents a parsed BinaryView with real segments + (ELF, PE, Mach-O, etc.). Returns ``False`` for Raw BinaryViews or views that failed + to parse segments. + + This is determined by whether the BinaryView has a parent view - parsed views have a + parent Raw view, while Raw views have no parent. + + Use this to gate features that require parsed binary structure (sections, imports, + relocations, etc.). For basic analysis queries (start, length, is_offset_readable, etc.), + use the MemoryMap directly regardless of activation state - all BinaryViews have a + usable MemoryMap. + + :return: True if this is an activated (parsed) memory map, False otherwise + :rtype: bool + """ return core.BNIsMemoryMapActivated(self.handle) def add_memory_region(self, name: str, start: int, source: Optional[Union['os.PathLike', str, bytes, bytearray, 'BinaryView', 'databuffer.DataBuffer', 'fileaccessor.FileAccessor']] = None, flags: SegmentFlag = 0, fill: int = 0, length: Optional[int] = None) -> bool: diff --git a/python/workflow.py b/python/workflow.py index 6f3b2dd8..ed6bc876 100644 --- a/python/workflow.py +++ b/python/workflow.py @@ -188,6 +188,204 @@ class AnalysisContext: def inform(self, request: str) -> bool: return core.BNAnalysisContextInform(self.handle, request) + def get_setting_bool(self, key: str) -> bool: + """ + Get a boolean setting from the cached settings. + + :param key: Setting key + :return: Boolean setting value + """ + return core.BNAnalysisContextGetSettingBool(self.handle, key) + + def get_setting_double(self, key: str) -> float: + """ + Get a double setting from the cached settings. + + :param key: Setting key + :return: Double setting value + """ + return core.BNAnalysisContextGetSettingDouble(self.handle, key) + + def get_setting_int64(self, key: str) -> int: + """ + Get a 64-bit signed integer setting from the cached settings. + + :param key: Setting key + :return: Int64 setting value + """ + return core.BNAnalysisContextGetSettingInt64(self.handle, key) + + def get_setting_uint64(self, key: str) -> int: + """ + Get a 64-bit unsigned integer setting from the cached settings. + + :param key: Setting key + :return: UInt64 setting value + """ + return core.BNAnalysisContextGetSettingUInt64(self.handle, key) + + def get_setting_string(self, key: str) -> str: + """ + Get a string setting from the cached settings. + + :param key: Setting key + :return: String setting value + """ + return core.BNAnalysisContextGetSettingString(self.handle, key) + + def get_setting_string_list(self, key: str) -> List[str]: + """ + Get a string list setting from the cached settings. + + :param key: Setting key + :return: List of strings + """ + count = ctypes.c_size_t() + result = core.BNAnalysisContextGetSettingStringList(self.handle, key, ctypes.byref(count)) + out_list = [] + for i in range(count.value): + out_list.append(result[i].decode('utf-8')) + core.BNFreeStringList(result, count.value) + return out_list + + def is_valid_offset(self, offset: int) -> bool: + """ + Check if an offset is mapped in the cached memory map. + + :param offset: Offset to check + :return: True if offset is mapped + """ + return core.BNAnalysisContextIsValidOffset(self.handle, offset) + + def is_offset_readable(self, offset: int) -> bool: + """ + Check if an offset is readable in the cached memory map. + + :param offset: Offset to check + :return: True if offset is readable + """ + return core.BNAnalysisContextIsOffsetReadable(self.handle, offset) + + def is_offset_writable(self, offset: int) -> bool: + """ + Check if an offset is writable in the cached memory map. + + :param offset: Offset to check + :return: True if offset is writable + """ + return core.BNAnalysisContextIsOffsetWritable(self.handle, offset) + + def is_offset_executable(self, offset: int) -> bool: + """ + Check if an offset is executable in the cached memory map. + + :param offset: Offset to check + :return: True if offset is executable + """ + return core.BNAnalysisContextIsOffsetExecutable(self.handle, offset) + + def is_offset_backed_by_file(self, offset: int) -> bool: + """ + Check if an offset is backed by the file in the cached memory map. + + :param offset: Offset to check + :return: True if offset is backed by file + """ + return core.BNAnalysisContextIsOffsetBackedByFile(self.handle, offset) + + def get_start(self) -> int: + """ + Get the start address from the cached memory map. + + :return: Start address + """ + return core.BNAnalysisContextGetStart(self.handle) + + def get_end(self) -> int: + """ + Get the end address from the cached memory map. + + :return: End address + """ + return core.BNAnalysisContextGetEnd(self.handle) + + def get_length(self) -> int: + """ + Get the length of the cached memory map. + + :return: Length + """ + return core.BNAnalysisContextGetLength(self.handle) + + def get_next_valid_offset(self, offset: int) -> int: + """ + Get the next valid offset after the given offset from the cached memory map. + + :param offset: Starting offset + :return: Next valid offset + """ + return core.BNAnalysisContextGetNextValidOffset(self.handle, offset) + + def get_next_mapped_address(self, addr: int, flags: int = 0) -> int: + """ + Get the next mapped address after the given address from the cached memory map. + + :param addr: Starting address + :param flags: Optional flags to filter by + :return: Next mapped address + """ + return core.BNAnalysisContextGetNextMappedAddress(self.handle, addr, flags) + + def get_next_backed_address(self, addr: int, flags: int = 0) -> int: + """ + Get the next backed address after the given address from the cached memory map. + + :param addr: Starting address + :param flags: Optional flags to filter by + :return: Next backed address + """ + return core.BNAnalysisContextGetNextBackedAddress(self.handle, addr, flags) + + def get_segment_at(self, addr: int) -> Optional['binaryview.Segment']: + """ + Get the segment containing the given address from the cached memory map. + + :param addr: Address to query + :return: Segment containing the address, or None + """ + segment = core.BNAnalysisContextGetSegmentAt(self.handle, addr) + if not segment: + return None + return binaryview.Segment(segment) + + def get_mapped_address_ranges(self) -> List[tuple]: + """ + Get all mapped address ranges from the cached memory map. + + :return: List of (start, end) tuples + """ + count = ctypes.c_size_t() + ranges = core.BNAnalysisContextGetMappedAddressRanges(self.handle, ctypes.byref(count)) + result = [] + for i in range(count.value): + result.append((ranges[i].start, ranges[i].end)) + core.BNFreeAddressRanges(ranges) + return result + + def get_backed_address_ranges(self) -> List[tuple]: + """ + Get all backed address ranges from the cached memory map. + + :return: List of (start, end) tuples + """ + count = ctypes.c_size_t() + ranges = core.BNAnalysisContextGetBackedAddressRanges(self.handle, ctypes.byref(count)) + result = [] + for i in range(count.value): + result.append((ranges[i].start, ranges[i].end)) + core.BNFreeAddressRanges(ranges) + return result + class Activity(object): """ |
