Day 45: I now have Rust code for executing executables!
So I’m working on writing a kernel in Rust, and I wanted to be able to run executables because THAT WOULD BE COOL. Now I have the beginnings of this working!
I posted about my confusion about how to run programs a few days ago. Then Graydon Hoare sent me a super helpful email answering all my questions, and gave me an example minimal program to use (better than “Hello world”). The best. One of my favourite things about this blog is when delightful people comment or email and answer my questions
This is really useful because my statically compiled Hello World program takes up 800k, which is huge. The example program Graydon sent me is 897 bytes compiled, so about 1000 times smaller!
You can compile it like this:
$ cat >static.c
int _start() {
asm(
"mov $127,%ebx\n" /* exit code */
"mov $1,%eax\n" /* syscall #1, exit */
"int $0x80\n" /* invoke syscall */
);
}
^D
$ cc -m32 -static -nostdlib static.c
$ ./a.out
$ echo $?
127
This is super great, because it means that I can understand the whole
program and it doesn’t have a bunch of glibc/Linux stuff compiled into
it. The only OS-specific thing here is the int 80
interrupt, which
I’ll need to implement. I could also make up my own convention for
system calls, but that seems unnecessary.
So what I need to do is
- Implement the
exit()
system call - Read the ELF header
- Read the segment headers
- Find out
- what address the program needs to start at
- how many bytes the main section is
- The address of the program’s entry point
- Copy the segment marked LOAD into memory at the right address
- Jump to the start of the program!
This is what the code I have so far looks like. You can see that it’s mostly an ELF header definition, and then to read it I just cast the pointer to the array I’m trying to read.
This is typical of a lot of Rust code I’m writing – I need to write a
lot of unsafe
code.
pub fn read_header<'a>(file: &'a [u8]) -> &'a ELFHeader {
unsafe {
let x : *ELFHeader = to_ptr(file) as *ELFHeader;
return &*x;
}
}
#[packed]
struct ELFHeader {
e_ident: ELFIdent,
e_type: u16,
e_machine: u16,
e_version: u32,
e_entry: u32,
e_phoff: u32,
e_shoff: u32,
e_flags: u32,
e_ehsize: u16,
e_phentsize: u16,
e_phnum: u16,
e_shentsize: u16,
e_shnum: u16,
e_shstrndx: u16
}
and the final exec
function will look a bit like this:
unsafe fn jmp(addr: u32) {
asm!("jmp *($0)"
:
: "r" (addr));
}
// Executes a file starting at `addr`
pub unsafe fn exec(addr: uint) {
let bytes: &[u8] = transmute(Slice {data: (addr as *u8), len: 100});
let header = elf::read_header(bytes);
assert(header.e_ident.ei_mag.slice(1,4) == "ELF");
// Read the program header and load the program into memory at
// the right address
jmp(header.e_entry);
}
jmp
is a great example of an unsafe Rust function – what could be
more unsafe than jumping to a possibly arbitrary address in memory?