197 lines
6 KiB
Python
197 lines
6 KiB
Python
import argparse
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
|
|
from ci.bin_2_elf import bin_to_elf
|
|
from ci.elf import dump_symbol_sizes
|
|
from ci.map_dump import map_dump
|
|
|
|
|
|
def cpp_filt(cpp_filt_path: Path, input_text: str) -> str:
|
|
"""
|
|
Demangle C++ symbols using c++filt.
|
|
|
|
Args:
|
|
cpp_filt_path (Path): Path to c++filt executable.
|
|
input_text (str): Text to demangle.
|
|
|
|
Returns:
|
|
str: Demangled text.
|
|
"""
|
|
if not cpp_filt_path.exists():
|
|
raise FileNotFoundError(f"cppfilt not found at '{cpp_filt_path}'")
|
|
command = [str(cpp_filt_path), "-t", "-n"]
|
|
process = subprocess.Popen(
|
|
command,
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True,
|
|
)
|
|
stdout, stderr = process.communicate(input=input_text)
|
|
if process.returncode != 0:
|
|
raise RuntimeError(f"Error running c++filt: {stderr}")
|
|
return stdout
|
|
|
|
|
|
def demangle_gnu_linkonce_symbols(cpp_filt_path: Path, map_text: str) -> str:
|
|
"""
|
|
Demangle .gnu.linkonce.t symbols in the map file.
|
|
|
|
Args:
|
|
cpp_filt_path (Path): Path to c++filt executable.
|
|
map_text (str): Content of the map file.
|
|
|
|
Returns:
|
|
str: Map file content with demangled symbols.
|
|
"""
|
|
# Extract all .gnu.linkonce.t symbols
|
|
pattern = r"\.gnu\.linkonce\.t\.(.+?)\s"
|
|
matches = re.findall(pattern, map_text)
|
|
|
|
if not matches:
|
|
return map_text
|
|
|
|
# Create a block of text with the extracted symbols
|
|
symbols_block = "\n".join(matches)
|
|
|
|
# Demangle the symbols
|
|
demangled_block = cpp_filt(cpp_filt_path, symbols_block)
|
|
|
|
# Create a dictionary of mangled to demangled symbols
|
|
demangled_dict = dict(zip(matches, demangled_block.strip().split("\n")))
|
|
|
|
# Replace the mangled symbols with demangled ones in the original text
|
|
for mangled, demangled in demangled_dict.items():
|
|
map_text = map_text.replace(
|
|
f".gnu.linkonce.t.{mangled}", f".gnu.linkonce.t.{demangled}"
|
|
)
|
|
|
|
return map_text
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description="Convert a binary file to ELF using map file."
|
|
)
|
|
parser.add_argument("--first", action="store_true", help="Inspect the first board")
|
|
parser.add_argument("--cwd", type=Path, help="Custom working directory")
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def load_build_info(build_info_path: Path) -> dict:
|
|
"""
|
|
Load build information from a JSON file.
|
|
|
|
Args:
|
|
build_info_path (Path): Path to the build_info.json file.
|
|
|
|
Returns:
|
|
dict: Parsed JSON data.
|
|
"""
|
|
if not build_info_path.exists():
|
|
raise FileNotFoundError(f"Build info JSON not found at '{build_info_path}'")
|
|
return json.loads(build_info_path.read_text())
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
if args.cwd:
|
|
root_build_dir = args.cwd / ".build"
|
|
else:
|
|
root_build_dir = Path(".build")
|
|
|
|
board_dirs = [d for d in root_build_dir.iterdir() if d.is_dir()]
|
|
if not board_dirs:
|
|
print(f"No board directories found in {root_build_dir.absolute()}")
|
|
return 1
|
|
|
|
print("Available boards:")
|
|
for i, board_dir in enumerate(board_dirs):
|
|
print(f"[{i}]: {board_dir.name}")
|
|
|
|
which = (
|
|
0
|
|
if args.first
|
|
else int(input("Enter the number of the board you want to inspect: "))
|
|
)
|
|
board_dir = board_dirs[which]
|
|
build_info_json = board_dir / "build_info.json"
|
|
|
|
build_info = load_build_info(build_info_json)
|
|
board = board_dir.name
|
|
board_info = build_info.get(board) or build_info[next(iter(build_info))]
|
|
|
|
# Validate paths from build_info.json
|
|
elf_path = Path(board_info.get("prog_path", ""))
|
|
if not elf_path.exists():
|
|
print(
|
|
f"Error: ELF path '{elf_path}' does not exist. Check the 'prog_path' in build_info.json."
|
|
)
|
|
return 1
|
|
|
|
bin_file = elf_path.with_suffix(".bin")
|
|
if not bin_file.exists():
|
|
# use .hex or .uf2 if .bin doesn't exist
|
|
bin_file = elf_path.with_suffix(".hex")
|
|
if not bin_file.exists():
|
|
bin_file = elf_path.with_suffix(".uf2")
|
|
if not bin_file.exists():
|
|
print(f"Error: Binary file not found for '{elf_path}'")
|
|
return 1
|
|
cpp_filt_path = Path(board_info["aliases"]["c++filt"])
|
|
ld_path = Path(board_info["aliases"]["ld"])
|
|
as_path = Path(board_info["aliases"]["as"])
|
|
nm_path = Path(board_info["aliases"]["nm"])
|
|
objcopy_path = Path(board_info["aliases"]["objcopy"])
|
|
nm_path = Path(board_info["aliases"]["nm"])
|
|
map_file = board_dir / "firmware.map"
|
|
if not map_file.exists():
|
|
# Search for the map file
|
|
map_file = bin_file.with_suffix(".map")
|
|
if not map_file.exists():
|
|
possible_map_files = list(board_dir.glob("**/firmware.map"))
|
|
if possible_map_files:
|
|
map_file = possible_map_files[0]
|
|
else:
|
|
print("Error: firmware.map file not found")
|
|
return 1
|
|
|
|
try:
|
|
with TemporaryDirectory() as temp_dir:
|
|
temp_dir_path = Path(temp_dir)
|
|
output_elf = bin_to_elf(
|
|
bin_file,
|
|
map_file,
|
|
as_path,
|
|
ld_path,
|
|
objcopy_path,
|
|
temp_dir_path / "output.elf",
|
|
)
|
|
out = dump_symbol_sizes(nm_path, cpp_filt_path, output_elf)
|
|
print(out)
|
|
except Exception as e:
|
|
print(
|
|
f"Error while converting binary to ELF, binary analysis will not work on this build: {e}"
|
|
)
|
|
|
|
map_dump(map_file)
|
|
|
|
# Demangle .gnu.linkonce.t symbols and print map file
|
|
print("\n##################################################")
|
|
print("# Map file dump:")
|
|
print("##################################################\n")
|
|
map_text = map_file.read_text()
|
|
demangled_map_text = demangle_gnu_linkonce_symbols(cpp_filt_path, map_text)
|
|
print(demangled_map_text)
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|