I'm trying to convert the barebone c kernel listed in osdev c kernel to assembly language using NASM. The c version work just fine but somehow I screw up in the asm.
The program first initialize the video buffer with bg and fg color, then attempts to write 2 lines of string into the video buffer starting at 0xB8000, and then come to halt.
I tried changing the backcolor to blue and forecolor to white it seems to be working, this shows that the call to _term_init actually works. But other than that, nothing seems to be working as I want it to be.
I'm really new in assembly language, hope there's hero(s) out there can save my days...
Thanks in advance.
The following are my 3 asm files, compile using:
nasm -felf32 [input.asm] -o [output.o]
and then link using
i686-elf-gcc -T linker.ld -o myos.bin -ffreestanding -O2 -nostdlib [all object files] -lgcc
kernel.asm
[bits 32]
global kernel_main:function (kernel_main.end - kernel_main)
extern _term_writestring
extern _term_init
SECTION .text align=16
kernel_main:; Function begin
push ebp
mov ebp, esp
call _term_init
push version
call _term_writestring
add esp, 4
push cpright
call _term_writestring
add esp, 4
leave
ret
.end:
; kernel_main End of function
SECTION .data align=1
version db "Simple OS Version 0.0.3\n", 0
cpright db "Sin Hing 2017 all rights reserved\n", 0
SECTION .bss align=1
SECTION .rodata align=4
welcome_string: ; byte
db 53H, 69H, 6DH, 70H, 6CH, 65H, 20H, 4FH ; 0000 _ Simple O
db 53H, 20H, 56H, 65H, 72H, 73H, 69H, 6FH ; 0008 _ S Versio
db 6EH, 20H, 30H, 2EH, 30H, 2EH, 33H, 0AH ; 0010 _ n 0.0.3.
db 53H, 69H, 6EH, 20H, 48H, 69H, 6EH, 67H ; 0018 _ Sin Hing
db 20H, 61H, 6CH, 6CH, 20H, 72H, 69H, 67H ; 0020 _ all rig
db 68H, 74H, 73H, 20H, 72H, 65H, 73H, 65H ; 0028 _ hts rese
db 72H, 76H, 65H, 64H, 2EH, 0AH, 00H ; 0030 _ rved...
term.asm
[bits 32]
VGA_WIDTH equ 80
VGA_HEIGHT equ 25
global term_buffer
global term_col
global term_row
global _term_init:function (_term_init.end - _term_init) ; void _term_init(void)
global _term_setcolor:function (_term_setcolor.end - _term_setcolor) ; void _term_setcolor(char)
global _term_putentryat:function (_term_putentryat.end - _term_putentryat) ; void _term_putentryat(char, char, dword, dword)
global _term_putchar:function (_term_putchar.end - _term_putchar) ; void _term_putchar(char)
global _term_write:function (_term_write.end - _term_write) ; void _term_write(char*, dword)
global _term_writestring:function (_term_writestring.end - _term_writestring) ; void _term_writestring(char*)
extern _strlen ; near
SECTION .text align=16
; Function _term_init: Initialize terminal
_term_init:; Function begin
mov dword [term_row], 0 ; term_row = 0
mov dword [term_col], 0 ; term_col = 0
mov byte [term_color], 7 ; bg - 0(black), fg - 7(grey)
mov dword [term_buffer], 753664 ; [term_buffer] becoming origin or video mem
movzx edx, byte [term_color] ; set edx := term_color
mov eax, 753664 ; 0xB8000 - hardware video memory
shl edx, 8 ; edx ([term_color]) << 8
or edx, 0x20 ; 0x20=' '
.L1: mov word [eax], dx ; [eax] = ' '|bgfg
add eax, 2 ; 1 buffer = 1 word (2 byte)
cmp eax, 757664 ; 80*25*2byte = 4000byte + origin
jne .L1
ret
.end:
; term_init End of function
; Function _term_setcolor: Set terminal background and foreground color (fg | bg << 4)
_term_setcolor:; Function begin
mov eax, dword [esp+4]
mov byte [term_color], al
ret
.end:
; term_setcolor End of function
; Function _term_putentryat: Write an entry to terminal buffer
_term_putentryat:; Function begin
movzx edx, byte [esp+8] ; color
shl edx, 8
movzx eax, byte [esp+4] ; c
or edx, eax
mov eax, dword [esp+16] ; y
mov ebx, VGA_WIDTH
mul ebx ; y * 80
add eax, dword [esp+12] ; + x
mov ecx, dword [term_buffer]
mov word [ecx+eax*2], dx ; 2 byte per buffer
ret
.end:
; term_putentryat End of function
; Function _term_putchar: write a char into terminal buffer
_term_putchar:; Function begin
push ebp
mov ebp, esp
; Call _term_putentryat
mov eax, dword [term_row] ; y
push eax
mov eax, dword [term_col] ; x
push eax
movzx eax, byte [term_color]
push eax
movzx eax, byte [ebp+8] ; c
call _term_putentryat
add esp, 16
mov eax, dword [term_col] ; if (++term_col == 80)
inc eax
cmp eax, VGA_WIDTH
jne .leave
mov dword [term_col], 0 ; term_col = 0
; TODO: if reaching end of buffer, pop buffer top line into history, shift remaining
; lines 1 row up.
mov eax, dword [term_row] ; if (++term_row == 25)
inc eax
cmp eax, VGA_HEIGHT
jne .leave
mov dword [term_row], 0 ; term_col = 0
.leave: leave
ret
.end:
; term_putchar End of function
; Function _term_write: Write input buffer to terminal buffer for [size] long, at current x, y
_term_write:; Function begin
push ebp
mov ebp, esp
xor eax, eax
mov ecx, dword [ebp+12] ; size
mov edx, dword [ebp+8] ; data (const char*)
.for: push ecx ; keep the counter
movzx ebx, byte [edx+eax] ; edx+eax == data[i]
inc eax
push ebx
call _term_putchar
add esp, 4
pop ecx ; restore counter
loop .for
leave
ret ; 0133 _ C3
.end:
; term_write End of function
; Function _term_writesring: Write a string to terminal buffer at current x, y
_term_writestring:; Function begin
push ebp
mov ebp, esp
mov edx, dword [ebp+8] ; data (char*)
push edx
call _strlen
add esp, 4
test eax, eax ; leave if zero
jz .leave
push eax ; size
mov edx, dword [ebp+8]
push edx
call _term_write
add esp, 8
.leave: leave
ret
.end:
; term_writestring End of function
SECTION .data align=1 ; section number 2, data
SECTION .bss align=1 ; section number 3, bss
term_color:
resb 1
term_buffer:
resd 1
term_col:
resw 1
term_row:
resw 1
and the last one stdlib.asm
[bits 32]
global _strlen:function (_strlen.end - _strlen)
SECTION .text align=16
; Function _strlen: Measure the length of given string, in number of char. (ANSI)
_strlen: ; Function begin - _strlen(const char *)
push ebp
mov ebp, esp
mov edx, dword [ebp+8] ; const char * (param)
xor eax, eax ; comparand = 0
.L1: inc eax
cmp byte [edx+eax-1], 0
jne .L1
leave
ret
.end:
; strlen End of function
;SECTION .data align=1 noexecute ; section number 2, data
;SECTION .bss align=1 noexecute ; section number 3, bss
Edit: add linker script and how to run This is linker.ld
/* The bootloader will look at this image and start execution at the symbol
designated as the entry point. */
ENTRY(_start)
/* Tell where the various sections of the object files will be put in the final
kernel image. */
SECTIONS
{
/* Begin putting sections at 1 MiB, a conventional place for kernels to be
loaded at by the bootloader. */
. = 1M;
/* First put the multiboot header, as it is required to be put very early
early in the image or the bootloader won't recognize the file format.
Next we'll put the .text section. */
.text BLOCK(4K) : ALIGN(4K)
{
*(.multiboot)
*(.text)
}
/* Read-only data. */
.rodata BLOCK(4K) : ALIGN(4K)
{
*(.rodata)
}
/* Read-write data (initialized) */
.data BLOCK(4K) : ALIGN(4K)
{
*(.data)
}
/* Read-write data (uninitialized) and stack */
.bss BLOCK(4K) : ALIGN(4K)
{
*(COMMON)
*(.bss)
}
/* The compiler may produce other sections, by default it will put them in
a segment with the same name. Simply add stuff here as needed. */
}
I then createa multiboot iso using grub-mkrescue with this grub.cfg
menuentry "myos" {
multiboot /boot/myos.bin
}
finally, I test it using qemu
qemu-system-i386 -cdrom myos.bin