summaryrefslogtreecommitdiff
path: root/build-aux/lint-bin
blob: c487f360ea532fc16a9ee2f10ac44973e32d1d7d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/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 's/\.part\.[0-9]*$//' | 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 "$@"