Moving on to 0x02, we have another short but more subtle program:

neg      rax
sbb      rax,rax
neg      rax

We have two unique instructions, all dealing directly with the rax register.

Starting with neg, this is a two’s complement negation. It’s functionally equivalent to subtracting the value from zero. It also sets the cf flag (carry flag) if the source is zero to zero, otherwise it sets cf to one.

sbb is the next instruction, or subtraction with borrow. The Intel Instruction Reference has a good description of this, “Adds the source operand (second operand) and the carry (CF) flag, and subtracts the result from the destination operand (first operand). The result of the subtraction is stored in the destination operand. The destination operand can be a register or a memory location; the source operand can be an immediate, a register, or a memory location. (However, two memory operands cannot be used in one instruction.) The state of the CF flag represents a borrow from a previous subtraction.”

Let’s start by observing the affect of just the first two instructions and see how they work. We know from the descriptions of neg that we can expect different behaviors whether or not rax is zero, so let’s try it with one and zero and see what the results are.

Initial:
rax = 0x0000000000000000
rflags = 0x0000000000000202 (CF = 0)

Step (neg):
rax = 0x0000000000000000
rflags = 0x0000000000000246 (CF = 0)

Step (sbb):
rax = 0x0000000000000000
rflags = 0x0000000000000246 (CF = 0)

And let’s try it when rax is 1:

Initial:
rax = 0x0000000000000001
rflags = 0x0000000000000202 (CF = 0)

Step (neg):
rax = 0xffffffffffffffff
rflags = 0x0000000000000297 (CF = 1)

Step (sbb):
rax = 0xffffffffffffffff
rflags = 0x0000000000000297 (CF = 1)

Tip: You can use print/t $rflags to see the individual flags in LLDB. We know that the Carry Flag is bit zero.

rax is zero, and negating zero is zero again, so zero gets set in the destination. We can also see that CF is set to zero. Next is sbb. It adds the source (rax) and the carry flag. Zero plus zero is zero, then zero is subtracted from zero and stored in rax, which is zero.

A whole lot of zeros.

Now for the second example. We start with 1 and negate it, so -1. In two’s complement that’s 0xffffffffffffffff. We also see that the CF is set to 1. Next for sbb, we add -1 and the CF flag, so, back to zero. But we haven’t done the subtraction yet. So subtract zero from -1, and we are still left with -1, which is what we see in rax.

OK, but we still have a final neg left. We can easily determine that the negative of zero is zero for the second example, -1 negated is back to 1.

In both cases, we end up right back to where we started. Doesn’t seem spectacularly interesting. Let’s try a random-ish value for rax, like 89.

Initial:
rax = 0x0000000000000059
rflags = 0x0000000000000202 (CF = 0)

Step (neg):
rax = 0xffffffffffffffa7
rflags = 0x0000000000000293 (CF = 1)

Step (sbb):
rax = 0xffffffffffffffff
rflags = 0x0000000000000297 (CF = 1)

This one is more interesting. We take 89 and negate it, giving is -89. CF gets set to 1. Next for sbb, we take -89 and add CF, which gives us -88. The destination operand is -89 still, so -89 - (-88) is -1.

The final instruction negates that back to 1.

Turns out it behaves that way for all values other than zero because of the way neg behaves with regard to the carry flag.

So what is the purpose of this then? It’s a branchless, simple way of setting something to 1 for any value other than zero. It doesn’t seem like much, but it’s clever. In pseudo code, it might look something like this:

if (value != 0) then
    value = 1
else
    value = 0

But as the code shows, it does this all without branching. Very clever.