index.php

Main navigation | Main content

/* 00000000 <mystery>: */ /* 0: 55 push %ebp */ /* 1: 57 push %edi */ /* 2: 56 push %esi */ /* 3: 53 push %ebx */ /* 4: 83 ec 04 sub $0x4,%esp */ /* These instructions save the callee-saved registers and set up the stack frame; we don't need to translate them. */ int mystery1(int arg) { /* 7: 8b 44 24 18 mov 0x18(%esp),%eax */ /* Because we pushed/reserved 20 bytes of stack in the prolog, 0x18(%esp) corresponds to the first (and only) argument. */ int eax = arg; /* b: c7 04 24 00 a3 e1 11 movl $0x11e1a300,(%esp) */ /* A local variable on the stack */ int temp = 0x11e1a300; /* 12: 85 c0 test %eax,%eax */ /* This tests sets ZF iff %eax is 0, SF if it's negative, and clears OF. */ /* 14: 7e 36 jle 4c <mystery+0x4c> */ /* Thus, the condition "le" corresponds to %eax being signed less than or equal to zero. The branch is used to skip over code, so we turn it into an "if" with the opposite condition. */ if (eax > 0) { /* 16: be 03 00 00 00 mov $0x3,%esi */ int esi = 3; /* 1b: bb 00 e1 f5 05 mov $0x5f5e100,%ebx */ int ebx = 0x5f5e100; /* 20: b9 01 00 00 00 mov $0x1,%ecx */ int ecx = 1; int edx, ebp; /* 25: 8d 76 00 lea 0x0(%esi),%esi */ /* This instruction has no effect, it's just used for padding because the next instruction is the target of a loop back edge. */ do { /* 28: 89 cf mov %ecx,%edi */ int edi = ecx; /* 2a: 89 cd mov %ecx,%ebp */ ebp = ecx; /* 2c: 0f af fe imul %esi,%edi */ edi *= esi; /* 2f: 83 c1 01 add $0x1,%ecx */ ecx++; /* 32: 89 da mov %ebx,%edx */ edx = ebx; /* 34: 89 d8 mov %ebx,%eax */ eax = ebx; /* 36: c1 fa 1f sar $0x1f,%edx */ /* This is a sign-extending shift that throws away all but the sign bit, so it sets edx to -1 if it was negative and to 0 otherwise. */ edx >>= 31; /* 39: f7 db neg %ebx */ ebx = -ebx; /* 3b: 83 c6 02 add $0x2,%esi */ esi += 2; /* 3e: 0f af f9 imul %ecx,%edi */ edi *= ecx; /* 41: f7 ff idiv %edi */ { long long dividend = (long long)edx << 32 | eax; eax = dividend / edi; edx = dividend % edi; } /* 43: 01 04 24 add %eax,(%esp) */ temp += eax; /* 46: 3b 6c 24 18 cmp 0x18(%esp),%ebp */ /* 4a: 75 dc jne 28 <mystery+0x28> */ } while (ebp != arg); } /* 4c: 8b 04 24 mov (%esp),%eax */ eax = temp; /* 4f: 83 c4 04 add $0x4,%esp */ /* 52: 5b pop %ebx */ /* 53: 5e pop %esi */ /* 54: 5f pop %edi */ /* 55: 5d pop %ebp */ /* Matching the pushes and sub at the beginning */ /* 56: c3 ret */ /* The calling convention uses eax for the return value. */ return eax; }And here's just the C code:

int mystery1(int arg) { int eax = arg; int temp = 0x11e1a300; if (eax > 0) { int esi = 3; int ebx = 0x5f5e100; int ecx = 1; int edx, ebp; do { int edi = ecx; ebp = ecx; edi *= esi; ecx++; edx = ebx; eax = ebx; edx >>= 31; ebx = -ebx; esi += 2; edi *= ecx; { long long dividend = (long long)edx << 32 | eax; eax = dividend / edi; edx = dividend % edi; } temp += eax; } while (ebp != arg); } eax = temp; return eax; }This still looks rather complicated, so let's try cleaning up and simplifying it in various ways:

- First let's simplify the division. The compiler generated a
division with a 64-bit divisor because that's what the hardware
supports. But you can see that the high bits of the dividend in
`edx`are just the sign extension of the low bits in`eax`, so at the C level we can get the same effect with just a 32-bit division. Also the remainder is computed by the instruction but not used, so we can get rid of it.@@ -8,23 +8,17 @@ int ebx = 0x5f5e100; int ecx = 1; - int edx, ebp; + int ebp; do { int edi = ecx; ebp = ecx; edi *= esi; ecx++; - edx = ebx; eax = ebx; - edx >>= 31; ebx = -ebx; esi += 2; edi *= ecx; - { - long long dividend = (long long)edx << 32 | eax; - eax = dividend / edi; - edx = dividend % edi; - } + eax /= edi; temp += eax; } while (ebp != arg); }

