assemblyx86nasmosdevprotected-mode

Assembly 32 bit-Protected Mode, label not pointing to defined string?


I am trying to learn some x86 assembly. I have successfully created a MBR with a bootloader, loaded another sector, switched to Protected Mode and performed a far jump into the loaded sector.

Used environment

I am using NASM on a 64-bit Windows installation and assembling with nasm -s -f bin bootloader.asm -o test.img. I load the test.img file into a VirtualBox VM as a floppy.

The problem

I have written a subroutine (subroutine definition in code section) that uses lodsb to load characters from ESI which I have previously pointed to a label using mov esi, _LOW_KERNEL_MESSAGES_TEST (where _LOW_KERNEL_MESSAGES_TEST is a label defined to db "Hello, World!", 0). However, this does not print anything.

Neither does mov al, [_LOW_KERNEL_MESSAGES_TEST] which was expected to print the first character 'H' (character printing subroutine takes AL as character input)

Directly moving a character into AL by mov al, 't' and then calling the one character printing subroutine works fine, I have also tested the offset and successfully printed the word 'Test', the problem lies only in the "move label into esi and then load it into al" part of the printing

What I tried

The code

As per the request of the kind commenters below, I have attempted to create a minimal reproducible example, code for which can be found here:

https://pastebin.com/PaeLErDU

This can further be assembled by using:

nasm -s -f bin <name of the code file here>.asm -o floppy.img

By binding this floppy.img file to a floppy controller in a VirtualBox 6.1 VM

and running it, a completely black screen appeared with a white cursor in the top left corner and the VM stalled, with a "critical error has occured" message, the error log does not make much sense to me but might be helpful, it can be found at https://pastebin.com/g1C2xf7V

My files:

bootloader.asm:

%define KERNEL_LOAD_POSITION 0x1000

[bits 16]
[org 0x0600]

;***********************************************************;
;                       ENTRY SECTION                       ;
;***********************************************************;

__ENTRY:    ; ENTRY POINT
    cli          ; Clear Interrupts
    xor ax, ax   ; Zero out AX (set to 0)
    mov ds, ax   ; Set Data Segment to 0 (AX)
    mov es, ax   ; Set Extra Segment to 0 (AX)
    mov ss, ax   ; Set Stack Segment to 0 (AX)
    mov sp, ax   ; Set Stack Pointer to 0 (AX)
__SET__LOWER__ENTRY:
        mov cx, 0x0100   ; 256 WORDs in MBR
        mov si, 0x7C00   ; Current MBR Address
        mov di, 0x0600   ; New MBR Address
        rep movsw        ; Copy MBR
    jmp 0:__LOWER_ENTRY  ; Jump to new Address


__LOWER_ENTRY:
    sti
    mov al, 1
    call _BOOTLOADER_READSECTORS
    call _BOOTLOADER_ENTER_PROTECTED


;***********************************************************;
;                     FUNCTION SECTION                      ;
;***********************************************************;



_BOOTLOADER_READSECTORS:
    pusha
    mov ah, 02h
    mov ch, 0x0
    mov cl, 0x02
    mov dh, 0x0
    mov dl, 0x0
    mov bx, KERNEL_LOAD_POSITION
    int 13h
    jc _BOOTLOADER_HANG
    popa
    ret

_BOOTLOADER_HANG:
    jmp $
    ret

_BOOTLOADER_ENTER_PROTECTED:
    cli
    lgdt [GDTR]
    pusha

    mov eax, cr0
    or al, 1
    mov cr0, eax

    popa
    jmp 0x08:_BOOTLOADER_PERFORM_PROTECTED_FAR_JUMP

[bits 32]

_BOOTLOADER_PERFORM_PROTECTED_FAR_JUMP:
    mov ax, 0x10
    mov ds, ax
    mov ss, ax
    mov fs, ax
    mov es, ax
    mov gs, ax

    jmp KERNEL_LOAD_POSITION


;***********************************************************;
;                        DATA SECTION                       ;
;***********************************************************;



;***********************************************************;
;                        GDT SECTION                        ;
;***********************************************************;

GDT:
GDT_NULL_DESC:
    dd 0            ; null descriptor
    dd 0

GDT_CODE_DESC:
    dw 0xFFFF       ; limit low
    dw 0            ; base low
    db 0            ; base middle
    db 10011010b    ; access
    db 11001111b    ; granularity
    db 0            ; base high

GDT_DATA_DESC:
    dw 0xFFFF       ; data descriptor
    dw 0            ; limit low
    db 0            ; base low
    db 10010010b    ; access
    db 11001111b    ; granularity
    db 0            ; base high

GDTR:
    Limit dw 24         ; length of GDT
    Base dd GDT_NULL_DESC   ; base of GDT



;***********************************************************;
;                          PADDING                          ;
;***********************************************************;

times (440 - ($-$$)) db 0      ; Pad For MBR Partition Table


;***********************************************************;
;                      NON-CODE SECTION                     ;
;***********************************************************;


UID: times 4 db 0              ; Unique Disk ID


Reserved: times 2 db 0         ; Reserved, either 0x0000 (normal) or 0x5a5a (readonly)


;***********************************************************;
;                  PARTITION TABLE SECTION                  ;
;***********************************************************;

