Is there an option to GNU ld to omit -dynamic-linker (PT_INTERP) completely?

Expanding on my earlier note as this doesn't fit in that puny box (and this is just as an idea or discussion, please do not feel obligated to accept or reward bounty), perhaps the easiest and cleanest way of doing this is to juts add a post-build step to strip the PT_INTERP header from the resulting binary?

Even easier than manually editing the headers and potentially having to shift everything around is to just replace PT_INTERP with PT_NULL. I don't know whether you can find a way of simply patching the file via existing tools (some sort of scriptable hex find and replace) or if you'll have to write a small program to do that. I do know that libbfd (the GNU Binary File Descriptor library) might be your friend in the latter case, as it'll make that entire business a lot easier.

I guess I just don't understand why it's important to have this performed via an ld option. If available, I can see why it would be preferable; but as some (admittedly light) Googling indicates there isn't such a feature, it might be less of a hassle to just do it separately and after-the-fact. (Perhaps adding the flag to ld is easier than scripting the replacement of PT_INTERP with PT_NULL, but convincing the devs to pull it upstream is a different matter.)


Apparently (and please correct me if this is something you've already seen) you can override the behavior of ld with regards to any of the ELF headers in your linker script with the PHDRS command, and using :none to specify that a particular header type should not be included in any segment. I'm not certain of the syntax, but I presume it would look something like this:

PHDRS
{
  headers PT_PHDR PHDRS ;
  interp PT_INTERP ;
  text PT_LOAD FILEHDR PHDRS ;
  data PT_LOAD ;
  dynamic PT_DYNAMIC ;
}

SECTIONS
{
  . = SIZEOF_HEADERS;
  .interp : { } :none
  ...
}

From the ld docs you can override the linker script with --library-path:

--library-path=searchdir

Add path searchdir to the list of paths that ld will search for archive libraries and ld control scripts. You may use this option any number of times. The directories are searched in the order in which they are specified on the command line. Directories specified on the command line are searched before the default directories. All -L options apply to all -l options, regardless of the order in which the options appear. The default set of paths searched (without being specified with `-L') depends on which emulation mode ld is using, and in some cases also on how it was configured. See section Environment Variables. The paths can also be specified in a link script with the SEARCH_DIR command. Directories specified this way are searched at the point in which the linker script appears in the command line.

Also, from the section on Implicit Linker Scripts:

If you specify a linker input file which the linker can not recognize as an object file or an archive file, it will try to read the file as a linker script. If the file can not be parsed as a linker script, the linker will report an error.

Which would seem to imply values in user-defined linker scripts, in contrast with implicitly defined linker scripts, will replace values in the default scripts.


I think I might have found a solution: simply using -shared instead of -pie to make pie binaries. You need a few extra linker options to patch up the behavior, but it seems to avoid the need for a custom linker script. Or in other words, the -shared linker script is already essentially correct for linking static pie binaries.

If I get it working with this, I'll update the answer with the exact command line I'm using.

Update: It works! Here's the command line:

gcc -shared -static-libgcc -Wl,-static -Wl,-Bsymbolic \
    -nostartfiles -fPIE Zcrt1.s Zcrt2.c /usr/lib/crti.o hello.c /usr/lib/crtn.o

where Zcrt1.s is a modified version of Scrt1.s that calls a function in Zcrt2.c before doing its normal work, and the code in Zcrt2.c processes the aux vector just past the argv and environment arrays to find the DYNAMIC section, then loops over the relocation tables and applies all the relative-type relocations (the only ones that should exist).

Now all of this can (with a little work) be wrapped up into a script or gcc specfile...


Maybe I'm being naïve, but... woudn't suffice to search for the default linker script, edit it, and remove the line that links in the .interp section?

For example, in my machine the scripts are in /usr/lib/ldscripts and the line in question is interp : { *(.interp) } in the SECTIONS section.

You can dumpp the default script used running the following command:

$ ld --verbose ${YOUR_LD_FLAGS} | \
    gawk 'BEGIN { s = 0 } { if ($0 ~ /^=/) s = !s; else if (s == 1) print; }'

You can modify the gawk script slightly to remove the interp line (or just use grep -v and use that script to link your program.


I'am not an expert in GNU ld, but I have found the following information in the documentation:

The special secname `/DISCARD/' may be used to discard input sections. Any sections which are assigned to an output section named `/DISCARD/' are not included in the final link output.

I hope this will help you.

UPDATE:

(This is the first version of the solution, which don't work because INTERP section is dropped along with the header PT_INTERP.)

main.c:

int main(int argc, char **argv)                                                                                                                               
{                                                                                                                                                             
    return 0;                                                                                                                                                 
}

main.x:

SECTIONS {                                                                                                                                                    
    /DISCARD/ : { *(.interp) }                                                                                                                                
}

build command:

$ gcc -nostdlib -pie -static -Wl,-T,main.x main.c
$ readelf -S a.out | grep .interp

build command without option -Wl,-T,main.x:

$ gcc -nostdlib -pie -static main.c 
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000000218
$ readelf -S a.out | grep .interp
  [ 1] .interp           PROGBITS        00000134 000134 000013 00   A  0   0  1

UPDATE 2:

The idea of this solution is that the original section 'INTERP' (. interp in the linker script file) is renamed to .interp1. In other words, the entire contents of the section is placed to the .interp1 section. Therefore, we can safe remove INTERP section (now empty) without fear of losing default linker script settings and hence the header INTERP_PT will be removed too.

SECTIONS {
    .interp1 : { *(.interp); } : NONE
    /DISCARD/ : { *(.interp) }
}

In order to show that the contents of the section INTERP present in the file (as .interp1), but INTERP_PT header removed, I use a combination of readelf + grep.

$ gcc -nostdlib -pie -Wl,-T,main.x main.c
$ readelf -l a.out | grep interp
   00     .note.gnu.build-id .text .interp1 .dynstr .hash .gnu.hash .dynamic .got.plt 
$ readelf -S a.out | grep interp
  [ 3] .interp1          PROGBITS        0000002e 00102e 000013 00   A  0   0  1