summaryrefslogtreecommitdiff
path: root/view/bintxt/src/titxt.rs
diff options
context:
space:
mode:
authorRubens Brandao <git@rubens.io>2024-08-06 11:01:23 -0300
committerMason Reed <mason@vector35.com>2024-08-27 17:19:25 -0400
commit7e1027d904e66b0cadb277883cdfa7b13dcb209f (patch)
tree4451fe8134e7518d43b1e04c1446f56d3bb2091e /view/bintxt/src/titxt.rs
parentfeca74615ab3e10e7e256630946d830d07414d08 (diff)
Implement IHEX, SREC and TI-TXT views
All bundled under the arch_bintxt/arch_bintext view plugin
Diffstat (limited to 'view/bintxt/src/titxt.rs')
-rw-r--r--view/bintxt/src/titxt.rs267
1 files changed, 267 insertions, 0 deletions
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(), &sectors.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)
+ }
+}