binaryninja/
lib.rs

1// Copyright 2021-2025 Vector 35 Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// TODO: These clippy-allow are bad and needs to be removed
16#![allow(clippy::missing_safety_doc)]
17#![allow(clippy::result_unit_err)]
18#![allow(clippy::type_complexity)]
19#![allow(clippy::too_many_arguments)]
20#![allow(clippy::needless_doctest_main)]
21#![doc(html_root_url = "https://dev-rust.binary.ninja/")]
22#![doc(html_favicon_url = "https://binary.ninja/icons/favicon-32x32.png")]
23#![doc(html_logo_url = "https://binary.ninja/icons/android-chrome-512x512.png")]
24#![doc(issue_tracker_base_url = "https://github.com/Vector35/binaryninja-api/issues/")]
25#![doc = include_str!("../README.md")]
26
27#[macro_use]
28mod ffi;
29
30pub mod architecture;
31pub mod background_task;
32pub mod base_detection;
33pub mod basic_block;
34pub mod binary_view;
35pub mod calling_convention;
36pub mod collaboration;
37pub mod command;
38pub mod component;
39pub mod confidence;
40pub mod custom_binary_view;
41pub mod data_buffer;
42pub mod data_renderer;
43pub mod database;
44pub mod debuginfo;
45pub mod demangle;
46pub mod disassembly;
47pub mod download;
48pub mod enterprise;
49pub mod external_library;
50pub mod file_accessor;
51pub mod file_metadata;
52pub mod flowgraph;
53pub mod function;
54pub mod function_recognizer;
55pub mod headless;
56pub mod high_level_il;
57pub mod interaction;
58pub mod language_representation;
59pub mod line_formatter;
60pub mod linear_view;
61pub mod llvm;
62pub mod logger;
63pub mod low_level_il;
64pub mod main_thread;
65pub mod medium_level_il;
66pub mod metadata;
67pub mod platform;
68pub mod progress;
69pub mod project;
70pub mod rc;
71pub mod references;
72pub mod relocation;
73pub mod render_layer;
74pub mod repository;
75pub mod secrets_provider;
76pub mod section;
77pub mod segment;
78pub mod settings;
79pub mod string;
80pub mod symbol;
81pub mod tags;
82pub mod template_simplifier;
83pub mod type_archive;
84pub mod type_container;
85pub mod type_library;
86pub mod type_parser;
87pub mod type_printer;
88pub mod types;
89pub mod update;
90pub mod variable;
91pub mod websocket;
92pub mod worker_thread;
93pub mod workflow;
94
95use crate::file_metadata::FileMetadata;
96use crate::function::Function;
97use binary_view::BinaryView;
98use binaryninjacore_sys::*;
99use metadata::Metadata;
100use metadata::MetadataType;
101use rc::Ref;
102use std::cmp;
103use std::collections::HashMap;
104use std::ffi::{c_char, c_void, CStr};
105use std::path::{Path, PathBuf};
106use string::BnString;
107use string::IntoCStr;
108use string::IntoJson;
109
110use crate::progress::{NoProgressCallback, ProgressCallback};
111use crate::string::raw_to_string;
112pub use binaryninjacore_sys::BNBranchType as BranchType;
113pub use binaryninjacore_sys::BNDataFlowQueryOption as DataFlowQueryOption;
114pub use binaryninjacore_sys::BNEndianness as Endianness;
115pub use binaryninjacore_sys::BNILBranchDependence as ILBranchDependence;
116
117pub const BN_FULL_CONFIDENCE: u8 = u8::MAX;
118pub const BN_INVALID_EXPR: usize = usize::MAX;
119
120/// The main way to open and load files into Binary Ninja. Make sure you've properly initialized the core before calling this function. See [`crate::headless::init()`]
121pub fn load(file_path: impl AsRef<Path>) -> Option<Ref<BinaryView>> {
122    load_with_progress(file_path, NoProgressCallback)
123}
124
125/// Equivalent to [`load`] but with a progress callback.
126///
127/// NOTE: The progress callback will _only_ be called when loading BNDBs.
128pub fn load_with_progress<P: ProgressCallback>(
129    file_path: impl AsRef<Path>,
130    mut progress: P,
131) -> Option<Ref<BinaryView>> {
132    let file_path = file_path.as_ref().to_cstr();
133    let options = c"";
134    let handle = unsafe {
135        BNLoadFilename(
136            file_path.as_ptr() as *mut _,
137            true,
138            options.as_ptr() as *mut c_char,
139            Some(P::cb_progress_callback),
140            &mut progress as *mut P as *mut c_void,
141        )
142    };
143
144    if handle.is_null() {
145        None
146    } else {
147        Some(unsafe { BinaryView::ref_from_raw(handle) })
148    }
149}
150
151/// The main way to open and load files (with options) into Binary Ninja. Make sure you've properly initialized the core before calling this function. See [`crate::headless::init()`]
152///
153/// <div class="warning">Strict JSON doesn't support single quotes for strings, so you'll need to either use a raw strings (<code>f#"{"setting": "value"}"#</code>) or escape double quotes (<code>"{\"setting\": \"value\"}"</code>). Or use <code>serde_json::json</code>.</div>
154///
155/// ```no_run
156/// # // Mock implementation of json! macro for documentation purposes
157/// # macro_rules! json {
158/// #   ($($arg:tt)*) => {
159/// #     stringify!($($arg)*)
160/// #   };
161/// # }
162/// use binaryninja::{metadata::Metadata, rc::Ref};
163/// use std::collections::HashMap;
164///
165/// let bv = binaryninja::load_with_options("/bin/cat", true, Some(json!("analysis.linearSweep.autorun": false).to_string()))
166///     .expect("Couldn't open `/bin/cat`");
167/// ```
168pub fn load_with_options<O>(
169    file_path: impl AsRef<Path>,
170    update_analysis_and_wait: bool,
171    options: Option<O>,
172) -> Option<Ref<BinaryView>>
173where
174    O: IntoJson,
175{
176    load_with_options_and_progress(
177        file_path,
178        update_analysis_and_wait,
179        options,
180        NoProgressCallback,
181    )
182}
183
184/// Equivalent to [`load_with_options`] but with a progress callback.
185///
186/// NOTE: The progress callback will _only_ be called when loading BNDBs.
187pub fn load_with_options_and_progress<O, P>(
188    file_path: impl AsRef<Path>,
189    update_analysis_and_wait: bool,
190    options: Option<O>,
191    mut progress: P,
192) -> Option<Ref<BinaryView>>
193where
194    O: IntoJson,
195    P: ProgressCallback,
196{
197    let file_path = file_path.as_ref().to_cstr();
198    let options_or_default = if let Some(opt) = options {
199        opt.get_json_string()
200            .ok()?
201            .to_cstr()
202            .to_bytes_with_nul()
203            .to_vec()
204    } else {
205        Metadata::new_of_type(MetadataType::KeyValueDataType)
206            .get_json_string()
207            .ok()?
208            .as_ref()
209            .to_vec()
210    };
211    let handle = unsafe {
212        BNLoadFilename(
213            file_path.as_ptr() as *mut _,
214            update_analysis_and_wait,
215            options_or_default.as_ptr() as *mut c_char,
216            Some(P::cb_progress_callback),
217            &mut progress as *mut P as *mut c_void,
218        )
219    };
220
221    if handle.is_null() {
222        None
223    } else {
224        Some(unsafe { BinaryView::ref_from_raw(handle) })
225    }
226}
227
228pub fn load_view<O>(
229    bv: &BinaryView,
230    update_analysis_and_wait: bool,
231    options: Option<O>,
232) -> Option<Ref<BinaryView>>
233where
234    O: IntoJson,
235{
236    load_view_with_progress(bv, update_analysis_and_wait, options, NoProgressCallback)
237}
238
239/// Equivalent to [`load_view`] but with a progress callback.
240pub fn load_view_with_progress<O, P>(
241    bv: &BinaryView,
242    update_analysis_and_wait: bool,
243    options: Option<O>,
244    mut progress: P,
245) -> Option<Ref<BinaryView>>
246where
247    O: IntoJson,
248    P: ProgressCallback,
249{
250    let options_or_default = if let Some(opt) = options {
251        opt.get_json_string()
252            .ok()?
253            .to_cstr()
254            .to_bytes_with_nul()
255            .to_vec()
256    } else {
257        Metadata::new_of_type(MetadataType::KeyValueDataType)
258            .get_json_string()
259            .ok()?
260            .as_ref()
261            .to_vec()
262    };
263    let handle = unsafe {
264        BNLoadBinaryView(
265            bv.handle as *mut _,
266            update_analysis_and_wait,
267            options_or_default.as_ptr() as *mut c_char,
268            Some(P::cb_progress_callback),
269            &mut progress as *mut P as *mut c_void,
270        )
271    };
272
273    if handle.is_null() {
274        None
275    } else {
276        Some(unsafe { BinaryView::ref_from_raw(handle) })
277    }
278}
279
280pub fn install_directory() -> PathBuf {
281    let install_dir_ptr: *mut c_char = unsafe { BNGetInstallDirectory() };
282    assert!(!install_dir_ptr.is_null());
283    let install_dir_str = unsafe { BnString::into_string(install_dir_ptr) };
284    PathBuf::from(install_dir_str)
285}
286
287pub fn bundled_plugin_directory() -> Result<PathBuf, ()> {
288    let s: *mut c_char = unsafe { BNGetBundledPluginDirectory() };
289    if s.is_null() {
290        return Err(());
291    }
292    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
293}
294
295pub fn set_bundled_plugin_directory(new_dir: impl AsRef<Path>) {
296    let new_dir = new_dir.as_ref().to_cstr();
297    unsafe { BNSetBundledPluginDirectory(new_dir.as_ptr()) };
298}
299
300pub fn user_directory() -> PathBuf {
301    let user_dir_ptr: *mut c_char = unsafe { BNGetUserDirectory() };
302    assert!(!user_dir_ptr.is_null());
303    let user_dir_str = unsafe { BnString::into_string(user_dir_ptr) };
304    PathBuf::from(user_dir_str)
305}
306
307pub fn user_plugin_directory() -> Result<PathBuf, ()> {
308    let s: *mut c_char = unsafe { BNGetUserPluginDirectory() };
309    if s.is_null() {
310        return Err(());
311    }
312    let user_plugin_dir_str = unsafe { BnString::into_string(s) };
313    Ok(PathBuf::from(user_plugin_dir_str))
314}
315
316pub fn repositories_directory() -> Result<PathBuf, ()> {
317    let s: *mut c_char = unsafe { BNGetRepositoriesDirectory() };
318    if s.is_null() {
319        return Err(());
320    }
321    let repo_dir_str = unsafe { BnString::into_string(s) };
322    Ok(PathBuf::from(repo_dir_str))
323}
324
325pub fn settings_file_path() -> PathBuf {
326    let settings_file_name_ptr: *mut c_char = unsafe { BNGetSettingsFileName() };
327    assert!(!settings_file_name_ptr.is_null());
328    let settings_file_path_str = unsafe { BnString::into_string(settings_file_name_ptr) };
329    PathBuf::from(settings_file_path_str)
330}
331
332/// Write the installation directory of the currently running core instance to disk.
333///
334/// This is used to select the most recent installation for running scripts.
335pub fn save_last_run() {
336    unsafe { BNSaveLastRun() };
337}
338
339pub fn path_relative_to_bundled_plugin_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
340    let path_raw = path.as_ref().to_cstr();
341    let s: *mut c_char = unsafe { BNGetPathRelativeToBundledPluginDirectory(path_raw.as_ptr()) };
342    if s.is_null() {
343        return Err(());
344    }
345    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
346}
347
348pub fn path_relative_to_user_plugin_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
349    let path_raw = path.as_ref().to_cstr();
350    let s: *mut c_char = unsafe { BNGetPathRelativeToUserPluginDirectory(path_raw.as_ptr()) };
351    if s.is_null() {
352        return Err(());
353    }
354    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
355}
356
357pub fn path_relative_to_user_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
358    let path_raw = path.as_ref().to_cstr();
359    let s: *mut c_char = unsafe { BNGetPathRelativeToUserDirectory(path_raw.as_ptr()) };
360    if s.is_null() {
361        return Err(());
362    }
363    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
364}
365
366/// Returns if the running thread is the "main thread"
367///
368/// If there is no registered main thread than this will always return true.
369pub fn is_main_thread() -> bool {
370    unsafe { BNIsMainThread() }
371}
372
373pub fn memory_info() -> HashMap<String, u64> {
374    let mut count = 0;
375    let mut usage = HashMap::new();
376    unsafe {
377        let info_ptr = BNGetMemoryUsageInfo(&mut count);
378        let info_list = std::slice::from_raw_parts(info_ptr, count);
379        for info in info_list {
380            let info_name = CStr::from_ptr(info.name).to_str().unwrap().to_string();
381            usage.insert(info_name, info.value);
382        }
383        BNFreeMemoryUsageInfo(info_ptr, count);
384    }
385    usage
386}
387
388/// The trait required for receiving core object destruction callbacks.
389pub trait ObjectDestructor: 'static + Sync + Sized {
390    fn destruct_view(&self, _view: &BinaryView) {}
391    fn destruct_file_metadata(&self, _metadata: &FileMetadata) {}
392    fn destruct_function(&self, _func: &Function) {}
393
394    unsafe extern "C" fn cb_destruct_binary_view(ctxt: *mut c_void, view: *mut BNBinaryView) {
395        ffi_wrap!("ObjectDestructor::destruct_view", {
396            let view_type = &*(ctxt as *mut Self);
397            let view = BinaryView { handle: view };
398            view_type.destruct_view(&view);
399        })
400    }
401
402    unsafe extern "C" fn cb_destruct_file_metadata(ctxt: *mut c_void, file: *mut BNFileMetadata) {
403        ffi_wrap!("ObjectDestructor::destruct_file_metadata", {
404            let view_type = &*(ctxt as *mut Self);
405            let file = FileMetadata::from_raw(file);
406            view_type.destruct_file_metadata(&file);
407        })
408    }
409
410    unsafe extern "C" fn cb_destruct_function(ctxt: *mut c_void, func: *mut BNFunction) {
411        ffi_wrap!("ObjectDestructor::destruct_function", {
412            let view_type = &*(ctxt as *mut Self);
413            let func = Function { handle: func };
414            view_type.destruct_function(&func);
415        })
416    }
417
418    unsafe fn as_callbacks(&'static mut self) -> BNObjectDestructionCallbacks {
419        BNObjectDestructionCallbacks {
420            context: std::mem::transmute(&self),
421            destructBinaryView: Some(Self::cb_destruct_binary_view),
422            destructFileMetadata: Some(Self::cb_destruct_file_metadata),
423            destructFunction: Some(Self::cb_destruct_function),
424        }
425    }
426
427    fn register(&'static mut self) {
428        unsafe { BNRegisterObjectDestructionCallbacks(&mut self.as_callbacks()) };
429    }
430
431    fn unregister(&'static mut self) {
432        unsafe { BNUnregisterObjectDestructionCallbacks(&mut self.as_callbacks()) };
433    }
434}
435
436pub fn version() -> String {
437    unsafe { BnString::into_string(BNGetVersionString()) }
438}
439
440pub fn build_id() -> u32 {
441    unsafe { BNGetBuildId() }
442}
443
444#[derive(Clone, PartialEq, Eq, Hash, Debug)]
445pub struct VersionInfo {
446    pub major: u32,
447    pub minor: u32,
448    pub build: u32,
449    pub channel: String,
450}
451
452impl VersionInfo {
453    pub(crate) fn from_raw(value: &BNVersionInfo) -> Self {
454        Self {
455            major: value.major,
456            minor: value.minor,
457            build: value.build,
458            // NOTE: Because of plugin manager the channel might not be filled.
459            channel: raw_to_string(value.channel).unwrap_or_default(),
460        }
461    }
462
463    pub(crate) fn from_owned_raw(value: BNVersionInfo) -> Self {
464        let owned = Self::from_raw(&value);
465        Self::free_raw(value);
466        owned
467    }
468
469    pub(crate) fn into_owned_raw(value: &Self) -> BNVersionInfo {
470        BNVersionInfo {
471            major: value.major,
472            minor: value.minor,
473            build: value.build,
474            channel: value.channel.as_ptr() as *mut c_char,
475        }
476    }
477
478    pub(crate) fn free_raw(value: BNVersionInfo) {
479        unsafe { BnString::free_raw(value.channel) };
480    }
481}
482
483impl TryFrom<&str> for VersionInfo {
484    type Error = ();
485
486    fn try_from(value: &str) -> Result<Self, Self::Error> {
487        let string = value.to_cstr();
488        let result = unsafe { BNParseVersionString(string.as_ptr()) };
489        if result.build == 0 && result.channel.is_null() && result.major == 0 && result.minor == 0 {
490            return Err(());
491        }
492        Ok(Self::from_owned_raw(result))
493    }
494}
495
496impl PartialOrd for VersionInfo {
497    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
498        Some(self.cmp(other))
499    }
500}
501
502impl Ord for VersionInfo {
503    fn cmp(&self, other: &Self) -> cmp::Ordering {
504        if self == other {
505            return cmp::Ordering::Equal;
506        }
507        let bn_version_0 = VersionInfo::into_owned_raw(self);
508        let bn_version_1 = VersionInfo::into_owned_raw(other);
509        if unsafe { BNVersionLessThan(bn_version_0, bn_version_1) } {
510            cmp::Ordering::Less
511        } else {
512            cmp::Ordering::Greater
513        }
514    }
515}
516
517pub fn version_info() -> VersionInfo {
518    let info_raw = unsafe { BNGetVersionInfo() };
519    VersionInfo::from_owned_raw(info_raw)
520}
521
522pub fn serial_number() -> String {
523    unsafe { BnString::into_string(BNGetSerialNumber()) }
524}
525
526pub fn is_license_validated() -> bool {
527    unsafe { BNIsLicenseValidated() }
528}
529
530pub fn licensed_user_email() -> String {
531    unsafe { BnString::into_string(BNGetLicensedUserEmail()) }
532}
533
534pub fn license_path() -> PathBuf {
535    user_directory().join("license.dat")
536}
537
538pub fn license_count() -> i32 {
539    unsafe { BNGetLicenseCount() }
540}
541
542/// Set the license that will be used once the core initializes. You can reset the license by passing `None`.
543///
544/// If not set the normal license retrieval will occur:
545/// 1. Check the BN_LICENSE environment variable
546/// 2. Check the Binary Ninja user directory for license.dat
547#[cfg(not(feature = "demo"))]
548pub fn set_license(license: Option<&str>) {
549    let license = license.unwrap_or_default().to_cstr();
550    unsafe { BNSetLicense(license.as_ptr()) }
551}
552
553#[cfg(feature = "demo")]
554pub fn set_license(_license: Option<&str>) {}
555
556pub fn product() -> String {
557    unsafe { BnString::into_string(BNGetProduct()) }
558}
559
560pub fn product_type() -> String {
561    unsafe { BnString::into_string(BNGetProductType()) }
562}
563
564pub fn license_expiration_time() -> std::time::SystemTime {
565    let m = std::time::Duration::from_secs(unsafe { BNGetLicenseExpirationTime() });
566    std::time::UNIX_EPOCH + m
567}
568
569pub fn is_ui_enabled() -> bool {
570    unsafe { BNIsUIEnabled() }
571}
572
573pub fn is_database(file: &Path) -> bool {
574    let filename = file.to_cstr();
575    unsafe { BNIsDatabase(filename.as_ptr()) }
576}
577
578pub fn plugin_abi_version() -> u32 {
579    BN_CURRENT_CORE_ABI_VERSION
580}
581
582pub fn plugin_abi_minimum_version() -> u32 {
583    BN_MINIMUM_CORE_ABI_VERSION
584}
585
586pub fn core_abi_version() -> u32 {
587    unsafe { BNGetCurrentCoreABIVersion() }
588}
589
590pub fn core_abi_minimum_version() -> u32 {
591    unsafe { BNGetMinimumCoreABIVersion() }
592}
593
594pub fn plugin_ui_abi_version() -> u32 {
595    BN_CURRENT_UI_ABI_VERSION
596}
597
598pub fn plugin_ui_abi_minimum_version() -> u32 {
599    BN_MINIMUM_UI_ABI_VERSION
600}
601
602pub fn add_required_plugin_dependency(name: &str) {
603    let raw_name = name.to_cstr();
604    unsafe { BNAddRequiredPluginDependency(raw_name.as_ptr()) };
605}
606
607pub fn add_optional_plugin_dependency(name: &str) {
608    let raw_name = name.to_cstr();
609    unsafe { BNAddOptionalPluginDependency(raw_name.as_ptr()) };
610}
611
612// Provide ABI version automatically so that the core can verify binary compatibility
613#[cfg(not(feature = "no_exports"))]
614#[no_mangle]
615#[allow(non_snake_case)]
616pub extern "C" fn CorePluginABIVersion() -> u32 {
617    plugin_abi_version()
618}
619
620#[cfg(not(feature = "no_exports"))]
621#[no_mangle]
622pub extern "C" fn UIPluginABIVersion() -> u32 {
623    plugin_ui_abi_version()
624}