Skip to content

Commit b04444b

Browse files
kov wcet-elf: analyze any RISC-V ELF binary for WCET and stack depth
1 parent 55bc9a2 commit b04444b

1 file changed

Lines changed: 162 additions & 0 deletions

File tree

src/main.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,132 @@ fn main() {
114114
eprintln!(" nrf52840 Nordic nRF52840 (ARM Cortex-M4F, 256KB RAM, 64MHz)");
115115
eprintln!(" rp2040 Raspberry Pi Pico (ARM Cortex-M0+, 264KB RAM, 133MHz)");
116116
}
117+
"wcet-elf" => {
118+
if args.len() < 3 {
119+
eprintln!("usage: kov wcet-elf <firmware.elf>");
120+
process::exit(1);
121+
}
122+
let data = std::fs::read(&args[2]).unwrap_or_else(|e| die(&format!("{e}")));
123+
if data.len() < 84 || &data[0..4] != b"\x7fELF" {
124+
die("not a valid ELF file");
125+
}
126+
// parse ELF: find .text section
127+
let entry = u32::from_le_bytes([data[24], data[25], data[26], data[27]]);
128+
let phoff = u32::from_le_bytes([data[28], data[29], data[30], data[31]]) as usize;
129+
let phnum = u16::from_le_bytes([data[44], data[45]]) as usize;
130+
131+
// find PT_LOAD segment with execute permission
132+
let mut text_offset = 0usize;
133+
let mut text_vaddr = 0u32;
134+
let mut text_size = 0usize;
135+
for i in 0..phnum {
136+
let off = phoff + i * 32;
137+
if off + 32 > data.len() {
138+
break;
139+
}
140+
let p_type =
141+
u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]);
142+
let p_offset = u32::from_le_bytes([
143+
data[off + 4],
144+
data[off + 5],
145+
data[off + 6],
146+
data[off + 7],
147+
]) as usize;
148+
let p_vaddr = u32::from_le_bytes([
149+
data[off + 8],
150+
data[off + 9],
151+
data[off + 10],
152+
data[off + 11],
153+
]);
154+
let p_filesz = u32::from_le_bytes([
155+
data[off + 16],
156+
data[off + 17],
157+
data[off + 18],
158+
data[off + 19],
159+
]) as usize;
160+
let p_flags = u32::from_le_bytes([
161+
data[off + 24],
162+
data[off + 25],
163+
data[off + 26],
164+
data[off + 27],
165+
]);
166+
if p_type == 1 && (p_flags & 1) != 0 {
167+
// PT_LOAD + PF_X
168+
text_offset = p_offset;
169+
text_vaddr = p_vaddr;
170+
text_size = p_filesz;
171+
}
172+
}
173+
174+
if text_size == 0 {
175+
die("no executable segment found");
176+
}
177+
178+
let code = &data[text_offset..text_offset + text_size];
179+
eprintln!(" file: {}", args[2]);
180+
eprintln!(" entry: {:#010X}", entry);
181+
eprintln!(" .text: {} bytes at {:#010X}", text_size, text_vaddr);
182+
eprintln!();
183+
184+
// per-function WCET: scan for function prologues (addi sp, sp, -N)
185+
let mut funcs: Vec<(u32, usize)> = Vec::new(); // (addr, offset)
186+
for i in (0..code.len().saturating_sub(3)).step_by(2) {
187+
if i + 3 >= code.len() {
188+
break;
189+
}
190+
let inst = u32::from_le_bytes([code[i], code[i + 1], code[i + 2], code[i + 3]]);
191+
let opcode = inst & 0x7F;
192+
let rd = (inst >> 7) & 0x1F;
193+
let f3 = (inst >> 12) & 7;
194+
let rs1 = (inst >> 15) & 0x1F;
195+
let imm = (inst as i32) >> 20;
196+
if opcode == 0x13 && f3 == 0 && rd == 2 && rs1 == 2 && imm < 0 {
197+
funcs.push((text_vaddr + i as u32, i));
198+
}
199+
}
200+
201+
// analyze each detected function
202+
eprintln!(" detected {} function prologues:", funcs.len());
203+
let mut total_cycles = 0u32;
204+
for (j, &(addr, start)) in funcs.iter().enumerate() {
205+
let end = if j + 1 < funcs.len() {
206+
funcs[j + 1].1
207+
} else {
208+
code.len()
209+
};
210+
if start >= end {
211+
continue;
212+
}
213+
let func_code = &code[start..end];
214+
let mut cycles = 0u32;
215+
let mut k = 0;
216+
while k + 3 < func_code.len() {
217+
let inst = u32::from_le_bytes([
218+
func_code[k],
219+
func_code[k + 1],
220+
func_code[k + 2],
221+
func_code[k + 3],
222+
]);
223+
cycles += estimate_cycles(inst);
224+
k += 4;
225+
}
226+
let stack = estimate_stack(func_code);
227+
eprintln!(
228+
" {:#010X}: ~{} cycles, {} bytes stack, {} instructions",
229+
addr,
230+
cycles,
231+
stack,
232+
func_code.len() / 4
233+
);
234+
total_cycles += cycles;
235+
}
236+
eprintln!();
237+
eprintln!(
238+
" total: ~{} cycles worst-case across {} functions",
239+
total_cycles,
240+
funcs.len()
241+
);
242+
}
117243
"svd" => {
118244
if args.len() < 3 {
119245
eprintln!("usage: kov svd <file.svd> [--name <board>]");
@@ -723,6 +849,42 @@ fn find_flag(args: &[String], flag: &str) -> Option<String> {
723849
args.windows(2).find(|w| w[0] == flag).map(|w| w[1].clone())
724850
}
725851

852+
fn estimate_cycles(inst: u32) -> u32 {
853+
let opcode = inst & 0x7F;
854+
let f3 = (inst >> 12) & 7;
855+
let f7 = inst >> 25;
856+
match opcode {
857+
0x37 | 0x17 => 1, // LUI, AUIPC
858+
0x6F | 0x67 => 1, // JAL, JALR
859+
0x63 => 1, // branches
860+
0x03 => 2, // loads
861+
0x23 => 2, // stores
862+
0x13 => 1, // immediate ALU
863+
0x33 => match (f3, f7) {
864+
(0, 0x01) => 5, // MUL
865+
(4, 0x01) | (5, 0x01) => 33, // DIV, DIVU
866+
(6, 0x01) | (7, 0x01) => 33, // REM, REMU
867+
_ => 1,
868+
},
869+
0x73 => 1, // SYSTEM
870+
_ => 1,
871+
}
872+
}
873+
874+
fn estimate_stack(code: &[u8]) -> u32 {
875+
if code.len() < 4 {
876+
return 0;
877+
}
878+
let first = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
879+
// addi sp, sp, -N: opcode=0x13, rd=sp(2), rs1=sp(2), funct3=0
880+
if first & 0x000FFFFF == 0x00010113 {
881+
let imm = (first as i32) >> 20;
882+
(-imm) as u32
883+
} else {
884+
0
885+
}
886+
}
887+
726888
fn die(msg: &str) -> ! {
727889
eprintln!("error: {msg}");
728890
process::exit(1);

0 commit comments

Comments
 (0)