linux-kernel-modules.c revision 2e53b9950e52c08cc61c30755e58523860619b8a
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  free (sysfile);
377
378  if (f == NULL)
379    {
380      if (errno == ENOENT)
381	{
382	  /* The .modinfo and .data.percpu sections are never kept
383	     loaded in the kernel.  If the kernel was compiled without
384	     CONFIG_MODULE_UNLOAD, the .exit.* sections are not
385	     actually loaded at all.
386
387	     Just relocate these bogusly to zero.  This part of the
388	     debug information will not be of any use.  */
389
390	  if (!strcmp (secname, ".modinfo")
391	      || !strcmp (secname, ".data.percpu")
392	      || !strncmp (secname, ".exit", 5))
393	    {
394	      *addr = 0;
395	      return DWARF_CB_OK;
396	    }
397
398	  /* The goofy PPC64 module_frob_arch_sections function tweaks
399	     the section names as a way to control other kernel code's
400	     behavior, and this cruft leaks out into the /sys information.
401	     The file name for ".init*" may actually look like "_init*".  */
402
403	  if (!strncmp (secname, ".init", 5))
404	    {
405	      sysfile = NULL;
406	      asprintf (&sysfile, SECADDRFMT "%s", modname, "_", &secname[1]);
407	      if (sysfile == NULL)
408		return ENOMEM;
409	      f = fopen (sysfile, "r");
410	      free (sysfile);
411	      if (f != NULL)
412		goto ok;
413	    }
414	}
415
416      return DWARF_CB_ABORT;
417    }
418
419 ok:
420  (void) __fsetlocking (f, FSETLOCKING_BYCALLER);
421
422  int result = (fscanf (f, "%" PRIx64 "\n", addr) == 1 ? 0
423		: ferror_unlocked (f) ? errno : ENOEXEC);
424  fclose (f);
425
426  if (result == 0)
427    return DWARF_CB_OK;
428
429  errno = result;
430  return DWARF_CB_ABORT;
431}
432INTDEF (dwfl_linux_kernel_module_section_address)
433
434int
435dwfl_linux_kernel_report_modules (Dwfl *dwfl)
436{
437  FILE *f = fopen (MODULELIST, "r");
438  if (f == NULL)
439    return errno;
440
441  (void) __fsetlocking (f, FSETLOCKING_BYCALLER);
442
443  int result = 0;
444  Dwarf_Addr modaddr;
445  unsigned long int modsz;
446  char modname[128];
447  while (fscanf (f, "%128s %lu %*s %*s %*s %" PRIx64 "\n",
448		 modname, &modsz, &modaddr) == 3)
449    if (INTUSE(dwfl_report_module) (dwfl, modname,
450				    modaddr, modaddr + modsz) == NULL)
451      {
452	result = -1;
453	break;
454      }
455
456  if (result == 0)
457    result = ferror_unlocked (f) ? errno : feof_unlocked (f) ? 0 : ENOEXEC;
458
459  fclose (f);
460
461  return result;
462}
463INTDEF (dwfl_linux_kernel_report_modules)
464