Skip to content

Commit 0320807

Browse files
committed
[Rust] Finish out the PossibleValueSet implementation to include range and lists of values
This requires us to allocate on the rust side of the API, this is quite easy to mess up but the usage of it is limited.
1 parent 68b00cd commit 0320807

File tree

4 files changed

+155
-45
lines changed

4 files changed

+155
-45
lines changed

rust/src/medium_level_il/function.rs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ impl MediumLevelILFunction {
183183
/// Allows the user to specify a PossibleValueSet value for an MLIL
184184
/// variable at its definition site.
185185
///
186-
/// .. warning:: Setting the variable value, triggers a reanalysis of the
186+
/// WARNING: Setting the variable value, triggers a reanalysis of the
187187
/// function and allows the dataflow to compute and propagate values which
188188
/// depend on the current variable. This implies that branch conditions
189189
/// whose values can be determined statically will be computed, leading to
@@ -212,23 +212,15 @@ impl MediumLevelILFunction {
212212
value: PossibleValueSet,
213213
after: bool,
214214
) -> Result<(), ()> {
215-
let Some(_def_site) = self
216-
.var_definitions(var)
217-
.iter()
218-
.find(|def| def.address == addr)
219-
else {
220-
// Error "No definition for Variable found at given address"
221-
return Err(());
222-
};
223215
let function = self.function();
224216
let def_site = BNArchitectureAndAddress {
225217
arch: function.arch().handle,
226218
address: addr,
227219
};
228220
let raw_var = BNVariable::from(var);
229-
let raw_value = PossibleValueSet::into_raw(value);
221+
let raw_value = PossibleValueSet::into_rust_raw(value);
230222
unsafe { BNSetUserVariableValue(function.handle, &raw_var, &def_site, after, &raw_value) }
231-
PossibleValueSet::free_owned_raw(raw_value);
223+
PossibleValueSet::free_rust_raw(raw_value);
232224
Ok(())
233225
}
234226

rust/src/medium_level_il/instruction.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,7 +1167,7 @@ impl MediumLevelILInstruction {
11671167
options.len(),
11681168
)
11691169
};
1170-
PossibleValueSet::from_owned_raw(value)
1170+
PossibleValueSet::from_owned_core_raw(value)
11711171
}
11721172

11731173
pub fn possible_ssa_variable_values(&self, ssa_var: &SSAVariable) -> PossibleValueSet {
@@ -1190,7 +1190,7 @@ impl MediumLevelILInstruction {
11901190
options.len(),
11911191
)
11921192
};
1193-
PossibleValueSet::from_owned_raw(value)
1193+
PossibleValueSet::from_owned_core_raw(value)
11941194
}
11951195

11961196
/// Return the ssa version of a [`Variable`] at the given instruction.
@@ -1390,7 +1390,7 @@ impl MediumLevelILInstruction {
13901390
options.len(),
13911391
)
13921392
};
1393-
PossibleValueSet::from_owned_raw(value)
1393+
PossibleValueSet::from_owned_core_raw(value)
13941394
}
13951395

13961396
pub fn possible_register_values_after(&self, reg_id: RegisterId) -> PossibleValueSet {
@@ -1411,7 +1411,7 @@ impl MediumLevelILInstruction {
14111411
options.len(),
14121412
)
14131413
};
1414-
PossibleValueSet::from_owned_raw(value)
1414+
PossibleValueSet::from_owned_core_raw(value)
14151415
}
14161416

14171417
pub fn flag_value(&self, flag_id: FlagId) -> RegisterValue {
@@ -1454,7 +1454,7 @@ impl MediumLevelILInstruction {
14541454
options.len(),
14551455
)
14561456
};
1457-
PossibleValueSet::from_owned_raw(value)
1457+
PossibleValueSet::from_owned_core_raw(value)
14581458
}
14591459

14601460
pub fn possible_flag_values_after_with_opts(
@@ -1471,7 +1471,7 @@ impl MediumLevelILInstruction {
14711471
options.len(),
14721472
)
14731473
};
1474-
PossibleValueSet::from_owned_raw(value)
1474+
PossibleValueSet::from_owned_core_raw(value)
14751475
}
14761476

