diff options
Diffstat (limited to 'ksym.c')
-rw-r--r-- | ksym.c | 901 |
1 files changed, 901 insertions, 0 deletions
@@ -0,0 +1,901 @@ +/* + 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. + * + * Tue Sep 12 23:48:12 CEST 2000: Martin Schulze <joey@infodrom.ffis.de> + * Close symbol file in InitKsyms() when an error occurred. + */ + + +/* 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); + fclose(sym_file); + 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 |