├── .gitignore ├── .travis.rb ├── .travis.yml ├── LICENSE.txt ├── Makefile ├── README.md ├── linker.ld ├── loader.asm └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *.img 2 | *.swp 3 | *.bin 4 | *.o 5 | -------------------------------------------------------------------------------- /.travis.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # coding: BINARY 3 | require "socket" 4 | require "timeout" 5 | require "json" 6 | 7 | QEMU = ENV["QEMU"] || "qemu-system-i386" 8 | IMG = ENV["IMG"] || "floppy.img" 9 | 10 | File.delete("x.ppm") if File.exist?("x.ppm") 11 | 12 | Timeout.timeout(20) do 13 | server = TCPServer.new(4444) 14 | qemu = IO.popen([ 15 | QEMU, 16 | "-monitor", "tcp:127.0.0.1:4444", 17 | "-net", "none", 18 | "-nographic", 19 | "-fda", IMG, 20 | ], "r+") 21 | monitor = server.accept 22 | 23 | puts monitor.gets 24 | 25 | eip = nil 26 | loop do 27 | monitor.puts "print $eip" 28 | monitor.gets 29 | current_eip = monitor.gets 30 | puts "EIP is at #{current_eip}" 31 | if current_eip == eip 32 | puts "Detected halt, screenshotting." 33 | break 34 | else 35 | eip = current_eip 36 | sleep 0.1 37 | end 38 | end 39 | 40 | monitor.puts "screendump screen.ppm" 41 | monitor.gets 42 | 43 | monitor.puts "quit" 44 | monitor.gets 45 | 46 | Process.kill(:KILL, qemu.pid) 47 | end 48 | 49 | unless File.exist?("screen.ppm") 50 | abort "screen.ppm does not exist!" 51 | end 52 | 53 | if system "convert screen.ppm screen.png" 54 | begin 55 | Timeout.timeout(5) do 56 | response = `curl -X POST -F name=screen.png -F 'fileinput[0]=@screen.png' http://cubeupload.com/upload_json.php` 57 | filename = JSON.parse(response)["file_name"] 58 | $stderr.puts "Screenshot available at: http://i.cubeupload.com/#{filename}" 59 | end 60 | rescue Timeout::Error 61 | $stderr.puts "could not upload screenshot to cubeupload" 62 | end 63 | end 64 | 65 | magic, coords, channel_depth, data = File.read("screen.ppm").force_encoding("BINARY").split("\n", 4) 66 | 67 | unless magic == "P6" 68 | abort "screen.ppm is not a 24-bit binary portable pixmap" 69 | end 70 | 71 | unless coords == "720 400" 72 | abort "screen.ppm is not 720x400 (is: #{coords.inspect})" 73 | end 74 | 75 | unless channel_depth == "255" 76 | abort "channel depth is not 255" 77 | end 78 | 79 | data.bytes.each_slice(720 * 3).each_with_index do |row, y| 80 | row.each_slice(3).each_with_index do |pix, x| 81 | expected_colour = 82 | if (0..8).include?(x) && (62..63).include?(y) # cursor 83 | [0, 0, 0] 84 | else 85 | [0xff, 0x57, 0x57] 86 | end 87 | 88 | unless pix == expected_colour 89 | abort "pixel at (#{x}, #{y}) is not #%02x%02x%02x, is: #%02x%02x%02x" % (expected_colour + pix) 90 | end 91 | end 92 | end 93 | 94 | $stderr.puts "Tests passed." 95 | exit true 96 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - yes | sudo add-apt-repository ppa:hansjorg/rust 3 | - sudo apt-get update 4 | install: 5 | - sudo apt-get install rust-nightly nasm qemu imagemagick 6 | script: 7 | - sed -i 's/i386-elf-ld/ld/' Makefile 8 | - make 9 | - ruby .travis.rb 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Charlie Somerville 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LD=i386-elf-ld 2 | RUSTC=rustc 3 | NASM=nasm 4 | QEMU=qemu-system-i386 5 | 6 | all: floppy.img 7 | 8 | .SUFFIXES: .o .rs .asm 9 | 10 | .PHONY: clean run 11 | 12 | .rs.o: 13 | $(RUSTC) -O --target i386-intel-linux --crate-type lib -o $@ --emit obj $< 14 | 15 | .asm.o: 16 | $(NASM) -f elf32 -o $@ $< 17 | 18 | floppy.img: loader.bin main.bin 19 | dd if=/dev/zero of=$@ bs=512 count=2 &>/dev/null 20 | cat $^ | dd if=/dev/stdin of=$@ conv=notrunc &>/dev/null 21 | 22 | loader.bin: loader.asm 23 | $(NASM) -o $@ -f bin $< 24 | 25 | main.bin: linker.ld main.o 26 | $(LD) -m elf_i386 -o $@ -T $^ 27 | 28 | run: floppy.img 29 | $(QEMU) -fda $< 30 | 31 | clean: 32 | rm -f *.bin *.o *.img 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rustboot 2 | 3 | A tiny 32 bit kernel written in Rust. 4 | 5 | I was inspired to download Rust and try to do this after seeing [zero.rs](https://github.com/pcwalton/zero.rs) - a stub that lets Rust programs run almost freestanding. 6 | 7 | It paints the screen bright red and then hangs. That's it: 8 | 9 | ![](http://i.imgur.com/NWRehJJ.png) 10 | 11 | ## Interesting forks 12 | 13 | * [jvns/puddle](https://github.com/jvns/puddle) 14 | 15 | * [pczarn/rustboot](https://github.com/pczarn/rustboot) 16 | 17 | ## Setup 18 | 19 | You need a few things to run rustboot: 20 | 21 | 1. `qemu` 22 | 2. a cross-compiler for i386 23 | 3. `nasm` 24 | 4. Rust's `master` branch or 0.7 release. 25 | 26 | ### OSX 27 | 28 | To set things up on OSX, do this: 29 | 30 | Install `nasm` and `qemu` from homebrew: 31 | 32 | ```bash 33 | $ brew install nasm 34 | $ brew install qemu 35 | ``` 36 | 37 | Make sure the brew version of `nasm` is being used: 38 | 39 | ```bash 40 | $ nasm -v 41 | NASM version 2.11.02 compiled on Apr 14 2014 42 | ``` 43 | 44 | Install binutils from source. 45 | 46 | I personally keep things I manually compile limited to my home directory, so 47 | I use the `--prefix=/Users/steve` option. Put this wherever you want, of 48 | course. 49 | 50 | ```bash 51 | $ wget http://ftp.gnu.org/gnu/binutils/binutils-2.24.tar.gz 52 | $ tar xf binutils-2.24.tar.gz 53 | $ cd binutils-2.24 54 | $ ./configure --target=i386-elf --disable-werror --prefix=/Users/steve 55 | $ make && make install 56 | ``` 57 | 58 | To get edge Rust going, grab it from git: 59 | 60 | ```bash 61 | $ git clone https://github.com/mozilla/rust 62 | $ cd rust 63 | $ ./configure --prefix=/Users/steve 64 | $ make && make install 65 | ``` 66 | 67 | Same thing about the prefix applies. 68 | 69 | Then, just make sure that `~/bin` is in your `PATH`, if you're using a prefix. 70 | 71 | ## Running it 72 | 73 | To compile, simply 74 | 75 | ```bash 76 | $ make 77 | ``` 78 | 79 | To run, 80 | 81 | ```bash 82 | $ make run 83 | ``` 84 | -------------------------------------------------------------------------------- /linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(main) 2 | OUTPUT_FORMAT(binary) 3 | 4 | MEMORY { 5 | ram : org = 0x7e00, l = 12K 6 | } 7 | 8 | SECTIONS { 9 | . = 0x7e00; 10 | 11 | .text : { 12 | *(.text) 13 | } >ram 14 | 15 | /DISCARD/ : { 16 | *(.comment) 17 | *(.eh_frame) 18 | *(.rel.eh_frame) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /loader.asm: -------------------------------------------------------------------------------- 1 | use16 2 | 3 | org 0x7c00 4 | 5 | boot: 6 | ; initialize segment registers 7 | xor ax, ax 8 | mov ds, ax 9 | mov es, ax 10 | mov ss, ax 11 | ; initialize stack 12 | mov sp, 0x7bfe 13 | ; load rust code into 0x7e00 so we can jump to it later 14 | mov ah, 2 ; read 15 | mov al, 24 ; 24 sectors (12 KiB) 16 | mov ch, 0 ; cylinder & 0xff 17 | mov cl, 2 ; sector | ((cylinder >> 2) & 0xc0) 18 | mov dh, 0 ; head 19 | mov bx, 0x7e00 ; read buffer 20 | int 0x13 21 | jc error 22 | ; load protected mode GDT and a null IDT (we don't need interrupts) 23 | cli 24 | lgdt [gdtr] 25 | lidt [idtr] 26 | ; set protected mode bit of cr0 27 | mov eax, cr0 28 | or eax, 1 29 | mov cr0, eax 30 | ; far jump to load CS with 32 bit segment 31 | jmp 0x08:protected_mode 32 | 33 | error: 34 | mov si, .msg 35 | .loop: 36 | lodsb 37 | or al, al 38 | jz .done 39 | mov ah, 0x0e 40 | int 0x10 41 | jmp .loop 42 | .done: 43 | jmp $ 44 | .msg db "could not read disk", 0 45 | 46 | protected_mode: 47 | use32 48 | ; load all the other segments with 32 bit data segments 49 | mov eax, 0x10 50 | mov ds, eax 51 | mov es, eax 52 | mov fs, eax 53 | mov gs, eax 54 | mov ss, eax 55 | ; set up stack 56 | mov esp, 0x7bfc 57 | ; jump into rust 58 | call 0x7e00 59 | jmp $ 60 | 61 | gdtr: 62 | dw (gdt_end - gdt) + 1 ; size 63 | dd gdt ; offset 64 | 65 | idtr: 66 | dw 0 67 | dd 0 68 | 69 | gdt: 70 | ; null entry 71 | dq 0 72 | ; code entry 73 | dw 0xffff ; limit 0:15 74 | dw 0x0000 ; base 0:15 75 | db 0x00 ; base 16:23 76 | db 0b10011010 ; access byte - code 77 | db 0x4f ; flags/(limit 16:19). flag is set to 32 bit protected mode 78 | db 0x00 ; base 24:31 79 | ; data entry 80 | dw 0xffff ; limit 0:15 81 | dw 0x0000 ; base 0:15 82 | db 0x00 ; base 16:23 83 | db 0b10010010 ; access byte - data 84 | db 0x4f ; flags/(limit 16:19). flag is set to 32 bit protected mode 85 | db 0x00 ; base 24:31 86 | gdt_end: 87 | 88 | times 510-($-$$) db 0 89 | db 0x55 90 | db 0xaa 91 | -------------------------------------------------------------------------------- /main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![allow(ctypes)] 3 | 4 | enum Color { 5 | Black = 0, 6 | Blue = 1, 7 | Green = 2, 8 | Cyan = 3, 9 | Red = 4, 10 | Pink = 5, 11 | Brown = 6, 12 | LightGray = 7, 13 | DarkGray = 8, 14 | LightBlue = 9, 15 | LightGreen = 10, 16 | LightCyan = 11, 17 | LightRed = 12, 18 | LightPink = 13, 19 | Yellow = 14, 20 | White = 15, 21 | } 22 | 23 | enum Option { 24 | None, 25 | Some(T) 26 | } 27 | 28 | struct IntRange { 29 | cur: int, 30 | max: int 31 | } 32 | 33 | impl IntRange { 34 | fn next(&mut self) -> Option { 35 | if self.cur < self.max { 36 | self.cur += 1; 37 | Some(self.cur - 1) 38 | } else { 39 | None 40 | } 41 | } 42 | } 43 | 44 | fn range(lo: int, hi: int) -> IntRange { 45 | IntRange { cur: lo, max: hi } 46 | } 47 | 48 | fn clear_screen(background: Color) { 49 | for i in range(0, 80 * 25) { 50 | unsafe { 51 | *((0xb8000 + i * 2) as *mut u16) = (background as u16) << 12; 52 | } 53 | } 54 | } 55 | 56 | #[no_mangle] 57 | #[no_split_stack] 58 | pub fn main() { 59 | clear_screen(LightRed); 60 | } 61 | --------------------------------------------------------------------------------