212 lines
6.4 KiB
Python
212 lines
6.4 KiB
Python
# pyright: reportUnknownMemberType=false
|
|
"""
|
|
Tools for working with build info and tool paths.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Any, Dict
|
|
|
|
from ci.util.paths import BUILD
|
|
|
|
|
|
@dataclass
|
|
class Tools:
|
|
as_path: Path
|
|
ld_path: Path
|
|
objcopy_path: Path
|
|
objdump_path: Path
|
|
cpp_filt_path: Path
|
|
nm_path: Path
|
|
|
|
|
|
def load_tools(build_info_path: Path) -> Tools:
|
|
build_info: Dict[str, Any] = json.loads(build_info_path.read_text())
|
|
board_info: Dict[str, Any] = build_info[next(iter(build_info))]
|
|
aliases: Dict[str, str] = board_info["aliases"]
|
|
as_path = Path(aliases["as"])
|
|
ld_path = Path(aliases["ld"])
|
|
objcopy_path = Path(aliases["objcopy"])
|
|
objdump_path = Path(aliases["objdump"])
|
|
cpp_filt_path = Path(aliases["c++filt"])
|
|
nm_path = Path(aliases["nm"])
|
|
if sys.platform == "win32":
|
|
as_path = as_path.with_suffix(".exe")
|
|
ld_path = ld_path.with_suffix(".exe")
|
|
objcopy_path = objcopy_path.with_suffix(".exe")
|
|
objdump_path = objdump_path.with_suffix(".exe")
|
|
cpp_filt_path = cpp_filt_path.with_suffix(".exe")
|
|
nm_path = nm_path.with_suffix(".exe")
|
|
out = Tools(as_path, ld_path, objcopy_path, objdump_path, cpp_filt_path, nm_path)
|
|
tools = [as_path, ld_path, objcopy_path, objdump_path, cpp_filt_path, nm_path]
|
|
for tool in tools:
|
|
if not tool.exists():
|
|
raise FileNotFoundError(f"Tool not found: {tool}")
|
|
return out
|
|
|
|
|
|
def _list_builds() -> list[Path]:
|
|
str_paths = os.listdir(BUILD)
|
|
paths = [BUILD / p for p in str_paths]
|
|
dirs = [p for p in paths if p.is_dir()]
|
|
return dirs
|
|
|
|
|
|
def _check_build(build: Path) -> bool:
|
|
# 1. should contain a build_info.json file
|
|
# 2. should contain a .pio/build directory
|
|
has_build_info = (build / "build_info.json").exists()
|
|
has_pio_build = (build / ".pio" / "build").exists()
|
|
return has_build_info and has_pio_build
|
|
|
|
|
|
def _prompt_build() -> Path:
|
|
builds = _list_builds()
|
|
if not builds:
|
|
print("Error: No builds found", file=sys.stderr)
|
|
sys.exit(1)
|
|
print("Select a build:")
|
|
for i, build in enumerate(builds):
|
|
print(f" [{i}]: {build}")
|
|
while True:
|
|
try:
|
|
which = int(input("Enter the number of the build to use: "))
|
|
if 0 <= which < len(builds):
|
|
valid = _check_build(BUILD / builds[which])
|
|
if valid:
|
|
return BUILD / builds[which]
|
|
print("Error: Invalid build", file=sys.stderr)
|
|
else:
|
|
print("Error: Invalid selection", file=sys.stderr)
|
|
continue
|
|
except ValueError:
|
|
print("Error: Invalid input", file=sys.stderr)
|
|
continue
|
|
|
|
|
|
def _prompt_object_file(build: Path) -> Path:
|
|
# Look for object files in .pio/build directory
|
|
build_dir = build / ".pio" / "build"
|
|
object_files: list[Path] = []
|
|
|
|
# Walk through build directory to find .o files
|
|
for root, _, files in os.walk(build_dir):
|
|
for file in files:
|
|
if file.endswith(".o") and "FrameworkArduino" not in file:
|
|
full_path = Path(root) / file
|
|
if "FrameworkArduino" not in full_path.parts:
|
|
object_files.append(full_path)
|
|
|
|
if not object_files:
|
|
print("Error: No object files found", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
print("\nSelect an object file:")
|
|
for i, obj_file in enumerate(object_files):
|
|
print(f" [{i}]: {obj_file.relative_to(build_dir)}")
|
|
|
|
while True:
|
|
try:
|
|
which = int(input("Enter the number of the object file to use: "))
|
|
if 0 <= which < len(object_files):
|
|
return object_files[which]
|
|
print("Error: Invalid selection", file=sys.stderr)
|
|
except ValueError:
|
|
print("Error: Invalid input", file=sys.stderr)
|
|
continue
|
|
|
|
|
|
def cli() -> None:
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="Dump object file information using build tools"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"build_path",
|
|
type=Path,
|
|
nargs="?",
|
|
help="Path to build directory containing build info JSON file",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--symbols", action="store_true", help="Dump symbol table using nm"
|
|
)
|
|
parser.add_argument(
|
|
"--disassemble", action="store_true", help="Dump disassembly using objdump"
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
build_path = args.build_path
|
|
symbols = args.symbols
|
|
disassemble = args.disassemble
|
|
|
|
# Check if object file was provided and exists
|
|
if build_path is None:
|
|
build_path = _prompt_build()
|
|
else:
|
|
if not _check_build(build_path):
|
|
print("Error: Invalid build directory", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
assert build_path is not None
|
|
assert build_path
|
|
|
|
build_info_path = build_path / "build_info.json"
|
|
assert build_info_path.exists(), f"File not found: {build_info_path}"
|
|
|
|
tools = load_tools(build_info_path)
|
|
|
|
if not symbols and not disassemble:
|
|
while True:
|
|
print(
|
|
"Error: Please specify at least one action to perform", file=sys.stderr
|
|
)
|
|
action = input(
|
|
"Enter 's' to dump symbols, 'd' to disassemble, or 'q' to quit: "
|
|
)
|
|
if action == "s":
|
|
symbols = True
|
|
break
|
|
elif action == "d":
|
|
disassemble = True
|
|
break
|
|
elif action == "q":
|
|
sys.exit(0)
|
|
else:
|
|
print("Error: Invalid action", file=sys.stderr)
|
|
|
|
object_file = _prompt_object_file(build_path)
|
|
if symbols:
|
|
import subprocess
|
|
|
|
cmd_str = subprocess.list2cmdline(
|
|
[str(tools.objdump_path), str(object_file), "--syms"]
|
|
)
|
|
print(f"Running command: {cmd_str}")
|
|
subprocess.run([str(tools.objdump_path), str(object_file)])
|
|
|
|
if disassemble:
|
|
import subprocess
|
|
|
|
cmd_str = subprocess.list2cmdline(
|
|
[str(tools.objdump_path), "-d", str(object_file)]
|
|
)
|
|
print(f"Running command: {cmd_str}")
|
|
subprocess.run([str(tools.objdump_path), "-d", str(object_file)])
|
|
|
|
if not (symbols or disassemble):
|
|
parser.print_help()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
cli()
|
|
except KeyboardInterrupt:
|
|
print("Exiting...")
|
|
sys.exit(1)
|