xchg rax, rax – 0x02
Moving on to 0x02, we have another short but more subtle program:
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.