Files
rusty-minic/tools/run_arm_asm.py
T

178 lines
5.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""Compile an ARM assembly file and run it with qemu-arm-static."""
from __future__ import annotations
import argparse
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
def parse_args() -> argparse.Namespace:
if "--" in sys.argv:
separator_index = sys.argv.index("--")
tool_args = sys.argv[1:separator_index]
program_args = sys.argv[separator_index + 1 :]
else:
tool_args = sys.argv[1:]
program_args = []
parser = argparse.ArgumentParser(
description=(
"Compile an ARM assembly file with arm-linux-gnueabihf-gcc, "
"run it with qemu-arm-static, then print stdout and $?."
)
)
parser.add_argument("asm_file", type=Path, help="path to the assembly file")
parser.add_argument(
"--cc",
default="arm-linux-gnueabihf-gcc",
help="cross compiler to use (default: arm-linux-gnueabihf-gcc)",
)
parser.add_argument(
"--qemu",
default="qemu-arm-static",
help="ARM qemu runner to use (default: qemu-arm-static)",
)
parser.add_argument(
"-o",
"--output",
type=Path,
help="output executable path; defaults to an auto-cleaned temporary file",
)
parser.add_argument(
"-g",
"--gdb",
action="store_true",
help="compile with -g and run qemu in gdb stub mode",
)
parser.add_argument(
"--gdb-port",
default="1234",
metavar="PORT",
help="qemu gdb stub port used with -g/--gdb (default: 1234)",
)
parser.add_argument(
"--gdb-command",
help=(
"gdb command shown in debug instructions "
"(default: first available of arm-linux-gnueabihf-gdb, gdb-multiarch, gdb)"
),
)
parser.add_argument(
"--no-static",
action="store_true",
help="do not pass -static to the compiler",
)
parser.add_argument(
"--gcc-arg",
action="append",
default=[],
help="extra argument passed to gcc; repeat for multiple args",
)
parser.add_argument(
"--std-c",
type=Path,
default=Path("tests/std.c"),
help="C runtime source compiled with the assembly file (default: tests/std.c)",
)
parser.add_argument(
"--no-std-c",
action="store_true",
help="do not compile tests/std.c with the assembly file",
)
args = parser.parse_args(tool_args)
args.program_args = program_args
return args
def require_command(command: str) -> None:
if shutil.which(command) is None:
print(f"error: command not found: {command}", file=sys.stderr)
sys.exit(127)
def run_command(command: list[str]) -> subprocess.CompletedProcess[bytes]:
try:
return subprocess.run(command, capture_output=True, check=False)
except OSError as err:
print(f"error: failed to run {command[0]}: {err}", file=sys.stderr)
sys.exit(127)
def choose_gdb_command(requested_command: str | None) -> str:
if requested_command:
return requested_command
for command in ("arm-linux-gnueabihf-gdb", "gdb-multiarch", "gdb"):
if shutil.which(command) is not None:
return command
return "gdb-multiarch"
def main() -> int:
args = parse_args()
asm_file = args.asm_file
if not asm_file.is_file():
print(f"error: assembly file not found: {asm_file}", file=sys.stderr)
return 2
std_c = args.std_c
if not args.no_std_c and not std_c.is_file():
print(f"error: std C file not found: {std_c}", file=sys.stderr)
return 2
require_command(args.cc)
require_command(args.qemu)
with tempfile.TemporaryDirectory(prefix="run-arm-asm-") as temp_dir:
output = args.output or Path(temp_dir) / asm_file.with_suffix("").name
compile_cmd = [args.cc]
if not args.no_static:
compile_cmd.append("-static")
if args.gdb:
compile_cmd.append("-g")
compile_cmd.extend(args.gcc_arg)
compile_cmd.append(str(asm_file))
if not args.no_std_c:
compile_cmd.append(str(std_c))
compile_cmd.extend(["-o", str(output)])
compile_result = run_command(compile_cmd)
sys.stdout.buffer.write(compile_result.stdout)
sys.stderr.buffer.write(compile_result.stderr)
if compile_result.returncode != 0:
return compile_result.returncode
qemu_cmd = [args.qemu]
if args.gdb:
qemu_cmd.extend(["-g", args.gdb_port])
resolved_output = output.resolve()
gdb_command = choose_gdb_command(args.gdb_command)
print(f"executable: {resolved_output}", file=sys.stderr, flush=True)
print(
f"gdb: {gdb_command} {resolved_output} "
f"-ex 'target remote :{args.gdb_port}'",
file=sys.stderr,
flush=True,
)
print("gdb: use 'si' or 'c'; do not use 'run'", file=sys.stderr, flush=True)
qemu_cmd.extend([str(output), *args.program_args])
run_result = run_command(qemu_cmd)
sys.stdout.buffer.write(run_result.stdout)
sys.stderr.buffer.write(run_result.stderr)
if run_result.stdout and not run_result.stdout.endswith(b"\n"):
print()
print(f"$? = {run_result.returncode}")
return 0
if __name__ == "__main__":
raise SystemExit(main())