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// Root-absolute so every page depth resolves; assets are copied to
23// target/doc/brand/ by scripts/build-rust-docs.sh (docs are hosted at the
24// domain root, matching html_root_url).
25#![doc(html_favicon_url = "/brand/favicon-32x32.png")]
26#![doc(html_logo_url = "/brand/logo-vertical-dark.svg")]
27#![doc(issue_tracker_base_url = "https://github.com/Vector35/binaryninja-api/issues/")]
28#![doc = include_str!("../README.md")]
29
30#[macro_use]
31mod ffi;
32
33pub mod architecture;
34pub mod background_task;
35pub mod base_detection;
36pub mod basic_block;
37pub mod binary_view;
38pub mod calling_convention;
39pub mod collaboration;
40pub mod command;
41pub mod component;
42pub mod confidence;
43pub mod data_buffer;
44pub mod data_notification;
45pub mod data_renderer;
46pub mod database;
47pub mod debuginfo;
48pub mod demangle;
49pub mod disassembly;
50pub mod download;
51pub mod enterprise;
52pub mod external_library;
53pub mod file_accessor;
54pub mod file_metadata;
55pub mod flowgraph;
56pub mod function;
57pub mod function_recognizer;
58pub mod headless;
59pub mod high_level_il;
60pub mod interaction;
61pub mod language_representation;
62pub mod line_formatter;
63pub mod linear_view;
64pub mod llvm;
65pub mod logger;
66pub mod low_level_il;
67pub mod main_thread;
68pub mod medium_level_il;
69pub mod metadata;
70pub mod object_destructor;
71pub mod platform;
72pub mod progress;
73pub mod project;
74pub mod qualified_name;
75pub mod rc;
76pub mod references;
77pub mod relocation;
78pub mod render_layer;
79pub mod repository;
80pub mod secrets_provider;
81pub mod section;
82pub mod segment;
83pub mod settings;
84pub mod string;
85pub mod string_detection;
86pub mod symbol;
87pub mod tags;
88pub mod tracing;
89pub mod transform;
90pub mod types;
91pub mod update;
92pub mod variable;
93pub mod websocket;
94pub mod worker_thread;
95pub mod workflow;
96
97use crate::progress::{NoProgressCallback, ProgressCallback};
98use crate::string::raw_to_string;
99use binary_view::BinaryView;
100use binaryninjacore_sys::*;
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        "{}".to_cstr().to_bytes_with_nul().to_vec()
205    };
206    let handle = unsafe {
207        BNLoadFilename(
208            file_path.as_ptr() as *mut _,
209            update_analysis_and_wait,
210            options_or_default.as_ptr() as *mut c_char,
211            Some(P::cb_progress_callback),
212            &mut progress as *mut P as *mut c_void,
213        )
214    };
215
216    if handle.is_null() {
217        None
218    } else {
219        Some(unsafe { BinaryView::ref_from_raw(handle) })
220    }
221}
222
223pub fn load_view<O>(
224    bv: &BinaryView,
225    update_analysis_and_wait: bool,
226    options: Option<O>,
227) -> Option<Ref<BinaryView>>
228where
229    O: IntoJson,
230{
231    load_view_with_progress(bv, update_analysis_and_wait, options, NoProgressCallback)
232}
233
234/// Equivalent to [`load_view`] but with a progress callback.
235pub fn load_view_with_progress<O, P>(
236    bv: &BinaryView,
237    update_analysis_and_wait: bool,
238    options: Option<O>,
239    mut progress: P,
240) -> Option<Ref<BinaryView>>
241where
242    O: IntoJson,
243    P: ProgressCallback,
244{
245    let options_or_default = if let Some(opt) = options {
246        opt.get_json_string()
247            .ok()?
248            .to_cstr()
249            .to_bytes_with_nul()
250            .to_vec()
251    } else {
252        "{}".to_cstr().to_bytes_with_nul().to_vec()
253    };
254    let handle = unsafe {
255        BNLoadBinaryView(
256            bv.handle as *mut _,
257            update_analysis_and_wait,
258            options_or_default.as_ptr() as *mut c_char,
259            Some(P::cb_progress_callback),
260            &mut progress as *mut P as *mut c_void,
261        )
262    };
263
264    if handle.is_null() {
265        None
266    } else {
267        Some(unsafe { BinaryView::ref_from_raw(handle) })
268    }
269}
270
271pub fn load_project_file<O>(
272    file: &ProjectFile,
273    update_analysis_and_wait: bool,
274    options: Option<O>,
275) -> Option<Ref<BinaryView>>
276where
277    O: IntoJson,
278{
279    load_project_file_with_progress(file, update_analysis_and_wait, options, NoProgressCallback)
280}
281
282/// Equivalent to [`load_project_file`] but with a progress callback.
283pub fn load_project_file_with_progress<O, P>(
284    file: &ProjectFile,
285    update_analysis_and_wait: bool,
286    options: Option<O>,
287    mut progress: P,
288) -> Option<Ref<BinaryView>>
289where
290    O: IntoJson,
291    P: ProgressCallback,
292{
293    let options_or_default = if let Some(opt) = options {
294        opt.get_json_string()
295            .ok()?
296            .to_cstr()
297            .to_bytes_with_nul()
298            .to_vec()
299    } else {
300        "{}".to_cstr().to_bytes_with_nul().to_vec()
301    };
302    let handle = unsafe {
303        BNLoadProjectFile(
304            file.handle.as_ptr(),
305            update_analysis_and_wait,
306            options_or_default.as_ptr() as *mut c_char,
307            Some(P::cb_progress_callback),
308            &mut progress as *mut P as *mut c_void,
309        )
310    };
311
312    if handle.is_null() {
313        None
314    } else {
315        Some(unsafe { BinaryView::ref_from_raw(handle) })
316    }
317}
318
319pub fn install_directory() -> PathBuf {
320    let install_dir_ptr: *mut c_char = unsafe { BNGetInstallDirectory() };
321    assert!(!install_dir_ptr.is_null());
322    let install_dir_str = unsafe { BnString::into_string(install_dir_ptr) };
323    PathBuf::from(install_dir_str)
324}
325
326pub fn bundled_plugin_directory() -> Result<PathBuf, ()> {
327    let s: *mut c_char = unsafe { BNGetBundledPluginDirectory() };
328    if s.is_null() {
329        return Err(());
330    }
331    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
332}
333
334pub fn set_bundled_plugin_directory(new_dir: impl AsRef<Path>) {
335    let new_dir = new_dir.as_ref().to_cstr();
336    unsafe { BNSetBundledPluginDirectory(new_dir.as_ptr()) };
337}
338
339pub fn user_directory() -> PathBuf {
340    let user_dir_ptr: *mut c_char = unsafe { BNGetUserDirectory() };
341    assert!(!user_dir_ptr.is_null());
342    let user_dir_str = unsafe { BnString::into_string(user_dir_ptr) };
343    PathBuf::from(user_dir_str)
344}
345
346pub fn user_plugin_directory() -> Result<PathBuf, ()> {
347    let s: *mut c_char = unsafe { BNGetUserPluginDirectory() };
348    if s.is_null() {
349        return Err(());
350    }
351    let user_plugin_dir_str = unsafe { BnString::into_string(s) };
352    Ok(PathBuf::from(user_plugin_dir_str))
353}
354
355pub fn repositories_directory() -> Result<PathBuf, ()> {
356    let s: *mut c_char = unsafe { BNGetRepositoriesDirectory() };
357    if s.is_null() {
358        return Err(());
359    }
360    let repo_dir_str = unsafe { BnString::into_string(s) };
361    Ok(PathBuf::from(repo_dir_str))
362}
363
364pub fn settings_file_path() -> PathBuf {
365    let settings_file_name_ptr: *mut c_char = unsafe { BNGetSettingsFileName() };
366    assert!(!settings_file_name_ptr.is_null());
367    let settings_file_path_str = unsafe { BnString::into_string(settings_file_name_ptr) };
368    PathBuf::from(settings_file_path_str)
369}
370
371/// Write the installation directory of the currently running core instance to disk.
372///
373/// This is used to select the most recent installation for running scripts.
374pub fn save_last_run() {
375    unsafe { BNSaveLastRun() };
376}
377
378pub fn path_relative_to_bundled_plugin_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
379    let path_raw = path.as_ref().to_cstr();
380    let s: *mut c_char = unsafe { BNGetPathRelativeToBundledPluginDirectory(path_raw.as_ptr()) };
381    if s.is_null() {
382        return Err(());
383    }
384    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
385}
386
387pub fn path_relative_to_user_plugin_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
388    let path_raw = path.as_ref().to_cstr();
389    let s: *mut c_char = unsafe { BNGetPathRelativeToUserPluginDirectory(path_raw.as_ptr()) };
390    if s.is_null() {
391        return Err(());
392    }
393    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
394}
395
396pub fn path_relative_to_user_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
397    let path_raw = path.as_ref().to_cstr();
398    let s: *mut c_char = unsafe { BNGetPathRelativeToUserDirectory(path_raw.as_ptr()) };
399    if s.is_null() {
400        return Err(());
401    }
402    Ok(PathBuf::from(unsafe { BnString::into_string(s) }))
403}
404
405/// Returns if the running thread is the "main thread"
406///
407/// If there is no registered main thread than this will always return true.
408pub fn is_main_thread() -> bool {
409    unsafe { BNIsMainThread() }
410}
411
412pub fn memory_info() -> HashMap<String, u64> {
413    let mut count = 0;
414    let mut usage = HashMap::new();
415    unsafe {
416        let info_ptr = BNGetMemoryUsageInfo(&mut count);
417        let info_list = std::slice::from_raw_parts(info_ptr, count);
418        for info in info_list {
419            let info_name = CStr::from_ptr(info.name).to_str().unwrap().to_string();
420            usage.insert(info_name, info.value);
421        }
422        BNFreeMemoryUsageInfo(info_ptr, count);
423    }
424    usage
425}
426
427pub fn version() -> String {
428    unsafe { BnString::into_string(BNGetVersionString()) }
429}
430
431pub fn build_id() -> u32 {
432    unsafe { BNGetBuildId() }
433}
434
435#[derive(Clone, PartialEq, Eq, Hash, Debug)]
436pub struct VersionInfo {
437    pub major: u32,
438    pub minor: u32,
439    pub build: u32,
440    pub channel: String,
441}
442
443impl VersionInfo {
444    pub(crate) fn from_raw(value: &BNVersionInfo) -> Self {
445        Self {
446            major: value.major,
447            minor: value.minor,
448            build: value.build,
449            // NOTE: Because of plugin manager the channel might not be filled.
450            channel: raw_to_string(value.channel).unwrap_or_default(),
451        }
452    }
453
454    pub(crate) fn from_owned_raw(value: BNVersionInfo) -> Self {
455        let owned = Self::from_raw(&value);
456        Self::free_raw(value);
457        owned
458    }
459
460    pub(crate) fn into_owned_raw(value: &Self) -> BNVersionInfo {
461        BNVersionInfo {
462            major: value.major,
463            minor: value.minor,
464            build: value.build,
465            channel: value.channel.as_ptr() as *mut c_char,
466        }
467    }
468
469    pub(crate) fn free_raw(value: BNVersionInfo) {
470        unsafe { BnString::free_raw(value.channel) };
471    }
472}
473
474impl TryFrom<&str> for VersionInfo {
475    type Error = ();
476
477    fn try_from(value: &str) -> Result<Self, Self::Error> {
478        let string = value.to_cstr();
479        let result = unsafe { BNParseVersionString(string.as_ptr()) };
480        if result.build == 0 && result.channel.is_null() && result.major == 0 && result.minor == 0 {
481            return Err(());
482        }
483        Ok(Self::from_owned_raw(result))
484    }
485}
486
487impl PartialOrd for VersionInfo {
488    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
489        Some(self.cmp(other))
490    }
491}
492
493impl Ord for VersionInfo {
494    fn cmp(&self, other: &Self) -> cmp::Ordering {
495        if self == other {
496            return cmp::Ordering::Equal;
497        }
498        let bn_version_0 = VersionInfo::into_owned_raw(self);
499        let bn_version_1 = VersionInfo::into_owned_raw(other);
500        if unsafe { BNVersionLessThan(bn_version_0, bn_version_1) } {
501            cmp::Ordering::Less
502        } else {
503            cmp::Ordering::Greater
504        }
505    }
506}
507
508impl Display for VersionInfo {
509    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
510        if self.channel.is_empty() {
511            write!(f, "{}.{}.{}", self.major, self.minor, self.build)
512        } else {
513            write!(
514                f,
515                "{}.{}.{}-{}",
516                self.major, self.minor, self.build, self.channel
517            )
518        }
519    }
520}
521
522pub fn version_info() -> VersionInfo {
523    let info_raw = unsafe { BNGetVersionInfo() };
524    VersionInfo::from_owned_raw(info_raw)
525}
526
527pub fn serial_number() -> String {
528    unsafe { BnString::into_string(BNGetSerialNumber()) }
529}
530
531pub fn is_license_validated() -> bool {
532    unsafe { BNIsLicenseValidated() }
533}
534
535pub fn licensed_user_email() -> String {
536    unsafe { BnString::into_string(BNGetLicensedUserEmail()) }
537}
538
539pub fn license_path() -> PathBuf {
540    user_directory().join("license.dat")
541}
542
543pub fn license_count() -> i32 {
544    unsafe { BNGetLicenseCount() }
545}
546
547#[derive(Clone, Debug, Eq, PartialEq)]
548pub struct LicenseAddon {
549    pub id: String,
550    pub license_serial: String,
551    pub product: String,
552    pub created_timestamp: u64,
553    pub expiration_timestamp: u64,
554    pub signature: String,
555}
556
557pub fn license_addons() -> Vec<LicenseAddon> {
558    let mut count = 0;
559    let addons = unsafe { BNGetLicenseAddons(&mut count) };
560    if addons.is_null() {
561        return Vec::new();
562    }
563
564    let result = unsafe { std::slice::from_raw_parts(addons, count) }
565        .iter()
566        .map(|addon| LicenseAddon {
567            id: unsafe { CStr::from_ptr(addon.id).to_string_lossy().into_owned() },
568            license_serial: unsafe {
569                CStr::from_ptr(addon.licenseSerial)
570                    .to_string_lossy()
571                    .into_owned()
572            },
573            product: unsafe { CStr::from_ptr(addon.product).to_string_lossy().into_owned() },
574            created_timestamp: addon.createdTimestamp,
575            expiration_timestamp: addon.expirationTimestamp,
576            signature: unsafe {
577                CStr::from_ptr(addon.signature)
578                    .to_string_lossy()
579                    .into_owned()
580            },
581        })
582        .collect();
583    unsafe { BNFreeLicenseAddons(addons, count) };
584    result
585}
586
587/// Set the license that will be used once the core initializes. You can reset the license by passing `None`.
588///
589/// If not set, the normal license retrieval will occur:
590/// 1. Check the BN_LICENSE environment variable
591/// 2. Check the Binary Ninja user directory for license.dat
592#[cfg(not(feature = "demo"))]
593pub fn set_license(license: Option<&str>) {
594    let license = license.unwrap_or_default().to_cstr();
595    unsafe { BNSetLicense(license.as_ptr()) }
596}
597
598#[cfg(feature = "demo")]
599pub fn set_license(_license: Option<&str>) {}
600
601pub fn product() -> String {
602    unsafe { BnString::into_string(BNGetProduct()) }
603}
604
605pub fn product_type() -> String {
606    unsafe { BnString::into_string(BNGetProductType()) }
607}
608
609pub fn license_expiration_time() -> std::time::SystemTime {
610    let m = std::time::Duration::from_secs(unsafe { BNGetLicenseExpirationTime() });
611    std::time::UNIX_EPOCH + m
612}
613
614pub fn is_ui_enabled() -> bool {
615    unsafe { BNIsUIEnabled() }
616}
617
618pub fn is_database(file: &Path) -> bool {
619    let filename = file.to_cstr();
620    unsafe { BNIsDatabase(filename.as_ptr()) }
621}
622
623pub fn plugin_abi_version() -> u32 {
624    BN_CURRENT_CORE_ABI_VERSION
625}
626
627pub fn plugin_abi_minimum_version() -> u32 {
628    BN_MINIMUM_CORE_ABI_VERSION
629}
630
631pub fn core_abi_version() -> u32 {
632    unsafe { BNGetCurrentCoreABIVersion() }
633}
634
635pub fn core_abi_minimum_version() -> u32 {
636    unsafe { BNGetMinimumCoreABIVersion() }
637}
638
639pub fn plugin_ui_abi_version() -> u32 {
640    BN_CURRENT_UI_ABI_VERSION
641}
642
643pub fn plugin_ui_abi_minimum_version() -> u32 {
644    BN_MINIMUM_UI_ABI_VERSION
645}
646
647pub fn add_required_plugin_dependency(name: &str) {
648    let raw_name = name.to_cstr();
649    unsafe { BNAddRequiredPluginDependency(raw_name.as_ptr()) };
650}
651
652pub fn add_optional_plugin_dependency(name: &str) {
653    let raw_name = name.to_cstr();
654    unsafe { BNAddOptionalPluginDependency(raw_name.as_ptr()) };
655}
656
657/// Exported function to tell the core what core ABI version this plugin was compiled against.
658#[cfg(not(feature = "no_exports"))]
659#[no_mangle]
660#[allow(non_snake_case)]
661pub extern "C" fn CorePluginABIVersion() -> u32 {
662    plugin_abi_version()
663}
664
665/// Exported function to tell the core what UI ABI version this plugin was compiled against.
666#[cfg(not(feature = "no_exports"))]
667#[no_mangle]
668pub extern "C" fn UIPluginABIVersion() -> u32 {
669    plugin_ui_abi_version()
670}