Removing infinite loop in driver breaks code at jmp to protected mode

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
vinicatrinze
Posts: 5
Joined: Thu Apr 03, 2025 2:38 pm

Removing infinite loop in driver breaks code at jmp to protected mode

Post by vinicatrinze »

Hey guys, I hope everyone is doing well! Like many others here, I'm working on my own OS, and I've encountered a problem that I'm having a hard time diagnosing/solving, and so I'm looking for help.

In the current commit (ce5961e) you can see that my OS is triple faulting, however in a previous commit (22b13c5) you see the expected messages being printed correctly to the screen. The difference between these commits is that I removed the infinite while loop from the beginning of FdcInit, a init function of a C driver for the floppy disk that I call to load the kernel into RAM. (The attached image is of the "working" commit)

Here is my GitHub repo: https://github.com/viniciius14/os

Now there are multiple possible error sources, I imagine, and these are some of the ones that I think aren't because I've been messing around with these already after reading some posts from this forum:
  • Not enough sectors are being loaded - the FAT12 version of this bootloader "dynamically" reads sectors from the data section of the floppy disk depending on the size of the stage2, meaning that this shouldn't be an issue.
  • Setting up the stack so that C code doesn't return into oblivion - can be seen in stage2_32.S
  • Ensuring stage2_16 gets placed at 0x7E00 by the linker - added stage2_16.text section to the code and linker.
  • stage2_16 is being compiled with 32 bit instructions - now this one is a bit trickier to verify, but seeing the disassembly of stage2_16, the instructions being used had EAX and other 32 bit registers in the disassembly. Even though the file has [bits 16] at the beginning, it is being compiled with the -f elf32 flag due to the way the Makefile is set up. So I created a branch and you can see that I compile directly to a binary file and attach it later into the code. This didn't give many fruitful results, and I prefer the way my build system is right now without the exception (stage2_16.S) and the workarounds in the Makefile. Additionally, some new bugs showed up, like the beginning of messages was not being printed for some reason, so I dropped it for now. Here's that branch: https://github.com/viniciius14/os/tree/stage2_16_bin
A bit of context about the build system, the idea is to compile the kernel first and then borrow fdc.o from the already compiled drivers and just pluck them into the bootloader so that it can call them and load the kernel, so that's why the fdc.c file is in the kernel directory.

I imagine most of you don't want to run a random guy's code on your pc so I've temporarily included a debug folder in the repo with some debug info files and some binaries. (By the way, QEMU.log is 80-something MB in size, so it'll take a little to load).

In case you do want to run this code for yourself, head on to misc/config.mk and edit the first line, which has the absolute path hardcoded there (this project is meant to be run in a docker container, but that's not working on my machine atm due to a different issue), and you can also see in that file the tools used for compilation.

To do my test runs, I usually do a

Code: Select all

 make clean && make debug && make run 
I appreciate any help I can get, and if you spot something dumb/inefficient or otherwise not very clever, feel free to leave a remark about it as I'm doing this project for the sake of learning.
Attachments
working_print.png
working_print.png (9.16 KiB) Viewed 3002 times
sounds
Member
Member
Posts: 125
Joined: Sat Feb 04, 2012 5:03 pm

Re: Removing infinite loop in driver breaks code at jmp to protected mode

Post by sounds »

Hi! Welcome to the forum.

We will probably offer you a lot of suggestions, but this is a tricky question and I imagine you've spent plenty of hours already on it. It's how OS dev goes unfortunately...

My suggestion is that after reading your first post I get an idea of what's wrong but still can't quite determine very clearly what the "bad" situation is.

What exactly did you get as evidence? You say qemu is triple faulting at the jmp to protected mode? It would help if you can show how you work the problem.
User avatar
vinicatrinze
Posts: 5
Joined: Thu Apr 03, 2025 2:38 pm

Re: Removing infinite loop in driver breaks code at jmp to protected mode

Post by vinicatrinze »

Hey, thanks for the warm welcome :)

Well I used the QEMU.log file to see what was the last value that showed up in the "IN:" section and we get 0x00007e24.
Then I took a look at the disassembly of stage2 and saw that the instruction at 0x7e24 was:

