The second stage of the bootloader prints garbage using Int 0x10 / ah = 0x0e

I am trying to learn the assembly and write a bootloader. The following code loads the contents of the floppy drive into memory and proceeds to it (starts loading at 0x1000). This code should print β€œX” on the screen, but for some reason, it prints a space. Can someone please tell me what is wrong?

[bits 16] jmp reset reset: ;Resets floppy drive xor ax,ax ;0 = Reset floppy disk mov dl,0 ;Drive 0 is floppy int 0x13 jc reset ;If carry flag was set, try again mov ax,0x1000 ;When we read the sector, we are going to read address 0x1000 mov es,ax ;Set ES with 0x1000 floppy: mov ah,0x2 ;2 = Read floppy mov al,0x11 ;Reading one sector mov ch,0x0 ;Track 1 mov cl,0x2 ;Sector 2, track 1 mov dh,0x0 ;Head 1 mov dl,0x0 ;Drive = 0 (Floppy) int 0x13 jc floppy ;If carry flag was set, try again jmp 0x1000:0000 ;Jump to 0x1000, start of second program times 510 - ($ - $$) db 0 ;Fill the rest of sector with 0 dw 0xAA55 ;This is the boot signiture ;--- ;--[segment 2]-- mov bx, var mov ah, 0x0e mov al, [bx] int 0x10 jmp $ var: db 'X' times 737280 - ($ - $$) db 0 
+2
source share
1 answer

I can determine if you are using assembler NASM (or NASM compatible). I do not know which OS you use to build the bootloader, but I will consider Linux or Windows. Other environments will be somewhat similar.


You must split your bootloader in two to make it easier. One of them is the bootloader, and the second is the second stage of loading 0x1000: 0x0000. This allows us to correctly determine the starting point for our bootloader. It is expected that the bootloader will be loaded at the physical address 0x07c00, and the second stage - 0x10000 ((0x1000 <4 +) + 0). We need assembler to correctly generate addresses for our data and code.

I wrote some StackOverflow answers that describe some of the changes I made to the code. Some of the most important ones are:

  • General bootloader tips that contain general recommendations and assumptions that you do not want to make in the bootloader
  • Information about errors associated with improper DS setup and garbage collection when accessing memory variables. This refers to the second stage.

If you do not have a proper understanding of segment pairs: offsets, I recommend this article. I talk about this because there is confusion in your question and in your code. You seem to think that the physical memory address 0x1000 is the same as the segment: offset pair 0x1000: 0x0000. In your question you say:

The following code loads the contents of the floppy drive into memory and proceeds to it (starts loading at 0x1000).

In your code, you have this line and comment:

 jmp 0x1000:0000 ;Jump to 0x1000, start of second program 

If you look at this link, you will find that the: offset segment calculates the physical address by moving the segment to the left by 4 bits (multiply by 16 decimal), and then adding the offset. The equation usually appears as (segment <4) + offset. In your case, 0x1000: 0x0000 is the segment 0x1000 and the offset 0x0000. Using the equation to get the physical address in memory that you received (0x1000 <4) + 0x0000 = 0x10000 (not 0x1000)


From your code, it’s impossible to tell how you are going with NASM. I will give an example of how this can be done, but the important part is the bootloader partition. Suppose we put your bootloader in a file called bootload.asm :

 [bits 16] [ORG 0x7c00] ; Bootloader starts at physical address 0x07c00 ; BIOS sets DL to boot drive before jumping to the bootloader ; Since we specified an ORG(offset) of 0x7c00 we should make sure that ; Data Segment (DS) is set accordingly. The DS:Offset that would work ; in this case is DS=0 . That would map to segment:offset 0x0000:0x7c00 ; which is physical memory address (0x0000<<4)+0x7c00 . We can't rely on ; DS being set to what we expect upon jumping to our code so we set it ; explicitly xor ax, ax mov ds, ax ; DS=0 cli ; Turn off interrupts for SS:SP update ; to avoid a problem with buggy 8088 CPUs mov ss, ax ; SS = 0x0000 mov sp, 0x7c00 ; SP = 0x7c00 ; We'll set the stack starting just below ; where the bootloader is at 0x0:0x7c00. The ; stack can be placed anywhere in usable and ; unused RAM. sti ; Turn interrupts back on reset: ; Resets floppy drive xor ax,ax ; 0 = Reset floppy disk int 0x13 jc reset ; If carry flag was set, try again mov ax,0x1000 ; When we read the sector, we are going to read address 0x1000 mov es,ax ; Set ES with 0x1000 floppy: xor bx,bx ;Ensure that the buffer offset is 0! mov ah,0x2 ;2 = Read floppy mov al,0x1 ;Reading one sector mov ch,0x0 ;Track 1 mov cl,0x2 ;Sector 2, track 1 mov dh,0x0 ;Head 1 int 0x13 jc floppy ;If carry flag was set, try again jmp 0x1000:0000 ;Jump to 0x1000, start of second program times 510 - ($ - $$) db 0 ;Fill the rest of sector with 0 dw 0xAA55 ;This is the boot signature 

