简体   繁体   English

从程序集引导程序中调用C内核

[英]Call C kernel from assembly bootloader

EDIT: plz jump to my second post below... 编辑:请跳到我下面的第二篇文章...

I'm looking for a minimalist way to enter to my kernel from my bootloader. 我正在寻找一种极简主义的方式从我的引导程序进入我的内核。 Do you have any workable example to do so ? 你有任何可行的例子吗?

Here is the bootloader which enters protected mode : "boot.asm" 这是进入保护模式的引导加载程序:“boot.asm”

[org 0x7C00]

mov bp , 0x9000
mov sp , bp

cli
lgdt [gdt_descriptor] 

; Enter PM
mov eax, cr0
or  eax, 0x1
mov cr0, eax

jmp 0x8:init_pm 


[bits 32]
init_pm :
    mov ax, 0x10
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ;Tried to call my C function from here 
    call 0x8000      ; Kernel entry point       

    jmp $


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[bits 16]

GDT:
;null : 
    dd 0x0 
    dd 0x0

;code : 
    dw 0xffff       ;Limit
    dw 0x0          ;Base
    db 0x0          ;Base
    db 0b10011010   ;1st flag, Type flag
    db 0b11001111   ;2nd flag, Limit
    db 0x0          ;Base

;data : 
    dw 0xffff       
    dw 0x0          
    db 0x0
    db 0b10010010 
    db 0b11001111 
    db 0x0

gdt_descriptor :
    dw $ - GDT - 1      ;16-bit size
    dd GDT              ;32-bit start address


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Bootsector padding
times 510-($-$$) db 0
dw 0xaa55

Here my kernel entry point I would like to call from the bootloader : "kernel.c" 这里是我想从引导程序中调用的内核入口点:“kernel.c”

void main() {
    char * vga = (char *) 0xb8000 ;
    *vga = "X";
}

Thanks for your help, 谢谢你的帮助,

So far, this is what I tried to do, but it doesn't work : I just put the kernel to 0x8000 with ld and concatenate with cat, then jump from the bootloader to 0x8000 到目前为止,这是我试图做的,但它不起作用:我只是用ld将内核置于0x8000并与cat连接,然后从引导加载程序跳转到0x8000

nasm boot.asm -f bin -o boot.bin
gcc -ffreestanding -c kernel.c -o kernel.o
ld -o kernel.bin -Ttext 0x8000 kernel.o --oformat binary
cat boot.bin kernel.bin > os-image

Oh, this definitely works. 哦,这绝对有效。 It just seems to be your knowledge of C, really. 它似乎只是你对C的了解,真的。

I changed around your C kernel a bit and got this: 我改变了你的C内核并得到了这个:

void main (void) 
{
   unsigned char* vga = (unsigned char*) 0xb8000;
   vga[0] = 'X'; //need to make sure that this is a character
   vga[1] = 0x09; //append the attribute byte
   for(;;); //make sure our kernel never stops, with an infinite loop
}

This seems to have solved the problem for me! 这似乎解决了我的问题! Everything is in working order now, just write a shell in that file and then BAM! 现在一切正常,只需在该文件中写一个shell然后BAM! there you go! 你去吧!

Here is a workable solution for entering 64-bit mode and adds a C kernel entry point : 这是一个可行的解决方案,用于进入64位模式并添加一个C内核入口点:

boot.asm boot.asm

[org 0x7c00]


KERNEL_ADDRESS equ 0x100000


cli

lgdt [gdt_descriptor] 

;Switch to PM
mov eax, cr0 
or eax, 0x1 
mov cr0, eax 

jmp 0x8:init_pm 


[bits 32]

init_pm :
    mov ax, 0x10
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax


call build_page_tables


;Enable PAE
mov eax, cr4                 
or eax, 1 << 5               
mov cr4, eax

;# Optional : Enable global-page mechanism by setting CR0.PGE bit to 1
mov eax, cr4                 
or eax, 1 << 7               
mov cr4, eax

;Load CR3 with PML4 base address
;NB: in some examples online, the address is not offseted as it seems to
;be in the proc datasheet (if you were wondering about this strange thing).
mov eax, 0x1000
mov cr3, eax

