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