Skip to content

Commit f93e1a6

Browse files
committed
Implement function level metadata
1 parent 42886be commit f93e1a6

File tree

6 files changed

+235
-1
lines changed

6 files changed

+235
-1
lines changed

binaryninjaapi.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11458,6 +11458,12 @@ namespace BinaryNinja {
1145811458
void CollapseRegion(uint64_t hash);
1145911459
void ExpandRegion(uint64_t hash);
1146011460
void ExpandAll();
11461+
11462+
void StoreMetadata(const std::string& key, Ref<Metadata> value, bool isAuto = false);
11463+
Ref<Metadata> QueryMetadata(const std::string& key);
11464+
Ref<Metadata> GetMetadata();
11465+
Ref<Metadata> GetAutoMetadata();
11466+
void RemoveMetadata(const std::string& key);
1146111467
};
1146211468

1146311469
/*!

binaryninjacore.h

Lines changed: 8 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 105
40+
#define BN_CURRENT_CORE_ABI_VERSION 106
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
@@ -5011,6 +5011,13 @@ extern "C"
50115011
BINARYNINJACOREAPI void BNFunctionCollapseRegion(BNFunction* func, uint64_t hash);
50125012
BINARYNINJACOREAPI void BNFunctionExpandRegion(BNFunction* func, uint64_t hash);
50135013

5014+
BINARYNINJACOREAPI void BNFunctionStoreMetadata(
5015+
BNFunction* func, const char* key, BNMetadata* value, bool isAuto);
5016+
BINARYNINJACOREAPI BNMetadata* BNFunctionQueryMetadata(BNFunction* func, const char* key);
5017+
BINARYNINJACOREAPI void BNFunctionRemoveMetadata(BNFunction* func, const char* key);
5018+
BINARYNINJACOREAPI BNMetadata* BNFunctionGetMetadata(BNFunction* func);
5019+
BINARYNINJACOREAPI BNMetadata* BNFunctionGetAutoMetadata(BNFunction* func);
5020+
50145021
BINARYNINJACOREAPI void BNSetAutoCallTypeAdjustment(
50155022
BNFunction* func, BNArchitecture* arch, uint64_t addr, BNTypeWithConfidence* type);
50165023
BINARYNINJACOREAPI void BNSetUserCallTypeAdjustment(

function.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3369,6 +3369,42 @@ void Function::ExpandAll()
33693369
BNFunctionExpandAll(m_object);
33703370
}
33713371

3372+
3373+
void Function::StoreMetadata(const std::string& key, Ref<Metadata> value, bool isAuto)
3374+
{
3375+
if (!value)
3376+
return;
3377+
BNFunctionStoreMetadata(m_object, key.c_str(), value->GetObject(), isAuto);
3378+
}
3379+
3380+
3381+
Ref<Metadata> Function::QueryMetadata(const std::string& key)
3382+
{
3383+
BNMetadata* value = BNFunctionQueryMetadata(m_object, key.c_str());
3384+
if (!value)
3385+
return nullptr;
3386+
return new Metadata(value);
3387+
}
3388+
3389+
3390+
Ref<Metadata> Function::GetMetadata()
3391+
{
3392+
return new Metadata(BNFunctionGetMetadata(m_object));
3393+
}
3394+
3395+
3396+
Ref<Metadata> Function::GetAutoMetadata()
3397+
{
3398+
return new Metadata(BNFunctionGetAutoMetadata(m_object));
3399+
}
3400+
3401+
3402+
void Function::RemoveMetadata(const std::string& key)
3403+
{
3404+
BNFunctionRemoveMetadata(m_object, key.c_str());
3405+
}
3406+
3407+
33723408
AdvancedFunctionAnalysisDataRequestor::AdvancedFunctionAnalysisDataRequestor(Function* func) : m_func(func)
33733409
{
33743410
if (m_func)

python/function.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
from . import workflow
4848
from . import languagerepresentation
4949
from . import deprecation
50+
from . import metadata
5051
from . import __version__
5152

5253
# we define the following as such so the linter doesn't confuse 'highlight' the module with the
@@ -3390,6 +3391,68 @@ def is_region_collapsed(self, hash) -> bool:
33903391
"""
33913392
return core.BNFunctionIsRegionCollapsed(self.handle, hash)
33923393

3394+
def store_metadata(self, key: str, md: metadata.MetadataValueType, isAuto: bool = False) -> None:
3395+
"""
3396+
`store_metadata` stores an object for the given key in the current Function. Objects stored using
3397+
`store_metadata` can be retrieved when the database is reopened unless isAuto is set to True.
3398+
3399+
:param str key: key value to associate the Metadata object with
3400+
:param Varies md: object to store
3401+
:param bool isAuto: whether the metadata is an auto metadata
3402+
:rtype: None
3403+
"""
3404+
_md = md
3405+
if not isinstance(_md, metadata.Metadata):
3406+
_md = metadata.Metadata(_md)
3407+
core.BNFunctionStoreMetadata(self.handle, key, _md.handle, isAuto)
3408+
3409+
def query_metadata(self, key: str) -> 'metadata.MetadataValueType':
3410+
"""
3411+
`query_metadata` retrieves metadata associated with the given key stored in the current function.
3412+
3413+
:param str key: key to query
3414+
:rtype: metadata associated with the key
3415+
"""
3416+
md_handle = core.BNFunctionQueryMetadata(self.handle, key)
3417+
if md_handle is None:
3418+
raise KeyError(key)
3419+
return metadata.Metadata(handle=md_handle).value
3420+
3421+
def remove_metadata(self, key: str) -> None:
3422+
"""
3423+
`remove_metadata` removes the metadata associated with key from the current function.
3424+
3425+
:param str key: key associated with metadata to remove from the function
3426+
:rtype: None
3427+
"""
3428+
core.BNFunctionRemoveMetadata(self.handle, key)
3429+
3430+
@property
3431+
def metadata(self) -> Dict[str, 'metadata.MetadataValueType']:
3432+
"""
3433+
`metadata` retrieves the metadata associated with the current function.
3434+
3435+
:rtype: metadata associated with the function
3436+
"""
3437+
md_handle = core.BNFunctionGetMetadata(self.handle)
3438+
assert md_handle is not None, "core.BNFunctionGetMetadata returned None"
3439+
value = metadata.Metadata(handle=md_handle).value
3440+
assert isinstance(value, dict), "core.BNFunctionGetMetadata did not return a dict"
3441+
return value
3442+
3443+
@property
3444+
def auto_metadata(self) -> Dict[str, 'metadata.MetadataValueType']:
3445+
"""
3446+
`metadata` retrieves the metadata associated with the current function.
3447+
3448+
:rtype: metadata associated with the function
3449+
"""
3450+
md_handle = core.BNFunctionGetAutoMetadata(self.handle)
3451+
assert md_handle is not None, "core.BNFunctionGetAutoMetadata returned None"
3452+
value = metadata.Metadata(handle=md_handle).value
3453+
assert isinstance(value, dict), "core.BNFunctionGetAutoMetadata did not return a dict"
3454+
return value
3455+
33933456

33943457
class AdvancedFunctionAnalysisDataRequestor:
33953458
def __init__(self, func: Optional['Function'] = None):

rust/src/function.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use crate::variable::{
4848
StackVariableReference, Variable,
4949
};
5050
use crate::workflow::Workflow;
51+
use crate::metadata::Metadata;
5152
use std::ffi::CStr;
5253
use std::fmt::{Debug, Formatter};
5354
use std::ptr::NonNull;
@@ -2428,6 +2429,58 @@ impl Function {
24282429
assert!(!result.is_null());
24292430
unsafe { Array::new(result, count, ()) }
24302431
}
2432+
2433+
pub fn query_metadata(&self, key: &str) -> Option<Ref<Metadata>> {
2434+
let key = key.to_cstr();
2435+
let value: *mut BNMetadata =
2436+
unsafe { BNFunctionQueryMetadata(self.handle, key.as_ptr()) };
2437+
if value.is_null() {
2438+
None
2439+
} else {
2440+
Some(unsafe { Metadata::ref_from_raw(value) })
2441+
}
2442+
}
2443+
2444+
pub fn get_metadata(&self) -> Option<Ref<Metadata>> {
2445+
let value: *mut BNMetadata =
2446+
unsafe { BNFunctionGetMetadata(self.handle) };
2447+
if value.is_null() {
2448+
None
2449+
} else {
2450+
Some(unsafe { Metadata::ref_from_raw(value) })
2451+
}
2452+
}
2453+
2454+
pub fn get_auto_metadata(&self) -> Option<Ref<Metadata>> {
2455+
let value: *mut BNMetadata =
2456+
unsafe { BNFunctionGetAutoMetadata(self.handle) };
2457+
if value.is_null() {
2458+
None
2459+
} else {
2460+
Some(unsafe { Metadata::ref_from_raw(value) })
2461+
}
2462+
}
2463+
2464+
pub fn store_metadata<V>(&self, key: &str, value: V, is_auto: bool)
2465+
where
2466+
V: Into<Ref<Metadata>>,
2467+
{
2468+
let md = value.into();
2469+
let key = key.to_cstr();
2470+
unsafe {
2471+
BNFunctionStoreMetadata(
2472+
self.handle,
2473+
key.as_ptr(),
2474+
md.as_ref().handle,
2475+
is_auto,
2476+
)
2477+
};
2478+
}
2479+
2480+
pub fn remove_metadata(&self, key: &str) {
2481+
let key = key.to_cstr();
2482+
unsafe { BNFunctionRemoveMetadata(self.handle, key.as_ptr()) };
2483+
}
24312484
}
24322485

24332486
impl Debug for Function {

rust/tests/function.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use binaryninja::binary_view::BinaryViewExt;
2+
use binaryninja::headless::Session;
3+
use binaryninja::metadata::Metadata;
4+
use binaryninja::rc::Ref;
5+
use std::path::PathBuf;
6+
7+
#[test]
8+
fn store_and_query_function_metadata() {
9+
let _session = Session::new().expect("Failed to initialize session");
10+
let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
11+
let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
12+
let func = view
13+
.entry_point_function()
14+
.expect("Failed to get entry point function");
15+
16+
// Store key/value pairs to user and auto metadata
17+
func.store_metadata("one", "one", false);
18+
func.store_metadata("two", 2 as u64, true);
19+
func.store_metadata("three", "three", true);
20+
func.remove_metadata("three");
21+
22+
// Assert that we can query from both user and auto metadata
23+
assert_eq!(
24+
func.query_metadata("one")
25+
.expect("Failed to query key \"one\"")
26+
.get_string()
27+
.unwrap()
28+
.to_string_lossy(),
29+
"one"
30+
);
31+
assert_eq!(
32+
func.query_metadata("two")
33+
.expect("Failed to query key \"two\"")
34+
.get_unsigned_integer()
35+
.unwrap(),
36+
2
37+
);
38+
assert!(
39+
func.query_metadata("three") == None,
40+
"Query for key \"three\" returned a value"
41+
);
42+
43+
// Assert that user metadata only includes key/values from user data (not auto) and vice-versa
44+
let user_metadata = func.get_metadata().expect("Failed to query user metadata");
45+
assert_eq!(
46+
user_metadata
47+
.get("one")
48+
.expect("Failed to query key \"one\" from user metadata")
49+
.expect("User metadata ref is None")
50+
.get_string()
51+
.unwrap()
52+
.to_string_lossy(),
53+
"one"
54+
);
55+
assert_eq!(user_metadata.get("two"), Ok(None));
56+
let auto_metadata = func
57+
.get_auto_metadata()
58+
.expect("Failed to query auto metadata");
59+
assert_eq!(
60+
auto_metadata
61+
.get("two")
62+
.expect("Failed to query key \"two\" from auto metadata")
63+
.expect("Auto metadata ref is None")
64+
.get_unsigned_integer()
65+
.unwrap(),
66+
2
67+
);
68+
assert_eq!(auto_metadata.get("one"), Ok(None));
69+
}

0 commit comments

Comments
 (0)