Code: Select all

    7e20:	0c 02                	or     $0x2,%al
    7e22:	e6 92                	out    %al,$0x92
    7e24:	ea 2d 7e 08 00 fa f4 	ljmp   $0xf4fa,$0x87e2d
    7e2b:	eb fe                	jmp    7e2b <_stage2_16+0x2b>
From the _stage2_16.text section, which is where we take our jump to protected mode.

Meaning that allowing code to flow through the FdcInit function into the print_string function broke something earlier on in the execution.
Octocontrabass
Member
Member
Posts: 5754
Joined: Mon Mar 25, 2013 7:01 pm

Re: Removing infinite loop in driver breaks code at jmp to protected mode

Post by Octocontrabass »

vinicatrinze wrote: Fri Apr 04, 2025 12:39 pmNot enough sectors are being loaded - the FAT12 version of this bootloader "dynamically" reads sectors from the data section of the floppy disk depending on the size of the stage2, meaning that this shouldn't be an issue.
It might be an issue if you're loading stage2 directly into one of the buffers stage1 uses to keep track of what it's loading.
vinicatrinze wrote: Fri Apr 04, 2025 12:39 pmseeing the disassembly of stage2_16, the instructions being used had EAX and other 32 bit registers in the disassembly.
This is because your disassembler assumes a 32-bit ELF contains 32-bit code. If you disassemble it as 16-bit code, it will match your source code.
vinicatrinze wrote: Fri Apr 04, 2025 12:39 pmA bit of context about the build system, the idea is to compile the kernel first and then borrow fdc.o from the already compiled drivers and just pluck them into the bootloader so that it can call them and load the kernel, so that's why the fdc.c file is in the kernel directory.
How is that supposed to work? Won't your FDC driver depend on the rest of the kernel to handle IRQs and DMA, since those might be shared with other devices? And won't you have to include a whole bunch of different drivers for all the different devices that might be hiding behind INT 0x13?
vinicatrinze wrote: Fri Apr 04, 2025 12:39 pmI appreciate any help I can get, and if you spot something dumb/inefficient or otherwise not very clever, feel free to leave a remark about it as I'm doing this project for the sake of learning.
Have you tried setting a breakpoint on the call to FdcInit and stepping through the following code to see where it stops working correctly?

Your stage2 linker script is missing the appropriate wildcards. Wildcards are especially important when you use -ffunction-sections and/or -fdata-sections.

You don't need -mno-red-zone for 32-bit code. You probably do need -mgeneral-regs-only.
User avatar
vinicatrinze
Posts: 5
Joined: Thu Apr 03, 2025 2:38 pm

Re: Removing infinite loop in driver breaks code at jmp to protected mode

Post by vinicatrinze »

Octocontrabass wrote: Fri Apr 04, 2025 8:45 pm It might be an issue if you're loading stage2 directly into one of the buffers stage1 uses to keep track of what it's loading.
Yea but, to my understanding, it shouldn't be an issue this "early". I say this because my "disk_read" function (which is just an abstraction for calling the BIOS to load memory for us) can load up to 128 sectors in a single call meaning that we'd need to be exceeding 65KB in size and we're at 1.4 (according to Windows).
I don't think I fully understand your point about the buffers though, in this implementation only a single buffer is used and it's one that starts at 0x7E00 and we have no definitive end for it.
Octocontrabass wrote: Fri Apr 04, 2025 8:45 pm This is because your disassembler assumes a 32-bit ELF contains 32-bit code. If you disassemble it as 16-bit code, it will match your source code.
Ah yea this makes sense, already found the the flag for it, thanks.
Octocontrabass wrote: Fri Apr 04, 2025 8:45 pm How is that supposed to work? Won't your FDC driver depend on the rest of the kernel to handle IRQs and DMA, since those might be shared with other devices? And won't you have to include a whole bunch of different drivers for all the different devices that might be hiding behind INT 0x13?
Well normally I'd say yes but I'm going the polling only route, since it's the bootloader there isn't anything else to do like in a multitasking enviroment and I'd just need to call Init and Read. I have considered adding interrupts to the bootloader so that the drivers could also them but for some reason that doesn't really appeal to me and I think it's possible to do it this way. If I do find out that being stubborn is costing me the project I'll add interrupts and maybe even a "specialized" driver just for the bootloader, this move of borrowing the driver from the kernel was to prevent me from having duplicate code in my project
Octocontrabass wrote: Fri Apr 04, 2025 8:45 pm Have you tried setting a breakpoint on the call to FdcInit and stepping through the following code to see where it stops working correctly?
Well technically no, but that's because I can't get that far with GDB, I've left a screenshot in the attachments, and you can see that after instruction 0x7c42 we jump to 0x5606 which sounds very very invalid. After this involuntary jump we get "stuck" in the 0x5000's. Now I was investigating why this may be happening and I did the following:

