2. Genesis
2.1 The boot code
OK, It’s time for some code! Although the brunt of our kernel will be written in C, there are certain things we just must use assembly for. One of those things is the initial boot code.
Here we go:
OK,是时候编写一些代码了!尽管首当其冲的内核将是用C编写的,但是有些事情我们只需要使用汇编即可。其中之一是初始引导代码。
开始了:
;
; boot.s -- Kernel start location. Also defines multiboot header.
; Based on Bran's kernel development tutorial file start.asm
;
MBOOT_PAGE_ALIGN equ 1<<0 ; Load kernel and modules on a page boundary
MBOOT_MEM_INFO equ 1<<1 ; Provide your kernel with memory info
MBOOT_HEADER_MAGIC equ 0x1BADB002 ; Multiboot Magic value
; NOTE: We do not use MBOOT_AOUT_KLUDGE. It means that GRUB does not
; pass us a symbol table.
MBOOT_HEADER_FLAGS equ MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO
MBOOT_CHECKSUM equ -(MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS)
[BITS 32] ; All instructions should be 32-bit.
[GLOBAL mboot] ; Make 'mboot' accessible from C.
[EXTERN code] ; Start of the '.text' section.
[EXTERN bss] ; Start of the .bss section.
[EXTERN end] ; End of the last loadable section.
mboot:
dd MBOOT_HEADER_MAGIC ; GRUB will search for this value on each
; 4-byte boundary in your kernel file
dd MBOOT_HEADER_FLAGS ; How GRUB should load your file / settings
dd MBOOT_CHECKSUM ; To ensure that the above values are correct
dd mboot ; Location of this descriptor
dd code ; Start of kernel '.text' (code) section.
dd bss ; End of kernel '.data' section.
dd end ; End of kernel.
dd start ; Kernel entry point (initial EIP).
[GLOBAL start] ; Kernel entry point.
[EXTERN main] ; This is the entry point of our C code
start:
push ebx ; Load multiboot header location
; Execute the kernel:
cli ; Disable interrupts.
call main ; call our main() function.
jmp $ ; Enter an infinite loop, to stop the processor
; executing whatever rubbish is in the memory
; after our kernel!
2.2 Understanding the boot code
There’s actually only a few lines of code in that snippet:
该代码段中实际上只有几行代码:
push ebx
cli
call main
jmp $
The rest of it is all to do with the multiboot header.
其余全部与多重引导头有关。
2.2.1 Multiboot
Multiboot is a standard to which GRUB expects a kernel to comply. It is a way for the bootloader to
- Know exactly what environment the kernel wants/needs when it boots.
- Allow the kernel to query the environment it is in.
So, for example, if your kernel needs to be loaded in a specific VESA mode (which is a bad idea, by the way), you can inform the bootloader of this, and it can take care of it for you.
To make your kernel multiboot compatible, you need to add a header structure somewhere in your kernel (Actually, the header must be in the first 4KB of the kernel). Usefully, there is a NASM command that lets us embed specific constants in our code - ‘dd’. These lines:
Multiboot是GRUB期望内核遵循的标准。这是Bootloader的一种方式
- 确切了解内核在启动时需要/需要的环境。
- 允许内核查询其所在的环境。
因此,例如,如果您的内核需要以特定的VESA模式进行加载(顺便说一句,这是个坏主意),则可以将此信息通知给引导加载程序,并且它可以为您解决这一问题。
为了使您的内核具有多重引导兼容性,您需要在内核中的某个位置添加头结构(实际上,头必须位于内核的前4KB中)。有用的是,有一个NASM命令可以让我们在代码中嵌入特定的常量“ dd”。这些行:
dd MBOOT_HEADER_MAGIC
dd MBOOT_HEADER_FLAGS
dd MBOOT_CHECKSUM
dd mboot
dd code
dd bss
dd end
dd start
Do just that. The MBOOT_* constants are defined above.
那样做。上面定义了MBOOT_ *常量。
MBOOT_HEADER_MAGIC
A magic number. This identifies the kernel as multiboot-compatible.MBOOT_HEADER_FLAGS
A field of flags. We ask for GRUB to page-align all kernel sections
(MBOOT_PAGE_ALIGN) and also to give us some memory information (MBOOT_MEM_INFO). Note that some tutorials also use MBOOT_AOUT_KLUDGE. As we are using the ELF file format, this hack is not necessary, and adding it stops GRUB giving you your symbol table when you boot up.MBOOT_CHECKSUM
This field is defined such that when the magic number, the flags and
this are added together, the total must be zero. It is for error checking.mboot
The address of the structure that we are currently writing. GRUB uses
this to tell if we are expecting to be relocated.code,bss,end,start
These symbols are all defined by the linker. We use them to tell GRUB
where the different sections of our kernel can be located.
MBOOT_HEADER_MAGIC
魔数。这将内核标识为兼容多引导。
MBOOT_HEADER_FLAGS
标志域。我们要求GRUB对所有内核部分进行页面对齐
(MBOOT_PAGE_ALIGN),并给我们一些内存信息(MBOOT_MEM_INFO)。请注意,某些教程还使用MBOOT_AOUT_KLUDGE。由于我们使用的是ELF文件格式,因此无需进行此修改,并且在启动时添加它会停止GRUB,为您提供符号表。MBOOT_CHECKSUM
定义此字段,以便当魔术数字,标志和
将这些加在一起,总数必须为零。用于错误检查。mboot
我们当前正在编写的结构的地址。 GRUB的用途
这可以告诉我们是否要搬迁。code,bss,end,start
这些符号全部由链接器定义。我们用它们来告诉GRUB
内核不同部分的位置。
On bootup, GRUB will load a pointer to another information structure into the EBX register. This can be used to query the environment GRUB set up for us.
在启动时,GRUB会将指向另一个信息结构的指针加载到EBX寄存器中。这可以用来查询为我们设置的环境GRUB。
2.3 Adding some C code
Interfacing C code and assembly is dead easy. You just have to know the calling convention used. GCC on x86 uses the __cdecl calling convention:
- All parameters to a function are passed on the stack.
- The parameters are pushed right-to-left.
- The return value of a function is returned in EAX.
…so the function call:
连接C代码和汇编非常容易。您只需要知道使用的调用约定即可。 x86上的GCC使用__cdecl调用约定:
- 函数的所有参数都在堆栈上传递。
- 参数从右到左推。
- 函数的返回值以EAX返回。
…因此函数调用:
d = func(a, b, c);
变成了:
push [c]
push [b]
push [a]
call func
mov [d], eax
See? nothing to it! So, you can see that in our asm snippet above, that ‘push ebx’ is actually passing the value of ebx as a parameter to the function main().
看到?什么都没有!因此,您可以看到在上面的asm代码片段中,“ push ebx”实际上是将ebx的值作为参数传递给函数main()。
2.3.1 The C code
// main.c -- Defines the C-code kernel entry point, calls initialisation routines.
// Made for JamesM's tutorials
int main(struct multiboot *mboot_ptr)
{
// All our initialisation calls will go in here.
return 0xDEADBABA;
}
Here’s our first incarnation of the main() function. As you can see, we’ve made it take one parameter - a pointer to a multiboot struct. We’ll define that later (we don’t actually need to define it for the code to compile!).
All the function does is return a constant - 0xDEADBABA. That constant is unusual enough that it should stand out at you when we run the program in a second.
这是main函数的第一个体现。如您所见,我们使它带有一个参数-指向多重引导结构的指针。稍后我们将对其进行定义(我们实际上不需要为编译代码而定义它!)。
该函数所做的全部工作就是返回一个常量-0xDEADBABA。这个常数非常不寻常,当我们在一秒钟内运行该程序时,它应该会在您面前脱颖而出。
2.4. Compiling, linking and running!
Now that we’ve added a new file to our project, we have to add it to the makefile also. Edit these lines:
现在,我们已经向项目添加了一个新文件,我们还必须将其添加到makefile中。编辑这些行:
SOURCES=boot.o
CFLAGS=
变成:
SOURCES=boot.o main.o
CFLAGS=-nostdlib -nostdinc -fno-builtin -fno-stack-protector -m32
注意,-m32是我加上的,如果你的系统是64位的,就需要这个命令。
We must stop GCC trying to link your linux C library with our kernel - it won’t work at all (yet). That’s what those CFLAGS are for.
OK, you should now be able to compile, link and run your kernel!
我们必须停止GCC尝试将您的linux C库链接到我们的内核——它根本不起作用(尚未)。那就是那些CFLAGS的目的。
好的,您现在应该可以编译,链接和运行内核了!
cd src
make clean # Ignore any errors here.
make
cd ..
./update_image.sh
./run_bochs.sh # This may ask your for your root password.
That should cause bochs to boot, you’ll see GRUB for a few seconds then the kernel will run. It doesn’t actually do anything, so it’ll just freeze, saying ‘starting up…’.
If you open bochsout.txt, at the bottom you should see something like:
最终bochs开始引导,您将看到GRUB几秒钟(别管什么GRUB),然后内核将运行。它实际上并没有执行任何操作,因此只会停住,并说“正在启动…”。
如果打开bochsout.txt,则在底部应显示以下内容:
00074621500i[CPU ] | EAX=deadbaba EBX=0002d000 ECX=0001edd0 EDX=00000001
00074621500i[CPU ] | ESP=00067ec8 EBP=00067ee0 ESI=00053c76 EDI=00053c77
00074621500i[CPU ] | IOPL=0 id vip vif ac vm rf nt of df if tf sf zf af pf cf
00074621500i[CPU ] | SEG selector base limit G D
00074621500i[CPU ] | SEG sltr(index|ti|rpl) base limit G D
00074621500i[CPU ] | CS:0008( 0001| 0| 0) 00000000 000fffff 1 1
00074621500i[CPU ] | DS:0010( 0002| 0| 0) 00000000 000fffff 1 1
00074621500i[CPU ] | SS:0010( 0002| 0| 0) 00000000 000fffff 1 1
00074621500i[CPU ] | ES:0010( 0002| 0| 0) 00000000 000fffff 1 1
00074621500i[CPU ] | FS:0010( 0002| 0| 0) 00000000 000fffff 1 1
00074621500i[CPU ] | GS:0010( 0002| 0| 0) 00000000 000fffff 1 1
00074621500i[CPU ] | EIP=00100027 (00100027)
00074621500i[CPU ] | CR0=0x00000011 CR1=0 CR2=0x00000000
00074621500i[CPU ] | CR3=0x00000000 CR4=0x00000000
00074621500i[CPU ] >> jmp .+0xfffffffe (0x00100027) : EBFE
Notice what the value of EAX is? 0xDEADBABA - the return value of main(). Congratulations, you now have a multiboot compatible assembly trampoline, and you’re ready to start printing to the screen!
注意到eax的值了吗? 0xDEADBABA,这正是main函数的返回值。祝贺你,你现在 have a multiboot compatible assembly trampoline(我实在看不懂他在说什么了),你已经准备好进行打印屏幕了。