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