Code: Select all

objdump -b binary -m i8086 -D build/bootloader/bin/stage1_FAT12.bin
and I got the following disassembly for the stage1 of the bootloader (posting it all here)

Code: Select all

build/bootloader/bin/stage1_FAT12.bin:     file format binary


Disassembly of section .data:

00000000 <.data>:
   0:   eb 3c                   jmp    0x3e
   2:   90                      nop
   3:   47                      inc    %di
   4:   45                      inc    %bp
   5:   43                      inc    %bx
   6:   4b                      dec    %bx
   7:   4f                      dec    %di
   8:   53                      push   %bx
   9:   20 20                   and    %ah,(%bx,%si)
   b:   00 02                   add    %al,(%bp,%si)
   d:   01 01                   add    %ax,(%bx,%di)
   f:   00 02                   add    %al,(%bp,%si)
  11:   e0 00                   loopne 0x13
  13:   40                      inc    %ax
  14:   0b f0                   or     %ax,%si
  16:   09 00                   or     %ax,(%bx,%si)
  18:   12 00                   adc    (%bx,%si),%al
  1a:   02 00                   add    (%bx,%si),%al
        ...
  24:   00 00                   add    %al,(%bx,%si)
  26:   29 12                   sub    %dx,(%bp,%si)
  28:   34 56                   xor    $0x56,%al
  2a:   78 47                   js     0x73
  2c:   45                      inc    %bp
  2d:   43                      inc    %bx
  2e:   4b                      dec    %bx
  2f:   4f                      dec    %di
  30:   53                      push   %bx
  31:   20 20                   and    %ah,(%bx,%si)
  33:   20 20                   and    %ah,(%bx,%si)
  35:   20 46 41                and    %al,0x41(%bp)
  38:   54                      push   %sp
  39:   31 32                   xor    %si,(%bp,%si)
  3b:   20 20                   and    %ah,(%bx,%si)
  3d:   20 b4 00 b0             and    %dh,-0x5000(%si)
  41:   03 cd                   add    %bp,%cx
  43:   10 fa                   adc    %bh,%dl
  45:   fc                      cld
  46:   31 c0                   xor    %ax,%ax
  48:   89 c3                   mov    %ax,%bx
  4a:   89 c1                   mov    %ax,%cx
  4c:   89 c2                   mov    %ax,%dx
  4e:   8e c0                   mov    %ax,%es
  50:   8e d0                   mov    %ax,%ss
  52:   bc 00 7c                mov    $0x7c00,%sp
  55:   e9 96 00                jmp    0xee
  58:   fa                      cli
  59:   f4                      hlt
  5a:   eb fe                   jmp    0x5a
  5c:   56                      push   %si
  5d:   50                      push   %ax
  5e:   53                      push   %bx
  5f:   ac                      lods   %ds:(%si),%al
  60:   08 c0                   or     %al,%al
  62:   74 08                   je     0x6c
  64:   b4 0e                   mov    $0xe,%ah
  66:   b7 00                   mov    $0x0,%bh
  68:   cd 10                   int    $0x10
  6a:   eb f3                   jmp    0x5f
  6c:   5b                      pop    %bx
  6d:   58                      pop    %ax
  6e:   5e                      pop    %si
  6f:   c3                      ret
  70:   50                      push   %ax
  71:   53                      push   %bx
  72:   51                      push   %cx
  73:   52                      push   %dx
  74:   57                      push   %di
  75:   51                      push   %cx
  76:   e8 28 00                call   0xa1
  79:   58                      pop    %ax
  7a:   b4 02                   mov    $0x2,%ah
  7c:   bf 03 00                mov    $0x3,%di
  7f:   60                      pusha
  80:   f9                      stc
  81:   cd 13                   int    $0x13
  83:   73 0b                   jae    0x90
  85:   61                      popa
  86:   e8 0e 00                call   0x97
  89:   4f                      dec    %di
  8a:   85 ff                   test   %di,%di
  8c:   75 f1                   jne    0x7f
  8e:   eb 30                   jmp    0xc0
  90:   61                      popa
  91:   5f                      pop    %di
  92:   5a                      pop    %dx
  93:   59                      pop    %cx
  94:   5b                      pop    %bx
  95:   58                      pop    %ax
  96:   c3                      ret
  97:   60                      pusha
  98:   b4 00                   mov    $0x0,%ah
  9a:   f9                      stc
  9b:   cd 13                   int    $0x13
  9d:   72 21                   jb     0xc0
  9f:   61                      popa
  a0:   c3                      ret
  a1:   50                      push   %ax
  a2:   52                      push   %dx
  a3:   31 d2                   xor    %dx,%dx
  a5:   f7 36 18 7c             divw   0x7c18
  a9:   42                      inc    %dx
  aa:   89 d1                   mov    %dx,%cx
  ac:   31 d2                   xor    %dx,%dx
  ae:   f7 36 1a 7c             divw   0x7c1a
  b2:   88 d6                   mov    %dl,%dh
  b4:   88 c5                   mov    %al,%ch
  b6:   c0 e4 06                shl    $0x6,%ah
  b9:   08 e1                   or     %ah,%cl
  bb:   58                      pop    %ax
  bc:   88 c2                   mov    %al,%dl
  be:   58                      pop    %ax
  bf:   c3                      ret
  c0:   be ca 7c                mov    $0x7cca,%si
  c3:   e8 96 ff                call   0x5c
  c6:   eb fe                   jmp    0xc6
  c8:   fa                      cli
  c9:   f4                      hlt
  ca:   45                      inc    %bp
  cb:   52                      push   %dx
  cc:   52                      push   %dx
  cd:   4f                      dec    %di
  ce:   52                      push   %dx
  cf:   20 2d                   and    %ch,(%di)
  d1:   20 46 41                and    %al,0x41(%bp)
  d4:   49                      dec    %cx
  d5:   4c                      dec    %sp
  d6:   45                      inc    %bp
  d7:   44                      inc    %sp
  d8:   20 54 4f                and    %dl,0x4f(%si)
  db:   20 52 45                and    %dl,0x45(%bp,%si)
  de:   41                      inc    %cx
  df:   44                      inc    %sp
  e0:   20 46 52                and    %al,0x52(%bp)
  e3:   4f                      dec    %di
  e4:   4d                      dec    %bp
  e5:   20 46 4c                and    %al,0x4c(%bp)
  e8:   4f                      dec    %di
  e9:   50                      push   %ax
  ea:   50                      push   %ax
  eb:   59                      pop    %cx
  ec:   0d 0a a1                or     $0xa10a,%ax
  ef:   16                      push   %ss
  f0:   7c 8a                   jl     0x7c
  f2:   1e                      push   %ds
  f3:   10 7c 30                adc    %bh,0x30(%si)
  f6:   ff f7                   push   %di
  f8:   e3 03                   jcxz   0xfd
  fa:   06                      push   %es
  fb:   0e                      push   %cs
  fc:   7c 50                   jl     0x14e
  fe:   a1 11 7c                mov    0x7c11,%ax
 101:   c1 e0 05                shl    $0x5,%ax
 104:   31 d2                   xor    %dx,%dx
 106:   f7 36 0b 7c             divw   0x7c0b
 10a:   85 d2                   test   %dx,%dx
 10c:   74 01                   je     0x10f
 10e:   40                      inc    %ax
 10f:   88 c1                   mov    %al,%cl
 111:   58                      pop    %ax
 112:   8a 16 24 7c             mov    0x7c24,%dl
 116:   bb 00 7e                mov    $0x7e00,%bx
 119:   e8 54 ff                call   0x70
 11c:   31 db                   xor    %bx,%bx
 11e:   bf 00 7e                mov    $0x7e00,%di
 121:   be b4 7d                mov    $0x7db4,%si
 124:   b9 0b 00                mov    $0xb,%cx
 127:   57                      push   %di
 128:   f3 a6                   repz cmpsb %es:(%di),%ds:(%si)
 12a:   5f                      pop    %di
 12b:   74 0c                   je     0x139
 12d:   83 c7 20                add    $0x20,%di
 130:   43                      inc    %bx
 131:   3b 1e 11 7c             cmp    0x7c11,%bx
 135:   7c ea                   jl     0x121
 137:   eb 71                   jmp    0x1aa
 139:   8b 45 1a                mov    0x1a(%di),%ax
 13c:   a3 de 7d                mov    %ax,0x7dde
 13f:   a1 0e 7c                mov    0x7c0e,%ax
 142:   bb 00 7e                mov    $0x7e00,%bx
 145:   8a 0e 16 7c             mov    0x7c16,%cl
 149:   8a 16 24 7c             mov    0x7c24,%dl
 14d:   e8 20 ff                call   0x70
 150:   bb 00 00                mov    $0x0,%bx
 153:   8e c3                   mov    %bx,%es
 155:   bb 00 7e                mov    $0x7e00,%bx
 158:   a1 de 7d                mov    0x7dde,%ax
 15b:   83 c0 1f                add    $0x1f,%ax
 15e:   b1 01                   mov    $0x1,%cl
 160:   8a 16 24 7c             mov    0x7c24,%dl
 164:   e8 09 ff                call   0x70
 167:   03 1e 0b 7c             add    0x7c0b,%bx
 16b:   a1 de 7d                mov    0x7dde,%ax
 16e:   b9 03 00                mov    $0x3,%cx
 171:   f7 e1                   mul    %cx
 173:   b9 02 00                mov    $0x2,%cx
 176:   f7 f1                   div    %cx
 178:   be 00 7e                mov    $0x7e00,%si
 17b:   01 c6                   add    %ax,%si
 17d:   3e 8b 04                mov    %ds:(%si),%ax
 180:   09 d2                   or     %dx,%dx
 182:   74 05                   je     0x189
 184:   c1 e8 04                shr    $0x4,%ax
 187:   eb 03                   jmp    0x18c
 189:   25 ff 0f                and    $0xfff,%ax
 18c:   3d ff 00                cmp    $0xff,%ax
 18f:   73 05                   jae    0x196
 191:   a3 de 7d                mov    %ax,0x7dde
 194:   eb c2                   jmp    0x158
 196:   8a 16 24 7c             mov    0x7c24,%dl
 19a:   b8 00 00                mov    $0x0,%ax
 19d:   8e d8                   mov    %ax,%ds
 19f:   8e c0                   mov    %ax,%es
 1a1:   ea 00 7e 00 00          ljmp   $0x0,$0x7e00
 1a6:   fa                      cli
 1a7:   f4                      hlt
 1a8:   eb fe                   jmp    0x1a8
 1aa:   be bf 7d                mov    $0x7dbf,%si
 1ad:   e8 ac fe                call   0x5c
 1b0:   fa                      cli
 1b1:   f4                      hlt
 1b2:   eb fe                   jmp    0x1b2
 1b4:   53                      push   %bx
 1b5:   54                      push   %sp
 1b6:   41                      inc    %cx
 1b7:   47                      inc    %di
 1b8:   45                      inc    %bp
 1b9:   32 20                   xor    (%bx,%si),%ah
 1bb:   20 42 49                and    %al,0x49(%bp,%si)
 1be:   4e                      dec    %si
 1bf:   45                      inc    %bp
 1c0:   52                      push   %dx
 1c1:   52                      push   %dx
 1c2:   4f                      dec    %di
 1c3:   52                      push   %dx
 1c4:   20 2d                   and    %ch,(%di)
 1c6:   20 53 54                and    %dl,0x54(%bp,%di)
 1c9:   41                      inc    %cx
 1ca:   47                      inc    %di
 1cb:   45                      inc    %bp
 1cc:   32 2e 42 49             xor    0x4942,%ch
 1d0:   4e                      dec    %si
 1d1:   20 4e 4f                and    %cl,0x4f(%bp)
 1d4:   54                      push   %sp
 1d5:   20 46 4f                and    %al,0x4f(%bp)
 1d8:   55                      push   %bp
 1d9:   4e                      dec    %si
 1da:   44                      inc    %sp
 1db:   2e 0d 0a 00             cs or  $0xa,%ax
        ...
 1fb:   00 00                   add    %al,(%bx,%si)
 1fd:   00 55 aa                add    %dl,-0x56(%di)
