Free
no credit card
- Decompile x86-64 Programs
- Decompile ELF
- Upload 16kB Programs
- Upload 5 Programs
- Decompile -O0 -O1 -O2 -O3 Optimized Programs
- GPT 4o Refinement
- Communicate with an LLM for Code Changes
no credit card
per month
per month
price on request
Prices are final amounts. As a small business under § 19 UStG (Kleinunternehmerregelung), no VAT is charged or shown. Cancel anytime.
A detailed repository-level evaluation of C decompilers using the white-box symbolic-execution framework DecEvalSE, which employs differential testing.
price on request
price on request
tailored scope
Prices are final amounts. As a small business under § 19 UStG (Kleinunternehmerregelung), no VAT is charged or shown. Cancel anytime.
User provides compilable C repositories with build artifacts (configure scripts, make files, etc.).
While compiling, the framework intercepts compiler invocations and injects additional flags so that it can produce several artifacts in one pipeline: native executables for decompilation, GCOV-instrumented binaries for coverage analysis, and LLVM bitcode required for symbolic execution with KLEE.
For custom evaluations, the decompiled candidate is provided through a customer-implemented PredictionAPI.py bridge. DecEvalSE launches this Python process, sends a challenge request including the target function name and program path over IPC, and receives the predicted high-level function through a well-defined interface.
In practice, PredictionAPI.py is the integration layer between the customer’s decompiler and the evaluation framework. The template intentionally leaves both decompiler initialization and the actual prediction routine to the customer.
Customers may also expose their decompiler as a hosted online service, so the evaluation can interact with it through the interface without requiring access to the underlying source code, allowing proprietary implementations to remain protected.
def predict(functionName, programPath): # IMPLEMENT THIS PART
...
def main():
# Load decompiler here # IMPLEMENT THIS PART
...
# Challenge Request Protocol # WE PROVIDE THIS PART
...
Once the candidate function is available, we place it beside the original implementation and build an equivalence harness. The harness feeds both functions the same inputs and checks whether their observable results match.
int f_original(int num) {
int i;
if (num == 0)
return -1;
int count = -1;
for (i = 0; i < 32; i++) {
if ((num >> i) & 1)
count = i;
}
return count;
}
int f_decompiled(int num) {
if (num == 0)
return -1;
int position = -1;
for (int i = 0; i < sizeof(int) * 8; i++) {
if (num & (1 << i))
position = i;
}
return position;
}
In a normal unit test you choose concrete values such as x = 0 or x = 37. In symbolic execution, the input becomes symbolic. Instead of checking one run, the engine explores many feasible paths and accumulates path constraints as branches are taken.
If the assertion can be violated on some reachable path, the solver tries to synthesize a real concrete input that triggers the mismatch.
#include <assert.h>
#include "klee/klee.h"
int main() {
int x;
klee_make_symbolic(&x, sizeof(x), "x");
int a = f_original(x);
int b = f_decompiled(x);
klee_assert(a == b);
return 0;
}
The most useful output of symbolic execution is the counterexample. When the assertion is violated, the solver returns a concrete input that reproduces the discrepancy. That means we can show exactly where the original and decompiled implementations disagree.
Another useful output contains readability scores using Levenshtein Edit Distance expressed as a similarity metric, as well as CorpusBleu-, CodeBert-, CodeBleu-, CrystalBleu-Scores. In the future we plan to include LLM-assisted readability evaluations, variable name recover, struct name recovery, and many more as useful metric on the readability part.
A last useful output colntains the KLEE and GCOV coverage metrics, to give the evaluator an idea to which extent the symbolic execution engine explored the equivalence check per function.
DecEvalSE supports substantially more than primitive toy signatures.
Repository code rarely consists only of functions such as int f(int). In practice, functions operate on buffers, pointer chains, nested structs, static helpers, standard input, and observable memory effects rather than only through a direct scalar return value.
DecEvalSE therefore supports a broader class of signatures, including primitive parameters, arbitrary pointer depth, nested structs and structs containing supported pointer fields, void-returning functions, static functions, and functions whose behavior is exposed through return values, mutated arguments, standard input, or captured output.
This makes the evaluation applicable to more realistic repository code, while still acknowledging current limits: floating-point types are excluded, record return types are currently not evaluated symbolically, and highly environment-dependent behavior or large nullable pointer graphs can be more expensive to model precisely.
/* primitive parameters + primitive return */ int add(int a, int b); /* pointer parameter */ int sum_array(const int *arr, int n); /* output through memory */ void compute_stats(const int *arr, int n, int *min_out, int *max_out); /* arbitrary pointer depth */ int read_through_triple(int ***value); /* pointer selection / clamping */ int **pick_triple_slot(int ***slots, int count, int index); /* struct by value */ int process_point(struct Point p); /* nested struct by pointer */ int config_score(const struct Config *cfg); /* struct with pointer fields */ int bundle_score(struct Bundle bundle); /* struct with double-pointer field */ int boxholder_score(struct BoxHolder holder, int delta); /* in-place mutation via struct pointer */ void normalize_point(struct Point *p); /* void return with observable memory effects */ void swap_ints(int *a, int *b); /* static helper */ static int helper_hash(const char *s); /* reads from standard input */ int read_token(char *buf, int max_len); /* buffer transformation */ void xor_buf(uint8_t *dst, const uint8_t *src, int n); /* pointer + struct output */ void update_node(struct Node *node, int *status_out); /* array of pointers */ int count_tokens(const char **tokens, int count); /* recursive pointer-shaped input */ int score_chain(struct Node *head); /* string / buffer processing */ size_t trim_copy(char *dst, const char *src, size_t max_len);