_UCD_create.c revision 0628a9872ca4ca5a93b3671e21f099a81e21c07a
1/* libunwind - a platform-independent unwind library
2
3This file is part of libunwind.
4
5Permission is hereby granted, free of charge, to any person obtaining
6a copy of this software and associated documentation files (the
7"Software"), to deal in the Software without restriction, including
8without limitation the rights to use, copy, modify, merge, publish,
9distribute, sublicense, and/or sell copies of the Software, and to
10permit persons to whom the Software is furnished to do so, subject to
11the following conditions:
12
13The above copyright notice and this permission notice shall be
14included in all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  */
23
24#ifdef HAVE_CONFIG_H
25# include "config.h"
26#endif
27
28/* Endian detection */
29#include <limits.h>
30#if defined(HAVE_BYTESWAP_H)
31#include <byteswap.h>
32#endif
33#if defined(HAVE_ENDIAN_H)
34# include <endian.h>
35#elif defined(HAVE_SYS_ENDIAN_H)
36# include <sys/endian.h>
37#endif
38#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN
39# define WE_ARE_BIG_ENDIAN    1
40# define WE_ARE_LITTLE_ENDIAN 0
41#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN
42# define WE_ARE_BIG_ENDIAN    0
43# define WE_ARE_LITTLE_ENDIAN 1
44#elif defined(_BYTE_ORDER) && _BYTE_ORDER == _BIG_ENDIAN
45# define WE_ARE_BIG_ENDIAN    1
46# define WE_ARE_LITTLE_ENDIAN 0
47#elif defined(_BYTE_ORDER) && _BYTE_ORDER == _LITTLE_ENDIAN
48# define WE_ARE_BIG_ENDIAN    0
49# define WE_ARE_LITTLE_ENDIAN 1
50#elif defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN
51# define WE_ARE_BIG_ENDIAN    1
52# define WE_ARE_LITTLE_ENDIAN 0
53#elif defined(BYTE_ORDER) && BYTE_ORDER == LITTLE_ENDIAN
54# define WE_ARE_BIG_ENDIAN    0
55# define WE_ARE_LITTLE_ENDIAN 1
56#elif defined(__386__)
57# define WE_ARE_BIG_ENDIAN    0
58# define WE_ARE_LITTLE_ENDIAN 1
59#else
60# error "Can't determine endianness"
61#endif
62
63#include <elf.h>
64#include <sys/procfs.h> /* struct elf_prstatus */
65
66#include "_UCD_lib.h"
67#include "_UCD_internal.h"
68
69struct UCD_info *
70_UCD_create(const char *filename)
71{
72  union
73    {
74      Elf32_Ehdr h32;
75      Elf64_Ehdr h64;
76    } elf_header;
77#define elf_header32 elf_header.h32
78#define elf_header64 elf_header.h64
79  bool _64bits;
80
81  struct UCD_info *ui = memset(malloc(sizeof(*ui)), 0, sizeof(*ui));
82  ui->edi.di_cache.format = -1;
83  ui->edi.di_debug.format = -1;
84#if UNW_TARGET_IA64
85  ui->edi.ktab.format = -1;
86#endif
87
88  int fd = ui->coredump_fd = open(filename, O_RDONLY);
89  if (fd < 0)
90    goto err;
91  ui->coredump_filename = strdup(filename);
92
93  /* No sane ELF32 file is going to be smaller then ELF64 _header_,
94   * so let's just read 64-bit sized one.
95   */
96  if (read(fd, &elf_header64, sizeof(elf_header64)) != sizeof(elf_header64))
97    {
98      Debug(0, "'%s' is not an ELF file\n", filename);
99      goto err;
100    }
101
102  if (memcmp(&elf_header32, "\x7f""ELF", 4) != 0)
103    {
104      Debug(0, "'%s' is not an ELF file\n", filename);
105      goto err;
106    }
107
108  if (elf_header32.e_ident[EI_CLASS] != ELFCLASS32
109   && elf_header32.e_ident[EI_CLASS] != ELFCLASS64)
110    {
111      Debug(0, "'%s' is not a 32/64 bit ELF file\n", filename);
112      goto err;
113    }
114
115  if (WE_ARE_LITTLE_ENDIAN != (elf_header32.e_ident[EI_DATA] == ELFDATA2LSB))
116    {
117      Debug(0, "'%s' is endian-incompatible\n", filename);
118      goto err;
119    }
120
121  _64bits = (elf_header32.e_ident[EI_CLASS] == ELFCLASS64);
122  if (_64bits && sizeof(elf_header64.e_entry) > sizeof(off_t))
123    {
124      Debug(0, "Can't process '%s': 64-bit file "
125               "while only %ld bits are supported",
126            filename, 8L * sizeof(off_t));
127      goto err;
128    }
129
130  /* paranoia check */
131  if (_64bits
132            ? 0 /* todo: (elf_header64.e_ehsize != NN || elf_header64.e_phentsize != NN) */
133            : (elf_header32.e_ehsize != 52 || elf_header32.e_phentsize != 32)
134  )
135    {
136      Debug(0, "'%s' has wrong e_ehsize or e_phentsize\n", filename);
137      goto err;
138    }
139
140  off_t ofs = (_64bits ? elf_header64.e_phoff : elf_header32.e_phoff);
141  if (lseek(fd, ofs, SEEK_SET) != ofs)
142    {
143      Debug(0, "Can't read phdrs from '%s'\n", filename);
144      goto err;
145    }
146  unsigned size = ui->phdrs_count = (_64bits ? elf_header64.e_phnum : elf_header32.e_phnum);
147  coredump_phdr_t *phdrs = ui->phdrs = memset(malloc(size * sizeof(phdrs[0])), 0, size * sizeof(phdrs[0]));
148  if (_64bits)
149    {
150      coredump_phdr_t *cur = phdrs;
151      unsigned i = 0;
152      while (i < size)
153        {
154          Elf64_Phdr hdr64;
155          if (read(fd, &hdr64, sizeof(hdr64)) != sizeof(hdr64))
156            {
157              Debug(0, "Can't read phdrs from '%s'\n", filename);
158              goto err;
159            }
160          cur->p_type   = hdr64.p_type  ;
161          cur->p_flags  = hdr64.p_flags ;
162          cur->p_offset = hdr64.p_offset;
163          cur->p_vaddr  = hdr64.p_vaddr ;
164          /*cur->p_paddr  = hdr32.p_paddr ; always 0 */
165//TODO: check that and abort if it isn't?
166          cur->p_filesz = hdr64.p_filesz;
167          cur->p_memsz  = hdr64.p_memsz ;
168          cur->p_align  = hdr64.p_align ;
169          /* cur->backing_filename = NULL; - done by memset */
170          cur->backing_fd = -1;
171          cur->backing_filesize = hdr64.p_filesz;
172          i++;
173          cur++;
174        }
175    } else {
176      coredump_phdr_t *cur = phdrs;
177      unsigned i = 0;
178      while (i < size)
179        {
180          Elf32_Phdr hdr32;
181          if (read(fd, &hdr32, sizeof(hdr32)) != sizeof(hdr32))
182            {
183              Debug(0, "Can't read phdrs from '%s'\n", filename);
184              goto err;
185            }
186          cur->p_type   = hdr32.p_type  ;
187          cur->p_flags  = hdr32.p_flags ;
188          cur->p_offset = hdr32.p_offset;
189          cur->p_vaddr  = hdr32.p_vaddr ;
190          /*cur->p_paddr  = hdr32.p_paddr ; always 0 */
191          cur->p_filesz = hdr32.p_filesz;
192          cur->p_memsz  = hdr32.p_memsz ;
193          cur->p_align  = hdr32.p_align ;
194          /* cur->backing_filename = NULL; - done by memset */
195          cur->backing_fd = -1;
196          cur->backing_filesize = hdr32.p_memsz;
197          i++;
198          cur++;
199        }
200    }
201
202    unsigned i = 0;
203    coredump_phdr_t *cur = phdrs;
204    while (i < size)
205      {
206        Debug(2, "phdr[%03d]: type:%d", i, cur->p_type);
207        if (cur->p_type == PT_NOTE)
208          {
209            unsigned char *p, *note_end;
210            unsigned n_threads;
211
212            ui->note_phdr = malloc(cur->p_filesz);
213            if (lseek(fd, cur->p_offset, SEEK_SET) != (off_t)cur->p_offset
214             || (uoff_t)read(fd, ui->note_phdr, cur->p_filesz) != cur->p_filesz)
215              {
216                    Debug(0, "Can't read PT_NOTE from '%s'\n", filename);
217                    goto err;
218              }
219
220            note_end = (unsigned char *)ui->note_phdr + cur->p_filesz;
221
222            /* Count number of threads */
223            n_threads = 0;
224            p = ui->note_phdr;
225            while (p + sizeof (Elf32_Nhdr) <= note_end)
226              {
227                Elf32_Nhdr *note_hdr = (Elf32_Nhdr *)p;
228                unsigned char *p_next;
229
230                p_next = p + sizeof (Elf32_Nhdr)
231                         + ((note_hdr->n_namesz + 3) & ~3L)
232                         + note_hdr->n_descsz;
233
234                if (p_next >= note_end)
235                  break;
236
237                if (note_hdr->n_type == NT_PRSTATUS)
238                  n_threads++;
239
240                p = p_next;
241              }
242
243            ui->n_threads = n_threads;
244            ui->threads = malloc(sizeof (void *) * n_threads);
245
246            n_threads = 0;
247            p = ui->note_phdr;
248            while (p + sizeof (Elf32_Nhdr) <= note_end)
249              {
250                Elf32_Nhdr *note_hdr = (Elf32_Nhdr *)p;
251                unsigned char *p_next;
252
253                p_next = p + sizeof (Elf32_Nhdr)
254                           + ((note_hdr->n_namesz + 3) & ~3L)
255                           + note_hdr->n_descsz;
256
257                if (p_next >= note_end)
258                  break;
259
260                if (note_hdr->n_type == NT_PRSTATUS)
261                  ui->threads[n_threads++] = (void*) ((((long)note_hdr
262                    + sizeof(*note_hdr) + note_hdr->n_namesz) + 3) & ~3L);
263
264                p = p_next;
265              }
266          }
267        if (cur->p_type == PT_LOAD)
268          {
269            Debug(2, " ofs:%08llx va:%08llx filesize:%08llx memsize:%08llx flg:%x",
270                                (unsigned long long) cur->p_offset,
271                                (unsigned long long) cur->p_vaddr,
272                                (unsigned long long) cur->p_filesz,
273                                (unsigned long long) cur->p_memsz,
274                                cur->p_flags
275            );
276            if (cur->p_filesz < cur->p_memsz)
277              Debug(2, " partial");
278            if (cur->p_flags & PF_X)
279              Debug(2, " executable");
280          }
281        Debug(2, "\n");
282        i++;
283        cur++;
284      }
285
286    if (ui->n_threads == 0)
287      {
288        Debug(0, "No NT_PRSTATUS note found in '%s'\n", filename);
289        goto err;
290      }
291
292    ui->prstatus = ui->threads[0];
293
294  return ui;
295
296 err:
297  _UCD_destroy(ui);
298  return NULL;
299}
300
301int _UCD_get_num_threads(struct UCD_info *ui)
302{
303  return ui->n_threads;
304}
305
306void _UCD_select_thread(struct UCD_info *ui, int n)
307{
308  if (n >= 0 && n < ui->n_threads)
309    ui->prstatus = ui->threads[n];
310}
311
312pid_t _UCD_get_pid(struct UCD_info *ui)
313{
314  return ui->prstatus->pr_pid;
315}
316
317int _UCD_get_cursig(struct UCD_info *ui)
318{
319  return ui->prstatus->pr_cursig;
320}
321
322int _UCD_add_backing_file_at_segment(struct UCD_info *ui, int phdr_no, const char *filename)
323{
324  if ((unsigned)phdr_no >= ui->phdrs_count)
325    {
326      Debug(0, "There is no segment %d in this coredump\n", phdr_no);
327      return -1;
328    }
329
330  struct coredump_phdr *phdr = &ui->phdrs[phdr_no];
331  if (phdr->backing_filename)
332    {
333      Debug(0, "Backing file already added to segment %d\n", phdr_no);
334      return -1;
335    }
336
337  int fd = open(filename, O_RDONLY);
338  if (fd < 0)
339    {
340      Debug(0, "Can't open '%s'\n", filename);
341      return -1;
342    }
343
344  phdr->backing_fd = fd;
345  phdr->backing_filename = strdup(filename);
346
347  struct stat statbuf;
348  if (fstat(fd, &statbuf) != 0)
349    {
350      Debug(0, "Can't stat '%s'\n", filename);
351      goto err;
352    }
353  phdr->backing_filesize = (uoff_t)statbuf.st_size;
354
355  if (phdr->p_flags != (PF_X | PF_R))
356    Debug(1, "Note: phdr[%u] is not r-x: flags are 0x%x\n", phdr_no, phdr->p_flags);
357
358  if (phdr->backing_filesize > phdr->p_memsz)
359    {
360      /* This is expected */
361      Debug(2, "Note: phdr[%u] is %lld bytes, file is larger: %lld bytes\n",
362                        phdr_no,
363                        (unsigned long long)phdr->p_memsz,
364                        (unsigned long long)phdr->backing_filesize
365      );
366    }
367//TODO: else loudly complain? Maybe even fail?
368
369  if (phdr->p_filesz != 0)
370    {
371//TODO: loop and compare in smaller blocks
372      char *core_buf = malloc(phdr->p_filesz);
373      char *file_buf = malloc(phdr->p_filesz);
374      if (lseek(ui->coredump_fd, phdr->p_offset, SEEK_SET) != (off_t)phdr->p_offset
375       || (uoff_t)read(ui->coredump_fd, core_buf, phdr->p_filesz) != phdr->p_filesz
376      )
377        {
378          Debug(0, "Error reading from coredump file\n");
379 err_read:
380          free(core_buf);
381          free(file_buf);
382          goto err;
383        }
384      if ((uoff_t)read(fd, file_buf, phdr->p_filesz) != phdr->p_filesz)
385        {
386          Debug(0, "Error reading from '%s'\n", filename);
387          goto err_read;
388        }
389      int r = memcmp(core_buf, file_buf, phdr->p_filesz);
390      free(core_buf);
391      free(file_buf);
392      if (r != 0)
393        {
394          Debug(1, "Note: phdr[%u] first %lld bytes in core dump and in file do not match\n",
395                                phdr_no, (unsigned long long)phdr->p_filesz
396          );
397        } else {
398          Debug(1, "Note: phdr[%u] first %lld bytes in core dump and in file match\n",
399                                phdr_no, (unsigned long long)phdr->p_filesz
400          );
401        }
402    }
403
404  /* Success */
405  return 0;
406
407 err:
408  if (phdr->backing_fd >= 0)
409    {
410      close(phdr->backing_fd);
411      phdr->backing_fd = -1;
412    }
413  free(phdr->backing_filename);
414  phdr->backing_filename = NULL;
415  return -1;
416}
417
418int _UCD_add_backing_file_at_vaddr(struct UCD_info *ui,
419                                   unsigned long vaddr,
420                                   const char *filename)
421{
422  unsigned i;
423  for (i = 0; i < ui->phdrs_count; i++)
424    {
425      struct coredump_phdr *phdr = &ui->phdrs[i];
426      if (phdr->p_vaddr != vaddr)
427        continue;
428      /* It seems to match. Add it. */
429      return _UCD_add_backing_file_at_segment(ui, i, filename);
430    }
431  return -1;
432}
433