-1

Alright, I've been tearing out the remaining hair I have with this problem...

Basically, I'm am trying to develop a very simple hobby OS. The OS will run on an X86 platform, off of a FAT12 floppy disk. Before I test it on my machine, I create a disk image to test with Bochs 2.6.2.

As usual, I put the bootloader onto the bootsector of the disk image, then I added the kernel image(KERNEL.SYS) as a regular FAT12 file.

The bootloader is meant to locate KERNEL.SYS, load it at address 1000h:0000, and jump to it.

However, when I test the disk image with Bochs, I get these results(starting from the jump to 07C0:0000):

Results

From my knowledge, I know that Bochs either keeps resetting(triple fault?) or wrapping back to 0000:0000(A20 is not enabled). There is an infinite loop(JMP $) at start of the kernel(for testing reasons), so I know that it isn't getting executed. I also know that 1000:000 isn't above 1 MB, so I'm not sure.

This is the part that really bothers me though: When I took a memory dump from Bochs (32 MB), I see that KERNEL.SYS was loaded at 0980:0000 or 0000:9800. I know for a fact that I never loaded anything there, so what's going on?

Bootloader code:

[BITS 16]
[ORG 0x00]

JMP btldr_init
NOP

OEMName         DB 'MAGMAPRE'
BytesPerSector      DW 0x0200
SectorsPerCluster   DB 0x01
ReservedSectors     DW 0x0001
NumberOfFATS        DB 0x02
MaxRDirEntries      DW 0x00E0
TotalSectors        DW 0x0B40
MediaDescriptor     DB 0xF0
SectorsPerFAT       DW 0x0009
SectorsPerTrack     DW 0x0012
NumberOfHeads       DW 0x0002
HiddenSectors       DD 0x00000000
TotalSectorsL       DD 0x00000000

DriveNumber     DW 0x0000
Reserved        DB 0x00
VolumeID        DD 0xF00DCAFE
VolumeName      DB 'MAGMA DISK '
FileSysID       DB 'FAT12   '

btldr_init:

    MOV AX, 0x07C0
    MOV DS, AX
    MOV     ES, AX
    MOV SS, AX

    MOV SP, 0x7C00

    MOV DI, 0x03

read_rdir:

    MOV [driveNo], DL

    MOV AX, 0x13

    CALL    dos_to_bios


    MOV AX, 0x020E
    MOV BX, buffer

    CALL    reset_dsksys

    STC
    INT 0x13

    JNC search_rdir

    DEC DI
    JZ  fatal_error

    JMP read_rdir

search_rdir:

    MOV DI, buffer
    MOV SI, knlName
    MOV DX, [MaxRDirEntries]

.compare:

    PUSH    DI
    PUSH    SI

    CLD

    MOV CX, 0x0B

    REP CMPSB

    JE  load_disk_fath

    POP SI
    POP DI

.next_entry:

    DEC DX
    JZ  fatal_error

    ADD DI, 0x20
    JMP .compare

load_disk_fath:

    POP SI
    POP DI

    MOV AX, word [DS:DI+0x1A]
    MOV [cluster], AX
    MOV AX, word [DS:DI+0x1C]
    MOV [knlSize], AX

    MOV DI, 0x03

load_disk_fat:

    MOV AX, [ReservedSectors]
    CALL    dos_to_bios

    MOV AX, 0x0209

    MOV BX, buffer

    STC
    INT 0x13

    JNC load_file_clusterh

    DEC DI
    JZ  fatal_error

    JMP load_disk_fat

load_file_clusterh:

    MOV DI, 0x03

    MOV AX, 0x1000
    MOV ES, AX  

load_file_cluster:

    MOV AX, [cluster]
    ADD AX, 0x21

    CALL    dos_to_bios

    MOV AX, 0x0201

    MOV BX, [bufPos]
    CMP BX, [knlSize]
    JAE get_info

    STC
    INT 0x13

    JC  .try_again

    MOV BX, [bufPos]
    ADD BX, 0x200
    MOV [bufPos], BX
    JMP get_next_cluster

.try_again:

    DEC DI
    JZ  fatal_error

    JMP load_file_cluster

get_next_cluster:

    MOV AX, [cluster]
    MOV BX, [cluster]

    SHR BX, 0x01

    ADD BX, AX

    MOV DX, [DS:BX+buffer]

    TEST    BX, 0x01

    JNZ .even

