newlib/winsup/utils/ldd.cc
Mark Geisert e3ca53d9db Cygwin: ldd: Try harder to get dll names
Borrow a trick from strace to lessen occurrences of "??? => ???" in ldd
output.  Specifically, if the module name isn't found in the usual place
in the mapped image, use the file handle we have to look up the name.
2019-08-15 09:56:11 +02:00

723 lines
19 KiB
C++

/* Copyright (c) 2009, 2010, 2011, 2013 Chris Faylor
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <errno.h>
#include <getopt.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <locale.h>
#include <sys/cygwin.h>
#include <cygwin/version.h>
#include <unistd.h>
#include <libgen.h>
#define _WIN32_WINNT 0x0a00
#include <windows.h>
#include <winternl.h>
#include <imagehlp.h>
#include <psapi.h>
struct option longopts[] =
{
{"help", no_argument, NULL, 'h'},
{"verbose", no_argument, NULL, 'v'},
{"version", no_argument, NULL, 'V'},
{"data-relocs", no_argument, NULL, 'd'},
{"function-relocs", no_argument, NULL, 'r'},
{"unused", no_argument, NULL, 'u'},
{0, no_argument, NULL, 0}
};
const char *opts = "dhruvV";
static int process_file (const wchar_t *);
static void *drive_map;
static int
error (const char *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
fprintf (stderr, "ldd: ");
vfprintf (stderr, fmt, ap);
fprintf (stderr, "\nTry `ldd --help' for more information.\n");
exit (1);
}
static void
usage ()
{
printf ("Usage: %s [OPTION]... FILE...\n\
\n\
Print shared library dependencies\n\
\n\
-h, --help print this help and exit\n\
-V, --version print version information and exit\n\
-r, --function-relocs process data and function relocations\n\
(currently unimplemented)\n\
-u, --unused print unused direct dependencies\n\
(currently unimplemented)\n\
-v, --verbose print all information\n\
(currently unimplemented)\n",
program_invocation_short_name);
}
static void
print_version ()
{
printf ("ldd (cygwin) %d.%d.%d\n"
"Print shared library dependencies\n"
"Copyright (C) 2009 - %s Chris Faylor\n"
"This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
CYGWIN_VERSION_DLL_MAJOR / 1000,
CYGWIN_VERSION_DLL_MAJOR % 1000,
CYGWIN_VERSION_DLL_MINOR,
strrchr (__DATE__, ' ') + 1);
}
#define print_errno_error_and_return(__fn) \
do {\
fprintf (stderr, "ldd: %s: %s\n", (__fn), strerror (errno));\
return 1;\
} while (0)
#define set_errno_and_return(x) \
do {\
cygwin_internal (CW_SETERRNO, __FILE__, __LINE__ - 2);\
return (x);\
} while (0)
static HANDLE hProcess;
static struct filelist
{
struct filelist *next;
char *name;
} *head;
static bool
saw_file (char *name)
{
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;
}
static wchar_t *
get_module_filename (HANDLE hp, HMODULE hm)
{
size_t len;
wchar_t *buf = NULL;
DWORD res;
for (len = 1024; (res = GetModuleFileNameExW (hp, hm, (buf = (wchar_t *) realloc (buf, len * sizeof (wchar_t))), len)) == len; len += 1024)
continue;
if (!res)
{
free (buf);
buf = NULL;
}
return buf;
}
static BOOL
GetFileNameFromHandle(HANDLE hFile, WCHAR pszFilename[MAX_PATH+1])
{
BOOL result = FALSE;
ULONG len = 0;
OBJECT_NAME_INFORMATION *ntfn = (OBJECT_NAME_INFORMATION *) alloca (65536);
NTSTATUS status = NtQueryObject (hFile, ObjectNameInformation,
ntfn, 65536, &len);
if (NT_SUCCESS (status))
{
PWCHAR win32path = ntfn->Name.Buffer;
win32path[ntfn->Name.Length / sizeof (WCHAR)] = L'\0';
/* NtQueryObject returns a native NT path. (Try to) convert to Win32. */
if (!drive_map)
drive_map = (void *) cygwin_internal (CW_ALLOC_DRIVE_MAP);
if (drive_map)
win32path = (PWCHAR) cygwin_internal (CW_MAP_DRIVE_MAP, drive_map,
win32path);
pszFilename[0] = L'\0';
wcsncat (pszFilename, win32path, MAX_PATH);
result = TRUE;
}
return result;
}
static wchar_t *
load_dll (const wchar_t *fn)
{
wchar_t *buf = get_module_filename (GetCurrentProcess (), NULL);
if (!buf)
{
printf ("ldd: GetModuleFileName returned an error %u\n",
(unsigned int) GetLastError ());
exit (1); /* FIXME */
}
wchar_t *newbuf = (wchar_t *) malloc ((sizeof (L"\"\" -- ") + wcslen (buf) + wcslen (fn)) * sizeof (wchar_t));
newbuf[0] = L'"';
wcscpy (newbuf + 1, buf);
wchar_t *p = wcsstr (newbuf, L"\\ldd");
if (!p)
{
printf ("ldd: can't parse my own filename \"%ls\"\n", buf);
exit (1);
}
p[3] = L'h';
wcscat (newbuf, L"\" -- ");
wcscat (newbuf, fn);
free (buf);
return newbuf;
}
static int
start_process (const wchar_t *fn, bool& isdll)
{
STARTUPINFOW si = {};
PROCESS_INFORMATION pi;
si.cb = sizeof (si);
wchar_t *cmd;
/* OCaml natdynlink plugins (.cmxs) cannot be handled by ldd because they
can only be loaded by flexdll_dlopen() */
if (wcslen (fn) < 4 || (wcscasecmp (wcschr (fn, L'\0') - 4, L".dll") != 0
&& wcscasecmp (wcschr (fn, L'\0') - 4, L".oct") != 0
&& wcscasecmp (wcschr (fn, L'\0') - 3, L".so") != 0))
{
cmd = wcsdup (fn);
isdll = false;
}
else
{
cmd = load_dll (fn);
isdll = true;
}
if (CreateProcessW (NULL, cmd, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &si, &pi))
{
free (cmd);
hProcess = pi.hProcess;
DebugSetProcessKillOnExit (true);
return 0;
}
free (cmd);
set_errno_and_return (1);
}
struct dlls
{
LPVOID lpBaseOfDll;
HANDLE hFile;
struct dlls *next;
};
#define SLOP strlen (" (?)")
char *
tocyg (wchar_t *win_fn)
{
ssize_t cwlen = cygwin_conv_path (CCP_WIN_W_TO_POSIX, win_fn, NULL, 0);
char *fn;
if (cwlen <= 0)
{
int len = wcstombs (NULL, win_fn, 0) + 1;
if ((fn = (char *) malloc (len)))
wcstombs (fn, win_fn, len);
}
else
{
char *fn_cyg = (char *) malloc (cwlen + SLOP + 1);
if (cygwin_conv_path (CCP_WIN_W_TO_POSIX, win_fn, fn_cyg, cwlen) == 0)
fn = fn_cyg;
else
{
free (fn_cyg);
int len = wcstombs (NULL, win_fn, 0);
fn = (char *) malloc (len + SLOP + 1);
wcstombs (fn, win_fn, len + SLOP + 1);
}
}
return fn;
}
#define CYGWIN_DLL_LEN (wcslen (L"\\cygwin1.dll"))
static int
print_dlls (dlls *dll, const wchar_t *dllfn, const wchar_t *process_fn)
{
head = NULL; /* FIXME: memory leak */
while ((dll = dll->next))
{
char *fn;
wchar_t *fullpath = get_module_filename (hProcess, (HMODULE) dll->lpBaseOfDll);
if (!fullpath)
{
// if no path found yet, try getting it from an open handle to the DLL
wchar_t dllname[MAX_PATH+1];
if (GetFileNameFromHandle (dll->hFile, dllname))
{
fn = tocyg (dllname);
saw_file (basename (fn));
}
else
fn = strdup ("???");
}
else if (dllfn && wcscmp (fullpath, dllfn) == 0)
{
free (fullpath);
continue;
}
else
{
fn = tocyg (fullpath);
saw_file (basename (fn));
free (fullpath);
}
printf ("\t%s => %s (%p)\n", basename (fn), fn, dll->lpBaseOfDll);
free (fn);
}
if (process_fn)
return process_file (process_fn);
return 0;
}
static int
report (const char *in_fn, bool multiple)
{
if (multiple)
printf ("%s:\n", in_fn);
char *fn = realpath (in_fn, NULL);
if (!fn)
print_errno_error_and_return (in_fn);
ssize_t len = cygwin_conv_path (CCP_POSIX_TO_WIN_W, fn, NULL, 0);
if (len <= 0)
print_errno_error_and_return (fn);
bool isdll;
wchar_t fn_win[len + 1];
if (cygwin_conv_path (CCP_POSIX_TO_WIN_W, fn, fn_win, len))
print_errno_error_and_return (fn);
if (!fn || start_process (fn_win, isdll))
print_errno_error_and_return (in_fn);
DEBUG_EVENT ev;
dlls dll_list = {};
dlls *dll_last = &dll_list;
const wchar_t *process_fn = NULL;
int res = 0;
while (1)
{
bool exitnow = false;
DWORD cont = DBG_CONTINUE;
if (!WaitForDebugEvent (&ev, INFINITE))
break;
switch (ev.dwDebugEventCode)
{
case CREATE_PROCESS_DEBUG_EVENT:
if (!isdll)
{
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER) alloca (4096);
PIMAGE_NT_HEADERS nt_header;
PVOID entry_point;
static const unsigned char int3 = 0xcc;
SIZE_T bytes;
if (!ReadProcessMemory (hProcess,
ev.u.CreateProcessInfo.lpBaseOfImage,
dos_header, 4096, &bytes))
print_errno_error_and_return (in_fn);
nt_header = PIMAGE_NT_HEADERS (PBYTE (dos_header)
+ dos_header->e_lfanew);
entry_point = (PVOID)
((caddr_t) ev.u.CreateProcessInfo.lpBaseOfImage
+ nt_header->OptionalHeader.AddressOfEntryPoint);
if (!WriteProcessMemory (hProcess, entry_point, &int3, 1, &bytes))
print_errno_error_and_return (in_fn);
}
break;
case LOAD_DLL_DEBUG_EVENT:
dll_last->next = (dlls *) malloc (sizeof (dlls));
dll_last->next->lpBaseOfDll = ev.u.LoadDll.lpBaseOfDll;
dll_last->next->hFile = ev.u.LoadDll.hFile;
dll_last->next->next = NULL;
dll_last = dll_last->next;
break;
case EXCEPTION_DEBUG_EVENT:
switch (ev.u.Exception.ExceptionRecord.ExceptionCode)
{
case STATUS_ENTRYPOINT_NOT_FOUND:
/* A STATUS_ENTRYPOINT_NOT_FOUND might be encountered right after
loading all DLLs. We have to handle it here, otherwise ldd
runs into an endless loop. */
goto print_and_exit;
case STATUS_DLL_NOT_FOUND:
process_fn = fn_win;
break;
case STATUS_BREAKPOINT:
if (!isdll)
TerminateProcess (hProcess, 0);
break;
}
if (ev.u.Exception.ExceptionRecord.ExceptionFlags &
EXCEPTION_NONCONTINUABLE) {
res = 1;
goto print_and_exit;
}
break;
case EXIT_PROCESS_DEBUG_EVENT:
print_and_exit:
print_dlls (&dll_list, isdll ? fn_win : NULL, process_fn);
exitnow = true;
break;
default:
break;
}
if (!ContinueDebugEvent (ev.dwProcessId, ev.dwThreadId, cont))
{
cygwin_internal (CW_SETERRNO, __FILE__, __LINE__ - 2);
print_errno_error_and_return (in_fn);
}
if (exitnow)
break;
}
return res;
}
int
main (int argc, char **argv)
{
int optch;
/* Use locale from environment. If not set or set to "C", use UTF-8. */
setlocale (LC_CTYPE, "");
if (!strcmp (setlocale (LC_CTYPE, NULL), "C"))
setlocale (LC_CTYPE, "en_US.UTF-8");
while ((optch = getopt_long (argc, argv, opts, longopts, NULL)) != -1)
switch (optch)
{
case 'd':
case 'r':
case 'u':
error ("option not implemented `-%c'", optch);
exit (1);
case 'h':
usage ();
exit (0);
case 'V':
print_version ();
return 0;
default:
fprintf (stderr, "Try `%s --help' for more information.\n",
program_invocation_short_name);
return 1;
}
argv += optind;
if (!*argv)
error ("missing file arguments");
int ret = 0;
bool multiple = !!argv[1];
char *fn;
while ((fn = *argv++))
if (report (fn, multiple))
ret = 1;
if (drive_map)
cygwin_internal (CW_FREE_DRIVE_MAP, drive_map);
exit (ret);
}
static bool printing = 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++)
{
wchar_t full_path[PATH_MAX];
wchar_t *dummy;
char *fn = (char *) adr (imp->Name);
if (saw_file (fn))
continue;
int len = mbstowcs (NULL, fn, 0);
if (len <= 0)
continue;
wchar_t fnw[len + 1];
mbstowcs (fnw, fn, len + 1);
/* output DLL's name */
char *print_fn;
if (!SearchPathW (NULL, fnw, NULL, PATH_MAX, 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 wchar_t *filename)
{
HANDLE hFile, hMapping;
void *basepointer;
if ((hFile = CreateFileW (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 %ls\n", filename);
return 0;
}
if (!(hMapping = CreateFileMapping (hFile, 0, PAGE_READONLY | SEC_COMMIT, 0, 0, 0)))
{
fprintf (stderr, "CreateFileMapping failed with windows error %u\n",
(unsigned int) GetLastError ());
CloseHandle (hFile);
return 0;
}
if (!(basepointer = MapViewOfFile (hMapping, FILE_MAP_READ, 0, 0, 0)))
{
fprintf (stderr, "MapViewOfFile failed with windows error %u\n",
(unsigned int) 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 wchar_t *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 <pshpack1.h>
const struct tag_header
{
DWORD signature;
IMAGE_FILE_HEADER file_head;
IMAGE_OPTIONAL_HEADER opt_head;
IMAGE_SECTION_HEADER section_header[1]; /* an array of unknown length */
} *header;
/* revert to regular alignment */
#include <poppack.h>
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 = (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;
}
/* 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;
}