Right at the beginning we have a jump to 0x3e which doesn't match any instruction... Is the objdump correctly done from my side? Or did I screw up something in the command? From the screenshot we can see that it in fact does jump to 0x7c3e which could explain the weird behavior but I'm not sure why adding more code to an unrelated (at build time) part of the code is breaking things...
This whole behavior is very weird specially since this isn't the instruction QEMU.log said was the last to be ran...
I'll definitely keep looking more into this.
Octocontrabass wrote: Fri Apr 04, 2025 8:45 pm Your stage2 linker script is missing the appropriate wildcards. Wildcards are especially important when you use -ffunction-sections and/or -fdata-sections.
Ok, I'm going to read up on those and see how they can help me out here.
Octocontrabass wrote: Fri Apr 04, 2025 8:45 pm You don't need -mno-red-zone for 32-bit code. You probably do need -mgeneral-regs-only.
Alright I'll make that switch, thanks for the reply :)
Attachments
qemu_print.png
Octocontrabass
Member
Member
Posts: 5754
Joined: Mon Mar 25, 2013 7:01 pm

Re: Removing infinite loop in driver breaks code at jmp to protected mode

Post by Octocontrabass »

vinicatrinze wrote: Sat Apr 05, 2025 12:47 ammy "disk_read" function (which is just an abstraction for calling the BIOS to load memory for us) can load up to 128 sectors in a single call
When reading from a 1.44MB floppy disk, the BIOS can never read more than 36 sectors in a single call, and usually the limit will be less than that. The BIOS can't read sectors across multiple cylinders, and some BIOSes also can't read sectors across multiple tracks (heads). When reading those sectors, the BIOS can't cross a 64kB boundary in memory.
vinicatrinze wrote: Sat Apr 05, 2025 12:47 amI don't think I fully understand your point about the buffers though, in this implementation only a single buffer is used and it's one that starts at 0x7E00 and we have no definitive end for it.
What about here? You need to read the FAT to follow the cluster chain while you load stage2, so you can't have both of those things at the same location in memory.
vinicatrinze wrote: Sat Apr 05, 2025 12:47 amWell normally I'd say yes but I'm going the polling only route, since it's the bootloader there isn't anything else to do like in a multitasking enviroment and I'd just need to call Init and Read.
Won't your kernel have a multitasking environment in the future? If it does, you won't be able to share the driver between the bootloader and the kernel anymore because the kernel driver won't be allowed to poll.
vinicatrinze wrote: Sat Apr 05, 2025 12:47 ammaybe even a "specialized" driver just for the bootloader,
The BIOS already provides a specialized driver just for the bootloader. It's called INT 0x13.
vinicatrinze wrote: Sat Apr 05, 2025 12:47 aminstruction 0x7c42 we jump to 0x5606 which sounds very very invalid.
The instruction at 0x7C42 is INT 0x10. It's probably jumping to the correct location somewhere in the BIOS, and if you step through it for long enough, you should see it eventually return to your code. (GDB doesn't handle segmentation correctly, so you might see some funky things while the BIOS code is running.)

