#!/usr/bin/perl # SPDX-License-Identifier: Apache-2.0 # # Turn `nm -n kernel` into a symbol table the kernel can carry with it. # # docs/architecture/debugging.md milestone D0. Everything the debugger prints is an # address until this exists, and an address has to be resolved by hand against # a disassembly that may not be the one that was running. # # Perl and not Python, deliberately: this runs on every kernel link, so it is # on the critical path of a clean build, and tools/vectors.pl already makes # perl a requirement there. The Makefile comment above include/logo.h says why # that line is worth holding. # # Two modes: # (default) read nm output on stdin, write a C file on stdout # --list read nm output on stdin, write "addr name" lines -- used by the # Makefile to prove the two link passes agree # # The layout trick this depends on: .ksym is placed after .text and .rodata in # kernel/kernel.ld, so adding the table to the image cannot move any of the # text symbols that are *in* the table. Two passes, and the second is stable. # The Makefile verifies that rather than trusting it. use strict; use warnings; # hex() on a 64-bit kernel address is fine on any perl with 64-bit integers, # which is every perl this will meet; the warning is about 32-bit ones. no warnings q(portable); my $list = (@ARGV && $ARGV[0] eq '--list'); my @sym; # [addr, name] my $textend = 0; while (my $line = ) { chomp $line; # "ffff800000100000 T begin". Symbols with no address print as spaces and # fall out here on the field count. my ($addr, $type, $name) = $line =~ /^([0-9a-fA-F]+)\s+(\S)\s+(\S+)$/ or next; # etext is the end of the text section, and it is the only non-function we # want: without it a lookup of an address past the last function walks off # the end of the table and names whatever was linked last. if ($name eq 'etext') { $textend = hex($addr); next; } # t/T are text, w/W are weak text. Everything else is data, bss or absolute # and naming it would make a code address resolve to a variable. next unless $type =~ /^[tTwW]$/; # Compiler-local labels. They have no meaning outside one translation unit # and they would shadow the function they sit inside. next if $name =~ /^\.L/; next if $name =~ /\$/; push @sym, [hex($addr), $name, $type]; } die "ksyms: no text symbols on stdin\n" unless @sym; # nm -n sorts numerically, but ties come out in whatever order the symbol table # had them. Prefer the global at a shared address: `begin` and `mboot_header` # are the same byte, and naming that byte `begin` tells you nothing. my @uniq; for my $s (@sym) { if (@uniq && $uniq[-1][0] == $s->[0]) { $uniq[-1] = $s if $s->[2] =~ /^[TW]$/ && $uniq[-1][2] =~ /^[tw]$/; next; } push @uniq, $s; } @sym = @uniq; $textend = $sym[-1][0] + 0x1000 unless $textend; if ($list) { printf "%016x %s\n", $_->[0], $_->[1] for @sym; exit 0; } my $base = $sym[0][0]; for my $s (@sym) { die sprintf("ksyms: symbol %s at %x is more than 4 GB above the base\n", $s->[1], $s->[0]) if $s->[0] - $base > 0xffffffff; } # Offsets into the name blob, computed here so the kernel does not have to # scan for NULs at boot. my @off; my $cur = 0; for my $s (@sym) { push @off, $cur; $cur += length($s->[1]) + 1; } my $n = scalar @sym; print <<"HDR"; // Generated by tools/ksyms.pl from the first link pass. Do not edit. // // See docs/architecture/debugging.md, milestone D0, and kernel/core/ksym.c for the // lookup that reads this. Everything lands in .ksym, which kernel/kernel.ld // places after .text and .rodata so that carrying the table cannot move the // addresses recorded in it. #include "types.h" #define KSYM_SECTION __attribute__((section(".ksym"), used)) KSYM_SECTION const uint32 ksym_count = ${n}u; KSYM_SECTION const uint64 ksym_base = 0x${\ sprintf("%x", $base) }ULL; KSYM_SECTION const uint64 ksym_textend = 0x${\ sprintf("%x", $textend) }ULL; // Offsets from ksym_base rather than absolute addresses: the kernel spans // about a megabyte, so 32 bits is plenty and it halves the table. KSYM_SECTION const uint32 ksym_addr[${n}] = { HDR for (my $i = 0; $i < $n; $i += 8) { my $end = $i + 7; $end = $n - 1 if $end > $n - 1; print " ", join(",", map { sprintf("0x%08x", $sym[$_][0] - $base) } ($i .. $end)), ",\n"; } print "};\n\nKSYM_SECTION const uint32 ksym_nameoff[${n}] = {\n"; for (my $i = 0; $i < $n; $i += 8) { my $end = $i + 7; $end = $n - 1 if $end > $n - 1; print " ", join(",", map { sprintf("%6d", $off[$_]) } ($i .. $end)), ",\n"; } print "};\n\nKSYM_SECTION const char ksym_names[] =\n"; for my $s (@sym) { my $name = $s->[1]; $name =~ s/([\\"])/\\$1/g; print " \"$name\\0\"\n"; } print ";\n";