Spaces:
Running
Running
Commit ·
52a9362
1
Parent(s): f09ab97
Add three Faultless completeness examples
Browse files- README.md +5 -2
- demo/cases/README.md +36 -0
- demo/cases/my_putchar_count/candidate.c +10 -0
- demo/cases/my_putchar_count/decompiled.c +8 -0
- demo/cases/my_putchar_count/metadata.json +10 -0
- demo/cases/my_putchar_count/original.c +5 -0
- demo/cases/my_putchar_count/runnable.json +17 -0
- demo/cases/print_page_header/candidate.c +8 -0
- demo/cases/print_page_header/decompiled.c +5 -0
- demo/cases/print_page_header/metadata.json +10 -0
- demo/cases/print_page_header/original.c +6 -0
- demo/cases/print_page_header/runnable.json +25 -0
- demo/cases/upper/candidate.c +17 -0
- demo/cases/upper/decompiled.c +15 -0
- demo/cases/upper/metadata.json +10 -0
- demo/cases/upper/original.c +7 -0
- demo/cases/upper/runnable.json +12 -0
- demo/examples.py +14 -5
- demo/faultless.py +57 -0
- tests/test_examples.py +2 -2
- tests/test_faultless.py +5 -0
- tests/test_ui.py +3 -2
README.md
CHANGED
|
@@ -13,7 +13,7 @@ fullWidth: true
|
|
| 13 |
Public Gradio frontend for comparing a binary function with candidate C. Select
|
| 14 |
an example or upload an ELF x86-64 binary, supply its function address and candidate
|
| 15 |
source, and stream verification results with assembly previews and a JSON download.
|
| 16 |
-
The application includes
|
| 17 |
memory, Types and ABI, Real binaries, Related work, and Ghidra exports. Each section
|
| 18 |
has a short description with relevant paper or tool links. Basics starts open;
|
| 19 |
expand any other section to browse its examples.
|
|
@@ -117,9 +117,12 @@ to clone private code before the Docker build. Local developers use the explicit
|
|
| 117 |
Startup retains the pinned private core checkout in `core/` after removing its Git
|
| 118 |
metadata. The UI reads example sources from that checkout and compiles originals
|
| 119 |
when preparing examples. Local development uses the same loader and submodule.
|
| 120 |
-
The Related work section combines
|
| 121 |
DiscScope, and Decompile-Diverge, including a deliberately uncompilable export. See `core/docs/related-work-examples.md`
|
| 122 |
for provenance, interface adaptations, and published counterexamples.
|
|
|
|
|
|
|
|
|
|
| 123 |
Reference C is a display aid; uploaded binaries have none. DiscScope `orig.c`
|
| 124 |
excerpts are stored alongside their `raw.c` and `cand.c` in the shared core cases.
|
| 125 |
They come from binutils 2.29, libsmi 0.4.8, and JasPer 1.900.16, with source
|
|
|
|
| 13 |
Public Gradio frontend for comparing a binary function with candidate C. Select
|
| 14 |
an example or upload an ELF x86-64 binary, supply its function address and candidate
|
| 15 |
source, and stream verification results with assembly previews and a JSON download.
|
| 16 |
+
The application includes 63 examples in expandable sections: Basics, Calls and
|
| 17 |
memory, Types and ABI, Real binaries, Related work, and Ghidra exports. Each section
|
| 18 |
has a short description with relevant paper or tool links. Basics starts open;
|
| 19 |
expand any other section to browse its examples.
|
|
|
|
| 117 |
Startup retains the pinned private core checkout in `core/` after removing its Git
|
| 118 |
metadata. The UI reads example sources from that checkout and compiles originals
|
| 119 |
when preparing examples. Local development uses the same loader and submodule.
|
| 120 |
+
The Related work section combines nine Faultless cases with nine cases from D-Helix,
|
| 121 |
DiscScope, and Decompile-Diverge, including a deliberately uncompilable export. See `core/docs/related-work-examples.md`
|
| 122 |
for provenance, interface adaptations, and published counterexamples.
|
| 123 |
+
The three O0 completeness pairs (`my_putchar_count`, `upper`, and `print_page_header`)
|
| 124 |
+
are stored in `demo/cases/`, including raw exports, source metadata, and documented
|
| 125 |
+
runnable adaptations in [demo/cases/README.md](demo/cases/README.md).
|
| 126 |
Reference C is a display aid; uploaded binaries have none. DiscScope `orig.c`
|
| 127 |
excerpts are stored alongside their `raw.c` and `cand.c` in the shared core cases.
|
| 128 |
They come from binutils 2.29, libsmi 0.4.8, and JasPer 1.900.16, with source
|
demo/cases/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Faultless examples
|
| 2 |
+
|
| 3 |
+
Original/decompiled pairs extracted verbatim from the O0 completeness dataset. Decompiled files retain decompiler syntax and are not standalone compilation units.
|
| 4 |
+
|
| 5 |
+
- `my_putchar_count` (Ismuur/Magic): Faultless treats the entire decompiled buffer as involved in write, although write consumes only the first byte (count = 1). Equivalence depends on modeling the callee behavior.
|
| 6 |
+
- `upper` (aaronokano/ecs150-hw1): The decompiled loop exits through an early break; Faultless loop pairing can be brittle around breaks.
|
| 7 |
+
- `print_page_header` (achasse/cse220): The original uses a static local integer, represented as a global in the decompiled code; Faultless has difficulty with static integers.
|
| 8 |
+
|
| 9 |
+
The three directories preserve `original.c`, `decompiled.c`, and `metadata.json`
|
| 10 |
+
from `/tmp/decompile-verify-faultless-examples` unchanged. The metadata records
|
| 11 |
+
the dataset index, canonical function name, source repository, and Faultless's
|
| 12 |
+
reported failure. That failure is distinct from this verifier's result.
|
| 13 |
+
|
| 14 |
+
`candidate.c` is the runnable version. It adds standard headers and removes the
|
| 15 |
+
decompiler's `__fastcall` spelling. `my_putchar_count` restores the original
|
| 16 |
+
32-bit return type. `upper` restores the original void return and writable
|
| 17 |
+
`char *` parameter, retaining the early break and repeated `strlen` calls.
|
| 18 |
+
`print_page_header` restores the void return while retaining the increment,
|
| 19 |
+
and declares the exported counter as an external integer. The original static
|
| 20 |
+
local remains intact; its `page_number.0` symbol supplies the global binding.
|
| 21 |
+
These examples compare behavior under the original interfaces, including these
|
| 22 |
+
documented corrections to the inferred signatures.
|
| 23 |
+
|
| 24 |
+
`runnable.json` records declarations, original and candidate symbols, callee and
|
| 25 |
+
global bindings, and interfaces. Originals are compiled with GCC at `-O0` and
|
| 26 |
+
`-fno-builtin`. Linker stubs resolve `write`, `strlen`, and `printf`; the verifier
|
| 27 |
+
intercepts them with shared synthetic responses. The stubs do not model libc.
|
| 28 |
+
The `write` interface explicitly compares one byte of its second argument;
|
| 29 |
+
`printf` specifies its four variadic argument types.
|
| 30 |
+
|
| 31 |
+
The one-byte write can remain inconclusive because this verifier requires the
|
| 32 |
+
whole escaped stack frame to agree even with an explicit one-byte range.
|
| 33 |
+
The uppercase loop uses synthetic `strlen` responses, so budget exhaustion is
|
| 34 |
+
possible; it does not demonstrate real libc string handling or loop equivalence.
|
| 35 |
+
Agreement for the page header covers the bound counter and intercepted `printf`
|
| 36 |
+
arguments on tested inputs.
|
demo/cases/my_putchar_count/candidate.c
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <unistd.h>
|
| 2 |
+
|
| 3 |
+
int func0(char a1)
|
| 4 |
+
{
|
| 5 |
+
char buf[4]; // [rsp+Ch] [rbp-4h] BYREF
|
| 6 |
+
|
| 7 |
+
buf[0] = a1;
|
| 8 |
+
write(1, buf, 1uLL);
|
| 9 |
+
return 1LL;
|
| 10 |
+
}
|
demo/cases/my_putchar_count/decompiled.c
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__int64 __fastcall func0(char a1)
|
| 2 |
+
{
|
| 3 |
+
char buf[4]; // [rsp+Ch] [rbp-4h] BYREF
|
| 4 |
+
|
| 5 |
+
buf[0] = a1;
|
| 6 |
+
write(1, buf, 1uLL);
|
| 7 |
+
return 1LL;
|
| 8 |
+
}
|
demo/cases/my_putchar_count/metadata.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"repo": "Ismuur/Magic",
|
| 3 |
+
"name": "my_putchar_count",
|
| 4 |
+
"index": 74,
|
| 5 |
+
"canonical_name": "func0",
|
| 6 |
+
"status": "failure",
|
| 7 |
+
"nonequivalence_reason": "Function write in the original function has no equivalent in the decompiled function.",
|
| 8 |
+
"source_report": "O0_opt_parity_test/report.md",
|
| 9 |
+
"note": "Faultless treats the entire decompiled buffer as involved in write, although write consumes only the first byte (count = 1). Equivalence depends on modeling the callee behavior."
|
| 10 |
+
}
|
demo/cases/my_putchar_count/original.c
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
int my_putchar_count(char c)
|
| 2 |
+
{
|
| 3 |
+
write(1, &c, 1);
|
| 4 |
+
return (1);
|
| 5 |
+
}
|
demo/cases/my_putchar_count/runnable.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"original_symbol": "my_putchar_count",
|
| 3 |
+
"candidate_symbol": "func0",
|
| 4 |
+
"declarations": "#include <unistd.h>\n",
|
| 5 |
+
"stubs": "#include <unistd.h>\nssize_t write(int fd, const void *buffer, size_t count) { return 0; }\n",
|
| 6 |
+
"callees": {
|
| 7 |
+
"write": "write"
|
| 8 |
+
},
|
| 9 |
+
"interfaces": {
|
| 10 |
+
"original_prototype": "int my_putchar_count(char c);",
|
| 11 |
+
"memory_ranges": {
|
| 12 |
+
"write": {
|
| 13 |
+
"1": 1
|
| 14 |
+
}
|
| 15 |
+
}
|
| 16 |
+
}
|
| 17 |
+
}
|
demo/cases/print_page_header/candidate.c
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <stdio.h>
|
| 2 |
+
extern int page_number_0;
|
| 3 |
+
|
| 4 |
+
void func9(const char *a1, const char *a2)
|
| 5 |
+
{
|
| 6 |
+
printf("Page %3d %*s\t %s\t \n", (unsigned int)page_number_0, 26, a1, a2);
|
| 7 |
+
++page_number_0;
|
| 8 |
+
}
|
demo/cases/print_page_header/decompiled.c
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__int64 __fastcall func9(const char *a1, const char *a2)
|
| 2 |
+
{
|
| 3 |
+
printf("Page %3d %*s\t %s\t \n", (unsigned int)page_number_0, 26, a1, a2);
|
| 4 |
+
return (unsigned int)++page_number_0;
|
| 5 |
+
}
|
demo/cases/print_page_header/metadata.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"repo": "achasse/cse220",
|
| 3 |
+
"name": "print_page_header",
|
| 4 |
+
"index": 194,
|
| 5 |
+
"canonical_name": "func9",
|
| 6 |
+
"status": "failure",
|
| 7 |
+
"nonequivalence_reason": "Function printf in the original function has no equivalent in the decompiled function.",
|
| 8 |
+
"source_report": "O0_opt_parity_test/report.md",
|
| 9 |
+
"note": "The original uses a static local integer, represented as a global in the decompiled code; Faultless has difficulty with static integers."
|
| 10 |
+
}
|
demo/cases/print_page_header/original.c
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
static void print_page_header(char source_name[], char date[])
|
| 2 |
+
{
|
| 3 |
+
static int page_number = 0;
|
| 4 |
+
printf("Page %3d %*s\t %s\t \n", page_number, 80/3, source_name, date);
|
| 5 |
+
page_number++;
|
| 6 |
+
}
|
demo/cases/print_page_header/runnable.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"original_symbol": "print_page_header",
|
| 3 |
+
"candidate_symbol": "func9",
|
| 4 |
+
"declarations": "#include <stdio.h>\n",
|
| 5 |
+
"stubs": "int printf(const char *format, ...) { return 0; }\n",
|
| 6 |
+
"callees": {
|
| 7 |
+
"printf": "printf"
|
| 8 |
+
},
|
| 9 |
+
"globals": {
|
| 10 |
+
"page_number_0": "page_number.0"
|
| 11 |
+
},
|
| 12 |
+
"interfaces": {
|
| 13 |
+
"original_prototype": "void print_page_header(char *source_name, char *date);",
|
| 14 |
+
"callee_interfaces": {
|
| 15 |
+
"printf": {
|
| 16 |
+
"variadic_types": [
|
| 17 |
+
"int",
|
| 18 |
+
"int",
|
| 19 |
+
"char *",
|
| 20 |
+
"char *"
|
| 21 |
+
]
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
}
|
demo/cases/upper/candidate.c
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <string.h>
|
| 2 |
+
|
| 3 |
+
void func8(char *a1)
|
| 4 |
+
{
|
| 5 |
+
size_t result; // rax
|
| 6 |
+
int i; // [rsp+1Ch] [rbp-14h]
|
| 7 |
+
|
| 8 |
+
for ( i = 0; ; ++i )
|
| 9 |
+
{
|
| 10 |
+
result = strlen(a1);
|
| 11 |
+
if ( i >= result )
|
| 12 |
+
break;
|
| 13 |
+
if ( a1[i] > 96 && a1[i] <= 122 )
|
| 14 |
+
a1[i] -= 32;
|
| 15 |
+
}
|
| 16 |
+
return;
|
| 17 |
+
}
|
demo/cases/upper/decompiled.c
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
size_t __fastcall func8(const char *a1)
|
| 2 |
+
{
|
| 3 |
+
size_t result; // rax
|
| 4 |
+
int i; // [rsp+1Ch] [rbp-14h]
|
| 5 |
+
|
| 6 |
+
for ( i = 0; ; ++i )
|
| 7 |
+
{
|
| 8 |
+
result = strlen(a1);
|
| 9 |
+
if ( i >= result )
|
| 10 |
+
break;
|
| 11 |
+
if ( a1[i] > 96 && a1[i] <= 122 )
|
| 12 |
+
a1[i] -= 32;
|
| 13 |
+
}
|
| 14 |
+
return result;
|
| 15 |
+
}
|
demo/cases/upper/metadata.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"repo": "aaronokano/ecs150-hw1",
|
| 3 |
+
"name": "upper",
|
| 4 |
+
"index": 188,
|
| 5 |
+
"canonical_name": "func8",
|
| 6 |
+
"status": "failure",
|
| 7 |
+
"nonequivalence_reason": "Function strlen in the original function has no equivalent in the decompiled function.",
|
| 8 |
+
"source_report": "O0_opt_parity_test/report.md",
|
| 9 |
+
"note": "The decompiled loop exits through an early break; Faultless loop pairing can be brittle around breaks."
|
| 10 |
+
}
|
demo/cases/upper/original.c
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
void upper(char *mystring) {
|
| 2 |
+
int i; int diff = 'a' - 'A';
|
| 3 |
+
for( i = 0; i < strlen( mystring ); i++ ) {
|
| 4 |
+
if( mystring[i] >= 'a' && mystring[i] <= 'z' )
|
| 5 |
+
mystring[i] -= diff;
|
| 6 |
+
}
|
| 7 |
+
}
|
demo/cases/upper/runnable.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"original_symbol": "upper",
|
| 3 |
+
"candidate_symbol": "func8",
|
| 4 |
+
"declarations": "#include <string.h>\n",
|
| 5 |
+
"stubs": "#include <stddef.h>\nsize_t strlen(const char *s) { return 0; }\n",
|
| 6 |
+
"callees": {
|
| 7 |
+
"strlen": "strlen"
|
| 8 |
+
},
|
| 9 |
+
"interfaces": {
|
| 10 |
+
"original_prototype": "void upper(char *mystring);"
|
| 11 |
+
}
|
| 12 |
+
}
|
demo/examples.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""
|
| 2 |
from pathlib import Path
|
| 3 |
import sys
|
| 4 |
import json
|
|
@@ -6,13 +6,21 @@ import json
|
|
| 6 |
ROOT = Path(__file__).resolve().parents[1]
|
| 7 |
sys.path.insert(0, str(ROOT / "core"))
|
| 8 |
|
| 9 |
-
from examples.ui import
|
| 10 |
-
ORIGINAL_EXAMPLE_LABELS, GHIDRA_EXAMPLE_NAMES,
|
| 11 |
-
ghidra_metadata)
|
| 12 |
from examples import ui as core_examples
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
|
| 15 |
def example_metadata(name):
|
|
|
|
|
|
|
| 16 |
metadata = core_examples.example_metadata(name)
|
| 17 |
if name == "faultless-linked-list":
|
| 18 |
metadata["expected_verdict"] = "mismatch"
|
|
@@ -34,6 +42,7 @@ def expected_result(name, compiler="gcc"):
|
|
| 34 |
|
| 35 |
def prepare_examples(directory, *, include_ghidra=True):
|
| 36 |
rows = dict(zip(core_examples.EXAMPLE_NAMES, core_examples.prepare_examples(directory)))
|
|
|
|
| 37 |
names = EXAMPLE_NAMES if include_ghidra else tuple(
|
| 38 |
name for name in EXAMPLE_NAMES if name not in GHIDRA_EXAMPLE_NAMES)
|
| 39 |
selected = [rows[name] for name in names]
|
|
@@ -50,7 +59,7 @@ EXAMPLE_GROUPS = (
|
|
| 50 |
("Types and ABI", ("wide-integer", "float-identity", "signed-zero", "nan-payload",
|
| 51 |
"struct-fields", "struct-indirect", "struct-padding", "pointer-identity")),
|
| 52 |
("Real binaries", ("factor-gcd", "factor-gcd-euclidean", "factor-gcd-even")),
|
| 53 |
-
("Related work", core_examples.FAULTLESS_NAMES + core_examples.RESEARCH_NAMES),
|
| 54 |
("Ghidra exports", GHIDRA_EXAMPLE_NAMES),
|
| 55 |
)
|
| 56 |
|
|
|
|
| 1 |
+
"""Combine shared core examples with saved frontend completeness pairs."""
|
| 2 |
from pathlib import Path
|
| 3 |
import sys
|
| 4 |
import json
|
|
|
|
| 6 |
ROOT = Path(__file__).resolve().parents[1]
|
| 7 |
sys.path.insert(0, str(ROOT / "core"))
|
| 8 |
|
| 9 |
+
from examples.ui import GHIDRA_EXAMPLE_NAMES, ghidra_metadata
|
|
|
|
|
|
|
| 10 |
from examples import ui as core_examples
|
| 11 |
+
from demo import faultless
|
| 12 |
+
|
| 13 |
+
ORIGINAL_EXAMPLE_NAMES = core_examples.ORIGINAL_EXAMPLE_NAMES + faultless.NAMES
|
| 14 |
+
ORIGINAL_EXAMPLE_LABELS = core_examples.ORIGINAL_EXAMPLE_LABELS + faultless.LABELS
|
| 15 |
+
EXAMPLE_NAMES = ORIGINAL_EXAMPLE_NAMES + GHIDRA_EXAMPLE_NAMES
|
| 16 |
+
EXAMPLE_LABELS = ORIGINAL_EXAMPLE_LABELS + tuple(
|
| 17 |
+
core_examples.EXAMPLE_LABELS[core_examples.EXAMPLE_NAMES.index(name)]
|
| 18 |
+
for name in GHIDRA_EXAMPLE_NAMES)
|
| 19 |
|
| 20 |
|
| 21 |
def example_metadata(name):
|
| 22 |
+
if name in faultless.NAMES:
|
| 23 |
+
return faultless.metadata(name)
|
| 24 |
metadata = core_examples.example_metadata(name)
|
| 25 |
if name == "faultless-linked-list":
|
| 26 |
metadata["expected_verdict"] = "mismatch"
|
|
|
|
| 42 |
|
| 43 |
def prepare_examples(directory, *, include_ghidra=True):
|
| 44 |
rows = dict(zip(core_examples.EXAMPLE_NAMES, core_examples.prepare_examples(directory)))
|
| 45 |
+
rows.update(zip(faultless.NAMES, faultless.prepare_examples(directory)))
|
| 46 |
names = EXAMPLE_NAMES if include_ghidra else tuple(
|
| 47 |
name for name in EXAMPLE_NAMES if name not in GHIDRA_EXAMPLE_NAMES)
|
| 48 |
selected = [rows[name] for name in names]
|
|
|
|
| 59 |
("Types and ABI", ("wide-integer", "float-identity", "signed-zero", "nan-payload",
|
| 60 |
"struct-fields", "struct-indirect", "struct-padding", "pointer-identity")),
|
| 61 |
("Real binaries", ("factor-gcd", "factor-gcd-euclidean", "factor-gcd-even")),
|
| 62 |
+
("Related work", core_examples.FAULTLESS_NAMES + core_examples.RESEARCH_NAMES + faultless.NAMES),
|
| 63 |
("Ghidra exports", GHIDRA_EXAMPLE_NAMES),
|
| 64 |
)
|
| 65 |
|
demo/faultless.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Saved O0 completeness pairs, with explicit runnable interface adaptations."""
|
| 2 |
+
import json
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import subprocess
|
| 5 |
+
import tempfile
|
| 6 |
+
|
| 7 |
+
import cle
|
| 8 |
+
import yaml
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
CASES = Path(__file__).with_name("cases")
|
| 12 |
+
NAMES = ("faultless-my-putchar-count", "faultless-upper", "faultless-print-page-header")
|
| 13 |
+
CASE_NAMES = ("my_putchar_count", "upper", "print_page_header")
|
| 14 |
+
LABELS = ("Faultless: One-byte write", "Faultless: Uppercase loop",
|
| 15 |
+
"Faultless: Static page counter")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def source(name, filename):
|
| 19 |
+
return (CASES / CASE_NAMES[NAMES.index(name)] / filename).read_text()
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def metadata(name):
|
| 23 |
+
info = json.loads(source(name, "metadata.json"))
|
| 24 |
+
info.update(reference=source(name, "original.c"),
|
| 25 |
+
expected_verdict="inconclusive" if name == NAMES[0] else "agree")
|
| 26 |
+
return info
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def prepare_examples(directory):
|
| 30 |
+
output = Path(directory).resolve()
|
| 31 |
+
output.mkdir(parents=True, exist_ok=True)
|
| 32 |
+
rows = []
|
| 33 |
+
with tempfile.TemporaryDirectory(prefix="faultless-completeness-") as temporary:
|
| 34 |
+
temporary = Path(temporary)
|
| 35 |
+
for name in NAMES:
|
| 36 |
+
case = CASES / CASE_NAMES[NAMES.index(name)]
|
| 37 |
+
info = json.loads((case / "runnable.json").read_text())
|
| 38 |
+
original = temporary / "original.c"
|
| 39 |
+
original.write_text(info["declarations"] + source(name, "original.c"))
|
| 40 |
+
stubs = temporary / "callees.c"
|
| 41 |
+
stubs.write_text(info["stubs"])
|
| 42 |
+
binary = output / (name + ".elf")
|
| 43 |
+
flags = ["-O0", "-fno-builtin"]
|
| 44 |
+
subprocess.run(["gcc", *flags, "-fno-pie", "-no-pie", "-fno-stack-protector",
|
| 45 |
+
"-nostdlib", "-Wl,--build-id=none", "-Wl,-e," + info["original_symbol"],
|
| 46 |
+
str(original), str(stubs), "-o", str(binary)],
|
| 47 |
+
check=True, capture_output=True, text=True, timeout=60)
|
| 48 |
+
obj = cle.Loader(str(binary), auto_load_libs=False).main_object
|
| 49 |
+
addresses = {symbol.name: symbol.rebased_addr for symbol in obj.symbols
|
| 50 |
+
if not symbol.is_import}
|
| 51 |
+
bindings = {kind: {candidate: addresses[original] for candidate, original in
|
| 52 |
+
info.get(kind, {}).items()} for kind in ("callees", "globals")}
|
| 53 |
+
rows.append([str(binary), f"{addresses[info['original_symbol']]:#x}",
|
| 54 |
+
(case / "candidate.c").read_text(), yaml.safe_dump(bindings, sort_keys=True),
|
| 55 |
+
info["candidate_symbol"], 100, "0", 1000, "gcc", " ".join(flags),
|
| 56 |
+
yaml.safe_dump(info["interfaces"], sort_keys=True)])
|
| 57 |
+
return rows
|
tests/test_examples.py
CHANGED
|
@@ -5,7 +5,7 @@ from demo import examples
|
|
| 5 |
|
| 6 |
def test_examples_prepare_from_core_checkout(tmp_path):
|
| 7 |
rows = examples.prepare_examples(tmp_path)
|
| 8 |
-
assert len(rows) == len(examples.EXAMPLE_NAMES) ==
|
| 9 |
assert len(examples.GHIDRA_EXAMPLE_NAMES) == 21
|
| 10 |
assert all(Path(row[0]).is_file() for row in rows)
|
| 11 |
assert all(Path(row[0]).is_relative_to(tmp_path) for row in rows)
|
|
@@ -18,7 +18,7 @@ def test_example_groups_cover_catalog_once():
|
|
| 18 |
assert len(members) == len(set(members)) == len(examples.EXAMPLE_NAMES)
|
| 19 |
assert set(members) == set(examples.EXAMPLE_NAMES)
|
| 20 |
groups = examples.example_groups(examples.EXAMPLE_NAMES)
|
| 21 |
-
assert [len(indices) for _, indices in groups] == [6, 7, 8, 3,
|
| 22 |
for _, indices in groups:
|
| 23 |
assert list(indices) == sorted(indices)
|
| 24 |
|
|
|
|
| 5 |
|
| 6 |
def test_examples_prepare_from_core_checkout(tmp_path):
|
| 7 |
rows = examples.prepare_examples(tmp_path)
|
| 8 |
+
assert len(rows) == len(examples.EXAMPLE_NAMES) == 63
|
| 9 |
assert len(examples.GHIDRA_EXAMPLE_NAMES) == 21
|
| 10 |
assert all(Path(row[0]).is_file() for row in rows)
|
| 11 |
assert all(Path(row[0]).is_relative_to(tmp_path) for row in rows)
|
|
|
|
| 18 |
assert len(members) == len(set(members)) == len(examples.EXAMPLE_NAMES)
|
| 19 |
assert set(members) == set(examples.EXAMPLE_NAMES)
|
| 20 |
groups = examples.example_groups(examples.EXAMPLE_NAMES)
|
| 21 |
+
assert [len(indices) for _, indices in groups] == [6, 7, 8, 3, 18, 21]
|
| 22 |
for _, indices in groups:
|
| 23 |
assert list(indices) == sorted(indices)
|
| 24 |
|
tests/test_faultless.py
CHANGED
|
@@ -5,6 +5,7 @@ import pytest
|
|
| 5 |
|
| 6 |
from demo.app import run_verification
|
| 7 |
from demo.examples import EXAMPLE_NAMES, example_metadata
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
@pytest.mark.parametrize('compiler', ['gcc', 'clang'])
|
|
@@ -37,6 +38,10 @@ def test_faultless_frontend_stream(tmp_path, compiler, name, example_rows):
|
|
| 37 |
assert report['counts']['agree'] == seed_count
|
| 38 |
elif name == 'faultless-byte-reinterpretation':
|
| 39 |
assert report['counts'][expected] == seed_count
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
else:
|
| 41 |
assert report['counts']['mismatch'] == seed_count
|
| 42 |
assert all(r['divergence'] == 'call[0]' for r in report['results'])
|
|
|
|
| 5 |
|
| 6 |
from demo.app import run_verification
|
| 7 |
from demo.examples import EXAMPLE_NAMES, example_metadata
|
| 8 |
+
from demo.faultless import NAMES as COMPLETENESS_NAMES
|
| 9 |
|
| 10 |
|
| 11 |
@pytest.mark.parametrize('compiler', ['gcc', 'clang'])
|
|
|
|
| 38 |
assert report['counts']['agree'] == seed_count
|
| 39 |
elif name == 'faultless-byte-reinterpretation':
|
| 40 |
assert report['counts'][expected] == seed_count
|
| 41 |
+
elif name in COMPLETENESS_NAMES:
|
| 42 |
+
assert report['counts'][expected] == seed_count
|
| 43 |
+
if name == 'faultless-my-putchar-count':
|
| 44 |
+
assert all(r['reason'] == 'escaped_frame_differs' for r in report['results'])
|
| 45 |
else:
|
| 46 |
assert report['counts']['mismatch'] == seed_count
|
| 47 |
assert all(r['divergence'] == 'call[0]' for r in report['results'])
|
tests/test_ui.py
CHANGED
|
@@ -10,6 +10,7 @@ from urllib.parse import quote
|
|
| 10 |
|
| 11 |
from demo.examples import (EXAMPLE_NAMES, EXAMPLE_LABELS, ORIGINAL_EXAMPLE_NAMES,
|
| 12 |
GHIDRA_EXAMPLE_NAMES, ROOT, ghidra_metadata)
|
|
|
|
| 13 |
from demo.presentation import json_detail, mismatch_summary
|
| 14 |
from tests.support import check_report
|
| 15 |
|
|
@@ -102,8 +103,8 @@ def test_example_reference_pane_events(tmp_path):
|
|
| 102 |
app.close()
|
| 103 |
|
| 104 |
|
| 105 |
-
@pytest.mark.parametrize("names", [ORIGINAL_EXAMPLE_NAMES, GHIDRA_EXAMPLE_NAMES],
|
| 106 |
-
ids=["original", "ghidra"])
|
| 107 |
def test_queued_ui_upload_streaming_and_download(tmp_path, monkeypatch, names):
|
| 108 |
from gradio_client import Client, handle_file
|
| 109 |
from gradio_client.exceptions import AppError
|
|
|
|
| 10 |
|
| 11 |
from demo.examples import (EXAMPLE_NAMES, EXAMPLE_LABELS, ORIGINAL_EXAMPLE_NAMES,
|
| 12 |
GHIDRA_EXAMPLE_NAMES, ROOT, ghidra_metadata)
|
| 13 |
+
from demo.faultless import NAMES as COMPLETENESS_NAMES
|
| 14 |
from demo.presentation import json_detail, mismatch_summary
|
| 15 |
from tests.support import check_report
|
| 16 |
|
|
|
|
| 103 |
app.close()
|
| 104 |
|
| 105 |
|
| 106 |
+
@pytest.mark.parametrize("names", [ORIGINAL_EXAMPLE_NAMES, GHIDRA_EXAMPLE_NAMES, COMPLETENESS_NAMES],
|
| 107 |
+
ids=["original", "ghidra", "faultless-completeness"])
|
| 108 |
def test_queued_ui_upload_streaming_and_download(tmp_path, monkeypatch, names):
|
| 109 |
from gradio_client import Client, handle_file
|
| 110 |
from gradio_client.exceptions import AppError
|