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