Search code examples
assemblyx86-64divide-by-zero

How is the zero flag set in this x86 code?


We had a crash today caused by an arithmetic exception in the idiv instruction in the following x86 assembly.

mov    r10d,DWORD PTR [rbp+0x70]
xor    ecx,ecx
mov    eax,r15d
test   r10d,r10d
setle  cl
cdq    
idiv   ecx

Here are the values of all the registers in gdb:

rax            0x64     100
rbx            0xaebc30 11451440
rcx            0x0      0
rdx            0x0      0
rsi            0x1      1
rdi            0xaebc88 11451528
rbp            0x7fa56809b840   0x7fa56809b840
rsp            0x7fa56effc080   0x7fa56effc080
r8             0x12     18
r9             0x100016a0000a72e        72059148816131886
r10            0x1      1
r11            0x0      0
r12            0x9e2bb8 10365880
r13            0x0      0
r14            0xaebc80 11451520
r15            0x64     100
rip            0x495ebb 0x495ebb
eflags         0x246    [ PF ZF IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0

Since the zero flag is set, setle cl writes 0 into ecx, which causes a divide by zero. The thing I don't understand is how the zero flag is set in the first place. The value of r10 is 1, so to my knowledge test r10d,r10d should unset it. cdq doesn't appear to modify it so I'm not sure what's happened here.

Can anyone with more x86 experience understand what the problem is? Is more information needed?


Solution

  • And this register dump is from right before idiv (or after GDB catches the SIGFPE which should be the same thing)?

    You have it backwards: ZF=1 means the "equals" condition is true. (So less-or-equal setle is also true). So setle cl should give you a 1.

    With R10 = 1, R10D = 1 also. R10D & R10D != 0 so ZF should be cleared. Thus r10d is NOT <= 0, so setle puts a 0 in CL and you do get a #DE divide exception.

    With this NASM source code:

    mov    r10d, 1
    xor    ecx,ecx
    mov    eax, 0x64
    test   r10d,r10d
    setle  cl
    cdq    
    idiv   ecx
    

    Built into a Linux static executable with nasm -felf64 foo.asm && ld foo.o -o foo (the entry point defaults to the top of the .text section), I ran it under GDB (because that was quicker than thinking through all the details, and I wanted to see if EFLAGS were changed by the faulting idiv).

    Use GDB's starti command to start at the top, and single-step. (With layout reg).

    Right before idiv, I get EFLAGS = [ IF ], ecx = 0. (With r10d = 1).

    As expected, an R10D > 0 (not <= 0) leads to ECX = 0.

    After trying to step past the idiv, or using continue, EFLAGS = [ RF IF ]. So your GDB register dump doesn't make sense, unless a different CPU or OS can do something different with FLAGS on a fault.