SchOS/tools/ksyms.pl
Nexoniarz 26bca020b6 licence: SchOS is GPL-2.0-or-later, no longer Apache-2.0
Nexoniarz is the sole author and SchOS was never published under
Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text,
every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE,
README.md, docs/licensing.md and the site say so. projects/schfs-linux
follows the rest of the tree.

The GPL gives copyleft: a fork has to ship its source. It also lifts the
old "Linux drivers, never" rule, since GPL-2.0-only code combines with
GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still
has to pick one version, so docs/licensing.md now keeps Apache-2.0 and
GPLv3 code out of the kernel to keep Linux code possible there. BSD
4-clause code goes from "avoid" to "no", because the advertising clause
is GPL-incompatible.

LDLS still ships no .ko. That boundary is now project policy rather than
a legal requirement, and ldls.md, devices.md and licensing.md section 6
say so. refmod declares license=GPL, and ldls-check passes (27 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:48:45 +02:00

145 lines
4.8 KiB
Perl

#!/usr/bin/perl
# SPDX-License-Identifier: GPL-2.0-or-later
#
# 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 = <STDIN>) {
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";