Skip to content

Commit 56ce6d3

Browse files
committed
Unify SettingsCache and MemoryMap access under immutable snapshots for consistent, lock-free AnalysisContext queries.
1 parent 5880769 commit 56ce6d3

File tree

7 files changed

+659
-4
lines changed

7 files changed

+659
-4
lines changed

binaryninjaapi.h

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8129,6 +8129,22 @@ namespace BinaryNinja {
81298129
std::optional<std::pair<std::string, BNStringType>> StringifyUnicodeData(Architecture* arch, const DataBuffer& buffer, bool nullTerminates = true, bool allowShortStrings = false);
81308130
};
81318131

8132+
/*! MemoryMap provides access to the system-level memory map describing how a BinaryView is loaded into memory.
8133+
8134+
\note Architecture: This API-side MemoryMap class is a proxy that accesses the BinaryView's current
8135+
MemoryMap state through the core API. The proxy provides a simple mutable interface: when you call
8136+
modification operations (AddMemoryRegion, RemoveMemoryRegion, etc.), the proxy automatically accesses
8137+
the updated MemoryMap. Internally, the core uses immutable copy-on-write data structures, but the proxy
8138+
abstracts this away.
8139+
8140+
When you access a BinaryView's MemoryMap, you always see the current state. For lock-free access during
8141+
analysis, AnalysisContext provides memory layout query methods (IsValidOffset, IsOffsetReadable, GetStart,
8142+
GetLength, etc.) that operate on an immutable snapshot of the MemoryMap cached when the analysis was initiated.
8143+
8144+
A MemoryMap can contain multiple, arbitrarily overlapping memory regions. When modified, address space
8145+
segmentation is automatically managed. If multiple regions overlap, the most recently added region takes
8146+
precedence by default.
8147+
*/
81328148
class MemoryMap
81338149
{
81348150
BNBinaryView* m_object;
@@ -8142,6 +8158,22 @@ namespace BinaryNinja {
81428158
BNSetLogicalMemoryMapEnabled(m_object, enabled);
81438159
}
81448160

8161+
/*! Returns true if this MemoryMap represents a parsed BinaryView with real segments.
8162+
8163+
This is determined by whether the BinaryView has a parent view - parsed views
8164+
(ELF, PE, Mach-O, etc.) have a parent Raw view, while Raw views have no parent.
8165+
8166+
Returns true for parsed BinaryViews (ELF, PE, Mach-O, etc.) with segments from
8167+
binary format parsing. Returns false for Raw BinaryViews (flat file view with
8168+
synthetic MemoryMap) or views that failed to parse segments.
8169+
8170+
Use this to gate features that require parsed binary structure (sections, imports,
8171+
relocations, etc.). For basic analysis queries (GetStart, IsOffsetReadable,
8172+
GetLength, etc.), use the MemoryMap directly regardless of activation state - all
8173+
BinaryViews have a usable MemoryMap.
8174+
8175+
\return True if this is an activated (parsed) memory map, false otherwise
8176+
*/
81458177
bool IsActivated()
81468178
{
81478179
return BNIsMemoryMapActivated(m_object);
@@ -11420,8 +11452,52 @@ namespace BinaryNinja {
1142011452
request.Accept(writer);
1142111453
return Inform(buffer.GetString());
1142211454
}
11455+
11456+
// Settings cache access - lock-free access to cached settings
11457+
/*! Get a setting value from the cached settings
11458+
11459+
\code{.cpp}
11460+
bool enabled = analysisContext->GetSetting<bool>("analysis.conservative");
11461+
\endcode
11462+
11463+
\tparam T type for the value you are retrieving
11464+
\param key Key for the setting
11465+
\return Value for the setting, with type T
11466+
*/
11467+
template <typename T>
11468+
T GetSetting(const std::string& key);
11469+
11470+
// Memory map access - lock-free access to cached MemoryMap
11471+
bool IsValidOffset(uint64_t offset);
11472+
bool IsOffsetReadable(uint64_t offset);
11473+
bool IsOffsetWritable(uint64_t offset);
11474+
bool IsOffsetExecutable(uint64_t offset);
11475+
bool IsOffsetBackedByFile(uint64_t offset);
11476+
uint64_t GetStart();
11477+
uint64_t GetEnd();
11478+
uint64_t GetLength();
11479+
uint64_t GetNextValidOffset(uint64_t offset);
11480+
uint64_t GetNextMappedAddress(uint64_t addr, uint32_t flags = 0);
11481+
uint64_t GetNextBackedAddress(uint64_t addr, uint32_t flags = 0);
11482+
Ref<Segment> GetSegmentAt(uint64_t addr);
11483+
std::vector<BNAddressRange> GetMappedAddressRanges();
11484+
std::vector<BNAddressRange> GetBackedAddressRanges();
1142311485
};
1142411486

11487+
// Explicit template specialization declarations for AnalysisContext::GetSetting<T>
11488+
template <>
11489+
bool AnalysisContext::GetSetting<bool>(const std::string& key);
11490+
template <>
11491+
double AnalysisContext::GetSetting<double>(const std::string& key);
11492+
template <>
11493+
int64_t AnalysisContext::GetSetting<int64_t>(const std::string& key);
11494+
template <>
11495+
uint64_t AnalysisContext::GetSetting<uint64_t>(const std::string& key);
11496+
template <>
11497+
std::string AnalysisContext::GetSetting<std::string>(const std::string& key);
11498+
template <>
11499+
std::vector<std::string> AnalysisContext::GetSetting<std::vector<std::string>>(const std::string& key);
11500+
1142511501
/*!
1142611502
\ingroup workflow
1142711503
*/

binaryninjacore.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
// Current ABI version for linking to the core. This is incremented any time
3838
// there are changes to the API that affect linking, including new functions,
3939
// new types, or modifications to existing functions or types.
40-
#define BN_CURRENT_CORE_ABI_VERSION 147
40+
#define BN_CURRENT_CORE_ABI_VERSION 148
4141

4242
// Minimum ABI version that is supported for loading of plugins. Plugins that
4343
// are linked to an ABI version less than this will not be able to load and
@@ -5930,6 +5930,30 @@ extern "C"
59305930
BNAnalysisContext* analysisContext, BNHighLevelILFunction* highLevelIL);
59315931
BINARYNINJACOREAPI bool BNAnalysisContextInform(BNAnalysisContext* analysisContext, const char* request);
59325932

