好吧,我已经在程序集中编写了一个bootloader并试图从中加载一个C内核 .
这是引导程序:
bits 16
xor ax,ax
jmp 0x0000:boot
extern kernel_main
global boot
boot:
mov ah, 0x02 ; load second stage to memory
mov al, 1 ; numbers of sectors to read into memory
mov dl, 0x80 ; sector read from fixed/usb disk ;0 for floppy; 0x80 for hd
mov ch, 0 ; cylinder number
mov dh, 0 ; head number
mov cl, 2 ; sector number
mov bx, 0x8000 ; load into es:bx segment :offset of buffer
int 0x13 ; disk I/O interrupt
mov ax, 0x2401
int 0x15 ; enable A20 bit
mov ax, 0x3
int 0x10 ; set vga text mode 3
cli
lgdt [gdt_pointer] ; load the gdt table
mov eax, cr0
or eax,0x1 ; set the protected mode bit on special CPU reg cr0
mov cr0, eax
jmp CODE_SEG:boot2 ; long jump to the code segment
gdt_start:
dq 0x0
gdt_code:
dw 0xFFFF
dw 0x0
db 0x0
db 10011010b
db 11001111b
db 0x0
gdt_data:
dw 0xFFFF
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
gdt_pointer:
dw gdt_end - gdt_start
dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
bits 32
boot2:
mov ax, DATA_SEG
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
; mov esi,hello
; mov ebx,0xb8000
;.loop:
; lodsb
; or al,al
; jz haltz
; or eax,0x0100
; mov word [ebx], ax
; add ebx,2
; jmp .loop
;haltz:
;hello: db "Hello world!",0
mov esp,kernel_stack_top
jmp kernel_main
cli
hlt
times 510 -($-$$) db 0
dw 0xaa55
section .bss
align 4
kernel_stack_bottom: equ $
resb 16384 ; 16 KB
kernel_stack_top:
这是C内核:
__asm__("cli\n");
void kernel_main(void){
const char string[] = "012345678901234567890123456789012345678901234567890123456789012";
volatile unsigned char* vid_mem = (unsigned char*) 0xb8000;
int j=0;
while(string[j]!='\0'){
*vid_mem++ = (unsigned char) string[j++];
*vid_mem++ = 0x09;
}
for(;;);
}
现在我将两个源分别编译为ELF输出文件 . 并通过链接描述文件链接它们并输出原始二进制文件并使用qemu加载它 .
链接脚本:
ENTRY(boot)
OUTPUT_FORMAT("binary")
SECTIONS{
. = 0x7c00;
.boot1 : {
*(.boot)
}
.kernel : AT(0x7e00){
*(.text)
*(.rodata)
*(.data)
_bss_start = .;
*(.bss)
*(COMMON)
_bss_end = .;
*(.comment)
*(.symtab)
*(.shstrtab)
*(.strtab)
}
/DISCARD/ : {
*(.eh_frame)
}
}
使用构建脚本:
nasm -f elf32 boot.asm -o boot.o
/home/rakesh/Desktop/cross-compiler/i686-elf-4.9.1-Linux-x86_64/bin/i686-elf-gcc -m32 kernel.c -o kernel.o -e kernel_main -Ttext 0x0 -nostdlib -ffreestanding -std=gnu99 -mno-red-zone -fno-exceptions -nostdlib -Wall -Wextra
/home/rakesh/Desktop/cross-compiler/i686-elf-4.9.1-Linux-x86_64/bin/i686-elf-ld boot.o kernel.o -o kernel.bin -T linker3.ld
qemu-system-x86_64 kernel.bin
但我遇到了一个小问题 . 注意C内核中的字符串
const char string[] = "012345678901234567890123456789012345678901234567890123456789012";
当它的大小等于或小于64字节时(以及空终止) . 然后程序正常工作 .
但是当字符串大小从64字节增加时,程序似乎不起作用
我试图自己调试它并观察到当字符串大小小于或等于64字节然后输出ELF文件时,kernel.o有以下内容:
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x1
Start of program headers: 52 (bytes into file)
Start of section headers: 4412 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 1
Size of section headers: 40 (bytes)
Number of section headers: 7
Section header string table index: 4
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 001000 0000bd 00 AX 0 0 1
[ 2] .eh_frame PROGBITS 000000c0 0010c0 000034 00 A 0 0 4
[ 3] .comment PROGBITS 00000000 0010f4 000011 01 MS 0 0 1
[ 4] .shstrtab STRTAB 00000000 001105 000034 00 0 0 1
[ 5] .symtab SYMTAB 00000000 001254 0000a0 10 6 6 4
[ 6] .strtab STRTAB 00000000 0012f4 00002e 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
There are no section groups in this file.
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x001000 0x00000000 0x00000000 0x000f4 0x000f4 R E 0x1000
Section to Segment mapping:
Segment Sections...
00 .text .eh_frame
There is no dynamic section in this file.
There are no relocations in this file.
The decoding of unwind sections for machine type Intel 80386 is not currently supported.
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 SECTION LOCAL DEFAULT 1
2: 000000c0 0 SECTION LOCAL DEFAULT 2
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 FILE LOCAL DEFAULT ABS kernel.c
5: 00000000 0 FILE LOCAL DEFAULT ABS
6: 00000001 188 FUNC GLOBAL DEFAULT 1 kernel_main
7: 000010f4 0 NOTYPE GLOBAL DEFAULT 2 __bss_start
8: 000010f4 0 NOTYPE GLOBAL DEFAULT 2 _edata
9: 000010f4 0 NOTYPE GLOBAL DEFAULT 2 _end
No version information found in this file.
但是,当字符串的大小超过64个字节时,内容如下所示:
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x1
Start of program headers: 52 (bytes into file)
Start of section headers: 4432 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 1
Size of section headers: 40 (bytes)
Number of section headers: 8
Section header string table index: 5
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 001000 000083 00 AX 0 0 1
[ 2] .rodata PROGBITS 00000084 001084 000041 00 A 0 0 4
[ 3] .eh_frame PROGBITS 000000c8 0010c8 000038 00 A 0 0 4
[ 4] .comment PROGBITS 00000000 001100 000011 01 MS 0 0 1
[ 5] .shstrtab STRTAB 00000000 001111 00003c 00 0 0 1
[ 6] .symtab SYMTAB 00000000 001290 0000b0 10 7 7 4
[ 7] .strtab STRTAB 00000000 001340 00002e 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
There are no section groups in this file.
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x001000 0x00000000 0x00000000 0x00100 0x00100 R E 0x1000
Section to Segment mapping:
Segment Sections...
00 .text .rodata .eh_frame
There is no dynamic section in this file.
There are no relocations in this file.
The decoding of unwind sections for machine type Intel 80386 is not currently supported.
Symbol table '.symtab' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 SECTION LOCAL DEFAULT 1
2: 00000084 0 SECTION LOCAL DEFAULT 2
3: 000000c8 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 FILE LOCAL DEFAULT ABS kernel.c
6: 00000000 0 FILE LOCAL DEFAULT ABS
7: 00000001 130 FUNC GLOBAL DEFAULT 1 kernel_main
8: 00001100 0 NOTYPE GLOBAL DEFAULT 3 __bss_start
9: 00001100 0 NOTYPE GLOBAL DEFAULT 3 _edata
10: 00001100 0 NOTYPE GLOBAL DEFAULT 3 _end
No version information found in this file.
我注意到字符串现在位于.rodata部分,大小为41十六进制或65字节,必须映射到一个段,可能是第0个段,即NULL . 并且该程序无法找到.rodata .
我无法使它工作 . 我理解ELF结构,但我不知道如何使用它们 .
假设我是初学者,对低级编程知之甚少 .
1 回答
导致大多数问题的两个严重问题是:
当所有代码都希望在引导加载程序位于0x0000之后加载内核时,将磁盘的第二个扇区加载到0x0000:0x8000:0x7e00
您将
kernel.c
直接编译为可执行文件名kernel.o
. 您应该将其编译为正确的目标文件,以便在运行ld
时可以通过预期的链接阶段 .要解决内核在错误的内存位置加载到内存中的问题,请更改:
至:
要解决将
kernel.c
编译为名为kernel.o
的可执行ELF文件的问题,请删除-e kernel_main -Ttext 0x0
并将其替换为-c
.-c
选项强制GCC生成可与LD正确链接的目标文件 . 更改:至:
更长字符串失败的原因
小于64字节的字符串工作原因是因为编译器通过使用立即值初始化堆栈上的数组而以位置无关的方式生成代码 . 当大小达到64字节时,编译器将字符串放入
.rodata
部分,然后通过从.rodata
复制它来初始化堆栈上的数组 . 这使您的代码位置依赖 . 您的代码以错误的偏移量加载,并且原点不正确,导致代码引用错误的地址,因此失败了 .其他观察
在调用
kernel_main
之前,应将BSS(.bss
)部分初始化为0 . 这可以通过迭代从偏移_bss_start
到偏移_bss_end
的所有字节来在汇编中完成 ..comment
部分将被发送到二进制文件中,因此会浪费字节 . 你应该把它放在/DISCARD/
部分 .您应该将BSS部分放在链接器脚本中,而不是占用
kernel.bin
中的空间在
boot.asm
中,您应该在读取磁盘扇区之前将SS:SP(堆栈指针)设置在开头附近 . 应将其设置为一个知道BIOS放置当前堆栈的位置 . 您不希望在当前堆栈区域的顶部读取 . 将它设置在引导加载程序正下方0x0000:0x7c00应该可以工作 .在调用C代码之前,应清除方向标志以确保字符串指令使用向前移动 . 您可以使用CLD指令执行此操作 .
在
boot.asm
中,您可以通过使用DL寄存器中BIOS传递的引导驱动器编号使代码更通用,而不是将其硬编码为值0x80
(0x80是第一个硬盘驱动器)您可以考虑使用
-O3
启用优化,或使用优化级别-Os
来优化代码大小 .虽然链接器脚本产生了正确的结果,但它并不像您期望的那样工作 . 您从未在NASM文件中声明
.boot
部分,因此实际上没有任何内容放在链接描述文件的.boot1
输出部分中 . 它的工作原理是因为它包含在.kernel
输出部分的.text
部分中 .最好从程序集文件中删除填充和引导签名,并将其移动到链接描述文件
不是让链接器脚本直接输出二进制文件,而是输出到默认的ELF可执行格式更有用 . 然后,您可以使用OBJCOPY将ELF文件转换为二进制文件 . 这允许您使用将作为ELF可执行文件的一部分显示的调试信息进行构建 . ELF可执行文件可用于象征性地调试QEMU中的二进制内核 .
不是直接使用LD进行链接,而是使用GCC . 这样做的好处是可以添加
libgcc
库而无需指定库的完整路径 .libgcc
是使用GCC生成C代码可能需要的一组例程修改了源代码,链接器脚本和构建命令,并考虑了上述观察结果:
boot.asm :
kernel.c :
linker3.ld :
用于构建此引导加载程序和内核的命令:
要使用QEMU象征性地调试32位内核,您可以通过以下方式启动QEMU:
这将在QEMU中启动
kernel.bin
文件,然后远程连接GDB调试器 . 布局应显示源代码并在kernel_main
上中断 .