Lazarus

Programming => Operating Systems => Windows => Topic started by: Zoran on May 11, 2017, 08:33:28 am

Title: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: Zoran on May 11, 2017, 08:33:28 am
In a program I search for a dll in some standard paths in file system.

When the program finds the dll with the name I want, is there a way to detect whether this dll is 64-bit or 32-bit before trying to load it dynamically (so I can skip it if it doesn't match the program bitness)?
Title: Re: How to tell if dll is 32-bit or 64-bit
Post by: rvk on May 11, 2017, 09:24:16 am
Besides the fact there shouldn't be mismatching DLL in your path...

You can find the answer here:
How to determine if dll file was compiled as x64 or x86 bit using either Delphi or Lazarus (http://stackoverflow.com/questions/2748474/how-to-determine-if-dll-file-was-compiled-as-x64-or-x86-bit-using-either-delphi)
Title: Re: How to tell if dll is 32-bit or 64-bit
Post by: balazsszekely on May 11, 2017, 10:28:21 am
Hi Zoran,

I wrote this small function a few years ago. Works both wit exe and dll files:
Code: Pascal  [Select][+][-]
  1. uses JwaWindows;
  2.  
  3. function GetPEType(const APath: WideString): Byte;
  4. const
  5.   PE_UNKNOWN = 0;
  6.   PE_16BIT   = 1;
  7.   PE_32BIT   = 2;
  8.   PE_64BIT   = 3;
  9. var
  10.   hFile, hFileMap: THandle;
  11.   PMapView: Pointer;
  12.   PIDH: PImageDosHeader;
  13.   PINTH: PImageNtHeaders;
  14.   Base: LongWord;
  15. begin
  16.   Result := PE_UNKNOWN;
  17.  
  18.   hFile := CreateFileW(PWideChar(APath), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  19.   if hFile = INVALID_HANDLE_VALUE then
  20.   begin
  21.     CloseHandle(hFile);
  22.     Exit;
  23.   end;
  24.  
  25.   hFileMap  := CreateFileMapping(hFile, nil, PAGE_READONLY, 0, 0, nil);
  26.   if hFileMap = 0 then
  27.   begin
  28.     CloseHandle(hFile);
  29.     CloseHandle(hFileMap);
  30.     Exit;
  31.   end;
  32.  
  33.   PMapView := MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
  34.   if PMapView = nil then
  35.   begin
  36.     CloseHandle(hFile);
  37.     CloseHandle(hFileMap);
  38.     Exit;
  39.   end;
  40.  
  41.   PIDH := PImageDosHeader(PMapView);
  42.   if PIDH^.e_magic <> IMAGE_DOS_SIGNATURE then
  43.   begin
  44.     CloseHandle(hFile);
  45.     CloseHandle(hFileMap);
  46.     UnmapViewOfFile(PMapView);
  47.     Exit;
  48.   end;
  49.  
  50.   Base := LongWord(PIDH);
  51.   PINTH := PIMAGENTHEADERS(Base + LongWord(PIDH^.e_lfanew));
  52.   if PINTH^.Signature <> IMAGE_NT_SIGNATURE then
  53.   begin
  54.     CloseHandle(hFile);
  55.     CloseHandle(hFileMap);
  56.     UnmapViewOfFile(PMapView);
  57.     Exit;
  58.   end;
  59.  
  60.   if PINTH^.Signature = $4550 then
  61.   begin
  62.     case PINTH^.OptionalHeader.Magic of
  63.       $10b: Result := PE_32BIT;
  64.       $20b: Result := PE_64BIT
  65.     end;
  66.   end
  67.   else
  68.     Result := PE_16BIT;
  69.  
  70.   CloseHandle(hFile);
  71.   CloseHandle(hFileMap);
  72.   UnmapViewOfFile(PMapView);
  73. end;
  74.  
  75.  
  76. procedure TForm1.Button1Click(Sender: TObject);
  77. var
  78.   PEType: Byte;
  79. begin
  80.   PEType := GetPEType(WideString('C:\mydll.dll'));
  81.   case PEType of
  82.     0: ShowMessage('unknown PE');
  83.     1: ShowMessage('16 bit PE');
  84.     2: ShowMessage('32 bit PE');
  85.     3: ShowMessage('64 bit PE');
  86.   end;
  87. end;
Title: Re: How to tell if dll is 32-bit or 64-bit
Post by: Zoran on May 11, 2017, 01:21:40 pm
Thank you both.

GetMem, I tested your function and it works well. I'll use it, thanks again.
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: Zoran on May 12, 2017, 10:01:43 am
GetMem, I added your function to Wiki: http://wiki.freepascal.org/Multiplatform_Programming_Guide#Detecting_bitness_of_external_library_before_loading_it (http://wiki.freepascal.org/Multiplatform_Programming_Guide#Detecting_bitness_of_external_library_before_loading_it)
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: balazsszekely on May 12, 2017, 10:48:55 am
Quote
GetMem, I added your function to Wiki: http://wiki.freepascal.org/Multiplatform_Programming_Guide#Detecting_bitness_of_external_library_before_loading_it
Thank you @Zoran!
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: Thaddy on May 12, 2017, 12:15:47 pm
Same problem exists on Linux btw. Not always, but on 32/64 bit mixed.
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: Zoran on May 15, 2017, 08:10:48 am
Now I noticed that compiler issues hints on these two lines:
Code: Pascal  [Select][+][-]
  1.   Base := LongWord(PIDH);
  2.   PINTH := PIMAGENTHEADERS(Base + LongWord(PIDH^.e_lfanew));
  3.  

And I don't know is there a reason why you cut Pointer to LongWord (in a 64-bit application this might lose significant bits)?

Wouldn't it be better:
Code: Pascal  [Select][+][-]
  1. var
  2.   ...
  3.   Base: Pointer;
  4. ...
  5.  Base := PIDH;
  6.  PINTH := PIMAGENTHEADERS(Base + LongWord(PIDH^.e_lfanew));
  7.  

In my test, both versions (with Base as LongWord and as Pointer) seem to work always... but...

What do you think?
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: balazsszekely on May 15, 2017, 10:31:32 am
Hi Zoran,

Quote
And I don't know is there a reason why you cut Pointer to LongWord
Initially I wrote that function for delphi, where an explicit typecast needed, otherwise the function won't compile. The error message( both with D2007 and XE4): "E2015 Operator not applicable to this operand type".

Quote
Now I noticed that compiler issues hints on these two lines:
Because we casting a pointer to a fixed size variable. The compiler knows that the size of pointers can have different values on different platforms, while the size of the variable stays the same(or not, it depends on a lot of things), hence the warning, nothing to worry about in my opinion.

Quote
in a 64-bit application this might lose significant bits
You have a valid point here, Longword is enough for a 32 bit system, but NativeUInt(PtrUint) or QWord is more appropriate for 64 bit. Your pointer alternative is also good if you don't mind dropping the delphi compatibility support.

PS: I also find this: https://0x2a.wtf/projects/pesp . Supports both Windows and Linux. It's pure pascal. You get a lot more info then bitness: Import/Export tables, Entry Point, etc...This is also an answer to @Thaddy's reply.
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: Zoran on May 17, 2017, 10:00:15 am
Okay, thanks.
I updated the wiki to use Pointer.

Now, another thing, this function can never return PE_16BIT, take a look at these lines (see added comments):
Code: Pascal  [Select][+][-]
  1. ...
  2.  if PINTH^.Signature <> IMAGE_NT_SIGNATURE then // this is asked again later in code, to return 16-bit
  3.  begin
  4.    CloseHandle(hFile);
  5.    CloseHandle(hFileMap);
  6.    UnmapViewOfFile(PMapView);
  7.    Exit;
  8.  end;
  9.  
  10.  if PINTH^.Signature = $4550 then // $4550 is same as IMAGE_NT_SIGNATURE, which was already tested above.
  11.  begin
  12.    case PINTH^.OptionalHeader.Magic of
  13.      $10b: Result := PE_32BIT;
  14.      $20b: Result := PE_64BIT
  15.    end;
  16.  end
  17.  else
  18.    Result := PE_16BIT; // so, this line will never execute!!!!!
  19.  
  20.  CloseHandle(hFile);
  21.  CloseHandle(hFileMap);
  22.  UnmapViewOfFile(PMapView);  
  23. end; // the function ends here.
  24.  

Personally, I don't need 16-bit testing, but the wiki should be corrected. Either by removing the first test which prevents returning 16-bit, or by removing 16 bit result. What do you think?
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: balazsszekely on May 17, 2017, 10:14:19 am
Quote
Now, another thing, this function can never return PE_16BIT, take a look at these lines (see added comments):
You're correct! It's a logical error, thank you for noticing it. Feel free to modify the code anyway you like, personally I don't think 16 bit support is needed nowadays.
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: Zoran on May 17, 2017, 01:02:19 pm
Quote
Now, another thing, this function can never return PE_16BIT, take a look at these lines (see added comments):
You're correct! It's a logical error, thank you for noticing it. Feel free to modify the code anyway you like, personally I don't think 16 bit support is needed nowadays.

Thanks. I updated the wiki.
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: ASerge on May 19, 2017, 11:16:02 pm
Multiple CloseHandle calls can be excluded:
Code: Pascal  [Select][+][-]
  1. uses Windows;
  2.  
  3. type
  4.   TPEBitness = (pebUnknown, peb16, peb32, peb64);
  5.  
  6. function GetPEBitness(const APath: WideString): TPEBitness;
  7. const
  8.   IMAGE_NT_OPTIONAL_HDR32_MAGIC = $10b;
  9.   IMAGE_NT_OPTIONAL_HDR64_MAGIC = $20b;
  10. var
  11.   HFile, HFileMap: THandle;
  12.   PMapView: Pointer;
  13.   PIDH: PImageDosHeader;
  14.   PINTH: PImageNtHeaders;
  15. begin
  16.   Result := pebUnknown;
  17.   HFile := CreateFileW(PWideChar(APath), GENERIC_READ, FILE_SHARE_READ, nil,
  18.     OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  19.   if HFile = INVALID_HANDLE_VALUE then
  20.     Exit;
  21.   HFileMap  := CreateFileMapping(HFile, nil, PAGE_READONLY, 0, 0, nil);
  22.   CloseHandle(HFile);
  23.   if HFileMap = 0 then
  24.     Exit;
  25.   PMapView := MapViewOfFile(HFileMap, FILE_MAP_READ, 0, 0, 0);
  26.   CloseHandle(HFileMap);
  27.   if PMapView = nil then
  28.     Exit;
  29.   PIDH := PImageDosHeader(PMapView);
  30.   if PIDH^.e_magic = IMAGE_DOS_SIGNATURE then
  31.   begin
  32.     PINTH := PImageNtHeaders(PAnsiChar(PMapView) + PIDH^._lfanew);
  33.     if PINTH^.Signature <> IMAGE_NT_SIGNATURE then
  34.       Result := peb16
  35.     else
  36.       case PINTH^.OptionalHeader.Magic of
  37.         IMAGE_NT_OPTIONAL_HDR32_MAGIC:
  38.           Result := peb32;
  39.         IMAGE_NT_OPTIONAL_HDR64_MAGIC:
  40.           Result := peb64;
  41.       end;
  42.   end;
  43.   UnmapViewOfFile(PMapView);
  44. end;
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: Zoran on May 20, 2017, 12:03:44 am

Thank you, ASerge. Are you sure that it works well for 16-bit. Are you sure that it will not in some case return peb16 when it is not a valid 16-bit dll?

I'm asking because in the first version (see this post (http://forum.lazarus.freepascal.org/index.php/topic,36834.msg246442.html#msg246442)), there was a protective block, and I'm affraid that it was maybe inserted when the function returned 16-bit when it was not a valid 16-bit dll (I don't know, but it just looks like that...).
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: ASerge on May 20, 2017, 01:04:37 am
Are you sure that it works well for 16-bit
if PIDH^.e_magic = IMAGE_DOS_SIGNATURE then // Executable
  if PINTH^.Signature <> IMAGE_NT_SIGNATURE then // But for NT
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: ASerge on May 20, 2017, 01:19:49 am
Test
Code: Pascal  [Select][+][-]
  1. program Project1;
  2. {$APPTYPE CONSOLE}
  3.  
  4. // Code above paste here
  5.  
  6. function PEBitnessAsText(V: TPEBitness): string;
  7. const
  8.   CValue: array [TPEBitness] of string = (
  9.     'Unknown', '16 bit', '32 bit', '64 bit');
  10. begin
  11.   Result := CValue[V];
  12. end;
  13.  
  14. procedure WritelnBitness(const FileName: WideString);
  15. begin
  16.   Writeln(FileName, ' is ', PEBitnessAsText(GetPEBitness(FileName)));
  17. end;
  18.  
  19. const
  20.   CDlls: array [1..5] of WideString = (
  21.     'c:\Windows\SysWOW64\netmsg.dll',
  22.     'c:\Windows\System32\netmsg.dll',
  23.     'c:\Windows\Sysnative\netmsg.dll',
  24.     'c:\Windows\twain.dll',
  25.     'c:\Windows\twain_32.dll');
  26. var
  27.   i: Integer;
  28. begin
  29.   for i := Low(CDlls) to High(CDlls) do
  30.     WritelnBitness(CDlls[i]);
  31.   Readln;
  32. end.

Result with 64 compiler:
c:\Windows\SysWOW64\netmsg.dll is 32 bit
c:\Windows\System32\netmsg.dll is 64 bit
c:\Windows\Sysnative\netmsg.dll is Unknown
c:\Windows\twain.dll is 16 bit
c:\Windows\twain_32.dll is 32 bit

Result with 32 compiler:
c:\Windows\SysWOW64\netmsg.dll is 32 bit
c:\Windows\System32\netmsg.dll is 32 bit
c:\Windows\Sysnative\netmsg.dll is 64 bit
c:\Windows\twain.dll is 16 bit
c:\Windows\twain_32.dll is 32 bit
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: balazsszekely on May 20, 2017, 08:09:54 am
Quote
@Zoran
I'm asking because in the first version (see this post), there was a protective block, and I'm affraid that it was maybe inserted when the function returned 16-bit when it was not a valid 16-bit dll (I don't know, but it just looks like that...).
No, that was a logical error. My original code would fail to detect a 16 bit PE as you correctly pointed out in one of your previous post. I don't think it's a way to detect a corrupted 16/32/64 PE other then checking if a valid  IMAGE_DOS_SIGNATURE,  IMAGE_NT_SIGNATURE, etc... exist. I meant PE_UNKNOWN for special cases when the path to file is invalid or the memory map could not be crated or something else.

Quote
@ASerge
Multiple CloseHandle calls can be excluded.
Yes, your code is more compact, however I prefer a slightly verbose approach. It's much easier this way  to figure out what the code does at a later time. For example instead of:
Code: Pascal  [Select][+][-]
  1. if (Something) then
  2.   if (SomethingElse) then
  3.     DoThis
  4.   else
  5.     DoThat;
I always do:
Code: Pascal  [Select][+][-]
  1. if (Something) then
  2. begin
  3.   if (SomethingElse) then
  4.     DoThis
  5.   else
  6.     DoThat;
  7. end;
Which is code redundancy, but visually is more appealing to me, but I guess is just a personal preference.
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: Thaddy on May 20, 2017, 09:10:44 am
That's why I like editors with a line length of at least 1024. So I definitely know that my Pascal code intended to have a linebreak and not white  :o 8-) space, but I guess is just a personal preference.  >:( ;D
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: ASBzone on February 07, 2019, 02:16:15 am
Test

Result with 64 compiler:
c:\Windows\SysWOW64\netmsg.dll is 32 bit
c:\Windows\System32\netmsg.dll is 64 bit
c:\Windows\Sysnative\netmsg.dll is Unknown
c:\Windows\twain.dll is 16 bit
c:\Windows\twain_32.dll is 32 bit

Result with 32 compiler:
c:\Windows\SysWOW64\netmsg.dll is 32 bit
c:\Windows\System32\netmsg.dll is 32 bit
c:\Windows\Sysnative\netmsg.dll is 64 bit
c:\Windows\twain.dll is 16 bit
c:\Windows\twain_32.dll is 32 bit


Thanks again for this routine, ASerge.  It has come in very handy.
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: 440bx on February 07, 2019, 04:27:10 am
My original code would fail to detect a 16 bit PE as you correctly pointed out in one of your previous post.
just FYI, there is no such thing as a 16bit PE. 

twain.dll is 16bit but, it is not a PE file.  It is an NE file. 

Also,

In a PE file, the first DWORD of the IMAGE_NT_HEADERS is "PE00" (in string form - $00004550 in binary form).  In an NE file, at the same location, there is a WORD (not a dword) with the signature "NE".  See attached screenshot for a "dump" of twain.dll's header.

It's "risky" to conclude the DLL is 16 bit without checking that its signature is "NE".

It would probably be wise too to ensure that
Code: Pascal  [Select][+][-]
  1. PINTH := PImageNtHeaders(PAnsiChar(PMapView) + PIDH^._lfanew);
is readable before dereferencing it just in case the dll is corrupted.  The same applies to PIDH (after all, if the dll is corrupted, it could have a file size of zero.)

The magic value "IMAGE_ROM_OPTIONAL_HDR_MAGIC  0x107" is known/valid.  For that value the code will return pebUnknown, which is reasonable since it is quite unlikely that an application would want to load such a file but, strictly speaking, it is not "unknown".

The above comments are on sale for $0.02 ;)

ETA:

@Serge:

Do you know why the result is different for c:\Windows\Sysnative\netmsg.dll ?  the 64bit version says "unknown" and the 32bit version says 64 bit.  Do you know if Windows is playing some kind of trick on 32bit executables when they look at files in the Sysnative directory or is there some other reason for the difference ?
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: rvk on February 07, 2019, 06:30:56 am
Do you know why the result is different for c:\Windows\Sysnative\netmsg.dll ?  the 64bit version says "unknown" and the 32bit version says 64 bit.  Do you know if Windows is playing some kind of trick on 32bit executables when they look at files in the Sysnative directory or is there some other reason for the difference ?
The Sysnative doesn't really exist. It's a redirection which is only available in 32 bit processes. So not available in 64 bit.

Also see http://www.samlogic.net/articles/sysnative-folder-64-bit-windows.htm for an explanation.

And
Quote
Note that 64-bit applications cannot use the Sysnative alias as it is a virtual directory not a real one.
https://docs.microsoft.com/nl-nl/windows/desktop/WinProg64/file-system-redirector
Title: Re: [SOLVED] How to tell if dll is 32-bit or 64-bit
Post by: 440bx on February 07, 2019, 08:25:31 am
The Sysnative doesn't really exist. It's a redirection which is only available in 32 bit processes. So not available in 64 bit.
Thank you.    :)

I had read the documentation about file system redirection but, I did not encounter a reference to "sysnative" unitl now.  Good to know the tricks MS has pulled out of its sleeve and thrown in the binary bucket.


TinyPortal © 2005-2018