aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Ball <chris@printf.net>2023-11-11 01:45:13 +0000
committerChris Ball <chris@printf.net>2023-11-10 15:34:32 -0800
commit16993bba8fa359b093d44fe395044bfd392c19f3 (patch)
treeffa35006431a4cb1666a9244e9121650320052d3
parent3bfd194d469c04da7c74a3964bf31ef40605a178 (diff)
downloadafl++-16993bba8fa359b093d44fe395044bfd392c19f3.tar.gz
benchmark: Add support for COMPARISON file
-rw-r--r--benchmark/COMPARISON8
-rw-r--r--benchmark/benchmark-results.jsonl2
-rw-r--r--benchmark/benchmark.py130
3 files changed, 91 insertions, 49 deletions
diff --git a/benchmark/COMPARISON b/benchmark/COMPARISON
index 55ab94b4..d8750e34 100644
--- a/benchmark/COMPARISON
+++ b/benchmark/COMPARISON
@@ -1,4 +1,4 @@
-CPU | Mz | exec/s | afl-*-config |
-========================================|======|========|==============|
-CPU 12th Gen Intel(R) Core(TM) i7-1270P | 4200 | 12750 | both |
-AMD EPYC 7282 16-Core Processor | 3190 | 10060 | both |
+CPU | MHz | singlecore | multicore | afl-*-config |
+===============================|=======|============|===========|==============|
+Apple Mac Studio M2 Ultra 2023 | 3500 | 174386 | 1112585 | both |
+Intel(R) Core(TM) i9-9900K CPU | 4999 | 133706 | 1169989 | both |
diff --git a/benchmark/benchmark-results.jsonl b/benchmark/benchmark-results.jsonl
index eef18384..1d52402d 100644
--- a/benchmark/benchmark-results.jsonl
+++ b/benchmark/benchmark-results.jsonl
@@ -546,3 +546,5 @@
{"config": {"afl_persistent_config": true, "afl_system_config": true, "afl_version": "++4.09a", "comment": "AWS EC2 r6a.48xlarge spot instance", "compiler": "clang version 15.0.7 (Amazon Linux 15.0.7-3.amzn2023.0.1)", "target_arch": "x86_64-amazon-linux-gnu"}, "hardware": {"cpu_fastest_core_mhz": 3599.413, "cpu_model": "AMD EPYC 7R13 Processor", "cpu_threads": 192}, "targets": {"test-instr-persist-shmem": {"multicore": {"afl_execs_per_sec": 334191.98, "afl_execs_total": 102894843, "fuzzers_used": 198, "run_end": "2023-10-01 06:31:03.545925", "run_start": "2023-10-01 06:27:53.127882", "total_execs_per_sec": 270150.29, "total_run_time": 380.88}}}}
{"config": {"afl_persistent_config": true, "afl_system_config": true, "afl_version": "++4.09a", "comment": "AWS EC2 r6a.48xlarge spot instance", "compiler": "clang version 15.0.7 (Amazon Linux 15.0.7-3.amzn2023.0.1)", "target_arch": "x86_64-amazon-linux-gnu"}, "hardware": {"cpu_fastest_core_mhz": 3599.521, "cpu_model": "AMD EPYC 7R13 Processor", "cpu_threads": 192}, "targets": {"test-instr-persist-shmem": {"multicore": {"afl_execs_per_sec": 332929.11, "afl_execs_total": 103414536, "fuzzers_used": 199, "run_end": "2023-10-01 06:37:27.645981", "run_start": "2023-10-01 06:34:15.843433", "total_execs_per_sec": 269442.01, "total_run_time": 383.81}}}}
{"config": {"afl_persistent_config": true, "afl_system_config": true, "afl_version": "++4.09a", "comment": "AWS EC2 r6a.48xlarge spot instance", "compiler": "clang version 15.0.7 (Amazon Linux 15.0.7-3.amzn2023.0.1)", "target_arch": "x86_64-amazon-linux-gnu"}, "hardware": {"cpu_fastest_core_mhz": 3339.7, "cpu_model": "AMD EPYC 7R13 Processor", "cpu_threads": 192}, "targets": {"test-instr-persist-shmem": {"multicore": {"afl_execs_per_sec": 331957.22, "afl_execs_total": 103934201, "fuzzers_used": 200, "run_end": "2023-10-01 06:43:54.782368", "run_start": "2023-10-01 06:40:41.553700", "total_execs_per_sec": 268674.91, "total_run_time": 386.84}}}}
+{"config": {"afl_persistent_config": true, "afl_system_config": true, "afl_version": "++4.09a", "comment": "", "compiler": "Ubuntu clang version 16.0.6", "target_arch": "aarch64-unknown-linux-gnu"}, "hardware": {"cpu_fastest_core_mhz": 3500.0, "cpu_model": "Apple Mac Studio M2 Ultra 2023", "cpu_threads": 16}, "targets": {"test-instr-persist-shmem": {"multicore": {"afl_execs_per_sec": 1172198.55, "afl_execs_total": 12472081, "fuzzers_used": 16, "run_end": "2023-11-11 02:20:21.969027", "run_start": "2023-11-11 02:20:18.282561", "total_execs_per_sec": 1128695.11, "total_run_time": 11.05}, "singlecore": {"afl_execs_per_sec": 172105.52, "afl_execs_total": 779505, "fuzzers_used": 1, "run_end": "2023-11-11 02:20:10.920659", "run_start": "2023-11-11 02:20:09.459765", "total_execs_per_sec": 170570.02, "total_run_time": 4.57}}}}
+{"config": {"afl_persistent_config": true, "afl_system_config": true, "afl_version": "++4.09a", "comment": "", "compiler": "", "target_arch": "x86_64-pc-linux-gnu"}, "hardware": {"cpu_fastest_core_mhz": 4998.583, "cpu_model": "Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz", "cpu_threads": 16}, "targets": {"test-instr-persist-shmem": {"multicore": {"afl_execs_per_sec": 1193793.35, "afl_execs_total": 12472080, "fuzzers_used": 16, "run_end": "2023-11-10 10:25:40.047364", "run_start": "2023-11-10 10:25:36.510399", "total_execs_per_sec": 1169988.74, "total_run_time": 10.66}, "singlecore": {"afl_execs_per_sec": 134426.26, "afl_execs_total": 779505, "fuzzers_used": 1, "run_end": "2023-11-10 10:25:29.381317", "run_start": "2023-11-10 10:25:27.420959", "total_execs_per_sec": 133705.83, "total_run_time": 5.83}}}}
diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py
index e0dea299..6fde621b 100644
--- a/benchmark/benchmark.py
+++ b/benchmark/benchmark.py
@@ -42,8 +42,8 @@ class Config:
@dataclass
class Hardware:
- cpu_fastest_core_mhz: Optional[float]
- cpu_model: Optional[str]
+ cpu_fastest_core_mhz: float
+ cpu_model: str
cpu_threads: int
@dataclass
@@ -57,80 +57,85 @@ all_targets = [
Target(source=Path("../utils/persistent_mode/test-instr.c").resolve(), binary=Path("test-instr-persist-shmem")),
Target(source=Path("../test-instr.c").resolve(), binary=Path("test-instr"))
]
-mode_names = [mode.name for mode in all_modes]
-target_names = [str(target.binary) for target in all_targets]
+modes = [mode.name for mode in all_modes]
+targets = [str(target.binary) for target in all_targets]
cpu_count = multiprocessing.cpu_count()
+env_vars = {
+ "AFL_BENCH_JUST_ONE": "1", "AFL_DISABLE_TRIM": "1", "AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES": "1",
+ "AFL_NO_UI": "1", "AFL_TRY_AFFINITY": "1", "PATH": f'{str(Path("../").resolve())}:{os.environ["PATH"]}',
+}
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-b", "--basedir", help="directory to use for temp files", type=str, default="/tmp/aflpp-benchmark")
parser.add_argument("-d", "--debug", help="show verbose debugging output", action="store_true")
parser.add_argument("-r", "--runs", help="how many runs to average results over", type=int, default=3)
parser.add_argument("-f", "--fuzzers", help="how many afl-fuzz workers to use", type=int, default=cpu_count)
-parser.add_argument("-m", "--mode", help="pick modes", action="append", default=["multicore"], choices=mode_names)
+parser.add_argument("-m", "--mode", help="pick modes", action="append", default=modes, choices=modes)
parser.add_argument("-c", "--comment", help="add a comment about your setup", type=str, default="")
+parser.add_argument("--cpu", help="override the detected CPU model name", type=str, default="")
+parser.add_argument("--mhz", help="override the detected CPU MHz", type=str, default="")
parser.add_argument(
- "-t", "--target", help="pick targets", action="append", default=["test-instr-persist-shmem"], choices=target_names
+ "-t", "--target", help="pick targets", action="append", default=["test-instr-persist-shmem"], choices=targets
)
args = parser.parse_args()
# Really unsatisfying argparse behavior: we want a default and to allow multiple choices, but if there's a manual choice
# it should override the default. Seems like we have to remove the default to get that and have correct help text?
-if len(args.target) > 1: args.target = args.target[1:]
-if len(args.mode) > 1: args.mode = args.mode[1:]
-
-targets = [target for target in all_targets if str(target.binary) in args.target]
-modes = [mode for mode in all_modes if mode.name in args.mode]
-results = Results(config=None, hardware=None, targets={str(t.binary): {m.name: None for m in modes} for t in targets})
+if len(args.target) > 1:
+ args.target = args.target[1:]
+if len(args.mode) > 2:
+ args.mode = args.mode[2:]
+
+chosen_modes = [mode for mode in all_modes if mode.name in args.mode]
+chosen_targets = [target for target in all_targets if str(target.binary) in args.target]
+results = Results(config=None, hardware=None, targets={
+ str(t.binary): {m.name: None for m in chosen_modes} for t in chosen_targets}
+)
debug = lambda text: args.debug and print(blue(text))
-if Mode.multicore in modes:
+if Mode.multicore in chosen_modes:
print(blue(f" [*] Using {args.fuzzers} fuzzers for multicore fuzzing "), end="")
print(blue("(use --fuzzers to override)" if args.fuzzers == cpu_count else f"(the default is {cpu_count})"))
async def clean_up_tempfiles() -> None:
shutil.rmtree(f"{args.basedir}/in")
- for target in targets:
+ for target in chosen_targets:
target.binary.unlink()
- for mode in modes:
+ for mode in chosen_modes:
shutil.rmtree(f"{args.basedir}/out-{mode.name}-{str(target.binary)}")
async def check_afl_persistent() -> bool:
- with open("/proc/cmdline", "r") as cpuinfo:
- return "mitigations=off" in cpuinfo.read().split(" ")
+ with open("/proc/cmdline", "r") as cmdline:
+ return "mitigations=off" in cmdline.read().strip().split(" ")
async def check_afl_system() -> bool:
sysctl = next((s for s in ["sysctl", "/sbin/sysctl"] if shutil.which(s)), None)
if sysctl:
- (returncode, stdout, _) = await run_command([sysctl, "kernel.randomize_va_space"], None)
+ (returncode, stdout, _) = await run_command([sysctl, "kernel.randomize_va_space"])
return returncode == 0 and stdout.decode().rstrip().split(" = ")[1] == "0"
return False
-async def prep_env() -> dict:
+async def prep_env() -> None:
Path(f"{args.basedir}/in").mkdir(exist_ok=True, parents=True)
- with open(f"{args.basedir}/in/in.txt", "wb") as seed: seed.write(b"\x00" * 10240)
- return {
- "AFL_BENCH_JUST_ONE": "1", "AFL_DISABLE_TRIM": "1", "AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES": "1",
- "AFL_NO_UI": "1", "AFL_TRY_AFFINITY": "1", "PATH": str(Path("../").resolve()),
- }
+ with open(f"{args.basedir}/in/in.txt", "wb") as seed:
+ seed.write(b"\x00" * 10240)
async def compile_target(source: Path, binary: Path) -> None:
print(f" [*] Compiling the {binary} fuzzing harness for the benchmark to use.")
(returncode, stdout, stderr) = await run_command(
- [str(Path("../afl-clang-lto").resolve()), "-o", str(Path(binary.resolve())), str(Path(source).resolve())],
- env={"AFL_LLVM_INSTRUMENT": "PCGUARD"},
+ [str(Path("../afl-clang-lto").resolve()), "-o", str(Path(binary.resolve())), str(Path(source).resolve())]
)
- if returncode != 0:
- print(yellow(f" [*] afl-clang-lto was unable to compile; falling back to afl-cc."))
-
+ if returncode == 0:
+ return
+ print(yellow(f" [*] afl-clang-lto was unable to compile; falling back to afl-cc."))
(returncode, stdout, stderr) = await run_command(
- [str(Path("../afl-cc").resolve()), "-o", str(Path(binary.resolve())), str(Path(source).resolve())],
- env={"AFL_LLVM_INSTRUMENT": "PCGUARD"},
+ [str(Path("../afl-cc").resolve()), "-o", str(Path(binary.resolve())), str(Path(source).resolve())]
)
if returncode != 0:
sys.exit(red(f" [*] Error: afl-cc is unable to compile: {stderr.decode()} {stdout.decode()}"))
-async def run_command(cmd: list[str], env: Union[dict, None]) -> tuple[Union[int, None], bytes, bytes]:
- debug(f"Launching command: {cmd} with env {env}")
+async def run_command(cmd: list[str]) -> tuple[Union[int, None], bytes, bytes]:
+ debug(f"Launching command: {cmd} with env {env_vars}")
p = await asyncio.create_subprocess_exec(
- *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, env=env
+ *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, env=env_vars
)
stdout, stderr = await p.communicate()
debug(f"Output: {stdout.decode()} {stderr.decode()}")
@@ -142,13 +147,13 @@ async def check_deps() -> None:
os.path.exists(Path("../SanitizerCoveragePCGUARD.so").resolve())):
sys.exit(red(" [*] Compile AFL++: we need afl-fuzz, afl-clang-fast and SanitizerCoveragePCGUARD.so built."))
- (returncode, stdout, stderr) = await run_command([str(Path("../afl-cc").resolve()), "-v"], env={})
+ (returncode, stdout, stderr) = await run_command([str(Path("../afl-cc").resolve()), "-v"])
if returncode != 0:
sys.exit(red(f" [*] Error: afl-cc -v returned: {stderr.decode()} {stdout.decode()}"))
compiler = ""
target_arch = ""
for line in stderr.decode().split("\n"):
- if m := re.match(r"^(clang version .*)", line):
+ if m := re.match(r"(.* clang version .*?)\s", line):
compiler = m.group(1)
elif m := re.match(r"^Target: (.*)", line):
target_arch = m.group(1)
@@ -160,7 +165,6 @@ async def check_deps() -> None:
print(yellow(f" [*] afl-persistent-config did not run; run it to improve performance (and decrease security)."))
if not afl_sc:
print(yellow(f" [*] afl-system-config did not run; run it to improve performance (and decrease security)."))
-
results.config = Config(afl_persistent_config=afl_pc, afl_system_config=afl_sc, afl_version="",
comment=args.comment, compiler=compiler, target_arch=target_arch)
@@ -171,12 +175,41 @@ async def colon_values(filename: str, searchKey: str) -> list[str]:
v_list = [v.rstrip() for k, v in kv_pairs if k.rstrip() == searchKey]
return v_list
+async def describe_afl_config() -> str:
+ if results.config is None:
+ return "unknown"
+ elif results.config.afl_persistent_config and results.config.afl_system_config:
+ return "both"
+ elif results.config.afl_persistent_config:
+ return "persistent"
+ elif results.config.afl_system_config:
+ return "system"
+ else:
+ return "none"
+
async def save_benchmark_results() -> None:
"""Append a single row to the benchmark results in JSON Lines format (which is simple to write and diff)."""
with open("benchmark-results.jsonl", "a") as jsonfile:
json.dump(asdict(results), jsonfile, sort_keys=True)
jsonfile.write("\n")
print(blue(f" [*] Results have been written to {jsonfile.name}"))
+ with open("COMPARISON", "a") as comparisonfile:
+ described_config = await describe_afl_config()
+ if results.hardware is None:
+ return
+ cpu_model = results.hardware.cpu_model.ljust(42)
+ cpu_mhz = str(round(results.hardware.cpu_fastest_core_mhz)).ljust(5)
+ if "test-instr-persist-shmem" in results.targets and "multicore" in results.targets["test-instr-persist-shmem"]:
+ if results.targets["test-instr-persist-shmem"]["singlecore"] is None or \
+ results.targets["test-instr-persist-shmem"]["multicore"] is None:
+ return
+ single = str(round(results.targets["test-instr-persist-shmem"]["singlecore"].total_execs_per_sec)).ljust(10)
+ multi = str(round(results.targets["test-instr-persist-shmem"]["multicore"].total_execs_per_sec)).ljust(9)
+ if len(cpu_model) > 30:
+ cpu_model = cpu_model[:30]
+ comparisonfile.write(f"{cpu_model} | {cpu_mhz} | {single} | {multi} | {described_config.ljust(12)} |\n")
+ with open("COMPARISON", "r") as comparisonfile:
+ print(comparisonfile.read())
async def main() -> None:
@@ -185,17 +218,24 @@ async def main() -> None:
except FileNotFoundError:
pass
await check_deps()
- cpu_mhz_str = await colon_values("/proc/cpuinfo", "cpu MHz")
- cpu_mhz = max([float(c) for c in cpu_mhz_str]) # use the fastest CPU MHz for now
- cpu_model = await colon_values("/proc/cpuinfo", "model name")
- # Only record the first core's speed for now, even though it can vary between cores.
+ if args.mhz:
+ cpu_mhz = float(args.mhz)
+ else:
+ cpu_mhz_str = await colon_values("/proc/cpuinfo", "cpu MHz")
+ if len(cpu_mhz_str) == 0:
+ cpu_mhz_str.append("0")
+ cpu_mhz = max([float(c) for c in cpu_mhz_str]) # use the fastest CPU MHz for now
+ if args.cpu:
+ cpu_model = [args.cpu]
+ else:
+ cpu_model = await colon_values("/proc/cpuinfo", "model name") or [""]
results.hardware = Hardware(cpu_fastest_core_mhz=cpu_mhz, cpu_model=cpu_model[0], cpu_threads=cpu_count)
- env_vars = await prep_env()
+ await prep_env()
print(f" [*] Ready, starting benchmark...")
- for target in targets:
+ for target in chosen_targets:
await compile_target(target.source, target.binary)
binary = str(target.binary)
- for mode in modes:
+ for mode in chosen_modes:
afl_execs_per_sec, execs_total, run_time_total = ([] for _ in range(3))
for run_idx in range(0, args.runs):
print(gray(f" [*] {mode.name} {binary} run {run_idx+1} of {args.runs}, execs/s: "), end="", flush=True)
@@ -207,7 +247,7 @@ async def main() -> None:
cmds.append(["afl-fuzz", "-i", f"{args.basedir}/in"] + name + ["-s", "123", "-D", f"./{binary}"])
# Prepare the afl-fuzz tasks, and then block while waiting for them to finish.
- fuzztasks = [run_command(cmds[cpu], env_vars) for cpu in fuzzers]
+ fuzztasks = [run_command(cmds[cpu]) for cpu in fuzzers]
start_time = datetime.datetime.now()
await asyncio.gather(*fuzztasks)
end_time = datetime.datetime.now()