1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
use crate::cannon::{Page, State, PAGE_SIZE};
use elf::{
    endian::{BigEndian, EndianParse, LittleEndian},
    section::SectionHeader,
    ElfBytes,
};
use log::debug;
use std::{collections::HashMap, path::Path};

pub enum Architecture {
    Mips,
    RiscV32,
}

pub fn make_state<T: EndianParse>(file: ElfBytes<T>) -> Result<State, String> {
    // Checking it is RISC-V

    let (shdrs_opt, strtab_opt) = file
        .section_headers_with_strtab()
        .expect("shdrs offsets should be valid");
    let (shdrs, strtab) = (
        shdrs_opt.expect("Should have shdrs"),
        strtab_opt.expect("Should have strtab"),
    );

    // Parse the shdrs and collect them into a map keyed on their zero-copied name
    let sections_by_name: HashMap<&str, SectionHeader> = shdrs
        .iter()
        .map(|shdr| {
            (
                strtab
                    .get(shdr.sh_name as usize)
                    .expect("Failed to get section name"),
                shdr,
            )
        })
        .collect();

    debug!("Loading the text section, which contains the executable code.");
    // Getting the executable code.
    let text_section = sections_by_name
        .get(".text")
        .expect("Should have .text section");

    let (text_section_data, _) = file
        .section_data(text_section)
        .expect("Failed to read data from .text section");

    // address of starting instruction in code section
    let code_section_starting_address = text_section.sh_addr as usize;
    let code_section_size = text_section.sh_size as usize;
    // address of last instruction in code section
    let code_section_end_address = code_section_starting_address + code_section_size - 1;
    debug!(
        "The executable code starts at address {}, has size {} bytes, and ends at address {}.",
        code_section_starting_address, code_section_size, code_section_end_address
    );

    // Building the memory pages
    let mut memory: Vec<Page> = vec![];
    let page_size_usize: usize = PAGE_SIZE.try_into().unwrap();
    // Padding to get the right number of pages. We suppose that the memory
    // index starts at 0.

    // the address that the first page starts on
    let start_page_address: usize =
        (code_section_starting_address / page_size_usize) * page_size_usize;

    // the address that the last page starts on
    let end_page_address = (code_section_end_address / page_size_usize) * page_size_usize;

    let first_page_index = start_page_address / page_size_usize;

    let last_page_index = end_page_address / page_size_usize;

    let mut data_offset = 0;
    (first_page_index..=last_page_index).for_each(|page_index| {
        let mut data = vec![0; page_size_usize];
        // Special case where all code fits in one page
        if first_page_index == last_page_index {
            let data_length = code_section_end_address - code_section_starting_address;
            let page_offset = code_section_starting_address - start_page_address;
            data[page_offset..page_offset + data_length]
                .copy_from_slice(&text_section_data[0..data_length]);
            data_offset += data_length;
        } else {
            let data_length = if page_index == last_page_index {
                code_section_end_address - end_page_address
            } else {
                page_size_usize
            };
            let page_offset = if page_index == first_page_index {
                code_section_starting_address - start_page_address
            } else {
                0
            };
            data[page_offset..page_offset + data_length]
                .copy_from_slice(&text_section_data[data_offset..data_offset + data_length]);

            data_offset += data_length;
        }
        let page = Page {
            index: page_index as u32,
            data,
        };
        memory.push(page);
    });

    // FIXME: add data section into memory for static data saved in the binary

    // FIXME: we're lucky that RISCV32i and MIPS have the same number of
    let registers: [u32; 32] = [0; 32];

    // FIXME: it is only because we share the same structure for the state.
    let preimage_key: [u8; 32] = [0; 32];
    // FIXME: it is only because we share the same structure for the state.
    let preimage_offset = 0;

    // Entry point of the program
    let pc: u32 = file.ehdr.e_entry as u32;
    assert!(pc != 0, "Entry point is 0. The documentation of the ELF library says that it means the ELF doesn't have an entry point. This is not supported. This can happen if the binary given is an object file and not an executable file. You might need to call the linker (ld) before running the binary.");
    let next_pc: u32 = pc + 4u32;

    let state = State {
        memory,
        // FIXME: only because Cannon related
        preimage_key,
        // FIXME: only because Cannon related
        preimage_offset,
        pc,
        next_pc,
        // FIXME: only because Cannon related
        lo: 0,
        // FIXME: only because Cannon related
        hi: 0,
        heap: 0,
        exit: 0,
        exited: false,
        step: 0,
        registers,
        // FIXME: only because Cannon related
        last_hint: None,
        // FIXME: only because Cannon related
        preimage: None,
    };

    Ok(state)
}

pub fn parse_elf(arch: Architecture, path: &Path) -> Result<State, String> {
    debug!("Start parsing the ELF file to load a compatible state");
    let file_data = std::fs::read(path).expect("Could not read file.");
    let slice = file_data.as_slice();
    match arch {
        Architecture::Mips => {
            let file = ElfBytes::<BigEndian>::minimal_parse(slice).expect("Open ELF file failed.");
            assert_eq!(file.ehdr.e_machine, 8);
            make_state(file)
        }
        Architecture::RiscV32 => {
            let file =
                ElfBytes::<LittleEndian>::minimal_parse(slice).expect("Open ELF file failed.");
            assert_eq!(file.ehdr.e_machine, 243);
            make_state(file)
        }
    }
}