283

I'd like to write a test script or program that asserts that all DLL files in a given directory are of a particular build type.

I would use this as a sanity check at the end of a build process on an SDK to make sure that the 64-bit version hasn't somehow got some 32-bit DLL files in it and vice versa.

Is there an easy way to look at a DLL file and determine its type?

The solution should work on both xp32 and xp64.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
morechilli
  • 9,827
  • 7
  • 33
  • 54
  • 8
    I appreciate that once you know the answer this question and http://stackoverflow.com/q/197951/5427 share a solution. However the questions asked were different. One asked explicitly about dlls and one asked explicitly about exes. This question receives a fair few upvotes so I think it maps well to the problem people are trying to find an answer to. Similar thoughts on duplication discussed here http://meta.stackoverflow.com/q/266244/5427 – morechilli Jan 15 '16 at 10:45

5 Answers5

180

A crude way would be to call dumpbin with the headers option from the Visual Studio tools on each DLL and look for the appropriate output:

dumpbin /headers my32bit.dll

PE signature found

File Type: DLL

FILE HEADER VALUES
             14C machine (x86)
               1 number of sections
        45499E0A time date stamp Thu Nov 02 03:28:10 2006
               0 file pointer to symbol table
               0 number of symbols
              E0 size of optional header
            2102 characteristics
                   Executable
                   32 bit word machine
                   DLL

OPTIONAL HEADER VALUES
             10B magic # (PE32)

You can see a couple clues in that output that it is a 32 bit DLL, including the 14C value that Paul mentions. Should be easy to look for in a script.

Jeremy
  • 3,484
  • 3
  • 22
  • 25
  • 15
    WARNING: This method does not seem to work on anything .NET? Returns 32-bit for all .NET .dll or .exe programs, regardless of whether they are compiled for x32 or x32/x64 ("All CPU"). Appears as if every .NET executable has a 32-bit native header, and it calls the appropriate 32-bit or 64-bit .NET runtime when its invoked. – Contango Mar 02 '11 at 12:16
  • 3
    Interesting. That would seem to be ok by me since an AnyCPU DLL "could" run on a 32-bit machine. What about 64-bit only .NET DLLs? – Jeremy Mar 02 '11 at 20:56
  • 8
    @Contango: That's not entirely true(x64 only DLL's show correct header, even if .NET executable). The "Any CPU" part is true because the "real bitness" will be determined on assembly load, thus that can't be hardcoded in the assembly itself. You can use `corflags` utility that comes with `dumpbin` in order to see information about .NET executable. – Erti-Chris Eelmaa Oct 14 '14 at 15:53
  • 2
    Example of using corflags here http://stackoverflow.com/a/2418287/74585 – Matthew Lock Dec 04 '15 at 00:58
  • 3
    The 7-zip archive program command line can do something similar. So you can check on a PC that does not have DumpBin. (parameter is l, the letter before m) "C:\Program Files\7-Zip\"7z l MyFile.dll – Paul McCarthy Jun 20 '17 at 12:51
  • 1
    For AnyCPU, it will show x86 and "Application can handle large (>2GB) addresses" in characteristics. Only-x86 executables will instead have "32 bit word machine". Reminder that you can also explicitly compile non-AnyCPU x64/x86 binaries! (And Prefer32Bit off-AnyCPU) – AyCe Feb 01 '23 at 12:29
138

If you have Cygwin (or MobaXTerm, or Git Bash for Windows, or WSL, or...) installed (which I strongly recommend for a variety of reasons), you could use the 'file' utility on the DLL

file <filename>

which would give an output like this:

