summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorBrian Potchik <brian@vector35.com>2025-11-23 16:18:34 -0500
committerBrian Potchik <brian@vector35.com>2025-11-23 16:18:34 -0500
commit56ce6d3d63d6a5cf30ea1f326a91104db59555be (patch)
tree175fd417c6084a40823fb7aa5d38ffd7e7406308 /python
parent5880769cbac1362aad46a34faec4c553fa9fcac6 (diff)
Unify SettingsCache and MemoryMap access under immutable snapshots for consistent, lock-free AnalysisContext queries.
Diffstat (limited to 'python')
-rw-r--r--python/binaryview.py34
-rw-r--r--python/workflow.py198
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):
"""