1

I wrote a bootloader in assembly. Here is how it works:

  1. First, the BIOS loads the bootloader as normal. It is instructed to go to 200h.

  2. At 200h, there is some code situating between 200h and 21Eh. It simply switches to VGA mode, draws a magenta pixel on the coordinates 1,1 , using VGA capabilities. It loops that code, forever.

However, when i load it, it just goes on the blinking cursor, which a normal VGA .bin file would NOT do, and would display a pixel. I checked for a pixel and i saw nothing. What i saw meant that the VGA code was not running, and the bootloader was simply loaded and nothing else.

Code of the bootloader:

 cli
 startload:
 push 0x00000200
 ret

 times 510-($-$$) db 0
 db 0x55
 db 0xAA

You can see it simply go to the next sector (starting at 200h) and the code at 200h-21Eh:

BITS 16
org 200h
data:
xcoord DB '1'
ycoord DB '1'
color DB 'D'
.code:
vga:
mov ax, 13h
int 10h
mov ax, ycoord
mov bx, xcoord
mov cx, 320
mul cx
add ax, bx
mov di, ax
mov dl, color
mov [es:di],dl
jmp vga

(Yes i do know this is not 230h bytes, that's the compiled output's size, which is 230h.)

What is the problem? Note: This is not discussing about how to make it go to the second sector. It's asking why it is not going there. I have not found any solutions.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Star OS
  • 119
  • 1
  • 1
  • 15
  • Can you show the code that load the second sector? – Margaret Bloom Sep 16 '16 at 14:52
  • @MargaretBloom It's in the boot part. It's only "push 0x00000200" and "ret". – Star OS Sep 16 '16 at 14:53
  • 3
    Nope, that jumps to `CS:200h` (BTW a jump would be clearer and more well predicted). The BIOS only loads the first sector, the rest is up to you. – Margaret Bloom Sep 16 '16 at 14:54
  • I know. I thought it was gonna jump to 200h, not CS:200h. You can post an answer modifying that jump part for it to go to 200h instead of CS:200h. – Star OS Sep 16 '16 at 14:56
  • `CS:200h` is `200h` I was just being explicit on the segment part. I need to understand this: Who and when is loading the second sector? Rember that the first second ends at 200h. – Margaret Bloom Sep 16 '16 at 14:59
  • The tutorial i used, i either understanded it wrongly or it was not accurate. Though, thanks for you trying to help me. – Star OS Sep 16 '16 at 15:01
  • 1
    In a previous answer (not a duplicate of this one) I give an example of loading a sector and jumping to it as a second stage: http://stackoverflow.com/a/34095896/3857942 – Michael Petch Sep 16 '16 at 15:04
  • So replacing that jump command with jmp 0x0000:0200 ? – Star OS Sep 16 '16 at 15:07
  • 0x0000:0x0200 would be physical address 0x02000 (0x0000*16+0x200) . If that is where you loaded the second sector in memory yes. But we don't see your code that reads another sector from the floppy (ie: using int 13h) so we can't tell you if it is correct or not. – Michael Petch Sep 16 '16 at 15:11
  • 3
    I'm getting the sense that you haven't loaded the second sector *at all*, which is the source of your problem. – David Hoelzer Sep 16 '16 at 15:11
  • Can you point us to the tutorial you used? – Michael Petch Sep 16 '16 at 15:23
  • @MichaelPetch A variety. The jump one clearly said "CS:200h", probably did not see that. – Star OS Sep 16 '16 at 15:43
  • 2
    The BIOS only reads a single sector. If you have code outside the first 512 bytes then you need to use _int 13h/AH=2_ to read it or your bootloader will jump into memory that likely just has zero bytes in it.The computer doesn't read extra sectors from the drive, you need to do that manually. – Michael Petch Sep 16 '16 at 15:45
  • I don't understand well that, so you can post an answer if you have a solution. – Star OS Sep 16 '16 at 15:45

2 Answers2

5

In response to one of your questions. This code:

startload:
push 0x00000200
ret

Is pretty much the equivalent of a near absolute jump to CS:0x200. We don't know what the value in CS is, but many BIOSes will start with CS=0 and IP=0x7c00 (but that isn't always the case). You can't really rely on CS being a particular value unless you set it your self. In most cases CS is probably zero which means that you are likely jumping to physical memory address 0x00200 (0x0000:0x0200). This happens to be in the middle of the real mode interrupt table that runs from physical address 0x00000 to 0x003FF. Jumping to that location will likely result is some kind of undefined behavior.

You can load your bootloader into BOCHS which has a reasonable debugger that understands 16-bit real mode. You would be able to step through the code and determine exactly what CS is and where it jumps to.