- The two large constants look weird in hexadecimal, but if we
convert them to decimal they're just 100 million and 300
million. Let's guess it's not a coincidence that those two numbers are
similar.
@@ -1,11 +1,11 @@ int mystery1(int arg) { int eax = arg; - int temp = 0x11e1a300; + int ebx = 100000000; + int temp = 3 * ebx; if (eax > 0) { int esi = 3; - int ebx = 0x5f5e100; int ecx = 1; int ebp;

- The return value has to go into
`eax`at the end, but effectively it looks like`temp`is the value that will be returned. Since it's formed by adding something on every round of the loop, let's call it`total`. Also while we're at it, note that the use of`eax`for the branch is unrelated to the way it's used inside the loop (it's just a copy of the argument), so let's rewrite too.@@ -1,15 +1,14 @@ int mystery1(int arg) { - int eax = arg; - int ebx = 100000000; - int temp = 3 * ebx; + int total = 3 * ebx; - if (eax > 0) { + if (arg > 0) { int esi = 3; int ecx = 1; int ebp; do { + int eax; int edi = ecx; ebp = ecx; edi *= esi; @@ -19,9 +18,8 @@ esi += 2; edi *= ecx; eax /= edi; - temp += eax; + total += eax; } while (ebp != arg); } - eax = temp; - return eax; + return total; }

`ecx`starts at 1 and is incremented on every iteration of the loop, so let's give it the generic integer name`i`.@@ -4,19 +4,19 @@ if (arg > 0) { int esi = 3; - int ecx = 1; + int i = 1; int ebp; do { int eax; - int edi = ecx; - ebp = ecx; + int edi = i; + ebp = i; edi *= esi; - ecx++; + i++; eax = ebx; ebx = -ebx; esi += 2; - edi *= ecx; + edi *= i; eax /= edi; total += eax; } while (ebp != arg);

- The divisor for the division is built up in the register
`edi`. We'll call that value`divisor`instead, and put its construction into a single statement. Note that because`i`is incremented in the middle of the computation, one of the uses is really`i+1`. Then we'll move the increment of`i`down to the end of the loop body so it's not in the way.@@ -9,16 +9,14 @@ int ebp; do { int eax; - int edi = i; + int divisor = i * esi * (i + 1); ebp = i; - edi *= esi; - i++; eax = ebx; ebx = -ebx; esi += 2; - edi *= i; - eax /= edi; + eax /= divisor; total += eax; + i++; } while (ebp != arg); } return total;

- Next, we'll get rid of the use of
`eax`to hold the quotient. Note that we have move the negation of`ebx`to keep it after its use.@@ -8,14 +8,11 @@ int ebp; do { - int eax; int divisor = i * esi * (i + 1); ebp = i; - eax = ebx; - ebx = -ebx; esi += 2; - eax /= divisor; - total += eax; + total += ebx / divisor; + ebx = -ebx; i++; } while (ebp != arg); }

- We can eliminate the variable
`ebp`by replacing its one use with`i - 1`.@@ -6,15 +6,13 @@ int esi = 3; int i = 1; - int ebp; do { int divisor = i * esi * (i + 1); - ebp = i; esi += 2; total += ebx / divisor; ebx = -ebx; i++; - } while (ebp != arg); + } while (i - 1 != arg); } return total; }

- Observe that
`esi`is being incremented by two every time around the loop, which suggests that it has a value related to`2*i`. In particular from the way it was initialized, we can see that it's always equal to`2*i + 1`. GCC thought the code would run faster keeping it in a separate variable, but I think the code is easier to read if you use the more complex expression.@@ -3,12 +3,10 @@ int total = 3 * ebx; if (arg > 0) { - int esi = 3; int i = 1; do { - int divisor = i * esi * (i + 1); - esi += 2; + int divisor = i * (2*i + 1) * (i + 1); total += ebx / divisor; ebx = -ebx; i++;

- One thing that definitely still looks weird here is the
`do-while`loop inside the if statement. The point of the`do-while`construct is to let you write the loop test only after the loop body, so that the loop always executes at least once. But plain`while`loops are more popular because it's often more easy to reason about loops where the check is at the top: for instance this often easily covers the case where it's not necessary to execute the loop at all. The loop is counting`i`up to`arg`: specifically the last iteration is the one on which`i`is equal to`arg`. The condition in the`if`seems related, which suggests maybe we can combine the`if`and the`do-while`back into a regular`while`loop. Normally the following combination:if (cond) { do { ... } while (cond) }

would be equivalent to:while (cond) { ... }

The problem is that the two conditions

