Skip to main content

i8051_disassembler/
db.rs

1use std::collections::BTreeMap;
2use std::range::Range;
3
4use serde::{Deserialize, Serialize};
5
6use crate::address::{AREA_ORDER, AddressSpace, AddressValue, PhysicalAddr, Xref};
7use crate::command::{Command, Environment};
8use crate::labels::{ImplicitLabels, LabelCollector};
9pub use crate::note::{
10    Note, NoteAddressIndex, NoteDb, NoteField, NoteGlobalIndex, NoteId, NotePath, Notes,
11};
12pub use crate::region::{ByteRange, Region};
13use crate::render::Line;
14use crate::render::sdas::SdasWriter;
15
16pub struct Db {
17    regions: BTreeMap<AddressSpace, Region>,
18    pub notes: NoteDb,
19}
20
21impl Db {
22    pub fn new() -> Self {
23        Self {
24            regions: BTreeMap::new(),
25            notes: NoteDb::default(),
26        }
27    }
28
29    pub fn region(&self, space: AddressSpace) -> Option<&Region> {
30        self.regions.get(&space)
31    }
32
33    pub fn region_mut(&mut self, space: AddressSpace) -> &mut Region {
34        self.regions.entry(space).or_insert_with(Region::new)
35    }
36
37    pub fn xrefs_to(&self, target: &PhysicalAddr) -> Vec<Xref> {
38        let mut xrefs = Vec::new();
39        for (&space, region) in &self.regions {
40            xrefs.extend(region.xrefs_to(space, target));
41        }
42        xrefs
43    }
44
45    pub fn xrefs_from(&self, source: &PhysicalAddr) -> Vec<Xref> {
46        let Some(region) = self.regions.get(&source.space) else {
47            return Vec::new();
48        };
49        region.xrefs_from(source)
50    }
51
52    fn implicit_labels(&self) -> ImplicitLabels {
53        let mut label_collector = LabelCollector::default();
54        for (&space, region) in &self.regions {
55            region.collect_refs(space, &mut label_collector);
56        }
57        label_collector.into_implicit_labels()
58    }
59
60    pub fn render(&self, space: AddressSpace) -> Vec<Line> {
61        let implicit_labels = self.implicit_labels();
62
63        self.regions
64            .get(&space)
65            .map(|region| region.render(space, &implicit_labels))
66            .unwrap_or_default()
67    }
68
69    pub fn render_range(
70        &self,
71        space: AddressSpace,
72        start: AddressValue,
73        end: AddressValue,
74    ) -> Vec<Line> {
75        self.render(space)
76            .into_iter()
77            .filter(|line| {
78                let addr = line.addr();
79                addr >= start && addr < end
80            })
81            .collect()
82    }
83
84    pub fn to_sdas(&self) -> String {
85        let mut writer = SdasWriter::default();
86        let implicit_labels = self.implicit_labels();
87
88        for &space in &AREA_ORDER {
89            let Some(region) = self.regions.get(&space) else {
90                continue;
91            };
92            writer.write(space.area_header());
93            for line in region.render(space, &implicit_labels) {
94                writer.write_line(&line);
95            }
96        }
97
98        writer.into_string()
99    }
100
101    pub fn to_commands(&self) -> Vec<Command> {
102        let mut commands = Vec::new();
103        for (&space, region) in &self.regions {
104            commands.extend(region.to_commands(space));
105        }
106        commands
107    }
108
109    pub fn apply(
110        &mut self,
111        command: Command,
112        env: Option<&dyn Environment>,
113    ) -> Result<Vec<Command>, Error> {
114        command.apply(self, env)
115    }
116
117    /// Byte counts for mapped content classified by equivalent kind.
118    pub fn space_usage(&self, space: AddressSpace) -> SpaceUsage {
119        self.regions
120            .get(&space)
121            .map(Region::space_usage)
122            .unwrap_or_default()
123    }
124
125    pub fn clear_note(
126        &mut self,
127        id: &NoteId,
128    ) -> Option<(AddressSpace, crate::address::AddressRange, Note)> {
129        self.notes.clear_address(id)
130    }
131
132    pub fn note_tip(&self) -> Option<NoteId> {
133        self.notes.tip()
134    }
135
136    pub fn create_note(&mut self, content: impl Into<String>) -> Note {
137        self.notes.create(content)
138    }
139
140    pub fn get_notes_overlapping(
141        &self,
142        space: AddressSpace,
143        range: impl std::ops::RangeBounds<AddressValue>,
144    ) -> Vec<&Note> {
145        self.notes.get_notes_overlapping(space, range)
146    }
147
148    pub fn get_notes_inside(
149        &self,
150        space: AddressSpace,
151        range: impl std::ops::RangeBounds<AddressValue>,
152    ) -> Vec<&Note> {
153        self.notes.get_notes_inside(space, range)
154    }
155}
156
157impl Default for Db {
158    fn default() -> Self {
159        Self::new()
160    }
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct Function {
165    pub addr: PhysicalAddr,
166    pub name: String,
167    pub signature: Option<String>,
168    pub length: AddressValue,
169    pub noreturn: bool,
170}
171
172#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
173pub enum DataType {
174    Byte,
175    Word,
176    Dword,
177    Qword,
178    Reference(Box<DataType>),
179    Equivalent(Box<DataType>, String),
180    Array(Box<DataType>, usize),
181    String(usize),
182    Struct(Vec<DataType>),
183}
184
185#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
186pub enum OperandOverride {
187    Label(String),
188    LabelOffset { label: String, offset: i32 },
189    Text(String),
190}
191
192#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
193pub enum Equivalent {
194    Code(Vec<Option<OperandOverride>>),
195    Data(DataType, AddressValue),
196}
197
198impl Equivalent {
199    pub fn kind(&self) -> EquivalentKind {
200        match self {
201            Self::Code(_) => EquivalentKind::Code,
202            Self::Data(_, _) => EquivalentKind::Data,
203        }
204    }
205}
206
207#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
208pub enum EquivalentKind {
209    Code,
210    Data,
211}
212
213#[derive(Debug, Clone, Eq, PartialEq)]
214pub struct EquivalentRange {
215    pub end: AddressValue,
216    pub equivalent: Equivalent,
217}
218
219#[derive(Debug, Clone, Eq, PartialEq)]
220pub enum EquivalentAt<'a> {
221    Undefined(Range<AddressValue>),
222    Defined {
223        start: AddressValue,
224        range: &'a EquivalentRange,
225    },
226}
227
228impl<'a> EquivalentAt<'a> {
229    pub fn is_defined(&self) -> bool {
230        matches!(self, Self::Defined { .. })
231    }
232}
233
234#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
235pub struct SpaceUsage {
236    /// Bytes covered by a `Equivalent::Code` range.
237    pub code: AddressValue,
238    /// Bytes covered by a `Equivalent::Data` range.
239    pub data: AddressValue,
240    /// Mapped bytes with no equivalent (rendered as raw data).
241    pub undefined: AddressValue,
242}
243
244impl SpaceUsage {
245    pub fn total(&self) -> AddressValue {
246        self.code
247            .saturating_add(self.data)
248            .saturating_add(self.undefined)
249    }
250}
251
252#[derive(Debug)]
253pub enum Error {
254    NoEnvironment,
255    Overlap(AddressValue),
256    InvalidAddress(AddressValue),
257    InvalidEquivalent,
258    NotUndefined(AddressValue),
259    Io(std::io::Error),
260}
261
262#[cfg(test)]
263mod tests {
264    use std::collections::HashMap;
265    use std::io;
266
267    use super::*;
268    use crate::address::XrefType;
269    use crate::command::Command;
270    use pretty_assertions::assert_eq;
271
272    static TEST_BINARY: [u8; 12] = [
273        0x02, 0x00, 0x10, // LJMP 0x10
274        0x74, 0x01, // MOV A, #0x1
275        0xF0, // MOVX @DPTR, A
276        0x90, 0x00, 0x10, // MOV DPTR, #0x10
277        0x93, // MOVC A, @A+DPTR
278        0x80, 0xF7, // SJMP 0x3 (rel = 3 - (10 + 2))
279    ];
280
281    struct TestEnvironment {
282        files: HashMap<String, Vec<u8>>,
283    }
284
285    impl TestEnvironment {
286        fn new() -> Self {
287            Self {
288                files: HashMap::new(),
289            }
290        }
291
292        fn with_file(mut self, name: impl Into<String>, bytes: Vec<u8>) -> Self {
293            self.files.insert(name.into(), bytes);
294            self
295        }
296    }
297
298    impl Environment for TestEnvironment {
299        fn load_file_bytes(
300            &self,
301            file: &str,
302            offset: usize,
303            size: AddressValue,
304        ) -> Result<Vec<u8>, io::Error> {
305            let data = self.files.get(file).ok_or_else(|| {
306                io::Error::new(io::ErrorKind::NotFound, format!("file not found: {file}"))
307            })?;
308            let end = offset.saturating_add(size as usize);
309            if end > data.len() {
310                return Err(io::Error::new(
311                    io::ErrorKind::UnexpectedEof,
312                    "read past end of file",
313                ));
314            }
315            Ok(data[offset..end].to_vec())
316        }
317    }
318
319    fn apply_all(db: &mut Db, commands: Vec<Command>, env: &TestEnvironment) {
320        for command in commands {
321            db.apply(command, Some(env)).unwrap();
322        }
323    }
324
325    fn make_test_db() -> Db {
326        let mut db = Db::new();
327
328        let code = db.region_mut(AddressSpace::Code);
329        code.set_bytes("test.bin", 0, 0, &TEST_BINARY);
330
331        code.set_label(0, "start");
332        code.set_equivalent(0, Equivalent::Code(vec![])).unwrap();
333
334        code.set_comment(3, "Start of loop");
335        code.set_label(3, "loop");
336        code.set_equivalent(3, Equivalent::Code(vec![])).unwrap();
337        code.set_equivalent(5, Equivalent::Code(vec![])).unwrap();
338        code.set_equivalent(6, Equivalent::Code(vec![])).unwrap();
339        code.set_equivalent(9, Equivalent::Code(vec![])).unwrap();
340        code.set_equivalent(10, Equivalent::Code(vec![])).unwrap();
341        db
342    }
343
344    #[test]
345    fn test_db() {
346        let db = make_test_db();
347        assert_eq!(
348            db.xrefs_to(&PhysicalAddr {
349                space: AddressSpace::Code,
350                offset: 3
351            }),
352            vec![Xref {
353                xref_type: XrefType::Jump,
354                from: PhysicalAddr {
355                    space: AddressSpace::Code,
356                    offset: 10
357                },
358                to: PhysicalAddr {
359                    space: AddressSpace::Code,
360                    offset: 3
361                },
362            }]
363        );
364
365        assert_eq!(
366            db.xrefs_from(&PhysicalAddr {
367                space: AddressSpace::Code,
368                offset: 10
369            }),
370            vec![Xref {
371                xref_type: XrefType::Jump,
372                from: PhysicalAddr {
373                    space: AddressSpace::Code,
374                    offset: 10
375                },
376                to: PhysicalAddr {
377                    space: AddressSpace::Code,
378                    offset: 3
379                },
380            }]
381        );
382
383        let expected = r#"
384.area CODE (CODE,ABS)
385.org 0x0
386
387start:
388    LJMP    loc_0010
389; Start of loop
390loop:
391    MOV     A,#01
392    MOVX    @DPTR,A
393    MOV     DPTR,#0x0010
394    MOVC    A,@A+DPTR
395    SJMP    loop
396loc_0010:
397        "#;
398        assert_eq!(db.to_sdas().trim(), expected.trim());
399    }
400
401    #[test]
402    fn test_db_to_commands() {
403        let db = make_test_db();
404        let commands = db.to_commands();
405        let env = TestEnvironment::new().with_file("test.bin", TEST_BINARY.to_vec());
406        let mut new_db = Db::new();
407        for command in commands {
408            let env =
409                matches!(command, Command::MapBytes { .. }).then_some(&env as &dyn Environment);
410            new_db.apply(command, env).expect("command should apply");
411        }
412        assert_eq!(new_db.to_sdas(), db.to_sdas());
413    }
414
415    #[test]
416    fn test_db_space_usage() {
417        let db = make_test_db();
418        assert_eq!(
419            db.space_usage(AddressSpace::Code),
420            SpaceUsage {
421                code: 12,
422                data: 0,
423                undefined: 0,
424            }
425        );
426    }
427
428    #[test]
429    fn map_bytes_command_undo() {
430        let env = TestEnvironment::new()
431            .with_file("test.bin", vec![1, 2, 3])
432            .with_file("other.bin", vec![4, 5]);
433        let mut db = Db::new();
434        db.apply(
435            Command::map_bytes(AddressSpace::Code, 0, "test.bin", 0, 3),
436            Some(&env),
437        )
438        .unwrap();
439
440        let code = db.region(AddressSpace::Code).unwrap();
441        assert_eq!(code.bytes_at(0, 3), vec![1, 2, 3]);
442
443        let undo = db
444            .apply(
445                Command::map_bytes(AddressSpace::Code, 0, "other.bin", 0, 2),
446                Some(&env),
447            )
448            .unwrap();
449        assert_eq!(
450            db.region(AddressSpace::Code).unwrap().bytes_at(0, 2),
451            vec![4, 5]
452        );
453
454        apply_all(&mut db, undo, &env);
455        assert_eq!(
456            db.region(AddressSpace::Code).unwrap().bytes_at(0, 3),
457            vec![1, 2, 3]
458        );
459    }
460
461    #[test]
462    fn clear_bytes_command_undo() {
463        let env = TestEnvironment::new().with_file("test.bin", vec![1, 2, 3, 4, 5]);
464        let mut db = Db::new();
465        db.apply(
466            Command::map_bytes(AddressSpace::Code, 0, "test.bin", 0, 5),
467            Some(&env),
468        )
469        .unwrap();
470
471        let undo = db
472            .apply(Command::clear_bytes(AddressSpace::Code, 1, 2), None)
473            .unwrap();
474        assert_eq!(
475            db.region(AddressSpace::Code).unwrap().bytes_at(0, 5),
476            vec![1, 4, 5]
477        );
478
479        apply_all(&mut db, undo, &env);
480        assert_eq!(
481            db.region(AddressSpace::Code).unwrap().bytes_at(0, 5),
482            vec![1, 2, 3, 4, 5]
483        );
484    }
485
486    #[test]
487    fn set_constant_bytes_command_undo() {
488        let env = TestEnvironment::new().with_file("test.bin", vec![1, 2, 3]);
489        let mut db = Db::new();
490        db.apply(
491            Command::map_bytes(AddressSpace::Code, 0, "test.bin", 0, 3),
492            Some(&env),
493        )
494        .unwrap();
495
496        let undo = db
497            .apply(
498                Command::set_constant_bytes(AddressSpace::Code, 0, 2, 0xFF),
499                None,
500            )
501            .unwrap();
502        assert_eq!(
503            db.region(AddressSpace::Code).unwrap().bytes_at(0, 3),
504            vec![0xFF, 0xFF, 3]
505        );
506
507        apply_all(&mut db, undo, &env);
508        assert_eq!(
509            db.region(AddressSpace::Code).unwrap().bytes_at(0, 3),
510            vec![1, 2, 3]
511        );
512    }
513}