PT1:                           ; First Partition Entry ( KERNEL HERE )

    db 0x80                     ; Bootable ( Active ) ( 8 bits )

    db 0x00                     ; Starting Head ( Start at head 0 ) ( 8 bits )
    db 0b00000010               ; Starting Sector ( Start at Sector 2 ) ( 6 bits ) NOTE: 2 LSB are for the following cylinder field
    db 0b00000000               ; Starting Cylinder ( Start at Cylinder 0 ) ( 10 bits )

    db 0x21                     ; System ID ( Filysystem ) ( 21, Reserved )

    db 0x00                     ; Ending Head ( End at head 0 ) ( 8 bits )
    db 0b00000100               ; Ending Sector ( End at Sector 4 ) ( 6 bits ) NOTE: 2 LSB are for the following cylinder field
    db 0b00000000               ; Ending Cylinder ( End at Cylinder 0 ) ( 10 bits )

    db 0x00                     ; | Relative sector ( 32 bits )
    db 0x00                     ; |
    db 0x00                     ; |
    db 0x01                     ; |

    db 0x00                     ; | Total sectors in partition ( 32 bits )
    db 0x00                     ; |
    db 0x00                     ; |
    db 0x02                     ; |


PT2: times 16 db 0             ; Second Partition Entry


PT3: times 16 db 0             ; Third Partition Entry


PT4: times 16 db 0             ; Fourth Partition Entry


dw 0xAA55                     ; Magic Word


%define INCLUDE_OFFSET 512

%include "low_kernel.asm"

low_kernel.asm:

[bits 32]

jmp __KERNEL__ENTRY ; JUMP TO KERNEL ENTRY POINT


;***********************************************************;
;                    MACRO/DEFINE SECTION                   ;
;***********************************************************;

%define _LOW_KERNEL_VIDEO_MEMORY_ADDRESS 0xb8000


%define SECTOR_OFFSET INCLUDE_OFFSET

%macro PAD_SECTOR 0
times (512 - ($-$$) + SECTOR_OFFSET) db 0      ; Pad out sector
%define SECTOR_OFFSET SECTOR_OFFSET+512
%endmacro


;***********************************************************;
;                      KERNEL SECTION                       ;
;***********************************************************;

__KERNEL__ENTRY:
    mov esi, _LOW_KERNEL_MESSAGES_TEST
    mov ah, 0x0F
    call _LOW_KERNEL_PRINT

    call _LOW_KERNEL_HANGKERNEL


;; LOW KERNEL SUBROUTINE
; Desc: Prints one character to the designated offset
;
; _IN_ AL ( CHARACTER )
; _IN_ AH ( COLOR )
; _IN_ ECX ( VIDEO MEMORY OFFSET )
_LOW_KERNEL_CHPRINT:
    pushad
    mov ebx, _LOW_KERNEL_VIDEO_MEMORY_ADDRESS
    add ebx, ecx
    mov [ebx], eax
    popad
    ret


;; LOW KERNEL SUBROUTINE
; Desc: Prints strings from ESI
;
; _IN_ AH ( STRING COLOR )
; _IN_ ESI ( STRING ADDRESS )
; _IN_ ECX ( STARTING VIDEO MEMORY OFFSET )
_LOW_KERNEL_PRINT:
    pushad
.internal_loop:
    lodsb
    cmp al, 0
    je .internal_end
    call _LOW_KERNEL_CHPRINT
    add ecx, 0x02
    jmp .internal_loop
.internal_end:
    mov ah, 0x0F
    mov al, 'T'
    call _LOW_KERNEL_CHPRINT
    popad
    ret


_LOW_KERNEL_HANGKERNEL:
    jmp $
    ret


;***********************************************************;
;                   KERNEL DATA SECTION                     ;
;***********************************************************;

_LOW_KERNEL_MESSAGES_TEST:
    db "Hello, World!", 0


PAD_SECTOR

those are assembled by nasm -s -f bin bootloader.asm -o test.img, the .img result of which is then also bound as a floppy into a VM, by running it I got a completely black screen with no cursor

The GDT and far jump were scrambled together from what I found online, and I therefore suspect my low understanding of them the root of my problem

Please take into consideration that this is my first question on StackOverflow and I lack the understanding on how to format the question so that it's readable and everyone understands it. You're more than welcome to post some criticism in the comments so I can make better questions in the future.


Solution

  • You load the kernel (LBA-numbered sector 1 and following) to KERNEL_LOAD_POSITION which is 1000h (4 KiB). However, your kernel immediately follows the boot sector loader in your assembly, so its labels are evaluated as if the kernel was to be placed at 600h + 512 = 800h (2 KiB). The call instructions are not affected by this bug because they are relative, that is, position-independent. You can set the load position to 800h.

    A different way to solve this is by using NASM's multi-section bin format. This is what that would look like:

    org 600h
    section LOADER start=600h
    
    ; loader here
    
    times 510 - ($ - $$) db 0 ; pad to where sector's signature goes
    dw 0AA55h ; sector signature
    
    section KERNEL follows=LOADER vstart=KERNEL_LOAD_POSITION
    
    ; kernel here
    

    This should work even with the load position set to 1000h.