You should notice that I deleted this line:

 mov dl,0x0 ;Drive = 0 (Floppy) 

This hardcodes the boot disk to floppy A :. If you boot from a USB drive, hard drive, or floppy disk B: your code will not work because the number of disks will probably not be zero in these cases. The BIOS sends the actual boot disk that was used to boot your bootloader. This value is in the DL register. This is the value you must use for the functions of the BIOS drive. Since DL already contains a boot disk, we simply use it as is.


The second stage can be changed in this way. I will take a file called stage2.asm :

 [BITS 16] [ORG 0x0000] ; This code is intended to be loaded starting at 0x1000:0x0000 ; Which is physical address 0x10000. ORG represents the offset ; from the beginning of our segment. ; Our bootloader jumped to 0x1000:0x0000 which sets CS=0x1000 and IP=0x0000 ; We need to manually set the DS register so it can properly find our variables ; like 'var' mov ax, cs mov ds, ax ; Copy CS to DS (we can't do it directly so we use AX temporarily) mov bx, var mov ah, 0x0e mov al, [bx] xor bh, bh ; BH = 0 = Display on text mode page 0 int 0x10 jmp $ var: db 'X' 

I did not try to optimize your code. The idea is to show how to add glue to fix your problems. Both files indicate the starting point using the ORG directive. Boot loaders must be assembled so that they work at memory address 0x07c00. You load the second stage into 0x1000: 0x0000, which maps to the physical address 0x10000. We set the ORG to 0x0000, because FAR JUMP jmp 0x1000:0000 will set CS = 0x1000 and IP = 0x0000. Since the IP address is 0x0000, we want the ORG to match it so that the links to the closest memory are relative to the beginning of our 64k segment.

This will allow the assembler to generate the correct memory references for your variables and code. Since you did this incorrectly in your code, your second step read the wrong memory location for var and subsequently displayed the wrong character.


Once you split 2 files, you need to collect them using NASM and then put them in a disk image. Unlike your question, I will use DD to create a 720k floppy disk image, and then place the bootloader at the beginning (without cutting the disk), and then place the second stage, starting in the sector immediately after. This can be done as follows:

 # Assemble both components as binary images with NASM nasm -f bin bootload.asm -o bootload.bin nasm -f bin stage2.asm -o stage2.bin # Create a 720k disk image dd if=/dev/zero of=disk.img bs=1024 count=720 # Place bootload.bin at the beginning of disk.img without truncating dd if=bootload.bin of=disk.img conv=notrunc # Place stage2.bin starting at the second 512byte sector and write # it without truncating the disk image. bs=512 seek=1 will skip the # first 512 byte sector and start writing stage2.bin there. dd if=stage2.bin of=disk.img bs=512 seek=1 conv=notrunc 

You can run such an image using QEMU with something like:

 qemu-system-i386 -fda disk.img 

If you use Windows and you do not have access to DD, you can use this modification for stage2.asm :

 [BITS 16] [ORG 0x0000] ; This code is intended to be loaded starting at 0x1000:0x0000 ; Which is physical address 0x10000. ORG represents the offset ; from the beginning of our segment. ; Our bootloader jumped to 0x1000:0x0000 which sets CS=0x1000 and IP=0x0000 ; We need to manually set the DS register so it can properly find our variables ; like 'var' mov ax, cs mov ds, ax ; Copy CS to DS (we can't do it directly so we use AX temporarily) mov bx, var mov ah, 0x0e mov al, [bx] xor bh, bh ; BH = 0 = Display on text mode page 0 int 0x10 jmp $ var: db 'X' ; Extend the second stage to (720K - 512 bytes) ; bootload.bin will take up first 512 bytes times 737280 - 512 - ($ - $$) db 0 

Then create and create a 720K disk image with the following commands:

 nasm -f bin bootload.asm -o bootload.bin nasm -f bin stage2.asm -o stage2.bin copy /b bootload.bin+stage2.bin disk.img 

disk.img will be a 720K disk image to be used by QEMU or Bochs. The final size of disk.img should be disk.img bytes.


If you want to move a value from a memory address to a register, you can do it directly without an intermediate register. In your stage2.asm you have the following:

 mov bx, var mov ah, 0x0e mov al, [bx] 

It can be written as:

 mov ah, 0x0e mov al, [var] 

This will move one byte from the var memory area and move it directly to AL. The size is determined by NASM as byte, because the target AL is an 8-bit register.

+7
source

Source: https://habr.com/ru/post/1013793/


All Articles