diff --git a/winsup/utils/ChangeLog b/winsup/utils/ChangeLog index 02b77a946..e04eb8444 100644 --- a/winsup/utils/ChangeLog +++ b/winsup/utils/ChangeLog @@ -1,3 +1,21 @@ +2009-03-14 Christopher Faylor + + * ldd.cc: Rework to detect missing DLLs. + (start_process): Change to expect windows filename as input. + (tocyg): New function - convert cygwin fn to windows fn. + (print_dlls_and_kill_inferior): Accept extra argument denoting whether + to open input and look for nonexistent DLLs. Use tocyg to convert + filename and pass it to start_process. + (report): Flag when an DLL-not-found exception occurs and pass this + information to print_dlls_and_kill_inferior. + (filelist): New structure. + (saw_file): New function. + (dump_import_directory): Ditto. + (map_file): Ditto. + (skip_dos_stub): Ditto. + (get_directory_index): Ditto. + (process_file): Ditto. + 2009-03-09 Corinna Vinschen * utils.sgml: Fix typo. diff --git a/winsup/utils/ldd.cc b/winsup/utils/ldd.cc index bba9764f6..df374b49e 100644 --- a/winsup/utils/ldd.cc +++ b/winsup/utils/ldd.cc @@ -32,12 +32,17 @@ #include #include #include +#include #define _WIN32_WINNT 0x0501 #include #include #include +#ifndef STATUS_DLL_NOT_FOUND +#define STATUS_DLL_NOT_FOUND (0xC0000135L) +#endif + #define VERSION "1.0" struct option longopts[] = @@ -50,6 +55,8 @@ struct option longopts[] = {0, no_argument, NULL, 0} }; +static int process_file (const char *); + static int usage (const char *fmt, ...) { @@ -79,18 +86,10 @@ static HANDLE hProcess; static int start_process (const char *fn) { - ssize_t len = cygwin_conv_path (CCP_POSIX_TO_WIN_A, fn, NULL, 0); - if (len <= 0) - print_errno_error_and_return (fn); - - char fn_win[len + 1]; - if (cygwin_conv_path (CCP_POSIX_TO_WIN_A, fn, fn_win, len)) - print_errno_error_and_return (fn); - STARTUPINFO si = {}; PROCESS_INFORMATION pi; si.cb = sizeof (si); - if (CreateProcess (NULL, fn_win, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi)) + if (CreateProcess (NULL, (CHAR *) fn, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi)) { hProcess = pi.hProcess; DebugSetProcessKillOnExit (true); @@ -124,8 +123,32 @@ struct dlls struct dlls *next; }; +#define SLOP strlen (" (?)") +char * +tocyg (char *win_fn) +{ + win_fn[MAX_PATH] = '\0'; + ssize_t cwlen = cygwin_conv_path (CCP_WIN_A_TO_POSIX, win_fn, NULL, 0); + char *fn; + if (cwlen <= 0) + fn = strdup (win_fn); + else + { + char *fn_cyg = (char *) malloc (cwlen + SLOP + 1); + if (cygwin_conv_path (CCP_WIN_A_TO_POSIX, win_fn, fn_cyg, cwlen) == 0) + fn = fn_cyg; + else + { + free (fn_cyg); + fn = (char *) malloc (strlen (win_fn) + SLOP + 1); + strcpy (fn, win_fn); + } + } + return fn; +} + static int -print_dlls_and_kill_inferior (dlls *dll) +print_dlls_and_kill_inferior (dlls *dll, const char *process_fn) { while ((dll = dll->next)) { @@ -135,27 +158,13 @@ print_dlls_and_kill_inferior (dlls *dll) if (!len) fn = strdup ("???"); else - { - fnbuf[MAX_PATH] = '\0'; - ssize_t cwlen = cygwin_conv_path (CCP_WIN_A_TO_POSIX, fnbuf, NULL, 0); - if (cwlen <= 0) - fn = strdup (fnbuf); - else - { - char *fn_cyg = (char *) malloc (cwlen + 1); - if (cygwin_conv_path (CCP_WIN_A_TO_POSIX, fnbuf, fn_cyg, cwlen) == 0) - fn = fn_cyg; - else - { - free (fn_cyg); - fn = strdup (fnbuf); - } - } - } - printf ("\t%s (%p)\n", fn, dll->lpBaseOfDll); + fn = tocyg (fnbuf); + printf ("\t%s => %s (%p)\n", basename (fn), fn, dll->lpBaseOfDll); free (fn); } TerminateProcess (hProcess, 0); + if (process_fn) + return process_file (process_fn); return 0; } @@ -165,7 +174,18 @@ report (const char *in_fn, bool multiple) if (multiple) printf ("%s:\n", in_fn); char *fn = realpath (in_fn, NULL); - if (!fn || start_process (fn)) + if (!fn) + print_errno_error_and_return (in_fn); + + ssize_t len = cygwin_conv_path (CCP_POSIX_TO_WIN_A, fn, NULL, 0); + if (len <= 0) + print_errno_error_and_return (fn); + + char fn_win[len + 1]; + if (cygwin_conv_path (CCP_POSIX_TO_WIN_A, fn, fn_win, len)) + print_errno_error_and_return (fn); + + if (!fn || start_process (fn_win)) print_errno_error_and_return (in_fn); DEBUG_EVENT ev; @@ -174,6 +194,7 @@ report (const char *in_fn, bool multiple) dlls dll_list = {}; dlls *dll_last = &dll_list; + const char *process_fn = NULL; while (1) { if (WaitForDebugEvent (&ev, 1000)) @@ -198,7 +219,10 @@ report (const char *in_fn, bool multiple) dll_last = dll_last->next; break; case EXCEPTION_DEBUG_EVENT: - print_dlls_and_kill_inferior (&dll_list); + if (ev.u.Exception.ExceptionRecord.ExceptionCode == STATUS_DLL_NOT_FOUND) + process_fn = fn_win; + else + print_dlls_and_kill_inferior (&dll_list, process_fn); break; default: break; @@ -263,3 +287,278 @@ main (int argc, char **argv) ret = 1; exit (ret); } + +static struct filelist +{ + struct filelist *next; + char *name; +} *head; + +static bool printing = false; + +static bool +saw_file (char *name) +{ + + struct filelist *p; + + for (p=head; p; p = p->next) + if (strcasecmp (name, p->name) == 0) + return true; + + p = (filelist *) malloc(sizeof (struct filelist)); + p->next = head; + p->name = strdup (name); + head = p; + return false; +} + + +/* dump of import directory + section begins at pointer 'section base' + section RVA is 'section_rva' + import directory begins at pointer 'imp' */ +static int +dump_import_directory (const void *const section_base, + const DWORD section_rva, + const IMAGE_IMPORT_DESCRIPTOR *imp) +{ + /* get memory address given the RVA */ + #define adr(rva) ((const void*) ((char*) section_base+((DWORD) (rva))-section_rva)) + + /* continue until address inaccessible or there's no DLL name */ + for (; !IsBadReadPtr (imp, sizeof (*imp)) && imp->Name; imp++) + { + char full_path[MAX_PATH]; + char *dummy; + char *fn = (char *) adr (imp->Name); + + if (saw_file (fn)) + continue; + + /* output DLL's name */ + char *print_fn; + if (!SearchPath (NULL, fn, NULL, sizeof (full_path), full_path, &dummy)) + { + print_fn = strdup ("not found"); + printing = true; + } + else if (!printing) + continue; + else + { + print_fn = tocyg (full_path); + strcat (print_fn, " (?)"); + } + + printf ("\t%s => %s\n", (char *) fn, print_fn); + free (print_fn); + } + #undef adr + + return 0; +} + +/* load a file in RAM (memory-mapped) + return pointer to loaded file + 0 if no success */ +static void * +map_file (const char *filename) +{ + HANDLE hFile, hMapping; + void *basepointer; + if ((hFile = CreateFile (filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, + 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0)) == INVALID_HANDLE_VALUE) + { + fprintf (stderr, "couldn't open %s\n", filename); + return 0; + } + if (!(hMapping = CreateFileMapping (hFile, 0, PAGE_READONLY | SEC_COMMIT, 0, 0, 0))) + { + fprintf (stderr, "CreateFileMapping failed with windows error %lu\n", GetLastError ()); + CloseHandle (hFile); + return 0; + } + if (!(basepointer = MapViewOfFile (hMapping, FILE_MAP_READ, 0, 0, 0))) + { + fprintf (stderr, "MapViewOfFile failed with windows error %lu\n", GetLastError ()); + CloseHandle (hMapping); + CloseHandle (hFile); + return 0; + } + + CloseHandle (hMapping); + CloseHandle (hFile); + + return basepointer; +} + + +/* this will return a pointer immediatly behind the DOS-header + 0 if error */ +static void * +skip_dos_stub (const IMAGE_DOS_HEADER *dos_ptr) +{ + /* look there's enough space for a DOS-header */ + if (IsBadReadPtr (dos_ptr, sizeof (*dos_ptr))) + { + fprintf (stderr, "not enough space for DOS-header\n"); + return 0; + } + + /* validate MZ */ + if (dos_ptr->e_magic != IMAGE_DOS_SIGNATURE) + { + fprintf (stderr, "not a DOS-stub\n"); + return 0; + } + + /* ok, then, go get it */ + return (char*) dos_ptr + dos_ptr->e_lfanew; +} + + +/* find the directory's section index given the RVA + Returns -1 if impossible */ +static int +get_directory_index (const unsigned dir_rva, + const unsigned dir_length, + const int number_of_sections, + const IMAGE_SECTION_HEADER *sections) +{ + int sect; + for (sect = 0; sect < number_of_sections; sect++) + { + /* compare directory RVA to section RVA */ + if (sections[sect].VirtualAddress <= dir_rva + && dir_rva < sections[sect].VirtualAddress+sections[sect].SizeOfRawData) + return sect; + } + + return -1; +} + +/* dump imports of a single file + Returns 0 if successful, !=0 else */ +static int +process_file (const char *filename) +{ + void *basepointer; /* Points to loaded PE file + * This is memory mapped stuff + */ + int number_of_sections; + DWORD import_rva; /* RVA of import directory */ + DWORD import_length; /* length of import directory */ + int import_index; /* index of section with import directory */ + + /* ensure byte-alignment for struct tag_header */ + #include + + const struct tag_header + { + DWORD signature; + IMAGE_FILE_HEADER file_head; + IMAGE_OPTIONAL_HEADER opt_head; + IMAGE_SECTION_HEADER section_header[1]; /* this is an array of unknown length + actual number in file_head.NumberOfSections + if your compiler objects to it length 1 should work */ + } *header; + + /* revert to regular alignment */ + #include + + head = NULL; /* FIXME: memory leak */ + printing = false; + + /* first, load file */ + basepointer = map_file (filename); + if (!basepointer) + { + puts ("cannot load file"); + return 1; + } + + /* get header pointer; validate a little bit */ + header = (struct tag_header *) skip_dos_stub ((IMAGE_DOS_HEADER *) basepointer); + if (!header) + { + puts ("cannot skip DOS stub"); + UnmapViewOfFile (basepointer); + return 2; + } + + /* look there's enough space for PE headers */ + if (IsBadReadPtr (header, sizeof (*header))) + { + puts ("not enough space for PE headers"); + UnmapViewOfFile (basepointer); + return 3; + } + + /* validate PE signature */ + if (header->signature!=IMAGE_NT_SIGNATURE) + { + puts ("not a PE file"); + UnmapViewOfFile (basepointer); + return 4; + } + + /* get number of sections */ + number_of_sections = header->file_head.NumberOfSections; + + /* check there are sections... */ + if (number_of_sections<1) + { + UnmapViewOfFile (basepointer); + return 5; + } + + /* validate there's enough space for section headers */ + if (IsBadReadPtr (header->section_header, number_of_sections*sizeof (IMAGE_SECTION_HEADER))) + { + puts ("not enough space for section headers"); + UnmapViewOfFile (basepointer); + return 6; + } + + /* get RVA and length of import directory */ + import_rva = header->opt_head.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; + import_length = header->opt_head.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size; + + /* check there's stuff to care about */ + if (!import_rva || !import_length) + { + UnmapViewOfFile (basepointer); + return 0; /* success! */ + } + + /* get import directory pointer */ + import_index = get_directory_index (import_rva,import_length,number_of_sections,header->section_header); + + /* check directory was found */ + if (import_index <0) + { + puts ("couldn't find import directory in sections"); + UnmapViewOfFile (basepointer); + return 7; + } + + /* ok, we've found the import directory... action! */ + { + /* The pointer to the start of the import directory's section */ + const void *section_address = (char*) basepointer + header->section_header[import_index].PointerToRawData; + if (dump_import_directory (section_address, + header->section_header[import_index].VirtualAddress, + /* the last parameter is the pointer to the import directory: + section address + (import RVA - section RVA) + The difference is the offset of the import directory in the section */ + (const IMAGE_IMPORT_DESCRIPTOR *) ((char *) section_address+import_rva-header->section_header[import_index].VirtualAddress))) + { + UnmapViewOfFile (basepointer); + return 8; + } + } + + UnmapViewOfFile (basepointer); + return 0; +}