icuuc36.dll: MS-DOS executable PE  for MS Windows (DLL) (GUI) Intel 80386 32-bit
DevSolar
  • 67,862
  • 21
  • 134
  • 209
  • Hi DevSolar, Please let me know how to perform this in Cygwin. Thanks, Magesh. –  Jan 27 '11 at 12:27
  • 3
    Erm... where is the problem? `file `. I am not sure if `file` is part of the core setup, or if you have to select it for installation, but it's certainly available in Cygwin as I have used it in the past. – DevSolar Jan 27 '11 at 22:25
  • 4
    Anybody who uses MingW and doesn't realize it, this is also a Cygwin based thing, and it has this too. – Warren P Mar 16 '11 at 17:58
  • I tried this. All it ever returns is "mono" even on 64 bit test dll's I compiled. – BradLaney Apr 05 '12 at 16:37
  • @BradLaney: Could it be that what you tested are .NET assemblies? They are by nature very much different from e.g. compiled C++ binaries. – DevSolar Apr 05 '12 at 20:01
  • @BradLaney: Or, to put it differently: If `file` tells you it is mono / .NET code, then that's probably because it **is** mono / .NET code, or **perhaps** some strange bug in `file` (which I would bet money against), but certainly not a reason to vote me down. – DevSolar Apr 06 '12 at 09:00
  • @DevSolar shrug. I tested it on every DLL I could find on my machine until I got tired and it didn't give anything but the same result, every time. It doesn't work. And I tested it on DLL's I compiled specifically for 32bit, 64bit, still nothing. – BradLaney Apr 27 '12 at 17:56
  • 5
    @BradLaney: Which is funny, because the above output is copy & paste from my box (WinXP / Cygwin). Testing on another box (Win7) gives me: `MS-DOS executable, NE for MS Windows 3.x (driver)` for "Windows\system\COMMDLG.DLL", `PE32 executable for MS Windows (DLL) (GUI) Intel 80386 32-bit` for "Program Files\Internet Explorer\iecompat.dll"... at which point I stopped testing and still claim that, if you all you get is "mono", either you only tested Mono assemblies, or your Cygwin installation is borked. – DevSolar May 01 '12 at 11:20
  • 9
    Example output for a 64-bit DLL: `boost_math_c99f-vc140-mt-1_58.dll: PE32+ executable (DLL) (console) x86-64, for MS Windows` – Nick Jul 01 '15 at 17:29
  • 1
    If you've got *Git for Windows* installed, the `file` command available from the *Git Bash* console will work instead of Cygwin. – Leif Gruenwoldt May 11 '17 at 17:58
  • mingw/msys "file" command works for dll and exe files: exe:: Wireshark/WinPcap_4_1_3.exe: PE32 executable (GUI) Intel 80386, for MS Windows, Nullsoft Installer self-extracting archive Wireshark/Wireshark.exe: PE32+ executable (GUI) x86-64, for MS Windows dll: Win10Pcap/Win32/wpcap.dll: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows Win10Pcap/x64/wpcap.dll: PE32+ executable (DLL) (GUI) x86-64, for MS Windows – Don Mclachlan Jul 17 '17 at 18:01
  • GitBash for windows works as well. Example:- `$ file jniwrap.dll jniwrap.dll: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows ` – Paramvir Singh Karwal Jun 18 '18 at 13:47
  • WSL for windows 10 + ubuntu, you can use the same command – Thesane Aug 23 '18 at 01:13
  • you can also use `7z l *.dll | findstr CPU` – phuclv Sep 21 '18 at 17:01
  • FILE HEADER VALUES 8664 machine (x64) OPTIONAL HEADER VALUES 20B magic # (PE32+) which is it ? – oOo Jul 31 '22 at 12:44
  • @oOo You meant to write this comment under Jeremy's answer. – DevSolar Jul 31 '22 at 12:52
116

Gory details

A DLL uses the PE executable format, and it's not too tricky to read that information out of the file.

See this MSDN article on the PE File Format for an overview. You need to read the MS-DOS header, then read the IMAGE_NT_HEADERS structure. This contains the IMAGE_FILE_HEADER structure which contains the info you need in the Machine member which contains one of the following values

  • IMAGE_FILE_MACHINE_I386 (0x014c)
  • IMAGE_FILE_MACHINE_IA64 (0x0200)
  • IMAGE_FILE_MACHINE_AMD64 (0x8664)

This information should be at a fixed offset in the file, but I'd still recommend traversing the file and checking the signature of the MS-DOS header and the IMAGE_NT_HEADERS to be sure you cope with any future changes.

Use ImageHelp to read the headers...

You can also use the ImageHelp API to do this - load the DLL with LoadImage and you'll get a LOADED_IMAGE structure which will contain a pointer to an IMAGE_NT_HEADERS structure. Deallocate the LOADED_IMAGE with ImageUnload.

...or adapt this rough Perl script

Here's rough Perl script which gets the job done. It checks the file has a DOS header, then reads the PE offset from the IMAGE_DOS_HEADER 60 bytes into the file.

It then seeks to the start of the PE part, reads the signature and checks it, and then extracts the value we're interested in.

#!/usr/bin/perl
#
# usage: petype <exefile>
#
$exe = $ARGV[0];

open(EXE, $exe) or die "can't open $exe: $!";
binmode(EXE);
if (read(EXE, $doshdr, 64)) {

   ($magic,$skip,$offset)=unpack('a2a58l', $doshdr);
   die("Not an executable") if ($magic ne 'MZ');

   seek(EXE,$offset,SEEK_SET);
   if (read(EXE, $pehdr, 6)){
       ($sig,$skip,$machine)=unpack('a2a2v', $pehdr);
       die("No a PE Executable") if ($sig ne 'PE');

       if ($machine == 0x014c){
            print "i386\n";
       }
       elsif ($machine == 0x0200){
            print "IA64\n";
       }
       elsif ($machine == 0x8664){
            print "AMD64\n";
       }
       else{
            printf("Unknown machine type 0x%lx\n", $machine);
       }
   }
}

close(EXE);
phuclv
  • 37,963
  • 15
  • 156
  • 475
