diff options
Diffstat (limited to 'plugins/svd/src')
| -rw-r--r-- | plugins/svd/src/lib.rs | 112 | ||||
| -rw-r--r-- | plugins/svd/src/mapper.rs | 650 | ||||
| -rw-r--r-- | plugins/svd/src/settings.rs | 84 |
3 files changed, 846 insertions, 0 deletions
diff --git a/plugins/svd/src/lib.rs b/plugins/svd/src/lib.rs new file mode 100644 index 00000000..34bb0832 --- /dev/null +++ b/plugins/svd/src/lib.rs @@ -0,0 +1,112 @@ +pub mod mapper; +pub mod settings; + +use crate::mapper::DeviceMapper; +use crate::settings::LoadSettings; +use binaryninja::binary_view::{BinaryView, BinaryViewBase, BinaryViewExt}; +use binaryninja::command::Command; +use binaryninja::logger::Logger; +use binaryninja::workflow::{Activity, AnalysisContext, Workflow}; +use log::LevelFilter; + +pub const LOADER_ACTIVITY_NAME: &str = "analysis.svd.loader"; +const LOADER_ACTIVITY_CONFIG: &str = r#"{ + "name": "analysis.svd.loader", + "title" : "SVD Loader", + "description": "This analysis step applies SVD info to the view...", + "eligibility": { + "auto": {}, + "runOnce": true + } +}"#; + +struct LoadSVDFile; + +impl Command for LoadSVDFile { + fn action(&self, view: &BinaryView) { + let Some(file) = + binaryninja::interaction::get_open_filename_input("Select a .svd file", "*.svd") + else { + return; + }; + + let file_content = match std::fs::read_to_string(&file) { + Ok(content) => content, + Err(e) => { + log::error!("Failed to read file: {}", e); + return; + } + }; + + match svd_parser::parse(&file_content) { + Ok(device) => { + // We have a supported svd device. map it! + let load_settings = LoadSettings::from_view_settings(view); + let address_size = view.address_size(); + let mapper = DeviceMapper::new(load_settings, address_size, device); + mapper.map_to_view(view); + view.update_analysis(); + } + Err(e) => { + log::error!("Failed to parse SVD file: {}", e); + } + } + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn CorePluginInit() -> bool { + Logger::new("SVD").with_level(LevelFilter::Debug).init(); + + binaryninja::command::register_command( + "Load SVD File", + "Loads an SVD file into the current view.", + LoadSVDFile {}, + ); + + // Register settings globally. + LoadSettings::register(); + + let loader_activity = |ctx: &AnalysisContext| { + let view = ctx.view(); + let load_settings = LoadSettings::from_view_settings(&view); + let Some(file) = &load_settings.auto_load_file else { + log::debug!("No SVD file specified, skipping..."); + return; + }; + let file_content = match std::fs::read_to_string(file) { + Ok(content) => content, + Err(e) => { + log::error!("Failed to read file: {}", e); + return; + } + }; + match svd_parser::parse(&file_content) { + Ok(device) => { + let address_size = view.address_size(); + let mapper = DeviceMapper::new(load_settings, address_size, device); + mapper.map_to_view(&view); + } + Err(e) => { + log::error!("Failed to parse SVD file: {}", e); + } + } + }; + + // Register new workflow activity to load svd information. + let old_module_meta_workflow = Workflow::instance("core.module.metaAnalysis"); + let module_meta_workflow = old_module_meta_workflow.clone("core.module.metaAnalysis"); + let loader_activity = Activity::new_with_action(LOADER_ACTIVITY_CONFIG, loader_activity); + module_meta_workflow + .register_activity(&loader_activity) + .unwrap(); + module_meta_workflow.insert("core.module.loadDebugInfo", [LOADER_ACTIVITY_NAME]); + module_meta_workflow.register().unwrap(); + + true +} diff --git a/plugins/svd/src/mapper.rs b/plugins/svd/src/mapper.rs new file mode 100644 index 00000000..bf305eb2 --- /dev/null +++ b/plugins/svd/src/mapper.rs @@ -0,0 +1,650 @@ +use crate::settings::LoadSettings; +use binaryninja::binary_view::{BinaryView, BinaryViewExt}; +use binaryninja::confidence::{Conf, MAX_CONFIDENCE}; +use binaryninja::data_buffer::DataBuffer; +use binaryninja::rc::Ref; +use binaryninja::section::{SectionBuilder, Semantics}; +use binaryninja::segment::{SegmentBuilder, SegmentFlags}; +use binaryninja::symbol::{SymbolBuilder, SymbolType}; +use binaryninja::types::{ + BaseStructure, EnumerationBuilder, MemberAccess, MemberScope, NamedTypeReference, + NamedTypeReferenceClass, StructureBuilder, StructureMember, StructureType, Type, TypeBuilder, +}; +use std::collections::HashMap; +use std::num::NonZeroUsize; +use svd_parser::svd::{ + Access, AddressBlock, AddressBlockUsage, DataType, Device, EnumeratedValues, Field, FieldInfo, + Peripheral, PeripheralInfo, Register, RegisterCluster, RegisterInfo, Usage, +}; + +pub fn byte_aligned(bit_width: u32) -> bool { + bit_width % 8 == 0 +} + +/// Byte aligned width for bit-width +pub fn byte_width(bit_width: u32) -> u32 { + if byte_aligned(bit_width) { + bit_width / 8 + } else { + (bit_width / 8) + 1 + } +} + +#[derive(Clone, Debug)] +pub struct AddressBlockMemoryInfo { + pub name: String, + pub segment: SegmentBuilder, + pub segment_flags: SegmentFlags, + pub section: SectionBuilder, +} + +pub struct DeviceMapper { + settings: LoadSettings, + device: Device, + address_size: usize, +} + +impl DeviceMapper { + pub fn new(settings: LoadSettings, address_size: usize, mut device: Device) -> Self { + svd_parser::expand_properties(&mut device); + + // TODO: Until https://github.com/rust-embedded/svd/issues/288 is fixed + let mut new_device = device.clone(); + new_device.peripherals.clear(); + for peripheral in &device.peripherals { + let mut new_peripheral = peripheral.clone(); + if let Some(derived_periph_name) = &peripheral.derived_from { + // Add derived address blocks. + // TODO: Should this not be done by svd_parser::expand? + // TODO: Should this be recursive? + if let Some(derived_peripheral) = device.get_peripheral(derived_periph_name) { + if let Some(address_blocks) = &derived_peripheral.address_block { + new_peripheral + .address_block + .get_or_insert_with(Vec::new) + .extend(address_blocks.to_owned()); + } + } + } + new_device.peripherals.push(new_peripheral); + } + + // TODO: Return error instead. + let expanded_device = svd_parser::expand(&new_device).expect("Failed to expand device!"); + Self { + settings, + device: expanded_device, + address_size, + } + } + + pub fn map_to_view(&self, view: &BinaryView) { + log::info!("Mapping device... {}", self.device.name); + for peripheral in &self.device.peripherals { + match peripheral { + Peripheral::Single(info) => { + self.map_peripheral_to_view(view, info); + } + Peripheral::Array(_info, _elem) => { + // TODO: How do we handle this? + // TODO: I guess we will need to update the base address? + // TODO: expand feature solves this. + } + } + } + } + + // TODO: Add address blocks from derived peripherals? + pub fn map_peripheral_to_view(&self, view: &BinaryView, peripheral: &PeripheralInfo) { + if let Some(address_blocks) = &peripheral.address_block { + for address_block in address_blocks { + self.map_peripheral_block_to_view(view, peripheral, address_block); + } + } + } + + pub fn map_peripheral_block_to_view( + &self, + view: &BinaryView, + peripheral: &PeripheralInfo, + address_block: &AddressBlock, + ) { + let block_addr = peripheral.base_address + address_block.offset as u64; + log::info!( + "Mapping peripheral block @ 0x{:x} for {}", + block_addr, + peripheral.name + ); + let memory_info = self.peripheral_block_memory_info(peripheral, address_block); + + // Add the block segment, section and backing memory. + let data_memory = DataBuffer::new(&vec![0; address_block.size as usize]).unwrap(); + let added_memory = view.memory_map().add_data_memory_region( + &memory_info.name, + block_addr, + &data_memory, + Some(memory_info.segment_flags), + ); + view.add_segment(memory_info.segment); + view.add_section(memory_info.section); + + if !added_memory { + log::error!( + "Failed to add memory for peripheral block! {} @ 0x{:x}", + memory_info.name, + block_addr + ); + } + + // Handle usage specific stuff like adding registers. + match address_block.usage { + AddressBlockUsage::Registers => { + // Registers get comments + if self.settings.add_comments { + if let Some(periph_desc) = &peripheral.description { + // Add peripheral description + view.set_comment_at(block_addr, periph_desc); + } + // Add register descriptions + self.add_comments_for_registers(view, peripheral, address_block); + } + + // Registers will get the peripheral type. + let peripheral_ty = self.peripheral_type(peripheral); + let peripheral_ty_id = format!("SVD:{}", peripheral.name); + let id = view.define_auto_type_with_id( + &peripheral.name, + peripheral_ty_id, + &peripheral_ty, + ); + let ntr = + NamedTypeReference::new(NamedTypeReferenceClass::StructNamedTypeClass, id); + view.define_auto_data_var(block_addr, &Type::named_type(&ntr)); + let symbol = + SymbolBuilder::new(SymbolType::Data, peripheral.name.to_owned(), block_addr) + .create(); + view.define_auto_symbol(&symbol); + } + AddressBlockUsage::Buffer => { + let array_ty = Type::array(&Type::int(1, false), address_block.size as u64); + view.define_auto_data_var(block_addr, &array_ty); + let symbol_name = format!("buffer_0x{:x}", block_addr); + let symbol = SymbolBuilder::new(SymbolType::Data, symbol_name, block_addr).create(); + view.define_auto_symbol(&symbol); + view.set_comment_at( + block_addr, + format!("Buffer block with size {}", address_block.size), + ); + } + AddressBlockUsage::Reserved => { + // TODO: What to do for reserved blocks? + view.set_comment_at( + block_addr, + format!("Reserved block with size {}", address_block.size), + ); + } + } + } + + pub fn add_comments_for_registers( + &self, + view: &BinaryView, + peripheral: &PeripheralInfo, + address_block: &AddressBlock, + ) { + let block_addr = peripheral.base_address + address_block.offset as u64; + // Adding comments will add a bunch of undo actions. + let undo_id = view.file().begin_undo_actions(true); + for register in peripheral.all_registers() { + // TODO: The register offset is the enclosing element. + // TODO: We need to add a recursive function that keeps track of the offset. + let register_addr = block_addr + register.address_offset as u64; + if let Some(description) = ®ister.description { + view.set_comment_at(register_addr, description); + } + + // TODO: Add a setting to disable field comments + if let Some(fields) = ®ister.fields { + let (aligned, unaligned): (Vec<_>, Vec<_>) = fields.iter().partition(|f| { + byte_aligned(f.bit_range.width) && byte_aligned(f.bit_range.offset) + }); + + for field in aligned { + let field_byte_offset = field.bit_range.offset / 8; + let field_addr = register_addr + field_byte_offset as u64; + if let Some(description) = &field.description { + view.set_comment_at(field_addr, description); + } + } + + let mut unaligned_comments = HashMap::new(); + for field in unaligned { + // For unaligned fields we want to provide more information such as the bit offset and width. + let field_byte_offset = field.bit_range.offset / 8; + let field_bit_width = field.bit_range.width; + let field_addr = register_addr + field_byte_offset as u64; + let mut field_comment = format!( + "{}-{} {}", + field.bit_range.offset, + field.bit_range.offset + field_bit_width - 1, + field.name + ); + + if let Some(description) = &field.description { + field_comment.push_str(&format!(": {}", description)); + } + + unaligned_comments + .entry(field_addr) + .or_insert_with(Vec::new) + .push(field_comment); + } + + for (field_addr, comments) in unaligned_comments { + let comment = comments.join("\n"); + view.set_comment_at(field_addr, comment); + } + } + } + view.file().commit_undo_actions(undo_id); + } + + pub fn peripheral_block_memory_info( + &self, + peripheral: &PeripheralInfo, + address_block: &AddressBlock, + ) -> AddressBlockMemoryInfo { + let block_addr = peripheral.base_address + address_block.offset as u64; + let block_range = block_addr..(block_addr + address_block.size as u64); + let block_name = if address_block.offset == 0 { + // Block name: "PERIPH" + peripheral.name.to_owned() + } else { + // Block name: "PERIPH_0x40" + format!("{}_0x{:x}", peripheral.name, address_block.offset) + }; + + let block_access = peripheral.default_register_properties.access; + let semantics = match block_access { + Some(Access::ReadOnly) => Semantics::ReadOnlyData, + Some(Access::ReadWrite | Access::ReadWriteOnce) => Semantics::ReadWriteData, + // NOTE: Binary Ninja has no concept of write-only section semantics. + Some(Access::WriteOnce | Access::WriteOnly) => Semantics::ReadWriteData, + // TODO: This should never happen. We use the expand feature of svd_parser + None => Semantics::ReadWriteData, + }; + + let (readable, writable) = match block_access { + Some(Access::ReadOnly) => (true, false), + Some(Access::ReadWrite | Access::ReadWriteOnce) => (true, true), + Some(Access::WriteOnce | Access::WriteOnly) => (false, true), + None => (true, true), + }; + + let section_type_str = match address_block.protection { + Some(protection) => { + // Section type: "peripheral:s" + format!("peripheral:{}", protection.as_str()) + } + None => { + // Section type: "peripheral" + "peripheral".to_string() + } + }; + + let section = SectionBuilder::new(block_name.clone(), block_range.clone()) + .section_type(section_type_str) + .semantics(semantics); + let segment_flags = SegmentFlags::new() + .contains_code(false) + .contains_data(true) + .deny_execute(true) + .readable(readable) + .writable(writable); + let segment = SegmentBuilder::new(block_range).flags(segment_flags); + + AddressBlockMemoryInfo { + name: block_name, + segment, + segment_flags, + section, + } + } + + // TODO: In the future we might need to have partial types for each [`AddressBlock`] + // TODO: Support using header name, this requires we define the peripheral type id as the real peripheral name. + // TODO: cont. the reason is so that we can resolve the derived peripheral. + pub fn peripheral_type(&self, peripheral: &PeripheralInfo) -> Ref<Type> { + let mut peripheral_struct = StructureBuilder::new(); + + if let Some(derived_periph_name) = &peripheral.derived_from { + // We will create an NTR to ref the derived peripheral type. + let ntr = NamedTypeReference::new( + NamedTypeReferenceClass::StructNamedTypeClass, + derived_periph_name, + ); + let base_struct = BaseStructure::new(ntr, 0, 0); + peripheral_struct.base_structures(&[base_struct]); + } + + // TODO: Support non-contiguous register address blocks (i.e. partial types). + if let Some(address_blocks) = &peripheral.address_block { + // If we have more than one address block with registers we likely have an incorrect type. + let register_address_blocks: Vec<_> = address_blocks + .iter() + .filter(|a| a.usage == AddressBlockUsage::Registers) + .collect(); + if register_address_blocks.len() > 1 { + log::warn!( + "Peripheral {} has more than one register address block. The type likely is incorrect.", + peripheral.name + ); + } else if register_address_blocks.len() == 1 { + // Take the address block size and use it as the structure width. + let register_address_block = register_address_blocks[0]; + peripheral_struct.width(register_address_block.size as u64); + } + } + + if let Some(register_clusters) = &peripheral.registers { + for register_cluster in register_clusters { + match register_cluster { + RegisterCluster::Register(register) => { + let register_member = self.register_member(register); + let overwrite = false; // TODO: Handle overwrites? + peripheral_struct.insert_member(register_member, overwrite); + } + RegisterCluster::Cluster(_cluster) => { + // TODO: Support clusters + } + } + } + } + + Type::structure(&peripheral_struct.finalize()) + } + + pub fn register_member(&self, register: &Register) -> StructureMember { + let register_ty = self.register_type(register); + let conf_register_ty = Conf::new(register_ty, MAX_CONFIDENCE); + // TODO: Offset in peripheral + StructureMember::new( + conf_register_ty, + register.name.to_owned(), + register.address_offset as u64, + MemberAccess::PublicAccess, + MemberScope::NoScope, + ) + } + + pub fn register_type(&self, register: &Register) -> Ref<Type> { + match register { + Register::Single(info) => self.single_register_type(info), + Register::Array(info, elem) => { + // TODO: dimIncrement tells us the stride. We should consult that to + // TODO: make sure that the accesses are aligned. + Type::array(&self.single_register_type(info), elem.dim as u64) + } + } + } + + pub fn single_register_type(&self, register: &RegisterInfo) -> Ref<Type> { + match register.datatype { + Some(data_type) => self.data_type(&data_type), + None => { + // No data type means we have a structure! + let mut register_struct = StructureBuilder::new(); + + // Constrain the width of the struct to the register size if available. + if let Some(register_size) = register.properties.size { + let register_byte_size = byte_width(register_size); + register_struct.width(register_byte_size as u64); + } + + if let Some(derived_register_name) = ®ister.derived_from { + // We will create an NTR to ref the derived register type. + let ntr = NamedTypeReference::new( + NamedTypeReferenceClass::StructNamedTypeClass, + derived_register_name, + ); + let base_struct = BaseStructure::new(ntr, 0, 0); + register_struct.base_structures(&[base_struct]); + } + + let type_builder = match ®ister.fields { + Some(fields) => { + // Separate bitfields from regular fields. + let (fields, bitfield_items): (Vec<_>, Vec<_>) = + fields.iter().partition(|f| { + byte_aligned(f.bit_range.width) && byte_aligned(f.bit_range.offset) + }); + + for field in fields { + let field_member = self.field_member(field); + let overwrites = true; // TODO: Handle overwrites? + register_struct.insert_member(field_member, overwrites); + } + + if self.settings.add_bitfields { + // The bitfield items need to be coalesced to a map of byte offset to vec of fields. + let mut bitfield_map: HashMap<u64, Vec<&Field>> = HashMap::new(); + + // Sort bitfields by their offset + let mut sorted_bitfields = bitfield_items.iter().collect::<Vec<_>>(); + sorted_bitfields.sort_by_key(|f| f.bit_range.offset); + + // Group bitfields by overlapping bit offsets + let mut current_bit_start = 0; + let mut current_bit_end = 0; + for field in sorted_bitfields { + let bit_start = field.bit_range.offset; + let byte_start = bit_start / 8; + let current_byte_start = current_bit_start / 8; + if current_byte_start != byte_start && current_bit_end < bit_start { + // Make a new bitfield, only if the current field is in a new byte. + current_bit_start = bit_start; + } + current_bit_end = bit_start + field.bit_range.width; + bitfield_map + .entry(current_bit_start as u64) + .or_insert_with(Vec::new) + .push(field); + } + + for (bit_start, fields) in bitfield_map { + // Add each bitfield to the structure! + let byte_start = bit_start / 8; + let bitfield_member = self.bitfield_member(byte_start, fields); + let overwrites = true; // TODO: Handle overwrites? + register_struct.insert_member(bitfield_member, overwrites); + } + } + + TypeBuilder::structure(®ister_struct.finalize()) + } + None if register.derived_from.is_some() => { + // Use the structure so that we get the base fields. + TypeBuilder::structure(®ister_struct.finalize()) + } + None => { + // We don't have any fields, or a derived register, attempt to construct type ourselves. + match register.properties.size { + Some(bit_width) => { + // We have a sized register, convert to byte aligned int. + let byte_aligned_width = byte_width(bit_width); + TypeBuilder::int(byte_aligned_width as usize, false) + } + None => { + // TODO: How can we construct a type here? + panic!("Register {} has no size!", register.name); + } + } + } + }; + + if let Some(Access::ReadOnly) = register.properties.access { + type_builder.set_const(true); + } + + type_builder.finalize() + } + } + } + + // TODO: Register access should be consulted to see if we should set as const. + pub fn data_type(&self, data_type: &DataType) -> Ref<Type> { + match data_type { + DataType::U8 => Type::int(1, false), + DataType::U16 => Type::int(2, false), + DataType::U32 => Type::int(4, false), + DataType::U64 => Type::int(8, false), + DataType::I8 => Type::int(1, true), + DataType::I16 => Type::int(2, true), + DataType::I32 => Type::int(4, true), + DataType::I64 => Type::int(8, true), + // TODO: This can be cleaned up... + DataType::U8Ptr => { + Type::pointer_of_width(&Type::int(1, false), self.address_size, false, false, None) + } + DataType::U16Ptr => { + Type::pointer_of_width(&Type::int(2, false), self.address_size, false, false, None) + } + DataType::U32Ptr => { + Type::pointer_of_width(&Type::int(4, false), self.address_size, false, false, None) + } + DataType::U64Ptr => { + Type::pointer_of_width(&Type::int(8, false), self.address_size, false, false, None) + } + DataType::I8Ptr => { + Type::pointer_of_width(&Type::int(1, true), self.address_size, false, false, None) + } + DataType::I16Ptr => { + Type::pointer_of_width(&Type::int(2, true), self.address_size, false, false, None) + } + DataType::I32Ptr => { + Type::pointer_of_width(&Type::int(4, true), self.address_size, false, false, None) + } + DataType::I64Ptr => { + Type::pointer_of_width(&Type::int(8, true), self.address_size, false, false, None) + } + } + } + + pub fn bitfield_member(&self, byte_offset: u64, fields: Vec<&Field>) -> StructureMember { + let field_ty = self.bitfield_type(byte_offset, fields); + // TODO: Create bitfield name from the fields? + let field_name = format!("bitfield_0x{:x}", byte_offset); + // TODO: This should be like 120 confidence? + let conf_field_ty = Conf::new(field_ty, MAX_CONFIDENCE); + StructureMember::new( + conf_field_ty, + field_name, + byte_offset, + MemberAccess::PublicAccess, + MemberScope::NoScope, + ) + } + + pub fn bitfield_type(&self, byte_offset: u64, fields: Vec<&Field>) -> Ref<Type> { + let mut union_builder = StructureBuilder::new(); + union_builder.structure_type(StructureType::UnionStructureType); + for field in fields { + let mut field_member = self.field_member(field); + // Field members are relative to the union member, so we must remove the union member offset + // from the field member offset to make it relative to the union member. + field_member.offset -= byte_offset; + let overwrites = false; // TODO: Handle overwrites? + union_builder.insert_member(field_member, overwrites); + } + Type::structure(&union_builder.finalize()) + } + + pub fn field_member(&self, field: &Field) -> StructureMember { + let field_ty = self.field_type(field); + let conf_field_ty = Conf::new(field_ty, MAX_CONFIDENCE); + let byte_offset = field.bit_offset() / 8; + StructureMember::new( + conf_field_ty, + field.name.to_owned(), + byte_offset as u64, + MemberAccess::PublicAccess, + MemberScope::NoScope, + ) + } + + pub fn field_type(&self, field: &Field) -> Ref<Type> { + match field { + Field::Single(info) => self.single_field_type(info), + Field::Array(info, elem) => { + // TODO: dimIncrement tells us the stride. We should consult that to + // TODO: make sure that the accesses are aligned. + Type::array(&self.single_field_type(info), elem.dim as u64) + } + } + } + + // TODO: Handle enum type. + // TODO: Fields can derive from one another. + pub fn single_field_type(&self, field: &FieldInfo) -> Ref<Type> { + match field.enumerated_values.len() { + 0 => self.single_field_int_type(field), + 1 => { + // Unlike normal fields, enums must be registered with the view separately. + // If you do not register the enum with the view than you cannot view the enum type! + // TODO: Register enum type so they can be viewed. + self.single_field_enumerated_type(field, &field.enumerated_values[0]) + } + arr_len => { + // TODO: Untested, I guess this works? + let enum_value_ty = + self.single_field_enumerated_type(field, &field.enumerated_values[0]); + Type::array(&enum_value_ty, arr_len as u64) + } + } + } + + pub fn single_field_int_type(&self, field: &FieldInfo) -> Ref<Type> { + // TODO: Binary Ninja is unable to handle bit fields, so we abuse unions. + // Get the closest 8-bit aligned integer and use that. + let width = field.bit_width(); + let byte_aligned_width = byte_width(width); + let type_builder = TypeBuilder::int(byte_aligned_width as usize, false); + if let Some(Access::ReadOnly) = field.access { + // We set fields to volatile as well to prevent constant value propagation. + type_builder.set_volatile(true); + type_builder.set_const(true); + } + type_builder.finalize() + } + + // TODO: EnumeratedValues can derive from one another. + pub fn single_field_enumerated_type( + &self, + field: &FieldInfo, + enumerated_values: &EnumeratedValues, + ) -> Ref<Type> { + // Get the closest 8-bit aligned integer and use that. + let width = field.bit_width(); + let byte_aligned_width = byte_width(width); + let mut enum_builder = EnumerationBuilder::new(); + + let mut current_value = 0; + for enumerated_value in &enumerated_values.values { + current_value = enumerated_value.value.unwrap_or(current_value + 1); + // TODO: The Rust API needs to expose this... + let _is_default = enumerated_value.is_default.unwrap_or(false); + enum_builder.insert(enumerated_value.name.to_owned(), current_value); + } + + let enum_width = NonZeroUsize::new(byte_aligned_width as usize).unwrap(); + let type_builder = TypeBuilder::enumeration(&enum_builder.finalize(), enum_width, false); + + if let Some(Usage::Read) = enumerated_values.usage { + // We set fields to volatile as well to prevent constant value propagation. + type_builder.set_volatile(true); + type_builder.set_const(true); + } + + type_builder.finalize() + } +} diff --git a/plugins/svd/src/settings.rs b/plugins/svd/src/settings.rs new file mode 100644 index 00000000..ca6df47f --- /dev/null +++ b/plugins/svd/src/settings.rs @@ -0,0 +1,84 @@ +use binaryninja::binary_view::BinaryView; +use binaryninja::settings::{QueryOptions, Settings}; +use serde_json::json; +use std::path::PathBuf; + +#[derive(Debug, Clone)] +pub struct LoadSettings { + pub add_bitfields: bool, + pub add_comments: bool, + pub auto_load_file: Option<PathBuf>, +} + +impl LoadSettings { + pub const ADD_BITFIELDS_DEFAULT: bool = true; + pub const ADD_BITFIELDS_SETTING: &'static str = "analysis.svd.addBitfields"; + pub const ADD_COMMENTS_DEFAULT: bool = true; + pub const ADD_COMMENTS_SETTING: &'static str = "analysis.svd.addComments"; + pub const AUTO_LOAD_FILE_DEFAULT: &'static str = ""; + pub const AUTO_LOAD_FILE_SETTING: &'static str = "analysis.svd.autoLoadFile"; + + pub fn register() { + let bn_settings = Settings::new(); + + let add_bitfields_props = json!({ + "title" : "Add Bitfields", + "type" : "boolean", + "default" : Self::ADD_BITFIELDS_DEFAULT, + "description" : "Whether to add bitfields. Bitfields are not supported by Binary Ninja, so this is a workaround using unions.", + }); + bn_settings + .register_setting_json(Self::ADD_BITFIELDS_SETTING, add_bitfields_props.to_string()); + + let add_comments_props = json!({ + "title" : "Add Comments", + "type" : "boolean", + "default" : Self::ADD_COMMENTS_DEFAULT, + "description" : "Whether to add comments. If you see comment placement is off, try disabling this.", + }); + bn_settings + .register_setting_json(Self::ADD_COMMENTS_SETTING, add_comments_props.to_string()); + + let file_props = json!({ + "title" : "SVD File", + "type" : "string", + "default" : Self::AUTO_LOAD_FILE_DEFAULT, + "description" : "The SVD File to automatically load when opening the view.", + "uiSelectionAction" : "file" + }); + bn_settings.register_setting_json(Self::AUTO_LOAD_FILE_SETTING, file_props.to_string()); + } + + pub fn from_view_settings(view: &BinaryView) -> Self { + let mut load_settings = LoadSettings::default(); + let settings = Settings::new(); + let mut query_opts = QueryOptions::new_with_view(view); + if settings.contains(Self::ADD_BITFIELDS_SETTING) { + load_settings.add_bitfields = + settings.get_bool_with_opts(Self::ADD_BITFIELDS_SETTING, &mut query_opts); + } + if settings.contains(Self::ADD_COMMENTS_SETTING) { + load_settings.add_comments = + settings.get_bool_with_opts(Self::ADD_COMMENTS_SETTING, &mut query_opts); + } + if settings.contains(Self::AUTO_LOAD_FILE_SETTING) { + let path_str = + settings.get_string_with_opts(Self::AUTO_LOAD_FILE_SETTING, &mut query_opts); + if !path_str.is_empty() { + let path = PathBuf::from(path_str.to_string()); + load_settings.auto_load_file = Some(path); + } + } + load_settings + } +} + +impl Default for LoadSettings { + fn default() -> Self { + Self { + add_bitfields: Self::ADD_BITFIELDS_DEFAULT, + add_comments: Self::ADD_COMMENTS_DEFAULT, + auto_load_file: None, + } + } +} |
