diff options
| author | Rubens Brandao <git@rubens.io> | 2024-08-06 11:01:23 -0300 |
|---|---|---|
| committer | Mason Reed <mason@vector35.com> | 2024-08-27 17:19:25 -0400 |
| commit | 7e1027d904e66b0cadb277883cdfa7b13dcb209f (patch) | |
| tree | 4451fe8134e7518d43b1e04c1446f56d3bb2091e /view/bintxt/src | |
| parent | feca74615ab3e10e7e256630946d830d07414d08 (diff) | |
Implement IHEX, SREC and TI-TXT views
All bundled under the arch_bintxt/arch_bintext view plugin
Diffstat (limited to 'view/bintxt/src')
| -rw-r--r-- | view/bintxt/src/ihex.rs | 237 | ||||
| -rw-r--r-- | view/bintxt/src/lib.rs | 131 | ||||
| -rw-r--r-- | view/bintxt/src/srec.rs | 329 | ||||
| -rw-r--r-- | view/bintxt/src/titxt.rs | 267 |
4 files changed, 964 insertions, 0 deletions
diff --git a/view/bintxt/src/ihex.rs b/view/bintxt/src/ihex.rs new file mode 100644 index 00000000..5b210ff4 --- /dev/null +++ b/view/bintxt/src/ihex.rs @@ -0,0 +1,237 @@ +use binaryninja::binaryview::*; +use binaryninja::custombinaryview::*; +use binaryninja::rc::Ref; +use binaryninja::segment::SegmentBuilder; +use ihex::Record; + +use crate::segment_after_address; +use crate::segment_from_address; +use crate::sort_and_merge_segments; +use crate::MergedSegment; +use crate::UnmergedSegment; + +fn parse_ihex(string: &str) -> Result<(Vec<u8>, IHexViewData)> { + let mut reader = ihex::Reader::new(&string); + let mut unmerged_data: Vec<UnmergedSegment> = vec![]; + let mut start = None; + let mut offset = None; + while let Some(record) = reader.next() { + let record = record.map_err(|e| { + log::error!("Unable to parse record: {e}"); + () + })?; + match record { + ihex::Record::EndOfFile => break, + ihex::Record::Data { + offset: data_offset, + value, + } => { + let address = match offset { + Some(IHexOffset::Segment(offset)) => { + u64::from(offset) * 16 + u64::from(data_offset) + } + Some(IHexOffset::Linear(offset)) => { + u64::from(offset) << 16 | u64::from(data_offset) + } + None => data_offset.into(), + }; + // check if the block is just extending the previous one + match unmerged_data.last_mut() { + // check if the block is just extending the previous one, merge both + Some(last) if last.end() == address => last.data.extend(value), + // otherwise just add a new block + _ => unmerged_data.push(UnmergedSegment { + address, + data: value, + }), + } + } + ihex::Record::StartSegmentAddress { cs, ip } => { + if start.is_some() { + log::error!("Multiple Start Address defined"); + return Err(()); + } + start = Some(IHexStart::Segment { _cs: cs, ip }); + } + ihex::Record::StartLinearAddress(offset) => { + if start.is_some() { + log::error!("Multiple Start Address defined"); + return Err(()); + } + start = Some(IHexStart::Linear(offset)); + } + ihex::Record::ExtendedSegmentAddress(segment_offset) => { + offset = Some(IHexOffset::Segment(segment_offset)) + } + ihex::Record::ExtendedLinearAddress(linear_offset) => { + offset = Some(IHexOffset::Linear(linear_offset)) + } + } + } + // can't have other record after the EoF record + if reader.next().is_some() { + log::error!("Found record after EoF record"); + return Err(()); + } + + let merged = sort_and_merge_segments(unmerged_data)?; + Ok(( + merged.data, + IHexViewData { + segments: merged.segments, + start, + }, + )) +} + +pub struct IHexViewConstructor { + pub core: BinaryViewType, +} + +impl AsRef<BinaryViewType> for IHexViewConstructor { + fn as_ref(&self) -> &BinaryViewType { + &self.core + } +} + +impl CustomBinaryViewType for IHexViewConstructor { + fn create_custom_view<'builder>( + &self, + parent: &BinaryView, + builder: CustomViewBuilder<'builder, Self>, + ) -> Result<CustomView<'builder>> { + let bytes = parent.len(); + let mut buf = vec![0; bytes]; + let bytes_read = parent.read(&mut buf, 0); + if bytes_read != bytes { + log::error!("IHex file is too small"); + return Err(()); + } + let string = String::from_utf8(buf).map_err(|_| { + log::error!("File contains invalid UTF8 characters"); + })?; + + let (data, segments) = parse_ihex(&string)?; + + let parent_bin = BinaryView::from_data(&parent.file(), &data)?; + builder.create::<IHexView>(&parent_bin, segments) + } +} + +impl BinaryViewTypeBase for IHexViewConstructor { + fn is_valid_for(&self, data: &BinaryView) -> bool { + // TODO check filename and identify the type if necessary + //let filename = data.file().filename(); + //let filename = std::path::Path::new(filename.as_str()); + //let filetype = filename + // .extension() + // .map(|ext| match ext.to_string_lossy().as_ref() { + // // General-purpose: + // "hex" | "mcs" | "int" | "ihex" | "ihe" | "ihx" => true, + // // Platform-specific: + // "h80" | "h86" | "a43" | "a90" => Some(true), + // // Binary or Intel hex: + // "obj" | "obl" | "obh" | "rom" | "eep" => true, + // // TODO: Split, banked, or paged: + // //.hxl–.hxh,[8] .h00–.h15, .p00–.pff[9] + // _ => false, + // }); + + // The biggest possible record is data record with 255 bytes: 268 bytes + let mut first_bytes = [0u8; 384]; + let read_bytes = data.read(&mut first_bytes, 0); + let data = String::from_utf8_lossy(&first_bytes[0..read_bytes]); + let Some(line) = data.lines().next() else { + return false; + }; + Record::from_record_string(line).is_ok() + } + + fn is_deprecated(&self) -> bool { + false + } +} + +pub struct IHexView { + core: Ref<BinaryView>, + segments: Vec<MergedSegment>, + start: Option<IHexStart>, +} + +pub struct IHexViewData { + segments: Vec<MergedSegment>, + start: Option<IHexStart>, +} + +#[derive(Clone, Copy, Debug)] +enum IHexStart { + Segment { _cs: u16, ip: u16 }, + Linear(u32), +} + +#[derive(Clone, Copy, Debug)] +enum IHexOffset { + Segment(u16), + Linear(u16), +} + +impl AsRef<BinaryView> for IHexView { + fn as_ref(&self) -> &BinaryView { + &self.core + } +} + +unsafe impl CustomBinaryView for IHexView { + type Args = IHexViewData; + + fn new(handle: &BinaryView, _args: &Self::Args) -> Result<Self> { + Ok(Self { + core: handle.to_owned(), + // NOTE dummy values, final values are added on init + start: None, + segments: vec![], + }) + } + + fn init(&mut self, IHexViewData { start, segments }: Self::Args) -> Result<()> { + self.start = start; + self.segments = segments; + + for segment in self.segments.iter() { + self.add_segment( + SegmentBuilder::from(*segment) + .executable(true) + .readable(true) + .contains_data(true) + .contains_code(true), + ); + } + Ok(()) + } +} + +impl BinaryViewBase for IHexView { + fn entry_point(&self) -> u64 { + match self.start { + Some(IHexStart::Linear(addr)) => addr.into(), + Some(IHexStart::Segment { _cs, ip }) => ip.into(), + None => self.start(), + } + } + + fn default_endianness(&self) -> binaryninja::Endianness { + binaryninja::Endianness::LittleEndian + } + + fn address_size(&self) -> usize { + 4 + } + + fn offset_valid(&self, offset: u64) -> bool { + segment_from_address(&self.segments, offset).is_some() + } + + fn next_valid_offset_after(&self, offset: u64) -> u64 { + segment_after_address(&self.segments, offset) + } +} diff --git a/view/bintxt/src/lib.rs b/view/bintxt/src/lib.rs new file mode 100644 index 00000000..96cd1835 --- /dev/null +++ b/view/bintxt/src/lib.rs @@ -0,0 +1,131 @@ +mod ihex; +mod srec; +mod titxt; + +use binaryninja::segment::SegmentBuilder; +use ihex::*; +use srec::*; +use titxt::*; + +use std::ops::Range; + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn CorePluginInit() -> bool { + binaryninja::logger::init(log::LevelFilter::Error).expect("Unable to initialize logger"); + + binaryninja::custombinaryview::register_view_type(c"ti-txt", c"TI-TXT", |core| { + TiTxtViewConstructor { core } + }); + + binaryninja::custombinaryview::register_view_type(c"srec", c"Motorola S-record", |core| { + SRecViewConstructor { core } + }); + + binaryninja::custombinaryview::register_view_type(c"ihex", c"Intel HEX", |core| { + IHexViewConstructor { core } + }); + + true +} + +struct UnmergedSegment { + address: u64, + data: Vec<u8>, +} + +impl UnmergedSegment { + fn end(&self) -> u64 { + self.address + u64::try_from(self.data.len()).unwrap() + } +} + +#[derive(Clone, Copy, Debug)] +pub struct MergedSegment { + address: u64, + len: u64, + data_offset: u64, +} + +impl MergedSegment { + fn address_range(&self) -> Range<u64> { + self.address..self.end() + } + + fn data_range(&self) -> Range<u64> { + self.data_offset..self.data_offset + self.len + } + + fn end(&self) -> u64 { + self.address + self.len + } +} + +impl From<MergedSegment> for SegmentBuilder { + fn from(segment: MergedSegment) -> Self { + SegmentBuilder::new(segment.address_range()).parent_backing(segment.data_range()) + } +} + +struct MergedSegments { + data: Vec<u8>, + segments: Vec<MergedSegment>, +} + +fn sort_and_merge_segments(mut unmerged_data: Vec<UnmergedSegment>) -> Result<MergedSegments, ()> { + // sort segments by address and len, so we can detect overlaps + unmerged_data.sort_unstable_by_key(|segment| (segment.address, segment.data.len())); + + let mut data: Vec<u8> = + Vec::with_capacity(unmerged_data.iter().map(|sector| sector.data.len()).sum()); + let mut segments: Vec<MergedSegment> = Vec::with_capacity(unmerged_data.len()); + for segment in unmerged_data.into_iter() { + // add the data to the data poll + let data_offset = u64::try_from(data.len()).unwrap(); + let segment_len = u64::try_from(segment.data.len()).unwrap(); + data.extend(segment.data); + match segments.last_mut() { + // if have a last segment and the current chunk just extend it, merge both + Some(last) if segment.address == last.end() => last.len += segment_len, + // the same sector overlap, then the data was defined multiple times. + Some(last) if segment.address < last.end() => { + log::error!("Chunks of data overlap"); + return Err(()); + } + // otherwise just create a new segment + _ => segments.push(MergedSegment { + address: segment.address, + len: segment_len, + data_offset, + }), + } + } + + Ok(MergedSegments { data, segments }) +} + +fn segment_from_address(segments: &[MergedSegment], offset: u64) -> Option<&MergedSegment> { + segments + .binary_search_by(|segment| { + let range = segment.address_range(); + if range.contains(&offset) { + return core::cmp::Ordering::Equal; + } + offset.cmp(&range.start) + }) + .ok() + .map(|idx| &segments[idx]) +} + +fn segment_after_address(segments: &[MergedSegment], offset: u64) -> u64 { + let sector = segments.iter().find_map(|sector| { + if sector.address >= offset { + Some(sector.address) + } else if sector.end() < offset { + Some(offset) + } else { + None + } + }); + sector.unwrap_or(offset).into() +} diff --git a/view/bintxt/src/srec.rs b/view/bintxt/src/srec.rs new file mode 100644 index 00000000..d11e5fd5 --- /dev/null +++ b/view/bintxt/src/srec.rs @@ -0,0 +1,329 @@ +use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}; +use binaryninja::custombinaryview::{ + BinaryViewType, BinaryViewTypeBase, CustomBinaryView, CustomBinaryViewType, CustomView, + CustomViewBuilder, +}; +use binaryninja::rc::Ref; +use binaryninja::segment::SegmentBuilder; +use srec::Record; + +use crate::{ + segment_after_address, segment_from_address, sort_and_merge_segments, MergedSegment, + UnmergedSegment, +}; + +struct SRecParser<I> { + reader: I, + segments: Vec<UnmergedSegment>, + sector_counter: u32, + start: u32, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum RecordLen { + R16, + R24, + R32, +} + +impl<I: Iterator<Item = Result<Record, srec::ReaderError>>> SRecParser<I> { + fn parse(&mut self) -> Result<(), ()> { + // first sector need to be the header + let header = self.reader.next(); + let header = header + .ok_or_else(|| { + log::error!("Missing header record"); + })? + .map_err(|e| { + log::error!("Unable to parse header record: {e}"); + })?; + + let Record::S0(_header_info) = header else { + log::error!("Invalid first record"); + return Err(()); + }; + + // parse the first data record + let second_record = self + .reader + .next() + .ok_or_else(|| { + log::error!("Missing first data record"); + })? + .map_err(|e| { + log::error!("Unable to parse first data record: {e}"); + })?; + let (addr_len, first_data_addr, first_data) = match second_record { + Record::S0(_) + | Record::S5(_) + | Record::S6(_) + | Record::S7(_) + | Record::S8(_) + | Record::S9(_) => { + log::error!("Invalid second record type"); + return Err(()); + } + Record::S1(s) => (RecordLen::R16, u32::from(s.address).into(), s.data), + Record::S2(s) => (RecordLen::R24, u32::from(s.address).into(), s.data), + Record::S3(s) => (RecordLen::R32, u32::from(s.address).into(), s.data), + }; + self.sector_counter = 1; + self.segments.push(UnmergedSegment { + address: first_data_addr, + data: first_data, + }); + + // parse zero or more data after the first until hit a Count (S5, S6) + // or Start Address (S7, S8, S9) Record + let next = loop { + let record = self + .reader + .next() + .ok_or_else(|| { + log::error!("Final record can't be data"); + })? + .map_err(|e| { + log::error!("Unable to parse record: {e}"); + })?; + match record { + Record::S0(_) => { + log::error!("Multiple header records"); + return Err(()); + } + Record::S1(s) if addr_len == RecordLen::R16 => { + self.add_record_data(u32::from(s.address).into(), s.data) + } + Record::S2(s) if addr_len == RecordLen::R24 => { + self.add_record_data(u32::from(s.address).into(), s.data) + } + Record::S3(s) if addr_len == RecordLen::R32 => { + self.add_record_data(u32::from(s.address).into(), s.data) + } + Record::S1(_) | Record::S2(_) | Record::S3(_) => { + log::error!("Data record with invalid len"); + return Err(()); + } + other @ (Record::S5(_) + | Record::S6(_) + | Record::S7(_) + | Record::S8(_) + | Record::S9(_)) => break other, + } + }; + + // S5/S6 is an opional last-but-one record + let last = match next { + Record::S0(_) | Record::S1(_) | Record::S2(_) | Record::S3(_) => { + unreachable!() + } + Record::S5(s) => { + self.check_count_record(s.into())?; + self.reader.next() + } + Record::S6(s) => { + self.check_count_record(s.into())?; + self.reader.next() + } + s @ (Record::S7(_) | Record::S8(_) | Record::S9(_)) => Some(Ok(s)), + }; + + // the last record need to be S7 | S8 | S9 + let last = last + .ok_or_else(|| { + log::error!("Missing last record"); + })? + .map_err(|e| { + log::error!("Unable to parse last record: {e}"); + })?; + match last { + Record::S7(s) => self.start = s.into(), + Record::S8(s) => self.start = s.into(), + Record::S9(s) => self.start = s.into(), + _ => { + log::error!("Invalid last record type"); + return Err(()); + } + } + + // can't have any record after S7 | S8 | S9 + if let Some(_s) = self.reader.next() { + log::error!("Records found after the last"); + return Err(()); + } + + Ok(()) + } + + fn add_record_data(&mut self, address: u64, data: Vec<u8>) { + self.sector_counter += 1; + match self.segments.last_mut() { + // check if the block is just extending the previous one, merge both + Some(last) if last.end() == address => { + last.data.extend(data); + } + // otherwise just add a new block + _ => { + self.segments.push(UnmergedSegment { address, data }); + } + } + } + + fn check_count_record(&self, value: u32) -> Result<(), ()> { + if value != self.sector_counter { + log::error!("Invalid number of records"); + Err(()) + } else { + Ok(()) + } + } +} + +fn parse_srec(data: &str) -> Result<(Vec<u8>, SRecViewData), ()> { + let mut parser = SRecParser { + reader: srec::reader::read_records(&data), + segments: vec![], + sector_counter: 0, + start: 0, + }; + parser.parse()?; + + let segments = sort_and_merge_segments(parser.segments)?; + Ok(( + segments.data, + SRecViewData { + segments: segments.segments, + start: parser.start, + }, + )) +} + +pub struct SRecViewConstructor { + pub core: BinaryViewType, +} + +impl AsRef<BinaryViewType> for SRecViewConstructor { + fn as_ref(&self) -> &BinaryViewType { + &self.core + } +} +impl CustomBinaryViewType for SRecViewConstructor { + fn create_custom_view<'builder>( + &self, + parent: &BinaryView, + builder: CustomViewBuilder<'builder, Self>, + ) -> Result<CustomView<'builder>, ()> { + let bytes = parent.len(); + let mut buf = vec![0; bytes]; + let bytes_read = parent.read(&mut buf, 0); + if bytes_read != bytes { + log::error!("IHex file is too small"); + return Err(()); + } + let string = String::from_utf8(buf).map_err(|_| { + log::error!("File contains invalid UTF8 characters"); + })?; + let (data, regs) = parse_srec(&string)?; + + let parent_bin = BinaryView::from_data(&parent.file(), &data)?; + builder.create::<SRecView>(&parent_bin, regs) + } +} + +impl BinaryViewTypeBase for SRecViewConstructor { + fn is_valid_for(&self, data: &BinaryView) -> bool { + // TODO check filename and identify the type if necessary + //let filename = data.file().filename(); + //let filename = std::path::Path::new(filename.as_str()); + //let filetype = filename + // .extension() + // .map(|ext| match ext.to_string_lossy().as_ref() { + // "s19" | "s28" | "s37" | "s" | "s1" | "s2" | "s3" | "sx" | "srec" | "exo" + // | "mot" | "mxt" => true, + // _ => false, + // }); + + // The biggest possible record line is 269 + const READ_LEN: usize = 384; + let mut first_bytes = [0u8; READ_LEN]; + let read_bytes = data.read(&mut first_bytes, 0); + let line = String::from_utf8_lossy(&first_bytes[0..read_bytes]); + if !line.is_ascii() { + return false; + } + let first_record: Result<Record, _> = line.parse(); + // first record need to be and S0 + matches!(first_record, Ok(Record::S0(_))) + } + + fn is_deprecated(&self) -> bool { + false + } +} + +pub struct SRecView { + core: Ref<BinaryView>, + segments: Vec<MergedSegment>, + start: u32, +} + +pub struct SRecViewData { + segments: Vec<MergedSegment>, + start: u32, +} + +impl AsRef<BinaryView> for SRecView { + fn as_ref(&self) -> &BinaryView { + &self.core + } +} + +unsafe impl CustomBinaryView for SRecView { + type Args = SRecViewData; + + fn new(handle: &BinaryView, _args: &Self::Args) -> Result<Self, ()> { + Ok(Self { + core: handle.to_owned(), + // NOTE dummy values, final values are added on init + start: 0, + segments: vec![], + }) + } + + fn init(&mut self, SRecViewData { start, segments }: Self::Args) -> Result<(), ()> { + self.start = start; + self.segments = segments; + + for segment in self.segments.iter() { + self.add_segment( + SegmentBuilder::from(*segment) + .executable(true) + .readable(true) + .contains_data(true) + .contains_code(true), + ); + } + Ok(()) + } +} + +impl BinaryViewBase for SRecView { + fn entry_point(&self) -> u64 { + self.start.into() + } + + fn default_endianness(&self) -> binaryninja::Endianness { + binaryninja::Endianness::LittleEndian + } + + fn address_size(&self) -> usize { + 4 + } + + fn offset_valid(&self, offset: u64) -> bool { + segment_from_address(&self.segments, offset).is_some() + } + + fn next_valid_offset_after(&self, offset: u64) -> u64 { + segment_after_address(&self.segments, offset) + } +} diff --git a/view/bintxt/src/titxt.rs b/view/bintxt/src/titxt.rs new file mode 100644 index 00000000..6cb006ab --- /dev/null +++ b/view/bintxt/src/titxt.rs @@ -0,0 +1,267 @@ +use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}; +use binaryninja::custombinaryview::{ + BinaryViewType, BinaryViewTypeBase, CustomBinaryView, CustomBinaryViewType, CustomView, + CustomViewBuilder, +}; +use binaryninja::rc::Ref; +use binaryninja::segment::SegmentBuilder; + +use crate::{ + segment_after_address, segment_from_address, sort_and_merge_segments, MergedSegment, + MergedSegments, UnmergedSegment, +}; + +fn hex_to_byte(char: u8) -> Option<u8> { + match char { + b'0'..=b'9' => Some(char - b'0'), + b'a'..=b'f' => Some(0xa + (char - b'a')), + b'A'..=b'F' => Some(0xA + (char - b'A')), + _ => None, + } +} + +fn take_address(input: &[u8]) -> Result<(u16, &[u8]), ()> { + let [b'@', b1, b2, b3, b4, rest @ ..] = input else { + return Err(()); + }; + let b1 = hex_to_byte(*b1).map(u16::from).ok_or(())?; + let b2 = hex_to_byte(*b2).map(u16::from).ok_or(())?; + let b3 = hex_to_byte(*b3).map(u16::from).ok_or(())?; + let b4 = hex_to_byte(*b4).map(u16::from).ok_or(())?; + let value = b1 << 12 | b2 << 8 | b3 << 4 | b4; + Ok((value, rest)) +} + +fn take_byte(input: &[u8]) -> Option<(u8, &[u8])> { + let [b1, b2, rest @ ..] = input else { + return None; + }; + let b1 = hex_to_byte(*b1)?; + let b2 = hex_to_byte(*b2)?; + let value = b1 << 4 | b2; + Some((value, rest)) +} + +fn take_newline(input: &[u8]) -> Option<&[u8]> { + match input { + [b'\r', b'\n', rest @ ..] => Some(rest), + [b'\n', rest @ ..] => Some(rest), + _ => None, + } +} + +// take 0 or more newlines +fn take_newlines(mut input: &[u8]) -> (usize, &[u8]) { + let mut counter = 0; + while let Some(rest) = take_newline(input) { + input = rest; + counter += 1; + } + (counter, input) +} + +fn take_byte_line(input: &[u8]) -> Result<(Vec<u8>, &[u8]), ()> { + // a line can be 16 bytes long, or less in case it's the last line + let mut current_input = input; + let mut bytes = Vec::with_capacity(16); + + // line need to have at least one line + for _i in 0..16 { + let (byte, rest) = + take_byte(current_input).ok_or_else(|| log::error!("Unable to parse byte"))?; + current_input = rest; + bytes.push(byte); + + match current_input { + // space with newline ends this line + [b' ', b'\r', b'\n', rest @ ..] | [b' ', b'\n', rest @ ..] => return Ok((bytes, rest)), + // bytes are separated by space + [b' ', rest @ ..] => current_input = rest, + // newline ends this line + [b'\r', b'\n', rest @ ..] | [b'\n', rest @ ..] => return Ok((bytes, rest)), + // other chars are forbidden + _ => { + log::error!("Invalid character on data bytes"); + return Err(()); + } + } + } + // can't have more then 16 bytes + log::error!("Bytes line is too long"); + Err(()) +} + +fn parse_sections(input: &[u8]) -> Result<(UnmergedSegment, &[u8]), ()> { + // get the address part of the section + let (address, input) = + take_address(input).map_err(|_| log::error!("Unable to parse Address"))?; + let mut input = + take_newline(input).ok_or_else(|| log::error!("Unable to find Address delimiter"))?; + + // get one or more bytes + let mut data = vec![]; + let rest = loop { + let (line, rest) = take_byte_line(input)?; + let line_len = line.len(); + data.extend(line); + input = rest; + + // allow sections to be separated by multiple spaces + let (new_lines, rest) = take_newlines(input); + input = rest; + + match input { + // if after a line we find a 'q', then we found the end of file + b"q" | b"q\n" | b"q\r\n" => break &[][..], + + // end of file, but with data after + [b'q', ..] => { + log::error!("Found data after the end section"); + return Err(()); + } + + // found the next sector + [b'@', ..] => break input, + + // NOTE: only the last line is allowed to be less then 16 bytes, + // if less then 16 it's the end of a sector, so we need + // find a new sector or end of file + // NOTE: bytes can't be separated by multiple lines, only sections can + _ if line_len != 16 || new_lines > 0 => { + log::error!("Unable to find end of section"); + return Err(()); + } + + // line is followed by other line of bytes + _ => {} + } + }; + + let segment = UnmergedSegment { + address: address.into(), + data, + }; + Ok((segment, rest)) +} + +fn parse_ti_txt(data: &[u8]) -> Result<MergedSegments, ()> { + let mut current_data = data; + let mut unmerged_segments = vec![]; + while !current_data.is_empty() { + let (section, rest) = parse_sections(current_data)?; + current_data = rest; + + unmerged_segments.push(section); + } + sort_and_merge_segments(unmerged_segments) +} + +pub struct TiTxtViewConstructor { + pub core: BinaryViewType, +} + +impl AsRef<BinaryViewType> for TiTxtViewConstructor { + fn as_ref(&self) -> &BinaryViewType { + &self.core + } +} + +impl CustomBinaryViewType for TiTxtViewConstructor { + fn create_custom_view<'builder>( + &self, + parent: &BinaryView, + builder: CustomViewBuilder<'builder, Self>, + ) -> Result<CustomView<'builder>, ()> { + let bytes = parent.len(); + let mut buf = vec![0; bytes]; + let bytes_read = parent.read(&mut buf, 0); + if bytes_read != bytes { + log::error!("IHex file is too small"); + return Err(()); + } + let sectors = parse_ti_txt(&buf)?; + + let parent_bin = BinaryView::from_data(&parent.file(), §ors.data)?; + builder.create::<TiTxtView>(&parent_bin, sectors.segments) + } +} + +impl BinaryViewTypeBase for TiTxtViewConstructor { + fn is_valid_for(&self, data: &BinaryView) -> bool { + let mut first_bytes = [0u8; 16]; + let read_bytes = data.read(&mut first_bytes, 0); + + let [b'@', b1, b2, b3, b4, b'\r' | b'\n', ..] = first_bytes[0..read_bytes] else { + return false; + }; + b1.is_ascii_hexdigit() + && b2.is_ascii_hexdigit() + && b3.is_ascii_hexdigit() + && b4.is_ascii_hexdigit() + } + + fn is_deprecated(&self) -> bool { + false + } +} + +pub struct TiTxtView { + core: Ref<BinaryView>, + segments: Vec<MergedSegment>, +} + +impl AsRef<BinaryView> for TiTxtView { + fn as_ref(&self) -> &BinaryView { + &self.core + } +} + +unsafe impl CustomBinaryView for TiTxtView { + type Args = Vec<MergedSegment>; + + fn new(handle: &BinaryView, _args: &Self::Args) -> Result<Self, ()> { + Ok(Self { + core: handle.to_owned(), + // NOTE dummy value, final values are added on init + segments: vec![], + }) + } + + fn init(&mut self, segments: Self::Args) -> Result<(), ()> { + self.segments = segments; + + for segment in self.segments.iter() { + self.add_segment( + SegmentBuilder::from(*segment) + .executable(true) + .readable(true) + .contains_data(true) + .contains_code(true), + ); + } + Ok(()) + } +} + +impl BinaryViewBase for TiTxtView { + fn entry_point(&self) -> u64 { + // NOTE: TI TXT don't have any entry point information + self.segments.first().map(|s| s.address.into()).unwrap_or(0) + } + + fn default_endianness(&self) -> binaryninja::Endianness { + binaryninja::Endianness::LittleEndian + } + + fn address_size(&self) -> usize { + 4 + } + + fn offset_valid(&self, offset: u64) -> bool { + segment_from_address(&self.segments, offset).is_some() + } + + fn next_valid_offset_after(&self, offset: u64) -> u64 { + segment_after_address(&self.segments, offset) + } +} |
