Bug 30359 - Create Resource-Only DLL
Summary: Create Resource-Only DLL
Status: RESOLVED WONTFIX
Alias: None
Product: binutils
Classification: Unclassified
Component: ld (show other bugs)
Version: 2.39
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2023-04-15 23:23 UTC by Pali Rohár
Modified: 2023-05-16 16:28 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:


Attachments
Example linker script (324 bytes, text/plain)
2023-05-04 14:20 UTC, Nick Clifton
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Pali Rohár 2023-04-15 23:23:27 UTC
Resource-Only DLL is windows PE binary of DLL type which does not contain any code, just resources in .rsrc section. See MS documentation for more details:
https://learn.microsoft.com/en-us/cpp/build/creating-a-resource-only-dll

To create Resource-Only TEST-RSRC.DLL library from text resource file TEST-RSRC.RC (compiled to intermediate binary TEST-RSRC.RES) it is possible to use MS tools (as documented in above link):

  RC.EXE TEST-RSRC.RC
  LINK.EXE /DLL /NOENTRY TEST-RSRC.RES

Entrypoint of such library is set to zero.

I'm trying to achieve same thing via binutils, but even with stripped debug symbols and zero entry point, LD somehow always puts .idata and .text sections with empty import table and something in code .text sections.

Test case:

$ cat test-rsrc.rc
1 VERSIONINFO
BEGIN
END

$ i686-w64-mingw32-windres --input-format=rc --output-format=coff --input=test-rsrc.rc --output=test-rsrc.o

$ i686-w64-mingw32-ld -dll --subsystem windows -e 0 -s test-rsrc.o -o test-rsrc.dll

$ i686-w64-mingw32-objdump -h test-rsrc.dll

test-rsrc.dll:     file format pei-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000010  10001000  10001000  00000400  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .idata        00000014  10002000  10002000  00000600  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .rsrc         000000b8  10003000  10003000  00000800  2**2
                  CONTENTS, ALLOC, LOAD, DATA


