summaryrefslogtreecommitdiff
path: root/libdwfl/linux-kernel-modules.c
diff options
context:
space:
mode:
Diffstat (limited to 'libdwfl/linux-kernel-modules.c')
-rw-r--r--libdwfl/linux-kernel-modules.c243
1 files changed, 243 insertions, 0 deletions
diff --git a/libdwfl/linux-kernel-modules.c b/libdwfl/linux-kernel-modules.c
new file mode 100644
index 00000000..65e5ca2d
--- /dev/null
+++ b/libdwfl/linux-kernel-modules.c
@@ -0,0 +1,243 @@
+/* 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)