Coverage Report

Created: 2026-02-24 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/elfutils/libdwfl/find-debuginfo.c
Line
Count
Source
1
/* Standard find_debuginfo callback for libdwfl.
2
   Copyright (C) 2005-2010, 2014, 2015, 2019 Red Hat, Inc.
3
   This file is part of elfutils.
4
5
   This file is free software; you can redistribute it and/or modify
6
   it under the terms of either
7
8
     * the GNU Lesser General Public License as published by the Free
9
       Software Foundation; either version 3 of the License, or (at
10
       your option) any later version
11
12
   or
13
14
     * the GNU General Public License as published by the Free
15
       Software Foundation; either version 2 of the License, or (at
16
       your option) any later version
17
18
   or both in parallel, as here.
19
20
   elfutils is distributed in the hope that it will be useful, but
21
   WITHOUT ANY WARRANTY; without even the implied warranty of
22
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23
   General Public License for more details.
24
25
   You should have received copies of the GNU General Public License and
26
   the GNU Lesser General Public License along with this program.  If
27
   not, see <http://www.gnu.org/licenses/>.  */
28
29
#ifdef HAVE_CONFIG_H
30
# include <config.h>
31
#endif
32
33
#include "libdwflP.h"
34
#include <stdio.h>
35
#include <fcntl.h>
36
#include <sys/stat.h>
37
#include "system.h"
38
39
40
/* Try to open [DIR/][SUBDIR/]DEBUGLINK, return file descriptor or -1.
41
   On success, *DEBUGINFO_FILE_NAME has the malloc'd name of the open file.  */
42
static int
43
try_open (const struct stat *main_stat,
44
    const char *dir, const char *subdir, const char *debuglink,
45
    char **debuginfo_file_name)
46
3.95k
{
47
3.95k
  char *fname;
48
3.95k
  if (dir == NULL && subdir == NULL)
49
0
    {
50
0
      fname = strdup (debuglink);
51
0
      if (unlikely (fname == NULL))
52
0
  return -1;
53
0
    }
54
3.95k
  else if ((subdir == NULL ? asprintf (&fname, "%s/%s", dir, debuglink)
55
3.95k
      : dir == NULL ? asprintf (&fname, "%s/%s", subdir, debuglink)
56
7
      : asprintf (&fname, "%s/%s/%s", dir, subdir, debuglink)) < 0)
57
0
    return -1;
58
59
3.95k
  struct stat st;
60
3.95k
  int fd = TEMP_FAILURE_RETRY (open (fname, O_RDONLY));
61
3.95k
  if (fd < 0)
62
3.89k
    free (fname);
63
61
  else if (fstat (fd, &st) == 0
64
61
     && st.st_ino == main_stat->st_ino
65
0
     && st.st_dev == main_stat->st_dev)
66
0
    {
67
      /* This is the main file by another name.  Don't look at it again.  */
68
0
      free (fname);
69
0
      close (fd);
70
0
      errno = ENOENT;
71
0
      fd = -1;
72
0
    }
73
61
  else
74
61
    *debuginfo_file_name = fname;
75
76
3.95k
  return fd;
77
3.95k
}
78
79
/* Return true iff the FD's contents CRC matches DEBUGLINK_CRC.  */
80
static inline bool
81
check_crc (int fd, GElf_Word debuglink_crc)
82
44
{
83
44
  uint32_t file_crc;
84
44
  return (__libdwfl_crc32_file (fd, &file_crc) == 0
85
0
    && file_crc == debuglink_crc);
86
44
}
87
88
static bool
89
validate (Dwfl_Module *mod, int fd, bool check, GElf_Word debuglink_crc)
90
61
{
91
  /* For alt debug files always check the build-id from the Dwarf and alt.  */
92
61
  if (mod->dw != NULL)
93
3
    {
94
3
      bool valid = false;
95
3
      const void *build_id;
96
3
      const char *altname;
97
3
      ssize_t build_id_len = INTUSE(dwelf_dwarf_gnu_debugaltlink) (mod->dw,
98
3
                   &altname,
99
3
                   &build_id);
100
3
      if (build_id_len > 0)
101
3
  {
102
    /* We need to open an Elf handle on the file so we can check its
103
       build ID note for validation.  Backdoor the handle into the
104
       module data structure since we had to open it early anyway.  */
105
3
    Dwfl_Error error = __libdw_open_file (&fd, &mod->alt_elf,
106
3
            false, false);
107
3
    if (error != DWFL_E_NOERROR)
108
3
      __libdwfl_seterrno (error);
109
0
    else
110
0
      {
111
0
        const void *alt_build_id;
112
0
        ssize_t alt_len = INTUSE(dwelf_elf_gnu_build_id) (mod->alt_elf,
113
0
                &alt_build_id);
114
0
        if (alt_len > 0 && alt_len == build_id_len
115
0
      && memcmp (build_id, alt_build_id, alt_len) == 0)
116
0
    valid = true;
117
0
        else
118
0
    {
119
      /* A mismatch!  */
120
0
      elf_end (mod->alt_elf);
121
0
      mod->alt_elf = NULL;
122
0
      close (fd);
123
0
      fd = -1;
124
0
    }
125
0
      }
126
3
  }
127
3
      return valid;
128
3
    }
129
130
  /* If we have a build ID, check only that.  */
131
58
  if (mod->build_id_len > 0)
132
0
    {
133
      /* We need to open an Elf handle on the file so we can check its
134
   build ID note for validation.  Backdoor the handle into the
135
   module data structure since we had to open it early anyway.  */
136
137
0
      mod->debug.valid = false;
138
0
      Dwfl_Error error = __libdw_open_file (&fd, &mod->debug.elf, false, false);
139
0
      if (error != DWFL_E_NOERROR)
140
0
  __libdwfl_seterrno (error);
141
0
      else if (likely (__libdwfl_find_build_id (mod, false,
142
0
            mod->debug.elf) == 2))
143
  /* Also backdoor the gratuitous flag.  */
144
0
  mod->debug.valid = true;
145
0
      else
146
0
  {
147
    /* A mismatch!  */
148
0
    elf_end (mod->debug.elf);
149
0
    mod->debug.elf = NULL;
150
0
    close (fd);
151
0
    fd = -1;
152
0
  }
153
154
0
      return mod->debug.valid;
155
0
    }
156
157
58
  return !check || check_crc (fd, debuglink_crc);
158
58
}
159
160
static int
161
find_debuginfo_in_path (Dwfl_Module *mod, const char *file_name,
162
      const char *debuglink_file, GElf_Word debuglink_crc,
163
      char **debuginfo_file_name)
