Coverage Report

Created: 2025-11-24 06:43

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.27k
{
47
4.27k
  char *fname;
48
4.27k
  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.27k
  else if ((subdir == NULL ? asprintf (&fname, "%s/%s", dir, debuglink)
55
4.27k
      : dir == NULL ? asprintf (&fname, "%s/%s", subdir, debuglink)
56
4
      : asprintf (&fname, "%s/%s/%s", dir, subdir, debuglink)) < 0)
57
0
    return -1;
58
59
4.27k
  struct stat st;
60
4.27k
  int fd = TEMP_FAILURE_RETRY (open (fname, O_RDONLY));
61
4.27k
  if (fd < 0)
62
4.21k
    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
4.27k
  return fd;
77
4.27k
}
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
48
{
83
48
  uint32_t file_crc;
84
48
  return (__libdwfl_crc32_file (fd, &file_crc) == 0
85
0
    && file_crc == debuglink_crc);
86
48
}
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
4.27k
{
165
4.27k
  bool cancheck = debuglink_crc != (GElf_Word) 0;
166
167
4.27k
  const char *file_basename = file_name == NULL ? NULL : xbasename (file_name);
168
4.27k
  char *localname = NULL;
169
170
  /* We invent a debuglink .debug name if NULL, but then want to try the
171
     basename too.  */
172
4.27k
  bool debuglink_null = debuglink_file == NULL;
173
4.27k
  if (debuglink_null)
174
4.18k
    {
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.18k
      if (file_basename == NULL || mod->dw != NULL)
178
0
  {
179
0
    errno = 0;
180
0
    return -1;
181
0
  }
182
183
4.18k
      size_t len = strlen (file_basename);
184
4.18k
      localname = malloc (len + sizeof ".debug");
185
4.18k
      if (unlikely (localname == NULL))
186
0
  return -1;
187
4.18k
      memcpy (localname, file_basename, len);
188
4.18k
      memcpy (&localname[len], ".debug", sizeof ".debug");
189
4.18k
      debuglink_file = localname;
190
4.18k
      cancheck = false;
191
4.18k
    }
192
193
  /* Look for a file named DEBUGLINK_FILE in the directories
194
     indicated by the debug directory path setting.  */
195
196
4.27k
  const Dwfl_Callbacks *const cb = mod->dwfl->callbacks;
197
4.27k
  char *localpath = strdup ((cb->debuginfo_path ? *cb->debuginfo_path : NULL)
198
4.27k
          ?: DEFAULT_DEBUGINFO_PATH);
199
4.27k
  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.27k
  bool defcheck = true;
207
4.27k
  char *path = localpath;
208
4.27k
  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.27k
  struct stat main_stat;
216
4.27k
  if (unlikely ((mod->main.fd != -1 ? fstat (mod->main.fd, &main_stat)
217
4.27k
     : file_name != NULL ? stat (file_name, &main_stat)
218
4.27k
     : -1) < 0))
219
457
    {
220
457
      main_stat.st_dev = 0;
221
457
      main_stat.st_ino = 0;
222
457
    }
223
224
4.27k
  char *file_dirname = (file_basename == file_name ? NULL
225
4.27k
      : strndup (file_name, file_basename - 1 - file_name));
226
4.27k
  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.27k
  char *p;
233
8.52k
  while ((p = strsep (&path, ":")) != NULL)
234
4.27k
    {
235
      /* A leading - or + says whether to check file CRCs for this element.  */
236
4.27k
      bool check = defcheck;
237
4.27k
      if (*p == '+' || *p == '-')
238
0
  check = *p++ == '+';
239
4.27k
      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.27k
      bool try_file_basename;
244
245
4.27k
      const char *dir, *subdir, *file;
246
4.27k
      switch (p[0])
247
4.27k
  {
248
4.27k
  case '\0':
249
    /* An empty entry says to try the main file's directory.  */
250
4.27k
    dir = file_dirname;
251
4.27k
    subdir = NULL;
252
4.27k
    file = debuglink_file;
253
4.27k
    try_file_basename = false;
254
4.27k
    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.27k
  }
294
295
4.27k
      char *fname = NULL;
296
4.27k
      int fd = try_open (&main_stat, dir, subdir, file, &fname);
297
4.27k
      if (fd < 0 && try_file_basename)
298
0
  fd = try_open (&main_stat, dir, subdir, file_basename, &fname);
299
4.27k
      if (fd < 0)
300
4.21k
  switch (errno)
301
4.21k
    {
302
4.19k
    case ENOENT:
303
4.19k
    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.19k
      if (mod->dw != NULL && (p[0] == '\0' || p[0] == '/'))
307
4
        {
308
4
    fd = try_open (&main_stat, dir, ".dwz",
309
4
             xbasename (file), &fname);
310
4
    if (fd < 0)
311
4
      {
312
4
        if (errno != ENOENT && errno != ENOTDIR)
313
0
          goto fail_free;
314
4
        else
315
4
          continue;
316
4
      }
317
0
    break;
318
4
        }
319
      /* If possible try again with a sub-subdir.  */
320
4.19k
      if (mod->dw == NULL && subdir)
321
0
        goto explore_dir;
322
4.19k
      continue;
323
4.19k
    default:
324
15
      goto fail_free;
325
4.21k
    }
326
61
      if (validate (mod, fd, check, debuglink_crc))
327
10
  {
328
10
    free (localpath);
329
10
    free (localname);
330
10
    free (file_dirname);
331
10
    *debuginfo_file_name = fname;
332
10
    return fd;
333
10
  }
334
51
      free (fname);
335
51
      close (fd);
336
51
    }
337
338
  /* No dice.  */
339
4.27k
  errno = 0;
340
4.26k
fail_free:
341
4.26k
  free (localpath);
342
4.26k
  free (localname);
343
4.26k
  free (file_dirname);
344
4.26k
  return -1;
345
4.24k
}
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.27k
{
357
4.27k
  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.27k
  const unsigned char *bits = NULL;
363
4.27k
  GElf_Addr vaddr;
364
4.27k
  int bits_len;
365
4.27k
  if ((bits_len = INTUSE(dwfl_module_build_id) (mod, &bits, &vaddr)) > 0)
366
112
    {
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
112
      int fd = INTUSE(dwfl_build_id_find_debuginfo) (mod,
372
112
                 NULL, NULL, 0,
373
112
                 NULL, NULL, 0,
374
112
                 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
112
      if (fd >= 0
379
112
    || (mod->dw == NULL && mod->debug.elf != NULL)
380
112
    || (mod->dw != NULL && mod->alt_elf != NULL)
381
112
    || errno != 0)
382
0
  return fd;
383
112
    }
384
385
  /* Failing that, search the path by name.  */
386
4.27k
  int fd = find_debuginfo_in_path (mod, file_name,
387
4.27k
           debuglink_file, debuglink_crc,
388
4.27k
           debuginfo_file_name);
389
390
4.27k
  if (fd < 0 && errno == 0 && file_name != NULL)
391
4.24k
    {
392
      /* If FILE_NAME is a symlink, the debug file might be associated
393
   with the symlink target name instead.  */
394
395
4.24k
      char *canon = realpath (file_name, NULL);
396
4.24k
      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.24k
      free (canon);
401
4.24k
    }
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.27k
  return fd;
428
4.27k
}
429
INTDEF (dwfl_standard_find_debuginfo)