Is there some other flag for LD to achieve generation of resource-only DLL library? Or how to avoid putting (empty) .text and .idata sections into such DLL library?
Comment 1 Nick Clifton 2023-04-18 10:31:08 UTC
(In reply to Pali Rohár from comment #0)
Hi Pali,

> I'm trying to achieve same thing via binutils, but even with stripped debug
> symbols and zero entry point, LD somehow always puts .idata and .text
> sections with empty import table and something in code .text sections.

*sigh* Yes this is a "feature" of the linker.  It might be possible to hack
the linker to change the behaviour, but I think that there is a simpler 
solution:

> $ i686-w64-mingw32-windres --input-format=rc --output-format=coff
> --input=test-rsrc.rc --output=test-rsrc.o
> 
> $ i686-w64-mingw32-ld -dll --subsystem windows -e 0 -s test-rsrc.o -o
> test-rsrc.dll
> 
> $ i686-w64-mingw32-objdump -h test-rsrc.dll
> 
> test-rsrc.dll:     file format pei-i386
> 
> Sections:
> Idx Name          Size      VMA       LMA       File off  Algn
>   0 .text         00000010  10001000  10001000  00000400  2**2
>                   CONTENTS, ALLOC, LOAD, READONLY, CODE
>   1 .idata        00000014  10002000  10002000  00000600  2**2
>                   CONTENTS, ALLOC, LOAD, DATA
>   2 .rsrc         000000b8  10003000  10003000  00000800  2**2
>                   CONTENTS, ALLOC, LOAD, DATA

But if you add....

  $ i686-w64-mingw32-objcopy --remove-section .text --remove-section .idata test-rsrc.dll test-rsrc-2.dll

then...

  $ i686-w64-mingw32-objdump -h test-rsrc-2.dll

test-rsrc-2.dll:     file format pei-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .rsrc         000000b8  10003000  10003000  00000200  2**2
                  CONTENTS, ALLOC, LOAD, DATA

I realise that this means adding another step to the process, but it also
means that the workaround will work with the current tools, no need for any
updates.
Comment 2 Pali Rohár 2023-04-18 17:30:07 UTC
If adding empty import table and something in code .text sections is feature of the linker, it is somewhere documented? What it is doing and what is putting there (as those sections are not in any user supplied input file)? And I'm interesting why it is doing, it is some requirement for some SW? Just I'm currious here.

And about objcopy --remove-section workaround, this is problematic because for automatization, it is needed to know what sections has to be removed - meaning ability to ask LD what additional section it added there.
Comment 3 Nick Clifton 2023-04-19 10:44:00 UTC
(In reply to Pali Rohár from comment #2)
> If adding empty import table and something in code .text sections is feature
> of the linker, it is somewhere documented?

No. :-(

> What it is doing and what is
> putting there (as those sections are not in any user supplied input file)?

The (builtin, default) linker script is the cause.

> And I'm interesting why it is doing, it is some requirement for some SW?
> Just I'm currious here.

The PE linker script automatically puts some constant values into the .text
section as markers between the normal code and the constructors and destructors.  If you run "ld --verbose" you will see this in the details for the .text section:

    ...
       LONG (-1); LONG (-1);
       KEEP (*(.ctors));
       KEEP (*(.ctor));
       KEEP (*(SORT_BY_NAME(.ctors.*)));
       LONG (0); LONG (0);
       /* See comment about __CTOR_LIST__ above.  The same reasoning
    	  applies here too.  */
       ___DTOR_LIST__ = .;
       __DTOR_LIST__ = .;
       LONG (-1); LONG (-1);
       KEEP (*(.dtors));
       KEEP (*(.dtor));
       KEEP (*(SORT_BY_NAME(.dtors.*)));
       LONG (0); LONG (0);
    ...

These markers are used by the execution startup code to help find the two tables.


> And about objcopy --remove-section workaround, this is problematic because
> for automatization, it is needed to know what sections has to be removed -
> meaning ability to ask LD what additional section it added there.

  In which case, just use --only-section instead.  ie:

  $ objcopy --only-section=.rsrc test-rsrc.dll test-rsrc.3.dll
  $ objdump -h test-rsrc.3.dll
  test-rsrc.3.dll:     file format pei-i386

  Sections:
  Idx Name          Size      VMA       LMA       File off  Algn
    0 .rsrc         000000b8  10003000  10003000  00000200  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
Comment 4 Pali Rohár 2023-04-21 15:53:27 UTC
(In reply to Nick Clifton from comment #3)
> (In reply to Pali Rohár from comment #2)
> > If adding empty import table and something in code .text sections is feature
> > of the linker, it is somewhere documented?
> 
> No. :-(
> 
> > What it is doing and what is
> > putting there (as those sections are not in any user supplied input file)?
> 
> The (builtin, default) linker script is the cause.

Ok!

> > And I'm interesting why it is doing, it is some requirement for some SW?
> > Just I'm currious here.
> 
> The PE linker script automatically puts some constant values into the .text
> section as markers between the normal code and the constructors and
> destructors.  If you run "ld --verbose" you will see this in the details for
> the .text section:
> 
>     ...
>        LONG (-1); LONG (-1);
>        KEEP (*(.ctors));
>        KEEP (*(.ctor));
>        KEEP (*(SORT_BY_NAME(.ctors.*)));
>        LONG (0); LONG (0);
>        /* See comment about __CTOR_LIST__ above.  The same reasoning
>     	  applies here too.  */
>        ___DTOR_LIST__ = .;
>        __DTOR_LIST__ = .;
>        LONG (-1); LONG (-1);
>        KEEP (*(.dtors));
>        KEEP (*(.dtor));
>        KEEP (*(SORT_BY_NAME(.dtors.*)));
>        LONG (0); LONG (0);
>     ...
> 
> These markers are used by the execution startup code to help find the two
> tables.

I see... But in this case there is nothing in .text section, just those markers. Does not linker script language support conditions to put markers only when at least one of those input sections is non-empty? So when input for that section is completely empty then also output section would be empty.

> 
> > And about objcopy --remove-section workaround, this is problematic because
> > for automatization, it is needed to know what sections has to be removed -
> > meaning ability to ask LD what additional section it added there.
> 
>   In which case, just use --only-section instead.  ie:
> 
>   $ objcopy --only-section=.rsrc test-rsrc.dll test-rsrc.3.dll
>   $ objdump -h test-rsrc.3.dll
>   test-rsrc.3.dll:     file format pei-i386
> 
>   Sections:
>   Idx Name          Size      VMA       LMA       File off  Algn
>     0 .rsrc         000000b8  10003000  10003000  00000200  2**2
>                   CONTENTS, ALLOC, LOAD, READONLY, DATA

This looks better. objcopy needs to be called also with -S to drop symbols which that LD script added.

But still this --only-section does not remove .idata completelly. There is still reference to (now removed) import table and objdump prints warning about it. Visible also in readpe.

$ readpe test-rsrc.3.dll | grep -A 6 'Data directories'
export directory not found
Data directories
    Directory
        IMAGE_DIRECTORY_ENTRY_IMPORT:    0x2000 (20 bytes)
    Directory
        IMAGE_DIRECTORY_ENTRY_RESOURCE:  0x3000 (184 bytes)
Imported functions
Sections

$ i686-w64-mingw32-objdump -p test-rsrc.3.dll | grep -A 19 'The Data Directory'
The Data Directory
Entry 0 00000000 00000000 Export Directory [.edata (or where ever we found it)]
Entry 1 00002000 00000014 Import Directory [parts of .idata]
Entry 2 00003000 000000b8 Resource Directory [.rsrc]
Entry 3 00000000 00000000 Exception Directory [.pdata]
Entry 4 00000000 00000000 Security Directory
Entry 5 00000000 00000000 Base Relocation Directory [.reloc]
Entry 6 00000000 00000000 Debug Directory
Entry 7 00000000 00000000 Description Directory
Entry 8 00000000 00000000 Special Directory
Entry 9 00000000 00000000 Thread Storage Directory [.tls]
Entry a 00000000 00000000 Load Configuration Directory
Entry b 00000000 00000000 Bound Import Directory
Entry c 00000000 00000000 Import Address Table Directory
Entry d 00000000 00000000 Delay Import Directory
Entry e 00000000 00000000 CLR Runtime Header
Entry f 00000000 00000000 Reserved

There is an import table, but the section containing it could not be found
Comment 5 Nick Clifton 2023-04-25 13:58:50 UTC
(In reply to Pali Rohár from comment #4)
 
> > These markers are used by the execution startup code to help find the two
> > tables.
> 
> I see... But in this case there is nothing in .text section, just those
> markers. Does not linker script language support conditions to put markers
> only when at least one of those input sections is non-empty?

Sadly, no.

But of course it is possible to create a copy of the linker script that does not contain these directives and then use that when creating the dll.  I think that this could also solve the other problem...

> But still this --only-section does not remove .idata completelly. There is
> still reference to (now removed) import table and objdump prints warning
> about it. Visible also in readpe.

Except - when I tried to create a simple test to verify this I ran into a problem.  The dll linked OK with my test linker script:

  $ i686-w64-mingw32-ld -dll --subsystem windows -e 0 -s test-rsrc.o -o test-rsrc.dll -T test-rsrc.ld

and it only has the .rsrc section:

  $ i686-w64-mingw32-objdump -h test-rsrc.2.dll

  test-rsrc.dll:     file format pei-i386

  Sections:
  Idx Name          Size      VMA       LMA       File off  Algn
    0 .rsrc         000000b8  00000000  00000000  00000200  2**2

but, when I checked it with objdump, it complains about the .rsrc section being corrupt:

  $ i686-w64-mingw32-objdump -p test-rsrc.2.dll

  test-rsrc.2.dll:     file format pei-i386
  [...]
  The .rsrc Resource Directory section:
  000  Type Table: Char: 0, Time: 00000000, Ver: 0/0, Num Names: 0, IDs: 1
  010   Entry: ID: 0x000010, Value: 0x80000018
  018    Name Table: Char: 0, Time: 00000000, Ver: 0/0, Num Names: 0, IDs: 1
  028     Entry: ID: 0x000001, Value: 0x80000030
  030      Language Table: Char: 0, Time: 00000000, Ver: 0/0, Num Names: 0, IDs: 1
  040       Entry: ID: 0x000409, Value: 0x000048
  048        Leaf: Addr: 0x000058, Size: 0x00005c, Codepage: 0
  Corrupt .rsrc section detected!

I think that this might be due to missing padding at the end of the section, but I am not sure.  Given that you are a windows expert, perhaps you can find out more ?

The linker script that I am using looks like this:

  $ cat  test-rsrc.ld
  /* Copyright (C) 2014-2021 Free Software Foundation, Inc.
     Copying and distribution of this script, with or without modification,
     are permitted in any medium without royalty provided the copyright
     notice and this notice are preserved.  */
  OUTPUT_FORMAT(pei-i386)
  SECTIONS
  {
    .rsrc __image_base__ : SUBALIGN(4)
    {
      KEEP (*(.rsrc))
      KEEP (*(.rsrc$*))
    }

    /DISCARD/ : { *(*) }
  }
Comment 6 Pali Rohár 2023-04-25 19:13:13 UTC
(In reply to Nick Clifton from comment #5)
> (In reply to Pali Rohár from comment #4)
>  
> > > These markers are used by the execution startup code to help find the two
> > > tables.
> > 
> > I see... But in this case there is nothing in .text section, just those
> > markers. Does not linker script language support conditions to put markers
> > only when at least one of those input sections is non-empty?
> 
> Sadly, no.

I was thinking that it could be possible to define some linker variables before and after those symbols to the current position and then do difference between values of these variables. For example:

_BEGIN_OF_DATA_ = .
KEEP(...)
KEEP(...)
_END_OF_DATA_ = .
_LENGTH_OF_DATA_ = _END_OF_DATA_ - _BEGIN_OF_DATA_

And then somehow put 

LONG (-1); LONG (-1);

only if _LENGTH_OF_DATA_ is non-zero.

This is also not possible by linker script language? Because it looks like very common thing...

> But of course it is possible to create a copy of the linker script that does
> not contain these directives and then use that when creating the dll.  I
> think that this could also solve the other problem...
> 
> > But still this --only-section does not remove .idata completelly. There is
> > still reference to (now removed) import table and objdump prints warning
> > about it. Visible also in readpe.
> 
> Except - when I tried to create a simple test to verify this I ran into a
> problem.  The dll linked OK with my test linker script:
> 
>   $ i686-w64-mingw32-ld -dll --subsystem windows -e 0 -s test-rsrc.o -o
> test-rsrc.dll -T test-rsrc.ld
> 
> and it only has the .rsrc section:
> 
>   $ i686-w64-mingw32-objdump -h test-rsrc.2.dll
> 
>   test-rsrc.dll:     file format pei-i386
> 
>   Sections:
>   Idx Name          Size      VMA       LMA       File off  Algn
>     0 .rsrc         000000b8  00000000  00000000  00000200  2**2
> 
> but, when I checked it with objdump, it complains about the .rsrc section
> being corrupt:
> 
>   $ i686-w64-mingw32-objdump -p test-rsrc.2.dll
> 
>   test-rsrc.2.dll:     file format pei-i386
>   [...]
>   The .rsrc Resource Directory section:
>   000  Type Table: Char: 0, Time: 00000000, Ver: 0/0, Num Names: 0, IDs: 1
>   010   Entry: ID: 0x000010, Value: 0x80000018
>   018    Name Table: Char: 0, Time: 00000000, Ver: 0/0, Num Names: 0, IDs: 1
>   028     Entry: ID: 0x000001, Value: 0x80000030
>   030      Language Table: Char: 0, Time: 00000000, Ver: 0/0, Num Names: 0,
> IDs: 1
>   040       Entry: ID: 0x000409, Value: 0x000048
>   048        Leaf: Addr: 0x000058, Size: 0x00005c, Codepage: 0
>   Corrupt .rsrc section detected!

That is really strange.

> I think that this might be due to missing padding at the end of the section,
> but I am not sure.  Given that you are a windows expert, perhaps you can
> find out more ?

I have looked at the output and file seems to be correct. It does not missing any padding. I have replaced simple resource file by one with large icons and I still can see this error in objdump. I have tried to use this icon-only-dll on Windows and it loaded icons from this file without any issue.

So I have feeling that generated DLL file is valid and this is bug in the binutils PE parser.

What is interesting on this DLL file is that it has .rsrc section located to VMA address 0x0:

Entry 2 00000000 000000b8 Resource Directory [.rsrc]

I'm not sure if it is fully OK but if Windows do not see any problem with it then it is acceptable.

And resource entry points to VMA address 0x000058 which is in the range 0x0-0xb8 of .rsrc:

048        Leaf: Addr: 0x000058, Size: 0x00005c, Codepage: 0

So this is also correctly generated.

I played a bit with binutils code and found out that zero VMA in peicode.h is handled specially - as ignored. I'm feeling that this rather should have been check if directory entry is available/valid. So should not it rather check non-zero size?

I have done simple change

--- peicode.h	2023-04-25 21:01:05.005927648 +0200
+++ peicode.h	2023-04-25 21:01:30.558118254 +0200
@@ -223,15 +223,15 @@ coff_swap_scnhdr_in (bfd * abfd, void *
 			 + (H_GET_16 (abfd, scnhdr_ext->s_nreloc) << 16));
   scnhdr_int->s_nreloc = 0;
 #else
   scnhdr_int->s_nreloc = H_GET_16 (abfd, scnhdr_ext->s_nreloc);
   scnhdr_int->s_nlnno = H_GET_16 (abfd, scnhdr_ext->s_nlnno);
 #endif
 
-  if (scnhdr_int->s_vaddr != 0)
+  if (scnhdr_int->s_size != 0)
     {
       scnhdr_int->s_vaddr += pe_data (abfd)->pe_opthdr.ImageBase;
       /* Do not cut upper 32-bits for 64-bit vma.  */
 #ifndef COFF_WITH_pex64
       scnhdr_int->s_vaddr &= 0xffffffff;
 #endif
     }

And with it, objdump that generated DLL accepted.

But I really do not know if the issue is that LD put .rsrc to VMA address 0x0 or that OBJDUMP does not correctly re-calculate address of sections with VMA 0x0.
Comment 7 Pali Rohár 2023-04-25 20:15:12 UTC
Or maybe instead of "if (scnhdr_int->s_vaddr != 0)"
it should be "#ifdef COFF_IMAGE_WITH_PE"?
Comment 8 Nick Clifton 2023-04-26 15:19:34 UTC
(In reply to Pali Rohár from comment #6)
Hi Pali,

 
> And then somehow put 
> 
> LONG (-1); LONG (-1);
> 
> only if _LENGTH_OF_DATA_ is non-zero.
> 
> This is also not possible by linker script language? Because it looks like
> very common thing...

I agree that it would be useful, but sadly conditional linkage is just not supported by the linker script syntax.


> So I have feeling that generated DLL file is valid and this is bug in the
> binutils PE parser.

Yes, I am beginning to suspect this as well. :-(


> I played a bit with binutils code and found out that zero VMA in peicode.h
> is handled specially - as ignored.

If I remember correctly this is because no normal PE section has a VMA of zero.  Or rather the only sections that have a VMA of 0 are debugging sections and other non-execution related sections.  I am a little bit surprised that Windows did not complain about the .rsrc section having a VMA of 0, but if it likes it then I am not going to worry.

> -  if (scnhdr_int->s_vaddr != 0)
> +  if (scnhdr_int->s_size != 0)
>      {
>        scnhdr_int->s_vaddr += pe_data (abfd)->pe_opthdr.ImageBase;
 
This would be wrong, I think, as it would cause debugging sections to end up with non-0 load addresses, which I am pretty sure is a bad thing.

Cheers
  Nick
Comment 9 Pali Rohár 2023-04-26 23:23:29 UTC
That check s_size != 0 does not really look good. I have quickly scanned wine and reactos source codes and seems that they require non-zero VMA addresses for resources. Maybe another check could be based on existance of IMAGE_SCN_MEM_DISCARDABLE flag?

Well, this zero VMA for resource section is edge case. Why is LD generating it? I think that this is what should be fixed.
Comment 10 Pali Rohár 2023-05-03 20:16:19 UTC
Checking IMAGE_SCN_MEM_DISCARDABLE is not a good idea too as this section is still loaded from .sys drivers and discarded after driver is initialized.
Comment 11 Nick Clifton 2023-05-04 14:20:57 UTC
Created attachment 14861 [details]
Example linker script

Hi Pali,

  OK, so the problem was the linker script that I was using, which was
  not initialising the starting VMA address in the same way as the 
  default linker script.  After I fixed that, the script appears to
  work.  Please give it a go and let me know if it works for you too.
  (I have uploaded it to this PR).

  Assuming that it does work, are you happy to have this as a solution
  to the problem ?  It does mean that you will have a fix that will work
  with the current linker (or earlier versions), instead of having to
  wait for the release of a new linker.

Cheers
  Nick
Comment 12 Pali Rohár 2023-05-08 09:35:23 UTC
This new linker script works fine.
Comment 13 Nick Clifton 2023-05-09 10:59:16 UTC
(In reply to Pali Rohár from comment #12)
> This new linker script works fine.

Great - then I am going to close this PR.

I really like fixes that do not involve changing anything and that will work with older versions of the linker...

Cheers
  Nick
Comment 14 Pali Rohár 2023-05-10 17:51:27 UTC
Well, if this is the way how it should be used then "fixed" linker script should be distributed with linker. Or at least described in the LD documentation. Because it is really hard to find some information or figure out how to use it. Bugzilla is not really a good place for documentation.

And what about other bugs discovered in this bug report? There is a problem in LD that it can generate section to zero VMA address.
Comment 15 Nick Clifton 2023-05-16 14:50:32 UTC
(In reply to Pali Rohár from comment #14)
> Well, if this is the way how it should be used then "fixed" linker script
> should be distributed with linker. Or at least described in the LD
> documentation. Because it is really hard to find some information or figure
> out how to use it. Bugzilla is not really a good place for documentation.

OK, I can put it into the documentation.

> And what about other bugs discovered in this bug report? There is a problem
> in LD that it can generate section to zero VMA address.

That was not a linker bug, but rather a mistake in the linker script that I was using at the time.  The linker was doing what it was told to do.
Comment 16 Sourceware Commits 2023-05-16 15:05:24 UTC
The master branch has been updated by Nick Clifton <nickc@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=d1792f72bf92ac06be7a4785567e2c7bf78c0496

commit d1792f72bf92ac06be7a4785567e2c7bf78c0496
Author: Nick Clifton <nickc@redhat.com>
Date:   Tue May 16 16:04:58 2023 +0100

    Document how to use the linker to create a resource only DLL.
    
      PR 30359
      * ld.texi (WIN32): Document how to create a resource only DLL.
Comment 17 Pali Rohár 2023-05-16 16:28:02 UTC
Thanks!