1*ac7bd094SKris Van Hees#!/usr/bin/gawk -f
2*ac7bd094SKris Van Hees# SPDX-License-Identifier: GPL-2.0
3*ac7bd094SKris Van Hees# verify_builtin_ranges.awk: Verify address range data for builtin modules
4*ac7bd094SKris Van Hees# Written by Kris Van Hees <[email protected]>
5*ac7bd094SKris Van Hees#
6*ac7bd094SKris Van Hees# Usage: verify_builtin_ranges.awk modules.builtin.ranges System.map \
7*ac7bd094SKris Van Hees#				   modules.builtin vmlinux.map vmlinux.o.map
8*ac7bd094SKris Van Hees#
9*ac7bd094SKris Van Hees
10*ac7bd094SKris Van Hees# Return the module name(s) (if any) associated with the given object.
11*ac7bd094SKris Van Hees#
12*ac7bd094SKris Van Hees# If we have seen this object before, return information from the cache.
13*ac7bd094SKris Van Hees# Otherwise, retrieve it from the corresponding .cmd file.
14*ac7bd094SKris Van Hees#
15*ac7bd094SKris Van Heesfunction get_module_info(fn, mod, obj, s) {
16*ac7bd094SKris Van Hees	if (fn in omod)
17*ac7bd094SKris Van Hees		return omod[fn];
18*ac7bd094SKris Van Hees
19*ac7bd094SKris Van Hees	if (match(fn, /\/[^/]+$/) == 0)
20*ac7bd094SKris Van Hees		return "";
21*ac7bd094SKris Van Hees
22*ac7bd094SKris Van Hees	obj = fn;
23*ac7bd094SKris Van Hees	mod = "";
24*ac7bd094SKris Van Hees	fn = substr(fn, 1, RSTART) "." substr(fn, RSTART + 1) ".cmd";
25*ac7bd094SKris Van Hees	if (getline s <fn == 1) {
26*ac7bd094SKris Van Hees		if (match(s, /DKBUILD_MODFILE=['"]+[^'"]+/) > 0) {
27*ac7bd094SKris Van Hees			mod = substr(s, RSTART + 16, RLENGTH - 16);
28*ac7bd094SKris Van Hees			gsub(/['"]/, "", mod);
29*ac7bd094SKris Van Hees		} else if (match(s, /RUST_MODFILE=[^ ]+/) > 0)
30*ac7bd094SKris Van Hees			mod = substr(s, RSTART + 13, RLENGTH - 13);
31*ac7bd094SKris Van Hees	} else {
32*ac7bd094SKris Van Hees		print "ERROR: Failed to read: " fn "\n\n" \
33*ac7bd094SKris Van Hees		      "  For kernels built with O=<objdir>, cd to <objdir>\n" \
34*ac7bd094SKris Van Hees		      "  and execute this script as ./source/scripts/..." \
35*ac7bd094SKris Van Hees		      >"/dev/stderr";
36*ac7bd094SKris Van Hees		close(fn);
37*ac7bd094SKris Van Hees		total = 0;
38*ac7bd094SKris Van Hees		exit(1);
39*ac7bd094SKris Van Hees	}
40*ac7bd094SKris Van Hees	close(fn);
41*ac7bd094SKris Van Hees
42*ac7bd094SKris Van Hees	# A single module (common case) also reflects objects that are not part
43*ac7bd094SKris Van Hees	# of a module.  Some of those objects have names that are also a module
44*ac7bd094SKris Van Hees	# name (e.g. core).  We check the associated module file name, and if
45*ac7bd094SKris Van Hees	# they do not match, the object is not part of a module.
46*ac7bd094SKris Van Hees	if (mod !~ / /) {
47*ac7bd094SKris Van Hees		if (!(mod in mods))
48*ac7bd094SKris Van Hees			mod = "";
49*ac7bd094SKris Van Hees	}
50*ac7bd094SKris Van Hees
51*ac7bd094SKris Van Hees	gsub(/([^/ ]*\/)+/, "", mod);
52*ac7bd094SKris Van Hees	gsub(/-/, "_", mod);
53*ac7bd094SKris Van Hees
54*ac7bd094SKris Van Hees	# At this point, mod is a single (valid) module name, or a list of
55*ac7bd094SKris Van Hees	# module names (that do not need validation).
56*ac7bd094SKris Van Hees	omod[obj] = mod;
57*ac7bd094SKris Van Hees
58*ac7bd094SKris Van Hees	return mod;
59*ac7bd094SKris Van Hees}
60*ac7bd094SKris Van Hees
61*ac7bd094SKris Van Hees# Return a representative integer value for a given hexadecimal address.
62*ac7bd094SKris Van Hees#
63*ac7bd094SKris Van Hees# Since all kernel addresses fall within the same memory region, we can safely
64*ac7bd094SKris Van Hees# strip off the first 6 hex digits before performing the hex-to-dec conversion,
65*ac7bd094SKris Van Hees# thereby avoiding integer overflows.
66*ac7bd094SKris Van Hees#
67*ac7bd094SKris Van Heesfunction addr2val(val) {
68*ac7bd094SKris Van Hees	sub(/^0x/, "", val);
69*ac7bd094SKris Van Hees	if (length(val) == 16)
70*ac7bd094SKris Van Hees		val = substr(val, 5);
71*ac7bd094SKris Van Hees	return strtonum("0x" val);
72*ac7bd094SKris Van Hees}
73*ac7bd094SKris Van Hees
74*ac7bd094SKris Van Hees# Determine the kernel build directory to use (default is .).
75*ac7bd094SKris Van Hees#
76*ac7bd094SKris Van HeesBEGIN {
77*ac7bd094SKris Van Hees	if (ARGC < 6) {
78*ac7bd094SKris Van Hees		print "Syntax: verify_builtin_ranges.awk <ranges-file> <system-map>\n" \
79*ac7bd094SKris Van Hees		      "          <builtin-file> <vmlinux-map> <vmlinux-o-map>\n" \
80*ac7bd094SKris Van Hees		      >"/dev/stderr";
81*ac7bd094SKris Van Hees		total = 0;
82*ac7bd094SKris Van Hees		exit(1);
83*ac7bd094SKris Van Hees	}
84*ac7bd094SKris Van Hees}
85*ac7bd094SKris Van Hees
86*ac7bd094SKris Van Hees# (1) Load the built-in module address range data.
87*ac7bd094SKris Van Hees#
88*ac7bd094SKris Van HeesARGIND == 1 {
89*ac7bd094SKris Van Hees	ranges[FNR] = $0;
90*ac7bd094SKris Van Hees	rcnt++;
91*ac7bd094SKris Van Hees	next;
92*ac7bd094SKris Van Hees}
93*ac7bd094SKris Van Hees
94*ac7bd094SKris Van Hees# (2) Annotate System.map symbols with module names.
95*ac7bd094SKris Van Hees#
96*ac7bd094SKris Van HeesARGIND == 2 {
97*ac7bd094SKris Van Hees	addr = addr2val($1);
98*ac7bd094SKris Van Hees	name = $3;
99*ac7bd094SKris Van Hees
100*ac7bd094SKris Van Hees	while (addr >= mod_eaddr) {
101*ac7bd094SKris Van Hees		if (sect_symb) {
102*ac7bd094SKris Van Hees			if (sect_symb != name)
103*ac7bd094SKris Van Hees				next;
104*ac7bd094SKris Van Hees
105*ac7bd094SKris Van Hees			sect_base = addr - sect_off;
106*ac7bd094SKris Van Hees			if (dbg)
107*ac7bd094SKris Van Hees				printf "[%s] BASE (%s) %016x - %016x = %016x\n", sect_name, sect_symb, addr, sect_off, sect_base >"/dev/stderr";
108*ac7bd094SKris Van Hees			sect_symb = 0;
109*ac7bd094SKris Van Hees		}
110*ac7bd094SKris Van Hees
111*ac7bd094SKris Van Hees		if (++ridx > rcnt)
112*ac7bd094SKris Van Hees			break;
113*ac7bd094SKris Van Hees
114*ac7bd094SKris Van Hees		$0 = ranges[ridx];
115*ac7bd094SKris Van Hees		sub(/-/, " ");
116*ac7bd094SKris Van Hees		if ($4 != "=") {
117*ac7bd094SKris Van Hees			sub(/-/, " ");
118*ac7bd094SKris Van Hees			mod_saddr = strtonum("0x" $2) + sect_base;
119*ac7bd094SKris Van Hees			mod_eaddr = strtonum("0x" $3) + sect_base;
120*ac7bd094SKris Van Hees			$1 = $2 = $3 = "";
121*ac7bd094SKris Van Hees			sub(/^ +/, "");
122*ac7bd094SKris Van Hees			mod_name = $0;
123*ac7bd094SKris Van Hees
124*ac7bd094SKris Van Hees			if (dbg)
125*ac7bd094SKris Van Hees				printf "[%s] %s from %016x to %016x\n", sect_name, mod_name, mod_saddr, mod_eaddr >"/dev/stderr";
126*ac7bd094SKris Van Hees		} else {
127*ac7bd094SKris Van Hees			sect_name = $1;
128*ac7bd094SKris Van Hees			sect_off = strtonum("0x" $2);
129*ac7bd094SKris Van Hees			sect_symb = $5;
130*ac7bd094SKris Van Hees		}
131*ac7bd094SKris Van Hees	}
132*ac7bd094SKris Van Hees
133*ac7bd094SKris Van Hees	idx = addr"-"name;
134*ac7bd094SKris Van Hees	if (addr >= mod_saddr && addr < mod_eaddr)
135*ac7bd094SKris Van Hees		sym2mod[idx] = mod_name;
136*ac7bd094SKris Van Hees
137*ac7bd094SKris Van Hees	next;
138*ac7bd094SKris Van Hees}
139*ac7bd094SKris Van Hees
140*ac7bd094SKris Van Hees# Once we are done annotating the System.map, we no longer need the ranges data.
141*ac7bd094SKris Van Hees#
142*ac7bd094SKris Van HeesFNR == 1 && ARGIND == 3 {
143*ac7bd094SKris Van Hees	delete ranges;
144*ac7bd094SKris Van Hees}
145*ac7bd094SKris Van Hees
146*ac7bd094SKris Van Hees# (3) Build a lookup map of built-in module names.
147*ac7bd094SKris Van Hees#
148*ac7bd094SKris Van Hees# Lines from modules.builtin will be like:
149*ac7bd094SKris Van Hees#	kernel/crypto/lzo-rle.ko
150*ac7bd094SKris Van Hees# and we record the object name "crypto/lzo-rle".
151*ac7bd094SKris Van Hees#
152*ac7bd094SKris Van HeesARGIND == 3 {
153*ac7bd094SKris Van Hees	sub(/kernel\//, "");			# strip off "kernel/" prefix
154*ac7bd094SKris Van Hees	sub(/\.ko$/, "");			# strip off .ko suffix
155*ac7bd094SKris Van Hees
156*ac7bd094SKris Van Hees	mods[$1] = 1;
157*ac7bd094SKris Van Hees	next;
158*ac7bd094SKris Van Hees}
159*ac7bd094SKris Van Hees
160*ac7bd094SKris Van Hees# (4) Get a list of symbols (per object).
161*ac7bd094SKris Van Hees#
162*ac7bd094SKris Van Hees# Symbols by object are read from vmlinux.map, with fallback to vmlinux.o.map
163*ac7bd094SKris Van Hees# if vmlinux is found to have inked in vmlinux.o.
164*ac7bd094SKris Van Hees#
165*ac7bd094SKris Van Hees
166*ac7bd094SKris Van Hees# If we were able to get the data we need from vmlinux.map, there is no need to
167*ac7bd094SKris Van Hees# process vmlinux.o.map.
168*ac7bd094SKris Van Hees#
169*ac7bd094SKris Van HeesFNR == 1 && ARGIND == 5 && total > 0 {
170*ac7bd094SKris Van Hees	if (dbg)
171*ac7bd094SKris Van Hees		printf "Note: %s is not needed.\n", FILENAME >"/dev/stderr";
172*ac7bd094SKris Van Hees	exit;
173*ac7bd094SKris Van Hees}
174*ac7bd094SKris Van Hees
175*ac7bd094SKris Van Hees# First determine whether we are dealing with a GNU ld or LLVM lld linker map.
176*ac7bd094SKris Van Hees#
177*ac7bd094SKris Van HeesARGIND >= 4 && FNR == 1 && NF == 7 && $1 == "VMA" && $7 == "Symbol" {
178*ac7bd094SKris Van Hees	map_is_lld = 1;
179*ac7bd094SKris Van Hees	next;
180*ac7bd094SKris Van Hees}
181*ac7bd094SKris Van Hees
182*ac7bd094SKris Van Hees# (LLD) Convert a section record fronm lld format to ld format.
183*ac7bd094SKris Van Hees#
184*ac7bd094SKris Van HeesARGIND >= 4 && map_is_lld && NF == 5 && /[0-9] [^ ]+$/ {
185*ac7bd094SKris Van Hees	$0 = $5 " 0x"$1 " 0x"$3 " load address 0x"$2;
186*ac7bd094SKris Van Hees}
187*ac7bd094SKris Van Hees
188*ac7bd094SKris Van Hees# (LLD) Convert an object record from lld format to ld format.
189*ac7bd094SKris Van Hees#
190*ac7bd094SKris Van HeesARGIND >= 4 && map_is_lld && NF == 5 && $5 ~ /:\(/ {
191*ac7bd094SKris Van Hees	if (/\.a\(/ && !/ vmlinux\.a\(/)
192*ac7bd094SKris Van Hees		next;
193*ac7bd094SKris Van Hees
194*ac7bd094SKris Van Hees	gsub(/\)/, "");
195*ac7bd094SKris Van Hees	sub(/:\(/, " ");
196*ac7bd094SKris Van Hees	sub(/ vmlinux\.a\(/, " ");
197*ac7bd094SKris Van Hees	$0 = " "$6 " 0x"$1 " 0x"$3 " " $5;
198*ac7bd094SKris Van Hees}
199*ac7bd094SKris Van Hees
200*ac7bd094SKris Van Hees# (LLD) Convert a symbol record from lld format to ld format.
201*ac7bd094SKris Van Hees#
202*ac7bd094SKris Van HeesARGIND >= 4 && map_is_lld && NF == 5 && $5 ~ /^[A-Za-z_][A-Za-z0-9_]*$/ {
203*ac7bd094SKris Van Hees	$0 = "  0x" $1 " " $5;
204*ac7bd094SKris Van Hees}
205*ac7bd094SKris Van Hees
206*ac7bd094SKris Van Hees# (LLD) We do not need any other ldd linker map records.
207*ac7bd094SKris Van Hees#
208*ac7bd094SKris Van HeesARGIND >= 4 && map_is_lld && /^[0-9a-f]{16} / {
209*ac7bd094SKris Van Hees	next;
210*ac7bd094SKris Van Hees}
211*ac7bd094SKris Van Hees
212*ac7bd094SKris Van Hees# Handle section records with long section names (spilling onto a 2nd line).
213*ac7bd094SKris Van Hees#
214*ac7bd094SKris Van HeesARGIND >= 4 && !map_is_lld && NF == 1 && /^[^ ]/ {
215*ac7bd094SKris Van Hees	s = $0;
216*ac7bd094SKris Van Hees	getline;
217*ac7bd094SKris Van Hees	$0 = s " " $0;
218*ac7bd094SKris Van Hees}
219*ac7bd094SKris Van Hees
220*ac7bd094SKris Van Hees# Next section - previous one is done.
221*ac7bd094SKris Van Hees#
222*ac7bd094SKris Van HeesARGIND >= 4 && /^[^ ]/ {
223*ac7bd094SKris Van Hees	sect = 0;
224*ac7bd094SKris Van Hees}
225*ac7bd094SKris Van Hees
226*ac7bd094SKris Van Hees# Get the (top level) section name.
227*ac7bd094SKris Van Hees#
228*ac7bd094SKris Van HeesARGIND >= 4 && /^\./ {
229*ac7bd094SKris Van Hees	# Explicitly ignore a few sections that are not relevant here.
230*ac7bd094SKris Van Hees	if ($1 ~ /^\.orc_/ || $1 ~ /_sites$/ || $1 ~ /\.percpu/)
231*ac7bd094SKris Van Hees		next;
232*ac7bd094SKris Van Hees
233*ac7bd094SKris Van Hees	# Sections with a 0-address can be ignored as well (in vmlinux.map).
234*ac7bd094SKris Van Hees	if (ARGIND == 4 && $2 ~ /^0x0+$/)
235*ac7bd094SKris Van Hees		next;
236*ac7bd094SKris Van Hees
237*ac7bd094SKris Van Hees	sect = $1;
238*ac7bd094SKris Van Hees
239*ac7bd094SKris Van Hees	next;
240*ac7bd094SKris Van Hees}
241*ac7bd094SKris Van Hees
242*ac7bd094SKris Van Hees# If we are not currently in a section we care about, ignore records.
243*ac7bd094SKris Van Hees#
244*ac7bd094SKris Van Hees!sect {
245*ac7bd094SKris Van Hees	next;
246*ac7bd094SKris Van Hees}
247*ac7bd094SKris Van Hees
248*ac7bd094SKris Van Hees# Handle object records with long section names (spilling onto a 2nd line).
249*ac7bd094SKris Van Hees#
250*ac7bd094SKris Van HeesARGIND >= 4 && /^ [^ \*]/ && NF == 1 {
251*ac7bd094SKris Van Hees	# If the section name is long, the remainder of the entry is found on
252*ac7bd094SKris Van Hees	# the next line.
253*ac7bd094SKris Van Hees	s = $0;
254*ac7bd094SKris Van Hees	getline;
255*ac7bd094SKris Van Hees	$0 = s " " $0;
256*ac7bd094SKris Van Hees}
257*ac7bd094SKris Van Hees
258*ac7bd094SKris Van Hees# Objects linked in from static libraries are ignored.
259*ac7bd094SKris Van Hees# If the object is vmlinux.o, we need to consult vmlinux.o.map for per-object
260*ac7bd094SKris Van Hees# symbol information
261*ac7bd094SKris Van Hees#
262*ac7bd094SKris Van HeesARGIND == 4 && /^ [^ ]/ && NF == 4 {
263*ac7bd094SKris Van Hees	if ($4 ~ /\.a\(/)
264*ac7bd094SKris Van Hees		next;
265*ac7bd094SKris Van Hees
266*ac7bd094SKris Van Hees	idx = sect":"$1;
267*ac7bd094SKris Van Hees	if (!(idx in sect_addend)) {
268*ac7bd094SKris Van Hees		sect_addend[idx] = addr2val($2);
269*ac7bd094SKris Van Hees		if (dbg)
270*ac7bd094SKris Van Hees			printf "ADDEND %s = %016x\n", idx, sect_addend[idx] >"/dev/stderr";
271*ac7bd094SKris Van Hees	}
272*ac7bd094SKris Van Hees	if ($4 == "vmlinux.o") {
273*ac7bd094SKris Van Hees		need_o_map = 1;
274*ac7bd094SKris Van Hees		next;
275*ac7bd094SKris Van Hees	}
276*ac7bd094SKris Van Hees}
277*ac7bd094SKris Van Hees
278*ac7bd094SKris Van Hees# If data from vmlinux.o.map is needed, we only process section and object
279*ac7bd094SKris Van Hees# records from vmlinux.map to determine which section we need to pay attention
280*ac7bd094SKris Van Hees# to in vmlinux.o.map.  So skip everything else from vmlinux.map.
281*ac7bd094SKris Van Hees#
282*ac7bd094SKris Van HeesARGIND == 4 && need_o_map {
283*ac7bd094SKris Van Hees	next;
284*ac7bd094SKris Van Hees}
285*ac7bd094SKris Van Hees
286*ac7bd094SKris Van Hees# Get module information for the current object.
287*ac7bd094SKris Van Hees#
288*ac7bd094SKris Van HeesARGIND >= 4 && /^ [^ ]/ && NF == 4 {
289*ac7bd094SKris Van Hees	msect = $1;
290*ac7bd094SKris Van Hees	mod_name = get_module_info($4);
291*ac7bd094SKris Van Hees	mod_eaddr = addr2val($2) + addr2val($3);
292*ac7bd094SKris Van Hees
293*ac7bd094SKris Van Hees	next;
294*ac7bd094SKris Van Hees}
295*ac7bd094SKris Van Hees
296*ac7bd094SKris Van Hees# Process a symbol record.
297*ac7bd094SKris Van Hees#
298*ac7bd094SKris Van Hees# Evaluate the module information obtained from vmlinux.map (or vmlinux.o.map)
299*ac7bd094SKris Van Hees# as follows:
300*ac7bd094SKris Van Hees#  - For all symbols in a given object:
301*ac7bd094SKris Van Hees#     - If the symbol is annotated with the same module name(s) that the object
302*ac7bd094SKris Van Hees#       belongs to, count it as a match.
303*ac7bd094SKris Van Hees#     - Otherwise:
304*ac7bd094SKris Van Hees#        - If the symbol is known to have duplicates of which at least one is
305*ac7bd094SKris Van Hees#          in a built-in module, disregard it.
306*ac7bd094SKris Van Hees#        - If the symbol us not annotated with any module name(s) AND the
307*ac7bd094SKris Van Hees#          object belongs to built-in modules, count it as missing.
308*ac7bd094SKris Van Hees#        - Otherwise, count it as a mismatch.
309*ac7bd094SKris Van Hees#
310*ac7bd094SKris Van HeesARGIND >= 4 && /^ / && NF == 2 && $1 ~ /^0x/ {
311*ac7bd094SKris Van Hees	idx = sect":"msect;
312*ac7bd094SKris Van Hees	if (!(idx in sect_addend))
313*ac7bd094SKris Van Hees		next;
314*ac7bd094SKris Van Hees
315*ac7bd094SKris Van Hees	addr = addr2val($1);
316*ac7bd094SKris Van Hees
317*ac7bd094SKris Van Hees	# Handle the rare but annoying case where a 0-size symbol is placed at
318*ac7bd094SKris Van Hees	# the byte *after* the module range.  Based on vmlinux.map it will be
319*ac7bd094SKris Van Hees	# considered part of the current object, but it falls just beyond the
320*ac7bd094SKris Van Hees	# module address range.  Unfortunately, its address could be at the
321*ac7bd094SKris Van Hees	# start of another built-in module, so the only safe thing to do is to
322*ac7bd094SKris Van Hees	# ignore it.
323*ac7bd094SKris Van Hees	if (mod_name && addr == mod_eaddr)
324*ac7bd094SKris Van Hees		next;
325*ac7bd094SKris Van Hees
326*ac7bd094SKris Van Hees	# If we are processing vmlinux.o.map, we need to apply the base address
327*ac7bd094SKris Van Hees	# of the section to the relative address on the record.
328*ac7bd094SKris Van Hees	#
329*ac7bd094SKris Van Hees	if (ARGIND == 5)
330*ac7bd094SKris Van Hees		addr += sect_addend[idx];
331*ac7bd094SKris Van Hees
332*ac7bd094SKris Van Hees	idx = addr"-"$2;
333*ac7bd094SKris Van Hees	mod = "";
334*ac7bd094SKris Van Hees	if (idx in sym2mod) {
335*ac7bd094SKris Van Hees		mod = sym2mod[idx];
336*ac7bd094SKris Van Hees		if (sym2mod[idx] == mod_name) {
337*ac7bd094SKris Van Hees			mod_matches++;
338*ac7bd094SKris Van Hees			matches++;
339*ac7bd094SKris Van Hees		} else if (mod_name == "") {
340*ac7bd094SKris Van Hees			print $2 " in " mod " (should NOT be)";
341*ac7bd094SKris Van Hees			mismatches++;
342*ac7bd094SKris Van Hees		} else {
343*ac7bd094SKris Van Hees			print $2 " in " mod " (should be " mod_name ")";
344*ac7bd094SKris Van Hees			mismatches++;
345*ac7bd094SKris Van Hees		}
346*ac7bd094SKris Van Hees	} else if (mod_name != "") {
347*ac7bd094SKris Van Hees		print $2 " should be in " mod_name;
348*ac7bd094SKris Van Hees		missing++;
349*ac7bd094SKris Van Hees	} else
350*ac7bd094SKris Van Hees		matches++;
351*ac7bd094SKris Van Hees
352*ac7bd094SKris Van Hees	total++;
353*ac7bd094SKris Van Hees
354*ac7bd094SKris Van Hees	next;
355*ac7bd094SKris Van Hees}
356*ac7bd094SKris Van Hees
357*ac7bd094SKris Van Hees# Issue the comparison report.
358*ac7bd094SKris Van Hees#
359*ac7bd094SKris Van HeesEND {
360*ac7bd094SKris Van Hees	if (total) {
361*ac7bd094SKris Van Hees		printf "Verification of %s:\n", ARGV[1];
362*ac7bd094SKris Van Hees		printf "  Correct matches:  %6d (%d%% of total)\n", matches, 100 * matches / total;
363*ac7bd094SKris Van Hees		printf "    Module matches: %6d (%d%% of matches)\n", mod_matches, 100 * mod_matches / matches;
364*ac7bd094SKris Van Hees		printf "  Mismatches:       %6d (%d%% of total)\n", mismatches, 100 * mismatches / total;
365*ac7bd094SKris Van Hees		printf "  Missing:          %6d (%d%% of total)\n", missing, 100 * missing / total;
366*ac7bd094SKris Van Hees
367*ac7bd094SKris Van Hees		if (mismatches || missing)
368*ac7bd094SKris Van Hees			exit(1);
369*ac7bd094SKris Van Hees	}
370*ac7bd094SKris Van Hees}
371