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