Skip to content

Latest commit

 

History

History
134 lines (103 loc) · 3.62 KB

File metadata and controls

134 lines (103 loc) · 3.62 KB

Code: 02.Kernel64/[EntryPoint.s, Main.c] and binary_amd64.x

;; 02.Kernel64/EntryPoint.s


[BITS 64]

Section .text

extern Main ; Main function in 02.Kernel64/Main.c

START:
    mov ax, 0x10  ; IA-32e mode's data segment; def is in 32 bit EntryPoint.s
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ; address from 0x600000~0x6FFFFF (1MiB) is for stack
    mov ss, ax
    mov rsp, 0x6FFFF8
    mov rbp, 0x6FFFF8

    ; Unlike 32 bit EntryPoint.s, here I show there is another way to execute
    ; code in C language
    call Main

    jmp $  ; this instruction will not be executed 
// 02.Kernel64/Main.c
// Types.h is exactly same as one in 02.Kernel32. You can just copy it
// to 02.Kernel64/Source
#include "Types.h"

void kPrintString(int iX, int iY, const char *pcString);

void Main(void) {
    kPrintString(0, 10, "Switch To IA-32e Mode Success~!!");
    kPrintString(0, 11, "IA-32e C Language Kernel Start............. [PASS]");
}


// write string to specific addr which is used for text mode, so you can
// see string in screen.
// iX: row where string will be
//     possible range: [0~24]
// iY: column where string will be
//     possible range: [0~79]
// string: string to write in screen
// 
// if string overflows iY, it wll be written to next line
// There is no protection for memory overflow. This means if your string
// overflows iX, the string can be written to Kernel area in worst case
void kPrintString(int iX, int iY, const char *pcString) {
	CHARACTER *pstScreen = (CHARACTER *) 0xB8000;
    int i;

    pstScreen += iY * 80 + iX;

    for (i = 0; pcString[i] != 0; i++) {
    	pstScreen[i].bCharacter = pcString[i];
    }
}
/*
 * 02.Kernel/binary_amd64.x
 */

OUTPUT_FORMAT("binary")
OUTPUT_ARCH(i386:x86-64)

SECTIONS {
    /* long mode code resides at 0x200000
     * Instead of concatenating EntryPoint.bin with Kernel.bin, EntryPoint is
     * also involved in the linking process
     */ 

    .text 0x200000 : {
        /* text section of Main file comes first in file*/
        EntryPoint.o(.text)
        Main.o(.text)
        /* text section of other files comes after main in the order of input */
        *(.text)
    }
    . = ALIGN(512);

    /* as .rodata data comes here, operand of instruction that references to a
     * data in .rodata is adjusted
     */
    .rodata : {
        *(.rodata)

        /* add padding to the end so .rodata is aligned to multiply of 512 */
    }

    /* discard all other sections in input files. If this is not specified,
     * ld adds the sections somewhere in the program although instruction to
     * add the sections is not in the script
     */
    /DISCARD/ : {
        *(*)
    }
}

Explanation

Note

64 bit Kernel C code

  1. In 01.Kernel32, binary was made by concatenating EntryPoint.bin and Kernel32_C.bin. In memory layout, EntryPoint.bin is at 0x10000 and Kerne32_c.bin is at 0x10200. EntryPoint.bin exactly jump to 0x10200 to execute C code. However, In 02.Kernel64, I show different way to make 64 bit kernel.

  2. In 02.Kernel64, EntryPoint.s is supposed to be at 0x200000 (2MB) in memory layout, the code calls Main function in C code instead of jumping to specific address. EntryPoint.s is compiled into ELF object file and all object files are passed to linker script so it resolves the address problem. All we need is to make sure that the 64 bit EntryPoint.s is loaded at 0x200000

64 bit Kernel Linker Script

  1. They are almost the same as linker script in 01.Kernel32. The only difference is that OUTPUT_ARCH is changed for x86_64, and EntryPoint.o is the first line in .text section, and .text memory address