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