Linker script and PT_PHDR not working as expected (x86_64)

Discussions on more advanced topics such as monolithic vs micro-kernels, transactional memory models, and paging vs segmentation should go here. Use this forum to expand and improve the wiki!
Post Reply
kzinti
Member
Member
Posts: 898
Joined: Mon Feb 02, 2015 7:11 pm

Linker script and PT_PHDR not working as expected (x86_64)

Post by kzinti »

Consider this snippet from my linker script for ia32:

Code: Select all

PHDRS
{
    phdr_text   PT_LOAD FLAGS(5);           /* read + execute */
    phdr_rodata PT_LOAD FLAGS(4);           /* read */
    phdr_data   PT_LOAD FLAGS(6);           /* read + write */
    phdr_tls    PT_TLS  FLAGS(4);
}

SECTIONS
{
    . = 0x00010000;

    .text :
    {
        *(.text*)
    } :phdr_text
...
This produces the following program headers in my ELF file:

Code: Select all

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001000 0x00010000 0x00010000 0x3ee02 0x3ee02 R E 0x1000
  LOAD           0x040000 0x0004f000 0x0004f000 0x12ebf 0x12ebf R   0x1000
  LOAD           0x053000 0x00062000 0x00062000 0x00258 0x00898 RW  0x1000
  TLS            0x053258 0x00063000 0x00063000 0x00000 0x00060 R   0x4
Now I want to include the program headers in my image so that user space can walk them (needed by TLS code). I change the PHDRS section as follow:

Code: Select all

PHDRS
{
    phdr_phdrs  PT_PHDR PHDRS;
    phdr_text   PT_LOAD PHDRS FLAGS(5);     /* read + execute */
    phdr_rodata PT_LOAD FLAGS(4);           /* read */
    phdr_data   PT_LOAD FLAGS(6);           /* read + write */
    phdr_tls    PT_TLS  FLAGS(4);
}


SECTIONS
{
    . = 0x00010000 + SIZEOF_HEADERS;

    .text :
    {
        *(.text*)
    } :phdr_text
This produces the following in my ELF file:

Code: Select all

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00010034 0x00010034 0x000a0 0x000a0 R   0x4
  LOAD           0x000034 0x00010034 0x00010034 0x3eeae 0x3eeae R E 0x1000
  LOAD           0x03f000 0x0004f000 0x0004f000 0x12ebf 0x12ebf R   0x1000
  LOAD           0x052000 0x00062000 0x00062000 0x00258 0x00898 RW  0x1000
  TLS            0x052258 0x00063000 0x00063000 0x00000 0x00060 R   0x4
This works as expected and I am happy with it.

My problem is on x86_64 where I basically use identical linker scripts (except for OUTPUT_FORMAT and OUTPUT_ARCH). The result of applying the changes doesn't result in the same behaviour.

Without PT_PHDR:

Code: Select all

Program Headers:
  Type           Offset             VirtAddr           PhysAddr                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000010000 0x0000000000010000 0x0000000000010000       0x0000000000046782 0x0000000000046782  R E    0x200000
  LOAD           0x0000000000057000 0x0000000000057000 0x0000000000057000       0x000000000001c69b 0x000000000001c69b  R      0x200000
  LOAD           0x0000000000074000 0x0000000000074000 0x0000000000074000       0x0000000000000308 0x0000000000000ee0  RW     0x200000
  TLS            0x0000000000074308 0x0000000000075000 0x0000000000075000       0x0000000000000000 0x00000000000000b0  R      0x8
With PT_PHDR:

Code: Select all

Program Headers:
  Type           Offset             VirtAddr           PhysAddr                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040       0x0000000000000118 0x0000000000000118  R      0x8
  LOAD           0x0000000000000040 0x0000000000000040 0x0000000000000040       0x00000000000568a2 0x00000000000568a2  R E    0x200000
  LOAD           0x0000000000057000 0x0000000000057000 0x0000000000057000       0x000000000001c69b 0x000000000001c69b  R      0x200000
  LOAD           0x0000000000074000 0x0000000000074000 0x0000000000074000       0x0000000000000308 0x0000000000000ee0  RW     0x200000
  TLS            0x0000000000074308 0x0000000000075000 0x0000000000075000       0x0000000000000000 0x00000000000000b0  R      0x8
Notice how the VMA of the PHDR header is not at 0x10040 as I would expect it to be. What's more, the size of the first LOAD segment increased by roughly 0x10000. It looks like the linker wasn't smart enough to put the PHDR at VMA 0x10000 and instead added 0x10000 bytes of padding between the PHDR info and the start of my code (.text).

If I don't set the program counter at all and base my image at 0, it works the same in both cases (ia32 and x86_64). This results in an image based at 0. But I'd like my image to start at 0x10000 as I don't want to be relocating executables.

Anyone has ideas here and/or a different way to approach this?
nullplan
Member
Member
Posts: 1767
Joined: Wed Aug 30, 2017 8:24 am

Re: Linker script and PT_PHDR not working as expected (x86_6

Post by nullplan »

I had the same problem with the bloody linker, until I added "-z max-page-size=0x1000" to the linker command line. (That is, "-Wl,-z,max-page-size=0x1000" for GCC). This is because the linker somehow starts using 2MB pages, but fails to recognize that 4kB pages would fit the linker script better. No clue how it does realize this in 32-bit mode. Anyway, fixing the page size really helped.
Carpe diem!
sj95126
Member
Member
Posts: 151
Joined: Tue Aug 11, 2020 12:14 pm

Re: Linker script and PT_PHDR not working as expected (x86_6

Post by sj95126 »

It's a reasonable assumption on the linker's part that if you're generating a 32-bit binary, there's no guarantee your target environment will support anything more than 4K pages. The 32-bit linear space isn't huge, so you don't want to waste it putting a 10-20-30k section in a multi-MB block.

However, 64-bit always supports 2MB pages, so it's safe in assuming that.
kzinti
Member
Member
Posts: 898
Joined: Mon Feb 02, 2015 7:11 pm

Re: Linker script and PT_PHDR not working as expected (x86_6

Post by kzinti »

nullplan wrote:I had the same problem with the bloody linker, until I added "-z max-page-size=0x1000" to the linker command line. (That is, "-Wl,-z,max-page-size=0x1000" for GCC). This is because the linker somehow starts using 2MB pages, but fails to recognize that 4kB pages would fit the linker script better. No clue how it does realize this in 32-bit mode. Anyway, fixing the page size really helped.
OMG. It did cross my mind at some point last night that I might need this flag, but I was too tired and went to bed. I completely forgot about it until I saw your comment today.

It did indeed fix my problem. Thanks so much nullplan.
Post Reply