sysklogd/ksym.c
Joey Schulze 2c81663786 . Use lseek64() instead of llseek() which is deprecated these days
. Keith Owens <kaos@ocs.com.au>
   - Fixed bug that caused klogd to die if there is no sym_array available.
   - When symbols are expanded, print the line twice.  Once with
     addresses converted to symbols, once with the raw text.  Allows
     external programs such as ksymoops do their own processing on the
     original data.
2000-09-12 21:15:28 +00:00

898 lines
23 KiB
C

/*
ksym.c - functions for kernel address->symbol translation
Copyright (c) 1995, 1996 Dr. G.W. Wettstein <greg@wind.rmcc.com>
Copyright (c) 1996 Enjellic Systems Development
This file is part of the sysklogd package, a kernel and system log daemon.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* This file contains functions which handle the translation of kernel
* numeric addresses into symbols for the klogd utility.
*
* Sat Oct 28 09:00:14 CDT 1995: Dr. Wettstein
* Initial Version.
*
* Fri Nov 24 12:50:52 CST 1995: Dr. Wettstein
* Added VERBOSE_DEBUGGING define to make debugging output more
* manageable.
*
* Added support for verification of the loaded kernel symbols. If
* no version information can be be found in the mapfile a warning
* message is issued but translation will still take place. This
* will be the default case if kernel versions < 1.3.43 are used.
*
* If the symbols in the mapfile are of the same version as the kernel
* that is running an informative message is issued. If the symbols
* in the mapfile do not match the current kernel version a warning
* message is issued and translation is disabled.
*
* Wed Dec 6 16:14:11 CST 1995: Dr. Wettstein
* Added /boot/System.map to the list of symbol maps to search for.
* Also made this map the first item in the search list. I am open
* to CONSTRUCTIVE suggestions for any additions or corrections to
* the list of symbol maps to search for. Be forewarned that the
* list in use is the consensus agreement between myself, Linus and
* some package distributers. It is a given that no list will suit
* everyone's taste. If you have rabid concerns about the list
* please feel free to edit the system_maps array and compile your
* own binaries.
*
* Added support for searching of the list of symbol maps. This
* allows support for access to multiple symbol maps. The theory
* behind this is that a production kernel may have a system map in
* /boot/System.map. If a test kernel is booted this system map
* would be skipped in favor of one found in /usr/src/linux.
*
* Thu Jan 18 11:18:31 CST 1996: Dr. Wettstein
* Added patch from beta-testers to allow for reading of both
* ELF and a.out map files.
*
* Wed Aug 21 09:15:49 CDT 1996: Dr. Wettstein
* Reloading of kernel module symbols is now turned on by the
* SetParanoiaLevel function. The default behavior is to NOT reload
* the kernel module symbols when a protection fault is detected.
*
* Added support for freeing of the current kernel module symbols.
* This was necessary to support reloading of the kernel module symbols.
*
* When a matching static symbol table is loaded the kernel version
* number is printed.
*
* Mon Jun 9 17:12:42 CST 1997: Martin Schulze
* Added #1 and #2 to some error messages in order to being able
* to divide them (ulmo@Q.Net)
*
* Fri Jun 13 10:50:23 CST 1997: Martin Schulze
* Changed definition of LookupSymbol to non-static because it is
* used in klogd.c, too.
*
* Fri Jan 9 23:00:08 CET 1998: Martin Schulze <joey@infodrom.north.de>
* Fixed bug that caused klogd to die if there is no System.map available.
*
* Sun 29 Mar 18:14:07 BST 1998: Mark Simon Phillips <M.S.Phillips@nortel.co.uk>
* Switched to fgets() as gets() is not buffer overrun secure.
*
* Mon Apr 13 18:18:45 CEST 1998: Martin Schulze <joey@infodrom.north.de>
* Modified loop for detecting the correct system map. Now it won't
* stop if a file has been found but doesn't contain the correct map.
* Special thanks go go Mark Simon Phillips for the hint.
*
* Mon Oct 12 00:42:30 CEST 1998: Martin Schulze <joey@infodrom.north.de>
* Modified CheckVersion()
* . Use shift to decode the kernel version
* . Compare integers of kernel version
* . extract major.minor.patch from utsname.release via sscanf()
* The reason lays in possible use of kernel flavours which
* modify utsname.release but no the Version_ symbol.
*
* Sun Feb 21 22:27:49 EST 1999: Keith Owens <kaos@ocs.com.au>
* Fixed bug that caused klogd to die if there is no sym_array available.
*/
/* Includes. */
#include <stdlib.h>
#include <malloc.h>
#include <sys/utsname.h>
#include "klogd.h"
#include "ksyms.h"
#define VERBOSE_DEBUGGING 0
/* Variables static to this module. */
struct sym_table
{
unsigned long value;
char *name;
};
static int num_syms = 0;
static int i_am_paranoid = 0;
static char vstring[12];
static struct sym_table *sym_array = (struct sym_table *) 0;
static char *system_maps[] =
{
"/boot/System.map",
"/System.map",
#if defined(TEST)
"./System.map",
#endif
(char *) 0
};
#if defined(TEST)
int debugging;
#else
extern int debugging;
#endif
/* Function prototypes. */
static char * FindSymbolFile(void);
static int AddSymbol(unsigned long, char*);
static void FreeSymbols(void);
static int CheckVersion(char *);
static int CheckMapVersion(char *);
/**************************************************************************
* Function: InitKsyms
*
* Purpose: This function is responsible for initializing and loading
* the data tables used by the kernel address translations.
*
* Arguements: (char *) mapfile
*
* mapfile:-> A pointer to a complete path
* specification of the file containing
* the kernel map to use.
*
* Return: int
*
* A boolean style context is returned. The return value will
* be true if initialization was successful. False if not.
**************************************************************************/
extern int InitKsyms(mapfile)
char *mapfile;
{
auto char type,
sym[512];
auto int version = 0;
auto unsigned long int address;
auto FILE *sym_file;
/* Check and make sure that we are starting with a clean slate. */
if ( num_syms > 0 )
FreeSymbols();
/*
* Search for and open the file containing the kernel symbols.
*/
if ( mapfile != (char *) 0 )
{
if ( (sym_file = fopen(mapfile, "r")) == (FILE *) 0 )
{
Syslog(LOG_WARNING, "Cannot open map file: %s.", \
mapfile);
return(0);
}
}
else
{
if ( (mapfile = FindSymbolFile()) == (char *) 0 )
{
Syslog(LOG_WARNING, "Cannot find map file.");
if ( debugging )
fputs("Cannot find map file.\n", stderr);
return(0);
}
if ( (sym_file = fopen(mapfile, "r")) == (FILE *) 0 )
{
Syslog(LOG_WARNING, "Cannot open map file.");
if ( debugging )
fputs("Cannot open map file.\n", stderr);
return(0);
}
}
/*
* Read the kernel symbol table file and add entries for each
* line. I suspect that the use of fscanf is not really in vogue
* but it was quick and dirty and IMHO suitable for fixed format
* data such as this. If anybody doesn't agree with this please
* e-mail me a diff containing a parser with suitable political
* correctness -- GW.
*/
while ( !feof(sym_file) )
{
if ( fscanf(sym_file, "%lx %c %s\n", &address, &type, sym)
!= 3 )
{
Syslog(LOG_ERR, "Error in symbol table input (#1).");
fclose(sym_file);
return(0);
}
if ( VERBOSE_DEBUGGING && debugging )
fprintf(stderr, "Address: %lx, Type: %c, Symbol: %s\n",
address, type, sym);
if ( AddSymbol(address, sym) == 0 )
{
Syslog(LOG_ERR, "Error adding symbol - %s.", sym);
return(0);
}
if ( version == 0 )
version = CheckVersion(sym);
}
Syslog(LOG_INFO, "Loaded %d symbols from %s.", num_syms, mapfile);
switch ( version )
{
case -1:
Syslog(LOG_WARNING, "Symbols do not match kernel version.");
num_syms = 0;
break;
case 0:
Syslog(LOG_WARNING, "Cannot verify that symbols match " \
"kernel version.");
break;
case 1:
Syslog(LOG_INFO, "Symbols match kernel version %s.", vstring);
break;
}
fclose(sym_file);
return(1);
}
/**************************************************************************
* Function: FindSymbolFile
*
* Purpose: This function is responsible for encapsulating the search
* for a valid symbol file. Encapsulating the search for
* the map file in this function allows an intelligent search
* process to be implemented.
*
* The list of symbol files will be searched until either a
* symbol file is found whose version matches the currently
* executing kernel or the end of the list is encountered. If
* the end of the list is encountered the first available
* symbol file is returned to the caller.
*
* This strategy allows klogd to locate valid symbol files
* for both a production and an experimental kernel. For
* example a map for a production kernel could be installed
* in /boot. If an experimental kernel is loaded the map
* in /boot will be skipped and the map in /usr/src/linux would
* be used if its version number matches the executing kernel.
*
* Arguements: None specified.
*
* Return: char *
*
* If a valid system map cannot be located a null pointer
* is returned to the caller.
*
* If the search is succesful a pointer is returned to the
* caller which points to the name of the file containing
* the symbol table to be used.
**************************************************************************/
static char * FindSymbolFile()
{
auto char *file = (char *) 0,
**mf = system_maps;
auto struct utsname utsname;
static char symfile[100];
auto FILE *sym_file = (FILE *) 0;
if ( uname(&utsname) < 0 )
{
Syslog(LOG_ERR, "Cannot get kernel version information.");
return(0);
}
if ( debugging )
fputs("Searching for symbol map.\n", stderr);
for (mf = system_maps; *mf != (char *) 0 && file == (char *) 0; ++mf)
{
sprintf (symfile, "%s-%s", *mf, utsname.release);
if ( debugging )
fprintf(stderr, "Trying %s.\n", symfile);
if ( (sym_file = fopen(symfile, "r")) != (FILE *) 0 ) {
if (CheckMapVersion(symfile) == 1)
file = symfile;
}
if (sym_file == (FILE *) 0 || file == (char *) 0) {
sprintf (symfile, "%s", *mf);
if ( debugging )
fprintf(stderr, "Trying %s.\n", symfile);
if ( (sym_file = fopen(symfile, "r")) != (FILE *) 0 ) {
if (CheckMapVersion(symfile) == 1)
file = symfile;
}
}
}
/*
* At this stage of the game we are at the end of the symbol
* tables.
*/
if ( debugging )
fprintf(stderr, "End of search list encountered.\n");
return(file);
}
/**************************************************************************
* Function: CheckVersion
*
* Purpose: This function is responsible for determining whether or
* the system map being loaded matches the version of the
* currently running kernel.
*
* The kernel version is checked by examing a variable which
* is of the form: _Version_66347 (a.out) or Version_66437 (ELF).
*
* The suffix of this variable is the current kernel version
* of the kernel encoded in base 256. For example the
* above variable would be decoded as:
*
* (66347 = 1*65536 + 3*256 + 43 = 1.3.43)
*
* (Insert appropriate deities here) help us if Linus ever
* needs more than 255 patch levels to get a kernel out the
* door... :-)
*
* Arguements: (char *) version
*
* version:-> A pointer to the string which
* is to be decoded as a kernel
* version variable.
*
* Return: int
*
* -1:-> The currently running kernel version does
* not match this version string.
*
* 0:-> The string is not a kernel version variable.
*
* 1:-> The executing kernel is of the same version
* as the version string.
**************************************************************************/
static int CheckVersion(version)
char *version;
{
auto int vnum,
major,
minor,
patch;
#ifndef TESTING
int kvnum;
auto struct utsname utsname;
#endif
static char *prefix = { "Version_" };
/* Early return if there is no hope. */
if ( strncmp(version, prefix, strlen(prefix)) == 0 /* ELF */ ||
(*version == '_' &&
strncmp(++version, prefix, strlen(prefix)) == 0 ) /* a.out */ )
;
else
return(0);
/*
* Since the symbol looks like a kernel version we can start
* things out by decoding the version string into its component
* parts.
*/
vnum = atoi(version + strlen(prefix));
patch = vnum & 0x000000FF;
minor = (vnum >> 8) & 0x000000FF;
major = (vnum >> 16) & 0x000000FF;
if ( debugging )
fprintf(stderr, "Version string = %s, Major = %d, " \
"Minor = %d, Patch = %d.\n", version +
strlen(prefix), major, minor, \
patch);
sprintf(vstring, "%d.%d.%d", major, minor, patch);
#ifndef TESTING
/*
* We should now have the version string in the vstring variable in
* the same format that it is stored in by the kernel. We now
* ask the kernel for its version information and compare the two
* values to determine if our system map matches the kernel
* version level.
*/
if ( uname(&utsname) < 0 )
{
Syslog(LOG_ERR, "Cannot get kernel version information.");
return(0);
}
if ( debugging )
fprintf(stderr, "Comparing kernel %s with symbol table %s.\n",\
utsname.release, vstring);
if ( sscanf (utsname.release, "%d.%d.%d", &major, &minor, &patch) < 3 )
{
Syslog(LOG_ERR, "Kernel send bogus release string `%s'.",
utsname.release);
return(0);
}
/* Compute the version code from data sent by the kernel */
kvnum = (major << 16) | (minor << 8) | patch;
/* Failure. */
if ( vnum != kvnum )
return(-1);
/* Success. */
#endif
return(1);
}
/**************************************************************************
* Function: CheckMapVersion
*
* Purpose: This function is responsible for determining whether or
* the system map being loaded matches the version of the
* currently running kernel. It uses CheckVersion as
* backend.
*
* Arguements: (char *) fname
*
* fname:-> A pointer to the string which
* references the system map file to
* be used.
*
* Return: int
*
* -1:-> The currently running kernel version does
* not match the version in the given file.
*
* 0:-> No system map file or no version information.
*
* 1:-> The executing kernel is of the same version
* as the version of the map file.
**************************************************************************/
static int CheckMapVersion(fname)
char *fname;
{
int version;
FILE *sym_file;
auto unsigned long int address;
auto char type,
sym[512];
if ( (sym_file = fopen(fname, "r")) != (FILE *) 0 ) {
/*
* At this point a map file was successfully opened. We
* now need to search this file and look for version
* information.
*/
Syslog(LOG_INFO, "Inspecting %s", fname);
version = 0;
while ( !feof(sym_file) && (version == 0) )
{
if ( fscanf(sym_file, "%lx %c %s\n", &address, \
&type, sym) != 3 )
{
Syslog(LOG_ERR, "Error in symbol table input (#2).");
fclose(sym_file);
return(0);
}
if ( VERBOSE_DEBUGGING && debugging )
fprintf(stderr, "Address: %lx, Type: %c, " \
"Symbol: %s\n", address, type, sym);
version = CheckVersion(sym);
}
fclose(sym_file);
switch ( version )
{
case -1:
Syslog(LOG_ERR, "Symbol table has incorrect " \
"version number.\n");
break;
case 0:
if ( debugging )
fprintf(stderr, "No version information " \
"found.\n");
break;
case 1:
if ( debugging )
fprintf(stderr, "Found table with " \
"matching version number.\n");
break;
}
return(version);
}
return(0);
}
/**************************************************************************
* Function: AddSymbol
*
* Purpose: This function is responsible for adding a symbol name
* and its address to the symbol table.
*
* Arguements: (unsigned long) address, (char *) symbol
*
* Return: int
*
* A boolean value is assumed. True if the addition is
* successful. False if not.
**************************************************************************/
static int AddSymbol(address, symbol)
unsigned long address;
char *symbol;
{
/* Allocate the the symbol table entry. */
sym_array = (struct sym_table *) realloc(sym_array, (num_syms+1) * \
sizeof(struct sym_table));
if ( sym_array == (struct sym_table *) 0 )
return(0);
/* Then the space for the symbol. */
sym_array[num_syms].name = (char *) malloc(strlen(symbol)*sizeof(char)\
+ 1);
if ( sym_array[num_syms].name == (char *) 0 )
return(0);
sym_array[num_syms].value = address;
strcpy(sym_array[num_syms].name, symbol);
++num_syms;
return(1);
}
/**************************************************************************
* Function: LookupSymbol
*
* Purpose: Find the symbol which is related to the given kernel
* address.
*
* Arguements: (long int) value, (struct symbol *) sym
*
* value:-> The address to be located.
*
* sym:-> A pointer to a structure which will be
* loaded with the symbol's parameters.
*
* Return: (char *)
*
* If a match cannot be found a diagnostic string is printed.
* If a match is found the pointer to the symbolic name most
* closely matching the address is returned.
**************************************************************************/
char * LookupSymbol(value, sym)
unsigned long value;
struct symbol *sym;
{
auto int lp;
auto char *last;
if (!sym_array)
return((char *) 0);
last = sym_array[0].name;
sym->offset = 0;
sym->size = 0;
if ( value < sym_array[0].value )
return((char *) 0);
for(lp= 0; lp <= num_syms; ++lp)
{
if ( sym_array[lp].value > value )
{
sym->offset = value - sym_array[lp-1].value;
sym->size = sym_array[lp].value - \
sym_array[lp-1].value;
return(last);
}
last = sym_array[lp].name;
}
if ( (last = LookupModuleSymbol(value, sym)) != (char *) 0 )
return(last);
return((char *) 0);
}
/**************************************************************************
* Function: FreeSymbols
*
* Purpose: This function is responsible for freeing all memory which
* has been allocated to hold the static symbol table. It
* also initializes the symbol count and in general prepares
* for a re-read of a static symbol table.
*
* Arguements: void
*
* Return: void
**************************************************************************/
static void FreeSymbols()
{
auto int lp;
/* Free each piece of memory allocated for symbol names. */
for(lp= 0; lp < num_syms; ++lp)
free(sym_array[lp].name);
/* Whack the entire array and initialize everything. */
free(sym_array);
sym_array = (struct sym_table *) 0;
num_syms = 0;
return;
}
/**************************************************************************
* Function: LogExpanded
*
* Purpose: This function is responsible for logging a kernel message
* line after all potential numeric kernel addresses have
* been resolved symolically.
*
* Arguements: (char *) line, (char *) el
*
* line:-> A pointer to the buffer containing the kernel
* message to be expanded and logged.
*
* el:-> A pointer to the buffer into which the expanded
* kernel line will be written.
*
* Return: void
**************************************************************************/
extern char * ExpandKadds(line, el)
char *line;
char *el;
{
auto char dlm,
*kp,
*sl = line,
*elp = el,
*symbol;
char num[15];
auto unsigned long int value;
auto struct symbol sym;
/*
* This is as handy a place to put this as anyplace.
*
* Since the insertion of kernel modules can occur in a somewhat
* dynamic fashion we need some mechanism to insure that the
* kernel symbol tables get read just prior to when they are
* needed.
*
* To accomplish this we look for the Oops string and use its
* presence as a signal to load the module symbols.
*
* This is not the best solution of course, especially if the
* kernel is rapidly going out to lunch. What really needs to
* be done is to somehow generate a callback from the
* kernel whenever a module is loaded or unloaded. I am
* open for patches.
*/
if ( i_am_paranoid &&
(strstr(line, "Oops:") != (char *) 0) && !InitMsyms() )
Syslog(LOG_WARNING, "Cannot load kernel module symbols.\n");
/*
* Early return if there do not appear to be any kernel
* messages in this line.
*/
if ( (num_syms == 0) ||
(kp = strstr(line, "[<")) == (char *) 0 )
{
strcpy(el, line);
return(el);
}
/* Loop through and expand all kernel messages. */
do
{
while ( sl < kp+1 )
*elp++ = *sl++;
/* Now poised at a kernel delimiter. */
if ( (kp = strstr(sl, ">]")) == (char *) 0 )
{
strcpy(el, sl);
return(el);
}
dlm = *kp;
strncpy(num,sl+1,kp-sl-1);
num[kp-sl-1] = '\0';
value = strtoul(num, (char **) 0, 16);
if ( (symbol = LookupSymbol(value, &sym)) == (char *) 0 )
symbol = sl;
strcat(elp, symbol);
elp += strlen(symbol);
if ( debugging )
fprintf(stderr, "Symbol: %s = %lx = %s, %x/%d\n", \
sl+1, value, \
(sym.size==0) ? symbol+1 : symbol, \
sym.offset, sym.size);
value = 2;
if ( sym.size != 0 )
{
--value;
++kp;
elp += sprintf(elp, "+%x/%d", sym.offset, sym.size);
}
strncat(elp, kp, value);
elp += value;
sl = kp + value;
if ( (kp = strstr(sl, "[<")) == (char *) 0 )
strcat(elp, sl);
}
while ( kp != (char *) 0);
if ( debugging )
fprintf(stderr, "Expanded line: %s\n", el);
return(el);
}
/**************************************************************************
* Function: SetParanoiaLevel
*
* Purpose: This function is an interface function for setting the
* mode of loadable module symbol lookups. Probably overkill
* but it does slay another global variable.
*
* Arguements: (int) level
*
* level:-> The amount of paranoia which is to be
* present when resolving kernel exceptions.
* Return: void
**************************************************************************/
extern void SetParanoiaLevel(level)
int level;
{
i_am_paranoid = level;
return;
}
/*
* Setting the -DTEST define enables the following code fragment to
* be compiled. This produces a small standalone program which will
* echo the standard input of the process to stdout while translating
* all numeric kernel addresses into their symbolic equivalent.
*/
#if defined(TEST)
#include <stdarg.h>
extern int main(int, char **);
extern int main(int argc, char *argv[])
{
auto char line[1024], eline[2048];
debugging = 1;
if ( !InitKsyms((char *) 0) )
{
fputs("ksym: Error loading system map.\n", stderr);
return(1);
}
while ( !feof(stdin) )
{
fgets(line, sizeof(line), stdin);
if (line[strlen(line)-1] == '\n') line[strlen(line)-1] = '\0'; /* Trash NL char */
memset(eline, '\0', sizeof(eline));
ExpandKadds(line, eline);
fprintf(stdout, "%s\n", eline);
}
return(0);
}
extern void Syslog(int priority, char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stdout, "Pr: %d, ", priority);
vfprintf(stdout, fmt, ap);
va_end(ap);
fputc('\n', stdout);
return;
}
#endif