binaryninja/
lib.rs

1// Copyright 2021-2026 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_notification;
43pub mod data_renderer;
44pub mod database;
45pub mod debuginfo;
46pub mod demangle;
47pub mod disassembly;
48pub mod download;
49pub mod enterprise;
50pub mod external_library;
51pub mod file_accessor;
52pub mod file_metadata;
53pub mod flowgraph;
54pub mod function;
55pub mod function_recognizer;
56pub mod headless;
57pub mod high_level_il;
58pub mod interaction;
59pub mod language_representation;
60pub mod line_formatter;
61pub mod linear_view;
62pub mod llvm;
63pub mod logger;
64pub mod low_level_il;
65pub mod main_thread;
66pub mod medium_level_il;
67pub mod metadata;
68pub mod platform;
69pub mod progress;
70pub mod project;
71pub mod qualified_name;
72pub mod rc;
73pub mod references;
74pub mod relocation;
75pub mod render_layer;
76pub mod repository;
77pub mod secrets_provider;
78pub mod section;
79pub mod segment;
80pub mod settings;
81pub mod string;
82pub mod symbol;
83pub mod tags;
84pub mod template_simplifier;
85pub mod tracing;
86pub mod types;
87pub mod update;
88pub mod variable;
89pub mod websocket;
90pub mod worker_thread;
91pub mod workflow;
92
93use crate::file_metadata::FileMetadata;
94use crate::function::Function;
95use crate::progress::{NoProgressCallback, ProgressCallback};
96use crate::string::raw_to_string;
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::fmt::{Display, Formatter};
106use std::path::{Path, PathBuf};
107use string::BnString;
108use string::IntoCStr;
109use string::IntoJson;
110
111use crate::project::file::ProjectFile;
112pub use binaryninjacore_sys::BNDataFlowQueryOption as DataFlowQueryOption;
113pub use binaryninjacore_sys::BNEndianness as Endianness;
114pub use binaryninjacore_sys::BNILBranchDependence as ILBranchDependence;
115
116pub const BN_FULL_CONFIDENCE: u8 = u8::MAX;
117pub const BN_INVALID_EXPR: usize = usize::MAX;
118
119/// 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()`]
120pub fn load(file_path: impl AsRef<Path>) -> Option<Ref<BinaryView>> {
121    load_with_progress(file_path, NoProgressCallback)
122}
123
124/// Equivalent to [`load`] but with a progress callback.
125///
126/// NOTE: The progress callback will _only_ be called when loading BNDBs.
127pub fn load_with_progress<P: ProgressCallback>(
128    file_path: impl AsRef<Path>,
129    mut progress: P,
130) -> Option<Ref<BinaryView>> {
131    let file_path = file_path.as_ref().to_cstr();
132    let options = c"";
133    let handle = unsafe {
134        BNLoadFilename(
135            file_path.as_ptr() as *mut _,
136            true,
137            options.as_ptr() as *mut c_char,
138            Some(P::cb_progress_callback),
139            &mut progress as *mut P as *mut c_void,
140        )
141    };
142
143    if handle.is_null() {
144        None
145    } else {
146        Some(unsafe { BinaryView::ref_from_raw(handle) })
147    }
148}
149
150/// 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()`]
151///
152/// <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>
153///
154/// ```no_run
155/// # // Mock implementation of json! macro for documentation purposes
156/// # macro_rules! json {
157/// #   ($($arg:tt)*) => {
158/// #     stringify!($($arg)*)
159/// #   };
160/// # }
161/// use binaryninja::{metadata::Metadata, rc::Ref};
162/// use std::collections::HashMap;
163///
164/// let bv = binaryninja::load_with_options("/bin/cat", true, Some(json!("analysis.linearSweep.autorun": false).to_string()))
165///     .expect("Couldn't open `/bin/cat`");
166/// ```
167pub fn load_with_options<O>(
168    file_path: impl AsRef<Path>,
169    update_analysis_and_wait: bool,
170    options: Option<O>,
171) -> Option<Ref<BinaryView>>
172where
173    O: IntoJson,
174{
175    load_with_options_and_progress(
176        file_path,
177        update_analysis_and_wait,
178        options,
179        NoProgressCallback,
180    )
181}
182
183/// Equivalent to [`load_with_options`] but with a progress callback.
184///
185/// NOTE: The progress callback will _only_ be called when loading BNDBs.
186pub fn load_with_options_and_progress<O, P>(
187    file_path: impl AsRef<Path>,
188    update_analysis_and_wait: bool,
189    options: Option<O>,
190    mut progress: P,
191) -> Option<Ref<BinaryView>>
192where
193    O: IntoJson,
194    P: ProgressCallback,
195{
196    let file_path = file_path.as_ref().to_cstr();
197    let options_or_default = if let Some(opt) = options {
198        opt.get_json_string()
199            .ok()?
200            .to_cstr()
201            .to_bytes_with_nul()
202            .to_vec()
203    } else {
204        Metadata::new_of_type(MetadataType::KeyValueDataType)
205            .get_json_string()
206            .ok()?
207            .as_ref()
208            .to_vec()
209    };
210    let handle = unsafe {
211        BNLoadFilename(
212            file_path.as_ptr() as *mut _,
213            update_analysis_and_wait,
214            options_or_default.as_ptr() as *mut c_char,
215            Some(P::cb_progress_callback),
216            &mut progress as *mut P as *mut c_void,
217        )
218    };
219
220    if handle.is_null() {
221        None
222    } else {
223        Some(unsafe { BinaryView::ref_from_raw(handle) })
224    }
225}
226
227pub fn load_view<O>(
228    bv: &BinaryView,
229    update_analysis_and_wait: bool,
230    options: Option<O>,
231) -> Option<Ref<BinaryView>>
232where
233    O: IntoJson,
234{
235    load_view_with_progress(bv, update_analysis_and_wait, options, NoProgressCallback)
236}
237
238/// Equivalent to [`load_view`] but with a progress callback.
239pub fn load_view_with_progress<O, P>(
240    bv: &BinaryView,
241    update_analysis_and_wait: bool,
242    options: Option<O>,
243    mut progress: P,
244) -> Option<Ref<BinaryView>>
245where
246    O: IntoJson,
247    P: ProgressCallback,
248{
249    let options_or_default = if let Some(opt) = options {
250        opt.get_json_string()
251            .ok()?
252            .to_cstr()
253            .to_bytes_with_nul()
254            .to_vec()
255    } else {
256        "{}".to_cstr().to_bytes_with_nul().to_vec()
257    };
258    let handle = unsafe {
259        BNLoadBinaryView(
260            bv.handle as *mut _,
261            update_analysis_and_wait,
262            options_or_default.as_ptr() as *mut c_char,
263            Some(P::cb_progress_callback),
264            &mut progress as *mut P as *mut c_void,
265        )
266    };
267
268    if handle.is_null() {
269        None
270    } else {
271        Some(unsafe { BinaryView::ref_from_raw(handle) })
272    }
273}
274
275pub fn load_project_file<O>(
276    file: &ProjectFile,
277    update_analysis_and_wait: bool,
278    options: Option<O>,
279) -> Option<Ref<BinaryView>>
280where
281    O: IntoJson,
282{
283    load_project_file_with_progress(file, update_analysis_and_wait, options, NoProgressCallback)
284}
285
286/// Equivalent to [`load_project_file`] but with a progress callback.
287pub fn load_project_file_with_progress<O, P>(
288    file: &ProjectFile,
289    update_analysis_and_wait: bool,
290    options: Option<O>,
291    mut progress: P,
292) -> Option<Ref<BinaryView>>
293where
294    O: IntoJson,
295    P: ProgressCallback,
296{
297    let options_or_default = if let Some(opt) = options {
298        opt.get_json_string()
299            .ok()?
300            .to_cstr()
301            .to_bytes_with_nul()
302            .to_vec()
303    } else {
304        "{}".to_cstr().to_bytes_with_nul().to_vec()
305    };
306    let handle = unsafe {
307        BNLoadProjectFile(
308            file.handle.as_ptr(),
309            update_analysis_and_wait,
310            options_or_default.as_ptr() as *mut c_char,
311            Some(P::cb_progress_callback),
312            &mut progress as *mut P as *mut c_void,
313        )
314    };
315
316    if handle.is_null() {
317        None
318    } else {
319        Some(unsafe { BinaryView::ref_from_raw(handle) })
320    }
321}
322
323pub fn install_directory() -> PathBuf {
324    let install_dir_ptr: *mut c_char = unsafe { BNGetInstallDirectory() };
325    assert!(!install_dir_ptr.is_null());
326    let install_dir_str = unsafe { BnString::into_string(install_dir_ptr) };
327    PathBuf::from(install_dir_str)
328}
329
330pub fn bundled_plugin_directory() -> Result<PathBuf, ()> {
331    let s: *mut c_char = unsafe { BNGetBundledPluginDirectory() };
332    if s.is_null() {
333        return Err(());
334    }
335    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
336}
337
338pub fn set_bundled_plugin_directory(new_dir: impl AsRef<Path>) {
339    let new_dir = new_dir.as_ref().to_cstr();
340    unsafe { BNSetBundledPluginDirectory(new_dir.as_ptr()) };
341}
342
343pub fn user_directory() -> PathBuf {
344    let user_dir_ptr: *mut c_char = unsafe { BNGetUserDirectory() };
345    assert!(!user_dir_ptr.is_null());
346    let user_dir_str = unsafe { BnString::into_string(user_dir_ptr) };
347    PathBuf::from(user_dir_str)
348}
349
350pub fn user_plugin_directory() -> Result<PathBuf, ()> {
351    let s: *mut c_char = unsafe { BNGetUserPluginDirectory() };
352    if s.is_null() {
353        return Err(());
354    }
355    let user_plugin_dir_str = unsafe { BnString::into_string(s) };
356    Ok(PathBuf::from(user_plugin_dir_str))
357}
358
359pub fn repositories_directory() -> Result<PathBuf, ()> {
360    let s: *mut c_char = unsafe { BNGetRepositoriesDirectory() };
361    if s.is_null() {
362        return Err(());
363    }
364    let repo_dir_str = unsafe { BnString::into_string(s) };
365    Ok(PathBuf::from(repo_dir_str))
366}
367
368pub fn settings_file_path() -> PathBuf {
369    let settings_file_name_ptr: *mut c_char = unsafe { BNGetSettingsFileName() };
370    assert!(!settings_file_name_ptr.is_null());
371    let settings_file_path_str = unsafe { BnString::into_string(settings_file_name_ptr) };
372    PathBuf::from(settings_file_path_str)
373}
374
375/// Write the installation directory of the currently running core instance to disk.
376///
377/// This is used to select the most recent installation for running scripts.
378pub fn save_last_run() {
379    unsafe { BNSaveLastRun() };
380}
381
382pub fn path_relative_to_bundled_plugin_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
383    let path_raw = path.as_ref().to_cstr();
384    let s: *mut c_char = unsafe { BNGetPathRelativeToBundledPluginDirectory(path_raw.as_ptr()) };
385    if s.is_null() {
386        return Err(());
387    }
388    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
389}
390
391pub fn path_relative_to_user_plugin_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
392    let path_raw = path.as_ref().to_cstr();
393    let s: *mut c_char = unsafe { BNGetPathRelativeToUserPluginDirectory(path_raw.as_ptr()) };
394    if s.is_null() {
395        return Err(());
396    }
397    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
398}
399
400pub fn path_relative_to_user_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
401    let path_raw = path.as_ref().to_cstr();
402    let s: *mut c_char = unsafe { BNGetPathRelativeToUserDirectory(path_raw.as_ptr()) };
403    if s.is_null() {
404        return Err(());
405    }
406    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
407}
408
409/// Returns if the running thread is the "main thread"
410///
411/// If there is no registered main thread than this will always return true.
412pub fn is_main_thread() -> bool {
413    unsafe { BNIsMainThread() }
414}
415
416pub fn memory_info() -> HashMap<String, u64> {
417    let mut count = 0;
418    let mut usage = HashMap::new();
419    unsafe {
420        let info_ptr = BNGetMemoryUsageInfo(&mut count);
421        let info_list = std::slice::from_raw_parts(info_ptr, count);
422        for info in info_list {
423            let info_name = CStr::from_ptr(info.name).to_str().unwrap().to_string();
424            usage.insert(info_name, info.value);
425        }
426        BNFreeMemoryUsageInfo(info_ptr, count);
427    }
428    usage
429}
430
431/// The trait required for receiving core object destruction callbacks.
432pub trait ObjectDestructor: 'static + Sync + Sized {
433    fn destruct_view(&self, _view: &BinaryView) {}
434    fn destruct_file_metadata(&self, _metadata: &FileMetadata) {}
435    fn destruct_function(&self, _func: &Function) {}
436
437    unsafe extern "C" fn cb_destruct_binary_view(ctxt: *mut c_void, view: *mut BNBinaryView) {
438        ffi_wrap!("ObjectDestructor::destruct_view", {
439            let view_type = &*(ctxt as *mut Self);
440            let view = BinaryView { handle: view };
441            view_type.destruct_view(&view);
442        })
443    }
444
445    unsafe extern "C" fn cb_destruct_file_metadata(ctxt: *mut c_void, file: *mut BNFileMetadata) {
446        ffi_wrap!("ObjectDestructor::destruct_file_metadata", {
447            let view_type = &*(ctxt as *mut Self);
448            let file = FileMetadata::from_raw(file);
449            view_type.destruct_file_metadata(&file);
450        })
451    }
452
453    unsafe extern "C" fn cb_destruct_function(ctxt: *mut c_void, func: *mut BNFunction) {
454        ffi_wrap!("ObjectDestructor::destruct_function", {
455            let view_type = &*(ctxt as *mut Self);
456            let func = Function { handle: func };
457            view_type.destruct_function(&func);
458        })
459    }
460
461    unsafe fn as_callbacks(&'static mut self) -> BNObjectDestructionCallbacks {
462        BNObjectDestructionCallbacks {
463            context: std::mem::transmute(&self),
464            destructBinaryView: Some(Self::cb_destruct_binary_view),
465            destructFileMetadata: Some(Self::cb_destruct_file_metadata),
466            destructFunction: Some(Self::cb_destruct_function),
467        }
468    }
469
470    fn register(&'static mut self) {
471        unsafe { BNRegisterObjectDestructionCallbacks(&mut self.as_callbacks()) };
472    }
473
474    fn unregister(&'static mut self) {
475        unsafe { BNUnregisterObjectDestructionCallbacks(&mut self.as_callbacks()) };
476    }
477}
478
479pub fn version() -> String {
480    unsafe { BnString::into_string(BNGetVersionString()) }
481}
482
483pub fn build_id() -> u32 {
484    unsafe { BNGetBuildId() }
485}
486
487#[derive(Clone, PartialEq, Eq, Hash, Debug)]
488pub struct VersionInfo {
489    pub major: u32,
490    pub minor: u32,
491    pub build: u32,
492    pub channel: String,
493}
494
495impl VersionInfo {
496    pub(crate) fn from_raw(value: &BNVersionInfo) -> Self {
497        Self {
498            major: value.major,
499            minor: value.minor,
500            build: value.build,
501            // NOTE: Because of plugin manager the channel might not be filled.
502            channel: raw_to_string(value.channel).unwrap_or_default(),
503        }
504    }
505
506    pub(crate) fn from_owned_raw(value: BNVersionInfo) -> Self {
507        let owned = Self::from_raw(&value);
508        Self::free_raw(value);
509        owned
510    }
511
512    pub(crate) fn into_owned_raw(value: &Self) -> BNVersionInfo {
513        BNVersionInfo {
514            major: value.major,
515            minor: value.minor,
516            build: value.build,
517            channel: value.channel.as_ptr() as *mut c_char,
518        }
519    }
520
521    pub(crate) fn free_raw(value: BNVersionInfo) {
522        unsafe { BnString::free_raw(value.channel) };
523    }
524}
525
526impl TryFrom<&str> for VersionInfo {
527    type Error = ();
528
529    fn try_from(value: &str) -> Result<Self, Self::Error> {
530        let string = value.to_cstr();
531        let result = unsafe { BNParseVersionString(string.as_ptr()) };
532        if result.build == 0 && result.channel.is_null() && result.major == 0 && result.minor == 0 {
533            return Err(());
534        }
535        Ok(Self::from_owned_raw(result))
536    }
537}
538
539impl PartialOrd for VersionInfo {
540    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
541        Some(self.cmp(other))
542    }
543}
544
545impl Ord for VersionInfo {
546    fn cmp(&self, other: &Self) -> cmp::Ordering {
547        if self == other {
548            return cmp::Ordering::Equal;
549        }
550        let bn_version_0 = VersionInfo::into_owned_raw(self);
551        let bn_version_1 = VersionInfo::into_owned_raw(other);
552        if unsafe { BNVersionLessThan(bn_version_0, bn_version_1) } {
553            cmp::Ordering::Less
554        } else {
555            cmp::Ordering::Greater
556        }
557    }
558}
559
560impl Display for VersionInfo {
561    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
562        if self.channel.is_empty() {
563            write!(f, "{}.{}.{}", self.major, self.minor, self.build)
564        } else {
565            write!(
566                f,
567                "{}.{}.{}-{}",
568                self.major, self.minor, self.build, self.channel
569            )
570        }
571    }
572}
573
574pub fn version_info() -> VersionInfo {
575    let info_raw = unsafe { BNGetVersionInfo() };
576    VersionInfo::from_owned_raw(info_raw)
577}
578
579pub fn serial_number() -> String {
580    unsafe { BnString::into_string(BNGetSerialNumber()) }
581}
582
583pub fn is_license_validated() -> bool {
584    unsafe { BNIsLicenseValidated() }
585}
586
587pub fn licensed_user_email() -> String {
588    unsafe { BnString::into_string(BNGetLicensedUserEmail()) }
589}
590
591pub fn license_path() -> PathBuf {
592    user_directory().join("license.dat")
593}
594
595pub fn license_count() -> i32 {
596    unsafe { BNGetLicenseCount() }
597}
598
599/// Set the license that will be used once the core initializes. You can reset the license by passing `None`.
600///
601/// If not set, the normal license retrieval will occur:
602/// 1. Check the BN_LICENSE environment variable
603/// 2. Check the Binary Ninja user directory for license.dat
604#[cfg(not(feature = "demo"))]
605pub fn set_license(license: Option<&str>) {
606    let license = license.unwrap_or_default().to_cstr();
607    unsafe { BNSetLicense(license.as_ptr()) }
608}
609
610#[cfg(feature = "demo")]
611pub fn set_license(_license: Option<&str>) {}
612
613pub fn product() -> String {
614    unsafe { BnString::into_string(BNGetProduct()) }
615}
616
617pub fn product_type() -> String {
618    unsafe { BnString::into_string(BNGetProductType()) }
619}
620
621pub fn license_expiration_time() -> std::time::SystemTime {
622    let m = std::time::Duration::from_secs(unsafe { BNGetLicenseExpirationTime() });
623    std::time::UNIX_EPOCH + m
624}
625
626pub fn is_ui_enabled() -> bool {
627    unsafe { BNIsUIEnabled() }
628}
629
630pub fn is_database(file: &Path) -> bool {
631    let filename = file.to_cstr();
632    unsafe { BNIsDatabase(filename.as_ptr()) }
633}
634
635pub fn plugin_abi_version() -> u32 {
636    BN_CURRENT_CORE_ABI_VERSION
637}
638
639pub fn plugin_abi_minimum_version() -> u32 {
640    BN_MINIMUM_CORE_ABI_VERSION
641}
642
643pub fn core_abi_version() -> u32 {
644    unsafe { BNGetCurrentCoreABIVersion() }
645}
646
647pub fn core_abi_minimum_version() -> u32 {
648    unsafe { BNGetMinimumCoreABIVersion() }
649}
650
651pub fn plugin_ui_abi_version() -> u32 {
652    BN_CURRENT_UI_ABI_VERSION
653}
654
655pub fn plugin_ui_abi_minimum_version() -> u32 {
656    BN_MINIMUM_UI_ABI_VERSION
657}
658
659pub fn add_required_plugin_dependency(name: &str) {
660    let raw_name = name.to_cstr();
661    unsafe { BNAddRequiredPluginDependency(raw_name.as_ptr()) };
662}
663
664pub fn add_optional_plugin_dependency(name: &str) {
665    let raw_name = name.to_cstr();
666    unsafe { BNAddOptionalPluginDependency(raw_name.as_ptr()) };
667}
668
669/// Exported function to tell the core what core ABI version this plugin was compiled against.
670#[cfg(not(feature = "no_exports"))]
671#[no_mangle]
672#[allow(non_snake_case)]
673pub extern "C" fn CorePluginABIVersion() -> u32 {
674    plugin_abi_version()
675}
676
677/// Exported function to tell the core what UI ABI version this plugin was compiled against.
678#[cfg(not(feature = "no_exports"))]
679#[no_mangle]
680pub extern "C" fn UIPluginABIVersion() -> u32 {
681    plugin_ui_abi_version()
682}