OSDI lab II
Objective
- Learn how to build a customized BIOS and show some message on system startup.
- Understand kernel booting flow and boot a ‘hello world’ program.
- Learn how to use BIOS interrupt call to do I/O tasks.
- Learn how to modify linux-0.11 bootsect.s and the build system to create a multiboot kernel image.
Lab2.1 - Build SEABIOS, add some message before system startup
git clone https://git.seabios.org/seabios.git seabios
cd seabios/
make menuconfig
make
Print message in enable_vga_console function in src/bootsplash.c.
void
enable_vga_console(void)
{
dprintf(1, "Turning on vga text mode console\n");
struct bregs br;
/* Enable VGA text mode */
memset(&br, 0, sizeof(br));
br.ax = 0x0003;
call16_int10(&br);
printf("This is OSDI lab2.\n");
// Write to screen.
printf("SeaBIOS (version %s)\n", VERSION);
display_uuid();
}
Use the qemu -bios option to load customized BIOS.
qemu-system-i386 -m 16M -boot a -fda Image -hda osdi.img -bios seabios/out/bios.bin
Because the customized BIOS is part of our lab, so I push it to new repo and add the submodule to osdi.
git submodule add bios-url bios-in-osdi-path
After that, I can update the customized BIOS even though we develop the BIOS and OSDI at different repo using the following command.
git submodule update bios-name
Lab2.2 - Add image before system startup
We want to load the image when BIOS is loading.
Firstly, we want to find which function can help us to do that.
There is a function called enable_bootsplash @ src/bootsplash.c.
void
enable_bootsplash(void)
{
if (!CONFIG_BOOTSPLASH)
return;
/* splash picture can be bmp or jpeg file */
dprintf(3, "Checking for bootsplash\n");
u8 type = 0; /* 0 means jpg, 1 means bmp, default is 0=jpg */
int filesize;
u8 *filedata = romfile_loadfile("bootsplash.jpg", &filesize);
if (!filedata) {
filedata = romfile_loadfile("bootsplash.bmp", &filesize);
if (!filedata)
return;
type = 1;
}
...
}
This function will check the CONFIG_BOOTSPLASH and bootsplash image.
Secondly, we find that the enable_bootsplash will be called by interactive_bootmenu @ src/boot.c.
// Show IPL option menu.
void
interactive_bootmenu(void)
{
// XXX - show available drives?
printf("CONFIG_BOOTMENU: %d\n", CONFIG_BOOTMENU);
printf("show-boot-menu: %d\n", romfile_loadint("etc/show-boot-menu", 1));
if (! CONFIG_BOOTMENU || !romfile_loadint("etc/show-boot-menu", 1))
return;
while (get_keystroke(0) >= 0)
;
char *bootmsg = romfile_loadfile("etc/boot-menu-message", NULL);
int menukey = romfile_loadint("etc/boot-menu-key", 1);
printf("%s", bootmsg ?: "\nPress ESC for boot menu.\n\n");
free(bootmsg);
u32 menutime = romfile_loadint("etc/boot-menu-wait", DEFAULT_BOOTMENU_WAIT);
printf("menutime: %d\n", menutime);
enable_bootsplash();
...
It seems that it is the function to provide the boot menu (i.e, booting from which boot device).
This function will also check the CONFIG_BOOTMENU and the file etc/show-boot-menu.
If we print the config before the first if condition, we will find that

Therefore, the CONFIG_BOOTMENU is 1 but etc/show-boot-menu is 0, so the interactive_bootmenu will return early without executing the rest of code including the enable_bootsplash method.
After did some researches, qemu provide an option -fw_cfg to provide some file as
-fw_cfg [name=]<item_name>,file=<path>. or-fw_cfg [name=]<item_name>,string=<string>
So I revise the qemu command to
qemu-system-i386 -m 16M -boot a -fda Image -hda ../osdi.img -bios seabios/out/bios.bin -fw_cfg name=etc/show-boot-menu,string=1
After that, the result became

It did enter the rest of code and ready to execute boot menu, and because the enable_bootsplash will check the image before load it, and we provide the bootsplash image as following.
qemu-system-i386 -m 16M -boot a -fda Image -hda ../osdi.img -bios seabios/out/bios.bin -fw_cfg name=etc/show-boot-menu,string=1 -fw_cfg name=bootsplash.jpg,file=bootsplash.jpg

It work!
My friends also found that another qemu option -boot provide the same functionality.
qemu-system-i386 -m 16M -boot order=a,menu=on,splash=bootsplash.jpg -fda Image -hda ../osdi.img -bios seabios/out/bios.bin
Which will be more clever and elegant!
Lab2.3 - Boot the hello world program
In this time, we will revise the kernel image structure and boot the given hello.
We will revise the image from
to

Makefile
Of course we will revise the Makefile and build.sh.
Firstly, put the hello.s to boot, and revise the boot/Makefile
...
all: bootsect hello setup
...
hello: hello.s
@$(AS) -o hello.o hello.s
@$(LD) $(LDFLAGS) -o hello hello.o
@objcopy -R .pdr -R .comment -R.note -S -O binary hello
...
clean:
@rm -f bootsect bootsect.o setup setup.o head.o hello hello.o
Just add new rule for hello and remember to clean its object file when executing make clean.
Secondly, revise the Makefile
...
Image: boot/bootsect boot/hello boot/setup tools/system
@cp -f tools/system system.tmp
@strip system.tmp
@objcopy -O binary -R .note -R .comment system.tmp tools/kernel
@chmod +x tools/build.sh
@tools/build.sh boot/bootsect boot/hello boot/setup tools/kernel Image $(ROOT_DEV)
@rm system.tmp
@rm tools/kernel -f
@sync
...
Update the Image prerequisites and add hello to the tools/build.sh parameter.
Thirdly, revise the tools/build.sh.
#!/bin/bash
# build.sh -- a shell version of build.c for the new bootsect.s & setup.s
# author: falcon <wuzhangjin@gmail.com>
# update: 2008-10-10
bootsect=$1
hello=$2
setup=$3
system=$4
IMAGE=$5
root_dev=$6
# Set the biggest sys_size
# Changes from 0x20000 to 0x30000 by tigercn to avoid oversized code.
SYS_SIZE=$((0x3000*16))
# set the default "device" file for root image file
if [ -z "$root_dev" ]; then
DEFAULT_MAJOR_ROOT=3
DEFAULT_MINOR_ROOT=1
else
DEFAULT_MAJOR_ROOT=${root_dev:0:2}
DEFAULT_MINOR_ROOT=${root_dev:2:3}
fi
# Write bootsect (512 bytes, one sector) to stdout
[ ! -f "$bootsect" ] && echo "there is no bootsect binary file there" && exit -1
dd if=$bootsect bs=512 count=1 of=$IMAGE 2>&1 >/dev/null
# Write hello(512bytes, one sector) to stdout
[ ! -f "$hello" ] && echo "there is no hello binary file there" && exit -1
dd if=$hello seek=1 bs=512 count=1 of=$IMAGE 2>&1 >/dev/null
# Write setup(4 * 512bytes, four sectors) to stdout
[ ! -f "$setup" ] && echo "there is no setup binary file there" && exit -1
dd if=$setup seek=2 bs=512 count=4 of=$IMAGE 2>&1 >/dev/null
# Write system(< SYS_SIZE) to stdout
[ ! -f "$system" ] && echo "there is no system binary file there" && exit -1
system_size=`wc -c $system |cut -d" " -f1`
[ $system_size -gt $SYS_SIZE ] && echo "the system binary is too big" && exit -1
dd if=$system seek=6 bs=512 count=$((2888-1-1-4)) of=$IMAGE 2>&1 >/dev/null
# Set "device" for the root image file
echo -ne "\x$DEFAULT_MINOR_ROOT\x$DEFAULT_MAJOR_ROOT" | dd ibs=1 obs=1 count=2 seek=508 of=$IMAGE conv=notrunc 2>&1 >/dev/null
Remember that at the Makefile, we put the hello as the second parameter, and we also need to update the parameter index of rest.
Link the hello to Image, order matter!
We want to put the bootsect, hello, setup and system, so we must follow this order.
The hello contain 512 bytes and occupied 1 sector, that is the bs and count option for.
So far, we insert the hello to new image, and we are ready to boot it.
How OS boot?
At boot/bootsect.s
ljmp $BOOTSEG, $_start
_start:
mov $BOOTSEG, %ax
mov %ax, %ds
mov $INITSEG, %ax
mov %ax, %es
mov $256, %cx
sub %si, %si
sub %di, %di
rep
movsw
ljmp $INITSEG, $go
-
Long jump to
$BOOTSEG:$_startwhen OS is loaded by BIOS. -
Put
$BOOTSEGto%ds,$INITSEGto%esand 256 to%cx. -
Zero the
%siand%di. -
Repeat
movswuntil%cxis zero (i.e. copy itself to from$BOOTSEGto$INITSEG, total size is 512 bytes).Because the counter of
repwill decrease by 1 after executed, so themovwwill execute 256 times!movwwill move one word (2 bytes in 16-bits CPU) from%ds:%sito%es:%di.First step:
%ds = $BOOTSEG %si = 0 %es = $INITSEG %di = 0 # Copy from $BOOTSEG:0 to $INITSEG:0 movwSecond step: Because the
%siand%diwill increase by 2 (bytes, one word in 16-bits CPU) after done it. So%ds = $BOOTSEG %si = 2 %es = $INITSEG %di = 2 # Copy from $BOOTSEG:2 to $INITSEG:2 movwAfter 256 times, the
$BOOTSEG:0-$BOOTSEG:256will copy to$INITSEG:0-$INITSEG:256. That'sbootsectitself! -
Long jump to
$INITSEG:$go.
go: mov %cs, %ax # after long jump, %cs become $INITSEG
mov %ax, %ds
mov %ax, %es
# put stack at 0x9ff00.
mov %ax, %ss
mov $0xFF00, %sp # arbitrary value >>512
- Move
$INITSEGto%ds,%esand%ss. After long jump, the%cswill store the last segment it jump, that is$INITSEG. - Put the top of stack
%spto a huge address.
# load the setup-sectors directly after the bootblock.
# Note that 'es' is already set up.
load_setup:
mov $0x0000, %dx # drive 0, head 0
mov $0x0002, %cx # sector 2, track 0
mov $0x0200, %bx # address = 512, in INITSEG
.equ AX, 0x0200+SETUPLEN
mov $AX, %ax # service 2, nr of sectors
int $0x13 # read it
jnc ok_load_setup # ok - continue
mov $0x0000, %dx
mov $0x0000, %ax # reset the diskette
int $0x13
jmp load_setup
- In this time, we want to read drive 0 (floppy), from 0 cylinder, 0 track, from
2'th sector and readSETUPLENsectors, 0 head to memory based on address$INITSEG:$0x0200. (i.e. load the setup in memory) - If successfully read it,
%cfwill not be set, so thatjnc ok_load_setupwill jump took_load_setup. - Otherwise, reset the disk and try again.
Reference:
Drive table:
| Register value | Meaning |
|---|---|
| DL = 00h | 1st floppy disk ( "drive A:" ) |
| DL = 01h | 2nd floppy disk ( "drive B:" ) |
| DL = 80h | 1st hard disk |
| DL = 81h | 2nd hard disk |
| DL = e0h | CD/DVD |
INT 13h, AH = 02h: Read Sectors From Drive
Parameter:
| Register | Meaning |
|---|---|
| AH | 02h |
| AL | Sectors To Read Count |
| CH | Cylinder |
| CL | Sector |
| DH | Head |
| DL | Drive |
| ES:BX | Buffer Address Pointer |
Results:
| Register | Meaning |
|---|---|
| CF | Set On Error, Clear If No Error |
| AH | Return Code |
| AL | Actual Sectors Read Count |
INT 13h, AH = 00h: Reset Disk System
Parameter:
| Register | Meaning |
|---|---|
| AH | 00h |
| DL | Drive (bit 7 set means reset both hard and floppy disks) |
Results:
| Register | Meaning |
|---|---|
| CF | Set on error |
| AH | Return Code |
We can imitate the procedure to...
.equ HELLOLEN, 1 # nr of hello-sectors
.equ HELLOSEG, 0x0100 # hello starts here
...
go: mov %cs, %ax # after long jump, %cs become $INITSEG
mov %ax, %ds
mov %ax, %es
# put stack at 0x9ff00.
mov %ax, %ss
mov $0xFF00, %sp # arbitrary value >>512
# Lab 2
load_hello:
mov $0x0000, %dx # drive 0, head 0
mov $0x0002, %cx # sector 2, track 0
mov $HELLOSEG, %ax # segment
mov %ax, %es
mov $0x0000, %bx # offset, address = 0x0100
.equ AX, 0x0200+HELLOLEN
mov $AX, %ax # service 2, nr of sectors
int $0x13 # read it
jnc ok_load_hello # ok - continue
mov $0x0000, %dx
mov $0x0000, %ax # reset the diskette
int $0x13
jmp load_hello
ok_load_hello:
ljmp $HELLOSEG, $0 # Jump to hello
...

It works!
Lab2.4 - Multiboot support
In this experiment, you need to implement a simple keyboard character reader that when user:
-
Press ‘1’, it boots the linux-0.11 Image(just like lab1).
-
Press ‘2’, it boots the hello world program.
go: mov %cs, %ax # after long jump, %cs become $INITSEG
mov %ax, %ds
mov %ax, %es
# put stack at 0x9ff00.
mov %ax, %ss
mov $0xFF00, %sp # arbitrary value >>512
boot_menu:
# Print some inane message
mov $0x03, %ah # read cursor pos
xor %bh, %bh # page number
int $0x10
mov $21, %cx # string length
mov $0x000D, %bx # page 0, attribute 7 (normal)
#lea option, %bp
mov $option, %bp # point to message
mov $0x1301, %ax # write string, move cursor
int $0x10
mov $0x00, %ah
int $0x16
cmp $49, %al # 1 ascii
je load_setup
cmp $50, %al # 2 ascii
je load_hello
jmp boot_menu
...
msg1:
.byte 13,10
.ascii "Loading system ..."
.byte 13,10,13,10
option:
.byte 13,10
.ascii "1) linux, 2) hello"
.byte 13,10,13,10
- We locate the cursor.
- Print the string to show the boot option.
- Read the keyboard
- Goto
load_setupif pressing 1, gotoload_helloif pressing 2. - Otherwise, go to
boot_menuagain.
Reference:
INT 10h, AH = 03h: Get cursor position and shape
Parameter:
| Register | Meaning |
|---|---|
| AH | 03h |
| BH | Page Number |
Results:
| Register | Meaning |
|---|---|
| AX = 0 | |
| CH | Start scan line |
| CL | End scan line |
| DH | Row |
| DL | Column |
INT 10h, AH = 13h: Write string
Parameter:
| Register | Meaning |
|---|---|
| AH | 13h |
| AL | Write mode |
| BH | Page Number |
| BL | Color |
| CX | String length |
| DH | Row |
| DL | Column |
| ES:EP | Offset of string |
INT 16h, AH = 00h: Read keystroke
Parameter:
| Register | Meaning |
|---|---|
| AH | 00h |
Results:
| Register | Meaning |
|---|---|
| AH | Scan code of the key pressed down |
| AL | ASCII character of the button pressed |
BIOS color:
| Hex | Color |
|---|---|
| 0 | Black |
| 1 | Blue |
| 2 | Green |
| 3 | Cyan |
| 4 | Red |
| 5 | Magenta |
| 6 | Brown |
| 7 | Light Gray |
| 8 | Dark Gray |
| 9 | Light Blue |
| A | Light Green |
| B | Light Cyan |
| C | Light Red |
| D | Light Magenta |
| E | Yellow |
| F | White |
ASCII:
| Dec | Meaning |
|---|---|
| 10 | Line feed (\n) |
| 49 | 1 |
| 50 | 2 |
load_setup:
mov $0x0000, %dx # drive 0, head 0
mov $0x0003, %cx # sector 3, track 0
mov $0x0200, %bx # address = 512, in INITSEG
.equ AX, 0x0200+SETUPLEN
mov $AX, %ax # service 2, nr of sectors
int $0x13 # read it
jnc ok_load_setup # ok - continue
mov $0x0000, %dx
mov $0x0000, %ax # reset the diskette
int $0x13
jmp load_setup
load_hello remain the same, but the setup is located at 3rd sector, so we revise the %cl to 0x03.
It seems that all work are done. However, after making and run it.

Hello runs pretty well.
After press 1, I want to load the setup, but the OS begin to reboot continuously.
It seems that we didn't run the ljmp $SETUPSEG, $0.
Thus, we must continue to trace the rest of code.
ok_load_setup:
# Get disk drive parameters, specifically nr of sectors/track
mov $0x00, %dl
mov $0x0800, %ax # AH=8 is get drive parameters
int $0x13
mov $0x00, %ch
#seg cs
mov %cx, %cs:sectors+0 # %cs means sectors is in %cs
...
- Get sectors of track.
- Set
%ch(%cx[15:8]) to0x00. - Because we boot from floppy (
%dl= 0), and its maximum sectors per track will not exceed 256 (18 sectors per track normally), so%cx[7:6]will be0. - Move
%cxtosectors.
Reference:
INT 13h, AH = 08h: Read Drive Parameters
Parameter:
| Register | Meaning |
|---|---|
| AH | 08h |
| DL | drive index |
| ES:DI | set to 0000h:0000h to work around some buggy BIOS |
Results:
| Register | Meaning |
|---|---|
| CF | Set On Error, Clear If No Error |
| AH | Return Code |
| DL | number of hard disk drives |
| DH | logical last index of heads = number_of - 1 (because index starts with 0) |
| CX | [7:6][15:8] logical last index of cylinders = number_of - 1 (because index starts with 0) [5:0] logical last index of sectors per track = number_of (because index starts with 1) |
| BL | drive type (only AT/PS2 floppies) |
| ES:DI | pointer to drive parameter table (only for floppies) |
ok_load_setup:
...
mov $SYSSEG, %ax
mov %ax, %es # segment of 0x010000
call read_it
call kill_motor
...
Put $SYSSEG in %es, and call read_it.
read_it:
mov %es, %ax
test $0x0fff, %ax
die: jne die # es must be at 64kB boundary
xor %bx, %bx # bx is starting address within segment
TEST0x0fffand0x1000.TESTis bitwiseANDand result ofTESTis0,ZFis set to1. refJNEwill jump whenZFis0, so it will continue executing the next instruction. ref- Zero
%bx
rp_read:
mov %es, %ax
cmp $ENDSEG, %ax # have we loaded all yet?
jb ok1_read
ret
- Put
%esto%ax($SYSSEG). - Compare
$ENGSEG(0x4000) and%ax(0x1000). - In AT&T syntax,
%axwill subtract$ENGSEG. Result is0xD000.SFandCFwill be set. ref - Because
jbwill jump whenCFis 1, so jump took1_read. ref - Otherwise, return to last callee.
sread: .word 1+ SETUPLEN # sectors read of current track
...
ok1_read:
#seg cs
mov %cs:sectors+0, %ax
sub sread, %ax
mov %ax, %cx
shl $9, %cx
add %bx, %cx
jnc ok2_read
je ok2_read
xor %ax, %ax
sub %bx, %ax
shr $9, %ax
- Move
sectorsto%axand subtractsread. That is un-read sectors. - Calculate the total bytes of un-read sectors (512 bytes per sector) to
%cx - If not greater or eqaul to $2^{16}$ (maximum register capacity), goto
ok2_read. - Otherwise, count the maximum sectors can read (
%ax).
ok2_read:
call read_track
...
read_track:
push %ax
push %bx
push %cx
push %dx
mov track, %dx
mov sread, %cx
inc %cx
mov %dl, %ch
mov head, %dx
mov %dl, %dh
mov $0, %dl
and $0x0100, %dx
mov $2, %ah
int $0x13
jc bad_rt
pop %dx
pop %cx
pop %bx
pop %ax
ret
- Store all register.
- In this time, we want to read drive 0 (floppy), from 0 cylinder, 0 track, from
1+SETUPLEN+1'th sector and read%alun-read sectors, 0 head to memory based on address$SYSSEG:0x0000. (i.e. load the system in memory) - If failed,
%cfwill be set, so thatjc bad_rtwill jump tobad_rt. - Load all registers.
At this very moment, we have already known the error may come from here. Because the system is located at 1 + 1 + 1 + SETUPLEN, so we must change sread to 1 + 1 + SETUPLEN.
Reference:
INT 13h, AH = 02h: Read Sectors From Drive
Parameter:
| Register | Meaning |
|---|---|
| AH | 02h |
| AL | Sectors To Read Count |
| CH | Cylinder |
| CL | Sector |
| DH | Head |
| DL | Drive |
| ES:BX | Buffer Address Pointer |
Results:
| Register | Meaning |
|---|---|
| CF | Set On Error, Clear If No Error |
| AH | Return Code |
| AL | Actual Sectors Read Count |
After change sread to 1 + 1 + SETUP, it works!

Questions
-
What’s the meaning of ljmp $BOOTSEG, $_start(boot/bootsect.s)? What is the memory address of the beginning of bootsect.s? What is the value of $_start? From above questions, could you please clearly explain how do you jump to the beginning of hello image?
objdump -D -mi386 -Maddr16,data16 boot/bootsect.o
-
What’s the purpose of es register when the cpu is performing int $0x13 with AH=0x2h?
- base segment of memory address that put read sectors
-
Please change the Hello program’s font color to another
- Change
%blregister.
- Change
-
If we would like to swap the position of hello and setup in the Image. Where do we need to modify in tools/build.sh and bootsect.s?
- The order of Image at
tools/build.sh - The starting sector want to read when perform int 13h with AH=02h.
- The order of Image at
-
Please trace the SeaBIOS code. What are the first and the last instruction of the SeaBIOS? Where are they?
- First instruction:
reset_vector@src/romlayout.S - Last instruction:
call_boot_entry@src/boot.cin C__farcall16@src/romlayout.Sin asm
- First instruction: