linux-kernel-modules.c revision 07d4f2fc1cb53f170a71bc13617bbdd9cb1c3c60
1/* Standard libdwfl callbacks for debugging the running Linux kernel.
2   Copyright (C) 2005 Red Hat, Inc.
3
4   This program is Open Source software; you can redistribute it and/or
5   modify it under the terms of the Open Software License version 1.0 as
6   published by the Open Source Initiative.
7
8   You should have received a copy of the Open Software License along
9   with this program; if not, you may obtain a copy of the Open Software
10   License version 1.0 from http://www.opensource.org/licenses/osl.php or
11   by writing the Open Source Initiative c/o Lawrence Rosen, Esq.,
12   3001 King Ranch Road, Ukiah, CA 95482.   */
13
14#include <config.h>
15#undef	_FILE_OFFSET_BITS	/* Doesn't jibe with fts.  */
16
17#include "libdwflP.h"
18#include <inttypes.h>
19#include <errno.h>
20#include <stdio.h>
21#include <stdio_ext.h>
22#include <string.h>
23#include <stdlib.h>
24#include <sys/utsname.h>
25#include <fcntl.h>
26#include <unistd.h>
27#include <fts.h>
28
29
30#define MODULEDIRFMT	"/lib/modules/%s"
31
32#define MODULELIST	"/proc/modules"
33#define	SECADDRFMT	"/sys/module/%s/sections/%s"
34
35
36/* Try to open the given file as it is or under the debuginfo directory.  */
37static int
38try_kernel_name (Dwfl *dwfl, char **fname)
39{
40  if (*fname == NULL)
41    return -1;
42
43  int fd = TEMP_FAILURE_RETRY (open64 (*fname, O_RDONLY));
44  if (fd < 0)
45    {
46      char *debugfname = NULL;
47      Dwfl_Module fakemod = { .dwfl = dwfl };
48      fd = INTUSE(dwfl_standard_find_debuginfo) (&fakemod, NULL, NULL, 0,
49						 *fname, basename (*fname), 0,
50						 &debugfname);
51      free (*fname);
52      *fname = debugfname;
53    }
54
55  return fd;
56}
57
58static inline const char *
59kernel_release (void)
60{
61  /* Cache the `uname -r` string we'll use.  */
62  static struct utsname utsname;
63  if (utsname.release[0] == '\0' && uname (&utsname) != 0)
64    return NULL;
65  return utsname.release;
66}
67
68static int
69report_kernel (Dwfl *dwfl, const char *release,
70	       int (*predicate) (const char *module, const char *file))
71{
72  if (dwfl == NULL)
73    return -1;
74
75  char *fname = NULL;
76  if (release[0] == '/')
77    asprintf (&fname, "%s/vmlinux", release);
78  else
79    asprintf (&fname, "/boot/vmlinux-%s", release);
80  int fd = try_kernel_name (dwfl, &fname);
81  if (fd < 0 && release[0] != '/')
82    {
83      free (fname);
84      fname = NULL;
85      asprintf (&fname, MODULEDIRFMT "/vmlinux", release);
86      fd = try_kernel_name (dwfl, &fname);
87    }
88
89  int result = 0;
90  if (fd < 0)
91    result = ((predicate != NULL && !(*predicate) ("kernel", NULL))
92	      ? 0 : errno ?: ENOENT);
93  else
94    {
95      bool report = true;
96
97      if (predicate != NULL)
98	{
99	  /* Let the predicate decide whether to use this one.  */
100	  int want = (*predicate) ("kernel", fname);
101	  if (want < 0)
102	    result = errno;
103	  report = want > 0;
104	}
105
106      if (report
107	  && INTUSE(dwfl_report_elf) (dwfl, "kernel", fname, fd, 0) == NULL)
108	{
109	  close (fd);
110	  result = -1;
111	}
112    }
113
114  free (fname);
115
116  return result;
117}
118
119/* Report a kernel and all its modules found on disk, for offline use.
120   If RELEASE starts with '/', it names a directory to look in;
121   if not, it names a directory to find under /lib/modules/;
122   if null, /lib/modules/`uname -r` is used.
123   Returns zero on success, -1 if dwfl_report_module failed,
124   or an errno code if finding the files on disk failed.  */
125int
126dwfl_linux_kernel_report_offline (Dwfl *dwfl, const char *release,
127				  int (*predicate) (const char *module,
128						    const char *file))
129{
130  if (release == NULL)
131    {
132      release = kernel_release ();
133      if (release == NULL)
134	return errno;
135    }
136
137  /* First report the kernel.  */
138  int result = report_kernel (dwfl, release, predicate);
139  if (result == 0)
140    {
141      /* Do "find /lib/modules/RELEASE/kernel -name *.ko".  */
142
143      char *modulesdir[] = { NULL, NULL };
144      if (release[0] == '/')
145	modulesdir[0] = (char *) release;
146      else
147	{
148	  asprintf (&modulesdir[0], MODULEDIRFMT "/kernel", release);
149	  if (modulesdir[0] == NULL)
150	    return errno;
151	}
152
153      FTS *fts = fts_open (modulesdir, FTS_LOGICAL | FTS_NOSTAT, NULL);
154      if (modulesdir[0] == (char *) release)
155	modulesdir[0] = NULL;
156      if (fts == NULL)
157	{
158	  free (modulesdir[0]);
159	  return errno;
160	}
161
162      FTSENT *f;
163      while ((f = fts_read (fts)) != NULL)
164	{
165	  switch (f->fts_info)
166	    {
167	    case FTS_F:
168	    case FTS_NSOK:
169	      /* See if this file name matches "*.ko".  */
170	      if (f->fts_namelen > 3
171		  && !memcmp (f->fts_name + f->fts_namelen - 3, ".ko", 4))
172		{
173		  /* We have a .ko file to report.  Following the algorithm
174		     by which the kernel makefiles set KBUILD_MODNAME, we
175		     replace all ',' or '-' with '_' in the file name and
176		     call that the module name.  Modules could well be
177		     built using different embedded names than their file
178		     names.  To handle that, we would have to look at the
179		     __this_module.name contents in the module's text.  */
180
181		  char name[f->fts_namelen - 3 + 1];
182		  for (size_t i = 0; i < f->fts_namelen - 3U; ++i)
183		    if (f->fts_name[i] == '-' || f->fts_name[i] == ',')
184		      name[i] = '_';
185		    else
186		      name[i] = f->fts_name[i];
187		  name[f->fts_namelen - 3] = '\0';
188
189		  if (predicate != NULL)
190		    {
191		      /* Let the predicate decide whether to use this one.  */
192		      int want = (*predicate) (name, f->fts_path);
193		      if (want < 0)
194			{
195			  result = -1;
196			  break;
197			}
198		      if (!want)
199			continue;
200		    }
201
202		  if (dwfl_report_offline (dwfl, name,
203					   f->fts_path, -1) == NULL)
204		    {
205		      result = -1;
206		      break;
207		    }
208		}
209	      continue;
210
211	    case FTS_ERR:
212	    case FTS_DNR:
213	    case FTS_NS:
214	      result = f->fts_errno;
215	      break;
216
217	    default:
218	      continue;
219	    }
220
221	  /* We only get here in error cases.  */
222	  break;
223	}
224      fts_close (fts);
225      free (modulesdir[0]);
226    }
227
228  return result;
229}
230INTDEF (dwfl_linux_kernel_report_offline)
231
232
233/* Find the ELF file for the running kernel and dwfl_report_elf it.  */
234int
235dwfl_linux_kernel_report_kernel (Dwfl *dwfl)
236{
237  const char *release = kernel_release ();
238  if (release == NULL)
239    return errno;
240
241  return report_kernel (dwfl, release, NULL);
242}
243INTDEF (dwfl_linux_kernel_report_kernel)
244
245
246/* Dwfl_Callbacks.find_elf for the running Linux kernel and its modules.  */
247
248int
249dwfl_linux_kernel_find_elf (Dwfl_Module *mod __attribute__ ((unused)),
250			    void **userdata __attribute__ ((unused)),
251			    const char *module_name,
252			    Dwarf_Addr base __attribute__ ((unused)),
253			    char **file_name,
254			    Elf **elfp __attribute__ ((unused)))
255{
256  const char *release = kernel_release ();
257  if (release == NULL)
258    return errno;
259
260  /* Do "find /lib/modules/`uname -r`/kernel -name MODULE_NAME.ko".  */
261
262  char *modulesdir[] = { NULL, NULL };
263  asprintf (&modulesdir[0], MODULEDIRFMT "/kernel", release);
264  if (modulesdir[0] == NULL)
265    return -1;
266
267  FTS *fts = fts_open (modulesdir, FTS_LOGICAL | FTS_NOSTAT, NULL);
268  if (fts == NULL)
269    {
270      free (modulesdir[0]);
271      return -1;
272    }
273
274  size_t namelen = strlen (module_name);
275
276  /* This is a kludge.  There is no actual necessary relationship between
277     the name of the .ko file installed and the module name the kernel
278     knows it by when it's loaded.  The kernel's only idea of the module
279     name comes from the name embedded in the object's magic
280     .gnu.linkonce.this_module section.
281
282     In practice, these module names match the .ko file names except for
283     some using '_' and some using '-'.  So our cheap kludge is to look for
284     two files when either a '_' or '-' appears in a module name, one using
285     only '_' and one only using '-'.  */
286
287  char alternate_name[namelen + 1];
288  inline bool subst_name (char from, char to)
289    {
290      const char *n = memchr (module_name, from, namelen);
291      if (n == NULL)
292	return false;
293      char *a = mempcpy (alternate_name, module_name, n - module_name);
294      *a++ = to;
295      ++n;
296      const char *p;
297      while ((p = memchr (n, from, namelen - (n - module_name))) != NULL)
298	{
299	  a = mempcpy (a, n, p - n);
300	  *a++ = to;
301	  n = p + 1;
302	}
303      memcpy (a, n, namelen - (n - module_name) + 1);
304      return true;
305    }
306  if (!subst_name ('-', '_') && !subst_name ('_', '-'))
307    alternate_name[0] = '\0';
308
309  FTSENT *f;
310  int error = ENOENT;
311  while ((f = fts_read (fts)) != NULL)
312    {
313      error = ENOENT;
314      switch (f->fts_info)
315	{
316	case FTS_F:
317	case FTS_NSOK:
318	  /* See if this file name is "MODULE_NAME.ko".  */
319	  if (f->fts_namelen == namelen + 3
320	      && !memcmp (f->fts_name + namelen, ".ko", 4)
321	      && (!memcmp (f->fts_name, module_name, namelen)
322		  || !memcmp (f->fts_name, alternate_name, namelen)))
323	    {
324	      int fd = open64 (f->fts_accpath, O_RDONLY);
325	      *file_name = strdup (f->fts_path);
326	      fts_close (fts);
327	      free (modulesdir[0]);
328	      if (fd < 0)
329		free (*file_name);
330	      else if (*file_name == NULL)
331		{
332		  close (fd);
333		  fd = -1;
334		}
335	      return fd;
336	    }
337	  break;
338
339	case FTS_ERR:
340	case FTS_DNR:
341	case FTS_NS:
342	  error = f->fts_errno;
343	  break;
344
345	default:
346	  break;
347	}
348    }
349
350  fts_close (fts);
351  free (modulesdir[0]);
352  errno = error;
353  return -1;
354}
355INTDEF (dwfl_linux_kernel_find_elf)
356
357
358/* Dwfl_Callbacks.section_address for kernel modules in the running Linux.
359   We read the information from /sys/module directly.  */
360
361int
362dwfl_linux_kernel_module_section_address
363(Dwfl_Module *mod __attribute__ ((unused)),
364 void **userdata __attribute__ ((unused)),
365 const char *modname, Dwarf_Addr base __attribute__ ((unused)),
366 const char *secname, Elf32_Word shndx __attribute__ ((unused)),
367 const GElf_Shdr *shdr __attribute__ ((unused)),
368 Dwarf_Addr *addr)
369{
370  char *sysfile = NULL;
371  asprintf (&sysfile, SECADDRFMT, modname, secname);
372  if (sysfile == NULL)
373    return ENOMEM;
374
375  FILE *f = fopen (sysfile, "r");
376  if (f == NULL)
377    {
378      if (errno == ENOENT)
379	{
380	  /* The .modinfo and .data.percpu sections are never kept
381	     loaded in the kernel.  If the kernel was compiled without
382	     CONFIG_MODULE_UNLOAD, the .exit.* sections are not
383	     actually loaded at all.
384
385	     Just relocate these bogusly to zero.  This part of the
386	     debug information will not be of any use.  */
387
388	  if (!strcmp (secname, ".modinfo")
389	      || !strcmp (secname, ".data.percpu")
390	      || !strncmp (secname, ".exit", 5))
391	    {
392	      *addr = 0;
393	      return DWARF_CB_OK;
394	    }
395	}
396
397      return DWARF_CB_ABORT;
398    }
399
400  (void) __fsetlocking (f, FSETLOCKING_BYCALLER);
401
402  int result = (fscanf (f, "%" PRIx64 "\n", addr) == 1 ? 0
403		: ferror_unlocked (f) ? errno : ENOEXEC);
404  fclose (f);
405
406  if (result == 0)
407    return DWARF_CB_OK;
408
409  errno = result;
410  return DWARF_CB_ABORT;
411}
412INTDEF (dwfl_linux_kernel_module_section_address)
413
414int
415dwfl_linux_kernel_report_modules (Dwfl *dwfl)
416{
417  FILE *f = fopen (MODULELIST, "r");
418  if (f == NULL)
419    return errno;
420
421  (void) __fsetlocking (f, FSETLOCKING_BYCALLER);
422
423  int result = 0;
424  Dwarf_Addr modaddr;
425  unsigned long int modsz;
426  char modname[128];
427  while (fscanf (f, "%128s %lu %*s %*s %*s %" PRIx64 "\n",
428		 modname, &modsz, &modaddr) == 3)
429    if (INTUSE(dwfl_report_module) (dwfl, modname,
430				    modaddr, modaddr + modsz) == NULL)
431      {
432	result = -1;
433	break;
434      }
435
436  if (result == 0)
437    result = ferror_unlocked (f) ? errno : feof_unlocked (f) ? 0 : ENOEXEC;
438
439  fclose (f);
440
441  return result;
442}
443INTDEF (dwfl_linux_kernel_report_modules)
444