1

This is a followup to my previous question about relocating my x86 bootloader. It seems I still don't understand x86 addressing schemes correctly, my code is now the following (after preprocessing):

.code16

.global _start

jmp _start

enable_interrupts:
  sti

  in $0x70, %al
  and $0x7F, %al
  out %al, $0x70

  ret

disable_interrupts:
  cli

  in $0x70, %al
  or $0x80, %al
  out %al, $0x70

  ret

a20_enable:
  // removed for brevity
  ret

.p2align 3

gdt_begin:
  .quad 0
  .word 0x000FFFFF & 0xFFFF, 0x0 & 0xFFFF; .byte (0x0 >> 16) & 0xFF, 0x80 | 0x10 | 0x08 | 0x02, 0xC0 | ((0x000FFFFF >> 16) & 0xF), (0x0 >> 24) & 0xFF
  .word 0x000FFFFF & 0xFFFF, 0x0 & 0xFFFF; .byte (0x0 >> 16) & 0xFF, 0x80 | 0x10 | 0x02, 0xC0 | ((0x000FFFFF >> 16) & 0xF), (0x0 >> 24) & 0xFF
gdt_end:

gdt_descriptor:
  .word gdt_end - gdt_begin
  .long gdt_begin

protected_mode_enable:
  lgdt gdt_descriptor
  mov %cr0, %eax
  or $1, %al
  mov %eax, %cr0

  ret

_start:
  mov $0x60, %ax
  mov %ax, %es
  xor %bx, %bx

  mov $DISK_BOOT_SECT_COUNT, %ah
  mov $2, %al
  mov $0, %ch
  mov $1, %cl
  xor %dh, %dh

  int $0x13

reloc_jmp:
  jmp $0x0, $reloc_done

reloc_done:
  mov $0x60, %ax
  mov %ax, %ds
  mov %ax, %es

  mov $0xFFFF, %bx
  mov %bx, %ss
  xor %ax, %ax
  mov %ax, %sp

  cld

  call disable_interrupts

  call a20_enable

  test %ax, %ax
  jz real_mode_boot_error

  call protected_mode_enable

  jmp $0x8, $protected_mode_start

real_mode_boot_error:
  jmp real_mode_boot_error

.code32

protected_mode_start:
  mov $0x10, %ax
  mov %ax, %ds
  mov %ax, %es
  mov %ax, %fs
  mov %ax, %gs

  mov %ax, %ss
  mov $0x80000, %esp

I use the following linker script:

OUTPUT_FORMAT("elf32-i386");

ENTRY(_start);

SECTIONS
{
  . = 0x600;
  .text : {
    out/boot_S.o(.text);
    . = 510;
    SHORT(0xAA55);
  }

  .data : SUBALIGN(2) {
    *(.data);
    *(.rodata*);
  }

  /DISCARD/ : {
    *(.eh_frame);
    *(.comment);
  }
}

and create a binary image with:

gcc -m32 -fno-PIC -g -gdwarf -c asm/boot.S -o out/boot_S.o
ld -melf_i386 -Tout/boot.ld out/boot_S.o -o ../elf/boot.elf
objcopy -O binary ../elf/boot.elf ../img/boot.img

My code first loads the two disk sectors containing my bootloader to 0x600, jumps there and then afterwards tries to enable protected mode. Everything works as expected until the jump to the 32 bit code segment starting at protected_mode_start. objdump disassembles that instruction as follows:

    ...
     6ea:   ea f1 06 08 00          ljmp   $0x8,$0x6f1
    
    000006ef <real_mode_boot_error>:
     6ef:   eb fe                   jmp    6ef <real_mode_boot_error>
    
    000006f1 <protected_mode_start>:
     6f1:   66 b8 10 00 8e d8       mov    $0xd88e0010,%eax
    ...

So I would expected it to jump to 0x6f1. But it doesn't, it jumps to 0xe05b instead. Why? Is my GDT wrong? (it used to work before I added the relocation code). How do I fix this?

EDIT:

After reading all the comments to this question I have changed the following:

  • fixed the GDT descriptor
  • set DS/ES to 0x0 instead of 0x60 after relocation
  • fixed the al/ah mixup
  • don't perform any sort of jumps before the far jump to protected_mode_start after setting bit 0 in CR0

Now everything seems to work although I'm wary of any other basic mistakes I might still be making.

Peter
  • 2,919
  • 1
  • 16
  • 35
  • The *gdt_descriptor* is missing the -1: `.word gdt_end - gdt_begin - 1` This part of the descriptor is a *limit* (zero-based) and not a *size*. – Sep Roland Dec 01 '20 at 19:03
  • 2
    I have deleted my other comments. After reviewing all the code I can see that one significant problem and it is related to one of my earlier comments. I think you have a fundamental misunderstanding as to how segment:offset addressing works in real mode&the VMA in a linker script. The linker script has no notion of 16 bit segment:offset addressing. After you do the relocation you set DS to 0x60. DS should be zero since the VMA you used is 0x600. SO rather than setting DS to 0x60 you need to set it to 0x0000. This alone would cause the GDT descriptor to be loaded from the wrong palce in memory. – Michael Petch Dec 01 '20 at 19:28
  • 1
    @MichaelPetch: Is it actually on top of the BIOS? With A20 disabled it's equivalent to `0x0f00:0ffe` which should be ordinary accessible memory, With A20 enabled it would be above 1MB but still not in the BIOS. But either way it definitely doesn't seem right, and I particularly don't see how a `call` to `a20_enable` can return properly. – Nate Eldredge Dec 01 '20 at 20:03
  • 2
    `mov $DISK_BOOT_SECT_COUNT, %ah` `mov $2, %al` is wrong. Probably works for you because `DISK_BOOT_SECT_COUNT` is 2. You have AL and AH reversed. Should be `mov $DISK_BOOT_SECT_COUNT, %al` `mov $2, %ah` – Michael Petch Dec 01 '20 at 20:17
  • 1
    Although protected mode is officially switched on by performing a "far jump", I would neither use "near jumps" nor data memory accesses between the `mov %eax, %cr0` instruction and the "far jump" activating the protected mode. `ret` is both a data memory access and a "near jump"... Especially when using a CPU emulator (such as DOSEMU), it is not sure if the emulator already activates protected mode immediately after `mov %eax, %cr0`. – Martin Rosenau Dec 02 '20 at 07:49
  • 1
    I'm just now reading this and trying to piece it together. There is probably a lot of stuff I still don't understand but it seems to work now. – Peter Dec 02 '20 at 09:35

0 Answers0