How does x86 eflags bit 18 (alignment check) work? (Related to check for 386 vs. 486 and later.)
Asked Answered
S

2

5

I've read that if eflags bit 18 (AC - alignment check) can be modified, you know the CPU is a 486 or newer. On the 386, the bit resists modification.

I've lifted the following assembly code from this site and added exhaustive comments (leaving the odd syntax intact):

asm
    mov  bx,sx            ; Save the stack pointer to bx (is sx a typo or a real register?).
    and  sp,$fffc         ; Truncate the stack pointer to a 4-byte boundary.
    pushfl                ; Push the eflags register to the stack.
    pop  eax              ; Pop it into eax.
    mov  ecx,eax          ; Save the original eflags value into ecx.
    xor  eax,$40000       ; Flip bit 18 in eax.
    push eax              ; Push eax to the stack.
    popfl                 ; Pop modified value into eflags.
    pushfl                ; Push eflags back onto the stack.
    pop  eax              ; Pop it into eax.
    xor  eax,ecx          ; Get changed bits from the original value.
    setz al               ; Set al register to 1 if no bits changed (0 otherwise).
    and  sp,$fffc         ; Truncate the stack pointer to a 4-byte boundary.
    push ecx              ; Push ecx (original eflags) to stack.
    popfl                 ; Pop it into eflags to restore the original value.
    mov  sp,bx            ; Restore the original stack pointer.
end ['eax','ebx','ecx'];

The CPU is a 386 if the al register is set to 1 at the end (assuming from the start that it's not older), and it's a 486 or newer otherwise. I understand this part.

What I don't understand is, why does the stack pointer have to be truncated to a 4-byte boundary before doing the flag modification test? I assume that it's meant to set bit 18, since it's the alignment bit after all...but the xor with 0x40000 will flip the bit regardless of its value. In other words, the modification test should have the same result regardless of the initial value, right?

If the answer is no, my best [uneducated] guess as to "why" is, "Maybe the following push/pop instructions could force alignment? This would align a previously unaligned stack pointer and cause the alignment bit to flip from 0 to 1 by itself. In that case, a successful modification would appear unsuccessful, and vice versa." (EDIT: This is definitely incorrect, because the alignment bit is about enforcing rather than tracking alignment. Plus, I doubt that pop/push would force alignment on a previously unaligned stack anyway.)

Even if that's the case though, what is the purpose of aligning the stack pointer again after the test (right before restoring the original eflags and stack pointer)? Shouldn't it already be on a 4-byte boundary from before? If not, how could that have changed from pushing/popping 4-byte values?

In short, some of the instructions seem redundant to me, and I feel that I must be missing something important. Can anyone here explain it?

(Side question: The very first line copies the value from "sx" into bx. I've never seen a reference to an sx register anywhere. Does it actually exist, or is it a typo? The 'x' key is pretty far from the 'p' key, at least on US keyboards.)

EDIT: Now that this question has been answered, I decided to remove an incorrect comment from the two alignment lines in the code. I originally made an assumption that aligning the stack would set the alignment bit, and I wrote that into my comment (the rest of the question continues with this incorrect logic). Instead, the alignment check bit really is about enforcing alignment (rather than tracking it), as flolo's answer regarding sigbus indicates. I decided to fix the comments to avoid confusing people with similar questions.

Saharan answered 23/9, 2011 at 15:22 Comment(2)
What museum did you break into to find a 386? You'd better put it back.Brelje
LOL! I've never touched a 386 desktop, to my knowledge...I messed with an old 286 back in the day, but I skipped right from that to a Pentium 75. I just decided to cover my bases before calling the CPUID instruction (first make sure you don't have a 386, then test eflags bit 21 for the presence of CPUID), in the off-chance some crazy ever decided to use my code on ancient hardware. I guess I'm just anal...but at least I'm not checking for 8-bit and 16-bit CPU's, since modern compilers apparently don't even generate code for those. ;)Saharan
Q
5

My guess is very easy: The code dont want to sigbus. In case the check alignment is not set, and you set it, you actually enabling alignment checks (when setting it works). And when the stack pointer isnt aligned to a 4-byte boundary guess what happens? You got an unaligned memory access, which results in a sigbus. And if you dont want to let that invalid memory access happen (as you just want to change the bit for testing purpose), you have to take care that all accesses while you test are assuming the worst case (which is: you have enabled it, and your stack was before it not aligned, because it dont need to, as up to now the checks were disabled).

Quintillion answered 23/9, 2011 at 15:40 Comment(3)
Thank you. That perfectly explains the first alignment instruction; I didn't recognize the significance of the bit being set (the potential for a sigbus). Do you know why the alignment is set again later, when it seems clear that the memory should already be aligned?Saharan
+1 This is the reason (It looks like it's from a pascal fragment and would thus be running off a 16-bit stack, which is not guaranteed to be 32-bit aligned). The second alignment of sp does indeed look redundant, it's probably just there to emphasize that the alignment check could be potentially enabled again by the popfl instruction.Boxthorn
Thanks again, guys! Unless and until someone else comes along and explains why the second alignment is actually necessary, I'll mark this as accepted and assume the second alignment is redundant.Saharan
H
2

There's no SX register. Either a typo or refers to a memory location.

Hornet answered 23/9, 2011 at 15:30 Comment(1)
Thanks. I was banking on that when I wrote the comment about saving the stack pointer in the first line, but it's good to have confirmation that it isn't some obscure sub-register. (I guess that wouldn't make sense anyway: The 16-bit sp is restored from the 16-bit bx, so nothing less than 16 bits would do for saving the original value to bx in the first place.)Saharan

© 2022 - 2024 — McMap. All rights reserved.