0

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
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
tongko
  • 139
  • 2
  • 10
  • You don't show us your linker script (linker.ld) and I am confused. How are you launching this kernel? Booting it from floppy? With GRUB (Mulitboot)? The code seems to be 32-bit code but you won't be able to run this code in 16-bit real mode. I'm assuming that you are using GRUB/Multiboot but you aren't showing the code that bootstraps your kernel entry point. – Michael Petch Oct 30 '17 at 17:51
  • One obvious thing I do see is that you define things like `term_row` and `term_col` like this: `resw 1` . That reserves a single word (a word is 16-bits) but then you load that as a 32-bit word using `mov dword [term_row], eax` (you also read from such location incorrectly too). The way you have written your code the simplest thing would be to change `term_row`, `term_col` to be `resd 1` instead of `resw 1` (resd is a 32-bit dword) – Michael Petch Oct 30 '17 at 17:58
  • NASM supports hexadecmial values. 753664 can be expressed as 0xb8000 in the assembly code. It makes it more readable given most people recognize the video by its hex address (not the decimal one). I only mention it for readability. – Michael Petch Oct 30 '17 at 18:06
  • Rather than use `0x20` for space you can use `' '` instead (again readibility) – Michael Petch Oct 30 '17 at 18:18
  • Best recommendation I can make is to use a debugger (ie: GDB). If you use QEMU you can do remote symbolic debugging via GDB (I gather from the OSDev article this is 32-bit protected mode code). Using a debugger would allow you to step through the assembly code and watch where/how things may be going wrong. – Michael Petch Oct 30 '17 at 19:01
  • A couple of bugs you may wish to look at. `_term_putentryat` uses the [_MUL_ instruction](http://www.felixcloutier.com/x86/MUL.html). _MUL_ will overwite the _EDX_ register with the upper 32-bits of the multiplication result. Problem is your MUL is clobbering what you placed in EDX. In `_term_putchar` you move the character to print into _EAX_ with `movzx eax, byte [ebp+8]` but you never push it on the stack before doing `call _term_putentryat` – Michael Petch Oct 30 '17 at 19:52
  • As well its hard to tell what your calling convention is. I'm not sure what registers you intend to be caller saved and callee saved. One other issue that stands out is that in the function `_term_write` you use the _EDX_ register and seem to expect that value to survive the call `_term_putchar` but `_term_putchar` calls `_term_putentryat` which destroys _EDX_. So the value in _EDX_ can't be relied on after calling `_term_putchar` – Michael Petch Oct 30 '17 at 20:23
  • 1
    As well in `_term_putchar` you increment the column but don't save it back to memory so you are missing a `mov dword [term_col], eax` after the `inc eax` . – Michael Petch Oct 30 '17 at 20:25
  • I've added the linker script and how I run this program at the end of the question. I'm at work right now so I can't take all you good advise into practice, but thank you so much for pointing out so many defects that I should have rectify :) – tongko Oct 31 '17 at 02:30

0 Answers0