sysklogd/src/ksym.c
Joachim Nilsson bf448fc6a1 Remove historical changelogs from files
We don't need to carry it any longer.  Anyone who wants to read them can
access an older GIT version, or released tarball.

Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2019-10-27 21:30:41 +01:00

732 lines
19 KiB
C

/*-
* SPDX-License-Identifier: GPL-2.0-or-later
*
* 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
* Copyright (c) 1997-2007 Martin Schulze <joey@infodrom.org>
*
* 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 file; see the file COPYING. If not, write to the
* Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
/*
* This file contains functions which handle the translation of kernel
* numeric addresses into symbols for the klogd utility.
*/
#include "klogd.h"
#include "ksyms.h"
#include "module.h"
#include <malloc.h>
#include <stdlib.h>
#include <sys/utsname.h>
#define VERBOSE_DEBUGGING 0
int num_syms = 0;
static int i_am_paranoid = 0;
static char vstring[12];
static struct sym_table *sym_array = NULL;
static char *system_maps[] =
{
"/boot/System.map",
"/System.map",
"/usr/src/linux/System.map",
#if defined(TEST)
"./System.map",
#endif
NULL
};
#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.
*
* Arguments: (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.
**************************************************************************/
int InitKsyms(char *mapfile)
{
unsigned long int address;
FILE *sym_file;
char sym[512];
char type;
int version = 0;
/* 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 == NULL) {
if ((mapfile = FindSymbolFile()) == NULL) {
Syslog(LOG_WARNING, "Cannot find a map file.");
if (debugging)
fputs("Cannot find a map file.\n", stderr);
return 0;
}
}
if ((sym_file = fopen(mapfile, "r")) == NULL) {
Syslog(LOG_WARNING, "Cannot open map file: %s.", mapfile);
if (debugging)
fprintf(stderr, "Cannot open map file: %s.\n", mapfile);
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 %511s\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.
*
* Arguments: 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(void)
{
static char symfile[100];
struct utsname utsname;
FILE *sym_file = NULL;
char **mf = system_maps;
char *file = NULL;
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 != NULL && file == NULL; ++mf) {
sprintf(symfile, "%s-%s", *mf, utsname.release);
if (debugging)
fprintf(stderr, "Trying %s.\n", symfile);
if ((sym_file = fopen(symfile, "r")) != NULL) {
if (CheckMapVersion(symfile) == 1)
file = symfile;
fclose(sym_file);
}
if (sym_file == NULL || file == NULL) {
sprintf(symfile, "%s", *mf);
if (debugging)
fprintf(stderr, "Trying %s.\n", symfile);
if ((sym_file = fopen(symfile, "r")) != NULL) {
if (CheckMapVersion(symfile) == 1)
file = symfile;
fclose(sym_file);
}
}
}
/*
* 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... :-)
*
* Arguments: (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(char *version)
{
static char *prefix = { "Version_" };
int vnum;
int major;
int minor;
int patch;
#ifndef TESTING
struct utsname utsname;
int kvnum;
#endif
/* 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.
*
* Arguments: (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(char *fname)
{
unsigned long int address;
FILE *sym_file;
char sym[512];
char type;
int version;
if ((sym_file = fopen(fname, "r")) != NULL) {
/*
* 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 %511s\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.
*
* Arguments: (unsigned long) address, (char *) symbol
*
* Return: int
*
* A boolean value is assumed. True if the addition is
* successful. False if not.
**************************************************************************/
static int AddSymbol(unsigned long address, char *symbol)
{
/* Allocate the the symbol table entry. */
sym_array = realloc(sym_array, (num_syms + 1) * sizeof(struct sym_table));
if (sym_array == NULL)
return 0;
/* Then the space for the symbol. */
sym_array[num_syms].name = malloc(strlen(symbol) * sizeof(char) + 1);
if (sym_array[num_syms].name == NULL)
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.
*
* Arguments: (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(unsigned long value, struct symbol *sym)
{
struct symbol ksym, msym;
char *last;
char *name;
int lp;
if (!sym_array)
return NULL;
last = sym_array[0].name;
ksym.offset = 0;
ksym.size = 0;
if (value < sym_array[0].value)
return NULL;
for (lp = 0; lp <= num_syms; ++lp) {
if (sym_array[lp].value > value) {
ksym.offset = value - sym_array[lp - 1].value;
ksym.size = sym_array[lp].value -
sym_array[lp - 1].value;
break;
}
last = sym_array[lp].name;
}
name = LookupModuleSymbol(value, &msym);
if (ksym.offset == 0 && msym.offset == 0) {
return NULL;
}
if (ksym.offset == 0 || msym.offset < 0 ||
(ksym.offset > 0 && ksym.offset < msym.offset)) {
sym->offset = ksym.offset;
sym->size = ksym.size;
return last;
} else {
sym->offset = msym.offset;
sym->size = msym.size;
return name;
}
return NULL;
}
/**************************************************************************
* 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.
*
* Arguments: void
*
* Return: void
**************************************************************************/
static void FreeSymbols(void)
{
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 = NULL;
num_syms = 0;
}
/**************************************************************************
* Function: LogExpanded
*
* Purpose: This function is responsible for logging a kernel message
* line after all potential numeric kernel addresses have
* been resolved symolically.
*
* Arguments: (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
**************************************************************************/
char *ExpandKadds(char *line, char *el)
{
unsigned long int value;
struct symbol sym;
char *symbol;
char *elp = el;
char *sl = line;
char *kp;
char num[15];
sym.offset = 0;
sym.size = 0;
/*
* 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:") != NULL) && !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, "[<")) == NULL) {
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, ">]")) == NULL) {
strcpy(el, sl);
return el;
}
strncpy(num, sl + 1, kp - sl - 1);
num[kp - sl - 1] = '\0';
value = strtoul(num, NULL, 16);
if ((symbol = LookupSymbol(value, &sym)) == NULL)
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, "+0x%x/0x%02x", sym.offset, sym.size);
}
strncat(elp, kp, value);
elp += value;
sl = kp + value;
if ((kp = strstr(sl, "[<")) == NULL)
strcat(elp, sl);
} while (kp != NULL);
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.
*
* Arguments: (int) level
*
* level:-> The amount of paranoia which is to be
* present when resolving kernel exceptions.
* Return: void
**************************************************************************/
void SetParanoiaLevel(int level)
{
i_am_paranoid = level;
}
/*
* 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>
int main(int argc, char *argv[])
{
char line[1024], eline[2048];
debugging = 1;
if (!InitKsyms(NULL)) {
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;
}
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);
}
#endif
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/