Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 698
Mike S Avatar asked Sep 23 '11 15:09

Mike S


1 Answers

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).

like image 179
flolo Avatar answered Dec 01 '22 07:12

flolo