forked from succinctlabs/sp1
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathelf.rs
148 lines (129 loc) · 5.45 KB
/
elf.rs
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
use std::cmp::min;
use elf::{
abi::{EM_RISCV, ET_EXEC, PF_X, PT_LOAD},
endian::LittleEndian,
file::Class,
ElfBytes,
};
use hashbrown::HashMap;
use sp1_primitives::consts::{MAXIMUM_MEMORY_SIZE, WORD_SIZE};
/// RISC-V 32IM ELF (Executable and Linkable Format) File.
///
/// This file represents a binary in the ELF format, specifically the RISC-V 32IM architecture
/// with the following extensions:
///
/// - Base Integer Instruction Set (I)
/// - Integer Multiplication and Division (M)
///
/// This format is commonly used in embedded systems and is supported by many compilers.
#[derive(Debug, Clone)]
pub(crate) struct Elf {
/// The instructions of the program encoded as 32-bits.
pub(crate) instructions: Vec<u32>,
/// The start address of the program.
pub(crate) pc_start: u32,
/// The base address of the program.
pub(crate) pc_base: u32,
/// The initial memory image, useful for global constants.
pub(crate) memory_image: HashMap<u32, u32>,
}
impl Elf {
/// Create a new [Elf].
#[must_use]
pub(crate) const fn new(
instructions: Vec<u32>,
pc_start: u32,
pc_base: u32,
memory_image: HashMap<u32, u32>,
) -> Self {
Self { instructions, pc_start, pc_base, memory_image }
}
/// Parse the ELF file into a vector of 32-bit encoded instructions and the first memory
/// address.
///
/// # Errors
///
/// This function may return an error if the ELF is not valid.
///
/// Reference: [Executable and Linkable Format](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format)
pub(crate) fn decode(input: &[u8]) -> eyre::Result<Self> {
let mut image: HashMap<u32, u32> = HashMap::new();
// Parse the ELF file assuming that it is little-endian..
let elf = ElfBytes::<LittleEndian>::minimal_parse(input)?;
// Some sanity checks to make sure that the ELF file is valid.
if elf.ehdr.class != Class::ELF32 {
eyre::bail!("must be a 32-bit elf");
} else if elf.ehdr.e_machine != EM_RISCV {
eyre::bail!("must be a riscv machine");
} else if elf.ehdr.e_type != ET_EXEC {
eyre::bail!("must be executable");
}
// Get the entrypoint of the ELF file as an u32.
let entry: u32 = elf.ehdr.e_entry.try_into()?;
// Make sure the entrypoint is valid.
if entry == MAXIMUM_MEMORY_SIZE || entry % WORD_SIZE as u32 != 0 {
eyre::bail!("invalid entrypoint");
}
// Get the segments of the ELF file.
let segments = elf.segments().ok_or_else(|| eyre::eyre!("failed to get segments"))?;
if segments.len() > 256 {
eyre::bail!("too many program headers");
}
let mut instructions: Vec<u32> = Vec::new();
let mut base_address = u32::MAX;
// Only read segments that are executable instructions that are also PT_LOAD.
for segment in segments.iter().filter(|x| x.p_type == PT_LOAD) {
// Get the file size of the segment as an u32.
let file_size: u32 = segment.p_filesz.try_into()?;
if file_size == MAXIMUM_MEMORY_SIZE {
eyre::bail!("invalid segment file_size");
}
// Get the memory size of the segment as an u32.
let mem_size: u32 = segment.p_memsz.try_into()?;
if mem_size == MAXIMUM_MEMORY_SIZE {
eyre::bail!("Invalid segment mem_size");
}
// Get the virtual address of the segment as an u32.
let vaddr: u32 = segment.p_vaddr.try_into()?;
if vaddr % WORD_SIZE as u32 != 0 {
eyre::bail!("vaddr {vaddr:08x} is unaligned");
}
// If the virtual address is less than the first memory address, then update the first
// memory address.
if (segment.p_flags & PF_X) != 0 && base_address > vaddr {
base_address = vaddr;
}
// Get the offset to the segment.
let offset: u32 = segment.p_offset.try_into()?;
// Read the segment and decode each word as an instruction.
for i in (0..mem_size).step_by(WORD_SIZE) {
let addr = vaddr.checked_add(i).ok_or_else(|| eyre::eyre!("vaddr overflow"))?;
if addr >= MAXIMUM_MEMORY_SIZE {
eyre::bail!(
"address [0x{addr:08x}] exceeds maximum address for guest programs [0x{MAXIMUM_MEMORY_SIZE:08x}]"
);
}
// If we are reading past the end of the file, then break.
if i >= file_size {
image.insert(addr, 0);
continue;
}
// Get the word as an u32 but make sure we don't read past the end of the file.
let mut word = 0;
let len = min(file_size - i, WORD_SIZE as u32);
for j in 0..len {
let offset = (offset + i + j) as usize;
let byte = input
.get(offset)
.ok_or_else(|| eyre::eyre!("failed to read segment offset"))?;
word |= u32::from(*byte) << (j * 8);
}
image.insert(addr, word);
if (segment.p_flags & PF_X) != 0 {
instructions.push(word);
}
}
}
Ok(Elf::new(instructions, entry, base_address, image))
}
}