I wrote really simple a C kernel and a bootloader. I'm loading the kernel in the QEMU like this:
qemu-system-i386 kernel.image
When I load the kernel and switch from real mode to protected mode (that step success completed) and main method start execute other method like this
void foo(int a, int b, int c, int d, int e) {return;}. But it execution leads to crash QEMU. However if I rewrite foo method for void foo(int a, int b, int c, int d) {return;} - everything will be ok. Here is the error information:
(qemu) qemu: fatal: Trying to execute code outside RAM or ROM at 0xf4000010
EAX=ffffffff EBX=00001000 ECX=00000000 EDX=00000000
ESI=00000000 EDI=00000000 EBP=ae00008f ESP=00008fcd
EIP=f4000010 EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 00007c75 00000017
IDT= 00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000000 CCD=00008fc5 CCO=INCL
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
My bootloader.S
KERNEL_OFFSET equ 0x1000
[org 0x7c00]
[bits 16]
mov bp, 0x8000
mov sp, bp
mov bx, bootstring
call print
mov [BOOT_DRIVE], dl
mov dh, 3h
mov bx, KERNEL_OFFSET
call load_kernel
call enable_A20
call init_protected
loop:
jmp loop
bootstring: db "Loading...", 0
BOOT_DRIVE: db 0
print:
mov ah, 0xE
mov al, [bx]
cmp al, 0
jz print_done
int 0x10
inc bx
jmp print
print_done:
ret
load_kernel:
push dx
mov ah, 0x02
mov al, dh
mov ch, 0
mov cl, 2
mov dh, 0
int 0x13
jc load_error
pop dx
cmp al, dh
jne load_error
ret
load_error:
mov bx, load_err_msg
call print
jmp $
enable_A20:
cli
call a20wait
mov al, 0xAD
out 0x64, al
call a20wait
mov al, 0xD0
out 0x64, al
call a20wait2
in al, 0x60
push eax
call a20wait
mov al, 0xD1
out 0x64, al
call a20wait
pop eax
or al, 2
out 0x60, al
call a20wait
mov al, 0xAE
out 0x64, al
call a20wait
sti
ret
a20wait:
in al, 0x64
test al, 2
jnz a20wait
ret
a20wait2:
in al, 0x64
test al, 1
jz a20wait2
ret
load_err_msg: db "Cannot load from disk", 0
gdt:
gdt_null:
dd 0
dd 0
gdt_cs:
dw 0xffff
dw 0
db 0
db 10011010b
db 11001111b
db 0
gdt_ds:
dw 0xffff
dw 0
db 0
db 10010010b
db 11001111b
db 0
gdt_end:
gdt_desc:
dw gdt_end - gdt - 1
dd gdt
CODE_SEG equ gdt_cs - gdt
DATA_SEG equ gdt_ds - gdt
[bits 16]
init_protected:
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ax, 0x9000
mov ss, ax
mov sp, 0xFFFF
lgdt [gdt_desc]
mov eax, cr0
or eax, 0x1
mov cr0, eax
jmp CODE_SEG:start_protected
[bits 32]
start_protected:
mov ax, DATA_SEG
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, 0x9000
call KERNEL_OFFSET
times 510 -( $ - $$ ) db 0
dw 0xAA55
Where I'm wrong?
The symptoms suggest that your stack memory is not set up correctly -- it looks like things go wrong when there are enough arguments to the function that some of them have to go on the stack rather than all being in registers. The message from QEMU is telling you that your guest tried to execute from a physical address with no RAM in it. You should be able to debug what exactly has gone wrong using the debug logging options (-d in_asm,exec,cpu,int -D qemu.log) to see what the CPU actually did to get to a point where it tried to execute from an invalid address.
Note that although the QEMU error message looks a bit like "QEMU crashed" it always indicates a guest OS bug.
Related
I've looked more into creating my own bootloader, rather than using grub. I soon came up with this: It takes care of switching to 32bit pm, it loads my kernel from the disk & it jumps to it to execute it.
I'm catting my kernel & my bootloader like this: cat boot.bin kernel > img.bin
I'm assembling my bootloader like this: nasm -f bin boot.s -o boot.bin
i686-elf-ld -o kernel -Ttext=0x1000 kernel_entry.bin kernel.bin --oformat binary
I'm compiling my kernel like this: i686-elf-gcc *.o -Ttext=0x1000 -o kernel.bin -ffreestanding -O2 -nostdlib -lgcc
(*.o are all compiled C files which I compile like this: i686-elf-gcc -c file.c -o file.o -std=gnu99 -ffreestanding -O2 -Wall -Wextra
[org 0x7c00]
[bits 16]
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00
jmp 0:skip ; far jump
skip:
; load kernel
mov bx, 0x1000
mov dh, 17 ; reading 20 sectors should be enough ._.
mov dl, [BOOT_DRIVE]
call dsk_load
call load_kernel
dsk_load:
mov [SECTORS], dh
mov ch, 0x00 ; C = 0
mov dh, 0x00 ; H = 0
mov cl, 0x02 ; S = 2
next_group:
mov di, 5 ; retry 5 times
again:
mov ah, 0x02
mov al, [SECTORS]
int 0x13
jc maybe_retry
sub [SECTORS], al ; set remaining sectors
jz done
mov cl, 0x01 ; read sector 1
xor dh, 1 ; next head
jnz next_group
inc ch ; next cylinder
jmp next_group
maybe_retry:
mov ah, 0x00 ; reset drive
int 0x13
dec di
jnz again
jmp dsk_err ; we've tried too many times, give up
dsk_err:
mov bx, BOOTLOADER_SIG
call print
mov bx, DISK_READ_FAIL
call print
jmp $
done:
ret
; print string
print:
; print loop
print_loop:
mov ah, 0x0e
mov al, [bx] ; load current character
cmp al, 0
je print_return ; return when finished
int 0x10 ; print character
inc bx ; next character
jmp print_loop
print_return:
ret
load_kernel:
; If all that went well, we can switch to protected mode
cli
lgdt [gdt_descriptor]
mov eax, cr0
or eax, 0x1
mov cr0 , eax
jmp CODE_SEG:init_32_pm ; make a far jump
[bits 32]
init_32_pm:
set_up_stack:
mov esp, stack_end
mov ax, DATA_SEG
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
jmp 0x1000 ; jump to kernel_entry.s
; our beloved gdt
gdt_start:
gdt_null: ; null descriptor
dd 0x0
dd 0x0
gdt_code: ; code segment descriptor
dw 0xffff ; limit (bits 0-15)
dw 0x0 ; base (bits 0-15)
db 0x0 ; base (bits 16-23)
db 10011010b ; 1st flags, type flags
db 11001111b ; 2nd flags, Limit (bits 16-19)
db 0x0 ; base (bits 24 - 31)
gdt_data: ; data segment descriptor
dw 0xffff ; limit (bits 0-15)
dw 0x0 ; base (bits 0-15)
db 0x0 ; base (bits 16 -23)
db 10010010b ; 1st flags, type flags
db 11001111b ; 2nd flags, Limit (bits 16-19)
db 0x0 ; base (bits 24 - 31)
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; gdt size
dd gdt_start ; gdt start address
; some handy constants
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
BOOT_DRIVE db 0
SECTORS db 0
BOOTLOADER_SIG db "------ bootloader ------", 0x0d, 0xa, 0
DISK_READ_FAIL db "An error occurred while loading the kernel! Please restart your computer.", 0x0d, 0xa, 0
times 510-($-$$) db 0
dw 0xaa55
section .bss
stack_begin:
resb 4096 ; 4kib stack
stack_end:
; 9 sectors
The code that resides over at 0x1000 is this:
; kernel_entry.s
[bits 32]
[extern kmain]
call kmain
jmp $
times 510-($-$$) db 0
dw 0xaa55
; 1 sectors
My bootloader doesn't crash but it does not load my kernel, which should print some things to the screen.
This is the kmain function:
void kmain(void)
{
/* Initialize terminal */
tty_init();
tty_puts("Hello kernel!", VGA_COLOR_LIGHT_CYAN);
}
Assume the tty functions are working, since they were doing just fine when testing with grub instead of my own bootloader. Does anyone know what's going on? (Testing in bochs shows no errors)
I've looked more into creating my own bootloader, rather than using grub. I soon came up with this:
It takes care of switching to 32bit pm, it loads my kernel from the disk & it jumps to it to execute it.
I'm catting my kernel & my bootloader like this: cat boot.bin kernel.bin > img.bin
I'm assembling my bootloader like this: nasm -f bin kernel/arch/$ARCH_TARGET/boot/boot.s -o boot.bin
I'm compiling my kernel like this: i686-elf-gcc *.o -Ttext=0x0 -o kernel.bin -ffreestanding -O2 -nostdlib -lgcc
(*.o are all compiled C files which I compile like this: i686-elf-gcc -c file.c -o file.o -std=gnu99 -ffreestanding -O2 -Wall -Wextra
This is my bootloader code:
[org 0x7c00]
[bits 16]
xor ax, ax
mov ds, ax
mov es, ax
; load kernel
mov bx, 0x1000
mov dh, 10 ; reading 15 sectors should be enough ._.
mov dl, [BOOT_DRIVE]
call dsk_load
call load_kernel
dsk_load:
mov [SECTORS], dh
mov ch, 0x00 ; C = 0
mov dh, 0x00 ; H = 0
mov cl, 0x02 ; S = 2
next_group:
mov di, 5 ; retry 5 times
again:
mov ah, 0x02
mov al, [SECTORS]
int 0x13
jc maybe_retry
sub [SECTORS], al ; set remaining sectors
jz done
mov cl, 0x01 ; read sector 1
xor dh, 1 ; next head
jnz next_group
inc ch ; next cylinder
jmp next_group
maybe_retry:
mov ah, 0x00 ; reset drive
int 0x13
dec di
jnz again
jmp dsk_err ; we've tried too many times, give up
dsk_err:
mov bx, BOOTLOADER_SIG
call print
mov bx, DISK_READ_FAIL
call print
jmp $
done:
ret
; print string
print:
; print loop
print_loop:
mov ah, 0x0e
mov al, [bx] ; load current character
cmp al, 0
je print_return ; return when finished
int 0x10 ; print character
inc bx ; next character
jmp print_loop
print_return:
ret
load_kernel:
; If all that went well, we can switch to protected mode
cli
lgdt [gdt_descriptor]
mov eax, cr0
or eax, 0x1
mov cr0 , eax
jmp CODE_SEG:init_32_pm ; make a far jump
[bits 32]
init_32_pm:
mov ax, DATA_SEG
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
jmp 0x1000 ; call kernel
jmp $
; our beloved gdt
gdt_start:
gdt_null: ; null descriptor
dd 0x0
dd 0x0
gdt_code: ; code segment descriptor
dw 0xffff ; limit (bits 0-15)
dw 0x0 ; base (bits 0-15)
db 0x0 ; base (bits 16 -23)
db 10011010b ; 1st flags, type flags
db 11001111b ; 2nd flags, Limit (bits 16-19)
db 0x0 ; base (bits 24 - 31)
gdt_data: ; data segment descriptor
dw 0xffff ; limit (bits 0-15)
dw 0x0 ; base (bits 0-15)
db 0x0 ; base (bits 16 -23)
db 10010010b ; 1st flags, type flags
db 11001111b ; 2nd flags, Limit (bits 16-19)
db 0x0 ; base (bits 24 - 31)
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; gdt size
dd gdt_start ; gdt start address
; some handy constants
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
BOOT_DRIVE db 0
SECTORS db 0
BOOTLOADER_SIG db "------ NubelaOS bootloader ------", 0x0d, 0xa, 0
DISK_READ_FAIL db "An error occurred while loading the kernel! Please restart your computer.", 0x0d, 0xa, 0
times 510-($-$$) db 0
dw 0xaa55
Booting it into Qemu makes it go in a "boot loop", by rebooting constantly
I am trying to write a simple OS just for fun and somewhat practice. I've worked in the real mode before but I decided to move on and try playing with protected mode to have an opportunity to use C rather than plain assembly. I have copied bootloader code that seemed to work and it seems to me that basically all it does is just goes into long mode and of course starts the kernel. So it works fine until you declare a variable in the code, because if you do, QEMU will output what I believe is a chunk of memory translated to ASCII.
So the bootloader code itself:
[org 0x7c00]
KERNEL_ADDRESS equ 0x100000
cli
lgdt [gdt_descriptor]
;Switch to PM
mov eax, cr0
or eax, 0x1
mov cr0, eax
jmp 0x8:init_pm
[bits 32]
init_pm :
mov ax, 0x10
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
call build_page_tables
;Enable PAE
mov eax, cr4
or eax, 1 << 5
mov cr4, eax
;# Optional : Enable global-page mechanism by setting CR0.PGE bit to 1
mov eax, cr4
or eax, 1 << 7
mov cr4, eax
;Load CR3 with PML4 base address
;NB: in some examples online, the address is not offseted as it seems to
;be in the proc datasheet (if you were wondering about this strange thing).
mov eax, 0x1000
mov cr3, eax
;Set LME bit in EFER register (address 0xC0000080)
mov ecx, 0xC0000080 ;operand of 'rdmsr' and 'wrmsr'
rdmsr ;read before pr ne pas écraser le contenu
or eax, 1 << 8 ;eax : operand de wrmsr
wrmsr
;Enable paging by setting CR0.PG bit to 1
mov eax, cr0
or eax, (1 << 31)
mov cr0, eax
;Load 64-bit GDT
lgdt [gdt64_descriptor]
;Jump to code segment in 64-bit GDT
jmp 0x8:init_lm
[bits 64]
init_lm:
mov ax, 0x10
mov fs, ax ;other segments are ignored
mov gs, ax
mov rbp, 0x90000 ;set up stack
mov rsp, rbp
;Load kernel from disk
xor ebx, ebx ;upper 2 bytes above bh in ebx is for cylinder = 0x0
mov bl, 0x2 ;read from 2nd sectors
mov bh, 0x0 ;head
mov ch, 2 ;read 2 sectors
mov rdi, KERNEL_ADDRESS
call ata_chs_read
jmp KERNEL_ADDRESS
jmp $
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[bits 16]
;; http://wiki.osdev.org/ATA_in_x86_RealMode_%28BIOS%29
;load_loader:
;;!! il faut rester sur le meme segment, ie <0x10000 (=2**16)
;mov bx, LOADER_OFFSET
;mov dh, 1 ;load 1 sector (max allowed by BIOS is 128)
;mov dl, 0x80 ;drive number
;mov ah, 0x02 ;read function
;mov al, dh
;mov ch, 0x00 ;cylinder
;mov dh, 0x00 ;head
;; !! Sector is 1-based, and not 0-based
;mov cl, 0x02 ;1st sector to read
;int 0x13
;ret
[bits 32]
build_page_tables:
;PML4 starts at 0x1000
;il faut laisser la place pour tte la page PML4/PDP/PD ie. 0x1000
;PML4 # 0x1000
mov eax, 0x2000 ;PDP base address
or eax, 0b11 ;P and R/W bits
mov ebx, 0x1000 ;MPL4 base address
mov [ebx], eax
;PDP # 0x2000; maps 64Go
mov eax, 0x3000 ;PD base address
mov ebx, 0x2000 ;PDP physical address
mov ecx, 64 ;64 PDP
build_PDP:
or eax, 0b11
mov [ebx], eax
add ebx, 0x8
add eax, 0x1000 ;next PD page base address
loop build_PDP
;PD # 0x3000 (ends at 0x4000, fits below 0x7c00)
; 1 entry maps a 2MB page, the 1st starts at 0x0
mov eax, 0x0 ;1st page physical base address
mov ebx, 0x3000 ;PD physical base address
mov ecx, 512
build_PD:
or eax, 0b10000011 ;P + R/W + PS (bit for 2MB page)
mov [ebx], eax
add ebx, 0x8
add eax, 0x200000 ;next 2MB physical page
loop build_PD
;(tables end at 0x4000 => fits before Bios boot sector at 0x7c00)
ret
;=============================================================================
; ATA read sectors (CHS mode)
; Max head index is 15, giving 16 possible heads
; Max cylinder index can be a very large number (up to 65535)
; Sector is usually always 1-63, sector 0 reserved, max 255 sectors/track
; If using 63 sectors/track, max disk size = 31.5GB
; If using 255 sectors/track, max disk size = 127.5GB
; See OSDev forum links in bottom of [http://wiki.osdev.org/ATA]
;
; #param EBX The CHS values; 2 bytes, 1 byte (BH), 1 byte (BL) accordingly
; #param CH The number of sectors to read
; #param RDI The address of buffer to put data obtained from disk
;
; #return None
;=============================================================================
[bits 64]
ata_chs_read: pushfq
push rax
push rbx
push rcx
push rdx
push rdi
mov rdx,1f6h ;port to send drive & head numbers
mov al,bh ;head index in BH
and al,00001111b ;head is only 4 bits long
or al,10100000b ;default 1010b in high nibble
out dx,al
mov rdx,1f2h ;Sector count port
mov al,ch ;Read CH sectors
out dx,al
mov rdx,1f3h ;Sector number port
mov al,bl ;BL is sector index
out dx,al
mov rdx,1f4h ;Cylinder low port
mov eax,ebx ;byte 2 in ebx, just above BH
mov cl,16
shr eax,cl ;shift down to AL
out dx,al
mov rdx,1f5h ;Cylinder high port
mov eax,ebx ;byte 3 in ebx, just above byte 2
mov cl,24
shr eax,cl ;shift down to AL
out dx,al
mov rdx,1f7h ;Command port
mov al,20h ;Read with retry.
out dx,al
.still_going: in al,dx
test al,8 ;the sector buffer requires servicing.
jz .still_going ;until the sector buffer is ready.
mov rax,512/2 ;to read 256 words = 1 sector
xor bx,bx
mov bl,ch ;read CH sectors
mul bx
mov rcx,rax ;RCX is counter for INSW
mov rdx,1f0h ;Data port, in and out
rep insw ;in to [RDI]
pop rdi
pop rdx
pop rcx
pop rbx
pop rax
popfq
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[bits 16]
GDT:
;null :
dd 0x0
dd 0x0
;code :
dw 0xffff ;Limit
dw 0x0 ;Base
db 0x0 ;Base
db 10011010b ;1st flag, Type flag
db 11001111b ;2nd flag, Limit
db 0x0 ;Base
;data :
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_descriptor :
dw $ - GDT - 1 ;16-bit size
dd GDT ;32-bit start address
[bits 32]
;see manual 2, §4.8: most fields are ignored in long mode
GDT64:
;null;
dq 0x0
;code
dd 0x0
db 0x0
db 0b10011000
db 0b00100000
db 0x0
;data
dd 0x0
db 0x0
db 0b10010000
db 0b00000000
db 0x0
gdt64_descriptor :
dw $ - GDT64 - 1 ;16-bit size
dd GDT64 ;32-bit start address
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[bits 16]
times 510 -($-$$) db 0
dw 0xaa55
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I've played around with the code a little bit and simplified it to this state:
void putc(uchar_t __char, uint8_t pos) {
uint16_t* vidmemory = (uint16_t*) 0xB8000;
vidmemory[pos] = (uint16_t)__char | (uint16_t)0x0F << 8;
}
const char* hw = "Hello, World!";
void kmain() {
for (uint8_t i = 0; i < 14; ++i) {
putc(hw[i], i);
}
for (;;);
return;
}
I had the same problem with the real mode after loading up the kernel, because I forgot to set the Data Segment, but as far as I am concerned segments are almost obsolete in the long mode.
Update
After debugging I, as was expected, found out that values of hw were some random bytes from memory. It looks like this:
(gdb) x/14bc hw
0x0 <putc>: 83 'S' -1 '\377' 0 '\000' -16 '\360' 83 'S' -1 '\377' 0 '\000' -16 '\360'
0x8 <putc+8>: -61 '\303' -30 '\342' 0 '\000' -16 '\360' 83 'S' -1 '\377'
I build my image with this script, which also uses "loader.s", but essentially all it does is just calls the kernel itself:
loader.s:
[bits 64]
extern kmain
global _start
_start:
call kmain
jmp $
And the build.sh:
#!/bin/bash
nasm -f bin bootload.s -o boot.bin
nasm -f elf64 loader.s -o loader.o
cc -m64 -masm=intel -c kernel.c -ffreestanding -Wall -Wextra -g -O2
ld -Ttext 0x100000 -o kernel.elf loader.o kernel.o -e kmain
objcopy -R .note -R .comment -S -O binary kernel.elf kernel.bin
dd if=/dev/zero of=image.bin bs=512 count=2880
dd if=boot.bin of=image.bin conv=notrunc
dd if=kernel.bin of=image.bin conv=notrunc bs=512 seek=1
There are probably other problems with my code, but for now I believe it's the matter of the data segment, which I haven't set.
Screenshot of QEMU
I am writing a custom kernel, but I am getting an error when linking. I have taken the code and broken it down, but still no luck. Here's my code:
kernel.c:
#include <string.h>
// define our structure
typedef struct __attribute__ ((packed)) {
unsigned short di, si, bp, sp, bx, dx, cx, ax;
unsigned short gs, fs, es, ds, eflags;
} regs16_t;
// tell compiler our int32 function is external
extern void int32(unsigned char intnum, regs16_t *regs);
// int32 test
void int32_test()
{
int y;
regs16_t regs;
// switch to 320x200x256 graphics mode
regs.ax = 0x0013;
int32(0x10, ®s);
// full screen with blue color (1)
memset((char *)0xA0000, 1, (320*200));
// draw horizontal line from 100,80 to 100,240 in multiple colors
for(y = 0; y < 200; y++)
memset((char *)0xA0000 + (y*320+80), y, 160);
// wait for key
regs.ax = 0x0000;
int32(0x16, ®s);
// switch to 80x25x16 text mode
regs.ax = 0x0003;
int32(0x10, ®s);
}
kernel.asm:
[bits 32]
global int32, _int32
struc regs16_t
.di resw 1
.si resw 1
.bp resw 1
.sp resw 1
.bx resw 1
.dx resw 1
.cx resw 1
.ax resw 1
.gs resw 1
.fs resw 1
.es resw 1
.ds resw 1
.ef resw 1
endstruc
%define INT32_BASE 0x7C00
%define REBASE(x) (((x) - reloc) + INT32_BASE)
%define GDTENTRY(x) ((x) << 3)
%define CODE32 GDTENTRY(1)
%define DATA32 GDTENTRY(2)
%define CODE16 GDTENTRY(3)
%define DATA16 GDTENTRY(4)
%define STACK16 (INT32_BASE - regs16_t_size)
extern int32_test
global start
section .text
align 4
dd 0x1BADB002
dd 0x00
dd - (0x1BADB002 + 0x00)
int32: use32
_int32:
cli
pusha
mov esi, reloc
mov edi, INT32_BASE
mov ecx, (int32_end - reloc)
cld
rep movsb
jmp INT32_BASE
reloc: use32
mov [REBASE(stack32_ptr)], esp
sidt [REBASE(idt32_ptr)]
sgdt [REBASE(gdt32_ptr)]
lgdt [REBASE(gdt16_ptr)]
lea esi, [esp+0x24]
lodsd
mov [REBASE(ib)], al
mov esi, [esi]
mov edi, STACK16
mov ecx, regs16_t_size
mov esp, edi
rep movsb
jmp word CODE16:REBASE(p_mode16)
p_mode16: use16
mov ax, DATA16
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and al, ~0x01
mov cr0, eax
jmp word 0x0000:REBASE(r_mode16)
r_mode16: use16
xor ax, ax
mov ds, ax
mov ss, ax
lidt [REBASE(idt16_ptr)]
mov bx, 0x0870
call resetpic
popa
pop gs
pop fs
pop es
pop ds
sti
db 0xCD
ib: db 0x00
cli
xor sp, sp
mov ss, sp
mov sp, INT32_BASE
pushf
push ds
push es
push fs
push gs
pusha
mov bx, 0x2028
call resetpic
mov eax, cr0
inc eax
mov cr0, eax
jmp dword CODE32:REBASE(p_mode32)
p_mode32: use32
mov ax, DATA32
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
lgdt [REBASE(gdt32_ptr)]
lidt [REBASE(idt32_ptr)]
mov esp, [REBASE(stack32_ptr)]
mov esi, STACK16
lea edi, [esp+0x28]
mov edi, [edi]
mov ecx, regs16_t_size
cld
rep movsb
popa
sti
ret
resetpic:
push ax
mov al, 0x11
out 0x20, al
out 0xA0, al
mov al, bh
out 0x21, al
mov al, bl
out 0xA1, al
mov al, 0x04
out 0x21, al
shr al, 1
out 0xA1, al
shr al, 1
out 0x21, al
out 0xA1, al
pop ax
ret
stack32_ptr:
dd 0x00000000
idt32_ptr:
dw 0x0000
dd 0x00000000
gdt32_ptr:
dw 0x0000
dd 0x00000000
idt16_ptr:
dw 0x03FF
dd 0x00000000
gdt16_base:
.null:
dd 0x00000000
dd 0x00000000
.code32:
dw 0xFFFF
dw 0x0000
db 0x00
db 0x9A
db 0xCF
db 0x00
.data32:
dw 0xFFFF
dw 0x0000
db 0x00
db 0x92
db 0xCF
db 0x00
.code16:
dw 0xFFFF
dw 0x0000
db 0x00
db 0x9A
db 0x0F
db 0x00
.data16:
dw 0xFFFF
dw 0x0000
db 0x00
db 0x92
db 0x0F
db 0x00
gdt16_ptr:
dw gdt16_ptr - gdt16_base - 1
dd gdt16_base
int32_end:
start:
call int32_test
hlt
link.ld:
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{
. = 0x100000;
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
}
Command Line & Output:
# gcc -fno-stack-protector -m32 -c kernel.c -o kc.o
# nasm -f elf32 kernel.asm -o kasm.o
# ld -m elf_i386 -T link.ld -o kernel kasm.o kc.o
kc.o: In function `int32_test':
kernel.c:(.text+0x2d): undefined reference to `memset'
kernel.c:(.text+0x5c): undefined reference to `memset'
A little bit of background:
I am working on a custom OS for a custom project that I call AI Glasses. I am currently building a bootloader that will show a logo and some loading indicator. I am thinking that my indicator will be sort of like on Ubuntu. Anyways, I need to be able to go into a VGA mode and display stuff on the screen, while the rest of my (future) code can boot it up fully.
My question:
How do I import memset's lib into my code so that ld can fully link my code.
Most library functions, including memset, are not available in kernel modules.
You'll need to create your own implementation. It should be as simple as a single for loop. Since you're also writing some assembly, you may want to try writing it that way.
The issue here is that linker doesn't find memset function in the object files you provide to ld. Neither kasm.o nor kc.o seem to have this function. In fact this is part of standard C library. So, you need to link standard C library, libc.so or libc.a. If you don't want to use standard C library in your kernel, you will need to provide your own implementation of memset
According to this tutorial it is sufficient to create a simple operating system with switching to protected mode as simple as the following code without the need for other well known actions such as enabling A20...
Anyway, I am newbie to this domain, I wrote the following code as they mentioned exactly with the modification inspired from this SO.
Code Structure:
This simple operating system should load briefly as follows:
Load/read 15 sectors
Enable GDT
Switch to protected mode (And print "Successfully landed in 32-bit Protected Mode").
Load kernel and print "X"
However the emulator is still rebooting. Please find enclosed the entire code.
bootloader.asm
[bits 16]
[org 0x7C00]
KERNEL_OFFSET equ 0x1000
xor ax, ax
mov ds, ax
mov es, ax
mov [BOOT_DRIVE], dl
mov ax, 0x07E0 ; End of stack
cli
mov ss, ax
mov sp, 0x1200 ; Size of Stack. By this, we assume that stack starts at 9000h
; of size 1200h and ends at 7E00h to avoid being overwritten.
sti
call load_kernel
call switch_to_pm
jmp $
%include "src/functions/disk_load.asm"
load_kernel:
mov bx, KERNEL_OFFSET
mov dh, 15
mov dl, [BOOT_DRIVE]
call disk_load
ret
; Global variables
BOOT_DRIVE db 0
SECTORS db 0
MSG_PROT_MODE db "Successfully landed in 32-bit Protected Mode" , 0
%include "src/functions/gdt.asm"
%include "src/functions/switch_to_pm.asm"
[ bits 32]
; This is where we arrive after switching to and initialising protected mode.
BEGIN_PM:
mov ebx , MSG_PROT_MODE
call print_string_pm ; Use our 32 - bit print routine.
;call KERNEL_OFFSET ; Now jump to the address of our loaded
; kernel code , assume the brace position ,
; and cross your fingers. Here we go !
jmp $ ; Hang.
%include "src/functions/writing_video_mode.asm"
; Bootsector padding
times 510-($-$$) db 0
dw 0xAA55
; 15 sector padding
times 15*256 dw 0xDADA
disk_load.asm
disk_load:
mov [SECTORS], dh
mov ch, 0x00 ;C=0
mov dh, 0x00 ;H=0
mov cl, 0x02 ;S=2
next_group:
mov di, 5 ;Max 5 tries
again:
mov ah, 0x02 ;Read sectors
mov al, [SECTORS]
int 0x13
jc maybe_retry
sub [SECTORS], al ;Remaining sectors
jz ready
mov cl, 0x01 ;Always sector 1
xor dh, 1 ;Next head on diskette!
jnz next_group
inc ch ;Next cylinder
jmp next_group
maybe_retry:
mov ah, 0x00 ;Reset diskdrive
int 0x13
dec di
jnz again
jmp disk_error
ready:
ret
disk_error:
mov ah, 0x0e
mov al, 'Y'
int 0x10
jmp $
DISK_ERROR_MSG db "Disk read error!", 0
gdt.asm
gdt_start:
gdt_null:
dd 0x0 ; ’ dd ’ means define double word ( i.e. 4 bytes )
dd 0x0
gdt_code:
dw 0xffff
dw 0x0
db 0x0
db 10011010b ; 1 st flags , type flags
db 11001111b ; 2 nd flags , Limit ( bits 16 -19)
db 0x0
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b ; 1 st flags , type flags
db 11001111b ; 2 nd flags , Limit ( bits 16 -19)
db 0x0
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
switch_to_pm.asm
[ bits 16 ]
switch_to_pm:
cli
lgdt [ gdt_descriptor ]
mov eax , cr0
or eax , 0x1
mov cr0 , eax
jmp CODE_SEG:init_pm
[ bits 32 ]
init_pm:
mov ax, DATA_SEG
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp , 0x90000
mov esp , ebp
call BEGIN_PM
And in order to make sure that we landed in the protected mode:
writing_video_mode.asm
[ bits 32]
VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f
print_string_pm:
push eax
push ebx
push edx
mov edx , VIDEO_MEMORY ; Set edx to the start of vid mem.
print_string_pm_loop:
mov al, [ebx]
mov ah, WHITE_ON_BLACK
cmp al, 0
je print_string_pm_done
mov [edx], ax
add ebx, 1
add edx, 2
jmp print_string_pm_loop
print_string_pm_done:
pop edx
pop ebx
pop eax
ret
kernel.c
void main () {
char * video_memory = (char *) 0xb8000;
*video_memory = 'X';
}
By the way, I am using this Makefile:
all: bootloader.bin kernel.bin
bootloader.bin: src/bootloader.asm
nasm src/bootloader.asm -f bin -o output/bootloader.bin
kernel.o: src/kernel/kernel.c
gcc -ffreestanding -c src/kernel/kernel.c -o output/kernel.o -m32
kernel.bin: kernel.o
ld -o output/kernel.bin -Ttext 0x1000 --oformat binary output/kernel.o -melf_i386
clean:
rm -f output/*.* output/*
and in order to move it into the flash memory, I use these commands:
cat output/bootloader.bin output/kernel.bin > os-image
sudo dd if=os-image of=/dev/sdb bs=512 conv=notrunc && sync
In order to run it, I am using qemu with this command:
qemu-system-i386 -hda /dev/sdb
Noting that /dev/sdb is my flash memory drive.
Problem:
Indeed, the code is landing into the protected mode (I.e. Printing "Successfully landed in 32-bit Protected Mode") just when disabling/commenting the call KERNEL_OFFSET in the bootloader.asm. However when enabling this line it starts booting and rebooting.
I hope I have provided every needed information. It seems for me that the far jump should not be done this way. Any comment is appreciated.
Just remove
times 15*256 dw 0xDADA
(btw, why DADA?)
then compile your kernel, after that
cat output/bootloader.bin output/kernel.bin > os-image
and somehow make your os image 8192 byte long (16 sectors, bootloader + 15). I'm not Linux/Unix fan (even can't use them), but I think dd command (something like dd if=dev\zero of=temp_file count=(8192 - file actual size), and then cat os-image temp-file > os-image) should do the job. I'm also not sure is this compilation command correct (only not sure). I would remove "-melf_i386" from linker command, but idk, I have only used MinGW on Windows (it's only similar to GCC).
Sorry for my bad English, I hope I helped.