.odd:

    SHR DX, 0x04
    JMP .check  

.even:

    AND DX, 0x0FFF

.check:

    CMP DX, 0xFF0
    JE  get_info

    MOV [cluster], DX

    ;JMP    load_file_cluster

get_info:

    MOV AX, 0x0BE0
    MOV ES, AX
    MOV DI, 0x02
    XOR SI, SI

.low_mem:

    XOR AX, AX
    INT 0x12

    JC  .low_mem_err

    TEST    AX, AX
    JZ  .low_mem_err

.low_mem_success:

    MOV [ES:DI], AX
    JMP .upper_memE801

.low_mem_err:

    XOR DI, DI
    OR  SI, 0x0001
    MOV [ES:DI], SI

.upper_memE801:

    XOR     CX, CX
    XOR     DX, DX
    MOV     AX, 0xE801
    INT     0x15        
    JC  SHORT .upper_mem_err
    CMP     AH, 0x86    
    JE  SHORT .upper_mem_err
    CMP     AH, 0x80    
    JE  SHORT .upper_mem_err
    JCXZ    .useax      

    MOV     AX, CX
    MOV     BX, DX
    JMP .useax

.upper_mem_err:

    JMP .upper_mem88

.useax:

    MOV DI, 0x04
    MOV [ES:DI], AX
    ADD DI, 0x02
    MOV [ES:DI], BX
    JMP goto_kernel

.upper_mem88:

    MOV AH, 0x88
    INT     0x15        
    JC  SHORT .upper_mem_err88
    TEST    AX, AX      
    JE  SHORT .upper_mem_err88
    CMP     AH, 0x86        
    JE  SHORT .upper_mem_err88
    CMP     AH, 0x80        
    JE  SHORT .upper_mem_err88

.success:

    MOV DI, 0x08
    MOV [ES:DI], AX
    JMP goto_kernel

.upper_mem_err88:

    OR  SI, 0x0002

goto_kernel:


    JMP 1000h:0000  

reset_dsksys:

    PUSHA

    XOR AX, AX
    MOV DL, [driveNo]

    INT 0x13

    JC  fatal_error

    POPA
    RET

fatal_error:

.repeat:

    MOV AL, [DS:SI]
    OR  AL, AL
    JZ  .end

    MOV AH, 0x0E
    INT 0x10

    INC SI
    JMP .repeat

.end:

    CLI
    HLT

dos_to_bios:

    PUSH    BX
    PUSH    AX

    MOV     BX, AX          ; SAVE LOGICAL SECTOR

    MOV     DX, 0           ; FIRST THE SECTOR
    DIV WORD [SectorsPerTrack]
    ADD     DL, 01H         ; PHYSICAL SECTORS START AT 1
    MOV     CL, DL          ; SECTORS BELONG IN CL FOR INT 13H
    MOV AX, BX

    MOV     DX, 0           ; NOW CALCULATE THE HEAD
    DIV     WORD [SectorsPerTrack]
    MOV     DX, 0
    DIV     WORD [NumberOfHeads]
    MOV     DH, DL          ; HEAD/SIDE
    MOV     CH, AL          ; TRACK

    POP AX
    POP     BX

    MOV     DL, BYTE [driveNo]      ; SET CORRECT DEVICE


    RET

knlName     DB 'KERNEL  SYS'

knlSize     DW 0x0000

err     DB 'Could not load Magma.', 0x00

driveNo     DB 0x00

cluster     DW 0x0000
cOffset     DW 0x0000
bufPos      DW 0x0000

TIMES   510 - ($ - $$) DB 0x00

DB  0x55
DB  0xAA

buffer:
s0d4pop
  • 532
  • 4
  • 14
  • I would use the GRUB bootloader, and focus on proper kernel development. Don't spend much time on the boot loading (it is not the interesting part of the exercise). See also http://wiki.osdev.org/Main_Page – Basile Starynkevitch Jul 31 '13 at 05:22
  • @BasileStarynkevitch - Yes, I know for a fact that the bootloader is a very bland part of OS development. However, I prefer to use my own code instead of other pre-made objects ;). – s0d4pop Jul 31 '13 at 05:34
  • Nice job! It is a mistake to do `mov [driveNo], dl` before explicitly setting `ds`, IMHO. I don't think that's your problem. Try looking for 0xEBFE with ds=1000h and offset 0 before making the jump. Apparently, it's not there. Work backwards from there. – Frank Kotler Jul 31 '13 at 07:28
  • A20 is also not a problem, since that's at 1MB - you're loading the code at 64KB – Drew McGowen Jul 31 '13 at 13:20
  • And like @FrankKotler mentioned, not having `DS` set before referencing memory is likely the cause of your problems - you're probably not writing to the location you want, so you're not reading the drive number correctly – Drew McGowen Jul 31 '13 at 13:35
  • @DrewMcGowen - I fixed the problem with `MOV [driveNo], DL`. However, I don't think it was the problem, because `driveNo` was initialized to 0(first floppy). – s0d4pop Jul 31 '13 at 16:04
  • One issue is that you seem to be missing a byte in your FAT12 VBR. Your first `jmp` instruction assembles to `jmp 0x3d`, whereas on a fresh floppy image (via `mkdosfs`), I get `jmp 0x3e`. – Drew McGowen Jul 31 '13 at 16:25
  • @DrewMcGowen - Thanks, I didn't notice that! However, even with an extra byte added(just a reserved byte), it still produces the same results. – s0d4pop Jul 31 '13 at 17:57
  • Just ran this in qemu - only thing I had to do was force `knlSize` to be non-zero (since it wound up as zero by default). Besides that, it worked fine. – Drew McGowen Jul 31 '13 at 18:51
  • That, or the memory at that address is being modified. – Drew McGowen Jul 31 '13 at 19:05
  • @DrewMcGowen - You were exactly right. It was hard to notice, but I had attempted to read data from the root directory entry. However, I had preformed the read after it was overwritten by the FAT. That is why `knlSize` was zero. – s0d4pop Jul 31 '13 at 19:52
  • I suspected it was something involving overwriting memory - I've run into the same issue(s) before when writing my own FAT32 loader. – Drew McGowen Jul 31 '13 at 19:53
  • @DrewMcGowen - Everything that I can see is fixed now, but the kernel still ends up at 0980:000 :(. – s0d4pop Jul 31 '13 at 20:16
  • @DrewMcGowen - Alright, I finally got. `ADD AX, 0x21` was supposed to be `ADD AX, 0x1F`. The difference of 2 ended up reading the wrong disk sector, which ultimately ended in KERNEL.SYS being at the wrong place. This entire problem was simply due to my incorrect conversion from decimal to hex. – s0d4pop Aug 01 '13 at 04:18

2 Answers2

0

Well, everything seems to be OK, so the problem is maybe somewhere else. (BTW, it is not necessary to STC before int 13h, but this can't be a reason for the described problem).

So, I would suggest to try to use the Bochs debugger and inserting int3 breakpoints on the key positions in your source - for example before the kernel read operations and just before the far jump (where you can check what is loaded on $1000:0000

Another note, also not very important, is the value of SP. 7c00h is somehow not usual value for SP, but anyway it is not obvious problem.

Also, the kernel code has to be checked one more time.

johnfound
  • 6,857
  • 4
  • 31
  • 60
0

One thing that seems off about your code is that you set the Stack segment to 07C0 and then the stack pointer to 7C00, so the stack is located at 07C0:7C00 or 0000:F800 which isn't what is causing it to fail AFAIK. Something you should consider though is that the bootrecord can be loaded to either 0000:7C00 or 07C0:0000 depending on the BIOS and it would be a good idea to normalize the code segment with a far jump just to be safe, also remember that it doesn't matter if you originate it at 07C0:0000 or 0000:7C00 as long as it is consistent.

As mentioned before you do not need to STC before a INT 13h and you can omit these to save space, better yet you could clean things up by calling a subroutine that resets the disk, converts LBA to CHS and reads the sectors all in one piece of code rather that have the disk reading code scattered throughout the rest of the code. Also when resetting the disk it sets the carry flag if the drive isn't ready and it is perfectly safe to keep calling INT 13h AX=0 until the carry flag clears.

If you look at the address where you claim the kernel was loaded, convert it to an absolute address, then back to segment offset with a segment of 07C00h you loaded you kernel to 07C0:1C00 which may make more sense if the issue is segmentation.