123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- #!/usr/bin/env node
- "use strict";
- const assert = require("assert").strict;
- const fs = require("fs");
- const os = require("os");
- const path = require("path");
- const { spawn, spawnSync } = require("child_process");
- const DEBUG = process.env.DEBUG || false;
- // Maximum number of gdb processes to spawn in parallel
- const MAX_PARALLEL_PROCS = +process.env.MAX_PARALLEL_PROCS || 32;
- // Default to true for now. It's slower, but async execution occasionally gets stuck
- const SYNC_GDB_EXECUTION = process.env.SYNC_GDB_EXECUTION || true;
- // Usage: console.log(CYAN_FMT, "This shows up in cyan!")
- const CYAN_FMT = "\x1b[36m%s\x1b[0m";
- const YELLOW_FMT = "\x1b[33m%s\x1b[0m";
- const TEST_DIR = __dirname + "/";
- const BUILD_DIR = path.join(TEST_DIR, "build");
- const GDB_DEFAULT_ARGS = [
- "-batch",
- "--eval-command=set disable-randomization off", // allow execution on docker
- `--command=${TEST_DIR}gdb-extract-def`,
- // Set a breakpoint "in the future", which all the test binaries can then share
- "--eval-command=set breakpoint pending on",
- "--eval-command=break loop",
- "--eval-command=catch signal SIGFPE",
- "--eval-command=catch signal SIGILL",
- "--eval-command=catch signal SIGSEGV",
- "--eval-command=catch signal SIGBUS",
- ];
- /* Split up an array into semi-evenly sized chunks */
- function chunk(source, num_chunks)
- {
- const arr = source.slice();
- const ret = [];
- let rem_chunks = num_chunks;
- while(rem_chunks > 0)
- {
- // We guarantee that the entire array is processed because when rem_chunk=1 -> len/1 = len
- ret.push(arr.splice(0, Math.floor(arr.length / rem_chunks)));
- rem_chunks--;
- }
- return ret;
- }
- assert(
- JSON.stringify(chunk("0 0 1 1 2 2 2 3 3 3".split(" "), 4)) ===
- JSON.stringify([["0", "0"],
- ["1", "1"],
- ["2", "2", "2"],
- ["3", "3", "3"]]),
- "Chunk"
- );
- const dir_files = fs.readdirSync(BUILD_DIR);
- const test_files = dir_files.filter(name => {
- return name.endsWith(".img");
- }).map(name => {
- return name.slice(0, -4);
- }).filter(name => {
- const bin_file = path.join(BUILD_DIR, `${name}.img`);
- const fixture_file = path.join(BUILD_DIR, `${name}.fixture`);
- if(!fs.existsSync(fixture_file))
- {
- return true;
- }
- return fs.statSync(bin_file).mtime > fs.statSync(fixture_file).mtime;
- });
- const nr_of_cpus = Math.min(
- os.cpus().length || 1,
- test_files.length,
- MAX_PARALLEL_PROCS
- );
- if(SYNC_GDB_EXECUTION)
- {
- console.log("[+] Generating %d fixtures", test_files.length);
- }
- else
- {
- console.log("[+] Using %d cpus to generate %d fixtures", nr_of_cpus, test_files.length);
- }
- const workloads = chunk(test_files, nr_of_cpus);
- function test_arg_formatter(workload)
- {
- return workload.map(test => {
- const test_path = path.join(BUILD_DIR, test);
- return `--eval-command=extract-state ${test_path}.img ${test_path}.fixture`;
- });
- }
- function set_proc_handlers(proc, n)
- {
- proc.on("close", (code) => on_proc_close(code, n));
- if(DEBUG)
- {
- proc.stdout.on("data", (data) => {
- console.log(CYAN_FMT, "stdout", `${n}: ${data}`);
- });
- proc.stderr.on("data", (data) => {
- console.log(YELLOW_FMT, "stderr", `${n}: ${data}`);
- });
- }
- }
- function on_proc_close(code, n)
- {
- console.log(`[+] child process ${n} exited with code ${code}`);
- if(code !== 0)
- {
- process.exit(code);
- }
- }
- for(let i = 0; i < nr_of_cpus; i++)
- {
- const gdb_args = GDB_DEFAULT_ARGS.concat(test_arg_formatter(workloads[i]));
- if(DEBUG)
- {
- console.log(CYAN_FMT, "[DEBUG]", "gdb", gdb_args.join(" "));
- }
- if(SYNC_GDB_EXECUTION || nr_of_cpus === 1)
- {
- const { status: code } = spawnSync("gdb", gdb_args);
- on_proc_close(code, i);
- }
- else
- {
- const gdb = spawn("gdb", gdb_args);
- set_proc_handlers(gdb, i);
- }
- }
|