Skip to main content

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