5933+
// Settings cache access
5934+
BINARYNINJACOREAPI bool BNAnalysisContextGetSettingBool(BNAnalysisContext* analysisContext, const char* key);
5935+
BINARYNINJACOREAPI double BNAnalysisContextGetSettingDouble(BNAnalysisContext* analysisContext, const char* key);
5936+
BINARYNINJACOREAPI int64_t BNAnalysisContextGetSettingInt64(BNAnalysisContext* analysisContext, const char* key);
5937+
BINARYNINJACOREAPI uint64_t BNAnalysisContextGetSettingUInt64(BNAnalysisContext* analysisContext, const char* key);
5938+
BINARYNINJACOREAPI char* BNAnalysisContextGetSettingString(BNAnalysisContext* analysisContext, const char* key);
5939+
BINARYNINJACOREAPI char** BNAnalysisContextGetSettingStringList(BNAnalysisContext* analysisContext, const char* key, size_t* count);
5940+
5941+
// Memory map access
5942+
BINARYNINJACOREAPI bool BNAnalysisContextIsValidOffset(BNAnalysisContext* analysisContext, uint64_t offset);
5943+
BINARYNINJACOREAPI bool BNAnalysisContextIsOffsetReadable(BNAnalysisContext* analysisContext, uint64_t offset);
5944+
BINARYNINJACOREAPI bool BNAnalysisContextIsOffsetWritable(BNAnalysisContext* analysisContext, uint64_t offset);
5945+
BINARYNINJACOREAPI bool BNAnalysisContextIsOffsetExecutable(BNAnalysisContext* analysisContext, uint64_t offset);
5946+
BINARYNINJACOREAPI bool BNAnalysisContextIsOffsetBackedByFile(BNAnalysisContext* analysisContext, uint64_t offset);
5947+
BINARYNINJACOREAPI uint64_t BNAnalysisContextGetStart(BNAnalysisContext* analysisContext);
5948+
BINARYNINJACOREAPI uint64_t BNAnalysisContextGetEnd(BNAnalysisContext* analysisContext);
5949+
BINARYNINJACOREAPI uint64_t BNAnalysisContextGetLength(BNAnalysisContext* analysisContext);
5950+
BINARYNINJACOREAPI uint64_t BNAnalysisContextGetNextValidOffset(BNAnalysisContext* analysisContext, uint64_t offset);
5951+
BINARYNINJACOREAPI uint64_t BNAnalysisContextGetNextMappedAddress(BNAnalysisContext* analysisContext, uint64_t addr, uint32_t flags);
5952+
BINARYNINJACOREAPI uint64_t BNAnalysisContextGetNextBackedAddress(BNAnalysisContext* analysisContext, uint64_t addr, uint32_t flags);
5953+
BINARYNINJACOREAPI BNSegment* BNAnalysisContextGetSegmentAt(BNAnalysisContext* analysisContext, uint64_t addr);
5954+
BINARYNINJACOREAPI BNAddressRange* BNAnalysisContextGetMappedAddressRanges(BNAnalysisContext* analysisContext, size_t* count);
5955+
BINARYNINJACOREAPI BNAddressRange* BNAnalysisContextGetBackedAddressRanges(BNAnalysisContext* analysisContext, size_t* count);
5956+
59335957
// Activity
59345958
BINARYNINJACOREAPI BNActivity* BNCreateActivity(const char* configuration, void* ctxt, void (*action)(void*, BNAnalysisContext*));
59355959
BINARYNINJACOREAPI BNActivity* BNCreateActivityWithEligibility(const char* configuration, void* ctxt, void (*action)(void*, BNAnalysisContext*), bool (*eligibilityHandler)(void*, BNActivity*, BNAnalysisContext*));