vinicatrinze wrote: Sat Apr 05, 2025 12:47 amRight at the beginning we have a jump to 0x3e which doesn't match any instruction... Is the objdump correctly done from my side? Or did I screw up something in the command?
Since x86 instructions are variable-length, any non-instruction data mixed in with your code can cause objdump to get lost and disassemble things wrong. It's been a while so I don't remember if it works, but I'd try using --start-address to force objdump to disassemble the code exactly at offset 0x3E. Off the top of my head, the bytes at 0x3E do appear to be reasonable x86 instructions.
User avatar
vinicatrinze
Posts: 5
Joined: Thu Apr 03, 2025 2:38 pm

Re: Removing infinite loop in driver breaks code at jmp to protected mode

Post by vinicatrinze »

Let me start of by saying, what an amazing answer, I learnt a bunch just from your reply right now. :D
Octocontrabass wrote: Sat Apr 05, 2025 1:40 am When reading from a 1.44MB floppy disk, the BIOS can never read more than 36 sectors in a single call, and usually the limit will be less than that. The BIOS can't read sectors across multiple cylinders, and some BIOSes also can't read sectors across multiple tracks (heads). When reading those sectors, the BIOS can't cross a 64kB boundary in memory.
Oh ok, I didn't know that! I'll be sure to add a check for the size of the bootloader so that it can call the BIOS multiple times depending on that.
Octocontrabass wrote: Sat Apr 05, 2025 1:40 am What about here? You need to read the FAT to follow the cluster chain while you load stage2, so you can't have both of those things at the same location in memory.
Damn, I didn't even consider that... So it's some time later I've come back after having implemented that (and made a diagram cause I wasn't sure that would actually be an issue) And it works! That was the main issue.
Octocontrabass wrote: Sat Apr 05, 2025 1:40 am Won't your kernel have a multitasking environment in the future? If it does, you won't be able to share the driver between the bootloader and the kernel anymore because the kernel driver won't be allowed to poll.
Probably, but I'm not sure yet what I'm gonna do regarding that point, though I've thought about it a bit. Either move the driver into the bootloader and make a new one for the kernel or use defines to be able to compile a polling and a interrupt version from the same source or something else.
Octocontrabass wrote: Sat Apr 05, 2025 1:40 am The BIOS already provides a specialized driver just for the bootloader. It's called INT 0x13.
Yea true xD but that driver stops working after jumping to protected mode and I'd like to load my kernel into somewhere above the 1MB mark in RAM (for no particular reason).
Octocontrabass wrote: Sat Apr 05, 2025 1:40 am The instruction at 0x7C42 is INT 0x10. It's probably jumping to the correct location somewhere in the BIOS, and if you step through it for long enough, you should see it eventually return to your code. (GDB doesn't handle segmentation correctly, so you might see some funky things while the BIOS code is running.)
Before applying the changes to the source code I did this and yea you're right I do reach my code again. I was stepping through the instructions and saw that we were jumping into what looked like an invalid address and assumed automatically that it was probably something related with exception handling.
How did you know that it may be the BIOS? The adddress 0x5606 is in the "conventional" memory section of the first MB, I would assume that if it was something BIOS related it would be somewhere like 0x80000 - 0x9FFF but apparently not.

