I'm using GNU GRUB version 2.04
bootloader and Linux Kernel 5.19-rc2
I'm debugging the Linux Kernel initialization and expected that right upon the Kernel startup the CPU should be in real mode. Actually as specified in the Intel System Programming Manual, Chapter 2
:
All Intel 64 and IA-32 processors enter real-address mode following a power-up or reset (see Chapter 9, “Processor Management and Initialization”). Software then initiates the switch from real-address mode to protected mode.
So I set breakpoint in GDB right at the first Linux Kernel function GRUB calls (which I beleive is secondary_startup_64
on x86-64) to debug Ubuntu 20.04 in QEMU. I expected PE
and PG
flags of CR0
to be disabled since there is no paging and no protected mode in real address mode. But here is what I got in gdb
:
Thread 2 hit Breakpoint 1, secondary_startup_64 () at arch/x86/kernel/head_64.S:147
147 call verify_cpu
(gdb) p $cr0
$1 = [ PG AM WP NE ET MP PE ]
As can be seen PG
and PE
flags are set which is unclear why. I see 2 possibilities:
secondary_startup_64
is not the first function of the Linux Kernel that GRUB calls and there is Kernel code executed before. If so what is the real entry point from GRUB to the Linux Kernel?"All Intel 64 and IA-32 processors enter real-address mode following a power-up or reset", and then the firmware starts (using one CPU), then it starts all the other CPUs, and all the CPUs get switched to protected mode or long mode (along with initializing a lot of stuff - memory, MTRRs, ACPI tables, ...); and all the CPUs except one are put back to sleep.
Eventually; if the firmware is BIOS it switches the CPU back to real mode and starts a boot loader; and if the firmware is UEFI it leaves the CPU in long mode and starts a boot loader. Either way GRUB is started.
If the firmware was BIOS then GRUB switches the CPU to protected mode (and then switches it from protected mode to real mode and back to protected mode each time it wants to use BIOS functions); does a bunch of stuff; figures out it's loading a 64-bit Linux kernel and switches to long mode. If the firmware is UEFI it just stays in long mode the whole time. Note that GRUB's code is mostly 32-bit code; and both protected mode and long mode allow you to run 32-bit code; so most of GRUB's code works for both protected mode and long mode.
Even later; after a huge amount of stuff has been done by firmware and GRUB, and possibly after the CPU's mode has been changed a few thousand times; GRUB passes control to Linux.
Now... Linux has several different entry points, where the boot loader uses whichever entry point suits it the most. This changes occasionally - e.g. very old versions of Linux has a floppy disk boot loader built into it (they got rid of a bit later), and modern versions of Linux can have a UEFI boot loader built into it. There's also a real mode entry point and a protected mode entry point (which are older) and a 64-bit entry point (which is newer). Of these GRUB probably just uses the 32-bit entry point if the firmware was BIOS, and the 64-bit entry point if the firmware was UEFI.
Most of these entry points (excluding the UEFI boot loader entry point) work as a chain - e.g. if the real mode entry point is used it switches to protect mode and jumps to the 32-bit entry point; and if the 32-bit entry point is used it sets up paging and long mode and jumps to the 64-bit entry point.
Your breakpoint is at/near the 64-bit entry point; so regardless of which entry point the boot loader used (the 32-bit one or the 64-bit one) you start looking at 64-bit code. 64-bit code requires long mode, and long mode requires paging; so PG and PE must be set.
Of course (for the 64-bit entry point) paging is likely configured so that all physical addresses are mapped to the identical virtual addresses, so even though paging is enabled it doesn't actually do anything. Kernel's 64-bit startup code would change the way paging is configured (map kernel into kernel space, etc).