diff --git a/Booting/linux-bootstrap-1.md b/Booting/linux-bootstrap-1.md index 0450500..d41ecb9 100644 --- a/Booting/linux-bootstrap-1.md +++ b/Booting/linux-bootstrap-1.md @@ -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.