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?
(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.
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.
(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
(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
(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/ : { *(*) } }
(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.
Or maybe instead of "if (scnhdr_int->s_vaddr != 0)" it should be "#ifdef COFF_IMAGE_WITH_PE"?
(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
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.
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.
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
This new linker script works fine.
(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
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.
(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.
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.
Thanks!