Code: Select all

+---------+---------+------------+----------------------------------------+
| 0x00400 | 0x004FF | 256 Bytes  | BDA (BIOS data area)                   |
+---------+---------+------------+----------------------------------------+
| 0x00500 | 0x07BFF | 30 KiB     | Conventional Memory (1)                |
+---------+---------+------------+----------------------------------------+
| 0x07C00 | 0x07DFF | 512 Bytes  | OS Bootsector                          |
+---------+---------+------------+----------------------------------------+
| 0x07E00 | 0x7FFFF | 480.5 KiB  | Conventional Memory (2)                |
+---------+---------+------------+----------------------------------------+
| 0x80000 | 0x9FFFF | 128 KiB    | EBDA (Extended BIOS Data Area)         |
+---------+---------+------------+----------------------------------------+
Octocontrabass wrote: Sat Apr 05, 2025 1:40 am Since x86 instructions are variable-length, any non-instruction data mixed in with your code can cause objdump to get lost and disassemble things wrong. It's been a while so I don't remember if it works, but I'd try using --start-address to force objdump to disassemble the code exactly at offset 0x3E. Off the top of my head, the bytes at 0x3E do appear to be reasonable x86 instructions.
I couldn't get any output with --start-address but I think --adjust-vma should do the same and it works, unfortunately the code is still kinda funky and we still jump to an address at the middle of an instruction.

