3

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?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Cretu Bogdan
  • 35
  • 1
  • 3
  • Please don't tag your question with irrelevant languages. This question has nothing to do with either C or C++, so trying to attract C/C++ coders will not get you good answers. – Silvio Mayolo Jan 10 '18 at 07:12
  • 1
    You could easily try this yourself: single-step past the `add` and look at FLAGS. Or put an `int3` instruction after the `add`. – Peter Cordes Jan 10 '18 at 09:24
  • 1
    The correct answer here is to read the documentation on the processor. While most of us here have this memorized for x86, it is not necessarily true in general that all instructions touch all flags for all instruction sets (some ISA's dont have flags), so as a habit look it up in the documentation. And absolutely the carry, overflow, zero check and negative flags (and any others) are separate logic paths that can one, none, all or any combination can be evaluated for an operation. Read the documentation for that processor/core instruction. – old_timer Jan 10 '18 at 12:58
  • Now depending on what you mean by activated, if the flag is part of the operation then be it zero or one after the operation is very relvant, both states have value. if your question is not about is it possible to do but but are both not zero (one) at the end of this operation, that is a different question and for 8 bit (or any number of bits operation) a signed 0x80+0x80 results in a signed over flow, look at the lsbits of the operation 1 + 1 = 10 binary. the carry in is different from the carry out or in other words the operands are the same and the msbit result is different 1+1=0. – old_timer Jan 10 '18 at 13:00
  • and the carry out, which is the carry bit A.K.A unsigned overflow, is also set because 1 + 1 = 10 binary so if this is evaluated as an 8 bit which that instruction set does, and being 8 bit evaluates both on the bit 7/8 boundry, then yes you should see both overflows, unsigned and signed, set, non-zero. – old_timer Jan 10 '18 at 13:02

3 Answers3

4

You can generalize this result to the exact condition in which both C and O are set. The rules for C and O in an addition are (at least this is one possible formulation)

  • C = carry out of the top bit
  • O = carry into the top bit XOR carry out of the top bit

So they can both be true, namely exactly when there is a carry out of the top bit but not into it.

harold
  • 61,398
  • 6
  • 86
  • 164
3

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:

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
2

Yes, in the example you gave, both carry and overflow will be set.

The overflow flag pertains to signed numbers. your example is adding -128 + -128. The result (-256) clearly doesn't fit in an 8-bit register, so the overflow flag is set.

prl
  • 11,716
  • 2
  • 13
  • 31