diff options
Diffstat (limited to 'build-aux/lint-bin')
-rwxr-xr-x | build-aux/lint-bin | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/build-aux/lint-bin b/build-aux/lint-bin new file mode 100755 index 0000000..91f1612 --- /dev/null +++ b/build-aux/lint-bin @@ -0,0 +1,142 @@ +#!/usr/bin/env bash +# build-aux/lint-bin - Lint final binary images +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +set -euE -o pipefail +shopt -s extglob + +# There are several exisi files we can use: +# +# Binaries: +# - ${elf} : firmware image with debug symbols and relocationd data +# - ${elf%.elf}.bin : raw firmware image +# - ${elf%.elf}.hex : .bin as Intel HEX +# - ${elf%.elf}.uf2 : .bin as USB Flashing Format (UF2) +# +# Textual info: +# - ${elf%.elf}.dis : `objdump --section-headers ${elf}; objdump --disassemble ${elf}; picotool coprodis --quiet ${elf}` +# - ${elf}.map : `ld --print-map` info +# - ${elf%.elf}_stack.c : `stack.c.gen` + +RED=$(tput setaf 1) +RESET=$(tput sgr0) + +err() { + printf "${RED}%s${RESET}: %s\n" "$1" "$2" >&2 + r=1 +} + +# Input is `ld --print-map` format. +# +# Output is a series of lines in the format "symbol location size +# source". Whitespace may seem silly. +objdump_globals() { + sed -E -n '/^ \.t?(data|bss)\./{ / 0x/{ p; D; }; N; s/\n/ /; p; }' <"$1" +} + +readelf_funcs() { + local in_elffile + in_elffile=$1 + + readelf --syms --wide -- "$in_elffile" | + awk '$4 == "FUNC" { print $8 }' +} + +lint_globals() { + local in_mapfile + in_mapfile=$1 + + local rel_base + rel_base=${in_mapfile#build/*/} + rel_base=${rel_base%/*} + + local topdir + topdir=$PWD + + { + echo 'Source Symbol Size' + objdump_globals "$in_mapfile" | + { + cd "$rel_base" + total=0 + while read -r symbol addr size source; do + if ((addr == 0)); then + continue + fi + case "$source" in + /*) + # libg.a(whatever.o) -> libg.a + source="${source%(*)}" + # resolve `..` components + source="$(realpath --canonicalize-missing --no-symlinks -- "$source")" + ;; + CMakeFiles/*.dir/*.@(obj|o)) + # CMakeFiles/sbc_harnes_objs.dir/... + source="${source#CMakeFiles/*.dir/}" + source="${source%.@(obj|o)}" + source="${source//__/..}" + source="$(realpath --canonicalize-missing --no-symlinks --relative-to="$topdir" -- "$source")" + ;; + esac + printf "%s %s 0x%04x (%'d)\n" "$source" "$symbol" "$size" "$size" + total=$((total + size)) + done + printf "~ Total 0x%04x (%'d)\n" "$total" "$total" + } | + LC_COLLATE=C sort + } | column -t +} + +lint_stack() { + local in_elffile + in_elffile=$1 + + IFS='' + while read -r line; do + func=${line#$'\t'} + if [[ $line == $'\t'* ]]; then + err "$in_elffile" "function in binary but not _stack.c: ${func}" + else + err "$in_elffile" "function in _stack.c but not binary: ${func}" + fi + done < <( + comm -3 \ + <(sed -En 's/^included: (.*:)?//p' "${in_elffile%.elf}_stack.c" | sort -u) \ + <(readelf_funcs "$in_elffile" | sed -E -e 's/\.part\.[0-9]*$//' -e 's/^__(.*)_veneer$/\1/' | sort -u) + ) +} + +lint_func_blocklist() { + local in_elffile + in_elffile=$1 + + local blocklist=( + gpio_default_irq_handler + ) + + while read -r func; do + err "$in_elffile" "Contains blocklisted function: ${func}" + done < <(readelf --syms --wide -- "$in_elffile" | + awk '$4 == "FUNC" { print $8 }' | + grep -Fx "${blocklist[@]/#/-e}") +} + +main() { + r=0 + + local elf + for elf in "$@"; do + { + echo 'Global variables:' + lint_globals "${elf}.map" | sed 's/^/ /' + } >"${elf%.elf}.lint.globals" + (lint_stack "$elf") &>"${elf%.elf}.lint.stack" + lint_func_blocklist "$elf" + done + + return $r +} + +main "$@" |