Search code examples
assemblyx86integer-overflow

Can an ADD set both Carry and Overflow flags?


I've been working for a few months with assembly. I know the difference between the carry flag and overflow flag.

My question (I couldn't find an answer on google) is if the carry and overflow flags can be activated in the same time. I can't find out where do I heard that only one flag (or carry or overflow) can be activated (not both in the same time).

Let's say we have the next code:

xor eax, eax
xor ebx, ebx
mov al, 128
mov bl, 128
add al, bl

Do the last line activates C and O flags? (note that the al and bl have the same sign)

In the above situation I would say that only carry will be activated. Am I wrong?


Solution

  • Adding 127 or less can't set OF and CF at the same time (for any starting value). Maybe what you read was talking about adding 1? (Note that inc / dec leave CF unmodified, so this would only apply to add al, 1)

    BTW, 128 + 128 is the first pair of inputs that sets both flags (for 8-bit operand size), if you search with nested loops from 0..255. I wrote a program to do exactly that.

    global _start
    _start:
    
        mov al, 128
        add al, 128        ; set a breakpoint here and single step this, then look at flags
    
        xor ecx, ecx
        xor edx, edx
    
    .edx:                       ; do {
    
    .ecx:                       ;   do {
        movzx eax, cl           ; eax as a scratch register every iteration
                          ; writing to eax instead of al avoids a false dependency for performance.
                          ; mov eax, ecx is just as good on AMD and Haswell/Skylake, but would have partial-reg penalties on earlier Intel CPUs.
        add   al, dl
        seto  al                ; al = OF  (0 or 1)
        lahf                    ; CF is the lowest bit of FLAGS.  LAFH loads AH from the low byte of FLAGS.
        test  ah, al            ; ZF = OF & CF
        jnz   .carry_and_overflow
    
    .continue:
        add   cl, 1             ;    add to set CF on unsigned wraparound
        jnc   .ecx              ;    } while(cl++ doesn't wrap)
        ; fall through when ECX=0
    
        add   dl, 1
        jnc   .edx              ; } while(dl++ doesn't wrap)
    
        xor   edi,edi
        mov   eax, 231
        syscall          ; exit(0) Linux 64-bit.
    
    .carry_and_overflow:
        int3             ; run this code inside GDB.
                         ; int3 is a software breakpoint
                         ; if execution stops here, look at cl and dl
        jmp  .continue
    

    Build on Linux (or any OS if you stop at a breakpoint before the exit system call) with NASM or YASM.

    yasm -felf64 -Worphan-labels -gdwarf2 foo.asm &&
    ld -o foo foo.o
    

    I ran this under gdb with gdb ./foo, then run.

    In my ~/.gdbinit I have:

    set disassembly-flavor intel
    layout reg
    
    set print static-members off
    set print pretty on
    macro define offsetof(t, f) &((t *) 0)->f)  # https://stackoverflow.com/questions/1768620/how-do-i-show-what-fields-a-struct-has-in-gdb#comment78715348_1770422
    

    layout reg puts GDB into a text-UI fullscreen mode (instead of line-oriented). After holding down return for a while after the first c (continue) command (at rax=128 / rdx=128), then hitting control-L to redraw the screen because GDB's TUI stuff doesn't work very well, I got this:

    ┌──Register group: general──────────────────────────────────────────────────────────────────────────────────────────────────────┐
    │rax            0x301    769                                    rbx            0x0      0                                       │
    │rcx            0xa7     167                                    rdx            0x85     133                                     │
    │rsi            0x0      0                                      rdi            0x0      0                                       │
    │rbp            0x0      0x0                                    rsp            0x7fffffffe6c0   0x7fffffffe6c0                  │
    │r8             0x0      0                                      r9             0x0      0                                       │
    │r10            0x0      0                                      r11            0x0      0                                       │
    │r12            0x0      0                                      r13            0x0      0                                       │
    │r14            0x0      0                                      r15            0x0      0                                       │
    │rip            0x4000a5 0x4000a5 <_start.carry_and_overflow+1> eflags         0x202    [ IF ]                                  │
    │cs             0x33     51                                     ss             0x2b     43                                      │
    │ds             0x0      0                                      es             0x0      0                                       │
    │fs             0x0      0                                      gs             0x0      0                                       │
    │                                                                                                                               │
       ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
       │0x400089 <_start.edx+5>                 seto   al                                                                           │
       │0x40008c <_start.edx+8>                 lahf                                                                                │
       │0x40008d <_start.edx+9>                 test   ah,al                                                                        │
       │0x40008f <_start.edx+11>                jne    0x4000a4 <_start.carry_and_overflow>                                         │
       │0x400091 <_start.continue>              add    cl,0x1                                                                       │
       │0x400094 <_start.continue+3>            jae    0x400084 <_start.edx>                                                        │
       │0x400096 <_start.continue+5>            add    dl,0x1                                                                       │
       │0x400099 <_start.continue+8>            jae    0x400084 <_start.edx>                                                        │
       │0x40009b <_start.continue+10>           xor    edi,edi                                                                      │
       │0x40009d <_start.continue+12>           mov    eax,0xe7                                                                     │
       │0x4000a2 <_start.continue+17>           syscall                                                                             │
       │0x4000a4 <_start.carry_and_overflow>    int3                                                                                │
      >│0x4000a5 <_start.carry_and_overflow+1>  jmp    0x400091 <_start.continue>                                                   │
       └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
    native process 5074 In: _start.carry_and_overflow                                                             L37   PC: 0x4000a5 
    Program received signal SIGTRAP, Trace/breakpoint trap.
    _start.carry_and_overflow () at foo.asm:37
    Continuing.
    
    Program received signal SIGTRAP, Trace/breakpoint trap.
    _start.carry_and_overflow () at foo.asm:37
    Continuing.
    
    Program received signal SIGTRAP, Trace/breakpoint trap.
    _start.carry_and_overflow () at foo.asm:37
    Continuing.
    
    Program received signal SIGTRAP, Trace/breakpoint trap.
    _start.carry_and_overflow () at foo.asm:37
    (gdb) 
    

    The pattern is interesting but easy to explain once you stop to think about the math: For DL=128, all CL values from 128 to 255 set both CF and OF. But for higher DL values, only CL from 128 to some value less than 255 set both. Because 133 for example represents 133 - 256 = -123, and (-123) + (-5) = -128, with no signed OverFlow. Very large unsigned values represent signed values of -1 or just below.


    See also: