xref: /aosp_15_r20/external/AFLplusplus/benchmark/benchmark.py (revision 08b48e0b10e97b33e7b60c5b6e2243bd915777f2)
1*08b48e0bSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*08b48e0bSAndroid Build Coastguard Worker# Part of the aflplusplus project, requires Python 3.8+.
3*08b48e0bSAndroid Build Coastguard Worker# Author: Chris Ball <[email protected]>, ported from Marc "van Hauser" Heuse's "benchmark.sh".
4*08b48e0bSAndroid Build Coastguard Workerimport argparse, asyncio, json, multiprocessing, os, platform, re, shutil, sys
5*08b48e0bSAndroid Build Coastguard Workerfrom dataclasses import asdict, dataclass
6*08b48e0bSAndroid Build Coastguard Workerfrom decimal import Decimal
7*08b48e0bSAndroid Build Coastguard Workerfrom enum import Enum, auto
8*08b48e0bSAndroid Build Coastguard Workerfrom pathlib import Path
9*08b48e0bSAndroid Build Coastguard Workerfrom typing import Dict, List, Optional, Tuple
10*08b48e0bSAndroid Build Coastguard Worker
11*08b48e0bSAndroid Build Coastguard Workerblue   = lambda text: f"\033[1;94m{text}\033[0m"; gray = lambda text: f"\033[1;90m{text}\033[0m"
12*08b48e0bSAndroid Build Coastguard Workergreen  = lambda text: f"\033[0;32m{text}\033[0m"; red  = lambda text: f"\033[0;31m{text}\033[0m"
13*08b48e0bSAndroid Build Coastguard Workeryellow = lambda text: f"\033[0;33m{text}\033[0m"
14*08b48e0bSAndroid Build Coastguard Worker
15*08b48e0bSAndroid Build Coastguard Workerclass Mode(Enum):
16*08b48e0bSAndroid Build Coastguard Worker    multicore  = auto()
17*08b48e0bSAndroid Build Coastguard Worker    singlecore = auto()
18*08b48e0bSAndroid Build Coastguard Worker
19*08b48e0bSAndroid Build Coastguard Worker@dataclass
20*08b48e0bSAndroid Build Coastguard Workerclass Target:
21*08b48e0bSAndroid Build Coastguard Worker    source: Path
22*08b48e0bSAndroid Build Coastguard Worker    binary: Path
23*08b48e0bSAndroid Build Coastguard Worker
24*08b48e0bSAndroid Build Coastguard Worker@dataclass
25*08b48e0bSAndroid Build Coastguard Workerclass Run:
26*08b48e0bSAndroid Build Coastguard Worker    execs_per_sec: float
27*08b48e0bSAndroid Build Coastguard Worker    execs_total: float
28*08b48e0bSAndroid Build Coastguard Worker    fuzzers_used: int
29*08b48e0bSAndroid Build Coastguard Worker
30*08b48e0bSAndroid Build Coastguard Worker@dataclass
31*08b48e0bSAndroid Build Coastguard Workerclass Config:
32*08b48e0bSAndroid Build Coastguard Worker    afl_persistent_config: bool
33*08b48e0bSAndroid Build Coastguard Worker    afl_system_config: bool
34*08b48e0bSAndroid Build Coastguard Worker    afl_version: Optional[str]
35*08b48e0bSAndroid Build Coastguard Worker    comment: str
36*08b48e0bSAndroid Build Coastguard Worker    compiler: str
37*08b48e0bSAndroid Build Coastguard Worker    target_arch: str
38*08b48e0bSAndroid Build Coastguard Worker
39*08b48e0bSAndroid Build Coastguard Worker@dataclass
40*08b48e0bSAndroid Build Coastguard Workerclass Hardware:
41*08b48e0bSAndroid Build Coastguard Worker    cpu_fastest_core_mhz: float
42*08b48e0bSAndroid Build Coastguard Worker    cpu_model: str
43*08b48e0bSAndroid Build Coastguard Worker    cpu_threads: int
44*08b48e0bSAndroid Build Coastguard Worker
45*08b48e0bSAndroid Build Coastguard Worker@dataclass
46*08b48e0bSAndroid Build Coastguard Workerclass Results:
47*08b48e0bSAndroid Build Coastguard Worker    config: Optional[Config]
48*08b48e0bSAndroid Build Coastguard Worker    hardware: Optional[Hardware]
49*08b48e0bSAndroid Build Coastguard Worker    targets: Dict[str, Dict[str, Optional[Run]]]
50*08b48e0bSAndroid Build Coastguard Worker
51*08b48e0bSAndroid Build Coastguard Workerall_modes = [Mode.singlecore, Mode.multicore]
52*08b48e0bSAndroid Build Coastguard Workerall_targets = [
53*08b48e0bSAndroid Build Coastguard Worker    Target(source=Path("../utils/persistent_mode/test-instr.c").resolve(), binary=Path("test-instr-persist-shmem")),
54*08b48e0bSAndroid Build Coastguard Worker    Target(source=Path("../test-instr.c").resolve(), binary=Path("test-instr"))
55*08b48e0bSAndroid Build Coastguard Worker]
56*08b48e0bSAndroid Build Coastguard Workermodes = [mode.name for mode in all_modes]
57*08b48e0bSAndroid Build Coastguard Workertargets = [str(target.binary) for target in all_targets]
58*08b48e0bSAndroid Build Coastguard Workercpu_count = multiprocessing.cpu_count()
59*08b48e0bSAndroid Build Coastguard Workerenv_vars = {
60*08b48e0bSAndroid Build Coastguard Worker    "AFL_DISABLE_TRIM": "1", "AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES": "1", "AFL_FAST_CAL": "1",
61*08b48e0bSAndroid Build Coastguard Worker    "AFL_NO_UI": "1", "AFL_TRY_AFFINITY": "1", "PATH": f'{str(Path("../").resolve())}:{os.environ["PATH"]}',
62*08b48e0bSAndroid Build Coastguard Worker}
63*08b48e0bSAndroid Build Coastguard Worker
64*08b48e0bSAndroid Build Coastguard Workerparser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
65*08b48e0bSAndroid Build Coastguard Workerparser.add_argument("-b", "--basedir", help="directory to use for temp files", type=str, default="/tmp/aflpp-benchmark")
66*08b48e0bSAndroid Build Coastguard Workerparser.add_argument("-d", "--debug", help="show verbose debugging output", action="store_true")
67*08b48e0bSAndroid Build Coastguard Workerparser.add_argument("-r", "--runs", help="how many runs to average results over", type=int, default=3)
68*08b48e0bSAndroid Build Coastguard Workerparser.add_argument("-f", "--fuzzers", help="how many afl-fuzz workers to use", type=int, default=cpu_count)
69*08b48e0bSAndroid Build Coastguard Workerparser.add_argument("-m", "--mode", help="pick modes", action="append", default=modes, choices=modes)
70*08b48e0bSAndroid Build Coastguard Workerparser.add_argument("-c", "--comment", help="add a comment about your setup", type=str, default="")
71*08b48e0bSAndroid Build Coastguard Workerparser.add_argument("--cpu", help="override the detected CPU model name", type=str, default="")
72*08b48e0bSAndroid Build Coastguard Workerparser.add_argument("--mhz", help="override the detected CPU MHz", type=str, default="")
73*08b48e0bSAndroid Build Coastguard Workerparser.add_argument(
74*08b48e0bSAndroid Build Coastguard Worker    "-t", "--target", help="pick targets", action="append", default=["test-instr-persist-shmem"], choices=targets
75*08b48e0bSAndroid Build Coastguard Worker)
76*08b48e0bSAndroid Build Coastguard Workerargs = parser.parse_args()
77*08b48e0bSAndroid Build Coastguard Worker# Really unsatisfying argparse behavior: we want a default and to allow multiple choices, but if there's a manual choice
78*08b48e0bSAndroid Build Coastguard Worker# it should override the default.  Seems like we have to remove the default to get that and have correct help text?
79*08b48e0bSAndroid Build Coastguard Workerif len(args.target) > 1:
80*08b48e0bSAndroid Build Coastguard Worker    args.target = args.target[1:]
81*08b48e0bSAndroid Build Coastguard Workerif len(args.mode) > 2:
82*08b48e0bSAndroid Build Coastguard Worker    args.mode = args.mode[2:]
83*08b48e0bSAndroid Build Coastguard Worker
84*08b48e0bSAndroid Build Coastguard Workerchosen_modes = [mode for mode in all_modes if mode.name in args.mode]
85*08b48e0bSAndroid Build Coastguard Workerchosen_targets = [target for target in all_targets if str(target.binary) in args.target]
86*08b48e0bSAndroid Build Coastguard Workerresults = Results(config=None, hardware=None, targets={
87*08b48e0bSAndroid Build Coastguard Worker    str(t.binary): {m.name: None for m in chosen_modes} for t in chosen_targets}
88*08b48e0bSAndroid Build Coastguard Worker)
89*08b48e0bSAndroid Build Coastguard Workerdebug = lambda text: args.debug and print(blue(text))
90*08b48e0bSAndroid Build Coastguard Worker
91*08b48e0bSAndroid Build Coastguard Workerasync def clean_up_tempfiles() -> None:
92*08b48e0bSAndroid Build Coastguard Worker    shutil.rmtree(f"{args.basedir}/in")
93*08b48e0bSAndroid Build Coastguard Worker    for target in chosen_targets:
94*08b48e0bSAndroid Build Coastguard Worker        target.binary.unlink()
95*08b48e0bSAndroid Build Coastguard Worker        for mode in chosen_modes:
96*08b48e0bSAndroid Build Coastguard Worker            shutil.rmtree(f"{args.basedir}/out-{mode.name}-{str(target.binary)}")
97*08b48e0bSAndroid Build Coastguard Worker
98*08b48e0bSAndroid Build Coastguard Workerasync def check_afl_persistent() -> bool:
99*08b48e0bSAndroid Build Coastguard Worker    with open("/proc/cmdline", "r") as cmdline:
100*08b48e0bSAndroid Build Coastguard Worker        return "mitigations=off" in cmdline.read().strip().split(" ")
101*08b48e0bSAndroid Build Coastguard Worker
102*08b48e0bSAndroid Build Coastguard Workerasync def check_afl_system() -> bool:
103*08b48e0bSAndroid Build Coastguard Worker    sysctl = next((s for s in ["sysctl", "/sbin/sysctl"] if shutil.which(s)), None)
104*08b48e0bSAndroid Build Coastguard Worker    if sysctl:
105*08b48e0bSAndroid Build Coastguard Worker        (returncode, stdout, _) = await run_command([sysctl, "kernel.randomize_va_space"])
106*08b48e0bSAndroid Build Coastguard Worker        return returncode == 0 and stdout.decode().rstrip().split(" = ")[1] == "0"
107*08b48e0bSAndroid Build Coastguard Worker    return False
108*08b48e0bSAndroid Build Coastguard Worker
109*08b48e0bSAndroid Build Coastguard Workerasync def prep_env() -> None:
110*08b48e0bSAndroid Build Coastguard Worker    Path(f"{args.basedir}/in").mkdir(exist_ok=True, parents=True)
111*08b48e0bSAndroid Build Coastguard Worker    with open(f"{args.basedir}/in/in.txt", "wb") as seed:
112*08b48e0bSAndroid Build Coastguard Worker        seed.write(b"\x00" * 10240)
113*08b48e0bSAndroid Build Coastguard Worker
114*08b48e0bSAndroid Build Coastguard Workerasync def compile_target(source: Path, binary: Path) -> None:
115*08b48e0bSAndroid Build Coastguard Worker    print(f" [*] Compiling the {binary} fuzzing harness for the benchmark to use.")
116*08b48e0bSAndroid Build Coastguard Worker    (returncode, stdout, stderr) = await run_command(
117*08b48e0bSAndroid Build Coastguard Worker        [str(Path("../afl-clang-lto").resolve()), "-o", str(Path(binary.resolve())), str(Path(source).resolve())]
118*08b48e0bSAndroid Build Coastguard Worker    )
119*08b48e0bSAndroid Build Coastguard Worker    if returncode == 0:
120*08b48e0bSAndroid Build Coastguard Worker        return
121*08b48e0bSAndroid Build Coastguard Worker    print(yellow(f" [*] afl-clang-lto was unable to compile; falling back to afl-cc."))
122*08b48e0bSAndroid Build Coastguard Worker    (returncode, stdout, stderr) = await run_command(
123*08b48e0bSAndroid Build Coastguard Worker        [str(Path("../afl-cc").resolve()), "-o", str(Path(binary.resolve())), str(Path(source).resolve())]
124*08b48e0bSAndroid Build Coastguard Worker    )
125*08b48e0bSAndroid Build Coastguard Worker    if returncode != 0:
126*08b48e0bSAndroid Build Coastguard Worker        sys.exit(red(f" [*] Error: afl-cc is unable to compile: {stderr.decode()} {stdout.decode()}"))
127*08b48e0bSAndroid Build Coastguard Worker
128*08b48e0bSAndroid Build Coastguard Workerasync def run_command(cmd: List[str]) -> Tuple[Optional[int], bytes, bytes]:
129*08b48e0bSAndroid Build Coastguard Worker    debug(f"Launching command: {cmd} with env {env_vars}")
130*08b48e0bSAndroid Build Coastguard Worker    p = await asyncio.create_subprocess_exec(
131*08b48e0bSAndroid Build Coastguard Worker        *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, env=env_vars
132*08b48e0bSAndroid Build Coastguard Worker    )
133*08b48e0bSAndroid Build Coastguard Worker    stdout, stderr = await p.communicate()
134*08b48e0bSAndroid Build Coastguard Worker    debug(f"Output: {stdout.decode()} {stderr.decode()}")
135*08b48e0bSAndroid Build Coastguard Worker    return (p.returncode, stdout, stderr)
136*08b48e0bSAndroid Build Coastguard Worker
137*08b48e0bSAndroid Build Coastguard Workerasync def check_deps() -> None:
138*08b48e0bSAndroid Build Coastguard Worker    if not (plat := platform.system()) == "Linux": sys.exit(red(f" [*] {plat} is not supported by this script yet."))
139*08b48e0bSAndroid Build Coastguard Worker    if not os.access(Path("../afl-fuzz").resolve(), os.X_OK) and os.access(Path("../afl-cc").resolve(), os.X_OK) and (
140*08b48e0bSAndroid Build Coastguard Worker        os.path.exists(Path("../SanitizerCoveragePCGUARD.so").resolve())):
141*08b48e0bSAndroid Build Coastguard Worker        sys.exit(red(" [*] Compile AFL++: we need afl-fuzz, afl-clang-fast and SanitizerCoveragePCGUARD.so built."))
142*08b48e0bSAndroid Build Coastguard Worker
143*08b48e0bSAndroid Build Coastguard Worker    (returncode, stdout, stderr) = await run_command([str(Path("../afl-cc").resolve()), "-v"])
144*08b48e0bSAndroid Build Coastguard Worker    if returncode != 0:
145*08b48e0bSAndroid Build Coastguard Worker        sys.exit(red(f" [*] Error: afl-cc -v returned: {stderr.decode()} {stdout.decode()}"))
146*08b48e0bSAndroid Build Coastguard Worker    compiler = ""
147*08b48e0bSAndroid Build Coastguard Worker    target_arch = ""
148*08b48e0bSAndroid Build Coastguard Worker    for line in stderr.decode().split("\n"):
149*08b48e0bSAndroid Build Coastguard Worker        if "clang version" in line:
150*08b48e0bSAndroid Build Coastguard Worker            compiler = line
151*08b48e0bSAndroid Build Coastguard Worker        elif m := re.match(r"^Target: (.*)", line):
152*08b48e0bSAndroid Build Coastguard Worker            target_arch = m.group(1)
153*08b48e0bSAndroid Build Coastguard Worker
154*08b48e0bSAndroid Build Coastguard Worker    # Pick some sample settings from afl-{persistent,system}-config to try to see whether they were run.
155*08b48e0bSAndroid Build Coastguard Worker    afl_pc = await check_afl_persistent()
156*08b48e0bSAndroid Build Coastguard Worker    afl_sc = await check_afl_system()
157*08b48e0bSAndroid Build Coastguard Worker    if not afl_pc:
158*08b48e0bSAndroid Build Coastguard Worker        print(yellow(f" [*] afl-persistent-config did not run; run it to improve performance (and decrease security)."))
159*08b48e0bSAndroid Build Coastguard Worker    if not afl_sc:
160*08b48e0bSAndroid Build Coastguard Worker        print(yellow(f" [*] afl-system-config did not run; run it to improve performance (and decrease security)."))
161*08b48e0bSAndroid Build Coastguard Worker    results.config = Config(afl_persistent_config=afl_pc, afl_system_config=afl_sc, afl_version="",
162*08b48e0bSAndroid Build Coastguard Worker                            comment=args.comment, compiler=compiler, target_arch=target_arch)
163*08b48e0bSAndroid Build Coastguard Worker
164*08b48e0bSAndroid Build Coastguard Workerasync def colon_values(filename: str, searchKey: str) -> List[str]:
165*08b48e0bSAndroid Build Coastguard Worker    """Return a colon-separated value given a key in a file, e.g. 'cpu MHz         : 4976.109')"""
166*08b48e0bSAndroid Build Coastguard Worker    with open(filename, "r") as fh:
167*08b48e0bSAndroid Build Coastguard Worker        kv_pairs = (line.split(": ", 1) for line in fh if ": " in line)
168*08b48e0bSAndroid Build Coastguard Worker        v_list = [v.rstrip() for k, v in kv_pairs if k.rstrip() == searchKey]
169*08b48e0bSAndroid Build Coastguard Worker        return v_list
170*08b48e0bSAndroid Build Coastguard Worker
171*08b48e0bSAndroid Build Coastguard Workerasync def describe_afl_config() -> str:
172*08b48e0bSAndroid Build Coastguard Worker   if results.config is None:
173*08b48e0bSAndroid Build Coastguard Worker       return "unknown"
174*08b48e0bSAndroid Build Coastguard Worker   elif results.config.afl_persistent_config and results.config.afl_system_config:
175*08b48e0bSAndroid Build Coastguard Worker       return "both"
176*08b48e0bSAndroid Build Coastguard Worker   elif results.config.afl_persistent_config:
177*08b48e0bSAndroid Build Coastguard Worker       return "persistent"
178*08b48e0bSAndroid Build Coastguard Worker   elif results.config.afl_system_config:
179*08b48e0bSAndroid Build Coastguard Worker       return "system"
180*08b48e0bSAndroid Build Coastguard Worker   else:
181*08b48e0bSAndroid Build Coastguard Worker       return "none"
182*08b48e0bSAndroid Build Coastguard Worker
183*08b48e0bSAndroid Build Coastguard Workerasync def save_benchmark_results() -> None:
184*08b48e0bSAndroid Build Coastguard Worker    """Append a single row to the benchmark results in JSON Lines format (which is simple to write and diff)."""
185*08b48e0bSAndroid Build Coastguard Worker    with open("benchmark-results.jsonl", "a") as jsonfile:
186*08b48e0bSAndroid Build Coastguard Worker        json.dump(asdict(results), jsonfile, sort_keys=True)
187*08b48e0bSAndroid Build Coastguard Worker        jsonfile.write("\n")
188*08b48e0bSAndroid Build Coastguard Worker        print(blue(f" [*] Results have been written to the {jsonfile.name} file."))
189*08b48e0bSAndroid Build Coastguard Worker    with open("COMPARISON.md", "r+") as comparisonfile:
190*08b48e0bSAndroid Build Coastguard Worker        described_config = await describe_afl_config()
191*08b48e0bSAndroid Build Coastguard Worker        aflconfig = described_config.ljust(12)
192*08b48e0bSAndroid Build Coastguard Worker        if results.hardware is None:
193*08b48e0bSAndroid Build Coastguard Worker            return
194*08b48e0bSAndroid Build Coastguard Worker        cpu_model = results.hardware.cpu_model.ljust(51)
195*08b48e0bSAndroid Build Coastguard Worker        if cpu_model in comparisonfile.read():
196*08b48e0bSAndroid Build Coastguard Worker            print(blue(f" [*] Results have not been written to the COMPARISON.md file; this CPU is already present."))
197*08b48e0bSAndroid Build Coastguard Worker            return
198*08b48e0bSAndroid Build Coastguard Worker        cpu_mhz = str(round(results.hardware.cpu_fastest_core_mhz)).ljust(5)
199*08b48e0bSAndroid Build Coastguard Worker        if not "test-instr-persist-shmem" in results.targets or \
200*08b48e0bSAndroid Build Coastguard Worker           not "multicore" in results.targets["test-instr-persist-shmem"] or \
201*08b48e0bSAndroid Build Coastguard Worker           not "singlecore" in results.targets["test-instr-persist-shmem"] or \
202*08b48e0bSAndroid Build Coastguard Worker           results.targets["test-instr-persist-shmem"]["singlecore"] is None or \
203*08b48e0bSAndroid Build Coastguard Worker           results.targets["test-instr-persist-shmem"]["multicore"] is None:
204*08b48e0bSAndroid Build Coastguard Worker            return
205*08b48e0bSAndroid Build Coastguard Worker        single = str(round(results.targets["test-instr-persist-shmem"]["singlecore"].execs_per_sec)).ljust(10)
206*08b48e0bSAndroid Build Coastguard Worker        multi = str(round(results.targets["test-instr-persist-shmem"]["multicore"].execs_per_sec)).ljust(9)
207*08b48e0bSAndroid Build Coastguard Worker        cores = str(args.fuzzers).ljust(7)
208*08b48e0bSAndroid Build Coastguard Worker        comparisonfile.write(f"{cpu_model} | {cpu_mhz} | {cores} | {single} | {multi} | {aflconfig} |\n")
209*08b48e0bSAndroid Build Coastguard Worker        print(blue(f" [*] Results have been written to the COMPARISON.md file."))
210*08b48e0bSAndroid Build Coastguard Worker    with open("COMPARISON.md", "r") as comparisonfile:
211*08b48e0bSAndroid Build Coastguard Worker        print(comparisonfile.read())
212*08b48e0bSAndroid Build Coastguard Worker
213*08b48e0bSAndroid Build Coastguard Worker
214*08b48e0bSAndroid Build Coastguard Workerasync def main() -> None:
215*08b48e0bSAndroid Build Coastguard Worker    try:
216*08b48e0bSAndroid Build Coastguard Worker        await clean_up_tempfiles()
217*08b48e0bSAndroid Build Coastguard Worker    except FileNotFoundError:
218*08b48e0bSAndroid Build Coastguard Worker        pass
219*08b48e0bSAndroid Build Coastguard Worker    await check_deps()
220*08b48e0bSAndroid Build Coastguard Worker    if args.mhz:
221*08b48e0bSAndroid Build Coastguard Worker        cpu_mhz = float(args.mhz)
222*08b48e0bSAndroid Build Coastguard Worker    else:
223*08b48e0bSAndroid Build Coastguard Worker        cpu_mhz_str = await colon_values("/proc/cpuinfo", "cpu MHz")
224*08b48e0bSAndroid Build Coastguard Worker        if len(cpu_mhz_str) == 0:
225*08b48e0bSAndroid Build Coastguard Worker            cpu_mhz_str.append("0")
226*08b48e0bSAndroid Build Coastguard Worker        cpu_mhz = max([float(c) for c in cpu_mhz_str]) # use the fastest CPU MHz for now
227*08b48e0bSAndroid Build Coastguard Worker    if args.cpu:
228*08b48e0bSAndroid Build Coastguard Worker        cpu_model = [args.cpu]
229*08b48e0bSAndroid Build Coastguard Worker    else:
230*08b48e0bSAndroid Build Coastguard Worker        cpu_model = await colon_values("/proc/cpuinfo", "model name") or [""]
231*08b48e0bSAndroid Build Coastguard Worker    results.hardware = Hardware(cpu_fastest_core_mhz=cpu_mhz, cpu_model=cpu_model[0], cpu_threads=cpu_count)
232*08b48e0bSAndroid Build Coastguard Worker    await prep_env()
233*08b48e0bSAndroid Build Coastguard Worker    print(f" [*] Ready, starting benchmark...")
234*08b48e0bSAndroid Build Coastguard Worker    for target in chosen_targets:
235*08b48e0bSAndroid Build Coastguard Worker        await compile_target(target.source, target.binary)
236*08b48e0bSAndroid Build Coastguard Worker        binary = str(target.binary)
237*08b48e0bSAndroid Build Coastguard Worker        for mode in chosen_modes:
238*08b48e0bSAndroid Build Coastguard Worker            if mode == Mode.multicore:
239*08b48e0bSAndroid Build Coastguard Worker                print(blue(f" [*] Using {args.fuzzers} fuzzers for multicore fuzzing "), end="")
240*08b48e0bSAndroid Build Coastguard Worker                print(blue("(use --fuzzers to override)." if args.fuzzers == cpu_count else f"(the default is {cpu_count})"))
241*08b48e0bSAndroid Build Coastguard Worker            execs_per_sec, execs_total = ([] for _ in range(2))
242*08b48e0bSAndroid Build Coastguard Worker            for run_idx in range(0, args.runs):
243*08b48e0bSAndroid Build Coastguard Worker                print(gray(f" [*] {mode.name} {binary} run {run_idx+1} of {args.runs}, execs/s: "), end="", flush=True)
244*08b48e0bSAndroid Build Coastguard Worker                fuzzers = range(0, args.fuzzers if mode == Mode.multicore else 1)
245*08b48e0bSAndroid Build Coastguard Worker                outdir = f"{args.basedir}/out-{mode.name}-{binary}"
246*08b48e0bSAndroid Build Coastguard Worker                cmds = []
247*08b48e0bSAndroid Build Coastguard Worker                for fuzzer_idx, afl in enumerate(fuzzers):
248*08b48e0bSAndroid Build Coastguard Worker                    name = ["-o", outdir, "-M" if fuzzer_idx == 0 else "-S", str(afl)]
249*08b48e0bSAndroid Build Coastguard Worker                    cmds.append(["afl-fuzz", "-i", f"{args.basedir}/in"] + name + ["-s", "123", "-V10", "-D", f"./{binary}"])
250*08b48e0bSAndroid Build Coastguard Worker                # Prepare the afl-fuzz tasks, and then block while waiting for them to finish.
251*08b48e0bSAndroid Build Coastguard Worker                fuzztasks = [run_command(cmds[cpu]) for cpu in fuzzers]
252*08b48e0bSAndroid Build Coastguard Worker                await asyncio.gather(*fuzztasks)
253*08b48e0bSAndroid Build Coastguard Worker                afl_versions = await colon_values(f"{outdir}/0/fuzzer_stats", "afl_version")
254*08b48e0bSAndroid Build Coastguard Worker                if results.config:
255*08b48e0bSAndroid Build Coastguard Worker                    results.config.afl_version = afl_versions[0]
256*08b48e0bSAndroid Build Coastguard Worker                # Our score is the sum of all execs_per_sec entries in fuzzer_stats files for the run.
257*08b48e0bSAndroid Build Coastguard Worker                sectasks = [colon_values(f"{outdir}/{afl}/fuzzer_stats", "execs_per_sec") for afl in fuzzers]
258*08b48e0bSAndroid Build Coastguard Worker                all_execs_per_sec = await asyncio.gather(*sectasks)
259*08b48e0bSAndroid Build Coastguard Worker                execs = sum([Decimal(count[0]) for count in all_execs_per_sec])
260*08b48e0bSAndroid Build Coastguard Worker                print(green(execs))
261*08b48e0bSAndroid Build Coastguard Worker                execs_per_sec.append(execs)
262*08b48e0bSAndroid Build Coastguard Worker                # Also gather execs_total and total_run_time for this run.
263*08b48e0bSAndroid Build Coastguard Worker                exectasks = [colon_values(f"{outdir}/{afl}/fuzzer_stats", "execs_done") for afl in fuzzers]
264*08b48e0bSAndroid Build Coastguard Worker                all_execs_total = await asyncio.gather(*exectasks)
265*08b48e0bSAndroid Build Coastguard Worker                execs_total.append(sum([Decimal(count[0]) for count in all_execs_total]))
266*08b48e0bSAndroid Build Coastguard Worker
267*08b48e0bSAndroid Build Coastguard Worker            # (Using float() because Decimal() is not JSON-serializable.)
268*08b48e0bSAndroid Build Coastguard Worker            avg_afl_execs_per_sec = round(Decimal(sum(execs_per_sec) / len(execs_per_sec)), 2)
269*08b48e0bSAndroid Build Coastguard Worker            afl_execs_total = int(sum([Decimal(execs) for execs in execs_total]))
270*08b48e0bSAndroid Build Coastguard Worker            run = Run(execs_per_sec=float(avg_afl_execs_per_sec), execs_total=afl_execs_total, fuzzers_used=len(fuzzers))
271*08b48e0bSAndroid Build Coastguard Worker            results.targets[binary][mode.name] = run
272*08b48e0bSAndroid Build Coastguard Worker            print(f" [*] Average execs/sec for this test across all runs was: {green(avg_afl_execs_per_sec)}")
273*08b48e0bSAndroid Build Coastguard Worker            if (((max(execs_per_sec) - min(execs_per_sec)) / avg_afl_execs_per_sec) * 100) > 15:
274*08b48e0bSAndroid Build Coastguard Worker                print(yellow(" [*] The difference between your slowest and fastest runs was >15%, maybe try again?"))
275*08b48e0bSAndroid Build Coastguard Worker
276*08b48e0bSAndroid Build Coastguard Worker    await clean_up_tempfiles()
277*08b48e0bSAndroid Build Coastguard Worker    await save_benchmark_results()
278*08b48e0bSAndroid Build Coastguard Worker
279*08b48e0bSAndroid Build Coastguard Workerif __name__ == "__main__":
280*08b48e0bSAndroid Build Coastguard Worker    asyncio.run(main())
281*08b48e0bSAndroid Build Coastguard Worker
282