What you are probably trying to accomplish can be done with the following code. This is a simple alteration of my previous Stackoverflow answer to a different question. To get an understanding of what this code is doing, please see my previous answer.

In a nutshell, the BIOS reads a single disk sector (512 bytes) from the disk starting at physical memory address 0x7C00. If you want other sectors to be loaded you must write code that loads them into memory, and then jump to that code after loading.

In this example the first stage is a bootloader that loads a single sector second stage from sector 2 of the disk (the sector right after the boot sector). In this example I am going to load the second sector right after the first at physical address 0x07e00 via segment:offset pair 0x7e0:0x0000 . (0x07e0<<4)+0x0000 = 0x07e00.

bootload.asm

bits 16
ORG 0x7c00      ; Bootloader starts at physical address 0x07c00

    ; At start bootloader sets DL to boot drive

    ; 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         ; AH = 0 = Reset floppy disk
    int 0x13
    jc reset          ; If carry flag was set, try again

    mov ax,0x07e0     ; When we read the sector, we are going to read to
                      ;    address 0x07e0:0x0000 (phys address 0x07e00)
                      ;    right after the bootloader in memory
    mov es,ax         ; Set ES with 0x07e0
    xor bx,bx         ; Offset to read sector to
floppy:
    mov ah,0x2        ; 2 = Read floppy
    mov al,0x1        ; Reading one sector
    mov ch,0x0        ; Track(Cylinder) 1
    mov cl,0x2        ; Sector 2
    mov dh,0x0        ; Head 1
    int 0x13
    jc floppy         ; If carry flag was set, try again

    jmp 0x07e0:0x0000 ; Jump to 0x7e0:0x0000 setting CS to 0x07e0
                      ;    IP to 0 which is code in second stage
                      ;    (0x07e0<<4)+0x0000 = 0x07e00 physical address

times 510 - ($ - $$) db 0   ; Fill the rest of sector with 0
dw 0xAA55                   ; This is the boot signature

The second stage will be loaded by INT 13h/AH=2h right after the bootloader in memory, starting at physical address 0x07e00.

stage2.asm

BITS 16

; ORG needs to be set to the offset of the far jump used to
; reach us. Jump was 0x7e0:0x0000 so ORG = Offset = 0x0000.
ORG 0x0000

main:
    ; Set DS = CS
    mov ax, cs
    mov ds, ax

    ; Set to graphics mode 0x13 (320x200x256)
    mov ax, 13h
    int 10h

    ; Set ES to the VGA video segment at 0xA000
    mov ax, 0xa000
    mov es, ax

vga:
    ; Draw pixel in middle of screen
    mov ax, [ycoord]
    mov bx, [xcoord]
    mov cx, 320
    mul cx
    add ax, bx
    mov di, ax
    mov dl, [color]
    mov [es:di],dl

    ; Put processor in an endless loop
    cli
.endloop:
    hlt
    jmp .endloop

; Put Data after the code
xcoord DW 160
ycoord DW 100
color  DB 0x0D    ; One of the magenta shades in VGA palette

The code above is a slightly altered version of your VGA code, as yours had bugs. Your VGA code didn't properly set the ES segment to 0xA000 which is the start of the VGA graphics memory; it didn't properly de-reference the variables (you used their addresses and not the values);the size of the coordinates were a BYTE instead of a WORD; defined values in variables with ASCII character values. I also moved the data after the code.

I modified the code to draw a pixel in the middle of the 320x200 display and then loop infinitely to end the program.


On Linux (or on Windows with Chrysocome DD) using NASM to assemble, you could generate a 720K bootdisk with these commands:

dd if=/dev/zero of=disk.img bs=1024 count=720
nasm -f bin bootload.asm -o bootload.bin
dd if=bootload.bin of=disk.img conv=notrunc

nasm -f bin stage2.asm -o stage2.bin    
dd if=stage2.bin of=disk.img bs=512 seek=1 conv=notrunc

This builds bootload.bin and places it into the first sector of the disk image, and then builds stage2.bin and places it in sector 2 of the disk image. The disk image is called disk.img.

You should be able to test it with QEMU with something like:

qemu-system-i386 -fda disk.img

More information on DD usage can be found in one of my other Stackoverflow answers. Coincidentally that answer was for a question you had last year.

