12

I am writing a toy kernel for learning purposes, and I am having a bit of trouble with it. I have made a simple bootloader that loads a segment from a floppy disk (which is written in 32 bit code), then the bootloader enables the A20 gate and turns on protected mode. I can jump to the 32 bit code fine if I write it in assembler, but if I write it in C, I get a triple fault. When I disassemble the C code I can see that the first two instructions involve setting up a new stack frame. This is the key difference between the working ASM code and the failing C code. I am using NASM v2.10.05 for the ASM code, and GCC from the DJGPP 4.72 collection for the C code.

This is the bootloader code:

org 7c00h
BITS 16

entry:
mov [drive], dl         ;Save the current drive


cli
mov ax,cs               ; Setup segment registers
mov ds,ax               ; Make DS correct
mov ss,ax               ; Make SS correct        

mov bp,0fffeh
mov sp,0fffeh           ;Setup a temporary stack
sti

;Set video mode to text
;===================
mov ah, 0
mov al, 3
int 10h
;===================

;Set current page to 0
;==================
mov ah, 5
mov al, 0
int 10h
;==================

;Load the sector
;=============
call load_image
;=============

;Clear interrupts
;=============
cli
;=============

;Disable NMIs
;============
in ax, 70h
and ax, 80h ;Set the high bit to 1
out 70h, ax
;============

;Enable A20:
;===========
mov ax, 02401h
int 15h
;===========

;Load the GDT
;===========
lgdt [gdt_pointer]
;===========

;Clear interrupts
;=============
cli
;=============

;Enter protected mode
;==================
mov eax, cr0
or eax, 1       ;Set the low bit to 1
mov cr0, eax
;==================

jmp 08h:clear_pipe  ;Far jump to clear the instruction queue


;======================================================
load_image:
reset_drive:
    mov ah, 00h
    ; DL contains *this* drive, given to us by the BIOS
    int 13h
    jc reset_drive

read_sectors:
    mov ah, 02h
    mov al, 01h
    mov ch, 00h
    mov cl, 02h
    mov dh, 00h
    ; DL contains *this* drive, given to us by the BIOS

    mov bx, 7E0h
    mov es, bx
    mov bx, 0

    int 13h
    jc read_sectors

ret
;======================================================

BITS 32 ;Protected mode now!
clear_pipe:

mov ax, 10h             ; Save data segment identifier
mov ds, ax              ; Move a valid data segment into the data segment register
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax              ; Move a valid data segment into the stack segment register
mov esp, 90000h        ; Move the stack pointer to 90000h
mov ebp, esp

jmp 08h:7E00h           ;Jump to the kernel proper

;===============================================
;========== GLOBAL DESCRIPTOR TABLE ==========
;===============================================

gdt:                    ; Address for the GDT

gdt_null:               ; Null Segment
    dd 0
    dd 0

gdt_code:               ; Code segment, read/execute, nonconforming
    dw 0FFFFh       ;   LIMIT, low 16 bits
    dw 0            ;   BASE, low 16 bits           
    db 0            ;   BASE, middle 8 bits
    db 10011010b    ;   ACCESS byte
    db 11001111b    ;   GRANULARITY byte
    db 0            ;   BASE, low 8 bits

gdt_data:               ; Data segment, read/write, expand down
    dw 0FFFFh
    dw 0
    db 0
    db 10010010b
    db 11001111b
    db 0

gdt_end:                ; Used to calculate the size of the GDT



gdt_pointer:                       ; The GDT descriptor
    dw gdt_end - gdt - 1    ; Limit (size)
    dd gdt                  ; Address of the GDT
;===============================================
;===============================================

drive: db 00    ;A byte to store the current drive in
times 510-($-$$) db 00
db 055h
db 0AAh

And this is the kernel code:

void main()
{
    asm("mov byte ptr [0x8000], 'T'");
    asm("mov byte ptr [0x8001], 'e'");
    asm("mov byte ptr [0x8002], 's'");
    asm("mov byte ptr [0x8003], 't'");
}

The kernel simply inserts those four bytes into memory, which I can check as I am running the code in a VMPlayer virtual machine. If the bytes appear, then I know the code is working. If I write code in ASM that looks like this, then the program works:

org 7E00h
BITS 32

main:
mov byte [8000h], 'T'
mov byte [8001h], 'e'
mov byte [8002h], 's'
mov byte [8003h], 't'

hang:
    jmp hang

The only differences are therefore the two stack operations I found in the disassembled C code, which are these:

push ebp
mov ebp, esp

