riscv_emu_rust/
lib.rs

1// @TODO: temporal
2const TEST_MEMORY_CAPACITY: u64 = 1024 * 512;
3const PROGRAM_MEMORY_CAPACITY: u64 = 1024 * 1024 * 128; // big enough to run Linux and xv6
4
5pub mod cpu;
6pub mod terminal;
7pub mod default_terminal;
8pub mod memory;
9pub mod mmu;
10pub mod device;
11
12use cpu::{Cpu, Xlen};
13use terminal::Terminal;
14
15/// RISC-V emulator. It emulates RISC-V CPU and peripheral devices.
16///
17/// Sample code to run the emulator.
18/// ```ignore
19/// // Creates an emulator with arbitary terminal
20/// let mut emulator = Emulator::new(Box::new(DefaultTerminal::new()));
21/// // Set up program content binary
22/// emulator.setup_program(program_content);
23/// // Set up Filesystem content binary
24/// emulator.setup_filesystem(fs_content);
25/// // Go!
26/// emulator.run();
27/// ```
28pub struct Emulator {
29	cpu: Cpu,
30
31	/// [`riscv-tests`](https://github.com/riscv/riscv-tests) program specific
32	/// properties. Whether the program set by `setup_program()` is
33	/// [`riscv-tests`](https://github.com/riscv/riscv-tests) program.
34	is_test: bool,
35
36	/// [`riscv-tests`](https://github.com/riscv/riscv-tests) specific properties.
37	/// The address where data will be sent to terminal
38	tohost_addr: u64
39}
40
41/// ELF section header
42struct SectionHeader {
43	sh_name: u64,
44	_sh_type: u64,
45	_sh_flags: u64,
46	sh_addr: u64,
47	sh_offset: u64,
48	sh_size: u64,
49	_sh_link: u64,
50	_sh_info: u64,
51	_sh_addralign: u64,
52	_sh_entsize: u64
53}
54
55impl Emulator {
56	/// Creates a new `Emulator`. [`Terminal`](terminal/trait.Terminal.html)
57	/// is internally used for transferring input/output data to/from `Emulator`.
58	/// 
59	/// # Arguments
60	/// * `terminal`
61	pub fn new(terminal: Box<dyn Terminal>) -> Self {
62		Emulator {
63			cpu: Cpu::new(terminal),
64
65			// These can be updated in setup_program()
66			is_test: false,
67			tohost_addr: 0
68		}
69	}
70
71	/// Runs program set by `setup_program()`. Calls `run_test()` if the program
72	/// is [`riscv-tests`](https://github.com/riscv/riscv-tests).
73	/// Otherwise calls `run_program()`.
74	pub fn run(&mut self) {
75		match self.is_test {
76			true => self.run_test(),
77			false => self.run_program()
78		};
79	}
80
81	/// Runs program set by `setup_program()`. The emulator won't stop forever.
82	pub fn run_program(&mut self) {
83		loop {
84			self.tick();
85		}
86	}
87
88	/// Method for running [`riscv-tests`](https://github.com/riscv/riscv-tests) program.
89	/// The differences from `run_program()` are
90	/// * Disassembles every instruction and dumps to terminal
91	/// * The emulator stops when the test finishes
92	/// * Displays the result message (pass/fail) to terminal
93	pub fn run_test(&mut self) {
94		// @TODO: Send this message to terminal?
95		println!("This elf file seems riscv-tests elf file. Running in test mode.");
96		loop {
97			let disas = self.cpu.disassemble_next_instruction();
98			self.put_bytes_to_terminal(disas.as_bytes());
99			self.put_bytes_to_terminal(&[10]); // new line
100
101			self.tick();
102
103			// It seems in riscv-tests ends with end code
104			// written to a certain physical memory address
105			// (0x80001000 in mose test cases) so checking
106			// the data in the address and terminating the test
107			// if non-zero data is written.
108			// End code 1 seems to mean pass.
109			let endcode = self.cpu.get_mut_mmu().load_word_raw(self.tohost_addr);
110			if endcode != 0 {
111				match endcode {
112					1 => {
113						self.put_bytes_to_terminal(format!("Test Passed with {:X}\n", endcode).as_bytes())
114					},
115					_ => {
116						self.put_bytes_to_terminal(format!("Test Failed with {:X}\n", endcode).as_bytes())
117					}
118				};
119				break;
120			}
121		}
122	}
123
124	/// Helper method. Sends ascii code bytes to terminal.
125	///
126	/// # Arguments
127	/// * `bytes`
128	fn put_bytes_to_terminal(&mut self, bytes: &[u8]) {
129		for i in 0..bytes.len() {
130			self.cpu.get_mut_terminal().put_byte(bytes[i]);
131		}
132	}
133
134	/// Runs CPU one cycle
135	pub fn tick(&mut self) {
136		self.cpu.tick();
137	}
138
139	/// Sets up program run by the program. This method analyzes the passed content
140	/// and configure CPU properly. If the passed contend doesn't seem ELF file,
141	/// it panics. This method is expected to be called only once.
142	///
143	/// # Arguments
144	/// * `data` Program binary
145	// @TODO: Make ElfAnalyzer and move the core logic there.
146	// @TODO: Returns `Err` if the passed contend doesn't seem ELF file
147	pub fn setup_program(&mut self, data: Vec<u8>) {
148		// analyze elf header
149
150		// check ELF magic number
151		if data[0] != 0x7f || data[1] != 0x45 || data[2] != 0x4c || data[3] != 0x46 {
152			panic!("This file does not seem ELF file");
153		}
154
155		let e_class = data[4];
156
157		let e_width = match e_class {
158			1 => 32,
159			2 => 64,
160			_ => panic!("Unknown e_class:{:X}", e_class)
161		};
162
163		let _e_endian = data[5];
164		let _e_elf_version = data[6];
165		let _e_osabi = data[7];
166		let _e_abi_version = data[8];
167
168		let mut offset = 0x10;
169
170		let mut _e_type = 0 as u64;
171		for i in 0..2 {
172			_e_type |= (data[offset] as u64) << (8 * i);
173			offset += 1;
174		}
175
176		let mut _e_machine = 0 as u64;
177		for i in 0..2 {
178			_e_machine |= (data[offset] as u64) << (8 * i);
179			offset += 1;
180		}
181
182		let mut _e_version = 0 as u64;
183		for i in 0..4 {
184			_e_version |= (data[offset] as u64) << (8 * i);
185			offset += 1;
186		}
187
188		let mut e_entry = 0 as u64;
189		for i in 0..e_width / 8 {
190			e_entry |= (data[offset] as u64) << (8 * i);
191			offset += 1;
192		}
193
194		let mut _e_phoff = 0 as u64;
195		for i in 0..e_width / 8 {
196			_e_phoff |= (data[offset] as u64) << (8 * i);
197			offset += 1;
198		}
199
200		let mut e_shoff = 0 as u64;
201		for i in 0..e_width / 8 {
202			e_shoff |= (data[offset] as u64) << (8 * i);
203			offset += 1;
204		}
205
206		let mut _e_flags = 0 as u64;
207		for i in 0..4 {
208			_e_flags |= (data[offset] as u64) << (8 * i);
209			offset += 1;
210		}
211
212		let mut _e_ehsize = 0 as u64;
213		for i in 0..2 {
214			_e_ehsize |= (data[offset] as u64) << (8 * i);
215			offset += 1;
216		}
217
218		let mut _e_phentsize = 0 as u64;
219		for i in 0..2 {
220			_e_phentsize |= (data[offset] as u64) << (8 * i);
221			offset += 1;
222		}
223
224		let mut _e_phnum = 0 as u64;
225		for i in 0..2 {
226			_e_phnum |= (data[offset] as u64) << (8 * i);
227			offset += 1;
228		}
229
230		let mut _e_shentsize = 0 as u64;
231		for i in 0..2 {
232			_e_shentsize |= (data[offset] as u64) << (8 * i);
233			offset += 1;
234		}
235
236		let mut e_shnum = 0 as u64;
237		for i in 0..2 {
238			e_shnum |= (data[offset] as u64) << (8 * i);
239			offset += 1;
240		}
241
242		let mut _e_shstrndx = 0 as u64;
243		for i in 0..2 {
244			_e_shstrndx |= (data[offset] as u64) << (8 * i);
245			offset += 1;
246		}
247
248		/*
249		println!("ELF:{}", e_width);
250		println!("e_endian:{:X}", _e_endian);
251		println!("e_elf_version:{:X}", _e_elf_version);
252		println!("e_osabi:{:X}", _e_osabi);
253		println!("e_abi_version:{:X}", _e_abi_version);
254		println!("e_type:{:X}", _e_type);
255		println!("e_machine:{:X}", _e_machine);
256		println!("e_version:{:X}", _e_version);
257		println!("e_entry:{:X}", e_entry);
258		println!("e_phoff:{:X}", _e_phoff);
259		println!("e_shoff:{:X}", e_shoff);
260		println!("e_flags:{:X}", _e_flags);
261		println!("e_ehsize:{:X}", _e_ehsize);
262		println!("e_phentsize:{:X}", _e_phentsize);
263		println!("e_phnum:{:X}", _e_phnum);
264		println!("e_shentsize:{:X}", _e_shentsize);
265		println!("e_shnum:{:X}", e_shnum);
266		println!("e_shstrndx:{:X}", _e_shstrndx);
267		*/
268
269		// analyze program headers
270
271		/*
272		offset = e_phoff as usize;
273		for i in 0..e_phnum {
274			let mut p_type = 0 as u64;
275			for i in 0..4 {
276				p_type |= (data[offset] as u64) << (8 * i);
277				offset += 1;
278			}
279
280			let mut p_flags = 0 as u64;
281			if e_width == 64 {
282				for i in 0..4 {
283					p_flags |= (data[offset] as u64) << (8 * i);
284					offset += 1;
285				}
286			}
287
288			let mut p_offset = 0 as u64;
289			for i in 0..e_width / 8 {
290				p_offset |= (data[offset] as u64) << (8 * i);
291				offset += 1;
292			}
293
294			let mut p_vaddr = 0 as u64;
295			for i in 0..e_width / 8 {
296				p_vaddr |= (data[offset] as u64) << (8 * i);
297				offset += 1;
298			}
299
300			let mut p_paddr = 0 as u64;
301			for i in 0..e_width / 8 {
302				p_paddr |= (data[offset] as u64) << (8 * i);
303				offset += 1;
304			}
305
306			let mut p_filesz = 0 as u64;
307			for i in 0..e_width / 8 {
308				p_filesz |= (data[offset] as u64) << (8 * i);
309				offset += 1;
310			}
311
312			let mut p_memsz = 0 as u64;
313			for i in 0..e_width / 8 {
314				p_memsz |= (data[offset] as u64) << (8 * i);
315				offset += 1;
316			}
317
318			if e_width == 32 {
319				for i in 0..4 {
320					p_flags |= (data[offset] as u64) << (8 * i);
321					offset += 1;
322				}
323			}
324
325			let mut p_align = 0 as u64;
326			for i in 0..e_width / 8 {
327				p_align |= (data[offset] as u64) << (8 * i);
328				offset += 1;
329			}
330
331			println!("");
332			println!("Program:{:X}", i);
333			println!("p_type:{:X}", p_type);
334			println!("p_flags:{:X}", p_flags);
335			println!("p_offset:{:X}", p_offset);
336			println!("p_vaddr:{:X}", p_vaddr);
337			println!("p_paddr:{:X}", p_paddr);
338			println!("p_filesz:{:X}", p_filesz);
339			println!("p_memsz:{:X}", p_memsz);
340			println!("p_align:{:X}", p_align);
341			println!("p_align:{:X}", p_align);
342		}
343		*/
344
345		// analyze section headers
346
347		let mut program_data_section_headers = vec![];
348		let mut string_table_section_headers = vec![];
349
350		offset = e_shoff as usize;
351		for _i in 0..e_shnum {
352			let mut sh_name = 0 as u64;
353			for i in 0..4 {
354				sh_name |= (data[offset] as u64) << (8 * i);
355				offset += 1;
356			}
357
358			let mut sh_type = 0 as u64;
359			for i in 0..4 {
360				sh_type |= (data[offset] as u64) << (8 * i);
361				offset += 1;
362			}
363
364			let mut sh_flags = 0 as u64;
365			for i in 0..e_width / 8 {
366				sh_flags |= (data[offset] as u64) << (8 * i);
367				offset += 1;
368			}
369
370			let mut sh_addr = 0 as u64;
371			for i in 0..e_width / 8 {
372				sh_addr |= (data[offset] as u64) << (8 * i);
373				offset += 1;
374			}
375
376			let mut sh_offset = 0 as u64;
377			for i in 0..e_width / 8 {
378				sh_offset |= (data[offset] as u64) << (8 * i);
379				offset += 1;
380			}
381
382			let mut sh_size = 0 as u64;
383			for i in 0..e_width / 8 {
384				sh_size |= (data[offset] as u64) << (8 * i);
385				offset += 1;
386			}
387
388			let mut sh_link = 0 as u64;
389			for i in 0..4 {
390				sh_link |= (data[offset] as u64) << (8 * i);
391				offset += 1;
392			}
393
394			let mut sh_info = 0 as u64;
395			for i in 0..4 {
396				sh_info |= (data[offset] as u64) << (8 * i);
397				offset += 1;
398			}
399
400			let mut sh_addralign = 0 as u64;
401			for i in 0..e_width / 8 {
402				sh_addralign |= (data[offset] as u64) << (8 * i);
403				offset += 1;
404			}
405
406			let mut sh_entsize = 0 as u64;
407			for i in 0..e_width / 8 {
408				sh_entsize |= (data[offset] as u64) << (8 * i);
409				offset += 1;
410			}
411
412			/*
413			println!("");
414			println!("Section:{:X}", i);
415			println!("sh_name:{:X}", sh_name);
416			println!("sh_type:{:X}", sh_type);
417			println!("sh_flags:{:X}", sh_flags);
418			println!("sh_addr:{:X}", sh_addr);
419			println!("sh_offset:{:X}", sh_offset);
420			println!("sh_size:{:X}", sh_size);
421			println!("sh_link:{:X}", sh_link);
422			println!("sh_info:{:X}", sh_info);
423			println!("sh_addralign:{:X}", sh_addralign);
424			println!("sh_entsize:{:X}", sh_entsize);
425			*/
426
427			let section_header = SectionHeader {
428				sh_name: sh_name,
429				_sh_type: sh_type,
430				_sh_flags: sh_flags,
431				sh_addr: sh_addr,
432				sh_offset: sh_offset,
433				sh_size: sh_size,
434				_sh_link: sh_link,
435				_sh_info: sh_info,
436				_sh_addralign: sh_addralign,
437				_sh_entsize: sh_entsize
438			};
439
440			if sh_type == 1 {
441				program_data_section_headers.push(section_header);
442			} else if sh_type == 3 {
443				string_table_section_headers.push(section_header);
444			}
445		}
446
447		// Find program data section named .tohost to detect if the elf file is riscv-tests
448		// @TODO: Expecting it can be only in the first string table section.
449		// What if .tohost section name is in the second or later string table sectioin?
450		let tohost_values = vec![0x2e, 0x74, 0x6f, 0x68, 0x6f, 0x73, 0x74, 0x00]; // ".tohost\null"
451		let mut tohost_addr = 0; // Expecting .tohost address is non-null if exists
452		for i in 0..program_data_section_headers.len() {
453			let sh_addr = program_data_section_headers[i].sh_addr;
454			let sh_name = program_data_section_headers[i].sh_name;
455			for j in 0..string_table_section_headers.len() {
456				let sh_offset = string_table_section_headers[j].sh_offset;
457				let sh_size = string_table_section_headers[j].sh_size;
458				let mut found = true;
459				for k in 0..tohost_values.len() as u64{
460					let addr = sh_offset + sh_name + k;
461					if addr >= sh_offset + sh_size || data[addr as usize] != tohost_values[k as usize] {
462						found = false;
463						break;
464					}
465				}
466				if found {
467					tohost_addr = sh_addr;
468				}
469			}
470			if tohost_addr != 0 {
471				break;
472			}
473		}
474
475		// Detected whether the elf file is riscv-tests.
476		// Setting up CPU and Memory depending on it.
477
478		self.cpu.update_xlen(match e_width {
479			32 => Xlen::Bit32,
480			64 => Xlen::Bit64,
481			_ => panic!("No happen")
482		});
483
484		if tohost_addr != 0 {
485			self.is_test = true;
486			self.tohost_addr = tohost_addr;
487			self.cpu.get_mut_mmu().init_memory(TEST_MEMORY_CAPACITY);
488		} else {
489			self.is_test = false;
490			self.tohost_addr = 0;
491			self.cpu.get_mut_mmu().init_memory(PROGRAM_MEMORY_CAPACITY);
492		}
493
494		for i in 0..program_data_section_headers.len() {
495			let sh_addr = program_data_section_headers[i].sh_addr;
496			let sh_offset = program_data_section_headers[i].sh_offset;
497			let sh_size = program_data_section_headers[i].sh_size;
498			if sh_addr >= 0x80000000 && sh_offset > 0 && sh_size > 0 {
499				for j in 0..sh_size as usize {
500					self.cpu.get_mut_mmu().store_raw(sh_addr + j as u64, data[sh_offset as usize + j]);
501				}
502			}
503		}
504
505		self.cpu.update_pc(e_entry);
506	}
507
508	/// Sets up filesystem. Use this method if program (e.g. Linux) uses
509	/// filesystem. This method is expected to be called up to only once.
510	///
511	/// # Arguments
512	/// * `content` File system content binary
513	pub fn setup_filesystem(&mut self, content: Vec<u8>) {
514		self.cpu.get_mut_mmu().init_disk(content);
515	}
516
517	/// Sets up device tree. The emulator has default device tree configuration.
518	/// If you want to override it, use this method. This method is expected to
519	/// to be called up to only once.
520	///
521	/// # Arguments
522	/// * `content` DTB content binary
523	pub fn setup_dtb(&mut self, content: Vec<u8>) {
524		self.cpu.get_mut_mmu().init_dtb(content);
525	}
526
527	/// Updates XLEN (the width of an integer register in bits) in CPU.
528	///
529	/// # Arguments
530	/// * `xlen`
531	pub fn update_xlen(&mut self, xlen: Xlen) {
532		self.cpu.update_xlen(xlen);
533	}
534
535	/// Returns mutable reference to `Terminal`.
536	pub fn get_mut_terminal(&mut self) -> &mut Box<dyn Terminal> {
537		self.cpu.get_mut_terminal()
538	}
539
540	/// Returns immutable reference to `Cpu`.
541	pub fn get_cpu(&self) -> &Cpu {
542		&self.cpu
543	}
544
545	/// Returns mutable reference to `Cpu`.
546	pub fn get_mut_cpu(&mut self) -> &mut Cpu {
547		&mut self.cpu
548	}
549}