Now the OS breaks somewhere else, it never returns from the FdcWaitInterrupt function. I'll take this opportunity to ask, in case anyone knows, is it possible to use polling only on QEMU for working with the FDC? I've had some past issues with the FDC and QEMU and since most people use interrupts and DMA I have never actually seen anyone do it with just polling.

Well at least I'm not stuck on this issue any longer, that was a pretty cool bug thank you so much for the help!
Attachments
qemu_working_print.png
qemu_working_print.png (8.58 KiB) Viewed 2379 times
sketch.png
Octocontrabass
Member
Member
Posts: 5754
Joined: Mon Mar 25, 2013 7:01 pm

Re: Removing infinite loop in driver breaks code at jmp to protected mode

Post by Octocontrabass »

vinicatrinze wrote: Sat Apr 05, 2025 4:44 amYea true xD but that driver stops working after jumping to protected mode and I'd like to load my kernel into somewhere above the 1MB mark in RAM (for no particular reason).
The BIOS also provides INT 0x15 AH=0x87 for copying data above the 1MB mark. (Or you can jump between real mode and protected mode.)
vinicatrinze wrote: Sat Apr 05, 2025 4:44 amHow did you know that it may be the BIOS?
What else could it be when it's immediately after the instruction that calls the BIOS?
vinicatrinze wrote: Sat Apr 05, 2025 4:44 amThe adddress 0x5606 is in the "conventional" memory section of the first MB, I would assume that if it was something BIOS related it would be somewhere like 0x80000 - 0x9FFF but apparently not.
This is some of that GDB weirdness I mentioned earlier. GDB isn't showing you the address of the code, it's showing you the contents of (E)IP. As long as the CS base is 0, (E)IP contains the address of the code. But in real mode, IP is only 16 bits, and the BIOS code lives at addresses that don't fit in 16 bits, so the CS base can't be 0 while that code is executing. It probably actually jumped to 0xC5606, with CS set to 0xC000 and IP set to 0x5606.
User avatar
vinicatrinze
Posts: 5
Joined: Thu Apr 03, 2025 2:38 pm

