I am currently studying low level organization of operating systems. In order to achive that I am trying to understand how Linux kernel is loaded.
A thing that I cannot comprehend is the transition from 16-bit (real mode) to 32-bit (protected mode). It happens in this file.
The protected_mode_jump
function performs various auxiliary calculations for 32-bit code that is executed later, then enables PE
bit in the CR0
reguster
movl %cr0, %edx
orb $X86_CR0_PE, %dl # Protected mode
movl %edx, %cr0
and after that performs long jump to 32-bit code:
# Transition to 32-bit mode
.byte 0x66, 0xea # ljmpl opcode
2: .long in_pm32 # offset
.word __BOOT_CS # segment
As far as I understand in_pm32
is the address of the 32-bit function which is declared right below the protected_mode_jump
:
.code32
.section ".text32","ax"
GLOBAL(in_pm32)
# some code
# ...
# some code
ENDPROC(in_pm32)
The __BOOT_CS
sector base is 0 (the GDT is set beforehand here), so that means that offset should be basically absolute address of the in_pm32
function.
That's the issue. During machine code generation the assembler/linker should not know the absolute address of the in_pm32
function, because it does not know where it will be loaded in the memory in the real mode (various bootloaders can occupy various amounts of space, and the real mode kernel is loaded just after a bootloader).
Moreover, the linker script (setup.ld
in the same folder) sets the origin of the code as 0, so seems like in_pm32
address will be the offset from the beginning of the real mode kernel. It should work just fine with 16-bit code because CS
register is set properly, but when long jump happens the CPU is already in protected mode, so a relative offset should not work.
So my question:
Why does the long jump in Protected Mode (.byte 0x66, 0xea
) sets the proper code position if the offset (.long in_pm32
) is relative?
Seems like I am missing something really important.
__BOOT_CS
selector base is 0. I suppose that it means that FAR JUMP should take the absolute address of desired function/label (because0 + offset
will be justoffset
). But FAR JUMP here is called with relative offset (.long in_pm32
is the address of thein_mp32
function from the beginning on the real mode kernel binary) - and I don't understand why at the end thein_pm32
function is executed. The far jump should be mismatched on 0x7C00 + bootloader_size bytes. – Thill2: .long in_pm32 # offset
will computein_pm32
relative to the beginning of the section in the linker script (which you seem to understand). That value changes at runtime with these instructionsmovw %cs, %bx
shll $4, %ebx
addl %ebx, 2f
These instruction take the current CS register and convert it into a linear address and that is added to the value AT label2f
.2f
is in fact this2: .long in_pm32
. At runtime that offset is being adjusted before the JMP is made. – Jessalin2
in memory which is the FAR JMP itself. – Jessalin