;Set LME bit in EFER register (address 0xC0000080)
mov ecx, 0xC0000080     ;operand of 'rdmsr' and 'wrmsr'
rdmsr                   ;read before pr ne pas écraser le contenu
or eax, 1 << 8          ;eax : operand de wrmsr
wrmsr

;Enable paging by setting CR0.PG bit to 1
mov eax, cr0
or eax, (1 << 31)
mov cr0, eax

;Load 64-bit GDT
lgdt [gdt64_descriptor]

;Jump to code segment in 64-bit GDT
jmp 0x8:init_lm


[bits 64]

init_lm:
    mov ax, 0x10
    mov fs, ax          ;other segments are ignored
    mov gs, ax

    mov rbp, 0x90000    ;set up stack
    mov rsp, rbp

    ;Load kernel from disk
    xor ebx, ebx        ;upper 2 bytes above bh in ebx is for cylinder = 0x0
    mov bl, 0x2         ;read from 2nd sectors
    mov bh, 0x0         ;head
    mov ch, 1           ;read 1 sector
    mov rdi, KERNEL_ADDRESS
    call ata_chs_read


    jmp KERNEL_ADDRESS

    jmp $



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[bits 16]
;; http://wiki.osdev.org/ATA_in_x86_RealMode_%28BIOS%29


;load_loader:
    ;;!! il faut rester sur le meme segment, ie <0x10000 (=2**16)
    ;mov bx, LOADER_OFFSET  
    ;mov dh, 1          ;load 1 sector (max allowed by BIOS is 128)
    ;mov dl, 0x80       ;drive number

    ;mov ah, 0x02       ;read function
    ;mov al, dh
    ;mov ch, 0x00       ;cylinder
    ;mov dh, 0x00       ;head
    ;; !! Sector is 1-based, and not 0-based
    ;mov cl, 0x02       ;1st sector to read 
    ;int 0x13
    ;ret


[bits 32]
build_page_tables:
    ;PML4 starts at 0x1000
    ;il faut laisser la place pour tte la page PML4/PDP/PD ie. 0x1000

    ;PML4 @ 0x1000
    mov eax, 0x2000         ;PDP base address            
    or eax, 0b11            ;P and R/W bits
    mov ebx, 0x1000         ;MPL4 base address
    mov [ebx], eax

    ;PDP @ 0x2000; maps 64Go
    mov eax, 0x3000         ;PD base address
    mov ebx, 0x2000         ;PDP physical address   
    mov ecx, 64             ;64 PDP

    build_PDP:
        or eax, 0b11    
        mov [ebx], eax
        add ebx, 0x8
        add eax, 0x1000     ;next PD page base address
        loop build_PDP

    ;PD @ 0x3000 (ends at 0x4000, fits below 0x7c00)
    ; 1 entry maps a 2MB page, the 1st starts at 0x0
    mov eax, 0x0            ;1st page physical base address     
    mov ebx, 0x3000         ;PD physical base address
    mov ecx, 512                        

    build_PD:
        or eax, 0b10000011      ;P + R/W + PS (bit for 2MB page)
        mov [ebx], eax
        add ebx, 0x8
        add eax, 0x200000       ;next 2MB physical page
        loop build_PD

    ;(tables end at 0x4000 => fits before Bios boot sector at 0x7c00)
    ret



