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

时间:2014-11-20 23:18:31

标签: assembly kernel

编辑:请跳到我下面的第二篇文章......

我正在寻找一种从我的bootloader进入我的内核的极简主义方式。 你有任何可行的例子吗?

这是进入保护模式的引导加载程序: “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

这里是我想从引导程序调用的内核入口点: “kernel.c”

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

感谢您的帮助,

到目前为止,这是我试图做的,但它不起作用: 我只是用ld将内核放到0x8000并用cat连接,然后从bootloader跳转到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

4 个答案:

答案 0 :(得分:6)

哦,这绝对有效。它似乎只是你对C的了解,真的。

我改变了你的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
}

这似乎解决了我的问题!现在一切正常,只需在该文件中写一个shell然后BAM!你去吧!

答案 1 :(得分:2)

这是一个可行的解决方案,用于进入64位模式并添加一个C内核入口点:

<强> 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:

 [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

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

    while(1){};
    return 1;

}

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

答案 2 :(得分:1)

这是我成功完成的事情,但是使用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:

[bits 32]

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

我构建了软盘映像,以便:

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

这种方式有效!那几乎就是...... 但现在,我用这个kernel.c替换kernel.asm。

<强> kernel.c

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

以下脚本:

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

请注意,我正在运行64位Linux发行版,因此我使用'-m32'gcc选项编译为32位 但它不起作用......请帮忙!

答案 3 :(得分:1)

内核的加载是在使用[segment base:offset]寻址的实模式下完成的。要在保护模式下访问缓冲区地址(KERNEL_OFFSET)的相同位置,请将内存地址值向左移动4次。因此,而不是0x1000,跳转到0x10000。我建议你尝试使用&#34; jmp&#34;首先将其更改为&#34; call&#34;,并为(;;)添加无限;循环在main函数的末尾。

这里的目标是以32位编译和链接你的内核,以便它可以在32位保护模式下调用。 因此,将机器选项设置为gcc命令的-m32并添加&#34; -m elf_i386&#34;告诉。通过删除&#34; - oformat binary&#34;使用默认格式elf。选项。