Assembly 从程序集引导加载程序调用C内核
编辑:请跳到我下面的第二篇文章 我正在寻找一种从引导加载程序进入内核的最简单方式。 你有没有任何可行的例子 以下是进入保护模式的引导加载程序: “boot.asm” 这里是我希望从引导加载程序调用的内核入口点: “kernel.c” 谢谢你的帮助 到目前为止,这是我试图做的,但不起作用: 我只是用ld将内核放到0x8000,用cat连接,然后从引导加载程序跳到0x8000Assembly 从程序集引导加载程序调用C内核,assembly,kernel,Assembly,Kernel,编辑:请跳到我下面的第二篇文章 我正在寻找一种从引导加载程序进入内核的最简单方式。 你有没有任何可行的例子 以下是进入保护模式的引导加载程序: “boot.asm” 这里是我希望从引导加载程序调用的内核入口点: “kernel.c” 谢谢你的帮助 到目前为止,这是我试图做的,但不起作用: 我只是用ld将内核放到0x8000,用cat连接,然后从引导加载程序跳到0x8000 nasm boot.asm -f bin -o boot.bin gcc -ffreestanding -c kernel.
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
以下是我在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
[bits 32]
mov al, 'K'
mov ah, 3 ; cyan
mov edx, 0xb8000
mov [edx], ax
jmp $
[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
kernel.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
[bits 32]
mov al, 'K'
mov ah, 3 ; cyan
mov edx, 0xb8000
mov [edx], ax
jmp $
[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
我构建软盘映像,以便:
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位
但它不起作用。。。。。。请帮忙 哦,这肯定管用。这似乎是你对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!好了 这里是一个进入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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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
内核的加载是在实模式下完成的,即使用[segment base:offset]寻址。要在保护模式下访问相同位置的缓冲区地址(KERNEL_OFFSET),请向左移动内存地址值4次。因此,跳转到0x10000而不是0x1000。我建议您先尝试使用“jmp”,然后再将其更改为“call”,并为(;;)添加无穷大;在主函数的末尾循环 这里的目标是以32位编译和链接内核,以便它可以在32位保护模式下调用。
因此,将machine选项设置为gcc命令的-m32,并将“-m elf_i386”添加到ld。通过删除“-oformat binary”选项,也可以使用默认格式elf。
jmp,您在哪里加载了主文件?我真的希望你把它加载到某个地方,在你的代码中看不到:)你可能想把main()
变成一个循环,否则你可能在计算机运行main()
几秒钟后,在某个地方执行内存之前看不到任何东西。是的,或者使用call
并保留jmp$
。我仍然看不到您实际加载内核的位置。你知道BIOS只加载第一个扇区,对吗?好的,我明白了,实际上我需要添加一个函数从磁盘读取内核。谢谢Jester你能解释一下我的代码有什么问题吗?我真的不明白。提前感谢通过将内核中的“X”替换为“X”解决了此问题。如果从LD中删除格式二进制文件的--of format binary
,则可能是在推断应该使用引导加载程序,使您进入保护模式并加载ELF映像(即GRUB),或者,您必须使用类似于OBJCOPY的东西从ELF图像中获取平面二进制文件。我通常从链接中排除格式二进制文件的--oformat binary
,这样我就可以生成一个带有调试信息的ELF映像,然后使用OBJCOPY获得最终的平面二进制文件。通过这种方式,我可以使用QEMU之类的远程符号调试。我想我是世界上唯一一个在完全按照您所做的尝试时得到完整错误列表的人:'(