|
@@ -70,26 +70,26 @@ The starting address is formed by adding the base address to the value in the EI
|
|
|
We get `0xfffffff0` which is 4GB - 16 bytes. This point is called the [Reset vector](http://en.wikipedia.org/wiki/Reset_vector). This is the memory location at which the CPU expects to find the first instruction to execute after reset. It contains a [jump](http://en.wikipedia.org/wiki/JMP_%28x86_instruction%29) instruction which usually points to the BIOS entry point. For example, if we look in the [coreboot](http://www.coreboot.org/) source code, we see:
|
|
|
|
|
|
```assembly
|
|
|
- .section ".reset"
|
|
|
- .code16
|
|
|
-.globl reset_vector
|
|
|
+ .section ".reset"
|
|
|
+ .code16
|
|
|
+.globl reset_vector
|
|
|
reset_vector:
|
|
|
- .byte 0xe9
|
|
|
- .int _start - ( . + 2 )
|
|
|
- ...
|
|
|
+ .byte 0xe9
|
|
|
+ .int _start - ( . + 2 )
|
|
|
+ ...
|
|
|
```
|
|
|
|
|
|
Here we can see the jmp instruction [opcode](http://ref.x86asm.net/coder32.html#xE9) - 0xe9 and its destination address - `_start - ( . + 2)`, and we can see that the `reset` section is 16 bytes and starts at `0xfffffff0`:
|
|
|
|
|
|
```
|
|
|
SECTIONS {
|
|
|
- _ROMTOP = 0xfffffff0;
|
|
|
- . = _ROMTOP;
|
|
|
- .reset . : {
|
|
|
- *(.reset)
|
|
|
- . = 15 ;
|
|
|
- BYTE(0x00);
|
|
|
- }
|
|
|
+ _ROMTOP = 0xfffffff0;
|
|
|
+ . = _ROMTOP;
|
|
|
+ .reset . : {
|
|
|
+ *(.reset)
|
|
|
+ . = 15 ;
|
|
|
+ BYTE(0x00);
|
|
|
+ }
|
|
|
}
|
|
|
```
|
|
|
|
|
@@ -191,15 +191,15 @@ Now that the BIOS has chosen a boot device and transferred control to the boot s
|
|
|
As we can read in the kernel boot protocol, the bootloader must read and fill some fields of the kernel setup header, which starts at `0x01f1` offset from the kernel setup code. The kernel header [arch/x86/boot/header.S](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S) starts from:
|
|
|
|
|
|
```assembly
|
|
|
- .globl hdr
|
|
|
+ .globl hdr
|
|
|
hdr:
|
|
|
- setup_sects: .byte 0
|
|
|
- root_flags: .word ROOT_RDONLY
|
|
|
- syssize: .long 0
|
|
|
- ram_size: .word 0
|
|
|
- vid_mode: .word SVGA_MODE
|
|
|
- root_dev: .word 0
|
|
|
- boot_flag: .word 0xAA55
|
|
|
+ setup_sects: .byte 0
|
|
|
+ root_flags: .word ROOT_RDONLY
|
|
|
+ syssize: .long 0
|
|
|
+ ram_size: .word 0
|
|
|
+ vid_mode: .word SVGA_MODE
|
|
|
+ root_dev: .word 0
|
|
|
+ boot_flag: .word 0xAA55
|
|
|
```
|
|
|
|
|
|
The bootloader must fill this and the rest of the headers (only marked as `write` in the Linux boot protocol, for example [this](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt#L354)) with values which it either got from command line or calculated. We will not see a description and explanation of all fields of the kernel setup header, we will get back to that when the kernel uses them. You can find a description of all fields in the [boot protocol](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt#L156).
|
|
@@ -270,8 +270,8 @@ Actually `header.S` starts from [MZ](https://en.wikipedia.org/wiki/DOS_MZ_execut
|
|
|
...
|
|
|
...
|
|
|
pe_header:
|
|
|
- .ascii "PE"
|
|
|
- .word 0
|
|
|
+ .ascii "PE"
|
|
|
+ .word 0
|
|
|
```
|
|
|
|
|
|
It needs this to load an operating system with [UEFI](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface). We won't see how this works right now, we'll see this in one of the next chapters.
|
|
@@ -298,14 +298,14 @@ The bootloader (grub2 and others) knows about this point (`0x200` offset from `M
|
|
|
So the kernel setup entry point is:
|
|
|
|
|
|
```assembly
|
|
|
- .globl _start
|
|
|
+ .globl _start
|
|
|
_start:
|
|
|
- .byte 0xeb
|
|
|
- .byte start_of_setup-1f
|
|
|
+ .byte 0xeb
|
|
|
+ .byte start_of_setup-1f
|
|
|
1:
|
|
|
- //
|
|
|
- // rest of the header
|
|
|
- //
|
|
|
+ //
|
|
|
+ // rest of the header
|
|
|
+ //
|
|
|
```
|
|
|
|
|
|
Here we can see a `jmp` instruction opcode - `0xeb` to the `start_of_setup-1f` point. `Nf` notation means `2f` refers to the next local `2:` label. In our case it is label `1` which goes right after jump. It contains the rest of the setup [header](https://github.com/torvalds/linux/blob/master/Documentation/x86/boot.txt#L156). Right after the setup header we see the `.entrytext` section which starts at the `start_of_setup` label.
|
|
@@ -313,8 +313,8 @@ Here we can see a `jmp` instruction opcode - `0xeb` to the `start_of_setup-1f` p
|
|
|
Actually this is the first code that runs (aside from the previous jump instruction of course). After the kernel setup got the control from the bootloader, the first `jmp` instruction is located at `0x200` (first 512 bytes) offset from the start of the kernel real mode. This we can read in the Linux kernel boot protocol and also see in the grub2 source code:
|
|
|
|
|
|
```C
|
|
|
- state.gs = state.fs = state.es = state.ds = state.ss = segment;
|
|
|
- state.cs = segment + 0x20;
|
|
|
+state.gs = state.fs = state.es = state.ds = state.ss = segment;
|
|
|
+state.cs = segment + 0x20;
|
|
|
```
|
|
|
|
|
|
It means that segment registers will have the following values after kernel setup starts:
|
|
@@ -341,25 +341,25 @@ Segment registers align
|
|
|
First of all it ensures that `ds` and `es` segment registers point to the same address and clears the direction flag with the `cld` instruction:
|
|
|
|
|
|
```assembly
|
|
|
- movw %ds, %ax
|
|
|
- movw %ax, %es
|
|
|
- cld
|
|
|
+ movw %ds, %ax
|
|
|
+ movw %ax, %es
|
|
|
+ cld
|
|
|
```
|
|
|
|
|
|
As I wrote earlier, grub2 loads kernel setup code at address `0x10000` and `cs` at `0x1020` because execution doesn't start from the start of file, but from:
|
|
|
|
|
|
```assembly
|
|
|
_start:
|
|
|
- .byte 0xeb
|
|
|
- .byte start_of_setup-1f
|
|
|
+ .byte 0xeb
|
|
|
+ .byte start_of_setup-1f
|
|
|
```
|
|
|
|
|
|
`jump`, which is at 512 bytes offset from the [4d 5a](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L47). It also needs to align `cs` from `0x10200` to `0x10000` as all other segment registers. After that we set up the stack:
|
|
|
|
|
|
```assembly
|
|
|
- pushw %ds
|
|
|
- pushw $6f
|
|
|
- lretw
|
|
|
+ pushw %ds
|
|
|
+ pushw $6f
|
|
|
+ lretw
|
|
|
```
|
|
|
|
|
|
push `ds` value to the stack with the address of the [6](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L494) label and execute `lretw` instruction. When we call `lretw`, it loads address of label `6` into the [instruction pointer](https://en.wikipedia.org/wiki/Program_counter) register and `cs` with the value of `ds`. After this `ds` and `cs` will have the same values.
|
|
@@ -370,10 +370,10 @@ Stack Setup
|
|
|
Actually, almost all of the setup code is preparation for the C language environment in real mode. The next [step](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L467) is checking the `ss` register value and making a correct stack if `ss` is wrong:
|
|
|
|
|
|
```assembly
|
|
|
- movw %ss, %dx
|
|
|
- cmpw %ax, %dx
|
|
|
- movw %sp, %dx
|
|
|
- je 2f
|
|
|
+ movw %ss, %dx
|
|
|
+ cmpw %ax, %dx
|
|
|
+ movw %sp, %dx
|
|
|
+ je 2f
|
|
|
```
|
|
|
|
|
|
This can lead to 3 different scenarios:
|
|
@@ -387,12 +387,12 @@ Let's look at all three of these scenarios:
|
|
|
* `ss` has a correct address (0x10000). In this case we go to label [2](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L481):
|
|
|
|
|
|
```assembly
|
|
|
-2: andw $~3, %dx
|
|
|
- jnz 3f
|
|
|
- movw $0xfffc, %dx
|
|
|
-3: movw %ax, %ss
|
|
|
- movzwl %dx, %esp
|
|
|
- sti
|
|
|
+2: andw $~3, %dx
|
|
|
+ jnz 3f
|
|
|
+ movw $0xfffc, %dx
|
|
|
+3: movw %ax, %ss
|
|
|
+ movzwl %dx, %esp
|
|
|
+ sti
|
|
|
```
|
|
|
|
|
|
Here we can see the alignment of `dx` (contains `sp` given by bootloader) to 4 bytes and a check for whether or not it is zero. If it is zero, we put `0xfffc` (4 byte aligned address before maximum segment size - 64 KB) in `dx`. If it is not zero we continue to use `sp` given by the bootloader (0xf7f4 in my case). After this we put the `ax` value to `ss` which stores the correct segment address of `0x10000` and sets up a correct `sp`. We now have a correct stack:
|
|
@@ -402,23 +402,23 @@ Here we can see the alignment of `dx` (contains `sp` given by bootloader) to 4 b
|
|
|
* In the second scenario, (`ss` != `ds`). First of all put the [_end](https://github.com/torvalds/linux/blob/master/arch/x86/boot/setup.ld#L52) (address of end of setup code) value in `dx` and check the `loadflags` header field with the `testb` instruction to see whether we can use the heap or not. [loadflags](https://github.com/torvalds/linux/blob/master/arch/x86/boot/header.S#L321) is a bitmask header which is defined as:
|
|
|
|
|
|
```C
|
|
|
-#define LOADED_HIGH (1<<0)
|
|
|
-#define QUIET_FLAG (1<<5)
|
|
|
-#define KEEP_SEGMENTS (1<<6)
|
|
|
-#define CAN_USE_HEAP (1<<7)
|
|
|
+#define LOADED_HIGH (1<<0)
|
|
|
+#define QUIET_FLAG (1<<5)
|
|
|
+#define KEEP_SEGMENTS (1<<6)
|
|
|
+#define CAN_USE_HEAP (1<<7)
|
|
|
```
|
|
|
|
|
|
And as we can read in the boot protocol:
|
|
|
|
|
|
```
|
|
|
-Field name: loadflags
|
|
|
+Field name: loadflags
|
|
|
|
|
|
This field is a bitmask.
|
|
|
|
|
|
Bit 7 (write): CAN_USE_HEAP
|
|
|
- Set this bit to 1 to indicate that the value entered in the
|
|
|
- heap_end_ptr is valid. If this field is clear, some setup code
|
|
|
- functionality will be disabled.
|
|
|
+ Set this bit to 1 to indicate that the value entered in the
|
|
|
+ heap_end_ptr is valid. If this field is clear, some setup code
|
|
|
+ functionality will be disabled.
|
|
|
```
|
|
|
|
|
|
If the `CAN_USE_HEAP` bit is set, put `heap_end_ptr` in `dx` which points to `_end` and add `STACK_SIZE` (minimal stack size - 512 bytes) to it. After this if `dx` is not carry (it will not be carry, dx = _end + 512), jump to label `2` as in the previous case and make a correct stack.
|
|
@@ -435,8 +435,8 @@ BSS Setup
|
|
|
The last two steps that need to happen before we can jump to the main C code, are setting up the [BSS](https://en.wikipedia.org/wiki/.bss) area and checking the "magic" signature. First, signature checking:
|
|
|
|
|
|
```assembly
|
|
|
-cmpl $0x5a5aaa55, setup_sig
|
|
|
-jne setup_bad
|
|
|
+ cmpl $0x5a5aaa55, setup_sig
|
|
|
+ jne setup_bad
|
|
|
```
|
|
|
|
|
|
This simply compares the [setup_sig](https://github.com/torvalds/linux/blob/master/arch/x86/boot/setup.ld#L39) with the magic number `0x5a5aaa55`. If they are not equal, a fatal error is reported.
|
|
@@ -446,12 +446,12 @@ If the magic number matches, knowing we have a set of correct segment registers
|
|
|
The BSS section is used to store statically allocated, uninitialized data. Linux carefully ensures this area of memory is first blanked, using the following code:
|
|
|
|
|
|
```assembly
|
|
|
- movw $__bss_start, %di
|
|
|
- movw $_end+3, %cx
|
|
|
- xorl %eax, %eax
|
|
|
- subw %di, %cx
|
|
|
- shrw $2, %cx
|
|
|
- rep; stosl
|
|
|
+ movw $__bss_start, %di
|
|
|
+ movw $_end+3, %cx
|
|
|
+ xorl %eax, %eax
|
|
|
+ subw %di, %cx
|
|
|
+ shrw $2, %cx
|
|
|
+ rep; stosl
|
|
|
```
|
|
|
|
|
|
First of all the [__bss_start](https://github.com/torvalds/linux/blob/master/arch/x86/boot/setup.ld#L47) address is moved into `di` and the `_end + 3` address (+3 - aligns to 4 bytes) is moved into `cx`. The `eax` register is cleared (using a `xor` instruction), and the bss section size (`cx`-`di`) is calculated and put into `cx`. Then, `cx` is divided by four (the size of a 'word'), and the `stosl` instruction is repeatedly used, storing the value of `eax` (zero) into the address pointed to by `di`, automatically increasing `di` by four (this occurs until `cx` reaches zero). The net effect of this code is that zeros are written through all words in memory from `__bss_start` to `_end`:
|
|
@@ -464,7 +464,7 @@ Jump to main
|
|
|
That's all, we have the stack and BSS so we can jump to the `main()` C function:
|
|
|
|
|
|
```assembly
|
|
|
- calll main
|
|
|
+ calll main
|
|
|
```
|
|
|
|
|
|
The `main()` function is located in [arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c). You can read about what this does in the next part.
|