164
3.95k
{
165
3.95k
  bool cancheck = debuglink_crc != (GElf_Word) 0;
166
167
3.95k
  const char *file_basename = file_name == NULL ? NULL : xbasename (file_name);
168
3.95k
  char *localname = NULL;
169
170
  /* We invent a debuglink .debug name if NULL, but then want to try the
171
     basename too.  */
172
3.95k
  bool debuglink_null = debuglink_file == NULL;
173
3.95k
  if (debuglink_null)
174
3.86k
    {
175
      /* For a alt debug multi file we need a name, for a separate debug
176
   name we may be able to fall back on file_basename.debug.  */
177
3.86k
      if (file_basename == NULL || mod->dw != NULL)
178
0
  {
179
0
    errno = 0;
180
0
    return -1;
181
0
  }
182
183
3.86k
      size_t len = strlen (file_basename);
184
3.86k
      localname = malloc (len + sizeof ".debug");
185
3.86k
      if (unlikely (localname == NULL))
186
0
  return -1;
187
3.86k
      memcpy (localname, file_basename, len);
188
3.86k
      memcpy (&localname[len], ".debug", sizeof ".debug");
189
3.86k
      debuglink_file = localname;
190
3.86k
      cancheck = false;
191
3.86k
    }
192
193
  /* Look for a file named DEBUGLINK_FILE in the directories
194
     indicated by the debug directory path setting.  */
195
196
3.95k
  const Dwfl_Callbacks *const cb = mod->dwfl->callbacks;
197
3.95k
  char *localpath = strdup ((cb->debuginfo_path ? *cb->debuginfo_path : NULL)
198
3.95k
          ?: DEFAULT_DEBUGINFO_PATH);
199
3.95k
  if (unlikely (localpath == NULL))
200
0
    {
201
0
      free (localname);
202
0
      return -1;
203
0
    }
204
205
  /* A leading - or + in the whole path sets whether to check file CRCs.  */
206
3.95k
  bool defcheck = true;
207
3.95k
  char *path = localpath;
208
3.95k
  if (path[0] == '-' || path[0] == '+')
209
0
    {
210
0
      defcheck = path[0] == '+';
211
0
      ++path;
212
0
    }
213
214
  /* XXX dev/ino should be cached in struct dwfl_file.  */
215
3.95k
  struct stat main_stat;
216
3.95k
  if (unlikely ((mod->main.fd != -1 ? fstat (mod->main.fd, &main_stat)
217
3.95k
     : file_name != NULL ? stat (file_name, &main_stat)
218
3.95k
     : -1) < 0))
219
396
    {
220
396
      main_stat.st_dev = 0;
221
396
      main_stat.st_ino = 0;
222
396
    }
223
224
3.95k
  char *file_dirname = (file_basename == file_name ? NULL
225
3.95k
      : strndup (file_name, file_basename - 1 - file_name));
226
3.95k
  if (file_basename != file_name && file_dirname == NULL)
227
0
    {
228
0
      free (localpath);
229
0
      free (localname);
230
0
      return -1;
231
0
    }
232
3.95k
  char *p;
233
7.86k
  while ((p = strsep (&path, ":")) != NULL)
234
3.95k
    {
235
      /* A leading - or + says whether to check file CRCs for this element.  */
236
3.95k
      bool check = defcheck;
237
3.95k
      if (*p == '+' || *p == '-')
238
0
  check = *p++ == '+';
239
3.95k
      check = check && cancheck;
240
241
      /* Try the basename too, if we made up the debuglink name and this
242
   is not the main directory.  */
243
3.95k
      bool try_file_basename;
244
245
3.95k
      const char *dir, *subdir, *file;
246
3.95k
      switch (p[0])
247
3.95k
  {
248
3.95k
  case '\0':
249
    /* An empty entry says to try the main file's directory.  */
250
3.95k
    dir = file_dirname;
251
3.95k
    subdir = NULL;
252
3.95k
    file = debuglink_file;
253
3.95k
    try_file_basename = false;
254
3.95k
    break;
255
0
  case '/':
256
    /* An absolute path says to look there for a subdirectory
257
       named by the main file's absolute directory.  This cannot
258
       be applied to a relative file name.  For alt debug files
259
       it means to look for the basename file in that dir or the
260
       .dwz subdir (see below).  */
261
0
    if (mod->dw == NULL
262
0
        && (file_dirname == NULL || file_dirname[0] != '/'))
263
0
      continue;
264
0
    dir = p;
265
0
    if (mod->dw == NULL)
266
0
      {
267
0
        subdir = file_dirname;
268
        /* We want to explore all sub-subdirs.  Chop off one slash
269
     at a time.  */
270
0
      explore_dir:
271
0
        subdir = strchr (subdir, '/');
272
0
        if (subdir != NULL)
273
0
    subdir = subdir + 1;
274
0
        if (subdir && *subdir == 0)
275
0
    continue;
276
0
        file = debuglink_file;
277
0
      }
278
0
    else
279
0
      {
280
0
        subdir = NULL;
281
0
        file = xbasename (debuglink_file);
282
0
      }
283
0
    try_file_basename = debuglink_null;
284
0
    break;
285
0
  default:
286
    /* A relative path says to try a subdirectory of that name
287
       in the main file's directory.  */
288
0
    dir = file_dirname;
289
0
    subdir = p;
290
0
    file = debuglink_file;
291
0
    try_file_basename = debuglink_null;
292
0
    break;
293
3.95k
  }
294
295
3.95k
      char *fname = NULL;
296
3.95k
      int fd = try_open (&main_stat, dir, subdir, file, &fname);
297
3.95k
      if (fd < 0 && try_file_basename)
298
0
  fd = try_open (&main_stat, dir, subdir, file_basename, &fname);
299
3.95k
      if (fd < 0)
300
3.89k
  switch (errno)
301
3.89k
    {
302
3.87k
    case ENOENT:
303
3.87k
    case ENOTDIR:
304
      /* If we are looking for the alt file also try the .dwz subdir.
305
         But only if this is the empty or absolute path.  */
306
3.87k
      if (mod->dw != NULL && (p[0] == '\0' || p[0] == '/'))
307
7
        {
308
7
    fd = try_open (&main_stat, dir, ".dwz",
309
7
             xbasename (file), &fname);
310
7
    if (fd < 0)
311
7
      {
312
7
        if (errno != ENOENT && errno != ENOTDIR)
313
0
          goto fail_free;
314
7
        else
315
7
          continue;
316
7
      }
317
0
    break;
318
7
        }
319
      /* If possible try again with a sub-subdir.  */
320
3.86k
      if (mod->dw == NULL && subdir)
321
0
        goto explore_dir;
322
3.86k
      continue;
323
3.86k
    default:
324
21
      goto fail_free;
325
3.89k
    }
326
61
      if (validate (mod, fd, check, debuglink_crc))
327
14
  {
328
14
    free (localpath);
329
14
    free (localname);
330
14
    free (file_dirname);
331
14
    *debuginfo_file_name = fname;
332
14
    return fd;
333
14
  }
334
47
      free (fname);
335
47
      close (fd);
336
47
    }
337
338
  /* No dice.  */
339
3.95k
  errno = 0;
340
3.93k
fail_free:
341
3.93k
  free (localpath);
342
3.93k
  free (localname);
343
3.93k
  free (file_dirname);
344
3.93k
  return -1;
345
3.91k
}
346
347
int
348
dwfl_standard_find_debuginfo (Dwfl_Module *mod,
349
            void **userdata __attribute__ ((unused)),
350
            const char *modname __attribute__ ((unused)),
351
            GElf_Addr base __attribute__ ((unused)),
352
            const char *file_name,
353
            const char *debuglink_file,
354
            GElf_Word debuglink_crc,
355
            char **debuginfo_file_name)
