Make your own OS in 30 days – day15/16: Multitasking

First, changed from Q ver 0.9 to qemu 2.2.1 to resolve VESA issue before day15/16. I needed to make qemu foreground after running it.

default :
	#./ -L . -m 32 -localtime -std-vga -fda fdimage0.bin
	/usr/local/Cellar/qemu/2.2.1/bin/qemu-system-i386 -fda fdimage0.bin -L . -m 32 -localtime -vga std -smp cpus=2 &
	sleep 1
	osascript -e "tell application \"/usr/local/Cellar/qemu/2.2.1/bin/qemu-system-i386\" to activate"

Day15/16 is about multitasking. You can do far-jump to TSS; Task Status Segment, CPU doesn’t make a regular jump but change contexts. If you want to create and jump to a new task, the procedure is as follows.
1. Register TSS in GDT
2. Allocate a stack for the new task
3. Far jump to the TSS

#define TASK_GDT0▸▸ 3 // first task segment number
for (i = 0; i < MAX_TASKS; i++) {
	taskctl->tasks0[i].flags = 0;
	taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8;
	set_segmdesc(gdt + TASK_GDT0 + i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32);

task_a = task_init(memman);
fifo.task = task_a;
task_b = task_alloc();
task_b->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
task_b->tss.eip = (int) &task_b_main;
task_b-> = 1 * 8;
task_b->tss.cs = 2 * 8;
task_b-> = 1 * 8;
task_b->tss.ds = 1 * 8;
task_b->tss.fs = 1 * 8;
task_b-> = 1 * 8;
*((int *) (task_b->tss.esp + 4)) = (int) sht_back;


Then it switches tasks using yesterday’s timer interrupt handler every 20ms.
Task_a (main task) is basically sleeping (not getting CPU time at all) until interrupted to use more CPU time for task_b. Task_a should not be sleeping when it’s interrupted. The OS resolves it by awaken task_a when it gets interrupting in FIFO handler via key/mouse/other interrupts.

It only uses a single core through the book. I found how to enable other CPU cores and message each other at University of San Francisco’s class “Advanced Microcomputer Programming”-> lesson 5 -> pmhello.s via StackOverflow.


Make your own OS in 30 days – day14: High resolution and key input

Day14 calls VESA BIOS extension and switch to high resolution mode if VESA 2.0 or later is available.

;	0x100 :  640 x  400 x 8bit color
;	0x101 :  640 x  480 x 8bit color
;	0x103 :  800 x  600 x 8bit color
;	0x105 : 1024 x  768 x 8bit color
;	0x107 : 1280 x 1024 x 8bit color

VMODE	EQU		0x0ff2			; screen mode

; VBE existence check
		MOV		AX,0x9000
		MOV		DI,0
		MOV		AX,0x4f00
		INT		0x10
		CMP		AX,0x004f
		JNE		scrn320

; VBE version check
		MOV		AX,[ES:DI+4]
		CMP		AX,0x0200
		JB		scrn320			; if (AX < 0x0200) goto scrn320

; Get screen mode
		MOV		AX,0x4f01
		INT		0x10
		CMP		AX,0x004f
		JNE		scrn320

; Check screen mode info
		CMP		BYTE [ES:DI+0x19],8
		JNE		scrn320
		CMP		BYTE [ES:DI+0x1b],4
		JNE		scrn320
		MOV		AX,[ES:DI+0x00]
		AND		AX,0x0080
		JZ		scrn320			; switch to 320x240 mode if above fails

; Switch screen mode
		MOV		BX,VBEMODE+0x4000
		MOV		AX,0x4f02
		INT		0x10
		MOV		BYTE [VMODE],8		; Memo screen mode
		MOV		AX,[ES:DI+0x12]
		MOV		AX,[ES:DI+0x14]
		MOV		EAX,[ES:DI+0x28]
		JMP		keystatus

		MOV		AL,0x13			; VGA, 320x200x8bit color
		MOV		AH,0x00
		INT		0x10
		MOV		BYTE [VMODE],8		; Memo screen mode
		MOV		DWORD [VRAM],0x000a0000

2nd part converts key codes into characters.
I changed from Q to Parallels since VESA didn't work well with Q ver 0.9.0.


Make your own OS in 30 days – day10-13: Layers, Windows and Timer

I skipped day10: Layers and day11: Windows which are generic topics and not Hardware/OS specific. It resolved yesterday’s mouse cursor overlay issue.

First, it sets up an interrupt handler for 0x20 for Timer, then sets up PIT; Programmable Interval Timer to interrupt every 10ms (100Hz).

set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);
#define PIT_CTRL 0x0043
#define PIT_CNT0 0x0040

