Você está na página 1de 6


analysis & symbolic execution


Siqi Ma (siqim@andrew.cmu.edu)
Ke Xu (kxu1@andrew.cmu.edu)

Testing reads all lines of code and figures out where the bugs are.

Its hard to write the test cases and pass all lines of code.
Its hard to hit the codes that are rarely used.
The links between test and bugs are unclear. Its difficult to find where the code
get bugs, since there are millions lines of programs.

Manual inspection code review
Smart human looks for the bugs or designs the bugs.
However its hard to define the bug if their skill is not very well or the error
occurs in different parts of the program. Therefore manual inspection is hard to
analyze many code at the same time.

Model checking
For the technique (i.e., testing and manual inspection), they cannot guarantee
that we find all the bugs. Model checking guarantees that we can find all the bugs.
Model checking verifies design, but there might be some issues in
implementation, since model may not accurately reflect implementation.

Static analysis performs on the real code not a model, which is good.

These methods are complementary. In practice, researchers attempt to make

them to work with each other(dynamic, model, symbolic)

P8-9: Metacompilation
Metacompilation leverages compilers to check bugs. It also generates warnings
regarding security issues.
By giving a bunch of rules of security bugs, you can overcome lots of security

Meta-level compilation teaches the compilers about the rules that define which
are allowed and which are not.

In metacompilation, state-machine defines what should happen and what should
not happen. It scales very well.
You can not use the memory after you free it.

In high level, each checker corresponds to a state machine. Getting start from
v.start and keep tracking it, it is legal from start to free state (i.e., v.start to
v.freed). If variable v is used again after it has freed, it will cause an error (i.e.,
from v.freed to error).

We use control flow graph to indicate what happens in a function. Different state
will trigger different checker.
The compiler walks through the free variable and the checker moves to free state
when the function freeit(x) is called.
After the free state, it remembers the previous state.


If you have an untrusted input, and then you use the boundary function, such as
memory copy with limited size, it will taint the input.
However, MC checker just checks whether you check variable, but cannot make
sure whether your checking is correct. So, it cannot ensure your code is
completely correct. In other words, it tells you there are some bugs in the code,
but can not guarantee that the code will be bug free after checking.

e.g, Integer over flow, it only checks two values of variables.

You calls a method whose library is blocked, then, you are interrupted. Next, you
start from a clean state. If you call method that disables interrupt, then you
transit to noblock, otherwise you transit to error state. Sometimes the block
exists in libraries, so it is very difficult for human or compiler to detect.

We assign certain static values to trigger some code path. Checker draws control
flow graph to insert the assigned value and throws the error out.
We are enable to find:
A function that is never been called.
A line of code that is forgotten to be written.

Q: Will it change the order of instructions?
A: Ideally, a designed compiler should not change the program behaviors.
However, its hard to guarantee. There are two kinds of goals, 1) compilers dont
really re-order things. 2) some behaviors change, but it doesnt always happen.
If you want to make your code efficient, it cannot gurantee the behave. If you
demonstrate semantic preserving, the code wont be changed in a bad way.

Q: Will it reach unknown state?
A: If checker can not tell a state, it will continue checking the states of other

Rules that you write can catch some kinds of errors, not for the other kinds of
Compiler does not understand control flow. It is not sure where the jump will be
happened. It just looks at the jump. It works well for something that we can not
find by ourselves.

Q: Why doesnt it perform formal?
A: It is imprecise in many ways and doesnt always work.
Q: Is there any method that works? Is model checking totally complete and
A: Model checking. And its completeness and soundness depend on situations.

Dynamic analysis
It takes the code and makes it run in control environment. But not really run it in
real, it is run as a debugger step by step.
The output static analysis is a line of code with bug.
The output of dynamic analysis gives the line of code and the input that causes
the error, which is immediate and flexible.

random testing.
test program with real inputs.
try to cover all lines of code randomly.
concrete execution.
give the test cases to the code you run.
symbolic execution.
pick some random number as inputs.
Goals of tools: catch the bugs that static analysis will miss.
Where they find the bug, they show the input that trigger the bug.

Bug is the exit of the program. They checks whether a program exits in a good
way or a bad way. Depends on the exit, they give the corresponding test cases.

Q: what kind of bug can static analysis catch but dynamic analysis cannot?
A: Deadlocks. Making blocking call will result in dead lock, but wont cause crash.
Use after free wont cause program to crash. It is clearly a bug, but dynamic
analysis doesnt care. For a piece of code, it needs specific input to crash the
program. If the current input does not induce crash, it wont be detected as a bug
by dynamic analysis.
1. Giving a program, it figures out what the parameters are and the type of
2. Generate the driver functions, and random input.
3. Directe the execution step by step to make sure all the branches are covered.

P34 - 52:
double(): helper function
error(): it causes crach
test_me(): main function, it calls helper function

1. the user looks at the main function and two integers (x, y). It calls external
function, and gives the interfaces. But it doesnt give the external variables.

2. It generates a driver that invokes the function. It gives the value of inputs
randomly. Just pick some random values and run the testing.

3. Concrete states: Find what the values are.
Symbolic states: Figure out the relationships of the variable. Track the meaning
of the variables, z = 2*x.
Path constraints: Figure out whether the conditions are true or not. We
compute z, we track times. Then we find z !=y , then it works.

What symbolic execution do next: Compute another random test to satisfy z=y
and run the program again. Compute the input again and run the program.
Then, the program ends again when y!=x+10 is not true. It finds a new path to
make y !=x+10 become true, and runs the constraints again. It runs into the path
that causes the program crash.

Q: how does user use DART?
A: Just provide the top-level functions.

It doesnt guarantee to reach all lines of code
If the constraints are too complicated, it cannot understand the constraints and
the relationships of those variables.
If there is a loop, touching those lines of code once is not enough. It might either
reach the code many times, or touch it once if it seems okay.
It cannot detect everything, but only specific things. Sometimes it might choose
wrong value for variable. For example, it might assign -100 for speed, which
only takes positive number as input.