]> sourceware.org Git - newlib-cygwin.git/blob - winsup/utils/ldd.cc
Make ldd stop after any non-continuable exception
[newlib-cygwin.git] / winsup / utils / ldd.cc
1 /* Copyright (c) 2009, 2010, 2011, 2013 Chris Faylor
2
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8 1. Redistributions of source code must retain the above copyright
9 notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
13
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
15 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
16 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
17 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
20 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include <errno.h>
28 #include <getopt.h>
29 #include <stdarg.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <wchar.h>
34 #include <locale.h>
35 #include <sys/cygwin.h>
36 #include <cygwin/version.h>
37 #include <unistd.h>
38 #include <libgen.h>
39
40 #define _WIN32_WINNT 0x0a00
41 #include <windows.h>
42 #include <imagehlp.h>
43 #include <psapi.h>
44
45 struct option longopts[] =
46 {
47 {"help", no_argument, NULL, 'h'},
48 {"verbose", no_argument, NULL, 'v'},
49 {"version", no_argument, NULL, 'V'},
50 {"data-relocs", no_argument, NULL, 'd'},
51 {"function-relocs", no_argument, NULL, 'r'},
52 {"unused", no_argument, NULL, 'u'},
53 {0, no_argument, NULL, 0}
54 };
55 const char *opts = "dhruvV";
56
57 static int process_file (const wchar_t *);
58
59 static int
60 error (const char *fmt, ...)
61 {
62 va_list ap;
63 va_start (ap, fmt);
64 fprintf (stderr, "ldd: ");
65 vfprintf (stderr, fmt, ap);
66 fprintf (stderr, "\nTry `ldd --help' for more information.\n");
67 exit (1);
68 }
69
70 static void
71 usage ()
72 {
73 printf ("Usage: %s [OPTION]... FILE...\n\
74 \n\
75 Print shared library dependencies\n\
76 \n\
77 -h, --help print this help and exit\n\
78 -V, --version print version information and exit\n\
79 -r, --function-relocs process data and function relocations\n\
80 (currently unimplemented)\n\
81 -u, --unused print unused direct dependencies\n\
82 (currently unimplemented)\n\
83 -v, --verbose print all information\n\
84 (currently unimplemented)\n",
85 program_invocation_short_name);
86 }
87
88 static void
89 print_version ()
90 {
91 printf ("ldd (cygwin) %d.%d.%d\n"
92 "Print shared library dependencies\n"
93 "Copyright (C) 2009 - %s Chris Faylor\n"
94 "This is free software; see the source for copying conditions. There is NO\n"
95 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
96 CYGWIN_VERSION_DLL_MAJOR / 1000,
97 CYGWIN_VERSION_DLL_MAJOR % 1000,
98 CYGWIN_VERSION_DLL_MINOR,
99 strrchr (__DATE__, ' ') + 1);
100 }
101
102 #define print_errno_error_and_return(__fn) \
103 do {\
104 fprintf (stderr, "ldd: %s: %s\n", (__fn), strerror (errno));\
105 return 1;\
106 } while (0)
107
108 #define set_errno_and_return(x) \
109 do {\
110 cygwin_internal (CW_SETERRNO, __FILE__, __LINE__ - 2);\
111 return (x);\
112 } while (0)
113
114
115 static HANDLE hProcess;
116
117 static struct filelist
118 {
119 struct filelist *next;
120 char *name;
121 } *head;
122
123 static bool
124 saw_file (char *name)
125 {
126 filelist *p;
127
128 for (p = head; p; p = p->next)
129 if (strcasecmp (name, p->name) == 0)
130 return true;
131
132 p = (filelist *) malloc(sizeof (struct filelist));
133 p->next = head;
134 p->name = strdup (name);
135 head = p;
136 return false;
137 }
138
139 static wchar_t *
140 get_module_filename (HANDLE hp, HMODULE hm)
141 {
142 size_t len;
143 wchar_t *buf = NULL;
144 DWORD res;
145 for (len = 1024; (res = GetModuleFileNameExW (hp, hm, (buf = (wchar_t *) realloc (buf, len * sizeof (wchar_t))), len)) == len; len += 1024)
146 continue;
147 if (!res)
148 {
149 free (buf);
150 buf = NULL;
151 }
152 return buf;
153 }
154
155 static wchar_t *
156 load_dll (const wchar_t *fn)
157 {
158 wchar_t *buf = get_module_filename (GetCurrentProcess (), NULL);
159 if (!buf)
160 {
161 printf ("ldd: GetModuleFileName returned an error %u\n",
162 (unsigned int) GetLastError ());
163 exit (1); /* FIXME */
164 }
165
166 wchar_t *newbuf = (wchar_t *) malloc ((sizeof (L"\"\" -- ") + wcslen (buf) + wcslen (fn)) * sizeof (wchar_t));
167 newbuf[0] = L'"';
168 wcscpy (newbuf + 1, buf);
169 wchar_t *p = wcsstr (newbuf, L"\\ldd");
170 if (!p)
171 {
172 printf ("ldd: can't parse my own filename \"%ls\"\n", buf);
173 exit (1);
174 }
175 p[3] = L'h';
176 wcscat (newbuf, L"\" -- ");
177 wcscat (newbuf, fn);
178 free (buf);
179 return newbuf;
180 }
181
182 static int
183 start_process (const wchar_t *fn, bool& isdll)
184 {
185 STARTUPINFOW si = {};
186 PROCESS_INFORMATION pi;
187 si.cb = sizeof (si);
188 wchar_t *cmd;
189 /* OCaml natdynlink plugins (.cmxs) cannot be handled by ldd because they
190 can only be loaded by flexdll_dlopen() */
191 if (wcslen (fn) < 4 || (wcscasecmp (wcschr (fn, L'\0') - 4, L".dll") != 0
192 && wcscasecmp (wcschr (fn, L'\0') - 4, L".oct") != 0
193 && wcscasecmp (wcschr (fn, L'\0') - 3, L".so") != 0))
194 {
195 cmd = wcsdup (fn);
196 isdll = false;
197 }
198 else
199 {
200 cmd = load_dll (fn);
201 isdll = true;
202 }
203 if (CreateProcessW (NULL, cmd, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &si, &pi))
204 {
205 free (cmd);
206 hProcess = pi.hProcess;
207 DebugSetProcessKillOnExit (true);
208 return 0;
209 }
210
211 free (cmd);
212 set_errno_and_return (1);
213 }
214
215 struct dlls
216 {
217 LPVOID lpBaseOfDll;
218 struct dlls *next;
219 };
220
221 #define SLOP strlen (" (?)")
222 char *
223 tocyg (wchar_t *win_fn)
224 {
225 ssize_t cwlen = cygwin_conv_path (CCP_WIN_W_TO_POSIX, win_fn, NULL, 0);
226 char *fn;
227 if (cwlen <= 0)
228 {
229 int len = wcstombs (NULL, win_fn, 0) + 1;
230 if ((fn = (char *) malloc (len)))
231 wcstombs (fn, win_fn, len);
232 }
233 else
234 {
235 char *fn_cyg = (char *) malloc (cwlen + SLOP + 1);
236 if (cygwin_conv_path (CCP_WIN_W_TO_POSIX, win_fn, fn_cyg, cwlen) == 0)
237 fn = fn_cyg;
238 else
239 {
240 free (fn_cyg);
241 int len = wcstombs (NULL, win_fn, 0);
242 fn = (char *) malloc (len + SLOP + 1);
243 wcstombs (fn, win_fn, len + SLOP + 1);
244 }
245 }
246 return fn;
247 }
248
249 #define CYGWIN_DLL_LEN (wcslen (L"\\cygwin1.dll"))
250 static int
251 print_dlls (dlls *dll, const wchar_t *dllfn, const wchar_t *process_fn)
252 {
253 head = NULL; /* FIXME: memory leak */
254 while ((dll = dll->next))
255 {
256 char *fn;
257 wchar_t *fullpath = get_module_filename (hProcess, (HMODULE) dll->lpBaseOfDll);
258 if (!fullpath)
259 fn = strdup ("???");
260 else if (dllfn && wcscmp (fullpath, dllfn) == 0)
261 {
262 free (fullpath);
263 continue;
264 }
265 else
266 {
267 fn = tocyg (fullpath);
268 saw_file (basename (fn));
269 free (fullpath);
270 }
271 printf ("\t%s => %s (%p)\n", basename (fn), fn, dll->lpBaseOfDll);
272 free (fn);
273 }
274 if (process_fn)
275 return process_file (process_fn);
276 return 0;
277 }
278
279 static int
280 report (const char *in_fn, bool multiple)
281 {
282 if (multiple)
283 printf ("%s:\n", in_fn);
284 char *fn = realpath (in_fn, NULL);
285 if (!fn)
286 print_errno_error_and_return (in_fn);
287
288 ssize_t len = cygwin_conv_path (CCP_POSIX_TO_WIN_W, fn, NULL, 0);
289 if (len <= 0)
290 print_errno_error_and_return (fn);
291
292 bool isdll;
293 wchar_t fn_win[len + 1];
294 if (cygwin_conv_path (CCP_POSIX_TO_WIN_W, fn, fn_win, len))
295 print_errno_error_and_return (fn);
296
297 if (!fn || start_process (fn_win, isdll))
298 print_errno_error_and_return (in_fn);
299
300 DEBUG_EVENT ev;
301
302 dlls dll_list = {};
303 dlls *dll_last = &dll_list;
304 const wchar_t *process_fn = NULL;
305
306 int res = 0;
307
308 while (1)
309 {
310 bool exitnow = false;
311 DWORD cont = DBG_CONTINUE;
312 if (!WaitForDebugEvent (&ev, INFINITE))
313 break;
314 switch (ev.dwDebugEventCode)
315 {
316 case CREATE_PROCESS_DEBUG_EVENT:
317 if (!isdll)
318 {
319 PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER) alloca (4096);
320 PIMAGE_NT_HEADERS nt_header;
321 PVOID entry_point;
322 static const unsigned char int3 = 0xcc;
323 SIZE_T bytes;
324
325 if (!ReadProcessMemory (hProcess,
326 ev.u.CreateProcessInfo.lpBaseOfImage,
327 dos_header, 4096, &bytes))
328 print_errno_error_and_return (in_fn);
329
330 nt_header = PIMAGE_NT_HEADERS (PBYTE (dos_header)
331 + dos_header->e_lfanew);
332 entry_point = (PVOID)
333 ((caddr_t) ev.u.CreateProcessInfo.lpBaseOfImage
334 + nt_header->OptionalHeader.AddressOfEntryPoint);
335
336 if (!WriteProcessMemory (hProcess, entry_point, &int3, 1, &bytes))
337 print_errno_error_and_return (in_fn);
338 }
339 break;
340 case LOAD_DLL_DEBUG_EVENT:
341 dll_last->next = (dlls *) malloc (sizeof (dlls));
342 dll_last->next->lpBaseOfDll = ev.u.LoadDll.lpBaseOfDll;
343 dll_last->next->next = NULL;
344 dll_last = dll_last->next;
345 break;
346 case EXCEPTION_DEBUG_EVENT:
347 switch (ev.u.Exception.ExceptionRecord.ExceptionCode)
348 {
349 case STATUS_ENTRYPOINT_NOT_FOUND:
350 /* A STATUS_ENTRYPOINT_NOT_FOUND might be encountered right after
351 loading all DLLs. We have to handle it here, otherwise ldd
352 runs into an endless loop. */
353 goto print_and_exit;
354 case STATUS_DLL_NOT_FOUND:
355 process_fn = fn_win;
356 break;
357 case STATUS_BREAKPOINT:
358 if (!isdll)
359 TerminateProcess (hProcess, 0);
360 break;
361 }
362 if (ev.u.Exception.ExceptionRecord.ExceptionFlags &
363 EXCEPTION_NONCONTINUABLE) {
364 res = 1;
365 goto print_and_exit;
366 }
367 break;
368 case EXIT_PROCESS_DEBUG_EVENT:
369 print_and_exit:
370 print_dlls (&dll_list, isdll ? fn_win : NULL, process_fn);
371 exitnow = true;
372 break;
373 default:
374 break;
375 }
376 if (!ContinueDebugEvent (ev.dwProcessId, ev.dwThreadId, cont))
377 {
378 cygwin_internal (CW_SETERRNO, __FILE__, __LINE__ - 2);
379 print_errno_error_and_return (in_fn);
380 }
381 if (exitnow)
382 break;
383 }
384
385 return res;
386 }
387
388 int
389 main (int argc, char **argv)
390 {
391 int optch;
392
393 /* Use locale from environment. If not set or set to "C", use UTF-8. */
394 setlocale (LC_CTYPE, "");
395 if (!strcmp (setlocale (LC_CTYPE, NULL), "C"))
396 setlocale (LC_CTYPE, "en_US.UTF-8");
397 while ((optch = getopt_long (argc, argv, opts, longopts, NULL)) != -1)
398 switch (optch)
399 {
400 case 'd':
401 case 'r':
402 case 'u':
403 error ("option not implemented `-%c'", optch);
404 exit (1);
405 case 'h':
406 usage ();
407 exit (0);
408 case 'V':
409 print_version ();
410 return 0;
411 default:
412 fprintf (stderr, "Try `%s --help' for more information.\n",
413 program_invocation_short_name);
414 return 1;
415 }
416 argv += optind;
417 if (!*argv)
418 error ("missing file arguments");
419
420 int ret = 0;
421 bool multiple = !!argv[1];
422 char *fn;
423 while ((fn = *argv++))
424 if (report (fn, multiple))
425 ret = 1;
426 exit (ret);
427 }
428
429 static bool printing = false;
430
431
432 /* dump of import directory
433 section begins at pointer 'section base'
434 section RVA is 'section_rva'
435 import directory begins at pointer 'imp' */
436 static int
437 dump_import_directory (const void *const section_base,
438 const DWORD section_rva,
439 const IMAGE_IMPORT_DESCRIPTOR *imp)
440 {
441 /* get memory address given the RVA */
442 #define adr(rva) ((const void*) ((char*) section_base+((DWORD) (rva))-section_rva))
443
444 /* continue until address inaccessible or there's no DLL name */
445 for (; !IsBadReadPtr (imp, sizeof (*imp)) && imp->Name; imp++)
446 {
447 wchar_t full_path[PATH_MAX];
448 wchar_t *dummy;
449 char *fn = (char *) adr (imp->Name);
450
451 if (saw_file (fn))
452 continue;
453
454 int len = mbstowcs (NULL, fn, 0);
455 if (len <= 0)
456 continue;
457 wchar_t fnw[len + 1];
458 mbstowcs (fnw, fn, len + 1);
459 /* output DLL's name */
460 char *print_fn;
461 if (!SearchPathW (NULL, fnw, NULL, PATH_MAX, full_path, &dummy))
462 {
463 print_fn = strdup ("not found");
464 printing = true;
465 }
466 else if (!printing)
467 continue;
468 else
469 {
470 print_fn = tocyg (full_path);
471 strcat (print_fn, " (?)");
472 }
473
474 printf ("\t%s => %s\n", (char *) fn, print_fn);
475 free (print_fn);
476 }
477 #undef adr
478
479 return 0;
480 }
481
482 /* load a file in RAM (memory-mapped)
483 return pointer to loaded file
484 0 if no success */
485 static void *
486 map_file (const wchar_t *filename)
487 {
488 HANDLE hFile, hMapping;
489 void *basepointer;
490 if ((hFile = CreateFileW (filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
491 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0)) == INVALID_HANDLE_VALUE)
492 {
493 fprintf (stderr, "couldn't open %ls\n", filename);
494 return 0;
495 }
496 if (!(hMapping = CreateFileMapping (hFile, 0, PAGE_READONLY | SEC_COMMIT, 0, 0, 0)))
497 {
498 fprintf (stderr, "CreateFileMapping failed with windows error %u\n",
499 (unsigned int) GetLastError ());
500 CloseHandle (hFile);
501 return 0;
502 }
503 if (!(basepointer = MapViewOfFile (hMapping, FILE_MAP_READ, 0, 0, 0)))
504 {
505 fprintf (stderr, "MapViewOfFile failed with windows error %u\n",
506 (unsigned int) GetLastError ());
507 CloseHandle (hMapping);
508 CloseHandle (hFile);
509 return 0;
510 }
511
512 CloseHandle (hMapping);
513 CloseHandle (hFile);
514
515 return basepointer;
516 }
517
518
519 /* this will return a pointer immediatly behind the DOS-header
520 0 if error */
521 static void *
522 skip_dos_stub (const IMAGE_DOS_HEADER *dos_ptr)
523 {
524 /* look there's enough space for a DOS-header */
525 if (IsBadReadPtr (dos_ptr, sizeof (*dos_ptr)))
526 {
527 fprintf (stderr, "not enough space for DOS-header\n");
528 return 0;
529 }
530
531 /* validate MZ */
532 if (dos_ptr->e_magic != IMAGE_DOS_SIGNATURE)
533 {
534 fprintf (stderr, "not a DOS-stub\n");
535 return 0;
536 }
537
538 /* ok, then, go get it */
539 return (char*) dos_ptr + dos_ptr->e_lfanew;
540 }
541
542
543 /* find the directory's section index given the RVA
544 Returns -1 if impossible */
545 static int
546 get_directory_index (const unsigned dir_rva,
547 const unsigned dir_length,
548 const int number_of_sections,
549 const IMAGE_SECTION_HEADER *sections)
550 {
551 int sect;
552 for (sect = 0; sect < number_of_sections; sect++)
553 {
554 /* compare directory RVA to section RVA */
555 if (sections[sect].VirtualAddress <= dir_rva
556 && dir_rva < sections[sect].VirtualAddress+sections[sect].SizeOfRawData)
557 return sect;
558 }
559
560 return -1;
561 }
562
563 /* dump imports of a single file
564 Returns 0 if successful, !=0 else */
565 static int
566 process_file (const wchar_t *filename)
567 {
568 void *basepointer; /* Points to loaded PE file
569 * This is memory mapped stuff
570 */
571 int number_of_sections;
572 DWORD import_rva; /* RVA of import directory */
573 DWORD import_length; /* length of import directory */
574 int import_index; /* index of section with import directory */
575
576 /* ensure byte-alignment for struct tag_header */
577 #include <pshpack1.h>
578
579 const struct tag_header
580 {
581 DWORD signature;
582 IMAGE_FILE_HEADER file_head;
583 IMAGE_OPTIONAL_HEADER opt_head;
584 IMAGE_SECTION_HEADER section_header[1]; /* an array of unknown length */
585 } *header;
586
587 /* revert to regular alignment */
588 #include <poppack.h>
589
590 printing = false;
591
592 /* first, load file */
593 basepointer = map_file (filename);
594 if (!basepointer)
595 {
596 puts ("cannot load file");
597 return 1;
598 }
599
600 /* get header pointer; validate a little bit */
601 header = (tag_header *) skip_dos_stub ((IMAGE_DOS_HEADER *) basepointer);
602 if (!header)
603 {
604 puts ("cannot skip DOS stub");
605 UnmapViewOfFile (basepointer);
606 return 2;
607 }
608
609 /* look there's enough space for PE headers */
610 if (IsBadReadPtr (header, sizeof (*header)))
611 {
612 puts ("not enough space for PE headers");
613 UnmapViewOfFile (basepointer);
614 return 3;
615 }
616
617 /* validate PE signature */
618 if (header->signature != IMAGE_NT_SIGNATURE)
619 {
620 puts ("not a PE file");
621 UnmapViewOfFile (basepointer);
622 return 4;
623 }
624
625 /* get number of sections */
626 number_of_sections = header->file_head.NumberOfSections;
627
628 /* check there are sections... */
629 if (number_of_sections < 1)
630 {
631 UnmapViewOfFile (basepointer);
632 return 5;
633 }
634
635 /* validate there's enough space for section headers */
636 if (IsBadReadPtr (header->section_header, number_of_sections*sizeof (IMAGE_SECTION_HEADER)))
637 {
638 puts ("not enough space for section headers");
639 UnmapViewOfFile (basepointer);
640 return 6;
641 }
642
643 /* get RVA and length of import directory */
644 import_rva = header->opt_head.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
645 import_length = header->opt_head.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size;
646
647 /* check there's stuff to care about */
648 if (!import_rva || !import_length)
649 {
650 UnmapViewOfFile (basepointer);
651 return 0; /* success! */
652 }
653
654 /* get import directory pointer */
655 import_index = get_directory_index (import_rva,import_length,number_of_sections,header->section_header);
656
657 /* check directory was found */
658 if (import_index < 0)
659 {
660 puts ("couldn't find import directory in sections");
661 UnmapViewOfFile (basepointer);
662 return 7;
663 }
664
665 /* The pointer to the start of the import directory's section */
666 const void *section_address = (char*) basepointer + header->section_header[import_index].PointerToRawData;
667 if (dump_import_directory (section_address,
668 header->section_header[import_index].VirtualAddress,
669 /* the last parameter is the pointer to the import directory:
670 section address + (import RVA - section RVA)
671 The difference is the offset of the import directory in the section */
672 (const IMAGE_IMPORT_DESCRIPTOR *) ((char *) section_address+import_rva-header->section_header[import_index].VirtualAddress)))
673 {
674 UnmapViewOfFile (basepointer);
675 return 8;
676 }
677
678 UnmapViewOfFile (basepointer);
679 return 0;
680 }
This page took 0.06607 seconds and 6 git commands to generate.