void init_pit(void) {
    io_out8(PIT_CTRL, 0x34);
    io_out8(PIT_CNT0, 0x9c);
    io_out8(PIT_CNT0, 0x2e);
    ... then init timers here

inthandler20 increments an integer counter and writes into FIFO buffer for Timer when it times out.

void inthandler20(int *esp)
	int i, j;
	io_out8(PIC0_OCW2, 0x60);
	if ( > timerctl.count) {
	for (i = 0; i < timerctl.using; i++) {
		if (timerctl.timers[i]->timeout > timerctl.count) {
		// timeout
		timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC;
		fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data);
	timerctl.using -= i;
	for (j = 0; j < timerctl.using; j++) {
		timerctl.timers[j] = timerctl.timers[i + j];
	if (timerctl.using > 0) { = timerctl.timers[0]->timeout;
	} else { = 0xffffffff;


Make your own OS in 30 days – day9: Memory management

Day9 manages memory by holding a list of free blocks.

#define MEMMAN_FREES		4090
#define MEMMAN_ADDR			0x003c0000

struct FREEINFO {
	unsigned int addr, size;

struct MEMMAN {
	int frees, maxfrees, lostsize, losts;

unsigned int memman_alloc(struct MEMMAN *man, unsigned int size);
int memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size);

// and use it like this
struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;

// get total memory
memtotal = memtest(0x00400000, 0xbfffffff);
// 0 init ... there is no free area at this time
// 'add' the following blocks into free list
memman_free(memman, 0x00400000, memtotal - 0x00400000); // 32MB-0x4000000 = 
// allocate it
char *hoge = (char *)memman_alloc(memman, 4 * 1024 * 1024);

After allocating 4MB on 32MB machine.

Make your own OS in 30 days – day7/8: Mouse interrupt handler

Day 7/8 implements FIFO ring buffer for Keyboard/Mouse handler to handle more than 1 byte code.
The main function wait for interrupt until it gets an interrupt by STI and HLT. When it’s interrupted, it disables interrupts by CLI, get key/mouse data by STI, draw it and wait for the next interrupt.

This version doesn’t have layers and mouse cursor overwrites background as below.

struct FIFO8 mousefifo;

struct FIFO8 {
	unsigned char *buf;
	int p, q, size, free, flags;

void inthandler21(int *esp)
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);	// IRQ-01 accepted -> PIC0
	data = io_in8(PORT_KEYDAT);
	fifo8_put(&keyfifo, data);

void inthandler2c(int *esp)
	unsigned char data;
	io_out8(PIC1_OCW2, 0x64);	// IRQ-12 accepted -> PIC1
	io_out8(PIC0_OCW2, 0x62);	// IRQ-02 accepted -> PIC0
	data = io_in8(PORT_KEYDAT);
	fifo8_put(&mousefifo, data);

int fifo8_put(struct FIFO8 *fifo, unsigned char data)
	if (fifo->free == 0) {
		fifo->flags |= FLAGS_OVERRUN;
		return -1;
	fifo->buf[fifo->p] = data;
	if (fifo->p == fifo->size) {
		fifo->p = 0;
	return 0;

// loop in the main function
	for (;;) {
		if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {
		} else {
			if (fifo8_status(&keyfifo) != 0) {
				i = fifo8_get(&keyfifo);
				// draw key data
			} else if (fifo8_status(&mousefifo) != 0) {
				i = fifo8_get(&mousefifo);
				// draw mouse data


Make your own OS in 30 days – day6: GDT/IDT and Keyboard/Mouse interrupts

Day6 describes the following 3 which are necessary to receive data from keyboard & mouse.
* GDT; Global Dispatch Table
* IDT; Interrupt Dispatch Table
* PIC; Programmable Interrupt Controller

Here is an int21 handler for keyboard interrupts. It has a similar mouse handler. It only shows the pre-defined message and halts after receiving the first key. It won’t show mouse data since it hasn’t reset interrupt handlers to receive more interrupts (mouse interrupt is not 1 byte).

Since interrupt handler should end with ‘IRETD’ instead of ‘RET’, it’s not fully written in C, but written in asm (_asm_inthandler21) which calls _inthandler21 written in C.

		EXTERN	_inthandler21, _inthandler27, _inthandler2c

		CALL	_inthandler21
void inthandler21(int *esp)
  struct BOOTINFO *binfo = (struct BOOTINFO *)ADR_BOOTINFO;
  boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15);
  putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF,
                "INT 21 (IRQ-1) : PS/2 keyboard");
  for (;;) {

Day 07 will get key codes and mouse data.

Keyboard Interrupt

MS-DOS COM on 16bit MS-DOS

I wrote and tested 16bit com on 32bit Win7 before if I remember correctly.
Since I couldn’t run 16bit linker on 64bit Win8 anymore, I installed MS-DOS 6.22 on Parallels. For some reason, EMM386 failed to test 4MB main memory which I think was sufficient. It passed when I assigned 64MB. Hm…
I couldn’t find exe2bin.exe and used exe2com; PDS version of exe2bin.


C:\PROJECTS\FIRST>ml /c first.asm
C:\PROJECTS\FIRST>link first.obj,first.exe,,,null.def
C:\PROJECTS\FIRST>exe2com first.exe


How to get .Net function parameters from crash dump

When debugging a crashed application from a crash dump, you often see that arguments to functions in the call stack are no data. Let’s say you want to get arguments for Hoge.Program.Test(Hoge.Foo, Hoge.Bar).
Since x64 calling convention use rcx/rdx/r8/r9 for the first 4 arguments, it’s not automatically pushed to the stack before the function call. However, sometimes the copy is stored in stack as below.


0:000> !pe
Exception object: 000000e1000068e0
Exception type:   System.Exception
Message:          dummy exception
StackTrace (generated):
    SP               IP               Function
    000000E17AADEF00 00007FFB516702BC Hoge!Hoge.Program.TestSub()+0x3c
    000000E17AADEF40 00007FFB51670243 Hoge!Hoge.Program.Test(Hoge.Foo, Hoge.Bar)+0x123
    000000E17AADEFB0 00007FFB516700FE Hoge!Hoge.Program.Main(System.String[])+0x6e

HResult: 80131500


0:000> !clrstack
OS Thread Id: 0x1bd4 (0)
        Child SP               IP Call Site
000000e17aadee18 00007ffbc0718b9c [HelperMethodFrame: 000000e17aadee18] 
000000e17aadef00 00007ffb516702bc Hoge.Program.TestSub() [c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 45]
000000e17aadef40 00007ffb51670243 Hoge.Program.Test(Hoge.Foo, Hoge.Bar) [c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 40]
000000e17aadefb0 00007ffb516700fe Hoge.Program.Main(System.String[]) [c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 29]
000000e17aadf2d0 00007ffbb0caa7f3 [GCFrame: 000000e17aadf2d0] 

0:000> !clrstack -a
OS Thread Id: 0x1bd4 (0)
        Child SP               IP Call Site
000000e17aadee18 00007ffbc0718b9c [HelperMethodFrame: 000000e17aadee18] 
000000e17aadef00 00007ffb516702bc Hoge.Program.TestSub() [c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 45]
        this = 

000000e17aadef40 00007ffb51670243 Hoge.Program.Test(Hoge.Foo, Hoge.Bar) [c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 40]
        this = 
        foo = 
        bar = 

000000e17aadefb0 00007ffb516700fe Hoge.Program.Main(System.String[]) [c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 29]
        args = 

If you see the JIT compiled code for Hoge.Program.Test(Hoge.Foo, Hoge.Bar), you can confirm that rcx is copied in rbi, and the rbi is pushed into the stack at 00007ffb`51670123.

0:000> !U 00007ffb51670243 
Normal JIT generated code
Hoge.Program.Test(Hoge.Foo, Hoge.Bar)
Begin 00007ffb51670120, size 131

c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 34:
00007ffb`51670120 53              push    rbx
00007ffb`51670121 55              push    rbp
00007ffb`51670122 56              push    rsi
00007ffb`51670123 57              push    rdi
00007ffb`51670124 4154            push    r12
00007ffb`51670126 4155            push    r13
00007ffb`51670128 4883ec38        sub     rsp,38h
00007ffb`5167012c 498be8          mov     rbp,r8
00007ffb`5167012f 488bfa          mov     rdi,rdx
00007ffb`51670132 488bf1          mov     rsi,rcx
00007ffb`51670135 33db            xor     ebx,ebx
00007ffb`51670137 bab0000000      mov     edx,0B0h
00007ffb`5167013c b901000000      mov     ecx,1

If you can get stack base address when Test(Hoge.Foo, Hoge.Bar) is called, you can get those copied registers which have the Hoge.Foo/Bar arguments.
Since child-sp when the Test function is called is 000000E17AADEFB0, 000000E17AADEFB0-0x8 is the ret address, and 000000E17AADEFB0-0x10 is the base address as below. Then you can get rcx (this), rdx (1st arg), r8 (2nd arg) stored in rsi(<-rcx), rdi(<-rdx), rbp(<-r8) respectively.

0:000> dq 000000e1`7aadef40 000000e1`7aadefff
[7aadef40 – 7aadef78] = 0x38 … local vars
000000e1`7aadef40  000000e1`00002d38 00000000`00000000
000000e1`7aadef50  000000e1`00005b0c 00000000`00000000
000000e1`7aadef60  000000e1`00000009 000000e1`00002d38
000000e1`7aadef70  000000e1`00002d68 000000e1`7aadf300 (r13)
000000e1`7aadef80  000000e1`7aadf348(r12) 000000e1`00002d50 (rdi)
000000e1`7aadef90  000000e1`00002d38(rsi) 000000e1`7aadf010 (rbp)
000000e1`7aadefa0  000000e1`00002d68(rbx) 00007ffb`516700fe (ret)
000000e1`7aadefb0  000000e1`00002d38(child-sp) 000000e1`00002d50
000000e1`7aadefc0  000000e1`00002d68 000000e1`00000001
000000e1`7aadefd0  000000e1`7aadf2d0 000000e1`7aadf128
000000e1`7aadefe0  000000e1`7aadf1a0 00007ffb`b0caa7f3
000000e1`7aadeff0  000000e1`00002cc8 000000e1`7aadf500

this = rcx)
0:000> !do 000000e1`00002d38
Name:        Hoge.Program
MethodTable: 00007ffb51554038
EEClass:     00007ffb516624a8
Size:        24(0x18) bytes
File:        C:\Users\sokoide\Projects\Spike\Hoge\bin\Release\Hoge.exe

1st arg = rdx)
0:000> !do 000000e1`00002d50
Name:        Hoge.Foo
MethodTable: 00007ffb51554120
EEClass:     00007ffb51662520
Size:        24(0x18) bytes
File:        C:\Users\sokoide\Projects\Spike\Hoge\bin\Release\Hoge.exe
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffbafad0e08  4000012        8        System.String  0 instance 000000e100002ce8 k__BackingField

2nd arg = r8)
0:000> !do 000000e1`7aadf010

Invalid object

… looks the pointer doesn’t have the class info. However, we know the type is Hoge.Bar!
0:000> !dumpheap -type Bar
         Address               MT     Size
000000e100002d68 00007ffb51554208       24     

              MT    Count    TotalSize Class Name
00007ffb51554208        1           24 Hoge.Bar
Total 1 objects

then we can get the object detail using !dumpvc and the method table and the object pointer as below.
0:000> !dumpvc 00007ffb51554208 000000e1`7aadf010
Name:        Hoge.Bar
MethodTable: 00007ffb51554208
EEClass:     00007ffb51662598
Size:        24(0x18) bytes
File:        C:\Users\sokoide\Projects\Spike\Hoge\bin\Release\Hoge.exe
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffbafad0e08  4000013        0        System.String  0 instance 000000e17aadf150 k__BackingField

Here is the target application which was used to demonstrate it.

class Foo
    public string Name { get; set; }

class Bar
    public string Name { get; set; }

class Program
    static void Main(string[] args)
        Program p = new Program();

        Foo foo = new Foo { Name = "hoge" };
        Bar bar = new Bar { Name = "page" };
        p.Test(foo, bar);

    private void Test(Foo foo, Bar bar)
        for (int i = 0; i < 10; i++)
            Console.WriteLine("Hello {0}", i);

    private void TestSub()
        throw new Exception("dummy exception");


I fixed find_goroutine(goid) using yesterday’s Allg class and finally I’m able to debug goroutines.Thank you Don!
I uploaded entire to my github repo at

(gdb) info goroutine
  1 waiting  fname=runtime.gopark faddr=0x13415 &g=0xc208000120 waitreason="sleep"
  2 waiting  fname=runtime.gopark faddr=0x13415 &g=0xc208000480 waitreason="force gc (idle)"
  3 waiting  fname=runtime.gopark faddr=0x13415 &g=0xc2080005a0 waitreason="GC sweep wait"
  4 waiting  fname=runtime.gopark faddr=0x13415 &g=0xc2080006c0 waitreason="finalizer wait"
  5 syscall  fname=runtime.switchtoM faddr=0x366c0 &g=0xc2080007e0

(gdb) goroutine 1 bt
#0  runtime.gopark (unlockf=0x2eba0 , lock=0x1576e0 , reason="sleep")
    at /Users/sokoide/repo/go/src/runtime/proc.go:131
#1  0x0000000000013488 in runtime.goparkunlock (lock=0x1576e0 , reason="sleep") at /Users/sokoide/repo/go/src/runtime/proc.go:136
#2  0x0000000000017de5 in runtime.timeSleep (ns=2000000000) at /Users/sokoide/repo/go/src/runtime/time.go:58
#3  0x00000000000020f8 in main.f2 (a=40, b=2, ~r2=833358237544) at /Users/sokoide/workspace/go/foo/foo.go:24
#4  0x0000000000002551 in main.main () at /Users/sokoide/workspace/go/foo/main.go:22

(gdb) goroutine 4 bt
#0  runtime.gopark (unlockf=0x2eba0 , lock=0x15e840 , reason="finalizer wait")
    at /Users/sokoide/repo/go/src/runtime/proc.go:131
#1  0x0000000000013488 in runtime.goparkunlock (lock=0x15e840 , reason="finalizer wait")
    at /Users/sokoide/repo/go/src/runtime/proc.go:136
#2  0x000000000000e66a in runtime.runfinq () at /Users/sokoide/repo/go/src/runtime/malloc.go:727
#3  0x00000000000388f1 in runtime.goexit () at /Users/sokoide/repo/go/src/runtime/asm_amd64.s:2232
#4  0x0000000000000000 in ?? ()

Here is the updated find_goroutine(good).

def find_goroutine(goid):
	find_goroutine attempts to find the goroutine identified by goid.
	It returns a touple of gdv.Value's representing the stack pointer
	and program counter pointer for the goroutine.

	@param int goid

	@return tuple (gdb.Value, gdb.Value)
	__allg = Allg()
	while True:
		ptr = __allg.fetch()
		if not ptr:
		if goid == __allg.Goid(ptr):
		    pc = __allg.Pc(ptr)
		    sp = __allg.Sp(ptr)
		    return pc, sp
	return None, None
1 2 3 4 5 8