`arg > 0`and`i - 1 != arg`look rather different. But maybe they both came from the same condition that the compiler optimized. For the`if`condition, the compiler knows that the initial value of the loop counter is`1`, so`0`might be the optimized version of`i - 1`. Similarly when we're inside the loop the compiler knows that`i-1`can never be greater than`arg`, because it was initialized to be less than or equal and we stop after the iteration when they're equal. So it looks like both conditions are equivalent to`i - 1 < arg`, or more idiomatically`i <= arg`.However there's a subtle problem lurking here, which you would probably only notice if you were paranoid about integer overflow attacks, or if you were testing the function with the largest possible positive integer (INT_MAX) as an argument. If we write the condition as

`i <= arg`, then when`arg`is equal to`INT_MAX`, the loop's exit condition will never be satisfied. (The program won't actually loop forever, because when`i`loops around back near zero, you'll get a divide by zero crash.) You might think you would be safe if you kept the condition as the less natural-looking`i - 1 < arg`, since that condition looks like it should still be false when`arg`is INT_MAX and`i`is INT_MAX + 1. (2's complement arithmetic is associative, so (*x*+ 1) - 1 =*x*for all values of*x*.) Unfortunately, the C compiler is still allowed to "optimize" the condition`i - 1 < arg`into the condition`i <= arg`, even though they have the differing behavior we just described. The reason is that the C standard says that overflow of a signed integer causes undefined behavior. This means that a program is entitled to do whatever it wants if an overflow might occur, or equivalently, optimize as if the situation triggering undefined behavior could never occur. It's the programmer's responsibility to ensure that the undefined behavior can never occur.Undefined behavior has turned out to be a ongoing source of friction between C programmers and compiler makers, since programmers' intuitions about how the compiler works often differ from what the standard allows. In particular problems tend to arise when compilers become more sophisticated at optimization: optimizations that take better advantage of the undefined-behavior rules can make some previously-working programs run faster, and other previously-working programs crash in hard-to-debug ways. John Regehr's blog has some lucid discussions of these issues.

For now, we can work around this behavior by adding an extra condition on

`i`to prevent overflow.@@ -2,15 +2,12 @@ int ebx = 100000000; int total = 3 * ebx; - if (arg > 0) { int i = 1; - - do { + while (i < 0x7fffffff && i <= arg) { int divisor = i * (2*i + 1) * (i + 1); total += ebx / divisor; ebx = -ebx; i++; - } while (i - 1 != arg); } return total; }

- Though actually it would be even more idiomatic to write this as a
`for`loop.@@ -2,12 +2,11 @@ int ebx = 100000000; int total = 3 * ebx; - int i = 1; - while (i < 0x7fffffff && i - 1 < arg) { + int i; + for (i = 1; i < 0x7fffffff && i - 1 < arg; i++) { int divisor = i * (2*i + 1) * (i + 1); total += ebx / divisor; ebx = -ebx; - i++; } return total; }

- This code now looks pretty good, though it still doesn't compile
to the same instructions as the mystery instructions; in particular
there's that weird condition to prevent overflow that wasn't present
in the binary. In fact both of these problems can be solved at once,
though it requires a somewhat non-obvious transformation: shifting the
index
`i`of the loop. You might have noticed it's a little bit more natural in C to use 0 rather than 1 as the starting index of a loop, and to compare to the bound using a strict comparison. We can achieve this by using a new index`j`which is related to the old index by`j = i - 1`and`i = j + 1`. This will make the bounds of the`for`loop look more natural, and most importantly it will avoid the overflow problem in the extreme case when the argument in`INT_MAX`. The trade-off is that the formula for the divisor will look somewhat more complicated.@@ -2,9 +2,9 @@ int ebx = 100000000; int total = 3 * ebx; - int i; - for (i = 1; i < 0x7fffffff && i - 1 < arg; i++) { - int divisor = i * (2*i + 1) * (i + 1); + int j; + for (j = 0; j < arg; j++) { + int divisor = (j + 1) * (2*j + 3) * (j + 2); total += ebx / divisor; ebx = -ebx; }

- We've now managed to match the original binary code. But what does
this function do, anyway? From the code, it appears to be computing
the sum of some sort of alternating series. The series probably isn't
a familiar one, but if you examine the function's output for an input
of 1000 or so, the output of 314159263 is suggestive: it looks a lot
like
*π*times`ebx`. And in fact that is what the series converges to mathematically; this series was discovered by the Indian mathematician Nilakantha in the 15th century. Based on this understanding, we might rename the argument to`terms`; a good name for the variable`ebx`is more elusive, but let's call it`unit`. Here's our final decompilation:int mystery1(int terms) { int unit = 100000000; int total = 3 * unit; int j; for (j = 0; j < terms; j++) { int divisor = (j + 1) * (2*j + 3) * (j + 2); total += unit / divisor; unit = -unit; } return total; }

For comparison, here's the code as the instructor originally wrote it:int mystery(int steps) { int m = 100000000; int sum = 3*m; int i; for (i = 0; i < steps; i++) { int denom = (i+1) * (2*i+3) * (i+2); int change = m / denom; sum += change; m = -m; } return sum; }