blob: 65e5ca2d49360b984ba12caf04e4f9fae31b767d [file] [log] [blame]
/* Standard libdwfl callbacks for debugging the running Linux kernel.
Copyright (C) 2005 Red Hat, Inc.
This program is Open Source software; you can redistribute it and/or
modify it under the terms of the Open Software License version 1.0 as
published by the Open Source Initiative.
You should have received a copy of the Open Software License along
with this program; if not, you may obtain a copy of the Open Software
License version 1.0 from http://www.opensource.org/licenses/osl.php or
by writing the Open Source Initiative c/o Lawrence Rosen, Esq.,
3001 King Ranch Road, Ukiah, CA 95482. */
#include <config.h>
#undef _FILE_OFFSET_BITS /* Doesn't jibe with fts. */
#include "libdwflP.h"
#include <inttypes.h>
#include <errno.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <string.h>
#include <stdlib.h>
#include <sys/utsname.h>
#include <fcntl.h>
#include <unistd.h>
#include <fts.h>
#define MODULEDIRFMT "/lib/modules/%s"
#define MODULELIST "/proc/modules"
#define SECADDRFMT "/sys/module/%s/sections/%s"
static inline const char *
kernel_release (void)
{
/* Cache the `uname -r` string we'll use. */
static struct utsname utsname;
if (utsname.release[0] == '\0' && uname (&utsname) != 0)
return NULL;
return utsname.release;
}
/* Find the ELF file for the running kernel and dwfl_report_elf it. */
int
dwfl_linux_kernel_report_kernel (Dwfl *dwfl)
{
if (dwfl == NULL)
return -1;
const char *release = kernel_release ();
if (release == NULL)
return errno;
char *fname = NULL;
asprintf (&fname, "/boot/vmlinux-%s", release);
if (fname == NULL)
return -1;
int fd = open64 (fname, O_RDONLY);
if (fd < 0)
{
free (fname);
fname = NULL;
asprintf (&fname, "/usr/lib/debug" MODULEDIRFMT "/vmlinux", release);
if (fname == NULL)
return -1;
fd = open64 (fname, O_RDONLY);
}
int result = 0;
if (fd < 0)
result = errno;
else if (INTUSE(dwfl_report_elf) (dwfl, "kernel", fname, fd, 0) == NULL)
{
close (fd);
result = -1;
}
free (fname);
return result;
}
INTDEF (dwfl_linux_kernel_report_kernel)
/* Dwfl_Callbacks.find_elf for the running Linux kernel and its modules. */
int
dwfl_linux_kernel_find_elf (Dwfl_Module *mod __attribute__ ((unused)),
void **userdata __attribute__ ((unused)),
const char *module_name,
Dwarf_Addr base __attribute__ ((unused)),
char **file_name,
Elf **elfp __attribute__ ((unused)))
{
const char *release = kernel_release ();
if (release == NULL)
return -1;
/* Do "find /lib/modules/`uname -r` -name MODULE_NAME.ko". */
char *modulesdir[] = { NULL, NULL };
asprintf (&modulesdir[0], MODULEDIRFMT "/kernel", release);
if (modulesdir[0] == NULL)
return -1;
FTS *fts = fts_open (modulesdir, FTS_LOGICAL | FTS_NOSTAT, NULL);
if (fts == NULL)
{
free (modulesdir[0]);
return -1;
}
size_t namelen = strlen (module_name);
FTSENT *f;
int error = ENOENT;
while ((f = fts_read (fts)) != NULL)
{
error = ENOENT;
switch (f->fts_info)
{
case FTS_F:
case FTS_NSOK:
/* See if this file name is "MODULE_NAME.ko". */
if (f->fts_namelen == namelen + 3
&& !memcmp (f->fts_name, module_name, namelen)
&& !memcmp (f->fts_name + namelen, ".ko", 4))
{
int fd = open64 (f->fts_accpath, O_RDONLY);
*file_name = strdup (f->fts_path);
fts_close (fts);
if (fd < 0)
free (*file_name);
else if (*file_name == NULL)
{
close (fd);
fd = -1;
}
return fd;
}
break;
case FTS_ERR:
case FTS_DNR:
case FTS_NS:
error = f->fts_errno;
break;
default:
break;
}
}
errno = error;
return -1;
}
INTDEF (dwfl_linux_kernel_find_elf)
/* Dwfl_Callbacks.section_address for kernel modules in the running Linux.
We read the information from /sys/module directly. */
int
dwfl_linux_kernel_module_section_address
(Dwfl_Module *mod __attribute__ ((unused)),
void **userdata __attribute__ ((unused)),
const char *modname, Dwarf_Addr base __attribute__ ((unused)),
const char *secname, Dwarf_Addr *addr)
{
char *sysfile = NULL;
asprintf (&sysfile, SECADDRFMT, modname, secname);
if (sysfile == NULL)
return ENOMEM;
FILE *f = fopen (sysfile, "r");
if (f == NULL)
{
if (errno == ENOENT)
{
/* The .modinfo and .data.percpu sections are never kept
loaded in the kernel. If the kernel was compiled without
CONFIG_MODULE_UNLOAD, the .exit.* sections are not
actually loaded at all.
Just relocate these bogusly to zero. This part of the
debug information will not be of any use. */
if (!strcmp (secname, ".modinfo")
|| !strcmp (secname, ".data.percpu")
|| !strncmp (secname, ".exit", 5))
{
*addr = 0;
return DWARF_CB_OK;
}
}
return DWARF_CB_ABORT;
}
(void) __fsetlocking (f, FSETLOCKING_BYCALLER);
int result = (fscanf (f, "%" PRIi64 "\n", addr) == 1 ? 0
: ferror_unlocked (f) ? errno : ENOEXEC);
fclose (f);
if (result == 0)
return DWARF_CB_OK;
errno = result;
return DWARF_CB_ABORT;
}
INTDEF (dwfl_linux_kernel_module_section_address)
int
dwfl_linux_kernel_report_modules (Dwfl *dwfl)
{
FILE *f = fopen (MODULELIST, "r");
if (f == NULL)
return errno;
(void) __fsetlocking (f, FSETLOCKING_BYCALLER);
int result = 0;
Dwarf_Addr modaddr;
unsigned long int modsz;
char modname[128];
while (fscanf (f, "%128s %lu %*s %*s %*s %" PRIi64 "\n",
modname, &modsz, &modaddr) == 3)
if (INTUSE(dwfl_report_module) (dwfl, modname,
modaddr, modaddr + modsz) == NULL)
{
result = -1;
break;
}
if (result == 0)
result = ferror_unlocked (f) ? errno : feof_unlocked (f) ? 0 : ENOEXEC;
fclose (f);
return result;
}
INTDEF (dwfl_linux_kernel_report_modules)