flat assembler
Message board for the users of flat assembler.
Index
> OS Construction > Did I enter protected mode right?Goto page 1, 2 Next |
| Author |
|
|
SeproMan 09 May 2026, 23:02
Looks ok, but following are some observations.
Quote: ;0000:7E00 = Kernel Here's a conflict about where the kernel resides! Code: mov ss, ax mov bp, 0x7000 mov sp, bp Always load SP directly beneath loading SS. Then you don't need those CLI and STI instructions. So better write: Code: mov bp, 0x7000 mov ss, ax mov sp, bp Code: hlt jmp $ In order for HLT to stay in effect, the better choice here would be: Code: cli hlt jmp $-2 Loading those 5 disk sectors could go wrong for any odd reason. Therefore it is better to give it a few tries (eg. 5) and not give up on the very first error. And to avoid some potential errors, it is more robust to read the 5 successive sectors one by one (AL=1) instead of all together (AL=5). The code at disk_error should carry a USE16 predicate. The code at kernel_start should not be repeating setting up the segment registers. _________________ Real Address Mode. |
|||
|
|
IsaacZ 10 May 2026, 21:30
Thanks for bringing those problems to my attention @SeproMan, I'm currently fixing those problems. I also found another potential problem, which I fixed by adding this:
Code: org 0x7C00 use16 jmp 0x0000:start ;Some BIOSes would jump to 07C0:0000 which would make CS be 0x07C0. start: ;Setup registers |
|||
|
|
Core i7 11 May 2026, 06:34
IsaacZ wrote: I would like to know anything I did wrong and how to fix it. 1. Load the kernel at the lowest available address to get more real-mode kernel space. Address 0x0600 is preferred, rather than 0x7E00, as you currently have, which is specified in int-13h. 2. There are also errors in the descriptor formatting. For example, the GDT should be aligned on a 4KB boundary in virtual memory, but since you haven't implemented paging yet, define it at least at 16 bytes. Also, pay attention to the "Accessed" bit in the descriptor's Type field. To avoid a GP# exception, it's best to set it to 1 beforehand. Even though the CPU automatically sets this bit the first time it accesses the code/data segment and updates the descriptor, the GDT may not be writable, and then a GP# exception will occur. While you're in real mode, this isn't critical, but after switching to protected mode, the error will be difficult to detect. For example, here are the WinDbg debugger logs for a GDT x32 table dump request - note the flags in the last column: Code: kd> dg 0 80 P Si Gr Pr Lo Sel Base Limit Type l ze an es ng Flags ---- -------- -------- ---------- - -- -- -- -- -------- 0000 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000 0008 00000000 ffffffff Code RE Ac 0 Bg Pg P Nl 00000c9b 0010 00000000 ffffffff Data RW Ac 0 Bg Pg P Nl 00000c93 0018 00000000 ffffffff Code RE Ac 3 Bg Pg P Nl 00000cfb 0020 00000000 ffffffff Data RW Ac 3 Bg Pg P Nl 00000cf3 0028 80042000 000020ab TSS32 Busy 0 Nb By P Nl 0000008b 0030 ffdff000 00001fff Data RW Ac 0 Bg Pg P Nl 00000c93 0038 7ffde000 00000fff Data RW Ac 3 Bg By P Nl 000004f3 0040 00000400 0000ffff Data RW 3 Nb By P Nl 000000f2 0048 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000 0050 8054a100 00000068 TSS32 Avl 0 Nb By P Nl 00000089 0058 8054a168 00000068 TSS32 Avl 0 Nb By P Nl 00000089 0060 00022f40 0000ffff Data RW Ac 0 Nb By P Nl 00000093 0068 000b8000 00003fff Data RW 0 Nb By P Nl 00000092 0070 ffff7000 000003ff Data RW 0 Nb By P Nl 00000092 0078 80400000 0000ffff Code RE 0 Nb By P Nl 0000009a 0080 80400000 0000ffff Data RW 0 Nb By P Nl 00000092 Now let's request the value of the GDTR register, and immediately output a dump of this GDT table: Code: kd> r @gdtr gdtr = 8003f000 <--------- 4Kb padding kd> dqs 8003f000 L5 8003f000 00000000`00000000 8003f008 00cf9b00`0000ffff <--- Code Ring(0) 8003f010 00cf9300`0000ffff <--- Data Ring(0) 8003f018 00cffb00`0000ffff <--- Code Ring(3) 8003f020 00cff300`0000ffff <--- Data Ring(3) Here you can see that bit (A) is set to 1 (see bytes 0x9b and 0x93). But if you decode the values from your table, bit (A) is cleared everywhere—here's the code: Code: format pe64 console include 'win64ax.inc' entry start ;//------------------ section '.data' data readable writeable gdtCode dw 0xffff,0 db 0, 10011010b, 11001111b, 0 gdtData dw 0xffff,0 db 0, 10010010b, 11001111b, 0 ;//------------------ section '.text' code readable executable start: sub rsp,8 mov rax,qword[gdtCode] mov rbx,qword[gdtData] cinvoke printf,<10,' Code desc: 0x%p',\ 10,' Data desc: 0x%p',0>,rax,rbx cinvoke getch cinvoke exit,0 ;//------------------ section '.idata' import data readable writeable library msvcrt,'msvcrt.dll' import msvcrt,printf,'printf',getch,'_getch',exit,'exit' ;//------- RESULT ------------// Code desc: 0x00CF9A000000FFFF Data desc: 0x00CF92000000FFFF So your table should look like this: Code: align 16 ;<------------ Padding gdt_start: dq 0 gdt_code0 dq 0x00cf9b000000ffff gdt_data0 dq 0x00cf93000000ffff gdt_code3 dq 0x00cffb000000ffff gdt_data3 dq 0x00cff3000000ffff gdt_desc: dw $ - gdt_start -1 dd gdt_start Last edited by Core i7 on 11 May 2026, 12:40; edited 1 time in total |
|||
|
|
Core i7 11 May 2026, 08:16
Moreover, while you're in RM, don't rush to switch to PM, but gather as much system information as possible using interrupts. In protected mode, you won't have BIOS services, and you'll have to do everything manually.
For example, for virtual memory with PAGE_TABLE, you'll need to know the size of physical memory above 1MB. Of course, you can read the SMBIOS table in RM, but it only shows the size of installed DDR-SDRAM. However, using the int-15h AX=0xE820 interrupt, you can get a full map of free/used memory, and then create a PAGE_TABLE based on that. In addition to memory, you need to know the number of CPU cores (if you plan on multitasking), collect information about physical devices on the motherboard using "PCI-Config-Spase", determine the hard drive type ATA/SATA/SSD/NVMe (you'll need to write a driver for PM), and much more. Once finished, collect all this data into a single structure somewhere in memory, and only then switch to protected mode. This will save you a lot of time and frustration in the future. Here are the sources for the OS I recently wrote: https://board.flatassembler.net/topic.php?t=23907 |
|||
|
|
IsaacZ 11 May 2026, 13:23
Thanks @Core i7, I'm fixing some of those problems right now, such as the GDT
Core i7 wrote: Moreover, while you're in RM, don't rush to switch to PM, but gather as much system information as possible using interrupts. In protected mode, you won't have BIOS services, and you'll have to do everything manually. I will, but at the moment, I'm trying to make my current code as solid as possible before moving on, Thanks again _________________ Developing a new Operating System... |
|||
|
|
IsaacZ 19 May 2026, 21:09
UPDATE: I spend a week doing some fixes, it should be alot more solid, if I did anything wrong or need to improve anything, please let me know. (I'm a beginner but slowly making progress learning
Here's the updated bootsector: Code: org 0x7C00 use16 jmp 0x0000:start ;Some BIOSes would jump to 07C0:0000 which would make CS be 0x07C0. start: cli cld xor ax, ax mov ds, ax mov es, ax mov bp, 0x7000 mov ss, ax mov sp, bp ;Load SP right after loading SS mov [boot_drive], dl mov di, 5 ;Set video mode 0x03 mov ax, 0x0003 int 0x10 enable_a20: in al, 0x92 or al, 0x2 out 0x92, al sti read_disk: mov ah, 0x02 mov al, 0x01 mov ch, 0x00 mov cl, 0x02 mov dh, 0x00 mov dl, [boot_drive] mov bx, 0x7E00 int 0x13 jnc read_successfully mov ax, 0 ;Reset Disk int 0x13 dec di jnz read_disk jmp disk_error CODE_OFFSET equ gdt_code - gdt_start ;Also a synonym to 0x08 DATA_OFFSET equ gdt_data - gdt_start ;Also a synonym to 0x10 read_successfully: load_gdt: cli lgdt [gdt_descriptor] mov eax, cr0 or eax, 1 mov cr0, eax jmp CODE_OFFSET:start_protected use32 start_protected: mov ax, DATA_OFFSET mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov esp, 0x90000 mov edi, 0xB8000 mov al, '?' mov ah, 0x0F mov [edi], ax jmp 0x08:0x7E00 align 16 gdt_start: gdt_null: dd 0x0 dd 0x0 gdt_code: dw 0xFFFF dw 0 db 0 db 10011011b ;Changed "Accessed" bit to 1 to avoid #GP exception db 11001111b db 0 gdt_data: dw 0xFFFF dw 0 db 0 db 10010011b ;Changed "Accessed" bit to 1 to avoid #GP exception db 11001111b db 0 gdt_end: gdt_descriptor: dw gdt_end - gdt_start - 1 dd gdt_start disk_error: mov ah, 0x0E mov al, '!' mov bh, 0 int 0x10 cli hlt jmp $-2 boot_drive db 0x00 times 510-($-$$) db 0 dw 0xAA55 _________________ Developing a new Operating System... |
|||
|
|
bzt 25 Jun 2026, 12:56
Here's my feedback, I hope you find it useful.
1. the first jump MUST BE a short jump, otherwise some BIOSes won't boot your boot sector 2. enable_a20: is usually ok, but won't work on some older models which need retriggering, for compatibility it's better to just use BIOS here too (AX=2401h/INT15h) 3. read_disk: you should use AH=42h/INT13h because modern devices (disks, SSDs, USB sticks, etc.) won't work with CHS, only with LBA 4. gdt_null / gdt_descriptor: that's not a descriptor, but value, and you can put the latter in the former (saves precious space) 5. you should disable PIC and NMI before switching CPU modes (according to the spec, omitting this unlikely would cause trouble but it could. Better to be safe than sorry) Here's an example in fasm (and source). This is extremely minimal, so should be easy to read and use as a skeleton. It also sets up long mode and parses PE/COFF, ignore those parts. You're basically interested in lines 47 to 106. This code is verified and reported to work on all virtual machines and on real hardware too. Another example boot sector written in fasm that you can use as a skeleton. It's a bit more complex as it loads the kernel file from Minix3 file system, and also supports long mode kernels, ignore those parts. |
|||
|
|
Tomasz Grysztar 16 Aug 2026, 08:45
bzt wrote: 1. the first jump MUST BE a short jump, otherwise some BIOSes won't boot your boot sector Another case is booting from floppy, and you could think that this would be like a boot sector for a FAT partition, because floppies normally were formatted as such. But floppies could also be formatted in non-standard ways, and I never had any trouble booting a non-FAT floppies on machines from the '90s. This is how I tested TetrOS on such machines. So if any BIOS made assumptions about a floppy boot sector other than 0AA55h signature, I would also consider it a bad BIOS. |
|||
|
|
revolution 16 Aug 2026, 09:49
Bad BIOS, yes indeed, they exist. But it doesn't change the problem space. Some BIOSes are bad, and there is very little any normal person can do about it. They will just shrug and say the program is broken.
Unless space is too tight to fit in the JMP, I would just put it in anyway. Where's the harm? The extra 1 nanosecond of execution time is inconsequential. |
|||
|
|
Tomasz Grysztar 16 Aug 2026, 10:02
revolution wrote: Unless space is too tight to fit in the JMP, I would just put it in anyway. Where's the harm? The extra 1 nanosecond of execution time is inconsequential. |
|||
|
|
macomics 16 Aug 2026, 10:22
If the BIOS loaded the MBR before the SBR and such a warning appears, then the fault may be this MBR, not the BIOS. It's just that the MBR implements incorrect checks for the SBR. This is usually eliminated by fully formatting the disk with the creation of a new partition table. And the BIOS has nothing to do with it.
|
|||
|
|
bzt 18 Aug 2026, 16:32
Tomasz Grysztar wrote:
Originally the storage was small, there was no point in partitioning, so the original BIOS was designed to load FAT boot sectors only (which always had that jump, not just the 0AA55h magic). This was the same with floppies and with the early XT disks as well. The MBR come many years later when storage capacity skyrocketed (in the AT era that followed XT), and it was a painful ugly hack to allow booting from partitioned disks without the need to change the already existing IBM PC BIOS (they couldn't care about all possible clones, the MBR was developed with the original IBM PC BIOS in mind). Since many IBM PC clones were just rough copies of the original IBM PC, their BIOS developers simply did not know how to do a proper bootable check and they couldn't possibly foresee MBR chainloading, hence the buggy BIOSes with the jump check. Tomasz Grysztar wrote: If any BIOS did not work with MS-DOS, I would certainly consider it a bug in the BIOS, not the other way around. I had such buggy machines in real life, one was a Compaq and the other one an Olivetti, both machines were famous to be just "somewhat" IBM PC compatible. Those refused to boot without the jump instruction for sure. As far as I know, the most common BIOS rewrites from the AT era (AMI BIOS and Award BIOS), had no such check issues, because these were created after MBR already become a widespread thing. But this doesn't mean there are no buggy BIOSes out there. |
|||
|
|
Tomasz Grysztar 18 Aug 2026, 17:19
bzt wrote:
Your explanation clarifies a lot, though. I did not even consider pre-MBR systems. Since this thread is about 32-bit protected mode OSes, were are firmly in the MBR era. BTW, why do you consider MBR "painful and ugly"? In my eyes it has always been a simple and clean design. Interestingly, a FAT boot record has three bytes reserved for the initial jump. Since the jump has always been short, the third byte was filled with 90h (NOP). But it seems the intention from the beginning was to have space for a 3-byte near jump, just in case it was ever needed. No place for a direct far jump, though. |
|||
|
|
revolution 19 Aug 2026, 02:07
Tomasz Grysztar wrote: Interestingly, a FAT boot record has three bytes reserved for the initial jump. Since the jump has always been short, the third byte was filled with 90h (NOP). But it seems the intention from the beginning was to have space for a 3-byte near jump, just in case it was ever needed. No place for a direct far jump, though. Pass 1: See a forward jmp, it has no idea how far it is so the allocate the maximum three bytes in case all are needed. Pass 2: Compute the jmp distance as < 128 so it only needs two bytes. But there are no more passes to re-assemble and adjust all the addresses, so the address will never change. the third byte is now redundant, might as well make it nop. |
|||
|
|
bzt 19 Aug 2026, 22:32
Tomasz Grysztar wrote: Your explanation clarifies a lot, though. Tomasz Grysztar wrote: BTW, why do you consider MBR "painful and ugly"? In my eyes it has always been a simple and clean design. They could have used more sectors for the partitioning table and the MBR code safely (even if the first sector would have to load the rest). Should they've used 16 sectors for example, 8192 bytes would be more than plenty, and it still wouldn't cross the first track therefore would work equally no matter the disk geometry (typical AT hard disks at that time had at least 16 sectors on their first cylinder, see for example here). I mean just like how GPT done it (yeah, UEFI is complicated, but GPT in itself isn't, just a simple table on multiple continuous sectors, leaving the first sector entirely for the code). |
|||
|
|
Tomasz Grysztar 20 Aug 2026, 08:18
revolution wrote: The reason is because early assemblers were very dumb. bzt wrote: They could have used more sectors for the partitioning table and the MBR code safely (even if the first sector would have to load the rest). Should they've used 16 sectors for example, 8192 bytes would be more than plenty, and it still wouldn't cross the first track therefore would work equally no matter the disk geometry (typical AT hard disks at that time had at least 16 sectors on their first cylinder, see for example here). I mean just like how GPT done it (yeah, UEFI is complicated, but GPT in itself isn't, just a simple table on multiple continuous sectors, leaving the first sector entirely for the code). Should GPT have come a bit sooner than it did? Maybe. Protective MBR is a natural extendability pattern, it could have allowed to move to new structures even back then. |
|||
|
|
bzt 20 Aug 2026, 11:09
Tomasz Grysztar wrote: Multiple sectors would take more time to access, the handling code would be more complex, this all would be a penalty for old systems just to accommodate potential needs of the future ones. Tomasz Grysztar wrote: Trying to predict all future needs from the beginning would likely result in many misses. At a bare minimum, they should have used 2 sectors: 512 bytes for the code, 512 for the table. Loading one more sector from the MBR code isn't complex (the sector load routine was there anyway to load the VBR) nor a real overhead on the boot time. It could use 32 bytes per partition (double the size of MBR's) and could store 16 partitions easily (no need for logical partitions). |
|||
|
|
Tomasz Grysztar 20 Aug 2026, 12:47
bzt wrote: Also it wasn't the first, other solutions were not unheard of when MBR was created; Solaris for example was loading the first 64k of the disk, and BSD slices were lot more capable back then. |
|||
|
|
bzt 20 Aug 2026, 16:30
Tomasz Grysztar wrote: OK, I understand where you're coming from and why we're looking at this so differently. If you consider systems like BSD or Solaris, then we're thinking of completely different worlds. Tomasz Grysztar wrote: Personally, I really liked the bareness and minimalism of MS-DOS, and in this context MBR appeared elegant and clean. Tomasz Grysztar wrote: It was a great platform for learning, when everything was so plain! |
|||
|
| Goto page 1, 2 Next < Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2026, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.