Skip to main content

sbpf_assembler/
program.rs

1use {
2    crate::{
3        debug::{self, DebugData, reuse_debug_sections},
4        dynsym::{DynamicSymbol, RelDyn, RelocationType},
5        header::{ElfHeader, ProgramHeader},
6        parser::ProgramLayout,
7        section::{
8            DebugSection, DynStrSection, DynSymSection, DynamicSection, NullSection, RelDynSection,
9            Section, SectionType, ShStrTabSection,
10        },
11    },
12    std::{fs::File, io::Write, path::Path},
13};
14
15#[derive(Debug)]
16pub struct Program {
17    pub elf_header: ElfHeader,
18    pub program_headers: Option<Vec<ProgramHeader>>,
19    pub sections: Vec<SectionType>,
20}
21
22impl Program {
23    pub fn from_parse_result(
24        ProgramLayout {
25            code_section,
26            data_section,
27            dynamic_symbols,
28            relocation_data,
29            prog_is_static,
30            arch,
31            debug_sections,
32        }: ProgramLayout,
33        debug_data: Option<DebugData>,
34    ) -> Self {
35        let mut elf_header = ElfHeader::new();
36        let mut program_headers = None;
37
38        let bytecode_size = code_section.size();
39        let rodata_size = data_section.size();
40
41        let has_rodata = rodata_size > 0;
42        let ph_count = if arch.is_v3() {
43            if has_rodata { 2 } else { 1 }
44        } else if prog_is_static {
45            0
46        } else {
47            3
48        };
49
50        elf_header.e_flags = arch.e_flags();
51        elf_header.e_phnum = ph_count;
52
53        // save read + execute size for program header before
54        // ownership of code/data sections is transferred
55        let text_size = bytecode_size + rodata_size;
56
57        // Calculate base offset after ELF header and program headers
58        let base_offset = 64 + (ph_count as u64 * 56); // 64 bytes ELF header, 56 bytes per program header
59        let mut current_offset = base_offset;
60
61        let text_offset = if arch.is_v3() && has_rodata {
62            rodata_size + base_offset
63        } else {
64            base_offset
65        };
66
67        // Get the entry point offset from dynamic_symbols if available
68        let entry_point_offset = dynamic_symbols
69            .get_entry_points()
70            .first()
71            .map(|(_, offset)| *offset)
72            .unwrap_or(0);
73
74        elf_header.e_entry = if arch.is_v3() {
75            ProgramHeader::V3_BYTECODE_VADDR + entry_point_offset
76        } else {
77            text_offset + entry_point_offset
78        };
79
80        // Create a vector of sections
81        let mut sections = Vec::new();
82        sections.push(SectionType::Default(NullSection::new()));
83
84        let mut section_names = Vec::new();
85        let has_debug_sections = debug_data.is_some() || !debug_sections.is_empty();
86
87        // Add section_names in fixed order for shstrtab
88        section_names.push(".text".to_string());
89        if has_rodata {
90            section_names.push(".rodata".to_string());
91        }
92
93        if arch.is_v3() && has_rodata {
94            // Data section
95            let mut rodata_section = SectionType::Data(data_section);
96            rodata_section.set_offset(current_offset);
97            rodata_section.set_vaddr(ProgramHeader::V3_RODATA_VADDR);
98            current_offset += rodata_section.size();
99            sections.push(rodata_section);
100
101            // Code section
102            let mut text_section = SectionType::Code(code_section);
103            text_section.set_offset(current_offset);
104            text_section.set_vaddr(ProgramHeader::V3_BYTECODE_VADDR);
105            current_offset += text_section.size();
106            sections.push(text_section);
107        } else {
108            // Code section
109            let mut text_section = SectionType::Code(code_section);
110            text_section.set_offset(current_offset);
111            if arch.is_v3() {
112                text_section.set_vaddr(ProgramHeader::V3_BYTECODE_VADDR);
113            }
114            current_offset += text_section.size();
115            sections.push(text_section);
116
117            // Data section (if any)
118            if has_rodata {
119                let mut rodata_section = SectionType::Data(data_section);
120                rodata_section.set_offset(current_offset);
121                if arch.is_v3() {
122                    rodata_section.set_vaddr(ProgramHeader::V3_RODATA_VADDR);
123                }
124                current_offset += rodata_section.size();
125                sections.push(rodata_section);
126            }
127        }
128
129        let padding = (8 - (current_offset % 8)) % 8;
130        current_offset += padding;
131
132        if arch.is_v3() {
133            // v3 programs are loaded entirely through program headers; the
134            // loader never reads the section header table. Unless the program
135            // is built in debug mode, we omit section headers along with the
136            // .shstrtab and debug sections that exist only to support them,
137            // keeping v3 binaries minimal.
138            if has_rodata {
139                // 2 headers: rodata (PF_R) then bytecode (PF_X)
140                let rodata_offset = base_offset;
141                let bytecode_offset = base_offset + rodata_size;
142                program_headers = Some(vec![
143                    ProgramHeader::new_load(rodata_offset, rodata_size, false, arch),
144                    ProgramHeader::new_load(bytecode_offset, bytecode_size, true, arch),
145                ]);
146            } else {
147                // 1 header: bytecode only (PF_X)
148                program_headers = Some(vec![ProgramHeader::new_load(
149                    base_offset,
150                    bytecode_size,
151                    true,
152                    arch,
153                )]);
154            }
155
156            if has_debug_sections {
157                // If debug info is present, generate debug sections
158                let debug_sections = Self::generate_debug_sections(
159                    debug_sections,
160                    &debug_data,
161                    ProgramHeader::V3_BYTECODE_VADDR,
162                    &mut section_names,
163                    &mut current_offset,
164                );
165
166                for debug_section in debug_sections {
167                    sections.push(debug_section);
168                }
169
170                let mut shstrtab_section = SectionType::ShStrTab(ShStrTabSection::new(
171                    (section_names
172                        .iter()
173                        .map(|name| name.len() + 1)
174                        .sum::<usize>()
175                        + 1) as u32,
176                    section_names,
177                ));
178                shstrtab_section.set_offset(current_offset);
179                current_offset += shstrtab_section.size();
180                sections.push(shstrtab_section);
181            }
182        } else if !prog_is_static {
183            let mut symbol_names = Vec::new();
184            let mut dyn_syms = Vec::new();
185            let mut dyn_str_offset = 1;
186
187            dyn_syms.push(DynamicSymbol::new(0, 0, 0, 0, 0, 0));
188
189            // all symbols handled right now are all global symbols
190            for (name, _) in dynamic_symbols.get_entry_points() {
191                symbol_names.push(name.clone());
192                dyn_syms.push(DynamicSymbol::new(
193                    dyn_str_offset as u32,
194                    0x10,
195                    0,
196                    1,
197                    elf_header.e_entry,
198                    0,
199                ));
200                dyn_str_offset += name.len() + 1;
201            }
202
203            for (name, _) in dynamic_symbols.get_call_targets() {
204                symbol_names.push(name.clone());
205                dyn_syms.push(DynamicSymbol::new(dyn_str_offset as u32, 0x10, 0, 0, 0, 0));
206                dyn_str_offset += name.len() + 1;
207            }
208
209            let mut rel_count = 0;
210            let mut rel_dyns = Vec::new();
211            for (offset, rel_type, name) in relocation_data.get_rel_dyns() {
212                if rel_type == RelocationType::RSbfSyscall {
213                    if let Some(index) = symbol_names.iter().position(|n| *n == name) {
214                        rel_dyns.push(RelDyn::new(
215                            offset + text_offset,
216                            rel_type as u64,
217                            index as u64 + 1,
218                        ));
219                    } else {
220                        panic!("Symbol {} not found in symbol_names", name);
221                    }
222                } else if rel_type == RelocationType::RSbf64Relative {
223                    rel_count += 1;
224                    rel_dyns.push(RelDyn::new(offset + text_offset, rel_type as u64, 0));
225                }
226            }
227            // create four dynamic related sections
228            let mut dynamic_section = SectionType::Dynamic(DynamicSection::new(
229                (section_names
230                    .iter()
231                    .map(|name| name.len() + 1)
232                    .sum::<usize>()
233                    + 1) as u32,
234            ));
235            section_names.push(dynamic_section.name().to_string());
236
237            let mut dynsym_section = SectionType::DynSym(DynSymSection::new(
238                (section_names
239                    .iter()
240                    .map(|name| name.len() + 1)
241                    .sum::<usize>()
242                    + 1) as u32,
243                dyn_syms,
244            ));
245            section_names.push(dynsym_section.name().to_string());
246
247            let mut dynstr_section = SectionType::DynStr(DynStrSection::new(
248                (section_names
249                    .iter()
250                    .map(|name| name.len() + 1)
251                    .sum::<usize>()
252                    + 1) as u32,
253                symbol_names,
254            ));
255            section_names.push(dynstr_section.name().to_string());
256
257            let mut rel_dyn_section = SectionType::RelDyn(RelDynSection::new(
258                (section_names
259                    .iter()
260                    .map(|name| name.len() + 1)
261                    .sum::<usize>()
262                    + 1) as u32,
263                rel_dyns,
264            ));
265            section_names.push(rel_dyn_section.name().to_string());
266
267            dynamic_section.set_offset(current_offset);
268            if let SectionType::Dynamic(ref mut dynamic_section) = dynamic_section {
269                // link to .dynstr
270                dynamic_section.set_link(
271                    section_names
272                        .iter()
273                        .position(|name| name == ".dynstr")
274                        .expect("missing .dynstr section") as u32
275                        + 1,
276                );
277                dynamic_section.set_rel_count(rel_count);
278            }
279            current_offset += dynamic_section.size();
280
281            dynsym_section.set_offset(current_offset);
282            if let SectionType::DynSym(ref mut dynsym_section) = dynsym_section {
283                // link to .dynstr
284                dynsym_section.set_link(
285                    section_names
286                        .iter()
287                        .position(|name| name == ".dynstr")
288                        .expect("missing .dynstr section") as u32
289                        + 1,
290                );
291            }
292            current_offset += dynsym_section.size();
293
294            dynstr_section.set_offset(current_offset);
295            current_offset += dynstr_section.size();
296
297            rel_dyn_section.set_offset(current_offset);
298            if let SectionType::RelDyn(ref mut rel_dyn_section) = rel_dyn_section {
299                // link to .dynsym
300                rel_dyn_section.set_link(
301                    section_names
302                        .iter()
303                        .position(|name| name == ".dynsym")
304                        .expect("missing .dynsym section") as u32
305                        + 1,
306                );
307            }
308            current_offset += rel_dyn_section.size();
309
310            if let SectionType::Dynamic(ref mut dynamic_section) = dynamic_section {
311                dynamic_section.set_rel_offset(rel_dyn_section.offset());
312                dynamic_section.set_rel_size(rel_dyn_section.size());
313                dynamic_section.set_dynsym_offset(dynsym_section.offset());
314                dynamic_section.set_dynstr_offset(dynstr_section.offset());
315                dynamic_section.set_dynstr_size(dynstr_section.size());
316            }
317
318            // Generate debug sections
319            let debug_sections = Self::generate_debug_sections(
320                debug_sections,
321                &debug_data,
322                text_offset,
323                &mut section_names,
324                &mut current_offset,
325            );
326
327            let mut shstrtab_section = SectionType::ShStrTab(ShStrTabSection::new(
328                (section_names
329                    .iter()
330                    .map(|name| name.len() + 1)
331                    .sum::<usize>()
332                    + 1) as u32,
333                section_names,
334            ));
335            shstrtab_section.set_offset(current_offset);
336            current_offset += shstrtab_section.size();
337
338            program_headers = Some(vec![
339                ProgramHeader::new_load(
340                    text_offset,
341                    text_size,
342                    true, // executable
343                    arch,
344                ),
345                ProgramHeader::new_load(
346                    dynsym_section.offset(),
347                    dynsym_section.size() + dynstr_section.size() + rel_dyn_section.size(),
348                    false,
349                    arch,
350                ),
351                ProgramHeader::new_dynamic(dynamic_section.offset(), dynamic_section.size()),
352            ]);
353
354            sections.push(dynamic_section);
355            sections.push(dynsym_section);
356            sections.push(dynstr_section);
357            sections.push(rel_dyn_section);
358
359            for debug_section in debug_sections {
360                sections.push(debug_section);
361            }
362
363            sections.push(shstrtab_section);
364        } else {
365            // Create a vector of section names
366            let mut section_names = Vec::new();
367            for section in &sections {
368                section_names.push(section.name().to_string());
369            }
370
371            // Generate debug sections
372            let debug_sections = Self::generate_debug_sections(
373                debug_sections,
374                &debug_data,
375                text_offset,
376                &mut section_names,
377                &mut current_offset,
378            );
379
380            for debug_section in debug_sections {
381                sections.push(debug_section);
382            }
383
384            let mut shstrtab_section = ShStrTabSection::new(
385                section_names
386                    .iter()
387                    .map(|name| name.len() + 1)
388                    .sum::<usize>() as u32,
389                section_names,
390            );
391            shstrtab_section.set_offset(current_offset);
392            current_offset += shstrtab_section.size();
393            sections.push(SectionType::ShStrTab(shstrtab_section));
394        }
395
396        // Update section header offset in ELF header. v3 binaries carry no
397        // section header table unless debug info is present.
398        if !arch.is_v3() || has_debug_sections {
399            let padding = (8 - (current_offset % 8)) % 8;
400            elf_header.e_shoff = current_offset + padding;
401            elf_header.e_shnum = sections.len() as u16;
402            elf_header.e_shstrndx = sections.len() as u16 - 1;
403        }
404
405        Self {
406            elf_header,
407            program_headers,
408            sections,
409        }
410    }
411
412    pub fn emit_bytecode(&self) -> Vec<u8> {
413        let mut bytes = Vec::new();
414
415        // Emit ELF Header bytes
416        bytes.extend(self.elf_header.bytecode());
417
418        // Emit program headers
419        if let Some(program_headers) = &self.program_headers {
420            for ph in program_headers {
421                bytes.extend(ph.bytecode());
422            }
423        }
424
425        // Emit sections
426        for section in &self.sections {
427            bytes.extend(section.bytecode());
428        }
429
430        // Emit section headers (omitted when there is no section header table,
431        // e.g. v3 binaries).
432        if self.elf_header.e_shoff != 0 {
433            for section in &self.sections {
434                bytes.extend(section.section_header_bytecode());
435            }
436        }
437
438        bytes
439    }
440
441    fn generate_debug_sections(
442        parsed_debug_sections: Vec<DebugSection>,
443        debug_data: &Option<DebugData>,
444        text_offset: u64,
445        section_names: &mut Vec<String>,
446        current_offset: &mut u64,
447    ) -> Vec<SectionType> {
448        if let Some(data) = debug_data {
449            debug::generate_debug_sections(data, text_offset, section_names, current_offset)
450                .into_iter()
451                .enumerate()
452                .map(|(i, s)| match i {
453                    0 => SectionType::DebugAbbrev(s),
454                    1 => SectionType::DebugInfo(s),
455                    2 => SectionType::DebugLine(s),
456                    3 => SectionType::DebugLineStr(s),
457                    _ => unreachable!(),
458                })
459                .collect()
460        } else {
461            reuse_debug_sections(parsed_debug_sections, section_names, current_offset)
462        }
463    }
464
465    pub fn has_rodata(&self) -> bool {
466        self.sections.iter().any(|s| s.name() == ".rodata")
467    }
468
469    pub fn parse_rodata(&self) -> Vec<(String, usize, String)> {
470        let rodata = self
471            .sections
472            .iter()
473            .find(|s| s.name() == ".rodata")
474            .unwrap();
475        if let SectionType::Data(data_section) = rodata {
476            data_section.rodata()
477        } else {
478            panic!("ROData section not found");
479        }
480    }
481
482    pub fn save_to_file(&self, input_path: &str) -> std::io::Result<()> {
483        // Get the file stem (name without extension) from input path
484        let path = Path::new(input_path);
485        let file_stem = path
486            .file_stem()
487            .and_then(|s| s.to_str())
488            .unwrap_or("output");
489
490        // Create the output file name with .so extension
491        let output_path = format!("{}.so", file_stem);
492
493        // Get the bytecode
494        let bytes = self.emit_bytecode();
495
496        // Write bytes to file
497        let mut file = File::create(output_path)?;
498        file.write_all(&bytes)?;
499
500        Ok(())
501    }
502}
503
504#[cfg(test)]
505mod tests {
506    use {
507        super::*,
508        crate::{SbpfArch, parser::parse},
509    };
510
511    #[test]
512    fn test_program_from_simple_source() {
513        let source = "exit";
514        for arch in [SbpfArch::V0, SbpfArch::V3] {
515            let parse_result = parse(source, arch).unwrap();
516            let program = Program::from_parse_result(parse_result, None);
517
518            // Verify basic structure
519            assert!(!program.sections.is_empty());
520            assert!(program.sections.len() >= 2);
521        }
522    }
523
524    #[test]
525    fn test_program_without_rodata() {
526        let source = "exit";
527        for arch in [SbpfArch::V0, SbpfArch::V3] {
528            let parse_result = parse(source, arch).unwrap();
529            let program = Program::from_parse_result(parse_result, None);
530
531            assert!(!program.has_rodata());
532        }
533    }
534
535    #[test]
536    fn test_program_emit_bytecode() {
537        let source = "exit";
538        for arch in [SbpfArch::V0, SbpfArch::V3] {
539            let parse_result = parse(source, arch).unwrap();
540            let program = Program::from_parse_result(parse_result, None);
541
542            let bytecode = program.emit_bytecode();
543            assert!(!bytecode.is_empty());
544            // Should start with ELF magic
545            assert_eq!(&bytecode[0..4], b"\x7fELF");
546        }
547    }
548
549    #[test]
550    fn test_v0_program_static_no_program_headers() {
551        // Create a static program (no dynamic symbols)
552        let source = "exit";
553        let mut parse_result = parse(source, SbpfArch::V0).unwrap();
554        parse_result.prog_is_static = true;
555
556        let program = Program::from_parse_result(parse_result, None);
557        assert!(program.program_headers.is_none());
558        assert_eq!(program.elf_header.e_phnum, 0);
559    }
560
561    #[test]
562    fn test_program_sections_ordering() {
563        let source = r"exit";
564        for arch in [SbpfArch::V0, SbpfArch::V3] {
565            let parse_result = parse(source, arch).unwrap();
566            let program = Program::from_parse_result(parse_result, None);
567
568            // First section should be null
569            assert_eq!(program.sections[0].name(), "");
570            // Second should be .text
571            assert_eq!(program.sections[1].name(), ".text");
572        }
573    }
574
575    #[test]
576    fn test_program_sections_debug() {
577        let source = "exit";
578        for arch in [SbpfArch::V0, SbpfArch::V3] {
579            let parse_result = parse(source, arch).unwrap();
580            let debug_data = Some(DebugData {
581                filename: "test.s".to_string(),
582                directory: "/test".to_string(),
583                lines: vec![],
584                labels: vec![],
585                code_start: 0,
586                code_end: 8,
587            });
588            let program = Program::from_parse_result(parse_result, debug_data);
589
590            let debug_section_names: Vec<&str> = program
591                .sections
592                .iter()
593                .map(|s| s.name())
594                .filter(|name| name.starts_with(".debug_"))
595                .collect();
596
597            assert!(debug_section_names.contains(&".debug_abbrev"));
598            assert!(debug_section_names.contains(&".debug_info"));
599            assert!(debug_section_names.contains(&".debug_line"));
600            assert!(debug_section_names.contains(&".debug_line_str"));
601        }
602    }
603
604    #[test]
605    fn test_v3_e_flags() {
606        let source = "exit";
607        let parse_result = parse(source, SbpfArch::V3).unwrap();
608        let program = Program::from_parse_result(parse_result, None);
609        assert_eq!(program.elf_header.e_flags, 3);
610    }
611
612    #[test]
613    fn test_v3_no_rodata_one_header() {
614        let source = "exit";
615        let parse_result = parse(source, SbpfArch::V3).unwrap();
616        let program = Program::from_parse_result(parse_result, None);
617
618        let headers = program.program_headers.as_ref().unwrap();
619        assert_eq!(headers.len(), 1);
620        assert_eq!(headers[0].p_flags, ProgramHeader::PF_X);
621        assert_eq!(headers[0].p_vaddr, ProgramHeader::V3_BYTECODE_VADDR);
622    }
623
624    #[test]
625    fn test_v3_with_rodata_two_headers() {
626        let source = r#"
627.rodata
628msg: .ascii "test"
629.text
630.globl entrypoint
631entrypoint:
632    exit
633        "#;
634        let parse_result = parse(source, SbpfArch::V3).unwrap();
635        let program = Program::from_parse_result(parse_result, None);
636
637        let headers = program.program_headers.as_ref().unwrap();
638        assert_eq!(headers.len(), 2);
639        // first header: rodata (PF_R, vaddr=0)
640        assert_eq!(headers[0].p_flags, ProgramHeader::PF_R);
641        assert_eq!(headers[0].p_vaddr, ProgramHeader::V3_RODATA_VADDR);
642        // second header: bytecode (PF_X, vaddr=1<<32)
643        assert_eq!(headers[1].p_flags, ProgramHeader::PF_X);
644        assert_eq!(headers[1].p_vaddr, ProgramHeader::V3_BYTECODE_VADDR);
645    }
646
647    #[test]
648    fn test_v3_e_entry() {
649        let source = r#"
650.globl entrypoint
651entrypoint:
652    exit
653        "#;
654        let parse_result = parse(source, SbpfArch::V3).unwrap();
655        let program = Program::from_parse_result(parse_result, None);
656
657        // v3: e_entry must be >= V3_BYTECODE_VADDR (1 << 32)
658        assert!(program.elf_header.e_entry >= ProgramHeader::V3_BYTECODE_VADDR,);
659    }
660
661    #[test]
662    fn test_v3_p_offset() {
663        let source = r#"
664.rodata
665msg: .ascii "test"
666.text
667.globl entrypoint
668entrypoint:
669    exit
670        "#;
671        let parse_result = parse(source, SbpfArch::V3).unwrap();
672        let program = Program::from_parse_result(parse_result, None);
673
674        let headers = program.program_headers.as_ref().unwrap();
675        let expected_first_offset = 64 + (program.elf_header.e_phnum as u64) * 56;
676        assert_eq!(headers[0].p_offset, expected_first_offset);
677        assert_eq!(
678            headers[1].p_offset,
679            headers[0].p_offset + headers[0].p_filesz
680        );
681    }
682
683    #[test]
684    fn test_v3_no_dynamic_sections() {
685        let source = r#"
686.globl entrypoint
687entrypoint:
688    call sol_log_64_
689    exit
690        "#;
691        let parse_result = parse(source, SbpfArch::V3).unwrap();
692        let program = Program::from_parse_result(parse_result, None);
693
694        // v3 should not have any dynamic sections
695        let section_names: Vec<&str> = program.sections.iter().map(|s| s.name()).collect();
696        assert!(!section_names.contains(&".dynamic"));
697        assert!(!section_names.contains(&".dynsym"));
698        assert!(!section_names.contains(&".dynstr"));
699        assert!(!section_names.contains(&".rel.dyn"));
700    }
701}