Coverage Report

Created: 2026-08-13 06:55

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
4.18k
{
47
4.18k
  char *fname;
48
4.18k
  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
4.18k
  else if ((subdir == NULL ? asprintf (&fname, "%s/%s", dir, debuglink)
55
4.18k
      : dir == NULL ? asprintf (&fname, "%s/%s", subdir, debuglink)
56
6
      : asprintf (&fname, "%s/%s/%s", dir, subdir, debuglink)) < 0)
57
0
    return -1;
58
59
4.18k
  struct stat st;
60
4.18k
  int fd = TEMP_FAILURE_RETRY (open (fname, O_RDONLY));
61
4.18k
  if (fd < 0)
62
4.10k
    free (fname);
63
74
  else if (fstat (fd, &st) == 0
64
74
     && 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
74
  else
74
74
    *debuginfo_file_name = fname;
75
76
4.18k
  return fd;
77
4.18k
}
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
56
{
83
56
  uint32_t file_crc;
84
56
  return (__libdwfl_crc32_file (fd, &file_crc) == 0
85
0
    && file_crc == debuglink_crc);
86
56
}
87
88
static bool
89
validate (Dwfl_Module *mod, int fd, bool check, GElf_Word debuglink_crc)
90
74
{
91
  /* For alt debug files always check the build-id from the Dwarf and alt.  */
92
74
  if (mod->dw != NULL)
93
6
    {
94
6
      bool valid = false;
95
6
      const void *build_id;
96
6
      const char *altname;
97
6
      ssize_t build_id_len = INTUSE(dwelf_dwarf_gnu_debugaltlink) (mod->dw,
98
6
                   &altname,
99
6
                   &build_id);
100
6
      if (build_id_len > 0)
101
6
  {
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
6
    Dwfl_Error error = __libdw_open_file (&fd, &mod->alt_elf,
106
6
            false, false);
107
6
    if (error != DWFL_E_NOERROR)
108
6
      __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
6
  }
127
6
      return valid;
128
6
    }
129
130
  /* If we have a build ID, check only that.  */
131
68
  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
68
  return !check || check_crc (fd, debuglink_crc);
158
68
}
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
4.17k
{
165
4.17k
  bool cancheck = debuglink_crc != (GElf_Word) 0;
166
167
4.17k
  const char *file_basename = file_name == NULL ? NULL : xbasename (file_name);
168
4.17k
  char *localname = NULL;
169
170
  /* We invent a debuglink .debug name if NULL, but then want to try the
171
     basename too.  */
172
4.17k
  bool debuglink_null = debuglink_file == NULL;
173
4.17k
  if (debuglink_null)
174
4.06k
    {
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
4.06k
      if (file_basename == NULL || mod->dw != NULL)
178
0
  {
179
0
    errno = 0;
180
0
    return -1;
181
0
  }
182
183
4.06k
      size_t len = strlen (file_basename);
184
4.06k
      localname = malloc (len + sizeof ".debug");
185
4.06k
      if (unlikely (localname == NULL))
186
0
  return -1;
187
4.06k
      memcpy (localname, file_basename, len);
188
4.06k
      memcpy (&localname[len], ".debug", sizeof ".debug");
189
4.06k
      debuglink_file = localname;
190
4.06k
      cancheck = false;
191
4.06k
    }
192
193
  /* Look for a file named DEBUGLINK_FILE in the directories
194
     indicated by the debug directory path setting.  */
195
196
4.17k
  const Dwfl_Callbacks *const cb = mod->dwfl->callbacks;
197
4.17k
  char *localpath = strdup ((cb->debuginfo_path ? *cb->debuginfo_path : NULL)
198
4.17k
          ?: DEFAULT_DEBUGINFO_PATH);
199
4.17k
  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
4.17k
  bool defcheck = true;
207
4.17k
  char *path = localpath;
208
4.17k
  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
4.17k
  struct stat main_stat;
216
4.17k
  if (unlikely ((mod->main.fd != -1 ? fstat (mod->main.fd, &main_stat)
217
4.17k
     : file_name != NULL ? stat (file_name, &main_stat)
218
4.17k
     : -1) < 0))
219
377
    {
220
377
      main_stat.st_dev = 0;
221
377
      main_stat.st_ino = 0;
222
377
    }
223
224
4.17k
  char *file_dirname = (file_basename == file_name ? NULL
225
4.17k
      : strndup (file_name, file_basename - 1 - file_name));
226
4.17k
  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
4.17k
  char *p;
233
8.31k
  while ((p = strsep (&path, ":")) != NULL)
234
4.17k
    {
235
      /* A leading - or + says whether to check file CRCs for this element.  */
236
4.17k
      bool check = defcheck;
237
4.17k
      if (*p == '+' || *p == '-')
238
0
  check = *p++ == '+';
239
4.17k
      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
4.17k
      bool try_file_basename;
244
245
4.17k
      const char *dir, *subdir, *file;
246
4.17k
      switch (p[0])
247
4.17k
  {
248
4.17k
  case '\0':
249
    /* An empty entry says to try the main file's directory.  */
250
4.17k
    dir = file_dirname;
251
4.17k
    subdir = NULL;
252
4.17k
    file = debuglink_file;
253
4.17k
    try_file_basename = false;
254
4.17k
    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
4.17k
  }
294
295
4.17k
      char *fname = NULL;
296
4.17k
      int fd = try_open (&main_stat, dir, subdir, file, &fname);
297
4.17k
      if (fd < 0 && try_file_basename)
298
0
  fd = try_open (&main_stat, dir, subdir, file_basename, &fname);
299
4.17k
      if (fd < 0)
300
4.10k
  switch (errno)
301
4.10k
    {
302
4.07k
    case ENOENT:
303
4.07k
    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
4.07k
      if (mod->dw != NULL && (p[0] == '\0' || p[0] == '/'))
307
6
        {
308
6
    fd = try_open (&main_stat, dir, ".dwz",
309
6
             xbasename (file), &fname);
310
6
    if (fd < 0)
311
6
      {
312
6
        if (errno != ENOENT && errno != ENOTDIR)
313
0
          goto fail_free;
314
6
        else
315
6
          continue;
316
6
      }
317
0
    break;
318
6
        }
319
      /* If possible try again with a sub-subdir.  */
320
4.07k
      if (mod->dw == NULL && subdir)
321
0
        goto explore_dir;
322
4.07k
      continue;
323
4.07k
    default:
324
23
      goto fail_free;
325
4.10k
    }
326
74
      if (validate (mod, fd, check, debuglink_crc))
327
12
  {
328
12
    free (localpath);
329
12
    free (localname);
330
12
    free (file_dirname);
331
12
    *debuginfo_file_name = fname;
332
12
    return fd;
333
12
  }
334
62
      free (fname);
335
62
      close (fd);
336
62
    }
337
338
  /* No dice.  */
339
4.17k
  errno = 0;
340
4.16k
fail_free:
341
4.16k
  free (localpath);
342
4.16k
  free (localname);
343
4.16k
  free (file_dirname);
344
4.16k
  return -1;
345
4.14k
}
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
4.17k
{
357
4.17k
  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
4.17k
  const unsigned char *bits = NULL;
363
4.17k
  GElf_Addr vaddr;
364
4.17k
  int bits_len;
365
4.17k
  if ((bits_len = INTUSE(dwfl_module_build_id) (mod, &bits, &vaddr)) > 0)
366
120
    {
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
120
      int fd = INTUSE(dwfl_build_id_find_debuginfo) (mod,
372
120
                 NULL, NULL, 0,
373
120
                 NULL, NULL, 0,
374
120
                 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
120
      if (fd >= 0
379
120
    || (mod->dw == NULL && mod->debug.elf != NULL)
380
120
    || (mod->dw != NULL && mod->alt_elf != NULL)
381
120
    || errno != 0)
382
0
  return fd;
383
120
    }
384
385
  /* Failing that, search the path by name.  */
386
4.17k
  int fd = find_debuginfo_in_path (mod, file_name,
387
4.17k
           debuglink_file, debuglink_crc,
388
4.17k
           debuginfo_file_name);
389
390
4.17k
  if (fd < 0 && errno == 0 && file_name != NULL)
391
4.14k
    {
392
      /* If FILE_NAME is a symlink, the debug file might be associated
393
   with the symlink target name instead.  */
394
395
4.14k
      char *canon = realpath (file_name, NULL);
396
4.14k
      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
4.14k
      free (canon);
401
4.14k
    }
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
4.17k
  return fd;
428
4.17k
}
429
INTDEF (dwfl_standard_find_debuginfo)