Serial
Serial

Reputation: 8045

Assembly call maps to wrong address

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

Answers (1)

user149341
user149341

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

Related Questions