Any help on this matter would be greatly appreciated. I figure I am missing something relatively minor, but crucial, here, as I know this sort of thing is possible to do.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
magpie
  • 121
  • 4
  • 3
    Are you sure you're jumping to the kernel? It looks like you're jumping to 0x08:0x7E00, and C doesn't necessarily put the main function at the top of the ELF or PE file that is spat out by the compiler (in fact, it never does). – SecurityMatt Feb 10 '13 at 10:18
  • 1
    You need to show us how you compile your C code. – Alexey Frunze Feb 10 '13 at 11:17
  • Sorry I forgot to mention this bit. I am using the following commands: `nasm -f bin bootloader.asm -o bootloader.bin` `gcc -nostdinc -s -masm=intel -Wall -c kernel.c -o kernel.o` `strip --strip-all kernel.o` Then I did this to check the disassembled code: `objdump --disassemble --disassembler-options intel kernel.o` I am using the strip function which I found here on stackoverflow while researching how to use GCC, I find that it halves the binary down from ~600 bytes (too big for one sector) to ~340 or so. – magpie Feb 10 '13 at 11:30
  • Do you have the same problem if you try to access the stack in your asm code? Are you sure you have RAM to use at the stack address of 90000h? – Michael Feb 10 '13 at 11:37
  • The VM has 256 meg of RAM. If I access the stack in assembly code (ie replacing the C binary with an assembly one) by adding a `push eax` then the code executes perfectly, and the values `Test` appear as expected in memory. However, if I get the disassembled C code, then assemble it in NASM, it still triple faults the computer. I had to change the disassembled code slightly so NASM would accept it: I changed all the `mov byte ptr ds:0x8000, 0x54` type lines to `mov byte [ds:0x8000], 0x54` instead. The fact that the disassembled C triple faults, but the plain ASM doesn't confuses me. – magpie Feb 10 '13 at 11:59
  • 3
    But the only difference between your disassembled code otherwise is the function prolog? Have you tried adding this to your asm file and seeing if this triple faults? – Michael Feb 10 '13 at 12:15
  • The other difference is at the end removing the `ret` and replacing it with a `jmp $`, as I forgot to add a `for(;;);` to the C code. Adding a `for(;;);` to the C code still triple faults, so that is unrelated. However, when I then prepend the disassembled C with `org 7E00h` and `BITS 32` and `main:` and reassemble with NASM, it all works correctly. I therefore conclude one of these two possibilities is the culprit: GCC is compiling the C such that it expects it is at a location other than 7E00, OR The extra information inside the compiled C code before the actual instructions is faulting – magpie Feb 10 '13 at 12:36
  • So therefore I need to find out how to add an ORG statement to C and also how to strip the file down so it is more like what NASM outputs. – magpie Feb 10 '13 at 12:36
  • I was thinking it might be the `ret` instruction trying to pop the return address off the stack (which won't be present as you `jmp`ed there - but when you compile the C code with an infinite loop and just allow to run (so it won't ever get to the ret instruction) it still triple-faults? It certainly sounds like there's something going on what gcc is compiling - are you able to post the disassembly which triple faults? – Michael Feb 10 '13 at 13:11
  • Here is the objdump of the GCC code. However I think that my problem is not in the code itself, but in the fact that as SecurityMatt said, the first bytes in the file are not code at all. I believe I need to use the LD linker as ring0 linked to. I'll have to use Pastebin here as the disassembly won't fit into this comment. http://pastebin.com/iCdeNpUt – magpie Feb 11 '13 at 04:01
  • I followed the instructions in Michael's link, extracted the .text section to a binary file, and now I've got some bootable C code. So it looks like the problem wasn't the stack at all, but the extra information inside the C file. I was barking up the wrong tree with the stack! Thanks to everyone for your suggestions here, I do appreciate it. – magpie Feb 11 '13 at 07:46

2 Answers2

3

Try using the technique here:

Is there a way to get gcc to output raw binary?

to produce a flat binary of the .text section from your object file.

Community
  • 1
  • 1
Michael
  • 1,136
  • 1
  • 7
  • 15
1

I'd try moving the address of your protected mode stack to something else - say 0x80000 - which (according to this: http://wiki.osdev.org/Memory_Map_(x86)) is RAM which is guaranteed free for use (as opposed to the address you currently use - 0x90000 - which apparently may not be present - depending a bit on what you're using as a testing environment).

Griwes
  • 8,805
  • 2
  • 43
  • 70
Michael
  • 1,136
  • 1
  • 7
  • 15
  • Sorry - just saw your latest comment - obviously this is not the case if you can get the stack working correctly using non-C code. – Michael Feb 10 '13 at 12:03
  • Going back to my original code and only changing the stack pointer to 0x7FFF (ie making no other changes) still triple faults. EDIT: Looks like we crossed posts there, never mind... – magpie Feb 10 '13 at 12:04