Finding DLLs used by a Cygwin program

I don't know how many other people get this issue, but it comes up at my work a bit: Some co-worker asks me if I know of a tool to do such-and-such, and invariably I think “well, that's easy to do on Unix, but on Windows…”.

Then I remember Cygwin, and quickly find a Cygwin utility that does it, or can be scripted to do it with a small amount of work. So then co-worker asks if they can have a copy of this utility, and of course Cygwin is Free, so I say “sure, go download from www.cygwin.com.” Then they say, “yeah, but I don't want to install all of Cygwin, can't you just give me that one program?”

Well, the Cygwin command-line tools can be run from a Windows CMD.EXE shell, so this is quite possible to do. However, they all require the Cygwin POSIX layer, which at a minimum means I should also give them cygwin1.dll. But what other DLLs might the program use?

MJL2008-09-10T14:37+1000 Update: since this page gets a lot of hits, here's the quick answer: use cygcheck, i.e:

cygcheck grep

Find it under Happy hacker discovery #2. Keep reading if you're bored...

...

In the past, I used to just give the person the cygwin1.dll DLL and a program (SPLIT.EXE,say), let the person run it on their computer, explaining that it will crash with an error about a missing DLL. Then they would come back to me and say that they also need cygintl-8.dll and try again. It will crash again because cygintl-8.dll just happens to link to cygiconv-2.dll. After supplying this third DLL file, my co-worker can finally run SPLIT.EXE and break their huge data set into manageable chunks. Everyone's happy.

The whole process is a bit embarrassing for me and annoying for them (though they could just download the base Cygwin install like I asked…) So, to solve this problem somewhat, I set about thinking “I wonder if there's a Cygwin tool that can find DLLs in a program?”.

Well, a quick look at info binutils reveals that there is nothing that directly solves my problem of listing which DLLs a program links to. There is this thing called dlltool, but it is for creating PE dynamic libraries, not for listing the libraries used by a PE executable. Bummer.

Happy hacker discovery #0

Well, there is also a tool called strings, which will list text strings in binary files. Now, Windows PE executables happen to contain the name of their linked DLLs as text strings, yay! So all I need to do, is pass strings over a given program, filter for DLL names (to strip out error messages and such) and I have my list.

Oh, but then I'll need to repeat for each DLL as well. So I need a script to do it:

#!/bin/bash

#
# finds all the DLL files linked by a Win32 PE executable.
# Useful for finding needed DLLs to supply with a Cygwin program when
# sharing just that program with other people.
#


if [ x"$1" = x ]; then
    PROG=`basename $0`
    printf "Usage:  $PROG <command to list DLLs for>\\n"
    printf "        (you should not add '.exe' to the end).\\n"
    exit 1
fi


SEARCH_FILE=`which $1`.exe


# This is not perfect, as it's too permissive, but should be good enough
DLL_REGEXP='^[[:alnum:][:punct:]]*\\.[Dd][Ll][Ll]$'


# We don't care about the Win32 kernel API library...
KERNEL_REGEXP='[Kk][Ee][Rr][Nn][Ee][Ll]32\\.[Dd][Ll][Ll]'


# Get list of program's DLLs:
DLL_LIST=`strings -n 5 $SEARCH_FILE \\
          |grep $DLL_REGEXP \\
          |grep -v $KERNEL_REGEXP`

# Print the DLLs used directly by the program
printf "$SEARCH_FILE:\\n"

for I in $DLL_LIST; do
   printf "\\t$I\\n"
done

printf "\\t(KERNEL32.DLL)\\n\\n"


#Now find each DLL's dependencies (only one level deep though)

for CURR_DLL in $DLL_LIST; do
    SEARCH_DLL=`which $CURR_DLL`
    DLL_DLL_LIST=`strings -n 5 $SEARCH_DLL \\
                  |grep $DLL_REGEXP \\
                  |grep -v $KERNEL_REGEXP \\
                  |grep -v $CURR_DLL`
    printf " -->\\t$CURR_DLL:\\n"
    for I in $DLL_DLL_LIST; do
       printf "\\t\\t$I\\n"
    done
    printf "\\t\\t(KERNEL32.DLL)\\n\\n"
done

There we are, a list of DLLs used by a given program, and the DLLs that those DLLs use.

It's not perfect: it is not recursive, so only goes one level deep, but this should cover most cases. One day I might come back to this, when I figure out how to write a recursive function in bash.

So now I know which DLLs to give to my co-worker along with the nifty tool they want. But where on my system are they? Cygwin installs most DLLs into /usr/bin. But sometimes they can be in /usr/lib. So do I have to make a search?

Happy hacker discovery #1

Well, no I don't. Cygwin's which command also works for DLLs, yay! You just have to go:

  $ which cygwin1.dll
  /usr/bin/cygwin1.dll
  $ which cygintl-8.dll
  /usr/bin/cygintl-8.dll
  $ which cygiconv-2.dll
  /usr/bin/cygiconv-2.dll

There, problem solved.

Happy hacker discovery #2

Update: 2008-01-21 09:56+1100: There's an awesome tool that comes with Cygwin, called cygcheck(1), that does exactly what I need. Thanks for the tip, Leni! Here's a sample output for the SPLIT.EXE example:

$ cygcheck split

Found: d:\cygwin\bin\split.exe

d:\cygwin\bin\split.exe
  d:\cygwin\bin\cygwin1.dll
    C:\WINDOWS\system32\ADVAPI32.DLL
      C:\WINDOWS\system32\ntdll.dll
      C:\WINDOWS\system32\KERNEL32.dll
      C:\WINDOWS\system32\RPCRT4.dll
        C:\WINDOWS\system32\Secur32.dll
  d:\cygwin\bin\cygintl-8.dll
    d:\cygwin\bin\cygiconv-2.dll