;=============================================================================
; ATA read sectors (CHS mode) 
; Max head index is 15, giving 16 possible heads
; Max cylinder index can be a very large number (up to 65535)
; Sector is usually always 1-63, sector 0 reserved, max 255 sectors/track
; If using 63 sectors/track, max disk size = 31.5GB
; If using 255 sectors/track, max disk size = 127.5GB
; See OSDev forum links in bottom of [http://wiki.osdev.org/ATA]
;
; @param EBX The CHS values; 2 bytes, 1 byte (BH), 1 byte (BL) accordingly
; @param CH The number of sectors to read
; @param RDI The address of buffer to put data obtained from disk               
;
; @return None
;=============================================================================
[bits 64]
ata_chs_read:   pushfq
                push rax
                push rbx
                push rcx
                push rdx
                push rdi

                mov rdx,1f6h            ;port to send drive & head numbers
                mov al,bh               ;head index in BH
                and al,00001111b        ;head is only 4 bits long
                or  al,10100000b        ;default 1010b in high nibble
                out dx,al

                mov rdx,1f2h            ;Sector count port
                mov al,ch               ;Read CH sectors
                out dx,al

                mov rdx,1f3h            ;Sector number port
                mov al,bl               ;BL is sector index
                out dx,al

                mov rdx,1f4h            ;Cylinder low port
                mov eax,ebx             ;byte 2 in ebx, just above BH
                mov cl,16
                shr eax,cl              ;shift down to AL
                out dx,al

                mov rdx,1f5h            ;Cylinder high port
                mov eax,ebx             ;byte 3 in ebx, just above byte 2
                mov cl,24
                shr eax,cl              ;shift down to AL
                out dx,al

                mov rdx,1f7h            ;Command port
                mov al,20h              ;Read with retry.
                out dx,al

.still_going:   in al,dx
                test al,8               ;the sector buffer requires servicing.
                jz .still_going         ;until the sector buffer is ready.

                mov rax,512/2           ;to read 256 words = 1 sector
                xor bx,bx
                mov bl,ch               ;read CH sectors
                mul bx
                mov rcx,rax             ;RCX is counter for INSW
                mov rdx,1f0h            ;Data port, in and out
                rep insw                ;in to [RDI]

                pop rdi
                pop rdx
                pop rcx
                pop rbx
                pop rax
                popfq
                ret


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[bits 16]

GDT:
;null : 
    dd 0x0 
    dd 0x0

;code : 
    dw 0xffff       ;Limit
    dw 0x0          ;Base
    db 0x0          ;Base
    db 10011010b    ;1st flag, Type flag
    db 11001111b    ;2nd flag, Limit
    db 0x0          ;Base

;data : 
    dw 0xffff       
    dw 0x0          
    db 0x0
    db 10010010b 
    db 11001111b 
    db 0x0

gdt_descriptor :
    dw $ - GDT - 1      ;16-bit size
    dd GDT              ;32-bit start address



[bits 32]
;see manual 2, §4.8: most fields are ignored in long mode
GDT64:
;null;
    dq 0x0

;code
    dd 0x0
    db 0x0
    db 0b10011000   
    db 0b00100000
    db 0x0

;data
    dd 0x0
    db 0x0
    db 0b10010000   
    db 0b00000000
    db 0x0

gdt64_descriptor :
    dw $ - GDT64 - 1        ;16-bit size
    dd GDT64                ;32-bit start address


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[bits 16]
times 510 -($-$$) db 0
dw 0xaa55

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

loader.asm : loader.asm:

 [bits 64]


;mov al, 'K'
;mov ah, 3 ; cyan
;mov edx, 0xb8000
;mov [edx], ax
;jmp $

extern main
global _start

_start:
  call main     ; Call our kernel's main() function
  hlt        

main.c : main.c

int main(void)
{
    //Prints a "X" on the upper-left corner
    char * vga = (char *) 0xb8000 ;
    *vga = 'X';

    while(1){};
    return 1;

}

build.sh : This script is for a 64-bit host only build.sh:此脚本仅适用于64位主机

#!/bin/bash

nasm -f bin boot.asm -o boot.bin
nasm -f elf64 loader.asm -o loader.o

#cc -m64  -ffreestanding -fno-builtin -nostdlib -c main.c
cc -m64 -masm=intel -c main.c
ld  -Ttext 0x100000 -o kernel.elf loader.o main.o 
objcopy -R .note -R .comment -S -O binary kernel.elf kernel.bin

dd if=/dev/zero of=image.bin bs=512 count=2880
dd if=boot.bin of=image.bin conv=notrunc
dd if=kernel.bin of=image.bin conv=notrunc bs=512 seek=1

rm ./boot.bin ./kernel.bin ./main.o ./loader.o ./kernel.elf

qemu-system-x86_64  image.bin

Here is what I successed to do but with a kernel written in asm : 这是我成功完成的事情,但是使用asm编写的内核:

boot.asm : boot.asm:

[org 0x7c00]

KERNEL_OFFSET equ 0x1000

call load_kernel

;Switch PM
cli
lgdt [gdt_descriptor]

mov eax, cr0
or eax, 0x1
mov cr0, eax

jmp 0x8:init_pm

[bits 32]
init_pm :
   mov ax, 0x10
   mov ds, ax
   mov ss, ax
   mov es, ax
   mov fs, ax
   mov gs, ax

   mov ebp, 0x90000
   mov esp, ebp

   call KERNEL_OFFSET
   jmp $

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[bits 16]

load_kernel:
   mov bx, KERNEL_OFFSET
   mov dh, 15
   mov dl, 0

   mov ah, 0x02
   mov al, dh
   mov ch, 0x00
   mov dh, 0x00
   mov cl, 0x02
   int 0x13
   ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[bits 16]

GDT:
;null :
   dd 0x0
   dd 0x0

;code :
   dw 0xffff      ;Limit
   dw 0x0         ;Base
   db 0x0         ;Base
   db 10011010b    ;1st flag, Type flag
   db 11001111b    ;2nd flag, Limit
   db 0x0         ;Base

;data :
   dw 0xffff      
   dw 0x0         
   db 0x0
   db 10010010b
   db 11001111b
   db 0x0

gdt_descriptor :
   dw $ - GDT - 1       ;16-bit size
   dd GDT            ;32-bit start address

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
times 510 -($-$$) db 0
dw 0xaa55

kernel.asm: kernel.asm:

[bits 32]

mov al, 'K'
mov ah, 3 ; cyan
mov edx, 0xb8000
mov [edx], ax
jmp $

I build the floppy image so that: 我构建了软盘映像,以便:

nasm boot.asm -o boot.bin
dd if=/dev/zero of=image.bin bs=512 count=2880
dd if=boot.bin of=image.bin conv=notrunc

nasm kernel.asm -o kernel.bin
dd if=kernel.bin of=image.bin conv=notrunc bs=512 seek=1

qemu -fda image.bin -boot a

This way it works ! 这种方式有效! that's almost that... But now, I replace kernel.asm by this kernel.c 这几乎就是......但是现在,我用这个kernel.c替换了kernel.asm

kernel.c kernel.c

void main () {
   char * vga = (char *) 0xb8000 ;
   *vga = "X";
}

And the following script: 以下脚本:

nasm boot.asm -o boot.bin
dd if=/dev/zero of=image.bin bs=512 count=2880
dd if=boot.bin of=image.bin conv=notrunc

gcc -ffreestanding -m32 -c kernel32.c -o kernel.bin
dd if=kernel.bin of=image.bin conv=notrunc bs=512 seek=1

qemu -fda image.bin -boot a

Note that I'm running a 64bit Linux distro, so I use the '-m32' gcc option to compile to 32bits But it doesn't work...... please help ! 请注意,我正在运行64位Linux发行版,因此我使用'-m32'gcc选项编译为32位但是它不起作用......请帮助!

Loading of your kernel was done in the real mode which is using the [segment base:offset] addressing. 内核的加载是在使用[segment base:offset]寻址的实模式下完成的。 To access the same location of the buffer address(KERNEL_OFFSET) in protected mode, shift the memory address value 4 times to the left. 要在保护模式下访问缓冲区地址(KERNEL_OFFSET)的相同位置,请将内存地址值向左移动4次。 So instead of 0x1000, jump to 0x10000. 因此,而不是0x1000,跳转到0x10000。 I would advise you to try using "jmp" first before changing it to "call", and add the infinite for(;;); 我建议你先尝试使用“jmp”,然后再将其改为“call”,并为(;;)添加无限; loop at the end of the main function. 循环在main函数的末尾。

The goal here is to compile and link your kernel in 32 bit so that it is callable in the 32 bit protected mode. 这里的目标是以32位编译和链接你的内核,以便它可以在32位保护模式下调用。 Therefore set the machine-option to -m32 of your gcc command and add "-m elf_i386" to ld. 因此,将机器选项设置为gcc命令的-m32,并将“-m elf_i386”添加到ld。 Use also the default format elf by removing "--oformat binary" option. 通过删除“--oformat binary”选项,也可以使用默认格式elf。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM