Bug 31795 - ld.bfd makes ELFs of type ET_EXEC for PIEs when load address is non-0
Summary: ld.bfd makes ELFs of type ET_EXEC for PIEs when load address is non-0
Status: REOPENED
Alias: None
Product: binutils
Classification: Unclassified
Component: ld (show other bugs)
Version: 2.43.1
: P2 critical
Target Milestone: 2.43
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2024-05-24 14:44 UTC by mintsuki
Modified: 2024-08-18 13:06 UTC (History)
4 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed: 2024-05-25 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description mintsuki 2024-05-24 14:44:13 UTC
When linking a static-pie kernel with ld.lld (from LLVM) or ld.gold, the output ELF file type is always ET_DYN (as I believe is correct), but, when linking using ld.bfd, the output ELF type is ET_DYN only if the load address of the PHDRs starts at 0, but gets set to ET_EXEC if the load address is a non-0 value.
Comment 1 H.J. Lu 2024-05-25 12:31:41 UTC
It is done to ensure that PIE is loaded at the specific address which may
be required for functional, performance or security purposes.
Comment 2 mintsuki 2024-05-25 12:55:53 UTC
(In reply to H.J. Lu from comment #1)
> It is done to ensure that PIE is loaded at the specific address which may
> be required for functional, performance or security purposes.

This does not match what all the other major linkers do (LLD, gold). It also isn't properly documented anywhere that I could find. Additionally, why would someone generate a PIE just for it to be loaded *always* at the same specified address? What is the use case?

From what I know, if a PIE has a specific non-0 load address, it should be taken as a hint or preferred load address, but then the program loader should be able to apply any slide to it as it sees fit.
Comment 3 mintsuki 2024-05-25 13:04:40 UTC
Also, when generating a shared object with -shared, without -pie, having a non-0 base address does not affect the ELF file type, which is always ET_DYN. If what you just said is true, then why is this the behaviour here and not when making a static PIE?
Comment 4 H.J. Lu 2024-05-25 13:07:07 UTC
(In reply to mintsuki from comment #2)
> (In reply to H.J. Lu from comment #1)
> > It is done to ensure that PIE is loaded at the specific address which may
> > be required for functional, performance or security purposes.
> 
> This does not match what all the other major linkers do (LLD, gold). It also
> isn't properly documented anywhere that I could find. Additionally, why
> would someone generate a PIE just for it to be loaded *always* at the same
> specified address? What is the use case?

A program may work properly only when it is loaded above 4GB address.

> From what I know, if a PIE has a specific non-0 load address, it should be
> taken as a hint or preferred load address, but then the program loader
> should be able to apply any slide to it as it sees fit.

If a PIE doesn't care where to load, set the load address to 0.
Comment 5 H.J. Lu 2024-05-25 13:13:55 UTC
(In reply to mintsuki from comment #3)
> Also, when generating a shared object with -shared, without -pie, having a
> non-0 base address does not affect the ELF file type, which is always

You can't load the multiple shared libraries at the overlapping address range.

> ET_DYN. If what you just said is true, then why is this the behaviour here
> and not when making a static PIE?

A testcase?
Comment 6 mintsuki 2024-05-25 13:21:34 UTC
(In reply to H.J. Lu from comment #4)
> (In reply to mintsuki from comment #2)
> > (In reply to H.J. Lu from comment #1)
> > > It is done to ensure that PIE is loaded at the specific address which may
> > > be required for functional, performance or security purposes.
> > 
> > This does not match what all the other major linkers do (LLD, gold). It also
> > isn't properly documented anywhere that I could find. Additionally, why
> > would someone generate a PIE just for it to be loaded *always* at the same
> > specified address? What is the use case?
> 
> A program may work properly only when it is loaded above 4GB address.

Yes, and I guess that could be explicitly specified as the functioning of a non-0 load
address.
But that doesn't mean the program needs to always be loaded at the specific load address.
For example, my kernel's load address is 0xffffffff80000000, aka the top 2GiB of address
space, but it can be relocated to anywhere in the range from there to the top of the
address space (as long as it fits), for things like, for example, KASLR.

Setting the load address to 0 works, the bootloader can relocate the kernel to at or above
0xffffffff80000000, but this makes debugging with KASLR disabled more annoying as one has
to subtract the slide from addresses passed to addr2line, or tell GDB about the slide.

> 
> > From what I know, if a PIE has a specific non-0 load address, it should be
> > taken as a hint or preferred load address, but then the program loader
> > should be able to apply any slide to it as it sees fit.
> 
> If a PIE doesn't care where to load, set the load address to 0.

See above.
Comment 7 mintsuki 2024-05-25 13:29:54 UTC
(In reply to H.J. Lu from comment #5)
> (In reply to mintsuki from comment #3)
> > Also, when generating a shared object with -shared, without -pie, having a
> > non-0 base address does not affect the ELF file type, which is always
> 
> You can't load the multiple shared libraries at the overlapping address
> range.
> 
> > ET_DYN. If what you just said is true, then why is this the behaviour here
> > and not when making a static PIE?
> 
> A testcase?

Download and unpack https://github.com/limine-bootloader/limine-c-template/archive/d90875580fb74c3cf075b659c995e4070d265c24.tar.gz

In the extracted directory, run "make -C kernel clean all KCC=gcc KLD=ld.bfd". The output kernel is found at "kernel/bin/kernel".

The linker script is found at "kernel/linker.ld".

So, building this with ld.bfd, the output ELF file type is ET_EXEC, as in the subject of this bug report. Replacing ld.bfd with ld.gold or ld.lld in the command above will generated a static PIE kernel with type ET_DYN and a non-0 base (0xffffffff80000000). ld.bfd is the outlier here.

Replacing -pie with -shared at line 73 of "kernel/GNUmakefile" and then rebuilding, as mentioned above, will cause all 3 linkers to (correctly) generate an ET_DYN shared object with the specified non-0 base.
Comment 8 H.J. Lu 2024-05-25 13:43:07 UTC
(In reply to mintsuki from comment #7)
> (In reply to H.J. Lu from comment #5)
> > (In reply to mintsuki from comment #3)
> > > Also, when generating a shared object with -shared, without -pie, having a
> > > non-0 base address does not affect the ELF file type, which is always
> > 
> > You can't load the multiple shared libraries at the overlapping address
> > range.
> > 
> > > ET_DYN. If what you just said is true, then why is this the behaviour here
> > > and not when making a static PIE?
> > 
> > A testcase?
> 

I was asking a testcase of static PIE where ELF type wasn't EXEC when a load
address was specified.
Comment 9 H.J. Lu 2024-05-25 13:45:37 UTC
(In reply to mintsuki from comment #6)
> (In reply to H.J. Lu from comment #4)
> > (In reply to mintsuki from comment #2)
> > > (In reply to H.J. Lu from comment #1)
> > > > It is done to ensure that PIE is loaded at the specific address which may
> > > > be required for functional, performance or security purposes.
> > > 
> > > This does not match what all the other major linkers do (LLD, gold). It also
> > > isn't properly documented anywhere that I could find. Additionally, why
> > > would someone generate a PIE just for it to be loaded *always* at the same
> > > specified address? What is the use case?
> > 
> > A program may work properly only when it is loaded above 4GB address.
> 
> Yes, and I guess that could be explicitly specified as the functioning of a
> non-0 load
> address.
> But that doesn't mean the program needs to always be loaded at the specific
> load address.
> For example, my kernel's load address is 0xffffffff80000000, aka the top
> 2GiB of address
> space, but it can be relocated to anywhere in the range from there to the
> top of the
> address space (as long as it fits), for things like, for example, KASLR.
> 
> Setting the load address to 0 works, the bootloader can relocate the kernel
> to at or above
> 0xffffffff80000000, but this makes debugging with KASLR disabled more
> annoying as one has
> to subtract the slide from addresses passed to addr2line, or tell GDB about
> the slide.

GDB has no problems to debug PIE with load address == 0.  Is this issue specific
to kernel?
Comment 10 mintsuki 2024-05-25 13:49:02 UTC
(In reply to H.J. Lu from comment #8)
> (In reply to mintsuki from comment #7)
> > (In reply to H.J. Lu from comment #5)
> > > (In reply to mintsuki from comment #3)
> > > > Also, when generating a shared object with -shared, without -pie, having a
> > > > non-0 base address does not affect the ELF file type, which is always
> > > 
> > > You can't load the multiple shared libraries at the overlapping address
> > > range.
> > > 
> > > > ET_DYN. If what you just said is true, then why is this the behaviour here
> > > > and not when making a static PIE?
> > > 
> > > A testcase?
> > 
> 
> I was asking a testcase of static PIE where ELF type wasn't EXEC when a load
> address was specified.

There is no such case for ld.bfd. I never said there was, sorry if I wasn't clear enough. That said, lld and gold both allow for such case as previously mentioned.

What I believe the bug consists of is the behaviour of ld.bfd not aligning with the other linkers. I think it makes perfect sense for an ET_DYN static PIE with non-0 load address to exist, as previously mentioned, and I believe the bug is ld.bfd not allowing this.

If this is policy, then I believe it should be properly documented somewhere, if it wasn't already, and then ld.gold (as part of GNU binutils) should be changed to match ld.bfd's behaviour. Though I hope this isn't done as I consider ld.gold's (and LLD's) behaviour to be the preferrable one.
Comment 11 H.J. Lu 2024-05-25 13:55:56 UTC
(In reply to mintsuki from comment #10)

> If this is policy, then I believe it should be properly documented

Updating document won't solve your debugging issue.  Why does GDB have
no issues with PIE in user space?

> somewhere, if it wasn't already, and then ld.gold (as part of GNU binutils)
> should be changed to match ld.bfd's behaviour. Though I hope this isn't done
> as I consider ld.gold's (and LLD's) behaviour to be the preferrable one.
Comment 12 mintsuki 2024-05-25 14:05:40 UTC
(In reply to H.J. Lu from comment #11)
> (In reply to mintsuki from comment #10)
> 
> > If this is policy, then I believe it should be properly documented
> 
> Updating document won't solve your debugging issue.  Why does GDB have
> no issues with PIE in user space?

I do not have a debugging issue, I am merely asking why ld.bfd is refusing to generate static PIEs with type ET_DYN when the base address is non-0, while all the other linkers generate them fine.

If the solution is to just never do that, I can easily instead generate a shared object instead of a PIE, with a non-0 base, as that is accepted by ld.bfd just fine, and by lld and gold.

Why doesn't ld.bfd allow generating a PIE with a non-0 base when lld and gold allow that?

Why is it not possible to change ld.bfd's behaviour to match lld and gold?

Where in the ELF spec and/or binutils documentation is it explained that PIE ELFs should only ever have a 0 load address? Why this?
Comment 13 H.J. Lu 2024-05-25 14:09:54 UTC
Does PIE with zero load address cause any real problems for you?
Comment 14 mintsuki 2024-05-25 14:11:39 UTC
(In reply to H.J. Lu from comment #13)
> Does PIE with zero load address cause any real problems for you?

Why not just answer the question? Whether it causes real problems for me or not should be irrelevant to the question I am posing, which I won't repeat again as I already did several times over.
Comment 16 mintsuki 2024-05-25 14:23:23 UTC
(In reply to H.J. Lu from comment #15)
> Here is the original discussion:
> 
> https://lore.kernel.org/all/CAMe9rOoJvfJ-
> R3zBwPEcpiULfCdRXPRpayZCaMmXf3k7014izw@mail.gmail.com/T/
> #m2cfa035c82e32ca865ec0f1c352b14470dcc7f39

Okay, thank you. Then this seems like it was done as a hack to get the Linux kernel to cooperate, from a quick skimming. I do not agree with that change, but I guess my opinion hardly matters, especially not 10 years after it was done.

I hope this can be reverted, then, or that a linker flag is added to allow this, as from all I can gather, this does not go against spec. Especially since shared objects (non-PIE) can be made with arbitary load addresses.
Comment 18 mintsuki 2024-05-25 15:05:31 UTC
(In reply to H.J. Lu from comment #17)
> Fixed in 2.43 by:
> 
> https://sourceware.org/git/?p=binutils-gdb.git;a=commit;
> h=0daa17bf187cebd5b200f4fd5405cc55e75c391f

Thank you very much! I'll just finish off by saying that, while I appreciate this being documented, I would've still preferred the behaviour to be reverted to the previous one, the same as lld and gold.

Is there a chance that this will ever be done? It doesn't seem to be an issue for lld and gold, but maybe those aren't used enough and as much as ld.bfd to know...

Anyways thanks again.
Comment 19 mintsuki 2024-05-26 09:29:15 UTC
Why are shared libraries (linked with -shared) allowed to have a non-0 load address? Is this a bug or is this intended behaviour? If it is intended behaviour, then what is said behaviour? Why would said behaviour be any different compared to a -pie with a non-0 load address?

I am sorry for my insistence, it's just that I strongly feel like forcing the ELF type to ET_EXEC with non-0 load addresses is a bug; a workaround for a specific situation from 10 years ago that should've never been added... Even the documentation you added doesn't really make anything clear, as it makes assumptions about the loader that are not necessarily what the ELF specification mandates, as far as I can tell.

To be more specific, I am the author and main developer of the Limine bootloader; the Limine bootloader has its own boot protocol supporting loading 64-bit ELF files. When loading relocatable ELFs, so far what I had to do was ignore the ET_EXEC file type and instead rely on the presence of the DF_1_PIE flag (which your linker emits anyways even if the ELF type is forced to ET_EXEC) to determine whether an ELF file is relocatable or not (if the type wasn't ET_DYN already).

This is because ld.bfd refuses to generate PIE ELFs with a non-0 base and ET_DYN type.

There are solutions like recommending people to generate shared objects instead of PIEs, but I feel like that's almost like relying on a workaround to avoid the workaround, and I do not like that.

The other solution was what I thought about doing first, which is to set the load address to 0; but as I mentioned before, that breaks addr2line, objdump, and friends if looking for the raw virtual address of something...

Is this workaround in ld.bfd *really* still necessary after 10 years from when it was added? Can you please strongly consider reverting this behaviour? I would be extremely grateful if that was done.
Comment 20 H.J. Lu 2024-05-26 13:53:26 UTC
It turns out that static PIE with non-zero load address must have ET_EXEC:

https://sourceware.org/bugzilla/show_bug.cgi?id=31799

Otherwise, there is no way for loader to tell if the dynamic section entries
contain the relocated values for the load address or not.  Since static PIE
with non-zero load address must have ET_EXEC, PIE with non-zero load address
also should have ET_EXEC.

BTW, gold doesn't support static PIE and static PIE with non-zero load address
generated by lld crashes with the fix glibc.
Comment 21 mintsuki 2024-05-26 14:07:01 UTC
(In reply to H.J. Lu from comment #20)
> It turns out that static PIE with non-zero load address must have ET_EXEC:
> 
> https://sourceware.org/bugzilla/show_bug.cgi?id=31799
> 
> Otherwise, there is no way for loader to tell if the dynamic section entries
> contain the relocated values for the load address or not.  Since static PIE
> with non-zero load address must have ET_EXEC, PIE with non-zero load address
> also should have ET_EXEC.
> 
> BTW, gold doesn't support static PIE and static PIE with non-zero load
> address
> generated by lld crashes with the fix glibc.

At least in the context of making a kernel, gold does generate a static-pie, at least as printed out by the file command, and the output of readelf -a looks similar to ld.bfd's -static -pie generation, even without the -static part.

I assume this is because no other dynamic libraries are linked in when using -nostdlib and not specifying any dynamic library explicitly.

I do not know what the problem with glibc is, but static-pie kernels loaded by Limine with a non-0 load address (or shared objects with a non-0 load address for that matter) work perfectly fine if relocated (at or above whatever specified non-0 load address).
Comment 22 mintsuki 2024-05-26 14:12:16 UTC
As far as I can tell, the linked issue seems to indicate more so that this is indeed a problem rather than some glibc bug.

Does glibc crash without any patch if you patch ld.bfd to create an ET_DYN instead?
Comment 23 H.J. Lu 2024-05-26 14:15:39 UTC
(In reply to mintsuki from comment #21)
> (In reply to H.J. Lu from comment #20)
> > It turns out that static PIE with non-zero load address must have ET_EXEC:
> > 
> > https://sourceware.org/bugzilla/show_bug.cgi?id=31799
> > 
> > Otherwise, there is no way for loader to tell if the dynamic section entries
> > contain the relocated values for the load address or not.  Since static PIE
> > with non-zero load address must have ET_EXEC, PIE with non-zero load address
> > also should have ET_EXEC.
> > 
> > BTW, gold doesn't support static PIE and static PIE with non-zero load
> > address
> > generated by lld crashes with the fix glibc.
> 
> At least in the context of making a kernel, gold does generate a static-pie,
> at least as printed out by the file command, and the output of readelf -a
> looks similar to ld.bfd's -static -pie generation, even without the -static
> part.

[hjl@gnu-cfl-3 tmp]$ gcc -static-pie x.c 
[hjl@gnu-cfl-3 tmp]$ gcc -static-pie x.c -fuse-ld=lld
[hjl@gnu-cfl-3 tmp]$ gcc -static-pie x.c -fuse-ld=gold
/usr/local/bin/ld.gold: --no-dynamic-linker: unknown option
/usr/local/bin/ld.gold: use the --help option for usage information
collect2: error: ld returned 1 exit status
[hjl@gnu-cfl-3 tmp]$ 

> I assume this is because no other dynamic libraries are linked in when using
> -nostdlib and not specifying any dynamic library explicitly.
> 
> I do not know what the problem with glibc is, but static-pie kernels loaded
> by Limine with a non-0 load address (or shared objects with a non-0 load
> address for that matter) work perfectly fine if relocated (at or above
> whatever specified non-0 load address).

Does your kernel have any dynamic tags which need relocation?

[hjl@gnu-cfl-3 tmp]$ readelf -d /bin/ld

Dynamic section at offset 0x11f4a8 contains 35 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libbfd-2.42.50.0.1.20240523.so]
 0x0000000000000001 (NEEDED)             Shared library: [libctf.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libjansson.so.4]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000c (INIT)               0x6000
 0x000000000000000d (FINI)               0x4e594
 0x0000000000000019 (INIT_ARRAY)         0x1197f0
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x1197f8
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x460
 0x0000000000000005 (STRTAB)             0x2148
 0x0000000000000006 (SYMTAB)             0x540
 0x000000000000000a (STRSZ)              4860 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000070000000 (DT_X86_64_PLT)      0x7140
 0x0000000070000001 (DT_X86_64_PLTSZ)    0x10c0
 0x0000000070000003 (DT_X86_64_PLTENT)   0x10
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x120728
 0x0000000000000002 (PLTRELSZ)           6432 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x39c8
 0x0000000000000007 (RELA)               0x37a0
 0x0000000000000008 (RELASZ)             552 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000000000001e (FLAGS)              BIND_NOW
 0x000000006ffffffb (FLAGS_1)            Flags: NOW PIE
 0x000000006ffffffe (VERNEED)            0x36a0
 0x000000006fffffff (VERNEEDNUM)         2
 0x000000006ffffff0 (VERSYM)             0x3444
 0x0000000000000024 (RELR)               0x52e8
 0x0000000000000023 (RELRSZ)             536 (bytes)
 0x0000000000000025 (RELRENT)            8 (bytes)
 0x0000000000000000 (NULL)               0x0
[hjl@gnu-cfl-3 tmp]$ 

These dynamic tags with addresses must be relocated before accessing the memory.
Comment 24 H.J. Lu 2024-05-26 14:18:17 UTC
(In reply to mintsuki from comment #22)
> As far as I can tell, the linked issue seems to indicate more so that this
> is indeed a problem rather than some glibc bug.
> 
> Does glibc crash without any patch if you patch ld.bfd to create an ET_DYN
> instead?

My ld ET_EXEC change predates static PIE.  lld generates ET_DYN and there is
no way for static PIE with non-zero load address to work if marked ET_DYN.
Comment 25 mintsuki 2024-05-26 14:23:36 UTC
(In reply to H.J. Lu from comment #23)
> (In reply to mintsuki from comment #21)
> > (In reply to H.J. Lu from comment #20)
> > > It turns out that static PIE with non-zero load address must have ET_EXEC:
> > > 
> > > https://sourceware.org/bugzilla/show_bug.cgi?id=31799
> > > 
> > > Otherwise, there is no way for loader to tell if the dynamic section entries
> > > contain the relocated values for the load address or not.  Since static PIE
> > > with non-zero load address must have ET_EXEC, PIE with non-zero load address
> > > also should have ET_EXEC.
> > > 
> > > BTW, gold doesn't support static PIE and static PIE with non-zero load
> > > address
> > > generated by lld crashes with the fix glibc.
> > 
> > At least in the context of making a kernel, gold does generate a static-pie,
> > at least as printed out by the file command, and the output of readelf -a
> > looks similar to ld.bfd's -static -pie generation, even without the -static
> > part.
> 
> [hjl@gnu-cfl-3 tmp]$ gcc -static-pie x.c 
> [hjl@gnu-cfl-3 tmp]$ gcc -static-pie x.c -fuse-ld=lld
> [hjl@gnu-cfl-3 tmp]$ gcc -static-pie x.c -fuse-ld=gold
> /usr/local/bin/ld.gold: --no-dynamic-linker: unknown option
> /usr/local/bin/ld.gold: use the --help option for usage information
> collect2: error: ld returned 1 exit status
> [hjl@gnu-cfl-3 tmp]$ 

This is not an issue if you call ld.gold manually as done by the kernel in question (see above test case).

I know that "you should not invoke the linker outside the driver" and I do agree, but I do make an exception for linking bare metal, freestanding stuff, as I like to have more control over the linking process.

With that, I just didn't pass --no-dynamic-linker (as it is not supported by gold, and the linker script makes it a non issue), and I didn't pass -static, as frankly it seems to do nothing anyways when linking a -nostdlib -pie kernel, and isn't supported by gold in combination with -pie.

Please once again see the test case above.

> 
> > I assume this is because no other dynamic libraries are linked in when using
> > -nostdlib and not specifying any dynamic library explicitly.
> > 
> > I do not know what the problem with glibc is, but static-pie kernels loaded
> > by Limine with a non-0 load address (or shared objects with a non-0 load
> > address for that matter) work perfectly fine if relocated (at or above
> > whatever specified non-0 load address).
> 
> Does your kernel have any dynamic tags which need relocation?
> 
> [hjl@gnu-cfl-3 tmp]$ readelf -d /bin/ld
> 
> Dynamic section at offset 0x11f4a8 contains 35 entries:
>   Tag        Type                         Name/Value
>  0x0000000000000001 (NEEDED)             Shared library:
> [libbfd-2.42.50.0.1.20240523.so]
>  0x0000000000000001 (NEEDED)             Shared library: [libctf.so.0]
>  0x0000000000000001 (NEEDED)             Shared library: [libjansson.so.4]
>  0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
>  0x000000000000000c (INIT)               0x6000
>  0x000000000000000d (FINI)               0x4e594
>  0x0000000000000019 (INIT_ARRAY)         0x1197f0
>  0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
>  0x000000000000001a (FINI_ARRAY)         0x1197f8
>  0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
>  0x000000006ffffef5 (GNU_HASH)           0x460
>  0x0000000000000005 (STRTAB)             0x2148
>  0x0000000000000006 (SYMTAB)             0x540
>  0x000000000000000a (STRSZ)              4860 (bytes)
>  0x000000000000000b (SYMENT)             24 (bytes)
>  0x0000000070000000 (DT_X86_64_PLT)      0x7140
>  0x0000000070000001 (DT_X86_64_PLTSZ)    0x10c0
>  0x0000000070000003 (DT_X86_64_PLTENT)   0x10
>  0x0000000000000015 (DEBUG)              0x0
>  0x0000000000000003 (PLTGOT)             0x120728
>  0x0000000000000002 (PLTRELSZ)           6432 (bytes)
>  0x0000000000000014 (PLTREL)             RELA
>  0x0000000000000017 (JMPREL)             0x39c8
>  0x0000000000000007 (RELA)               0x37a0
>  0x0000000000000008 (RELASZ)             552 (bytes)
>  0x0000000000000009 (RELAENT)            24 (bytes)
>  0x000000000000001e (FLAGS)              BIND_NOW
>  0x000000006ffffffb (FLAGS_1)            Flags: NOW PIE
>  0x000000006ffffffe (VERNEED)            0x36a0
>  0x000000006fffffff (VERNEEDNUM)         2
>  0x000000006ffffff0 (VERSYM)             0x3444
>  0x0000000000000024 (RELR)               0x52e8
>  0x0000000000000023 (RELRSZ)             536 (bytes)
>  0x0000000000000025 (RELRENT)            8 (bytes)
>  0x0000000000000000 (NULL)               0x0
> [hjl@gnu-cfl-3 tmp]$ 
> 
> These dynamic tags with addresses must be relocated before accessing the
> memory.

The test case above is too small to have any necessary relocation, but I have another kernel using the same build system that has plenty of R_X86_64_RELATIVE relocations and it works fine after Limine relocates it.
Comment 26 mintsuki 2024-05-26 14:28:03 UTC
(In reply to H.J. Lu from comment #24)
> (In reply to mintsuki from comment #22)
> > As far as I can tell, the linked issue seems to indicate more so that this
> > is indeed a problem rather than some glibc bug.
> > 
> > Does glibc crash without any patch if you patch ld.bfd to create an ET_DYN
> > instead?
> 
> My ld ET_EXEC change predates static PIE.  lld generates ET_DYN and there is
> no way for static PIE with non-zero load address to work if marked ET_DYN.

Well, evidently it works fine enough for Limine to load these properly as made by ld.bfd, lld, or gold...

Honestly to me this seems more of a glibc bug than anything, unless I am missing something.
Comment 27 mintsuki 2024-05-26 14:33:21 UTC
This is the aforementioned other kernel's readelf -d output:


Dynamic section at offset 0x46b70 contains 13 entries:
  Tag        Type                         Name/Value
 0x0000000000000004 (HASH)               0xffffffff8003c140
 0x000000006ffffef5 (GNU_HASH)           0xffffffff8003c150
 0x0000000000000005 (STRTAB)             0xffffffff8003c138
 0x0000000000000006 (SYMTAB)             0xffffffff8003c120
 0x000000000000000a (STRSZ)              1 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000007 (RELA)               0xffffffff8003c170
 0x0000000000000008 (RELASZ)             27264 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffb (FLAGS_1)            Flags: PIE
 0x000000006ffffff9 (RELACOUNT)          1136
 0x0000000000000000 (NULL)               0x0


And then:

Relocation section '.rela.dyn' at offset 0x3c170 contains 1136 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
ffffffff80042bf8  000000000008 R_X86_64_RELATIVE                    -7ffbc3a0
ffffffff80042c08  000000000008 R_X86_64_RELATIVE                    -7ffbc300
ffffffff80043c60  000000000008 R_X86_64_RELATIVE                    -7ffcaa06
ffffffff80043c70  000000000008 R_X86_64_RELATIVE                    -7fffc3a0
ffffffff80043d00  000000000008 R_X86_64_RELATIVE                    -7ffca920
ffffffff80043d10  000000000008 R_X86_64_RELATIVE                    -7fff8670
ffffffff80043ea0  000000000008 R_X86_64_RELATIVE                    -7ffca12c
ffffffff80043ea8  000000000008 R_X86_64_RELATIVE                    -7ffca119
ffffffff80043eb0  000000000008 R_X86_64_RELATIVE                    -7ffca113
ffffffff80043eb8  000000000008 R_X86_64_RELATIVE                    -7ffca10f
ffffffff80043ec0  000000000008 R_X86_64_RELATIVE                    -7ffca104
ffffffff80043ec8  000000000008 R_X86_64_RELATIVE                    -7ffca0fb
ffffffff80043ed0  000000000008 R_X86_64_RELATIVE                    -7ffca0e6
ffffffff80043ed8  000000000008 R_X86_64_RELATIVE                    -7ffca0d7
... continues ...
Comment 28 H.J. Lu 2024-05-26 14:33:33 UTC
(In reply to mintsuki from comment #25)
> (In reply to H.J. Lu from comment #23)
> > (In reply to mintsuki from comment #21)
> > > (In reply to H.J. Lu from comment #20)
> > > > It turns out that static PIE with non-zero load address must have ET_EXEC:
> > > > 
> > > > https://sourceware.org/bugzilla/show_bug.cgi?id=31799
> > > > 
> > > > Otherwise, there is no way for loader to tell if the dynamic section entries
> > > > contain the relocated values for the load address or not.  Since static PIE
> > > > with non-zero load address must have ET_EXEC, PIE with non-zero load address
> > > > also should have ET_EXEC.
> > > > 
> > > > BTW, gold doesn't support static PIE and static PIE with non-zero load
> > > > address
> > > > generated by lld crashes with the fix glibc.
> > > 
> > > At least in the context of making a kernel, gold does generate a static-pie,
> > > at least as printed out by the file command, and the output of readelf -a
> > > looks similar to ld.bfd's -static -pie generation, even without the -static
> > > part.
> > 
> > [hjl@gnu-cfl-3 tmp]$ gcc -static-pie x.c 
> > [hjl@gnu-cfl-3 tmp]$ gcc -static-pie x.c -fuse-ld=lld
> > [hjl@gnu-cfl-3 tmp]$ gcc -static-pie x.c -fuse-ld=gold
> > /usr/local/bin/ld.gold: --no-dynamic-linker: unknown option
> > /usr/local/bin/ld.gold: use the --help option for usage information
> > collect2: error: ld returned 1 exit status
> > [hjl@gnu-cfl-3 tmp]$ 
> 
> This is not an issue if you call ld.gold manually as done by the kernel in
> question (see above test case).
> 
> I know that "you should not invoke the linker outside the driver" and I do
> agree, but I do make an exception for linking bare metal, freestanding
> stuff, as I like to have more control over the linking process.
> 
> With that, I just didn't pass --no-dynamic-linker (as it is not supported by
> gold, and the linker script makes it a non issue), and I didn't pass
> -static, as frankly it seems to do nothing anyways when linking a -nostdlib

-static is required for static PIE in user space to avoid linking in
shared libraries.

> 
> The test case above is too small to have any necessary relocation, but I
> have another kernel using the same build system that has plenty of
> R_X86_64_RELATIVE relocations and it works fine after Limine relocates it.

I was not talking about R_XXX relocation.  If dynamic tag entry address
values contain unrelocated addresses (load address == 0), loader needs
to add the load address to these addresses before using them to access
their memory.
Comment 29 H.J. Lu 2024-05-26 14:39:00 UTC
(In reply to mintsuki from comment #27)
> This is the aforementioned other kernel's readelf -d output:
> 
> 
> Dynamic section at offset 0x46b70 contains 13 entries:
>   Tag        Type                         Name/Value
>  0x0000000000000004 (HASH)               0xffffffff8003c140
>  0x000000006ffffef5 (GNU_HASH)           0xffffffff8003c150
>  0x0000000000000005 (STRTAB)             0xffffffff8003c138
>  0x0000000000000006 (SYMTAB)             0xffffffff8003c120
>  0x000000000000000a (STRSZ)              1 (bytes)
>  0x000000000000000b (SYMENT)             24 (bytes)
>  0x0000000000000015 (DEBUG)              0x0
>  0x0000000000000007 (RELA)               0xffffffff8003c170
>  0x0000000000000008 (RELASZ)             27264 (bytes)
>  0x0000000000000009 (RELAENT)            24 (bytes)
>  0x000000006ffffffb (FLAGS_1)            Flags: PIE
>  0x000000006ffffff9 (RELACOUNT)          1136
>  0x0000000000000000 (NULL)               0x0
> 

Your kernel contains the relocated addresses in dynamic section since its
load address != 0.  Try load address == 0, these addresses will be different.
You need to decide how to handle addresses like

  0x0000000000000007 (RELA)               0xffffffff8003c170

Should you add load address to them or not?
Comment 30 mintsuki 2024-05-26 14:47:07 UTC
(In reply to H.J. Lu from comment #29)
> (In reply to mintsuki from comment #27)
> > This is the aforementioned other kernel's readelf -d output:
> > 
> > 
> > Dynamic section at offset 0x46b70 contains 13 entries:
> >   Tag        Type                         Name/Value
> >  0x0000000000000004 (HASH)               0xffffffff8003c140
> >  0x000000006ffffef5 (GNU_HASH)           0xffffffff8003c150
> >  0x0000000000000005 (STRTAB)             0xffffffff8003c138
> >  0x0000000000000006 (SYMTAB)             0xffffffff8003c120
> >  0x000000000000000a (STRSZ)              1 (bytes)
> >  0x000000000000000b (SYMENT)             24 (bytes)
> >  0x0000000000000015 (DEBUG)              0x0
> >  0x0000000000000007 (RELA)               0xffffffff8003c170
> >  0x0000000000000008 (RELASZ)             27264 (bytes)
> >  0x0000000000000009 (RELAENT)            24 (bytes)
> >  0x000000006ffffffb (FLAGS_1)            Flags: PIE
> >  0x000000006ffffff9 (RELACOUNT)          1136
> >  0x0000000000000000 (NULL)               0x0
> > 
> 
> Your kernel contains the relocated addresses in dynamic section since its
> load address != 0.  Try load address == 0, these addresses will be different.
> You need to decide how to handle addresses like
> 
>   0x0000000000000007 (RELA)               0xffffffff8003c170
> 
> Should you add load address to them or not?

Well, the way Limine has always dealt with this issue is: https://github.com/limine-bootloader/limine/blob/c204af454fc9c11b8ef3633664b6e03817c33ff1/common/lib/elf.c#L223

Basically, it tries to find a PHDR which contains the RELA section, and if it find it, it subtracts the PHDR's virtual address and adds its offset in order to find the offset of the RELA section inside the ELF file itself.
Comment 31 H.J. Lu 2024-05-26 14:54:34 UTC
(In reply to mintsuki from comment #30)
> Basically, it tries to find a PHDR which contains the RELA section, and if
> it find it, it subtracts the PHDR's virtual address and adds its offset in
> order to find the offset of the RELA section inside the ELF file itself.

In glibc, we know it is a static PIE.  We relocate PT_DYNAMIC segment only if
it is ET_DYN.
Comment 32 mintsuki 2024-05-26 14:58:28 UTC
(In reply to H.J. Lu from comment #31)
> (In reply to mintsuki from comment #30)
> > Basically, it tries to find a PHDR which contains the RELA section, and if
> > it find it, it subtracts the PHDR's virtual address and adds its offset in
> > order to find the offset of the RELA section inside the ELF file itself.
> 
> In glibc, we know it is a static PIE.  We relocate PT_DYNAMIC segment only if
> it is ET_DYN.

So what is the issue? And in any case, it doesn't feel right to me to have this behaviour in ld just to work around quirks of a specific ELF loader out of many.

If anything, it needs to be handled in glibc, and the behaviour of ld should be reverted to match gold and lld.

Just my opinion of course.
Comment 33 H.J. Lu 2024-05-26 15:07:07 UTC
(In reply to mintsuki from comment #32)
> (In reply to H.J. Lu from comment #31)
> > (In reply to mintsuki from comment #30)
> > > Basically, it tries to find a PHDR which contains the RELA section, and if
> > > it find it, it subtracts the PHDR's virtual address and adds its offset in
> > > order to find the offset of the RELA section inside the ELF file itself.
> > 
> > In glibc, we know it is a static PIE.  We relocate PT_DYNAMIC segment only if
> > it is ET_DYN.
> 
> So what is the issue? And in any case, it doesn't feel right to me to have

This is only after my patch:

https://patchwork.sourceware.org/project/glibc/list/?series=34373

Currently glibc relocates PT_DYNAMIC segment unconditionally.

> this behaviour in ld just to work around quirks of a specific ELF loader out
> of many.
> 
> If anything, it needs to be handled in glibc, and the behaviour of ld should
> be reverted to match gold and lld.
> 

The static PIE generated by ldd with non-zero load address won't work with
glibc before and after my glibc fix.  I will open an lld issue after glibc
is fixed.
Comment 34 mintsuki 2024-05-26 15:14:28 UTC
(In reply to H.J. Lu from comment #33)
> (In reply to mintsuki from comment #32)
> > (In reply to H.J. Lu from comment #31)
> > > (In reply to mintsuki from comment #30)
> > > > Basically, it tries to find a PHDR which contains the RELA section, and if
> > > > it find it, it subtracts the PHDR's virtual address and adds its offset in
> > > > order to find the offset of the RELA section inside the ELF file itself.
> > > 
> > > In glibc, we know it is a static PIE.  We relocate PT_DYNAMIC segment only if
> > > it is ET_DYN.
> > 
> > So what is the issue? And in any case, it doesn't feel right to me to have
> 
> This is only after my patch:
> 
> https://patchwork.sourceware.org/project/glibc/list/?series=34373
> 
> Currently glibc relocates PT_DYNAMIC segment unconditionally.
> 
> > this behaviour in ld just to work around quirks of a specific ELF loader out
> > of many.
> > 
> > If anything, it needs to be handled in glibc, and the behaviour of ld should
> > be reverted to match gold and lld.
> > 
> 
> The static PIE generated by ldd with non-zero load address won't work with
> glibc before and after my glibc fix.  I will open an lld issue after glibc
> is fixed.

I *strongly* disagree with this.

Why are glibc implementation details dictating how ld.bfd operates? Where does it say non-0 load addresses for static-PIE/PIE are not allowed in the ELF specification? If it is allowed, why is ld.bfd not allowing me to do it despite Limine accepting this form of ELF files just fine?

At least please consider adding a flag or linker script directive to manually set the ELF type...
Comment 35 H.J. Lu 2024-05-26 15:35:40 UTC
(In reply to mintsuki from comment #34)

> > The static PIE generated by ldd with non-zero load address won't work with
> > glibc before and after my glibc fix.  I will open an lld issue after glibc
> > is fixed.
> 
> I *strongly* disagree with this.
> 
> Why are glibc implementation details dictating how ld.bfd operates? Where

This is how ELF works on Linux.

> does it say non-0 load addresses for static-PIE/PIE are not allowed in the
> ELF specification? If it is allowed, why is ld.bfd not allowing me to do it

Static PIE isn't the part of ELF spec. There was a discussion:

https://groups.google.com/g/generic-abi/c/mBKlSNldFW4/m/GRddmWGGBQAJ

> despite Limine accepting this form of ELF files just fine?
> 
> At least please consider adding a flag or linker script directive to
> manually set the ELF type...

That will generate the wrong static PIE with non-zero load address.  You should
check DF_1_PIE for PIE, not ET_DYN.
Comment 36 mintsuki 2024-05-26 15:50:51 UTC
(In reply to H.J. Lu from comment #35)
> (In reply to mintsuki from comment #34)
> 
> > > The static PIE generated by ldd with non-zero load address won't work with
> > > glibc before and after my glibc fix.  I will open an lld issue after glibc
> > > is fixed.
> > 
> > I *strongly* disagree with this.
> > 
> > Why are glibc implementation details dictating how ld.bfd operates? Where
> 
> This is how ELF works on Linux.

Well, ELF is not Linux specific, though.

> 
> > does it say non-0 load addresses for static-PIE/PIE are not allowed in the
> > ELF specification? If it is allowed, why is ld.bfd not allowing me to do it
> 
> Static PIE isn't the part of ELF spec. There was a discussion:
> 
> https://groups.google.com/g/generic-abi/c/mBKlSNldFW4/m/GRddmWGGBQAJ
> 

Then what is ld.bfd following, exactly? Just what Linux/glibc wants? How loosely specified (or not at all) can this be?

> > despite Limine accepting this form of ELF files just fine?
> > 
> > At least please consider adding a flag or linker script directive to
> > manually set the ELF type...
> 
> That will generate the wrong static PIE with non-zero load address.  You
> should
> check DF_1_PIE for PIE, not ET_DYN.

Limine already does that. The algorithm for determining whether an ELF is relocatable or not is at https://github.com/limine-bootloader/limine/blob/c204af454fc9c11b8ef3633664b6e03817c33ff1/common/lib/elf.c#L148-L181

If that was the case, you could've said it earlier and it would've saved the time for this discussion. I still do not like this, but it never caused issues to me or Limine or Limine users. It's just a purely technical argument, because ld.bfd diverged in behaviour compared to other linkers in a silly way.

Now I see that this is done to make Linux/glibc happy, and thus unlikely to be fixed or reverted.
Comment 37 Fangrui Song 2024-05-27 02:15:32 UTC
I agree with mintsuki . The "-pie -Ttext-segment=non-zero => ET_EXEC" hack should not be needed.

From https://sourceware.org/pipermail/binutils/2013-December/083381.html

> Linker sets e_type in ELF header to ET_DYN for -pie -Ttext-segment=0xXXX.
> When I added -Ttext-segment=0xXXX, one goal was to load
> small model executable above 4GB on Linux/x86-64, which
> was done with -pie -Ttext-segment=0xXXX.  But -pie sets
> e_type in ELF header to ET_DYN and kernel may ignore
> p_vaddr in ELF header to load ET_DYN binary at a random
> address.  This patch changes ld to set e_type in ELF header
> to ET_EXEC if the first PT_LOAD segment has non-zero
> p_vaddr.  If this is unacceptable as generic ELF change,
> I can make it specific to x86.

Was the intention for the following command to load the text segment at an address >= 0x600000000000 ?

```
% cat a.c
#include <stdio.h>
int main() { printf("%p\n", main); }
% gcc -pie -Wl,-no-pie a.c -fuse-ld=bfd -Wl,--no-relax,-Ttext-segment=0x600000000000 -o a
% ./a
0x600000001139
% ./a
0x600000001139  # no ASLR
```

Changing ET_DYN to ET_EXEC fulfills the address requirement but disables ASLR.
Is it intentional?

I added `--no-pie` to GNU ld in 2021: https://sourceware.org/cgit/binutils-gdb/commit/?id=e8f6c2a5bab10b039a12b69a30a8248c91161e11 , with which we can do the following instead. (GNU ld also needs `--no-relax` while lld doesn't).

```
% gcc -pie a.c -fuse-ld=bfd -Wl,--no-pie,--no-relax,-Ttext-segment=0x600000000000 -o a
% ./a
0x600000001139
% ./a
0x600000001139
```
Comment 38 mintsuki 2024-05-27 22:06:58 UTC
(In reply to Fangrui Song from comment #37)
> I agree with mintsuki . The "-pie -Ttext-segment=non-zero => ET_EXEC" hack
> should not be needed.
> 
> From https://sourceware.org/pipermail/binutils/2013-December/083381.html
> 
> > Linker sets e_type in ELF header to ET_DYN for -pie -Ttext-segment=0xXXX.
> > When I added -Ttext-segment=0xXXX, one goal was to load
> > small model executable above 4GB on Linux/x86-64, which
> > was done with -pie -Ttext-segment=0xXXX.  But -pie sets
> > e_type in ELF header to ET_DYN and kernel may ignore
> > p_vaddr in ELF header to load ET_DYN binary at a random
> > address.  This patch changes ld to set e_type in ELF header
> > to ET_EXEC if the first PT_LOAD segment has non-zero
> > p_vaddr.  If this is unacceptable as generic ELF change,
> > I can make it specific to x86.
> 
> Was the intention for the following command to load the text segment at an
> address >= 0x600000000000 ?
> 
> ```
> % cat a.c
> #include <stdio.h>
> int main() { printf("%p\n", main); }
> % gcc -pie -Wl,-no-pie a.c -fuse-ld=bfd
> -Wl,--no-relax,-Ttext-segment=0x600000000000 -o a
> % ./a
> 0x600000001139
> % ./a
> 0x600000001139  # no ASLR
> ```
> 
> Changing ET_DYN to ET_EXEC fulfills the address requirement but disables
> ASLR.
> Is it intentional?
> 
> I added `--no-pie` to GNU ld in 2021:
> https://sourceware.org/cgit/binutils-gdb/commit/
> ?id=e8f6c2a5bab10b039a12b69a30a8248c91161e11 , with which we can do the
> following instead. (GNU ld also needs `--no-relax` while lld doesn't).
> 
> ```
> % gcc -pie a.c -fuse-ld=bfd
> -Wl,--no-pie,--no-relax,-Ttext-segment=0x600000000000 -o a
> % ./a
> 0x600000001139
> % ./a
> 0x600000001139
> ```

Thanks for agreeing with me and reopening this bug report.

I appreciate that someone else sees this for the bug that it is, rather than trying to hand wave the issue away. I hope it will get addressed.
Comment 39 Adhemerval Zanella 2024-05-28 14:42:06 UTC
(In reply to Fangrui Song from comment #37)
> I agree with mintsuki . The "-pie -Ttext-segment=non-zero => ET_EXEC" hack
> should not be needed.
> 
> From https://sourceware.org/pipermail/binutils/2013-December/083381.html
> 
> > Linker sets e_type in ELF header to ET_DYN for -pie -Ttext-segment=0xXXX.
> > When I added -Ttext-segment=0xXXX, one goal was to load
> > small model executable above 4GB on Linux/x86-64, which
> > was done with -pie -Ttext-segment=0xXXX.  But -pie sets
> > e_type in ELF header to ET_DYN and kernel may ignore
> > p_vaddr in ELF header to load ET_DYN binary at a random
> > address.  This patch changes ld to set e_type in ELF header
> > to ET_EXEC if the first PT_LOAD segment has non-zero
> > p_vaddr.  If this is unacceptable as generic ELF change,
> > I can make it specific to x86.
> 
> Was the intention for the following command to load the text segment at an
> address >= 0x600000000000 ?
> 
> ```
> % cat a.c
> #include <stdio.h>
> int main() { printf("%p\n", main); }
> % gcc -pie -Wl,-no-pie a.c -fuse-ld=bfd
> -Wl,--no-relax,-Ttext-segment=0x600000000000 -o a
> % ./a
> 0x600000001139
> % ./a
> 0x600000001139  # no ASLR
> ```
> 
> Changing ET_DYN to ET_EXEC fulfills the address requirement but disables
> ASLR.
> Is it intentional?

That's my understanding of reading the -Ttext-segment documentation.  The question is whether we relax the semantic to have it as a minimum address or define it as the expected address (thus disabling ASLR as a consequence). 

I don't have a strong opinion, but currently, Linux only enforces the former (I think it is the main reason this makes some sense) so we will need to discuss with kernel developers the expected semantics.

> 
> I added `--no-pie` to GNU ld in 2021:
> https://sourceware.org/cgit/binutils-gdb/commit/
> ?id=e8f6c2a5bab10b039a12b69a30a8248c91161e11 , with which we can do the
> following instead. (GNU ld also needs `--no-relax` while lld doesn't).
> 
> ```
> % gcc -pie a.c -fuse-ld=bfd
> -Wl,--no-pie,--no-relax,-Ttext-segment=0x600000000000 -o a
> % ./a
> 0x600000001139
> % ./a
> 0x600000001139
> ```
Comment 40 H.J. Lu 2024-05-28 14:49:27 UTC
-Ttext-segment=0x600000000000 should create a binary which is guaranteed to be
loaded at 0x600000000000.
Comment 41 mintsuki 2024-05-28 14:50:37 UTC
(In reply to Adhemerval Zanella from comment #39)
> (In reply to Fangrui Song from comment #37)
> > I agree with mintsuki . The "-pie -Ttext-segment=non-zero => ET_EXEC" hack
> > should not be needed.
> > 
> > From https://sourceware.org/pipermail/binutils/2013-December/083381.html
> > 
> > > Linker sets e_type in ELF header to ET_DYN for -pie -Ttext-segment=0xXXX.
> > > When I added -Ttext-segment=0xXXX, one goal was to load
> > > small model executable above 4GB on Linux/x86-64, which
> > > was done with -pie -Ttext-segment=0xXXX.  But -pie sets
> > > e_type in ELF header to ET_DYN and kernel may ignore
> > > p_vaddr in ELF header to load ET_DYN binary at a random
> > > address.  This patch changes ld to set e_type in ELF header
> > > to ET_EXEC if the first PT_LOAD segment has non-zero
> > > p_vaddr.  If this is unacceptable as generic ELF change,
> > > I can make it specific to x86.
> > 
> > Was the intention for the following command to load the text segment at an
> > address >= 0x600000000000 ?
> > 
> > ```
> > % cat a.c
> > #include <stdio.h>
> > int main() { printf("%p\n", main); }
> > % gcc -pie -Wl,-no-pie a.c -fuse-ld=bfd
> > -Wl,--no-relax,-Ttext-segment=0x600000000000 -o a
> > % ./a
> > 0x600000001139
> > % ./a
> > 0x600000001139  # no ASLR
> > ```
> > 
> > Changing ET_DYN to ET_EXEC fulfills the address requirement but disables
> > ASLR.
> > Is it intentional?
> 
> That's my understanding of reading the -Ttext-segment documentation.  The
> question is whether we relax the semantic to have it as a minimum address or
> define it as the expected address (thus disabling ASLR as a consequence). 

My understanding is that the PT_LOAD PHDR addresses could be slid, as long as they are slid above the specified address. The fact that the first PT_LOAD PHDR is 0 or not should be irrelevant. It makes no sense for it to be relevant, let alone for it to dictate the ELF type to be ET_EXEC.

This is how Limine behaves, and how I interpret the ELF format.

> 
> I don't have a strong opinion, but currently, Linux only enforces the former
> (I think it is the main reason this makes some sense) so we will need to
> discuss with kernel developers the expected semantics.
> 
> > 
> > I added `--no-pie` to GNU ld in 2021:
> > https://sourceware.org/cgit/binutils-gdb/commit/
> > ?id=e8f6c2a5bab10b039a12b69a30a8248c91161e11 , with which we can do the
> > following instead. (GNU ld also needs `--no-relax` while lld doesn't).
> > 
> > ```
> > % gcc -pie a.c -fuse-ld=bfd
> > -Wl,--no-pie,--no-relax,-Ttext-segment=0x600000000000 -o a
> > % ./a
> > 0x600000001139
> > % ./a
> > 0x600000001139
> > ```
Comment 42 mintsuki 2024-05-28 14:51:03 UTC
(In reply to H.J. Lu from comment #40)
> -Ttext-segment=0x600000000000 should create a binary which is guaranteed to
> be
> loaded at 0x600000000000.

...as long as it's not a PIE.
Comment 43 H.J. Lu 2024-05-28 15:08:34 UTC
(In reply to mintsuki from comment #42)
> (In reply to H.J. Lu from comment #40)
> > -Ttext-segment=0x600000000000 should create a binary which is guaranteed to
> > be
> > loaded at 0x600000000000.
> 
> ...as long as it's not a PIE.

Please read x86-64 psABI:

https://gitlab.com/x86-psABIs/x86-64-ABI

PIE is the only way to create a small mode executable loaded at 0x600000000000.
Comment 44 mintsuki 2024-05-28 15:13:08 UTC
(In reply to H.J. Lu from comment #43)
> (In reply to mintsuki from comment #42)
> > (In reply to H.J. Lu from comment #40)
> > > -Ttext-segment=0x600000000000 should create a binary which is guaranteed to
> > > be
> > > loaded at 0x600000000000.
> > 
> > ...as long as it's not a PIE.
> 
> Please read x86-64 psABI:
> 
> https://gitlab.com/x86-psABIs/x86-64-ABI
> 
> PIE is the only way to create a small mode executable loaded at
> 0x600000000000.

Can you not use -mcmodel=large? In any case even if that was the case, it should be opt-in to make the ELF ET_EXEC, rather than automatic and not explicitly mentioned in a warning or anything.
Comment 45 H.J. Lu 2024-05-28 15:19:32 UTC
(In reply to mintsuki from comment #44)
> (In reply to H.J. Lu from comment #43)
> > (In reply to mintsuki from comment #42)
> > > (In reply to H.J. Lu from comment #40)
> > > > -Ttext-segment=0x600000000000 should create a binary which is guaranteed to
> > > > be
> > > > loaded at 0x600000000000.
> > > 
> > > ...as long as it's not a PIE.
> > 
> > Please read x86-64 psABI:
> > 
> > https://gitlab.com/x86-psABIs/x86-64-ABI
> > 
> > PIE is the only way to create a small mode executable loaded at
> > 0x600000000000.
> 
> Can you not use -mcmodel=large? In any case even if that was the case, it

There are 2 issues with -mcmodel=large:

1. Since there are no -mcmodel=large run-time libraries, you can't use -mcmodel=large
to create any meaningful binaries.
2. -mcmodel=large performance is much slower.

> should be opt-in to make the ELF ET_EXEC, rather than automatic and not
> explicitly mentioned in a warning or anything.

Opt-in to ET_EXEC will be wrong.
Comment 46 mintsuki 2024-05-28 15:27:57 UTC
(In reply to H.J. Lu from comment #45)
> (In reply to mintsuki from comment #44)
> > (In reply to H.J. Lu from comment #43)
> > > (In reply to mintsuki from comment #42)
> > > > (In reply to H.J. Lu from comment #40)
> > > > > -Ttext-segment=0x600000000000 should create a binary which is guaranteed to
> > > > > be
> > > > > loaded at 0x600000000000.
> > > > 
> > > > ...as long as it's not a PIE.
> > > 
> > > Please read x86-64 psABI:
> > > 
> > > https://gitlab.com/x86-psABIs/x86-64-ABI
> > > 
> > > PIE is the only way to create a small mode executable loaded at
> > > 0x600000000000.
> > 
> > Can you not use -mcmodel=large? In any case even if that was the case, it
> 
> There are 2 issues with -mcmodel=large:
> 
> 1. Since there are no -mcmodel=large run-time libraries, you can't use
> -mcmodel=large
> to create any meaningful binaries.
> 2. -mcmodel=large performance is much slower.
> 
> > should be opt-in to make the ELF ET_EXEC, rather than automatic and not
> > explicitly mentioned in a warning or anything.
> 
> Opt-in to ET_EXEC will be wrong.

Why will it be wrong? What if someone (me) wants to make a PIE that loads at a minimum at the specified address, but can be relocated above it? Currently ld makes this impossible by simply checking the ELF type, forcing my ELF loader to additionally check for the presence of the DF_1_PIE flag to decide whether an ELF file is relocatable or not...

...but Linux doesn't do that, apparently, but instead forces the load address to be the one specified, due to the ELF type being ET_EXEC.

Earlier you said I should check DF_1_PIE to determine relocatability... so which one is it? If I check for DF_1_PIE then I am not following the same behaviour as the Linux ELF loader, which means that I either break from Linux's behaviour, or I follow it, but that means that ld will never allow me to make an ET_DYN PIE with a non-0 load address.
Comment 47 H.J. Lu 2024-05-28 15:34:17 UTC
(In reply to mintsuki from comment #46)
g.
> > 
> > Opt-in to ET_EXEC will be wrong.
> 
> Why will it be wrong? What if someone (me) wants to make a PIE that loads at

It is wrong because -Ttext-segment=0x600000000000 no longer works.

> a minimum at the specified address, but can be relocated above it? Currently
> ld makes this impossible by simply checking the ELF type, forcing my ELF
> loader to additionally check for the presence of the DF_1_PIE flag to decide
> whether an ELF file is relocatable or not...
> 
> ...but Linux doesn't do that, apparently, but instead forces the load
> address to be the one specified, due to the ELF type being ET_EXEC.
> 
> Earlier you said I should check DF_1_PIE to determine relocatability... so

No, that was not what I said.  DF_1_PIE can be used to determine if a binary
is PIE.

> which one is it? If I check for DF_1_PIE then I am not following the same
> behaviour as the Linux ELF loader, which means that I either break from
> Linux's behaviour, or I follow it, but that means that ld will never allow
> me to make an ET_DYN PIE with a non-0 load address.
Comment 48 mintsuki 2024-05-28 15:40:59 UTC
(In reply to H.J. Lu from comment #47)
> (In reply to mintsuki from comment #46)
> g.
> > > 
> > > Opt-in to ET_EXEC will be wrong.
> > 
> > Why will it be wrong? What if someone (me) wants to make a PIE that loads at
> 
> It is wrong because -Ttext-segment=0x600000000000 no longer works.
> 
> > a minimum at the specified address, but can be relocated above it? Currently
> > ld makes this impossible by simply checking the ELF type, forcing my ELF
> > loader to additionally check for the presence of the DF_1_PIE flag to decide
> > whether an ELF file is relocatable or not...
> > 
> > ...but Linux doesn't do that, apparently, but instead forces the load
> > address to be the one specified, due to the ELF type being ET_EXEC.
> > 
> > Earlier you said I should check DF_1_PIE to determine relocatability... so
> 
> No, that was not what I said.  DF_1_PIE can be used to determine if a binary
> is PIE.

Okay, then how can I make a *relocatable* aka ET_DYN (?) ELF file which is PIE and has a non-0 text segment load address using ld? This is, as far as my knowledge goes, *impossible* using GNU ld.bfd. Why?

I can still make an ET_DYN ELF using -shared instead of -pie, but then I have to build using -fPIC rather than -fPIE, as far as I understand. And in any case I don't understand why the limitation.

> 
> > which one is it? If I check for DF_1_PIE then I am not following the same
> > behaviour as the Linux ELF loader, which means that I either break from
> > Linux's behaviour, or I follow it, but that means that ld will never allow
> > me to make an ET_DYN PIE with a non-0 load address.
Comment 49 H.J. Lu 2024-05-28 15:50:48 UTC
(In reply to mintsuki from comment #48)
> (In reply to H.J. Lu from comment #47)
> > (In reply to mintsuki from comment #46)
> > g.
> > > > 
> > > > Opt-in to ET_EXEC will be wrong.
> > > 
> > > Why will it be wrong? What if someone (me) wants to make a PIE that loads at
> > 
> > It is wrong because -Ttext-segment=0x600000000000 no longer works.
> > 
> > > a minimum at the specified address, but can be relocated above it? Currently
> > > ld makes this impossible by simply checking the ELF type, forcing my ELF
> > > loader to additionally check for the presence of the DF_1_PIE flag to decide
> > > whether an ELF file is relocatable or not...
> > > 
> > > ...but Linux doesn't do that, apparently, but instead forces the load
> > > address to be the one specified, due to the ELF type being ET_EXEC.
> > > 
> > > Earlier you said I should check DF_1_PIE to determine relocatability... so
> > 
> > No, that was not what I said.  DF_1_PIE can be used to determine if a binary
> > is PIE.
> 
> Okay, then how can I make a *relocatable* aka ET_DYN (?) ELF file which is
> PIE and has a non-0 text segment load address using ld? This is, as far as
> my knowledge goes, *impossible* using GNU ld.bfd. Why?

Why can't you check DF_1_PIE for PIE?
Comment 50 mintsuki 2024-05-28 15:57:09 UTC
(In reply to H.J. Lu from comment #49)
> (In reply to mintsuki from comment #48)
> > (In reply to H.J. Lu from comment #47)
> > > (In reply to mintsuki from comment #46)
> > > g.
> > > > > 
> > > > > Opt-in to ET_EXEC will be wrong.
> > > > 
> > > > Why will it be wrong? What if someone (me) wants to make a PIE that loads at
> > > 
> > > It is wrong because -Ttext-segment=0x600000000000 no longer works.
> > > 
> > > > a minimum at the specified address, but can be relocated above it? Currently
> > > > ld makes this impossible by simply checking the ELF type, forcing my ELF
> > > > loader to additionally check for the presence of the DF_1_PIE flag to decide
> > > > whether an ELF file is relocatable or not...
> > > > 
> > > > ...but Linux doesn't do that, apparently, but instead forces the load
> > > > address to be the one specified, due to the ELF type being ET_EXEC.
> > > > 
> > > > Earlier you said I should check DF_1_PIE to determine relocatability... so
> > > 
> > > No, that was not what I said.  DF_1_PIE can be used to determine if a binary
> > > is PIE.
> > 
> > Okay, then how can I make a *relocatable* aka ET_DYN (?) ELF file which is
> > PIE and has a non-0 text segment load address using ld? This is, as far as
> > my knowledge goes, *impossible* using GNU ld.bfd. Why?
> 
> Why can't you check DF_1_PIE for PIE?

That is what I do now, but to check for *relocatability*. PIE in and of itself is not something that tells me whether I should relocate (for KASLR for example) or not. That is what you just said.
Comment 51 H.J. Lu 2024-05-28 16:01:11 UTC
(In reply to mintsuki from comment #50)

> > Why can't you check DF_1_PIE for PIE?
> 
> That is what I do now, but to check for *relocatability*. PIE in and of
> itself is not something that tells me whether I should relocate (for KASLR
> for example) or not. That is what you just said.

If DF_1_PIE is set, the binary can be relocated to any address. What did I miss?
Comment 52 H.J. Lu 2024-05-28 16:03:36 UTC
(In reply to H.J. Lu from comment #51)
> (In reply to mintsuki from comment #50)
> 
> > > Why can't you check DF_1_PIE for PIE?
> > 
> > That is what I do now, but to check for *relocatability*. PIE in and of
> > itself is not something that tells me whether I should relocate (for KASLR
> > for example) or not. That is what you just said.
> 
> If DF_1_PIE is set, the binary can be relocated to any address. What did I
> miss?

The first PT_LOAD segment has non-0 p_vaddr, the program may misbehave if
the load address != p_vaddr.
Comment 53 mintsuki 2024-05-28 16:07:07 UTC
(In reply to H.J. Lu from comment #51)
> (In reply to mintsuki from comment #50)
> 
> > > Why can't you check DF_1_PIE for PIE?
> > 
> > That is what I do now, but to check for *relocatability*. PIE in and of
> > itself is not something that tells me whether I should relocate (for KASLR
> > for example) or not. That is what you just said.
> 
> If DF_1_PIE is set, the binary can be relocated to any address. What did I
> miss?

This? :

> > > > Earlier you said I should check DF_1_PIE to determine relocatability... so
> > > 
> > > No, that was not what I said.  DF_1_PIE can be used to determine if a binary
> > > is PIE.

You said setting the text segment base address to non-0 means the load address has to be the one specified, due to the ELF type being forced to ET_EXEC by your linker; thus you *cannot* relocate it to any address. Linux *does not* relocate it, in fact, and ASLR is not done.
Comment 54 mintsuki 2024-05-28 16:08:34 UTC
(In reply to H.J. Lu from comment #52)
> (In reply to H.J. Lu from comment #51)
> > (In reply to mintsuki from comment #50)
> > 
> > > > Why can't you check DF_1_PIE for PIE?
> > > 
> > > That is what I do now, but to check for *relocatability*. PIE in and of
> > > itself is not something that tells me whether I should relocate (for KASLR
> > > for example) or not. That is what you just said.
> > 
> > If DF_1_PIE is set, the binary can be relocated to any address. What did I
> > miss?
> 
> The first PT_LOAD segment has non-0 p_vaddr, the program may misbehave if
> the load address != p_vaddr.

How is this possible? Under which circumstances? It works fine with all the other linkers for me.
Comment 55 H.J. Lu 2024-05-28 16:10:47 UTC
(In reply to mintsuki from comment #54)
> (In reply to H.J. Lu from comment #52)
> > (In reply to H.J. Lu from comment #51)
> > > (In reply to mintsuki from comment #50)
> > > 
> > > > > Why can't you check DF_1_PIE for PIE?
> > > > 
> > > > That is what I do now, but to check for *relocatability*. PIE in and of
> > > > itself is not something that tells me whether I should relocate (for KASLR
> > > > for example) or not. That is what you just said.
> > > 
> > > If DF_1_PIE is set, the binary can be relocated to any address. What did I
> > > miss?
> > 
> > The first PT_LOAD segment has non-0 p_vaddr, the program may misbehave if
> > the load address != p_vaddr.
> 
> How is this possible? Under which circumstances? It works fine with all the
> other linkers for me.

It works for your programs doesn't mean that it works all programs some of which
won't correctly if ET_DYN is used.
Comment 56 mintsuki 2024-05-28 16:11:17 UTC
(In reply to mintsuki from comment #54)
> (In reply to H.J. Lu from comment #52)
> > (In reply to H.J. Lu from comment #51)
> > > (In reply to mintsuki from comment #50)
> > > 
> > > > > Why can't you check DF_1_PIE for PIE?
> > > > 
> > > > That is what I do now, but to check for *relocatability*. PIE in and of
> > > > itself is not something that tells me whether I should relocate (for KASLR
> > > > for example) or not. That is what you just said.
> > > 
> > > If DF_1_PIE is set, the binary can be relocated to any address. What did I
> > > miss?
> > 
> > The first PT_LOAD segment has non-0 p_vaddr, the program may misbehave if
> > the load address != p_vaddr.
> 
> How is this possible? Under which circumstances? It works fine with all the
> other linkers for me.

And it has always worked fine when I simply checked DF_1_PIE and relocated to different addresses regardless of the ELF type.

The only difference between 0 and non-0 base is that ld.bfd decides to force the ELF type to ET_EXEC, and nothing more. I do not care about what Linux does, nor what glibc does; this is purely between me and the linker, what some specific OS or ELF loader does should not be of any concern to me.
Comment 57 mintsuki 2024-05-28 16:14:18 UTC
(In reply to H.J. Lu from comment #55)
> (In reply to mintsuki from comment #54)
> > (In reply to H.J. Lu from comment #52)
> > > (In reply to H.J. Lu from comment #51)
> > > > (In reply to mintsuki from comment #50)
> > > > 
> > > > > > Why can't you check DF_1_PIE for PIE?
> > > > > 
> > > > > That is what I do now, but to check for *relocatability*. PIE in and of
> > > > > itself is not something that tells me whether I should relocate (for KASLR
> > > > > for example) or not. That is what you just said.
> > > > 
> > > > If DF_1_PIE is set, the binary can be relocated to any address. What did I
> > > > miss?
> > > 
> > > The first PT_LOAD segment has non-0 p_vaddr, the program may misbehave if
> > > the load address != p_vaddr.
> > 
> > How is this possible? Under which circumstances? It works fine with all the
> > other linkers for me.
> 
> It works for your programs doesn't mean that it works all programs some of
> which
> won't correctly if ET_DYN is used.

Which? Under which circumstances? Do you have an example that doesn't involve Linux/glibc? Is this an actual ELF/toolchain issue, or is it some Linux/glibc specific issue? If the latter is the case, why on earth is this being worked around in binutils rather than fixed in Linux/glibc?
Comment 58 Adhemerval Zanella 2024-05-28 16:14:54 UTC
(In reply to mintsuki from comment #41)
> (In reply to Adhemerval Zanella from comment #39)
> > (In reply to Fangrui Song from comment #37)
> > > I agree with mintsuki . The "-pie -Ttext-segment=non-zero => ET_EXEC" hack
> > > should not be needed.
> > > 
> > > From https://sourceware.org/pipermail/binutils/2013-December/083381.html
> > > 
> > > > Linker sets e_type in ELF header to ET_DYN for -pie -Ttext-segment=0xXXX.
> > > > When I added -Ttext-segment=0xXXX, one goal was to load
> > > > small model executable above 4GB on Linux/x86-64, which
> > > > was done with -pie -Ttext-segment=0xXXX.  But -pie sets
> > > > e_type in ELF header to ET_DYN and kernel may ignore
> > > > p_vaddr in ELF header to load ET_DYN binary at a random
> > > > address.  This patch changes ld to set e_type in ELF header
> > > > to ET_EXEC if the first PT_LOAD segment has non-zero
> > > > p_vaddr.  If this is unacceptable as generic ELF change,
> > > > I can make it specific to x86.
> > > 
> > > Was the intention for the following command to load the text segment at an
> > > address >= 0x600000000000 ?
> > > 
> > > ```
> > > % cat a.c
> > > #include <stdio.h>
> > > int main() { printf("%p\n", main); }
> > > % gcc -pie -Wl,-no-pie a.c -fuse-ld=bfd
> > > -Wl,--no-relax,-Ttext-segment=0x600000000000 -o a
> > > % ./a
> > > 0x600000001139
> > > % ./a
> > > 0x600000001139  # no ASLR
> > > ```
> > > 
> > > Changing ET_DYN to ET_EXEC fulfills the address requirement but disables
> > > ASLR.
> > > Is it intentional?
> > 
> > That's my understanding of reading the -Ttext-segment documentation.  The
> > question is whether we relax the semantic to have it as a minimum address or
> > define it as the expected address (thus disabling ASLR as a consequence). 
> 
> My understanding is that the PT_LOAD PHDR addresses could be slid, as long
> as they are slid above the specified address. The fact that the first
> PT_LOAD PHDR is 0 or not should be irrelevant. It makes no sense for it to
> be relevant, let alone for it to dictate the ELF type to be ET_EXEC.
> 
> This is how Limine behaves, and how I interpret the ELF format.

Unfortunately, this is not what binutils documentation states ("When creating an ELF executable, it will set the address of the first byte of the text segment") nor how Linux handles it (where ET_DYN does not enforce e_entry).

And it seems to be a Linux-specific issue, since on FreeBSD:

$ cc -pie a.c -fuse-ld=lld -Wl,--no-relax,--image-base=0x600000000000 -Wl,-z,notext -o a-lld
$ readelf -h a-lld
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 09 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            FreeBSD
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices x86-64
  Version:                           0x1
  Entry point address:               0x6000000016b0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          12928 (bytes into file)
  Flags:                             0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         11
  Size of section headers:           64 (bytes)
  Number of section headers:         39
  Section header string table index: 37
$ readelf -d a-lld  | grep -w FLAGS_1
 0x000000006ffffffb FLAGS_1              PIE
$ doas sysctl -w kern.elf64.aslr.pie_enable=1
kern.elf64.aslr.pie_enable: 0 -> 1
$ ./a-lld
0x6000000019d0
$ ./a-lld
0x6000000019d0

So maybe we either enable this iff targeting Linux, or check if kernel is willing to enforce e_entry even for DYN. 

I am not sure if the correct approach is relaxing the '-Ttext-segment/--image-base' to be a minimum address.  For this, I would add another linker option.
Comment 59 mintsuki 2024-05-28 16:22:24 UTC
(In reply to Adhemerval Zanella from comment #58)
> (In reply to mintsuki from comment #41)
> > (In reply to Adhemerval Zanella from comment #39)
> > > (In reply to Fangrui Song from comment #37)
> > > > I agree with mintsuki . The "-pie -Ttext-segment=non-zero => ET_EXEC" hack
> > > > should not be needed.
> > > > 
> > > > From https://sourceware.org/pipermail/binutils/2013-December/083381.html
> > > > 
> > > > > Linker sets e_type in ELF header to ET_DYN for -pie -Ttext-segment=0xXXX.
> > > > > When I added -Ttext-segment=0xXXX, one goal was to load
> > > > > small model executable above 4GB on Linux/x86-64, which
> > > > > was done with -pie -Ttext-segment=0xXXX.  But -pie sets
> > > > > e_type in ELF header to ET_DYN and kernel may ignore
> > > > > p_vaddr in ELF header to load ET_DYN binary at a random
> > > > > address.  This patch changes ld to set e_type in ELF header
> > > > > to ET_EXEC if the first PT_LOAD segment has non-zero
> > > > > p_vaddr.  If this is unacceptable as generic ELF change,
> > > > > I can make it specific to x86.
> > > > 
> > > > Was the intention for the following command to load the text segment at an
> > > > address >= 0x600000000000 ?
> > > > 
> > > > ```
> > > > % cat a.c
> > > > #include <stdio.h>
> > > > int main() { printf("%p\n", main); }
> > > > % gcc -pie -Wl,-no-pie a.c -fuse-ld=bfd
> > > > -Wl,--no-relax,-Ttext-segment=0x600000000000 -o a
> > > > % ./a
> > > > 0x600000001139
> > > > % ./a
> > > > 0x600000001139  # no ASLR
> > > > ```
> > > > 
> > > > Changing ET_DYN to ET_EXEC fulfills the address requirement but disables
> > > > ASLR.
> > > > Is it intentional?
> > > 
> > > That's my understanding of reading the -Ttext-segment documentation.  The
> > > question is whether we relax the semantic to have it as a minimum address or
> > > define it as the expected address (thus disabling ASLR as a consequence). 
> > 
> > My understanding is that the PT_LOAD PHDR addresses could be slid, as long
> > as they are slid above the specified address. The fact that the first
> > PT_LOAD PHDR is 0 or not should be irrelevant. It makes no sense for it to
> > be relevant, let alone for it to dictate the ELF type to be ET_EXEC.
> > 
> > This is how Limine behaves, and how I interpret the ELF format.
> 
> Unfortunately, this is not what binutils documentation states ("When
> creating an ELF executable, it will set the address of the first byte of the
> text segment") nor how Linux handles it (where ET_DYN does not enforce
> e_entry).
> 
> And it seems to be a Linux-specific issue, since on FreeBSD:
> 
> $ cc -pie a.c -fuse-ld=lld -Wl,--no-relax,--image-base=0x600000000000
> -Wl,-z,notext -o a-lld
> $ readelf -h a-lld
> ELF Header:
>   Magic:   7f 45 4c 46 02 01 01 09 00 00 00 00 00 00 00 00
>   Class:                             ELF64
>   Data:                              2's complement, little endian
>   Version:                           1 (current)
>   OS/ABI:                            FreeBSD
>   ABI Version:                       0
>   Type:                              DYN (Shared object file)
>   Machine:                           Advanced Micro Devices x86-64
>   Version:                           0x1
>   Entry point address:               0x6000000016b0
>   Start of program headers:          64 (bytes into file)
>   Start of section headers:          12928 (bytes into file)
>   Flags:                             0
>   Size of this header:               64 (bytes)
>   Size of program headers:           56 (bytes)
>   Number of program headers:         11
>   Size of section headers:           64 (bytes)
>   Number of section headers:         39
>   Section header string table index: 37
> $ readelf -d a-lld  | grep -w FLAGS_1
>  0x000000006ffffffb FLAGS_1              PIE
> $ doas sysctl -w kern.elf64.aslr.pie_enable=1
> kern.elf64.aslr.pie_enable: 0 -> 1
> $ ./a-lld
> 0x6000000019d0
> $ ./a-lld
> 0x6000000019d0
> 
> So maybe we either enable this iff targeting Linux, or check if kernel is
> willing to enforce e_entry even for DYN. 
> 
> I am not sure if the correct approach is relaxing the
> '-Ttext-segment/--image-base' to be a minimum address.  For this, I would
> add another linker option.

I am not sure what you mean with "willing to enforce e_entry".

In any case, how about checking what the generated ELF file's OS/ABI is and only doing the DYN->EXEC hack if it happens to be GNU/Linux?
Comment 60 Adhemerval Zanella 2024-05-28 16:25:13 UTC
(In reply to mintsuki from comment #59)
> (In reply to Adhemerval Zanella from comment #58)
> > (In reply to mintsuki from comment #41)

> > So maybe we either enable this iff targeting Linux, or check if kernel is
> > willing to enforce e_entry even for DYN. 
> > 
> > I am not sure if the correct approach is relaxing the
> > '-Ttext-segment/--image-base' to be a minimum address.  For this, I would
> > add another linker option.
> 
> I am not sure what you mean with "willing to enforce e_entry".

I meant enforcing the vaddr set by e_entry for ET_DYN (and thus disabling ASLR), so this hack won't be necessary for Linux.

> 
> In any case, how about checking what the generated ELF file's OS/ABI is and
> only doing the DYN->EXEC hack if it happens to be GNU/Linux?

That was my 'So maybe we either enable this iff targeting Linux' suggestion was well.
Comment 61 mintsuki 2024-05-28 16:25:53 UTC
(In reply to mintsuki from comment #59)
> (In reply to Adhemerval Zanella from comment #58)
> > (In reply to mintsuki from comment #41)
> > > (In reply to Adhemerval Zanella from comment #39)
> > > > (In reply to Fangrui Song from comment #37)
> > > > > I agree with mintsuki . The "-pie -Ttext-segment=non-zero => ET_EXEC" hack
> > > > > should not be needed.
> > > > > 
> > > > > From https://sourceware.org/pipermail/binutils/2013-December/083381.html
> > > > > 
> > > > > > Linker sets e_type in ELF header to ET_DYN for -pie -Ttext-segment=0xXXX.
> > > > > > When I added -Ttext-segment=0xXXX, one goal was to load
> > > > > > small model executable above 4GB on Linux/x86-64, which
> > > > > > was done with -pie -Ttext-segment=0xXXX.  But -pie sets
> > > > > > e_type in ELF header to ET_DYN and kernel may ignore
> > > > > > p_vaddr in ELF header to load ET_DYN binary at a random
> > > > > > address.  This patch changes ld to set e_type in ELF header
> > > > > > to ET_EXEC if the first PT_LOAD segment has non-zero
> > > > > > p_vaddr.  If this is unacceptable as generic ELF change,
> > > > > > I can make it specific to x86.
> > > > > 
> > > > > Was the intention for the following command to load the text segment at an
> > > > > address >= 0x600000000000 ?
> > > > > 
> > > > > ```
> > > > > % cat a.c
> > > > > #include <stdio.h>
> > > > > int main() { printf("%p\n", main); }
> > > > > % gcc -pie -Wl,-no-pie a.c -fuse-ld=bfd
> > > > > -Wl,--no-relax,-Ttext-segment=0x600000000000 -o a
> > > > > % ./a
> > > > > 0x600000001139
> > > > > % ./a
> > > > > 0x600000001139  # no ASLR
> > > > > ```
> > > > > 
> > > > > Changing ET_DYN to ET_EXEC fulfills the address requirement but disables
> > > > > ASLR.
> > > > > Is it intentional?
> > > > 
> > > > That's my understanding of reading the -Ttext-segment documentation.  The
> > > > question is whether we relax the semantic to have it as a minimum address or
> > > > define it as the expected address (thus disabling ASLR as a consequence). 
> > > 
> > > My understanding is that the PT_LOAD PHDR addresses could be slid, as long
> > > as they are slid above the specified address. The fact that the first
> > > PT_LOAD PHDR is 0 or not should be irrelevant. It makes no sense for it to
> > > be relevant, let alone for it to dictate the ELF type to be ET_EXEC.
> > > 
> > > This is how Limine behaves, and how I interpret the ELF format.
> > 
> > Unfortunately, this is not what binutils documentation states ("When
> > creating an ELF executable, it will set the address of the first byte of the
> > text segment") nor how Linux handles it (where ET_DYN does not enforce
> > e_entry).
> > 
> > And it seems to be a Linux-specific issue, since on FreeBSD:
> > 
> > $ cc -pie a.c -fuse-ld=lld -Wl,--no-relax,--image-base=0x600000000000
> > -Wl,-z,notext -o a-lld
> > $ readelf -h a-lld
> > ELF Header:
> >   Magic:   7f 45 4c 46 02 01 01 09 00 00 00 00 00 00 00 00
> >   Class:                             ELF64
> >   Data:                              2's complement, little endian
> >   Version:                           1 (current)
> >   OS/ABI:                            FreeBSD
> >   ABI Version:                       0
> >   Type:                              DYN (Shared object file)
> >   Machine:                           Advanced Micro Devices x86-64
> >   Version:                           0x1
> >   Entry point address:               0x6000000016b0
> >   Start of program headers:          64 (bytes into file)
> >   Start of section headers:          12928 (bytes into file)
> >   Flags:                             0
> >   Size of this header:               64 (bytes)
> >   Size of program headers:           56 (bytes)
> >   Number of program headers:         11
> >   Size of section headers:           64 (bytes)
> >   Number of section headers:         39
> >   Section header string table index: 37
> > $ readelf -d a-lld  | grep -w FLAGS_1
> >  0x000000006ffffffb FLAGS_1              PIE
> > $ doas sysctl -w kern.elf64.aslr.pie_enable=1
> > kern.elf64.aslr.pie_enable: 0 -> 1
> > $ ./a-lld
> > 0x6000000019d0
> > $ ./a-lld
> > 0x6000000019d0
> > 
> > So maybe we either enable this iff targeting Linux, or check if kernel is
> > willing to enforce e_entry even for DYN. 
> > 
> > I am not sure if the correct approach is relaxing the
> > '-Ttext-segment/--image-base' to be a minimum address.  For this, I would
> > add another linker option.
> 
> I am not sure what you mean with "willing to enforce e_entry".
> 
> In any case, how about checking what the generated ELF file's OS/ABI is and
> only doing the DYN->EXEC hack if it happens to be GNU/Linux?

Actually I don't think that may be possible, at least not by checking the OS/ABI field alone, given on my Linux host it seems to be a generic UNIX - System V value...
Comment 62 mintsuki 2024-05-28 16:27:46 UTC
(In reply to Adhemerval Zanella from comment #60)
> (In reply to mintsuki from comment #59)
> > (In reply to Adhemerval Zanella from comment #58)
> > > (In reply to mintsuki from comment #41)
> 
> > > So maybe we either enable this iff targeting Linux, or check if kernel is
> > > willing to enforce e_entry even for DYN. 
> > > 
> > > I am not sure if the correct approach is relaxing the
> > > '-Ttext-segment/--image-base' to be a minimum address.  For this, I would
> > > add another linker option.
> > 
> > I am not sure what you mean with "willing to enforce e_entry".
> 
> I meant enforcing the vaddr set by e_entry for ET_DYN (and thus disabling
> ASLR), so this hack won't be necessary for Linux.

Ah, I see. Makes sense.
Comment 63 Adhemerval Zanella 2024-05-28 17:23:36 UTC
(In reply to mintsuki from comment #61)
> 
> Actually I don't think that may be possible, at least not by checking the
> OS/ABI field alone, given on my Linux host it seems to be a generic UNIX -
> System V value...

I think BFD can use the emulation (-m) option for this, since, for instance, gcc will pass -maarch64linux for aarch64-linux-gnu.
Comment 64 mintsuki 2024-05-28 17:40:05 UTC
(In reply to Adhemerval Zanella from comment #63)
> (In reply to mintsuki from comment #61)
> > 
> > Actually I don't think that may be possible, at least not by checking the
> > OS/ABI field alone, given on my Linux host it seems to be a generic UNIX -
> > System V value...
> 
> I think BFD can use the emulation (-m) option for this, since, for instance,
> gcc will pass -maarch64linux for aarch64-linux-gnu.

On my host system, gcc seems to just pass -m elf_x86_64, which is the same emulation mode we pass in the Limine template...
Comment 65 Fangrui Song 2024-05-28 18:06:55 UTC
> > Changing ET_DYN to ET_EXEC fulfills the address requirement but disables
> > ASLR.
> > Is it intentional?
> 
> That's my understanding of reading the -Ttext-segment documentation.  The question is whether we relax the semantic to have it as a minimum address or define it as the expected address (thus disabling ASLR as a consequence). 
> 
> I don't have a strong opinion, but currently, Linux only enforces the former (I think it is the main reason this makes some sense) so we will need to discuss with kernel developers the expected semantics.

If there is a strong need for the address requirement (>=p_vaddr), Linux kernel and glibc have the capability to implement it.
However, this alone does not justify keeping the ld hack that sets ET_EXEC for -pie -Ttext-segment=$non_zero.

> -Ttext-segment=0x600000000000 should create a binary which is guaranteed to be
> loaded at 0x600000000000.

-Ttext-segment sets the address of the first byte of the text segment, which likely influences the p_vaddr member of a PT_LOAD segment.
When e_type is ET_EXEC, this address is also the virtual address of the first memory area.
However, if e_type is ET_DYN, there's no guarantee of this address, and fulfilling this request is left to the discretion of the loaders.

Since ld offers the -no-pie flag, there's no need for a workaround to make -pie behave similarly.
(In addition, DF_1_PIE with ET_EXEC is very odd.)

If a user desires both address>=0x600000000000 && ASLR, this could be achieved if ET_DYN is used and loaders satisfy the address requirement.
However, retaining the ET_EXEC hack in ld would prevent the fulfillment of this goal.

> PIE is the only way to create a small mode executable loaded at 0x600000000000.

This is an oversimplification.

The -mcmodel= flag imposes specific code generation restrictions that allow relocatable files to be used in certain address space layouts after linking.
While it's (almost) a sufficient condition, it's not a necessary one.

Achieving high-address functionality doesn't necessitate -mcmodel=large. For
instance, you can use PIC symbol addressing (combining -mcmodel=small -fpie
with non-preemptible symbols) to achieve the same result.
If your code is larger than 2GiB, you can even use range extension thunks.

https://maskray.me/blog/2023-05-14-relocation-overflow-and-code-models#x86-64-code-models

    Similarly, for a function call, we no longer assume that the address of the function or its PLT entry is within the ±2GiB range from the program counter, so call callee cannot be used.
    
    Actually, call callee can still be used if we implement range extension thunks in the linker, unfortunately GCC/GNU ld did not pursue this direction.

> There are 2 issues with -mcmodel=large:
> 
> 1. Since there are no -mcmodel=large run-time libraries, you can't use -mcmodel=large
> to create any meaningful binaries.
> 2. -mcmodel=large performance is much slower.

True. (1) is an ecosystem issue with -mcmodel=large.
However, this point is unrelated to the ld ET_EXEC hack.

> I think BFD can use the emulation (-m) option for this, since, for instance, gcc will pass -maarch64linux for aarch64-linux-gnu.

I don't think this is necessary.

`-pie -Wl,-no-pie` works today (lld doesn't even need `--no-relax`).
Therefore, there isn't a strong argument retaining the ET_EXEC hack.

    % gcc -pie -Wl,-no-pie a.c -fuse-ld=bfd -Wl,--no-relax,-Ttext-segment=0x600000000000 -o a
    % ./a
    0x600000001139
    % ./a
    0x600000001139  # no ASLR
Comment 66 mintsuki 2024-05-29 12:43:16 UTC
(In reply to Fangrui Song from comment #65)
> > > Changing ET_DYN to ET_EXEC fulfills the address requirement but disables
> > > ASLR.
> > > Is it intentional?
> > 
> > That's my understanding of reading the -Ttext-segment documentation.  The question is whether we relax the semantic to have it as a minimum address or define it as the expected address (thus disabling ASLR as a consequence). 
> > 
> > I don't have a strong opinion, but currently, Linux only enforces the former (I think it is the main reason this makes some sense) so we will need to discuss with kernel developers the expected semantics.
> 
> If there is a strong need for the address requirement (>=p_vaddr), Linux
> kernel and glibc have the capability to implement it.
> However, this alone does not justify keeping the ld hack that sets ET_EXEC
> for -pie -Ttext-segment=$non_zero.
> 
> > -Ttext-segment=0x600000000000 should create a binary which is guaranteed to be
> > loaded at 0x600000000000.
> 
> -Ttext-segment sets the address of the first byte of the text segment, which
> likely influences the p_vaddr member of a PT_LOAD segment.
> When e_type is ET_EXEC, this address is also the virtual address of the
> first memory area.
> However, if e_type is ET_DYN, there's no guarantee of this address, and
> fulfilling this request is left to the discretion of the loaders.
> 
> Since ld offers the -no-pie flag, there's no need for a workaround to make
> -pie behave similarly.
> (In addition, DF_1_PIE with ET_EXEC is very odd.)

Indeed. This is not what lld does, at all.

Also the statement that PIE executables with non-0 load addresses would misbehave if relocated is also something that I cannot wrap my head around, especially since I have been relocating these for years without issues. Those generated by lld, gold, and bfd, the former 2 of which have absolutely no issue generating a PIE with non-0 load address.

Also I will change the title of the bug report from static PIE to PIE, because the PIE having or not having dynamically linked libraries is irrelevant to the issue at hand.

> 
> If a user desires both address>=0x600000000000 && ASLR, this could be
> achieved if ET_DYN is used and loaders satisfy the address requirement.
> However, retaining the ET_EXEC hack in ld would prevent the fulfillment of
> this goal.
> 
> > PIE is the only way to create a small mode executable loaded at 0x600000000000.
> 
> This is an oversimplification.
> 
> The -mcmodel= flag imposes specific code generation restrictions that allow
> relocatable files to be used in certain address space layouts after linking.
> While it's (almost) a sufficient condition, it's not a necessary one.
> 
> Achieving high-address functionality doesn't necessitate -mcmodel=large. For
> instance, you can use PIC symbol addressing (combining -mcmodel=small -fpie
> with non-preemptible symbols) to achieve the same result.
> If your code is larger than 2GiB, you can even use range extension thunks.
> 
> https://maskray.me/blog/2023-05-14-relocation-overflow-and-code-models#x86-
> 64-code-models
> 
>     Similarly, for a function call, we no longer assume that the address of
> the function or its PLT entry is within the ±2GiB range from the program
> counter, so call callee cannot be used.
>     
>     Actually, call callee can still be used if we implement range extension
> thunks in the linker, unfortunately GCC/GNU ld did not pursue this direction.
> 
> > There are 2 issues with -mcmodel=large:
> > 
> > 1. Since there are no -mcmodel=large run-time libraries, you can't use -mcmodel=large
> > to create any meaningful binaries.
> > 2. -mcmodel=large performance is much slower.
> 
> True. (1) is an ecosystem issue with -mcmodel=large.
> However, this point is unrelated to the ld ET_EXEC hack.
> 
> > I think BFD can use the emulation (-m) option for this, since, for instance, gcc will pass -maarch64linux for aarch64-linux-gnu.
> 
> I don't think this is necessary.
> 
> `-pie -Wl,-no-pie` works today (lld doesn't even need `--no-relax`).
> Therefore, there isn't a strong argument retaining the ET_EXEC hack.
> 
>     % gcc -pie -Wl,-no-pie a.c -fuse-ld=bfd
> -Wl,--no-relax,-Ttext-segment=0x600000000000 -o a
>     % ./a
>     0x600000001139
>     % ./a
>     0x600000001139  # no ASLR
Comment 67 mintsuki 2024-07-11 13:17:04 UTC
I'd like to give a summary of the issue, as the whole thread is pretty long and may get confusing:

ld.bfd, *regardless of target*, be it GNU/Linux or even *freestanding environments* (for example using an x86_64-elf binutils toolchain), generates perfectly fine relocatable PIE ELFs with proper PT_DYNAMIC segment and all, but with ELF type ET_EXEC, when the base load address is non-0, as this was added as some coarse workaround for some GNU/Linux specific issue.

This is a bug. It should be fixed, as ld.bfd doesn't exist in a bubble where it only builds executables for GNU/Linux necessitating some questionable workaround. Additionally, both ld.lld and ld.gold do not have this bug.

What's worse: this[1] issue was opened against LLD requesting that the bug be added to LLD!

Please, just revert the 2013 "workaround" for GNU/Linux or make it less coarse, and fix this bug.

References:
1. https://github.com/llvm/llvm-project/issues/93420
Comment 68 mintsuki 2024-08-07 08:38:50 UTC
Bug is still present in 2.43...
Comment 69 Nightishaman 2024-08-17 12:25:54 UTC
To be fair, we shouldn't still rely on a small hack to fix a small bug. We have specifications specifically to have predictability and this seems nowhere documented in the ELF specification. Also, I think a project such as binutils, which is used by many other operating system, shouldn't be hindered by GNU/Linux because it is more popular. If you were to apply such a hack inside the Linux kernel, Torvalds would reject the commit.

So please, remove this, and make ld.bfd more predictable like the other linkers.
Comment 70 mintsuki 2024-08-18 13:06:14 UTC
The bug is still present in 2.43.1. I also elevated the importance to "critical" since I was allowed to do so.