python/binaryview.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2457,8 +2457,19 @@ def __iter__(self):
24572457

24582458
class MemoryMap:
24592459
r"""
2460-
The MemoryMap object describes a system-level memory map into which a BinaryView is loaded. Each BinaryView
2461-
exposes its portion of the MemoryMap through the Segments defined within that view.
2460+
The MemoryMap object provides access to the system-level memory map describing how a BinaryView is loaded
2461+
into memory. Each BinaryView exposes its portion of the MemoryMap through the Segments defined within that view.
2462+
2463+
**Architecture Note:** This Python MemoryMap object is a proxy that accesses the BinaryView's current
2464+
MemoryMap state through the FFI boundary. The proxy provides a simple mutable interface: when you call
2465+
modification operations (``add_memory_region``, ``remove_memory_region``, etc.), the proxy automatically
2466+
accesses the updated MemoryMap. Internally, the core uses immutable copy-on-write data structures, but
2467+
the proxy abstracts this away.
2468+
2469+
When you access ``view.memory_map``, you always see the current state. For lock-free access during analysis,
2470+
AnalysisContext provides memory layout query methods (``is_valid_offset()``, ``is_offset_readable()``,
2471+
``get_start()``, ``get_length()``, etc.) that operate on an immutable snapshot of the MemoryMap cached when
2472+
the analysis was initiated.
24622473
24632474
A MemoryMap can contain multiple, arbitrarily overlapping memory regions. When modified, address space
24642475
segmentation is automatically managed. If multiple regions overlap, the most recently added region takes
@@ -2615,7 +2626,24 @@ def set_logical_memory_map_enabled(self, enabled: bool) -> None:
26152626

26162627
@property
26172628
def is_activated(self):
2618-
"""Whether the memory map is activated for the associated view."""
2629+
"""
2630+
Whether the memory map is activated for the associated view.
2631+
2632+
Returns ``True`` if this MemoryMap represents a parsed BinaryView with real segments
2633+
(ELF, PE, Mach-O, etc.). Returns ``False`` for Raw BinaryViews or views that failed
2634+
to parse segments.
2635+
2636+
This is determined by whether the BinaryView has a parent view - parsed views have a
2637+
parent Raw view, while Raw views have no parent.
2638+
2639+
Use this to gate features that require parsed binary structure (sections, imports,
2640+
relocations, etc.). For basic analysis queries (start, length, is_offset_readable, etc.),
2641+
use the MemoryMap directly regardless of activation state - all BinaryViews have a
2642+
usable MemoryMap.
2643+
2644+
:return: True if this is an activated (parsed) memory map, False otherwise
2645+
:rtype: bool
2646+
"""
26192647
return core.BNIsMemoryMapActivated(self.handle)
26202648

26212649
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:

python/workflow.py

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,204 @@ def basic_blocks(self, value: '_function.BasicBlockList') -> None:
188188
def inform(self, request: str) -> bool:
189189
return core.BNAnalysisContextInform(self.handle, request)
190190

191+
def get_setting_bool(self, key: str) -> bool:
192+
"""
193+
Get a boolean setting from the cached settings.
194+
195+
:param key: Setting key
196+
:return: Boolean setting value
197+
"""
198+
return core.BNAnalysisContextGetSettingBool(self.handle, key)
199+
200+
def get_setting_double(self, key: str) -> float:
201+
"""
202+
Get a double setting from the cached settings.
203+
204+
:param key: Setting key
205+
:return: Double setting value
206+
"""
207+
return core.BNAnalysisContextGetSettingDouble(self.handle, key)
208+
209+
def get_setting_int64(self, key: str) -> int:
210+
"""
211+
Get a 64-bit signed integer setting from the cached settings.
212+
213+
:param key: Setting key
214+
:return: Int64 setting value
215+
"""
216+
return core.BNAnalysisContextGetSettingInt64(self.handle, key)
217+
218+
def get_setting_uint64(self, key: str) -> int:
219+
"""
220+
Get a 64-bit unsigned integer setting from the cached settings.
221+
222+
:param key: Setting key
223+
:return: UInt64 setting value
224+
"""
225+
return core.BNAnalysisContextGetSettingUInt64(self.handle, key)
226+
227+
def get_setting_string(self, key: str) -> str:
228+
"""
229+
Get a string setting from the cached settings.
230+
231+
:param key: Setting key
232+
:return: String setting value
233+
"""
234+
return core.BNAnalysisContextGetSettingString(self.handle, key)
235+
236+
def get_setting_string_list(self, key: str) -> List[str]:
237+
"""
238+
Get a string list setting from the cached settings.
239+
240+
:param key: Setting key
241+
:return: List of strings
242+
"""
243+
count = ctypes.c_size_t()
244+
result = core.BNAnalysisContextGetSettingStringList(self.handle, key, ctypes.byref(count))
245+
out_list = []
246+
for i in range(count.value):
247+
out_list.append(result[i].decode('utf-8'))
248+
core.BNFreeStringList(result, count.value)
249+
return out_list
250+
251+
def is_valid_offset(self, offset: int) -> bool:
252+
"""
253+
Check if an offset is mapped in the cached memory map.
254+
255+
:param offset: Offset to check
256+
:return: True if offset is mapped
257+
"""
258+
return core.BNAnalysisContextIsValidOffset(self.handle, offset)
259+
260+
def is_offset_readable(self, offset: int) -> bool:
261+
"""
262+
Check if an offset is readable in the cached memory map.
263+
264+
:param offset: Offset to check
265+
:return: True if offset is readable
266+
"""
267+
return core.BNAnalysisContextIsOffsetReadable(self.handle, offset)
268+
269+
def is_offset_writable(self, offset: int) -> bool:
270+
"""
271+
Check if an offset is writable in the cached memory map.
272+
273+
:param offset: Offset to check
274+
:return: True if offset is writable
275+
"""
276+
return core.BNAnalysisContextIsOffsetWritable(self.handle, offset)
277+
278+
def is_offset_executable(self, offset: int) -> bool:
279+
"""
280+
Check if an offset is executable in the cached memory map.
281+
282+
:param offset: Offset to check
283+
:return: True if offset is executable
284+
"""
285+
return core.BNAnalysisContextIsOffsetExecutable(self.handle, offset)
286+
287+
def is_offset_backed_by_file(self, offset: int) -> bool:
288+
"""
289+
Check if an offset is backed by the file in the cached memory map.
290+
291+
:param offset: Offset to check
292+
:return: True if offset is backed by file
293+
"""
294+
return core.BNAnalysisContextIsOffsetBackedByFile(self.handle, offset)
295+
296+
def get_start(self) -> int:
297+
"""
298+
Get the start address from the cached memory map.
299+
300+
:return: Start address
301+
"""
302+
return core.BNAnalysisContextGetStart(self.handle)
303+
304+
def get_end(self) -> int:
305+
"""
306+
Get the end address from the cached memory map.
307+
308+
:return: End address
309+
"""
310+
return core.BNAnalysisContextGetEnd(self.handle)
311+
312+
def get_length(self) -> int:
313+
"""
314+
Get the length of the cached memory map.
315+
316+
:return: Length
317+
"""
318+
return core.BNAnalysisContextGetLength(self.handle)
319+
320+
def get_next_valid_offset(self, offset: int) -> int:
321+
"""
322+
Get the next valid offset after the given offset from the cached memory map.
323+
324+
:param offset: Starting offset
325+
:return: Next valid offset
326+
"""
327+
return core.BNAnalysisContextGetNextValidOffset(self.handle, offset)
328+
329+
def get_next_mapped_address(self, addr: int, flags: int = 0) -> int:
330+
"""
331+
Get the next mapped address after the given address from the cached memory map.
332+
333+
:param addr: Starting address
334+
:param flags: Optional flags to filter by
335+
:return: Next mapped address
336+
"""
337+
return core.BNAnalysisContextGetNextMappedAddress(self.handle, addr, flags)
338+
339+
def get_next_backed_address(self, addr: int, flags: int = 0) -> int:
340+
"""
341+
Get the next backed address after the given address from the cached memory map.
342+
343+
:param addr: Starting address
344+
:param flags: Optional flags to filter by
345+
:return: Next backed address
346+
"""
347+
return core.BNAnalysisContextGetNextBackedAddress(self.handle, addr, flags)
348+
349+
def get_segment_at(self, addr: int) -> Optional['binaryview.Segment']:
350+
"""
351+
Get the segment containing the given address from the cached memory map.
352+
353+
:param addr: Address to query
354+
:return: Segment containing the address, or None
355+
"""
356+
segment = core.BNAnalysisContextGetSegmentAt(self.handle, addr)
357+
if not segment:
358+
return None
359+
return binaryview.Segment(segment)
360+
361+
def get_mapped_address_ranges(self) -> List[tuple]:
362+
"""
363+
Get all mapped address ranges from the cached memory map.
364+
365+
:return: List of (start, end) tuples
366+
"""
367+
count = ctypes.c_size_t()
368+
ranges = core.BNAnalysisContextGetMappedAddressRanges(self.handle, ctypes.byref(count))
369+
result = []
370+
for i in range(count.value):
371+
result.append((ranges[i].start, ranges[i].end))
372+
core.BNFreeAddressRanges(ranges)
373+
return result
374+
375+
def get_backed_address_ranges(self) -> List[tuple]:
376+
"""
377+
Get all backed address ranges from the cached memory map.
378+
379+
:return: List of (start, end) tuples
380+
"""
381+
count = ctypes.c_size_t()
382+
ranges = core.BNAnalysisContextGetBackedAddressRanges(self.handle, ctypes.byref(count))
383+
result = []
384+
for i in range(count.value):
385+
result.append((ranges[i].start, ranges[i].end))
386+
core.BNFreeAddressRanges(ranges)
387+
return result
388+
191389

192390
class Activity(object):
193391
"""

0 commit comments

Comments
 (0)