#! /usr/bin/perl # kabi - Linux Kernel Application Binary Interface manager # # Copyright 2008 Sun Microsystems, Inc. All rights reserved # Use is subject to license terms. # # Gordon Matzigkeit , 2005-10-21 use warnings; use strict; my $VERSION = '0.2'; my $CC = $ENV{'CC'} || 'gcc'; my $LINUX = '/usr/src/linux'; my $MODE; my $OUTPUT; my @ARGS; my $VERBOSE = 0; my $progname = $0; $progname =~ s/^.*\///; my $modename = $progname; sub usage { my ($status) = @_; if ($status) { print STDERR "Try \`$0 --help' for more information\n"; } else { print < for Sun Microsystems, Inc. EOF } exit $status; } my @args = @ARGV; while ($#args >= 0) { if ($args[0] =~ /^--with-l(i(n(u(x)?)?)?)?=(.*)/) { $LINUX = $5; } elsif ($args[0] =~ /^--with-l(i(n(u(x)?)?)?)?$/) { shift @args; $LINUX = $args[0]; } elsif ($args[0] =~ /^--h(e(l(p)?)?)?$/) { usage(0); } elsif ($args[0] =~ /^--vers(i(o(n)?)?)?$/) { print "KABI $VERSION\n"; exit 0; } elsif ($args[0] eq '-v' || $args[0] =~ /^--verb(o(s(e)?)?)?$/) { $VERBOSE = 1; } elsif ($args[0] =~ /^-/) { print STDERR "$progname: unrecognized option \`$args[0]'\n"; usage(1); } elsif (!defined $MODE) { $MODE = $args[0]; } else { push @ARGS, $args[0]; } shift @args; } if (!defined $MODE) { print STDERR "$progname: you must specify a MODE\n"; usage(1); } $modename .= ": $MODE"; if ($MODE eq 'archive') { if ($#ARGS != 1) { print STDERR "$modename: you must specify a DIR and KMOD\n"; usage(1); } my $ARCHIVE = $ARGS[0]; my $KMOD = $ARGS[1]; my $KABI = $KMOD; $KABI =~ s/\.k?o$//; $KABI .= '.kabi'; open(MD5SUM, "md5sum $KABI|") or die "$modename: cannot execute \`md5sum': $!\n"; my $hash = ; close(MD5SUM); $hash =~ s/\s+.*//s; my $TAG = ''; if (-d 'CVS') { open(TAG, '; close(TAG); chomp $TAG; $TAG = "/$TAG"; } my ($dir, @sh_c, @cp); if ($ARCHIVE =~ /^([^:][^:]+):(.*)$/) { $dir = $2; @sh_c = ('ssh', '-o', 'BatchMode=yes', $1); @cp = ('scp', '-B'); } else { $dir = $ARCHIVE; @sh_c = ('sh', '-c'); @cp = ('cp'); } system(@sh_c, "test -d $dir"); if ($? >> 8 != 0) { print STDERR "$modename: warning: \`$dir' is not reachable or does not exist\n"; exit 0; } print "archiving $KMOD in $ARCHIVE$TAG/$KMOD/$hash\n" if $VERBOSE; foreach my $d ("$dir$TAG", "$dir$TAG/$KMOD", "$dir$TAG/$KMOD/$hash") { system(@sh_c, "test -d $d || mkdir $d"); if ($? >> 8 != 0) { exit $? >> 8; } } system(@cp, $KMOD, $KABI, "$ARCHIVE$TAG/$KMOD/$hash"); exit $? >> 8; } elsif ($MODE eq 'module') { if ($#ARGS != 0) { print STDERR "$modename: you must specify exactly one KMOD\n"; usage(1); } my $KMOD = $ARGS[0]; if (!defined $OUTPUT) { $OUTPUT = $KMOD; $OUTPUT =~ s/\.k?o$//; $OUTPUT .= '.kabi'; } print "create $OUTPUT\n" if $VERBOSE; open(OUT, ">$OUTPUT") or die "$modename: cannot create \`$OUTPUT': $!\n"; my $outname = $OUTPUT; $outname =~ s/^.*\///; print OUT <) { if (/\"__versions\"/) { $versions = 1; } elsif ($versions) { if (/^\s*\{\s*(0x[0-9a-f]+)\s*,\s*\"([^\"]*)\"\s*\}\s*,\s*$/) { $vers{$2} = $1; push(@undefs, $2); } elsif (/^\s*\}\s*;\s*$/) { $versions = 0; } } } close(MOD); } else { open(NM, "nm $KMOD |") or die "$modename: cannot execute \`nm $KMOD': $!\n"; while ($_ = ) { if (/^\s*U\s*(.*\S)\s*$/) { push @undefs, $1; } } close(NM); } foreach my $undef (sort @undefs) { print OUT "usym $undef"; if (defined $vers{$undef}) { print OUT " ", $vers{$undef}; } print OUT "\n"; } close(OUT) or die "$modename: cannot write \`$OUTPUT': $!\n"; } elsif ($MODE eq 'match') { my @KABIS; my @KMODS; my @todo = @ARGS; while ($#todo >= 0) { my $t = shift @todo; if ($t =~ /\.kabi$/) { push @KABIS, $t; } elsif (-d $t) { # Add all the contents of the directory to our todo list. opendir(DIR, $t); while (my $ent = readdir(DIR)) { if ($ent =~ /^\./) { # Skip dotfiles. } elsif (-d "$t/$ent") { # Recurse into subdirectories. unshift @todo, "$t/$ent"; } elsif ($ent =~ /\.k?o$/) { # Add kernel modules. unshift @todo, "$t/$ent"; } } closedir(DIR); } else { # It's an explicit kernel module. push @KMODS, $t; } } if ($#KABIS < 0) { print STDERR "$modename: you must specify at least one KABI\n"; usage(1); } my %dsyms; if (-f "$LINUX/Module.symvers") { # Look up the version numbers in Module.symvers. open(VERS, "<$LINUX/Module.symvers") or die "$modename: cannot read \`$LINUX/Module.symvers': $!\n"; while ($_ = ) { if (/^(0x[0-9a-f]+)\s+(\S+)/) { $dsyms{$2} = hex($1); } } close(VERS); } else { # Read in all the non-versioned symbols defined by this kernel. open(MAP, "<$LINUX/System.map") or die "$modename: cannot read \`$LINUX/System.map': $!\n"; while ($_ = ) { if (/^[0-9a-fA-F]*\s+[ABCDGIRSTW]+\s*(.*\S)\s*$/) { $dsyms{$1} = 0; } } close(MAP); } # Find the symbols for the installed modules, too. foreach my $mod (@KMODS) { open(NM, "nm $mod |") or die "$modename: cannot execute \`nm $mod': $!\n"; while ($_ = ) { if (/^[0-9a-fA-F]*\s+[ABCDGIRSTW]+\s*(.*\S)\s*$/) { $dsyms{$1} = 0; } } close(NM); } # Also get the kernel version. my $kver = kernel_version(); # Read each kabi file and print out the ones that are plausible # matches. foreach my $kabi (@KABIS) { open(KABI, "<$kabi") or die "$modename: cannot read \`$kabi': $!\n"; my $possible = 1; while ($possible && ($_ = )) { if (/^\s*#/) { # Skip comments. } elsif (/^\s*kver\s+(.*\S)\s*$/) { my $modkver = $1; if ($modkver ne $kver) { print STDERR "$kabi:$.: module version \`$modkver' differs from \`$kver'\n" if $VERBOSE; $possible = 0; } } elsif (/^\s*usym\s+(\S+)\s*(\S+)?\s*$/) { my ($modsym, $symver) = ($1, hex($2)); if (!defined $dsyms{$modsym}) { print STDERR "$kabi:$.: module symbol \`$modsym' is not defined\n" if $VERBOSE; $possible = 0; } elsif (defined $symver && $dsyms{$modsym} != 0 && $dsyms{$modsym} != $symver) { printf STDERR "$kabi:$.: module symbol \`$modsym' is version 0x%x, not 0x%x\n", $dsyms{$modsym}, $symver if $VERBOSE; $possible = 0; } } elsif (/^\s*(\S+)/) { print STDERR "$kabi:$.: unrecognized descriptor line \`$1'\n"; } } close(KABI); if ($possible) { # We got a match. print "$kabi\n"; } } } else { print STDERR "$progname: unrecognized mode \`$MODE'\n"; usage(1); } # Read the kernel version from its built source tree. sub kernel_version { my $verfile = "$LINUX/include/linux/version.h"; open(VERSION, "<$verfile") or die "$modename: cannot read \`$verfile': $!\n"; my $ver; while ($_ = ) { if (/^\s*#\s*define\s+UTS_RELEASE\s+"(.*)"\s*$/) { $ver = $1; last; } } close(VERSION); if (!defined $ver) { die "$modename: cannot find UTS_RELEASE in \`$verfile'\n"; } return "linux-$ver"; } exit 0;