Skip to main content

sbpf_disassembler/
rodata.rs

1use {
2    serde::{Deserialize, Serialize},
3    std::collections::BTreeSet,
4};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub enum RodataType {
8    Ascii(String),
9    Byte(Vec<i8>),
10    Word(i16),
11    Long(i32),
12    Quad(i64),
13}
14
15impl RodataType {
16    pub fn to_asm(&self) -> String {
17        match self {
18            RodataType::Ascii(s) => format!(".ascii \"{}\"", s),
19            RodataType::Byte(v) => format!(".byte {}", format_byte_values(v)),
20            RodataType::Word(v) => format!(".word 0x{:04x}", *v as u16),
21            RodataType::Long(v) => format!(".long 0x{:08x}", *v as u32),
22            RodataType::Quad(v) => format!(".quad 0x{:016x}", *v as u64),
23        }
24    }
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct RodataItem {
29    pub label: String,
30    pub offset: u64,
31    pub size: u64,
32    pub data_type: RodataType,
33    pub data: Vec<u8>,
34}
35
36impl RodataItem {
37    pub fn new(label: String, offset: u64, data: Vec<u8>, data_type: RodataType) -> Self {
38        Self {
39            label,
40            size: data.len() as u64,
41            offset,
42            data_type,
43            data,
44        }
45    }
46
47    pub fn to_asm(&self) -> String {
48        format!("{}: {}", self.label, self.data_type.to_asm())
49    }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct RodataSection {
54    pub base_address: u64,      // base virtual address of the rodata section
55    pub data: Vec<u8>,          // raw section data bytes
56    pub items: Vec<RodataItem>, // parsed rodata items
57}
58
59impl RodataSection {
60    pub fn parse(data: Vec<u8>, base_address: u64, references: &BTreeSet<u64>) -> Self {
61        let items = parse_rodata_items(&data, base_address, references);
62        Self {
63            base_address,
64            data,
65            items,
66        }
67    }
68
69    #[inline]
70    pub fn has_items(&self) -> bool {
71        !self.items.is_empty()
72    }
73
74    pub fn to_asm(&self) -> String {
75        if self.items.is_empty() {
76            return String::new();
77        }
78
79        let mut output = String::from(".rodata\n");
80        for item in &self.items {
81            output.push_str(&format!("  {}\n", item.to_asm()));
82        }
83        output
84    }
85
86    pub fn get_label(&self, address: u64) -> Option<&str> {
87        if address < self.base_address {
88            return None;
89        }
90        let offset = address - self.base_address;
91        self.items
92            .iter()
93            .find(|item| item.offset == offset)
94            .map(|item| item.label.as_str())
95    }
96
97    #[inline]
98    pub fn contains_address(&self, address: u64) -> bool {
99        address >= self.base_address && address < self.base_address + self.data.len() as u64
100    }
101}
102
103fn parse_rodata_items(
104    data: &[u8],
105    base_address: u64,
106    references: &BTreeSet<u64>,
107) -> Vec<RodataItem> {
108    if data.is_empty() {
109        return Vec::new();
110    }
111
112    // Convert absolute addresses to relative offsets within rodata.
113    let mut offsets: Vec<u64> = references
114        .iter()
115        .filter_map(|&addr| {
116            if addr >= base_address && addr < base_address + data.len() as u64 {
117                Some(addr - base_address)
118            } else {
119                None
120            }
121        })
122        .collect();
123
124    // Treat entire rodata as one item if there are no references.
125    if offsets.is_empty() {
126        let trimmed = trim_trailing_zeros(data);
127        if trimmed.is_empty() {
128            return Vec::new();
129        }
130        let data_type = infer_type(trimmed);
131        let label = generate_label(0, &data_type);
132        return vec![RodataItem::new(label, 0, trimmed.to_vec(), data_type)];
133    }
134
135    // Add offset 0 if the first reference isn't at the start.
136    if offsets[0] != 0 {
137        offsets.insert(0, 0);
138    }
139
140    // Create items from segments between consecutive offsets.
141    let mut items = Vec::new();
142    for (i, &offset) in offsets.iter().enumerate() {
143        let start = offset as usize;
144        if start >= data.len() {
145            continue;
146        }
147
148        // End is either the next offset or the end of data
149        let end = if i + 1 < offsets.len() {
150            (offsets[i + 1] as usize).min(data.len())
151        } else {
152            let remaining = &data[start..];
153            start + trim_trailing_zeros(remaining).len()
154        };
155
156        if start < end {
157            let bytes = data[start..end].to_vec();
158            let data_type = infer_type(&bytes);
159            let label = generate_label(offset, &data_type);
160            items.push(RodataItem::new(label, offset, bytes, data_type));
161        }
162    }
163
164    items
165}
166
167#[inline]
168fn trim_trailing_zeros(data: &[u8]) -> &[u8] {
169    let end = data.iter().rposition(|&b| b != 0).map_or(0, |i| i + 1);
170    &data[..end]
171}
172
173fn infer_type(data: &[u8]) -> RodataType {
174    if let Ok(s) = std::str::from_utf8(data)
175        && is_ascii(s)
176        && !s.is_empty()
177    {
178        return RodataType::Ascii(s.to_string());
179    }
180
181    match data.len() {
182        2 => RodataType::Word(i16::from_le_bytes([data[0], data[1]])),
183        4 => RodataType::Long(i32::from_le_bytes(data[0..4].try_into().unwrap())),
184        8 => RodataType::Quad(i64::from_le_bytes(data[0..8].try_into().unwrap())),
185        _ => RodataType::Byte(data.iter().map(|&b| b as i8).collect()),
186    }
187}
188
189#[inline]
190fn is_ascii(s: &str) -> bool {
191    s.chars()
192        .all(|c| c.is_ascii_graphic() || c == ' ' || c == '\t' || c == '\n' || c == '\r')
193}
194
195fn generate_label(offset: u64, data_type: &RodataType) -> String {
196    match data_type {
197        RodataType::Ascii(_) => format!("str_{:04x}", offset),
198        _ => format!("data_{:04x}", offset),
199    }
200}
201
202fn format_byte_values(vals: &[i8]) -> String {
203    vals.iter()
204        .map(|&v| format!("0x{:02x}", v as u8))
205        .collect::<Vec<_>>()
206        .join(", ")
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_infer_type_ascii() {
215        let data = b"Hello, World!";
216        let result = infer_type(data);
217        assert!(matches!(result, RodataType::Ascii(s) if s == "Hello, World!"));
218    }
219
220    #[test]
221    fn test_infer_type_byte() {
222        let data = &[0x01];
223        if let RodataType::Byte(vals) = infer_type(data) {
224            assert_eq!(vals[0], 0x01);
225        } else {
226            panic!("Expected Byte type");
227        }
228    }
229
230    #[test]
231    fn test_infer_type_word() {
232        let data = &[0x34, 0x12];
233        if let RodataType::Word(val) = infer_type(data) {
234            assert_eq!(val, 0x1234);
235        } else {
236            panic!("Expected Word type");
237        }
238    }
239
240    #[test]
241    fn test_infer_type_long() {
242        let data = &[0x78, 0x56, 0x34, 0x12];
243        if let RodataType::Long(val) = infer_type(data) {
244            assert_eq!(val, 0x12345678);
245        } else {
246            panic!("Expected Long type");
247        }
248    }
249
250    #[test]
251    fn test_infer_type_quad() {
252        let data = &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
253        if let RodataType::Quad(val) = infer_type(data) {
254            assert_eq!(val, 0x0807060504030201i64);
255        } else {
256            panic!("Expected Quad type");
257        }
258    }
259
260    #[test]
261    fn test_infer_type_bytes() {
262        let data = &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0];
263        if let RodataType::Byte(vals) = infer_type(data) {
264            assert_eq!(vals.len(), 9);
265        } else {
266            panic!("Expected Byte array for 9 bytes");
267        }
268    }
269
270    #[test]
271    fn test_generate_label_str() {
272        let t = RodataType::Ascii("test".to_string());
273        assert_eq!(generate_label(0, &t), "str_0000");
274        assert_eq!(generate_label(16, &t), "str_0010");
275        assert_eq!(generate_label(255, &t), "str_00ff");
276    }
277
278    #[test]
279    fn test_generate_label_data() {
280        assert_eq!(generate_label(0, &RodataType::Byte(vec![0])), "data_0000");
281        assert_eq!(generate_label(0, &RodataType::Word(0)), "data_0000");
282        assert_eq!(generate_label(0, &RodataType::Long(0)), "data_0000");
283        assert_eq!(generate_label(0, &RodataType::Quad(0)), "data_0000");
284    }
285
286    #[test]
287    fn test_rodata_type_to_asm() {
288        assert_eq!(
289            RodataType::Ascii("Hello".to_string()).to_asm(),
290            ".ascii \"Hello\""
291        );
292        assert_eq!(
293            RodataType::Byte(vec![0, 1, -1]).to_asm(),
294            ".byte 0x00, 0x01, 0xff"
295        );
296        assert_eq!(RodataType::Word(0x1234).to_asm(), ".word 0x1234");
297        assert_eq!(RodataType::Long(0x12345678).to_asm(), ".long 0x12345678");
298        assert_eq!(
299            RodataType::Quad(0x123456789ABCDEF0u64 as i64).to_asm(),
300            ".quad 0x123456789abcdef0"
301        );
302    }
303
304    #[test]
305    fn test_rodata_item_to_asm() {
306        let item = RodataItem::new(
307            "str_0000".to_string(),
308            0,
309            b"Hello".to_vec(),
310            RodataType::Ascii("Hello".to_string()),
311        );
312        assert_eq!(item.to_asm(), "str_0000: .ascii \"Hello\"");
313    }
314
315    #[test]
316    fn test_rodata_section_empty() {
317        let section = RodataSection::parse(Vec::new(), 0x100, &BTreeSet::new());
318        assert!(section.to_asm().is_empty());
319    }
320
321    #[test]
322    fn test_rodata_section_contains_address() {
323        let section = RodataSection::parse(vec![0x01, 0x02, 0x03, 0x04], 0x100, &BTreeSet::new());
324
325        assert!(section.contains_address(0x100));
326        assert!(section.contains_address(0x103));
327        assert!(!section.contains_address(0x99));
328        assert!(!section.contains_address(0x104));
329    }
330
331    #[test]
332    fn test_rodata_section_has_items() {
333        let section_with_data = RodataSection::parse(vec![0x01], 0x100, &BTreeSet::new());
334        assert!(section_with_data.has_items());
335
336        let section_empty = RodataSection::parse(Vec::new(), 0x100, &BTreeSet::new());
337        assert!(!section_empty.has_items());
338    }
339
340    #[test]
341    fn test_trim_trailing_zeros() {
342        assert_eq!(trim_trailing_zeros(&[1, 2, 3, 0, 0]), &[1, 2, 3]);
343        assert_eq!(trim_trailing_zeros(&[0, 0, 0]), &[] as &[u8]);
344        assert_eq!(trim_trailing_zeros(&[1, 0, 2, 0]), &[1, 0, 2]);
345        assert_eq!(trim_trailing_zeros(&[]), &[] as &[u8]);
346    }
347}