Reputation: 8045
I am trying to jump from my bootloader to my loaded kernel after switching into protected-mode.
The kernel loads fine and is in the right spot but when the second stage of the loader calls the kernel's main function it calls the wrong address.
Here is the second stage of the loader (loader.asm
):
global load_kern
extern main
[bits 32]
load_kern:
call main
cli
hlt
I then assemble and link this with a c object file to create an elf executable:
nasm -f elf32 loader.asm -o loader.o
ld -melf_i386 -n -Tos.lds loader.o os.o -o kernel.elf
I use this file to link them:
ENTRY(load_kern);
PHDRS
{
headers PT_PHDR FILEHDR PHDRS;
code PT_LOAD;
}
SECTIONS
{
.text 0x500: ALIGN(0x100) { *(.text) } :code
.data : { *(.data) }
.bss : { *(.bss) }
/DISCARD/ : { *(.eh_frame) }
}
I then place this kernel.elf
in the 2nd sector of my floppy image (after the boot sector).
When I objdump kernel.elf
the output is correct:
os/kernel.elf: file format elf32-i386
Disassembly of section .text:
00000500 <load_kern>:
500: e8 43 00 00 00 call 548 <main>
505: fa cli
506: f4 hlt
...
00000548 <main>:
548: 55 push %ebp
549: 89 e5 mov %esp,%ebp
54b: 68 5c 05 00 00 push $0x55c
550: 6a 05 push $0x5
552: e8 b0 ff ff ff call 507 <write_string>
557: 83 c4 08 add $0x8,%esp
55a: eb fe jmp 55a <main+0x12>
The address of main seems to be mapped fine but when I load my kernel sector and jump to it this is what gdb shows:
┌─────────────────────────────────────────────────────────────────┐
>│0x600 call 0x646 │
│0x603 add BYTE PTR [bx+si],al │
│0x605 cli │
│0x606 hlt
...
│0x646 leave │
│0x647 ret │
│0x648 push bp │
│0x649 mov bp,sp │
│0x64b push 0x55c |
The kernel is loaded at 0x500 but the text section of the file has an alignment of 0x100 so the code starts at 0x600 (after the elf header) instead of 0x500. The code loads fine but the call in loader.asm
uses 0x646 instead of 0x648 where main starts.
Note that there is an extra line of assembly at 0x603 that isn't supposed to be there. It looks like a junk instruction and I think it might be what's throwing it off. It looks like the the call instruction
500: e8 43 00 00 00 call 548 <main>
is being interpreted correctly but the zeros are carrying to the next instruction somehow to create the extra unexpected instruction. Not sure if that is a possibility.
I can't figure out why it uses an address that is 2 off, this happens in other parts of the kernel code as well.
Feel free to point out any other mistakes I have made, I am learning as I go. I am not sure if this has to do with how I am linking them, the format I am using or something different.
EDIT:
So, it appears this is actually a problem with my bootloader:
global start
[bits 16]
[org 0x7c00]
start: jmp boot
boot:
cli ; clear interrupts
cld ; clear direction flag
mov ax, 0
mov es, ax
mov bx, 0x500 ; set bx to where we load the kernel
mov al, 0x12 ; set lower byte of ax to read 18 sectors
mov ch, 0 ; set higher byte of cx to read track 0 (first track)
mov cl, 2 ; set lower byte of cx to read sector 2 (bootloader is sec1)
mov dh, 0 ; set higher byte of dx to read head 0 (top side of disk)
mov dl, 0 ; set lower byte of dx to read drive 0 (floppy drive)
mov ah, 0x02 ; read
int 0x13 ; call BIOS interrupt 13 to read drive
int 0x10 ; clear screen
jmp pm_switch
hlt ; this instruction should not execute
pm_switch:
xor ax, ax ; clear ds (used by lgdt)
mov ds, ax
cli
lgdt [gdt_desc]
mov eax, cr0
or eax, 1 ; switch to protected mode
mov cr0, eax
jmp 08h:select_jump
select_jump:
xor eax, eax
mov ax, 0x10 ; set data segments to data selector (0x10)
mov ds, ax
mov ss, ax
mov esp, 09000h
mov ax, 0
mov es, ax
; do a far jump to set cs and go to kernel code
jmp 08h:0x600
gdt: ; Address for the GDT
gdt_null: ; Null Segment
dd 0
dd 0
;KERNEL_CODE equ $-gdt
gdt_kernel_code:
dw 0FFFFh ; Limit 0xFFFF
dw 0 ; Base 0:15
db 0 ; Base 16:23
db 09Ah ; Present, Ring 0, Code, Non-conforming, Readable
db 00Fh ; Page-granular
db 0 ; Base 24:31
;KERNEL_DATA equ $-gdt
gdt_kernel_data:
dw 0FFFFh ; Limit 0xFFFF
dw 0 ; Base 0:15
db 0 ; Base 16:23
db 092h ; Present, Ring 0, Data, Expand-up, Writable
db 00Fh ; Page-granular
db 0 ; Base 24:32
gdt_end: ; Used to calculate the size of the GDT
gdt_desc: ; The GDT descriptor
dw gdt_end - gdt - 1 ; Limit (size)
dd gdt ; Address of the GDT
; pad the file to file the rest of the sector (512 bytes) and add boot sig
times 510 - ($-$$) db 0
dw 0xAA55
SOLUTION:
turns out my GDT code segment was set to 16-bit. I changed it to 32 and added a [bits 32]
instruction right after I make the switch to protected (right before select jump
).
Upvotes: 2
Views: 587
Reputation:
You are directing the assembler to generate code for protected mode (32-bit) with [bits 32]
, but your processor is running in real mode (16-bit).
The code you end up running is nonsense. Many instructions are interpreted differently in real and protected mode -- for instance, jmp
only takes two immediate bytes in real mode. (This is where the unexpected add
at 0x603 comes from, for instance -- it's the second half of the erroneous 32-bit immediate value.)
Upvotes: 2