Coverage Report

Created: 2026-02-14 06:24

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