Community
  • 1
  • 1
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • If your bootload.bin is correctly padded to 512B, you could also `cat bootload.bin stage2.bin /dev/zero | head -c 720K > disk.img`. But the dd method still puts the second sector at offset=512 regardless of the size of the first binary. Also possible: `cat bootload.bin stage2.bin > disk.img && truncate disk.img -s $((720*1024))` to make a sparse file. – Peter Cordes Sep 16 '16 at 19:49
  • I think the origins of using dd for stuff like this is some pretty ancient trivia. On some Unix systems, you had to use something like dd to write to block device files (like /dev/fd0), because they only accepted writes that were block-aligned and wrote full blocks. Linux has a buffer layer above block devices, so you can do anything. (Or you can open with O_DIRECT so writes don't go through the buffer cache). dd is certainly still useful for reading/writing at offsets within binary files, but there's nothing "special" about it these days. (Not saying you made that claim, just a fun fact). – Peter Cordes Sep 16 '16 at 19:56
  • That is true, however using DD method exclusively works on both Wndows and Linux platforms which is convenient. With Windows you get chrysocome DD and it supports pseudo devices including /dev/zero . Using your method would require a bash shell (cygwin, Msys, Msys2 etc) that has the other related tools (head, cat, truncate). It is also no coincidence my answer actually mentioned Chrysocome DD AND Widnows as it targets the non-Linux/*nix crowd as well. I learned my lesson about assuming Linux specific answers in past dealings with OS Dev people on SO. – Michael Petch Sep 16 '16 at 19:56
  • In MS-DOS you could at least concatenate two files with `type a.bin b.bin > disk.img`, I think. Good point that this is a portable suggestion that doesn't require a bunch of additional text in the answer. I wouldn't use Windows without a cygwin or mingw shell, but I guess some people do. – Peter Cordes Sep 16 '16 at 20:00
  • 2
    @PeterCordes: I'd use `copy /b` instead of `type` on MS-DOS but then the concern is that some virtual environments have been known to balk on sizes that aren't well known floppy disk sizes. Since the OP didn't mention environment I play it on the safe side. Usually the reason why when I give an answer I include the DD command to generate a well known floppy format like 720k (or 1.44MB, 360K, 1.2M). – Michael Petch Sep 16 '16 at 20:15
  • 2
    @PeterCordes You'd want to use `copy /b a.bin+b.bin disk.img` on MS-DOS (or the Windows command line), as `type` would treat the files as text files and stop reading after the first CTRL-Z character. – Ross Ridge Sep 16 '16 at 20:15
  • Actually @RossRidge I just said that too lol. Agree with you on that. – Michael Petch Sep 16 '16 at 20:16
  • You could have your assembler create the padding out to 720k if you wanted. Otherwise, that's the trickier part that DD is good at, and that I don't know how to do with regular DOS commands. – Peter Cordes Sep 16 '16 at 20:20
  • @PeterCordes : That is true and somewhere I have an SO answer that gets into that. However since there are 2 stages you have to concern yourself if you intend to use sections of code with different origin points. One can code it differently to avoid that. Although NASM doesn't support multiple ORG directives it does support `section` directive with the `vstart` option if you wanted multiple sections with the differing origins.The sample I have somewhere (maybe it was in #OSDev) uses a single NASM assembler file that makes a floppy with 2 stages in one file with multiple origins. – Michael Petch Sep 16 '16 at 20:27
  • 1
    My comment _One can code it differently to avoid that._ is referring to the scenario where you don't mind that the first and second stage are all in the same 64kb segment one after the other. In that case a single segment with boot signature in the middle of the 1st and 2nd stage and padding out to the size of a floppy after 2nd stage is dead easy. – Michael Petch Sep 16 '16 at 20:39
2

It appears that you are missing some pretty important code to actually read the second sector into memory. Something like this:

mov ah,0x02 ; read sectors into memory
mov al,0x10 ; number of sectors to read (16)
mov dl,[bootDrive]  ; drive number
mov ch,0    ; cylinder number
mov dh,0    ; head number
mov cl,2    ; starting sector number
mov bx,Main ; address to load to
int 0x13    ; call the interrupt routine

This assumes that you have saved the value of dl into a byte with label bootDrive. Obviously, you would need to change the address to which you load the code.

Using ORG 0x200 just configures the assembler to handle the generation of non-relocatable address references.

David Hoelzer
  • 15,862
  • 4
  • 48
  • 67
  • Gonna try that. – Star OS Sep 16 '16 at 15:29
  • Back; just jumped to the normal bootloader on the hard drive. I saw a blinking hard drive cursor which seems to, with that code go from bootloader - hard drive boot loader - initialization/etc. of graphics - boot menu installed in the hard drive. – Star OS Sep 16 '16 at 15:35
  • @StarOS: Probably a good idea to use a simulator like BOCHS for development: then you can single-step your boot-sector code in the built-in debugger, and examine registers / memory. This is *much* easier than working mostly blind. – Peter Cordes Sep 16 '16 at 19:42