linux-kernel-modules.c revision d17fac7e89666b47811581b10b5ca0d253a3a82d
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)) ? 0 : errno;
92  else
93    {
94      bool report = true;
95
96      if (predicate != NULL)
97	{
98	  /* Let the predicate decide whether to use this one.  */
99	  int want = (*predicate) ("kernel", fname);
100	  if (want < 0)
101	    result = errno;
102	  report = want > 0;
103	}
104
105      if (report
106	  && INTUSE(dwfl_report_elf) (dwfl, "kernel", fname, fd, 0) == NULL)
107	{
108	  close (fd);
109	  result = -1;
110	}
111    }
112
113  free (fname);
114
115  return result;
116}
117
118/* Report a kernel and all its modules found on disk, for offline use.
119   If RELEASE starts with '/', it names a directory to look in;
120   if not, it names a directory to find under /lib/modules/;
121   if null, /lib/modules/`uname -r` is used.
122   Returns zero on success, -1 if dwfl_report_module failed,
123   or an errno code if finding the files on disk failed.  */
124int
125dwfl_linux_kernel_report_offline (Dwfl *dwfl, const char *release,
126				  int (*predicate) (const char *module,
127						    const char *file))
128{
129  if (release == NULL)
130    {
131      release = kernel_release ();
132      if (release == NULL)
133	return errno;
134    }
135
136  /* First report the kernel.  */
137  int result = report_kernel (dwfl, release, predicate);
138  if (result == 0)
139    {
140      /* Do "find /lib/modules/RELEASE/kernel -name *.ko".  */
141
142      char *modulesdir[] = { NULL, NULL };
143      if (release[0] == '/')
144	modulesdir[0] = (char *) release;
145      else
146	{
147	  asprintf (&modulesdir[0], MODULEDIRFMT "/kernel", release);
148	  if (modulesdir[0] == NULL)
149	    return errno;
150	}
151
152      FTS *fts = fts_open (modulesdir, FTS_LOGICAL | FTS_NOSTAT, NULL);
153      if (modulesdir[0] == (char *) release)
154	modulesdir[0] = NULL;
155      if (fts == NULL)
156	{
157	  free (modulesdir[0]);
158	  return errno;
159	}
160
161      FTSENT *f;
162      while ((f = fts_read (fts)) != NULL)
163	{
164	  switch (f->fts_info)
165	    {
166	    case FTS_F:
167	    case FTS_NSOK:
168	      /* See if this file name matches "*.ko".  */
169	      if (f->fts_namelen > 3
170		  && !memcmp (f->fts_name + f->fts_namelen - 3, ".ko", 4))
171		{
172		  /* We have a .ko file to report.  Following the algorithm
173		     by which the kernel makefiles set KBUILD_MODNAME, we
174		     replace all ',' or '-' with '_' in the file name and
175		     call that the module name.  Modules could well be
176		     built using different embedded names than their file
177		     names.  To handle that, we would have to look at the
178		     __this_module.name contents in the module's text.  */
179
180		  char name[f->fts_namelen - 3 + 1];
181		  for (size_t i = 0; i < f->fts_namelen - 3U; ++i)
182		    if (f->fts_name[i] == '-' || f->fts_name[i] == ',')
183		      name[i] = '_';
184		    else
185		      name[i] = f->fts_name[i];
186		  name[f->fts_namelen - 3] = '\0';
187
188		  if (predicate != NULL)
189		    {
190		      /* Let the predicate decide whether to use this one.  */
191		      int want = (*predicate) (name, f->fts_path);
192		      if (want < 0)
193			{
194			  result = -1;
195			  break;
196			}
197		      if (!want)
198			continue;
199		    }
200
201		  if (dwfl_report_offline (dwfl, name,
202					   f->fts_path, -1) == NULL)
203		    {
204		      result = -1;
205		      break;
206		    }
207		}
208	      continue;
209
210	    case FTS_ERR:
211	    case FTS_DNR:
212	    case FTS_NS:
213	      result = f->fts_errno;
214	      break;
215
216	    default:
217	      continue;
218	    }
219
220	  /* We only get here in error cases.  */
221	  break;
222	}
223      fts_close (fts);
224      free (modulesdir[0]);
225    }
226
227  return result;
228}
229INTDEF (dwfl_linux_kernel_report_offline)
230
231
232/* Find the ELF file for the running kernel and dwfl_report_elf it.  */
233int
234dwfl_linux_kernel_report_kernel (Dwfl *dwfl)
235{
236  const char *release = kernel_release ();
237  if (release == NULL)
238    return errno;
239
240  return report_kernel (dwfl, release, NULL);
241}
242INTDEF (dwfl_linux_kernel_report_kernel)
243
244
245/* Dwfl_Callbacks.find_elf for the running Linux kernel and its modules.  */
246
247int
248dwfl_linux_kernel_find_elf (Dwfl_Module *mod __attribute__ ((unused)),
249			    void **userdata __attribute__ ((unused)),
250			    const char *module_name,
251			    Dwarf_Addr base __attribute__ ((unused)),
252			    char **file_name,
253			    Elf **elfp __attribute__ ((unused)))
254{
255  const char *release = kernel_release ();
256  if (release == NULL)
257    return errno;
258
259  /* Do "find /lib/modules/`uname -r`/kernel -name MODULE_NAME.ko".  */
260
261  char *modulesdir[] = { NULL, NULL };
262  asprintf (&modulesdir[0], MODULEDIRFMT "/kernel", release);
263  if (modulesdir[0] == NULL)
264    return -1;
265
266  FTS *fts = fts_open (modulesdir, FTS_LOGICAL | FTS_NOSTAT, NULL);
267  if (fts == NULL)
268    {
269      free (modulesdir[0]);
270      return -1;
271    }
272
273  size_t namelen = strlen (module_name);
274
275  /* This is a kludge.  There is no actual necessary relationship between
276     the name of the .ko file installed and the module name the kernel
277     knows it by when it's loaded.  The kernel's only idea of the module
278     name comes from the name embedded in the object's magic
279     .gnu.linkonce.this_module section.
280
281     In practice, these module names match the .ko file names except for
282     some using '_' and some using '-'.  So our cheap kludge is to look for
283     two files when either a '_' or '-' appears in a module name, one using
284     only '_' and one only using '-'.  */
285
286  char alternate_name[namelen + 1];
287  inline bool subst_name (char from, char to)
288    {
289      const char *n = memchr (module_name, from, namelen);
290      if (n == NULL)
291	return false;
292      char *a = mempcpy (alternate_name, module_name, n - module_name);
293      *a++ = to;
294      ++n;
295      const char *p;
296      while ((p = memchr (n, from, namelen - (n - module_name))) != NULL)
297	{
298	  a = mempcpy (a, n, p - n);
299	  *a++ = to;
300	  n = p + 1;
301	}
302      memcpy (a, n, namelen - (n - module_name) + 1);
303      return true;
304    }
305  if (!subst_name ('-', '_') && !subst_name ('_', '-'))
306    alternate_name[0] = '\0';
307
308  FTSENT *f;
309  int error = ENOENT;
310  while ((f = fts_read (fts)) != NULL)
311    {
312      error = ENOENT;
313      switch (f->fts_info)
314	{
315	case FTS_F:
316	case FTS_NSOK:
317	  /* See if this file name is "MODULE_NAME.ko".  */
318	  if (f->fts_namelen == namelen + 3
319	      && !memcmp (f->fts_name + namelen, ".ko", 4)
320	      && (!memcmp (f->fts_name, module_name, namelen)
321		  || !memcmp (f->fts_name, alternate_name, namelen)))
322	    {
323	      int fd = open64 (f->fts_accpath, O_RDONLY);
324	      *file_name = strdup (f->fts_path);
325	      fts_close (fts);
326	      free (modulesdir[0]);
327	      if (fd < 0)
328		free (*file_name);
329	      else if (*file_name == NULL)
330		{
331		  close (fd);
332		  fd = -1;
333		}
334	      return fd;
335	    }
336	  break;
337
338	case FTS_ERR:
339	case FTS_DNR:
340	case FTS_NS:
341	  error = f->fts_errno;
342	  break;
343
344	default:
345	  break;
346	}
347    }
348
349  fts_close (fts);
350  free (modulesdir[0]);
351  errno = error;
352  return -1;
353}
354INTDEF (dwfl_linux_kernel_find_elf)
355
356
357/* Dwfl_Callbacks.section_address for kernel modules in the running Linux.
358   We read the information from /sys/module directly.  */
359
360int
361dwfl_linux_kernel_module_section_address
362(Dwfl_Module *mod __attribute__ ((unused)),
363 void **userdata __attribute__ ((unused)),
364 const char *modname, Dwarf_Addr base __attribute__ ((unused)),
365 const char *secname, Elf32_Word shndx __attribute__ ((unused)),
366 const GElf_Shdr *shdr __attribute__ ((unused)),
367 Dwarf_Addr *addr)
368{
369  char *sysfile = NULL;
370  asprintf (&sysfile, SECADDRFMT, modname, secname);
371  if (sysfile == NULL)
372    return ENOMEM;
373
374  FILE *f = fopen (sysfile, "r");
375  if (f == NULL)
376    {
377      if (errno == ENOENT)
378	{
379	  /* The .modinfo and .data.percpu sections are never kept
380	     loaded in the kernel.  If the kernel was compiled without
381	     CONFIG_MODULE_UNLOAD, the .exit.* sections are not
382	     actually loaded at all.
383
384	     Just relocate these bogusly to zero.  This part of the
385	     debug information will not be of any use.  */
386
387	  if (!strcmp (secname, ".modinfo")
388	      || !strcmp (secname, ".data.percpu")
389	      || !strncmp (secname, ".exit", 5))
390	    {
391	      *addr = 0;
392	      return DWARF_CB_OK;
393	    }
394	}
395
396      return DWARF_CB_ABORT;
397    }
398
399  (void) __fsetlocking (f, FSETLOCKING_BYCALLER);
400
401  int result = (fscanf (f, "%" PRIi64 "\n", addr) == 1 ? 0
402		: ferror_unlocked (f) ? errno : ENOEXEC);
403  fclose (f);
404
405  if (result == 0)
406    return DWARF_CB_OK;
407
408  errno = result;
409  return DWARF_CB_ABORT;
410}
411INTDEF (dwfl_linux_kernel_module_section_address)
412
413int
414dwfl_linux_kernel_report_modules (Dwfl *dwfl)
415{
416  FILE *f = fopen (MODULELIST, "r");
417  if (f == NULL)
418    return errno;
419
420  (void) __fsetlocking (f, FSETLOCKING_BYCALLER);
421
422  int result = 0;
423  Dwarf_Addr modaddr;
424  unsigned long int modsz;
425  char modname[128];
426  while (fscanf (f, "%128s %lu %*s %*s %*s %" PRIi64 "\n",
427		 modname, &modsz, &modaddr) == 3)
428    if (INTUSE(dwfl_report_module) (dwfl, modname,
429				    modaddr, modaddr + modsz) == NULL)
430      {
431	result = -1;
432	break;
433      }
434
435  if (result == 0)
436    result = ferror_unlocked (f) ? errno : feof_unlocked (f) ? 0 : ENOEXEC;
437
438  fclose (f);
439
440  return result;
441}
442INTDEF (dwfl_linux_kernel_report_modules)
443