356
3.95k
{
357
3.95k
  if (mod == NULL)
358
0
    return -1;
359
360
  /* First try by build ID if we have one.  If that succeeds or fails
361
     other than just by finding nothing, that's all we do.  */
362
3.95k
  const unsigned char *bits = NULL;
363
3.95k
  GElf_Addr vaddr;
364
3.95k
  int bits_len;
365
3.95k
  if ((bits_len = INTUSE(dwfl_module_build_id) (mod, &bits, &vaddr)) > 0)
366
105
    {
367
      /* Dropping most arguments means we cannot rely on them in
368
   dwfl_build_id_find_debuginfo.  But leave it that way since
369
   some user code out there also does this, so we'll have to
370
   handle it anyway.  */
371
105
      int fd = INTUSE(dwfl_build_id_find_debuginfo) (mod,
372
105
                 NULL, NULL, 0,
373
105
                 NULL, NULL, 0,
374
105
                 debuginfo_file_name);
375
376
      /* Did the build_id callback find something or report an error?
377
         Then we are done.  Otherwise fallback on path based search.  */
378
105
      if (fd >= 0
379
105
    || (mod->dw == NULL && mod->debug.elf != NULL)
380
105
    || (mod->dw != NULL && mod->alt_elf != NULL)
381
105
    || errno != 0)
382
0
  return fd;
383
105
    }
384
385
  /* Failing that, search the path by name.  */
386
3.95k
  int fd = find_debuginfo_in_path (mod, file_name,
387
3.95k
           debuglink_file, debuglink_crc,
388
3.95k
           debuginfo_file_name);
389
390
3.95k
  if (fd < 0 && errno == 0 && file_name != NULL)
391
3.91k
    {
392
      /* If FILE_NAME is a symlink, the debug file might be associated
393
   with the symlink target name instead.  */
394
395
3.91k
      char *canon = realpath (file_name, NULL);
396
3.91k
      if (canon != NULL && strcmp (file_name, canon))
397
0
  fd = find_debuginfo_in_path (mod, canon,
398
0
             debuglink_file, debuglink_crc,
399
0
             debuginfo_file_name);
400
3.91k
      free (canon);
401
3.91k
    }
402
403
#ifdef ENABLE_LIBDEBUGINFOD
404
  /* Still nothing? Try if we can use the debuginfod client.
405
     But note that we might be looking for the alt file.
406
     We use the same trick as dwfl_build_id_find_debuginfo.
407
     If the debug file (dw) is already set, then we must be
408
     looking for the altfile. But we cannot use the actual
409
     file/path name given as hint. We'll have to lookup the
410
     alt file "build-id". Because the debuginfod client only
411
     handles build-ids.  */
412
  if (fd < 0)
413
    {
414
      if (mod->dw != NULL)
415
  {
416
    const char *altname;
417
    bits_len = INTUSE(dwelf_dwarf_gnu_debugaltlink) (mod->dw, &altname,
418
                 (const void **)
419
                 &bits);
420
  }
421
422
      if (bits_len > 0)
423
  fd = __libdwfl_debuginfod_find_debuginfo (mod->dwfl, bits, bits_len);
424
    }
425
#endif
426
427
3.95k
  return fd;
428
3.95k
}
429
INTDEF (dwfl_standard_find_debuginfo)