From cbf4534eca1d8ca5b0686e5162a2d144833a7ba8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 1 Feb 2016 19:28:18 +0100 Subject: [PATCH 1/6] Explain `cmp`, `jmp` and `jne` instructions more detailed --- posts/2015-08-25-entering-longmode.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/posts/2015-08-25-entering-longmode.md b/posts/2015-08-25-entering-longmode.md index 00072a33..3104cb03 100644 --- a/posts/2015-08-25-entering-longmode.md +++ b/posts/2015-08-25-entering-longmode.md @@ -87,9 +87,12 @@ check_multiboot: mov al, "0" jmp error ``` -We compare the value in `eax` with the magic value and jump to the label `no_multiboot` if they're not equal (`jne` – “jump if not equal”). In `no_multiboot`, we jump to the error function with error code `0`. +We use the `cmp` instruction to compare the value in `eax` to the magic value. If the values are equal, the `cmp` instruction sets the zero flag in the [FLAGS register]. The `jne` (“jump if not equal”) instruction reads this zero flag and jumps to the given address if it's not set. Thus we jump to the `.no_multiboot` label if `eax` does not contain the magic value. + +In `no_multiboot`, we use the `jmp` (“jump”) instruction to jump to our error function. We could just as well use the `call` instruction, which additionally pushes the return address. But the return address is not needed because `error` never returns. To pass `0` as error code to the `error` function, we move it into `al` before the jump (`error` will read it from there). [Multiboot specification]: http://nongnu.askapache.com/grub/phcoder/multiboot.pdf +[FLAGS register]: https://en.wikipedia.org/wiki/FLAGS_register ### CPUID check [CPUID] is a CPU instruction that can be used to get various information about the CPU. But not every processor supports it. CPUID detection is quite laborious, so we just copy a detection function from the [OSDev wiki][CPUID detection]: From b9c94baaaa7e066f1cf4433c677c3e4726a9b78c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 1 Feb 2016 23:40:45 +0100 Subject: [PATCH 2/6] Explain the CPUID check --- posts/2015-08-25-entering-longmode.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/posts/2015-08-25-entering-longmode.md b/posts/2015-08-25-entering-longmode.md index 3104cb03..88aee1f8 100644 --- a/posts/2015-08-25-entering-longmode.md +++ b/posts/2015-08-25-entering-longmode.md @@ -130,13 +130,18 @@ check_cpuid: ; Compare EAX and ECX. If they are equal then that means the bit wasn't ; flipped, and CPUID isn't supported. - xor eax, ecx - jz .no_cpuid + cmp eax, ecx + je .no_cpuid ret .no_cpuid: mov al, "1" jmp error ``` +Basically, the `CPUID` instruction is supported if we can flip some bit in the [FLAGS register]. We can't operate on the flags register directly, so we need to load it into some general purpose register such as `eax` first. The only way to do this is to push the `FLAGS` register on the stack through the `pushfd` instruction and then pop it into `eax`. Equally, we write it back through `push ecx` and `popfd`. To flip the bit we use the `xor` instruction to perform an [exclusive OR]. Finally we compare the two values and jump to `.no_cpuid` if both are equal (`je` – “jump if equal”). The `.no_cpuid` code just jumps to the `error` function with error code `1`. + +Don't worry, you don't need to understand the details. + +[exclusive OR]: https://en.wikipedia.org/wiki/Exclusive_or ### Long Mode check Now we can use CPUID to detect whether long mode can be used. I use code from [OSDev][long mode detection] again: From c2c863c7f3d8cea70b3c11b9f073aa88f5224a75 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 1 Feb 2016 23:42:09 +0100 Subject: [PATCH 3/6] Use `cmp` and `je` instead of `xor` and `jz` --- src/arch/x86_64/boot.asm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arch/x86_64/boot.asm b/src/arch/x86_64/boot.asm index 7ee4c9e7..58bbed04 100644 --- a/src/arch/x86_64/boot.asm +++ b/src/arch/x86_64/boot.asm @@ -139,8 +139,8 @@ check_cpuid: ; Compare EAX and ECX. If they are equal then that means the bit wasn't ; flipped, and CPUID isn't supported. - xor eax, ecx - jz .no_cpuid + cmp eax, ecx + je .no_cpuid ret .no_cpuid: mov al, "1" From d6efe62c80ddaa5bae072837d273a17a66d029f6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 1 Feb 2016 23:57:09 +0100 Subject: [PATCH 4/6] Add some explanation for the long mode check --- posts/2015-08-25-entering-longmode.md | 1 + 1 file changed, 1 insertion(+) diff --git a/posts/2015-08-25-entering-longmode.md b/posts/2015-08-25-entering-longmode.md index 88aee1f8..7aab5861 100644 --- a/posts/2015-08-25-entering-longmode.md +++ b/posts/2015-08-25-entering-longmode.md @@ -163,6 +163,7 @@ check_long_mode: mov al, "2" jmp error ``` +It tries to invoke a CPUID function to test if the long mode is available. But this function is not part of CPUIDs core functions, therefore we need to test if the so-called extended functions are available before. So the first `cpuid` call tests whether the function is available that checks long mode support. And then the second `cpuid` call uses that function to test whether long mode support is available. Whew! ### Putting it together We just call these check functions right after start: From bb4d64dc99baa4a8b72ca2a914afc87253f901cd Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 2 Feb 2016 23:09:44 +0100 Subject: [PATCH 5/6] Extend explanation for long mode test and improve code comments --- posts/2015-08-25-entering-longmode.md | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/posts/2015-08-25-entering-longmode.md b/posts/2015-08-25-entering-longmode.md index 7aab5861..1631da10 100644 --- a/posts/2015-08-25-entering-longmode.md +++ b/posts/2015-08-25-entering-longmode.md @@ -150,20 +150,27 @@ Now we can use CPUID to detect whether long mode can be used. I use code from [O ```nasm check_long_mode: - mov eax, 0x80000000 ; Set the A-register to 0x80000000. - cpuid ; CPU identification. - cmp eax, 0x80000001 ; Compare the A-register with 0x80000001. - jb .no_long_mode ; It is less, there is no long mode. - mov eax, 0x80000001 ; Set the A-register to 0x80000001. - cpuid ; CPU identification. - test edx, 1 << 29 ; Test if the LM-bit is set in the D-register. - jz .no_long_mode ; They aren't, there is no long mode. + ; test if extended processor info in available + mov eax, 0x80000000 ; implicit argument for cpuid + cpuid ; get highest supported argument + cmp eax, 0x80000001 ; it needs to be at least 0x80000001 + jb .no_long_mode ; if it's less, the CPU is too old for long mode + + ; use extended info to test if long mode is available + mov eax, 0x80000001 ; argument for extended processor info + cpuid ; returns various feature bits in ecx and edx + test edx, 1 << 29 ; test if the LM-bit is set in the D-register + jz .no_long_mode ; If it's not set, there is no long mode ret .no_long_mode: mov al, "2" jmp error ``` -It tries to invoke a CPUID function to test if the long mode is available. But this function is not part of CPUIDs core functions, therefore we need to test if the so-called extended functions are available before. So the first `cpuid` call tests whether the function is available that checks long mode support. And then the second `cpuid` call uses that function to test whether long mode support is available. Whew! +Like many low-level things, CPUID is a bit strange. Instead of taking a parameter, the `cpuid` instruction implicitely uses the `eax` register as argument. To test if long mode is available, we need to call `cpuid` with `0x80000001` in `eax`. This loads some information to the `ecx` and `edx` registers. Long mode is supported if the 29th bit in `edx` is set. [Wikipedia][cpuid long mode] has detailed information. + +[cpuid long mode]: https://en.wikipedia.org/wiki/CPUID#EAX.3D80000001h:_Extended_Processor_Info_and_Feature_Bits + +If you look at the assembly above, you'll probably notice that we call `cpuid` twice. The reason is that the CPUID command started with only a few functions and was extended over time. So old processors may not know the `0x80000001` argument at all. To test if they do, we need to invoke `cpuid` with `0x80000000` in `eax` first. It returns the highest supported parameter value in `eax`. If it's at least `0x80000001`, we can test for long mode as described above. Else the CPU is old and doesn't know what long mode is either. In that case, we directly jump to `.no_long_mode` through the `jb` instruction (“jump if below”). ### Putting it together We just call these check functions right after start: From de2305038aa7a856c10c3c1c002e32ec396cb489 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 2 Feb 2016 23:11:19 +0100 Subject: [PATCH 6/6] Improve comments in code as well --- src/arch/x86_64/boot.asm | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/arch/x86_64/boot.asm b/src/arch/x86_64/boot.asm index 58bbed04..64d0c0db 100644 --- a/src/arch/x86_64/boot.asm +++ b/src/arch/x86_64/boot.asm @@ -148,14 +148,17 @@ check_cpuid: ; Throw error 2 if the CPU doesn't support Long Mode. check_long_mode: - mov eax, 0x80000000 ; Set the A-register to 0x80000000. - cpuid ; CPU identification. - cmp eax, 0x80000001 ; Compare the A-register with 0x80000001. - jb .no_long_mode ; It is less, there is no long mode. - mov eax, 0x80000001 ; Set the A-register to 0x80000001. - cpuid ; CPU identification. - test edx, 1 << 29 ; Test if the LM-bit, which is bit 29, is set in the D-register. - jz .no_long_mode ; They aren't, there is no long mode. + ; test if extended processor info in available + mov eax, 0x80000000 ; implicit argument for cpuid + cpuid ; get highest supported argument + cmp eax, 0x80000001 ; it needs to be at least 0x80000001 + jb .no_long_mode ; if it's less, the CPU is too old for long mode + + ; use extended info to test if long mode is available + mov eax, 0x80000001 ; argument for extended processor info + cpuid ; returns various feature bits in ecx and edx + test edx, 1 << 29 ; test if the LM-bit is set in the D-register + jz .no_long_mode ; If it's not set, there is no long mode ret .no_long_mode: mov al, "2"