Paul Dixon
  • 295,876
  • 54
  • 310
  • 348
  • 6
    Very handy. I created a Python translation of your code: https://github.com/tgandor/meats/blob/master/missing/arch_of.py – Tomasz Gandor Sep 29 '16 at 09:48
  • @TomaszGandor great stuff. FYI, I had to change `'MZ'` and `'PE'` to `b'MZ'` and `b'PE'` to get those if's to evaluate properly. Not sure if it was a platform-specific issue or what. – valverij Oct 07 '16 at 20:46
  • No, it just means that you have Python 3.x ;) Thanks, fixed on GitHub. I'm migrating to 3.x reluctantly (writing on 2.7, trying to be forward-compatible). And so I sometimes forget, that files opened with `'rb'` mode return binary strings like `b'MZ'` (on Py2 `bytes` is just the default `str`, and Py3's `str` is `unicode`). – Tomasz Gandor Oct 07 '16 at 22:10
  • Just to clarify... The `offset` should always be a positive number? Considering the file hasn't been corrupted... – cameck Feb 07 '18 at 17:23
  • 1
    Well the example there unpacks it as a signed value - you could probably interpret is as unsigned but that would mean you've got a *very* large offset there. I think that would be unusual, but you should be able to verify if an unsigned offset is correct by finding 'PE' at the offset – Paul Dixon Feb 07 '18 at 18:55
  • Thanks for responding, that is helpful! The reason I ask is a header I unpacked had the value of `-1074807361`, as the offset. When I tried to set the file position to that it returned `Invalid argument @ rb_io_set_pos`. So to create a guard I could ensure it's a positive number or catch on that error. Just wasn't sure if ensuring it's a positive number would break in other cases i.e. is the "PE" always offset in a "forward position" from the mz header? – cameck Feb 07 '18 at 19:12
  • if you interpreted that as unsigned, it would be 3220159935, so unless you've got a 3GB EXE, something is probably wrong (either the file or your code). The MZ header is what makes the PE file executable in DOS - it would just display a "this requires windows" message, but Windows knows to look past this. Get a hex dump of the first 100 bytes or so and stick on pastebin! – Paul Dixon Feb 07 '18 at 22:11
  • https://pastebin.com/SZ1E4VgX – cameck Feb 08 '18 at 18:47
  • 1
    It starts with MZ but other than that it looks like garbage. Here's how you'd expect it to look https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files#MS-DOS_header – Paul Dixon Feb 09 '18 at 12:19
  • Thanks a lot for your help! And yes that file is garbage. So, I adapted this [ruby script](https://gist.github.com/rednaxelafx/5577012) and added that the offset should be greater than/equal to 0. – cameck Feb 09 '18 at 16:04
49

Dependency Walker tells all(well almost). http://www.dependencywalker.com/

It does not "install" -just get it, extract it and run the exec. It works for any x32 or x64 windows module|application.

As I recall it is fairly straightforward to see all dependencies, i.e. the dll modules, and since the appl. is a sum of the dependencies one can ascertain if it is full x64, x32(x86) or a bit of each.

Type of CPU that the module was built for is in the "CPU" column. Most 64-bit aps are still a bit of each but 32-bit ap w/b all x86.

Beautiful program for geeks/programmers and it is free...

Ric
  • 499
  • 4
  • 2
  • 5
    Dependency Walker doesn't seem to work on .NET .dll or .exe files. Did a quick test with 32-bit and 64-bit .NET console applications, and it couldn't tell the difference. – Contango Mar 02 '11 at 12:21
  • 4
    @Gravitas For .Net files you need to use CorFlags.exe – expert Aug 21 '12 at 23:01
  • The download links on the website are broken. – Jonathan Parker Nov 13 '13 at 00:48
  • 2
    If I do this job, I will simply let the user upload his/her dll file and display information, without download any executables and install it and run it. – user2384994 May 20 '14 at 05:16
  • 1
    If the file is 64-bit, there will be an icon with a little 64 at the right. Be careful to look at the file of interest, not its dependencies because if you are using the x64 version of Depends, it will show 64-bit dependencies for a 32-bit module (with a red icon and an error: Modules with different CPU types were found). – Maxence Dec 18 '15 at 10:31
  • That's not a good solution as Contango explained in a previous comment. –  Feb 01 '19 at 13:25
47

I have written a very simple tool that does exactly that - it's called PE Deconstructor.

Simply fire it up and load your DLL file:

enter image description here

In the example above, the loaded DLL is 32-bit.

You can download it here (I only have the 64-bit version compiled ATM):
https://files.quickmediasolutions.com/exe/pedeconstructor_0.1_amd64.exe

An older 32-bit version is available here:
https://dl.dropbox.com/u/31080052/pedeconstructor.zip

Nathan Osman
  • 71,149
  • 71
  • 256
  • 361