Re: Removing infinite loop in driver breaks code at jmp to protected mode

Post by vinicatrinze »

Octocontrabass wrote: Sun Apr 06, 2025 7:26 pm The BIOS also provides INT 0x15 AH=0x87 for copying data above the 1MB mark. (Or you can jump between real mode and protected mode.)
I did not know that! That might be an interesting option for the bootloader side of things if I decide I want the FDC driver to be functional in a multitasking enviroment and I personally find it much cleaner/less hacky than jumping back and forth from real to protected mode. Will definitely take a look at that thanks!
Octocontrabass wrote: Sun Apr 06, 2025 7:26 pm What else could it be when it's immediately after the instruction that calls the BIOS?
Non BIOS exception stuff? I don't know. I'm not all that experienced (as you may have been able to tell xD) in the OS world so the thought that it may have been the BIOS all along never really clicked for me. But it will click to me next time!

Octocontrabass wrote: Sun Apr 06, 2025 7:26 pm This is some of that GDB weirdness I mentioned earlier. GDB isn't showing you the address of the code, it's showing you the contents of (E)IP. As long as the CS base is 0, (E)IP contains the address of the code. But in real mode, IP is only 16 bits, and the BIOS code lives at addresses that don't fit in 16 bits, so the CS base can't be 0 while that code is executing. It probably actually jumped to 0xC5606, with CS set to 0xC000 and IP set to 0x5606.
Very interesting, thanks for all the replies so far, you've been very informative and helpful :D
Post Reply