// Copyright 2021 Vector 35 Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // TODO : These docs are here, but could afford to be cleaned up //! Parsers and providers of debug information to Binary Ninja. //! //! The debug information is used by Binary Ninja as ground-truth information about the attributes of functions, //! types, and variables that Binary Ninja's analysis pipeline would otherwise work to deduce. By providing //! debug info, Binary Ninja's output can be generated quicker, more accurately, and more completely. //! //! A DebugInfoParser consists of: //! 1. A name //! 2. An `is_valid` function which takes a BV and returns a bool //! 3. A `parse` function which takes a `DebugInfo` object and uses the member functions `add_type`, `add_function`, and `add_data_variable` to populate all the info it can. //! And finally calling `binaryninja::debuginfo::DebugInfoParser::register` to register it with the core. //! //! Here's a minimal, complete example boilerplate-plugin: //! ``` //! use binaryninja::{ //! binaryview::BinaryView, //! debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser}, //! }; //! //! struct ExampleDebugInfoParser; //! //! impl CustomDebugInfoParser for ExampleDebugInfoParser { //! fn is_valid(&self, _view: &BinaryView) -> bool { //! true //! } //! //! fn parse_info(&self, _debug_info: &mut DebugInfo, _view: &BinaryView) { //! println!("Parsing info"); //! } //! } //! //! #[no_mangle] //! pub extern "C" fn CorePluginInit() -> bool { //! DebugInfoParser::register("example debug info parser", ExampleDebugInfoParser {}); //! true //! } //! ``` //! //! `DebugInfo` can then be automatically applied to valid binary views (via the "Parse and Apply Debug Info" setting), or manually fetched/applied as bellow: //! ``` //! let valid_parsers = DebugInfoParser::parsers_for_view(bv); //! let parser = valid_parsers[0]; //! let debug_info = parser.parse_debug_info(bv); //! bv.apply_debug_info(debug_info); //! ``` //! //! Multiple debug-info parsers can manually contribute debug info for a binary view by simply calling `parse_debug_info` with the //! `DebugInfo` object just returned. This is automatic when opening a binary view with multiple valid debug info parsers. If you //! wish to set the debug info for a binary view without applying it as well, you can call `binaryninja::binaryview::BinaryView::set_debug_info`. use binaryninjacore_sys::*; use crate::{ architecture::{Architecture, CoreArchitecture}, binaryview::BinaryView, callingconvention::CallingConvention, platform::Platform, rc::*, string::{raw_to_string, BnStrCompatible, BnString}, types::{DataVariableAndName, NameAndType, Type}, }; use std::{hash::Hash, mem, os::raw::c_void, ptr, slice}; ////////////////////// // DebugInfoParser /// Represents the registered parsers and providers of debug information to Binary Ninja. /// See `binaryninja::debuginfo` for more information #[derive(PartialEq, Eq, Hash)] pub struct DebugInfoParser { pub(crate) handle: *mut BNDebugInfoParser, } impl DebugInfoParser { pub(crate) unsafe fn from_raw(handle: *mut BNDebugInfoParser) -> Ref { debug_assert!(!handle.is_null()); Ref::new(Self { handle }) } /// Returns debug info parser of the given name, if it exists pub fn from_name(name: S) -> Result, ()> { let name = name.as_bytes_with_nul(); let parser = unsafe { BNGetDebugInfoParserByName(name.as_ref().as_ptr() as *mut _) }; if parser.is_null() { Err(()) } else { unsafe { Ok(Self::from_raw(parser)) } } } /// List all debug-info parsers pub fn list() -> Array { let mut count: usize = unsafe { mem::zeroed() }; let raw_parsers = unsafe { BNGetDebugInfoParsers(&mut count as *mut _) }; unsafe { Array::new(raw_parsers, count, ()) } } /// Returns a list of debug-info parsers that are valid for the provided binary view pub fn parsers_for_view(bv: &BinaryView) -> Array { let mut count: usize = unsafe { mem::zeroed() }; let raw_parsers = unsafe { BNGetDebugInfoParsersForView(bv.handle, &mut count as *mut _) }; unsafe { Array::new(raw_parsers, count, ()) } } /// Returns the name of the current parser pub fn name(&self) -> BnString { unsafe { BnString::from_raw(BNGetDebugInfoParserName(self.handle)) } } /// Returns whether this debug-info parser is valid for the provided binary view pub fn is_valid_for_view(&self, view: &BinaryView) -> bool { unsafe { BNIsDebugInfoParserValidForView(self.handle, view.handle) } } /// Returns a `DebugInfo` object populated with debug info by this debug-info parser. Only provide a `DebugInfo` object if you wish to append to the existing debug info pub fn parse_debug_info( &self, view: &BinaryView, existing_debug_info: Option<&DebugInfo>, ) -> Ref { match existing_debug_info { Some(debug_info) => unsafe { DebugInfo::from_raw(BNParseDebugInfo( self.handle, view.handle, debug_info.handle, )) }, None => unsafe { DebugInfo::from_raw(BNParseDebugInfo(self.handle, view.handle, ptr::null_mut())) }, } } // Registers a DebugInfoParser. See `binaryninja::debuginfo::DebugInfoParser` for more details. pub fn register(name: S, parser_callbacks: C) -> Ref where S: BnStrCompatible, C: CustomDebugInfoParser, { extern "C" fn cb_is_valid(ctxt: *mut c_void, view: *mut BNBinaryView) -> bool where C: CustomDebugInfoParser, { ffi_wrap!("CustomDebugInfoParser::is_valid", unsafe { let cmd = &*(ctxt as *const C); let view = BinaryView::from_raw(view); cmd.is_valid(&view) }) } extern "C" fn cb_parse_info( ctxt: *mut c_void, debug_info: *mut BNDebugInfo, view: *mut BNBinaryView, ) where C: CustomDebugInfoParser, { ffi_wrap!("CustomDebugInfoParser::parse_info", unsafe { let cmd = &*(ctxt as *const C); let view = BinaryView::from_raw(view); let mut debug_info = DebugInfo::from_raw(debug_info); cmd.parse_info(&mut debug_info, &view); }) } let name = name.as_bytes_with_nul(); let name_ptr = name.as_ref().as_ptr() as *mut _; let ctxt = Box::into_raw(Box::new(parser_callbacks)); unsafe { DebugInfoParser::from_raw(BNRegisterDebugInfoParser( name_ptr, Some(cb_is_valid::), Some(cb_parse_info::), ctxt as *mut _, )) } } } unsafe impl RefCountable for DebugInfoParser { unsafe fn inc_ref(handle: &Self) -> Ref { Ref::new(Self { handle: BNNewDebugInfoParserReference(handle.handle), }) } unsafe fn dec_ref(handle: &Self) { BNFreeDebugInfoParserReference(handle.handle); } } impl AsRef for DebugInfoParser { fn as_ref(&self) -> &Self { self } } impl ToOwned for DebugInfoParser { type Owned = Ref; fn to_owned(&self) -> Self::Owned { unsafe { RefCountable::inc_ref(self) } } } unsafe impl CoreOwnedArrayProvider for DebugInfoParser { type Raw = *mut BNDebugInfoParser; type Context = (); unsafe fn free(raw: *mut Self::Raw, count: usize, _: &Self::Context) { BNFreeDebugInfoParserList(raw, count); } } /////////////////////// // DebugFunctionInfo /// Collates ground-truth function-external attributes for use in BinaryNinja's internal analysis. /// /// When contributing function info, provide only what you know - BinaryNinja will figure out everything else that it can, as it usually does. /// /// Functions will not be created if an address is not provided, but will be able to be queried from debug info for later user analysis. pub struct DebugFunctionInfo { short_name: Option, full_name: Option, raw_name: Option, return_type: Option>, address: u64, parameters: Vec<(S2, Ref)>, variable_parameters: bool, calling_convention: Option>>, platform: Option>, } impl From<&BNDebugFunctionInfo> for DebugFunctionInfo { fn from(raw: &BNDebugFunctionInfo) -> Self { let raw_parameter_names: &[*mut ::std::os::raw::c_char] = unsafe { slice::from_raw_parts(raw.parameterNames as *mut _, raw.parameterCount) }; let raw_parameter_types: &[*mut BNType] = unsafe { slice::from_raw_parts(raw.parameterTypes as *mut _, raw.parameterCount) }; let parameters: Vec<(String, Ref)> = (0..raw.parameterCount) .map(|i| { (raw_to_string(raw_parameter_names[i]).unwrap(), unsafe { Type::ref_from_raw(raw_parameter_types[i]) }) }) .collect(); Self { short_name: raw_to_string(raw.shortName), full_name: raw_to_string(raw.fullName), raw_name: raw_to_string(raw.rawName), return_type: if raw.returnType.is_null() { None } else { Some(unsafe { Type::ref_from_raw(raw.returnType) }) }, address: raw.address, parameters, variable_parameters: raw.variableParameters, calling_convention: if raw.callingConvention.is_null() { None } else { Some(unsafe { CallingConvention::ref_from_raw( raw.callingConvention, CoreArchitecture::from_raw(BNGetCallingConventionArchitecture( raw.callingConvention, )), ) }) }, platform: if raw.returnType.is_null() { None } else { Some(unsafe { Platform::ref_from_raw(raw.platform) }) }, } } } impl DebugFunctionInfo { pub fn new( short_name: Option, full_name: Option, raw_name: Option, return_type: Option>, address: Option, parameters: Option)>>, variable_parameters: Option, calling_convention: Option>>, platform: Option>, ) -> Self { Self { short_name, full_name, raw_name, return_type, address: match address { Some(address) => address, _ => 0, }, parameters: match parameters { Some(parameters) => parameters, _ => vec![], }, variable_parameters: match variable_parameters { Some(variable_parameters) => variable_parameters, _ => false, }, calling_convention, platform, } } } /////////////// // DebugInfo /// Provides an interface to both provide and query debug info. The DebugInfo object is used /// internally by the binary view to which it is applied to determine the attributes of functions, types, and variables /// that would otherwise be costly to deduce. /// /// DebugInfo objects themselves are independent of binary views; their data can be sourced from any arbitrary binary /// views and be applied to any other arbitrary binary view. A DebugInfo object can also contain debug info from multiple /// DebugInfoParsers. This makes it possible to gather debug info that may be distributed across several different /// formats and files. /// /// DebugInfo cannot be instantiated by the user, instead get it from either the binary view (see `binaryninja::binaryview::BinaryView::debug_info`) /// or a debug-info parser (see `binaryninja::debuginfo::DebugInfoParser::parse_debug_info`). /// /// Please note that calling one of `add_*` functions will not work outside of a debuginfo plugin. #[derive(PartialEq, Eq, Hash)] pub struct DebugInfo { pub(crate) handle: *mut BNDebugInfo, } impl DebugInfo { pub(crate) unsafe fn from_raw(handle: *mut BNDebugInfo) -> Ref { debug_assert!(!handle.is_null()); Ref::new(Self { handle }) } /// Returns a generator of all types provided by a named DebugInfoParser pub fn types_by_name(&self, parser_name: S) -> Vec> { let parser_name = parser_name.as_bytes_with_nul(); let mut count: usize = 0; let debug_types_ptr = unsafe { BNGetDebugTypes( self.handle, parser_name.as_ref().as_ptr() as *mut _, &mut count, ) }; let result: Vec> = unsafe { slice::from_raw_parts_mut(debug_types_ptr, count) .iter() .map(NameAndType::::from_raw) .collect() }; unsafe { BNFreeDebugTypes(debug_types_ptr, count) }; result } /// A generator of all types provided by DebugInfoParsers pub fn types(&self) -> Vec> { let mut count: usize = 0; let debug_types_ptr = unsafe { BNGetDebugTypes(self.handle, ptr::null_mut(), &mut count) }; let result: Vec> = unsafe { slice::from_raw_parts_mut(debug_types_ptr, count) .iter() .map(NameAndType::::from_raw) .collect() }; unsafe { BNFreeDebugTypes(debug_types_ptr, count) }; result } /// Returns a generator of all functions provided by a named DebugInfoParser pub fn functions_by_name( &self, parser_name: S, ) -> Vec> { let parser_name = parser_name.as_bytes_with_nul(); let mut count: usize = 0; let functions_ptr = unsafe { BNGetDebugFunctions( self.handle, parser_name.as_ref().as_ptr() as *mut _, &mut count, ) }; let result: Vec> = unsafe { slice::from_raw_parts_mut(functions_ptr, count) .iter() .map(DebugFunctionInfo::::from) .collect() }; unsafe { BNFreeDebugFunctions(functions_ptr, count) }; result } /// A generator of all functions provided by DebugInfoParsers pub fn functions(&self) -> Vec> { let mut count: usize = 0; let functions_ptr = unsafe { BNGetDebugFunctions(self.handle, ptr::null_mut(), &mut count) }; let result: Vec> = unsafe { slice::from_raw_parts_mut(functions_ptr, count) .iter() .map(DebugFunctionInfo::::from) .collect() }; unsafe { BNFreeDebugFunctions(functions_ptr, count) }; result } /// Returns a generator of all data variables provided by a named DebugInfoParser pub fn data_variables_by_name( &self, parser_name: S, ) -> Vec> { let parser_name = parser_name.as_bytes_with_nul(); let mut count: usize = 0; let data_variables_ptr = unsafe { BNGetDebugDataVariables( self.handle, parser_name.as_ref().as_ptr() as *mut _, &mut count, ) }; let result: Vec> = unsafe { slice::from_raw_parts_mut(data_variables_ptr, count) .iter() .map(DataVariableAndName::::from_raw) .collect() }; unsafe { BNFreeDataVariablesAndName(data_variables_ptr, count) }; result } /// A generator of all data variables provided by DebugInfoParsers pub fn data_variables(&self) -> Vec> { let mut count: usize = 0; let data_variables_ptr = unsafe { BNGetDebugDataVariables(self.handle, ptr::null_mut(), &mut count) }; let result: Vec> = unsafe { slice::from_raw_parts_mut(data_variables_ptr, count) .iter() .map(DataVariableAndName::::from_raw) .collect() }; unsafe { BNFreeDataVariablesAndName(data_variables_ptr, count) }; result } /// Adds a type scoped under the current parser's name to the debug info pub fn add_type(&mut self, name: S, new_type: &Type) -> bool { let name = name.as_bytes_with_nul(); unsafe { BNAddDebugType( self.handle, name.as_ref().as_ptr() as *mut _, new_type.handle, ) } } /// Adds a function scoped under the current parser's name to the debug info pub fn add_function( &mut self, new_func: DebugFunctionInfo, ) -> bool { let parameter_count: usize = new_func.parameters.len(); let short_name_bytes = new_func.short_name.map(|name| name.as_bytes_with_nul()); let short_name = short_name_bytes .as_ref() .map_or(ptr::null_mut() as *mut _, |name| { name.as_ref().as_ptr() as *mut _ }); let full_name_bytes = new_func.full_name.map(|name| name.as_bytes_with_nul()); let full_name = full_name_bytes .as_ref() .map_or(ptr::null_mut() as *mut _, |name| { name.as_ref().as_ptr() as *mut _ }); let raw_name_bytes = new_func.raw_name.map(|name| name.as_bytes_with_nul()); let raw_name = raw_name_bytes .as_ref() .map_or(ptr::null_mut() as *mut _, |name| { name.as_ref().as_ptr() as *mut _ }); let (mut parameter_names, mut parameter_types, _name_refs): ( Vec<*mut ::std::os::raw::c_char>, Vec<*mut BNType>, _, ) = new_func.parameters.into_iter().fold( ( Vec::with_capacity(parameter_count), Vec::with_capacity(parameter_count), Vec::with_capacity(parameter_count), ), |(mut parameter_names, mut parameter_types, mut name_refs), (n, t)| { let name_ref = n.as_bytes_with_nul(); parameter_names.push(name_ref.as_ref().as_ptr() as *mut _); name_refs.push(name_ref); parameter_types.push(t.handle); (parameter_names, parameter_types, name_refs) }, ); unsafe { BNAddDebugFunction( self.handle, &mut BNDebugFunctionInfo { shortName: short_name, fullName: full_name, rawName: raw_name, address: new_func.address, returnType: match new_func.return_type { Some(return_type) => return_type.handle, _ => ptr::null_mut(), }, parameterNames: match parameter_count { 0 => ptr::null_mut(), _ => parameter_names.as_mut_ptr(), }, parameterTypes: match parameter_count { 0 => ptr::null_mut(), _ => parameter_types.as_mut_ptr(), }, parameterCount: parameter_count, variableParameters: new_func.variable_parameters, callingConvention: match new_func.calling_convention { Some(calling_convention) => calling_convention.handle, _ => ptr::null_mut(), }, platform: match new_func.platform { Some(platform) => platform.handle, _ => ptr::null_mut(), }, }, ) } } /// Adds a data variable scoped under the current parser's name to the debug info pub fn add_data_variable( &self, address: u64, t: &Type, name: Option, ) -> bool { match name { Some(name) => { let name = name.as_bytes_with_nul(); unsafe { BNAddDebugDataVariable( self.handle, address, t.handle, name.as_ref().as_ptr() as *mut _, ) } } None => unsafe { BNAddDebugDataVariable(self.handle, address, t.handle, ptr::null_mut()) }, } } } unsafe impl RefCountable for DebugInfo { unsafe fn inc_ref(handle: &Self) -> Ref { Ref::new(Self { handle: BNNewDebugInfoReference(handle.handle), }) } unsafe fn dec_ref(handle: &Self) { BNFreeDebugInfoReference(handle.handle); } } impl AsRef for DebugInfo { fn as_ref(&self) -> &Self { self } } impl ToOwned for DebugInfo { type Owned = Ref; fn to_owned(&self) -> Self::Owned { unsafe { RefCountable::inc_ref(self) } } } //////////////////////////// // CustomDebugInfoParser /// Implement this trait to implement a debug info parser. See `DebugInfoParser` for more details. pub trait CustomDebugInfoParser: 'static + Sync { fn is_valid(&self, view: &BinaryView) -> bool; fn parse_info(&self, debug_info: &mut DebugInfo, view: &BinaryView); }