14771477
pub fn stack_contents(&self, offset: i64, size: usize) -> RegisterValue {
@@ -1514,7 +1514,7 @@ impl MediumLevelILInstruction {
15141514
options.len(),
15151515
)
15161516
};
1517-
PossibleValueSet::from_owned_raw(value)
1517+
PossibleValueSet::from_owned_core_raw(value)
15181518
}
15191519

15201520
pub fn possible_stack_contents_after_with_opts(
@@ -1533,7 +1533,7 @@ impl MediumLevelILInstruction {
15331533
options.len(),
15341534
)
15351535
};
1536-
PossibleValueSet::from_owned_raw(value)
1536+
PossibleValueSet::from_owned_core_raw(value)
15371537
}
15381538

15391539
/// Gets the unique variable for a definition instruction. This unique variable can be passed

rust/src/variable.rs

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use binaryninjacore_sys::{
1212
BNFreeVariableList, BNFreeVariableNameAndTypeList, BNFromVariableIdentifier,
1313
BNIndirectBranchInfo, BNLookupTableEntry, BNMergedVariable, BNPossibleValueSet,
1414
BNRegisterValue, BNRegisterValueType, BNStackVariableReference, BNToVariableIdentifier,
15-
BNUserVariableValue, BNValueRange, BNVariable, BNVariableNameAndType, BNVariableSourceType,
15+
BNTypeWithConfidence, BNUserVariableValue, BNValueRange, BNVariable, BNVariableNameAndType,
16+
BNVariableSourceType,
1617
};
1718
use std::collections::HashSet;
1819

@@ -238,7 +239,9 @@ impl UserVariableValue {
238239
var: value.variable.into(),
239240
defSite: value.def_site.into(),
240241
after: value.after,
241-
value: PossibleValueSet::into_raw(value.value),
242+
// TODO: This returns a rust allocated value, we should at some point provide allocators for the
243+
// TODO: internal state of BNPossibleValueSet, so we can store rust created object in core objects.
244+
value: PossibleValueSet::into_rust_raw(value.value),
242245
}
243246
}
244247
}
@@ -590,6 +593,28 @@ impl LookupTableEntry {
590593
to: value.toValue,
591594
}
592595
}
596+
597+
pub(crate) fn from_owned_raw(value: BNLookupTableEntry) -> Self {
598+
let owned = Self::from_raw(&value);
599+
Self::free_raw(value);
600+
owned
601+
}
602+
603+
pub(crate) fn into_raw(value: Self) -> BNLookupTableEntry {
604+
let from_values: Box<[i64]> = value.from.into_iter().collect();
605+
let from_values_len = from_values.len();
606+
BNLookupTableEntry {
607+
// Freed in [`Self::free_raw`]
608+
fromValues: Box::leak(from_values).as_mut_ptr(),
609+
fromCount: from_values_len,
610+
toValue: value.to,
611+
}
612+
}
613+
614+
pub(crate) fn free_raw(value: BNLookupTableEntry) {
615+
let raw_from = unsafe { std::slice::from_raw_parts_mut(value.fromValues, value.fromCount) };
616+
let boxed_from = unsafe { Box::from_raw(raw_from) };
617+
}
593618
}
594619

595620
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -724,15 +749,14 @@ impl PossibleValueSet {
724749
}
725750
}
726751

727-
/// Take ownership over an "owned" core allocated value. Do not call this for a rust allocated value.
728-
pub(crate) fn from_owned_raw(mut value: BNPossibleValueSet) -> Self {
752+
/// Take ownership over an "owned" **core allocated** value. Do not call this for a rust allocated value.
753+
pub(crate) fn from_owned_core_raw(mut value: BNPossibleValueSet) -> Self {
729754
let owned = Self::from_raw(&value);
730-
// TODO: This entire function is a little wonky.
731-
Self::free_raw(&mut value);
755+
Self::free_core_raw(&mut value);
732756
owned
733757
}
734758

735-
pub(crate) fn into_raw(value: Self) -> BNPossibleValueSet {
759+
pub(crate) fn into_rust_raw(value: Self) -> BNPossibleValueSet {
736760
let mut raw = BNPossibleValueSet {
737761
state: value.value_type(),
738762
..Default::default()
@@ -758,31 +782,39 @@ impl PossibleValueSet {
758782
PossibleValueSet::ReturnAddressValue => {}
759783
PossibleValueSet::ImportedAddressValue => {}
760784
PossibleValueSet::SignedRangeValue { value, ranges } => {
785+
let boxed_raw_ranges: Box<[BNValueRange]> =
786+
ranges.into_iter().map(BNValueRange::from).collect();
761787
raw.value = value;
762-
// TODO: raw.ranges
763-
// TODO: requires core allocation and freeing.
764-
// TODO: See `BNFreePossibleValueSet` for why this sucks.
788+
raw.count = boxed_raw_ranges.len();
789+
// NOTE: We are allocating this in rust, meaning core MUST NOT free this.
790+
raw.ranges = Box::leak(boxed_raw_ranges).as_mut_ptr();
765791
}
766792
PossibleValueSet::UnsignedRangeValue { value, ranges } => {
793+
let boxed_raw_ranges: Box<[BNValueRange]> =
794+
ranges.into_iter().map(BNValueRange::from).collect();
767795
raw.value = value;
768-
// TODO: raw.ranges
769-
// TODO: requires core allocation and freeing.
770-
// TODO: See `BNFreePossibleValueSet` for why this sucks.
796+
raw.count = boxed_raw_ranges.len();
797+
// NOTE: We are allocating this in rust, meaning core MUST NOT free this.
798+
raw.ranges = Box::leak(boxed_raw_ranges).as_mut_ptr();
771799
}
772800
PossibleValueSet::LookupTableValue { table } => {
773-
// TODO: raw.table
774-
// TODO: requires core allocation and freeing.
775-
// TODO: See `BNFreePossibleValueSet` for why this sucks.
801+
let boxed_raw_entries: Box<[BNLookupTableEntry]> =
802+
table.into_iter().map(LookupTableEntry::into_raw).collect();
803+
raw.count = boxed_raw_entries.len();
804+
// NOTE: We are allocating this in rust, meaning core MUST NOT free this.
805+
raw.table = Box::leak(boxed_raw_entries).as_mut_ptr();
776806
}
777807
PossibleValueSet::InSetOfValues { values } => {
778-
// TODO: raw.valueSet
779-
// TODO: requires core allocation and freeing.
780-
// TODO: See `BNFreePossibleValueSet` for why this sucks.
808+
let boxed_raw_values: Box<[i64]> = values.into_iter().collect();
809+
raw.count = boxed_raw_values.len();
810+
// NOTE: We are allocating this in rust, meaning core MUST NOT free this.
811+
raw.valueSet = Box::leak(boxed_raw_values).as_mut_ptr();
781812
}
782813
PossibleValueSet::NotInSetOfValues { values } => {
783-
// TODO: raw.valueSet
784-
// TODO: requires core allocation and freeing.
785-
// TODO: See `BNFreePossibleValueSet` for why this sucks.
814+
let boxed_raw_values: Box<[i64]> = values.into_iter().collect();
815+
raw.count = boxed_raw_values.len();
816+
// NOTE: We are allocating this in rust, meaning core MUST NOT free this.
817+
raw.valueSet = Box::leak(boxed_raw_values).as_mut_ptr();
786818
}
787819
PossibleValueSet::ConstantDataValue { value, size } => {
788820
raw.value = value;
@@ -804,14 +836,28 @@ impl PossibleValueSet {
804836
raw
805837
}
806838

807-
/// Free a CORE ALLOCATED possible value set. Do not use this with [Self::into_raw] values.
808-
pub(crate) fn free_raw(value: &mut BNPossibleValueSet) {
839+
/// Free a CORE ALLOCATED possible value set. Do not use this with [Self::into_rust_raw] values.
840+
pub(crate) fn free_core_raw(value: &mut BNPossibleValueSet) {
809841
unsafe { BNFreePossibleValueSet(value) }
810842
}
811843

812844
/// Free a RUST ALLOCATED possible value set. Do not use this with CORE ALLOCATED values.
813-
pub(crate) fn free_owned_raw(value: BNPossibleValueSet) {
814-
// TODO: Once we fill out allocation of the possible value set then we should fill this out as well.
845+
pub(crate) fn free_rust_raw(value: BNPossibleValueSet) {
846+
// Free the range list
847+
if !value.ranges.is_null() {
848+
let raw_ranges = unsafe { std::slice::from_raw_parts_mut(value.ranges, value.count) };
849+
let boxed_ranges = unsafe { Box::from_raw(raw_ranges) };
850+
}
851+
852+
if !value.table.is_null() {
853+
unsafe { LookupTableEntry::free_raw(*value.table) };
854+
}
855+
856+
if !value.valueSet.is_null() {
857+
let raw_value_set =
858+
unsafe { std::slice::from_raw_parts_mut(value.valueSet, value.count) };
859+
let boxed_value_set = unsafe { Box::from_raw(raw_value_set) };
860+
}
815861
}
816862

817863
pub fn value_type(&self) -> RegisterValueType {

rust/tests/medium_level_il.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use binaryninja::medium_level_il::{
44
MediumLevelExpressionIndex, MediumLevelILInstructionKind, MediumLevelILLiftedInstructionKind,
55
MediumLevelInstructionIndex,
66
};
7+
use binaryninja::variable::{PossibleValueSet, ValueRange};
78
use std::path::PathBuf;
89

910
#[test]
@@ -117,3 +118,74 @@ fn test_mlil_basic_blocks() {
117118
}
118119
}
119120
}
121+
122+
#[test]
123+
fn test_mlil_possible_values() {
124+
let _session = Session::new().expect("Failed to initialize session");
125+
let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
126+
let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
127+
let image_base = view.original_image_base();
128+
129+
let functions = view.functions_containing(image_base + 0x0002af9a);
130+
assert_eq!(functions.len(), 1);
131+
let function = functions.get(0);
132+
let mlil_function = function.medium_level_il().unwrap();
133+
134+
// 0 @ 0002af40 (MLIL_SET_VAR.d edi_1 = (MLIL_VAR.d edi))
135+
let instr_0 = mlil_function
136+
.instruction_from_index(MediumLevelInstructionIndex(0))
137+
.expect("Failed to get instruction");
138+
let lifted_instr_0 = instr_0.lift();
139+
match lifted_instr_0.kind {
140+
MediumLevelILLiftedInstructionKind::SetVar(op) => match op.src.kind {
141+
MediumLevelILLiftedInstructionKind::Var(var) => {
142+
let var = var.src;
143+
let pvs_value = PossibleValueSet::SignedRangeValue {
144+
value: 5,
145+
ranges: vec![ValueRange {
146+
start: -5,
147+
end: 0,
148+
step: 1,
149+
}],
150+
};
151+
mlil_function
152+
.set_user_var_value(&var, op.src.address, pvs_value.clone(), false)
153+
.expect("Failed to set user var value");
154+
let var_values = mlil_function.user_var_values();
155+
assert_eq!(var_values.len(), 1);
156+
let var_value = var_values.get(0);
157+
assert_eq!(var_value.value, pvs_value);
158+
assert_eq!(var_value.def_site.addr, op.src.address);
159+
assert_eq!(var_value.variable, var);
160+
assert_eq!(var_value.after, false);
161+
}
162+
_ => panic!("Expected Var"),
163+
},
164+
_ => panic!("Expected SetVar"),
165+
}
166+
167+
// 21 @ 0002af9a (MLIL_RET return (MLIL_VAR.d eax_2))
168+
let instr_21 = mlil_function
169+
.instruction_from_index(MediumLevelInstructionIndex(21))
170+
.expect("Failed to get instruction");
171+
let lifted_instr_21 = instr_21.lift();
172+
match lifted_instr_21.kind {
173+
MediumLevelILLiftedInstructionKind::Ret(ret) => {
174+
// This should be the eax variable, with the signed range.
175+
let eax_var_lifted = ret.src.get(0).unwrap();
176+
// TODO: I really dislike that we have lifted and unlifted versions of the same thing.
177+
let eax_var = mlil_function
178+
.instruction_from_expr_index(eax_var_lifted.expr_index)
179+
.unwrap();
180+
let eax_var_pvs = eax_var.possible_values();
181+
match eax_var_pvs {
182+
PossibleValueSet::SignedRangeValue { value, ranges } => {
183+
assert_eq!(value, -48);
184+
assert_eq!(ranges.len(), 2);
185+
}
186+
_ => panic!("Expected SignedRangeValue"),
187+
}
188+
}
189+
_ => panic!("Expected Ret"),
190+
}
191+
}

0 commit comments

Comments
 (0)