Reversing : The Hacker's Guide to Reverse Engineering

(ff) #1
represent actual local variables that were defined in the original program. Elim-
inating them might reduce program readability.
In terms of implementation, one representation that greatly simplifies this
process is the SSA notation described earlier. That’s because SSA provides a
clear picture of the lifespan of each register value and simplifies the process of
identifying ambiguous cases where different control flow paths lead to differ-
ent assignment instructions on the same register. This enables the decompiler
to determine when propagation should take place and when it shouldn’t.

Register Variable Identification

After you eliminate all temporary registers during the register copy propaga-
tion process, you’re left with registers that are actually used as variables. These
are easy to identify because they are used during longer code sequences com-
pared to temporary storage registers, which are often loaded from some mem-
ory address, immediately used in an instruction, and discarded. A register
variable is typically defined at some point in a procedure and is then used
(either read or updated) more than once in the code.
Still, the simple fact is that in some cases it is impossible to determine
whether a register originated in a variable in the program source code or
whether it was just allocated by the compiler for intermediate storage. Here is
a trivial example of how that happens:

int MyVariable = x * 4;
SomeFunc1(MyVariable);
SomeFunc2(MyVariable);
SomeFunc3(MyVariable);
MyVariable++;
SomeFunc4(MyVariable);

In this example the compiler is likely to assign a register for MyVariable,
calculate x * 4into it, and push it as the parameter in the first three function
calls. At that point, the register would be incremented and pushed as a param-
eter for the last function call. The problem is that this is exactly the same code
most optimizers would produce for the example that follows as well:

SomeFunc1(x * 4);
SomeFunc2(x * 4);
SomeFunc3(x * 4);
SomeFunc4(x * 4 + 1);

In this case, the compiler is smart enough to realize that x * 4doesn’t need
to be calculated four times. Instead it just computes x * 4into a register and
pushes that value into each function call. Before the last call to SomeFunc4
that register is incremented and is then passed into SomeFunc4, just as in the
previous example where the variable was explicitly defined. This is good

470 Chapter 13

Free download pdf