dtolnay / linkme

Safe cross-platform linker shenanigans
Apache License 2.0
628 stars 42 forks source link

make LINKME_PLEASE mutable to ensure writable flags on all relevant ELF sections #38

Closed ahl closed 3 years ago

ahl commented 3 years ago

The proximate problem this PR addresses is a failure on illumos demonstrated in this repo: https://github.com/ahl/linkme-test

In short: a library declares a distributed slice and a consumer of that library adds to that slice. On illumos the component of that distributed slice added by the consumer of the library is not present. We can see this in the absence of output from the test cmd.

Looking at ELF data on illumos reveals something interesting:

$ elfdump -c -N set_linkme_DOERS target/debug/cmd

Section Header[20]:  sh_name: set_linkme_DOERS
    sh_addr:      0x5b3068            sh_flags:   [ SHF_ALLOC ]
    sh_size:      0                   sh_type:    [ SHT_PROGBITS ]
    sh_offset:    0x1b3068            sh_entsize: 0
    sh_link:      0                   sh_info:    0
    sh_addralign: 0x8               

Section Header[28]:  sh_name: set_linkme_DOERS
    sh_addr:      0x5cc128            sh_flags:   [ SHF_WRITE SHF_ALLOC ]
    sh_size:      0x8                 sh_type:    [ SHT_PROGBITS ]
    sh_offset:    0x1bc128            sh_entsize: 0
    sh_link:      0                   sh_info:    0
    sh_addralign: 0x8               

We have not one, but two sections named set_linkme_DOERS. The first is zero-sized and not-writable; the second is 8 bytes and writable.

Looking at the object files and rlibs that went into this build, we see the same:

$ elfdump -c -N set_linkme_DOERS target/debug/deps/liblib-3d689bd64c3873d5.rlib 
target/debug/deps/liblib-3d689bd64c3873d5.rlib(lib-3d689bd64c3873d5.2vwi8dtk7ky9hvmq.rcgu.o):

Section Header[7]:  sh_name: set_linkme_DOERS
    sh_addr:      0                   sh_flags:   [ SHF_ALLOC ]
    sh_size:      0                   sh_type:    [ SHT_PROGBITS ]
    sh_offset:    0xc8                sh_entsize: 0
    sh_link:      0                   sh_info:    0
    sh_addralign: 0x8               

$ elfdump -c -N set_linkme_DOERS target/debug/incremental/cmd-w8yn2xhke56c/s-fwzp7udpq1-1agt1jj-121smxhf5scli/*.o
target/debug/incremental/cmd-w8yn2xhke56c/s-fwzp7udpq1-1agt1jj-121smxhf5scli/1m65hci6o5zni2mr.o:

Section Header[17]:  sh_name: set_linkme_DOERS
    sh_addr:      0                   sh_flags:   [ SHF_WRITE SHF_ALLOC ]
    sh_size:      0x8                 sh_type:    [ SHT_PROGBITS ]
    sh_offset:    0xf8                sh_entsize: 0
    sh_link:      0                   sh_info:    0
    sh_addralign: 0x8               

The rlib where the slice is declared is zero-bytes and non-writeable; the .o where we add to the slice is 8 bytes and writable. The illumos linker sees these as two different sections due to the difference in ELF flags so we end up with two different sections in the resulting binary.

On Linux, the test program works, and the ELF data is as expected:

$ readelf -S target/debug/cmd | grep -A1 linkme_DOERS
  [28] linkme_DOERS      PROGBITS         0000000000047050  00046050
       0000000000000008  0000000000000000  WA       0     0     8

Note that the section is writable (W) for reasons not readily apparent. Looking at the .o and .rlib files that went into the final binary we see the same disparity on Linux as we did on illumos:

$ readelf -S target/debug/deps/liblib-dbcc34a6ce485a29.rlib | grep -A1 linkme_DOERS
readelf: Error: Not an ELF file - it has the wrong magic bytes at the start
  [ 7] linkme_DOERS      PROGBITS         0000000000000000  000000d0
       0000000000000000  0000000000000000   A       0     0     8
$ readelf -S target/debug/incremental/cmd-3e55fmylgk0vb/s-fwzozwzwjv-zngw3r-3ojl9o3cuvpk4/*.o | grep -A1 linkme_DOERS
  [13] linkme_DOERS      PROGBITS         0000000000000000  000000c8
       0000000000000008  0000000000000000  WA       0     0     8

Note that lib where the slice is defined is not writeable whereas the .o where we add to the slice is writeable.


With the fix applied:

illumos

$ elfdump -c -N set_linkme_DOERS target/debug/deps/liblib-*.rlib 
target/debug/deps/liblib-c98febdfc141550b.rlib(lib-c98febdfc141550b.2o3xp1c7sa5vh54b.rcgu.o):

Section Header[7]:  sh_name: set_linkme_DOERS
    sh_addr:      0                   sh_flags:   [ SHF_WRITE SHF_ALLOC ]
    sh_size:      0                   sh_type:    [ SHT_PROGBITS ]
    sh_offset:    0xc8                sh_entsize: 0
    sh_link:      0                   sh_info:    0
    sh_addralign: 0x8             

$ elfdump -c -N set_linkme_DOERS target/debug/incremental/cmd-*/*/*.o
target/debug/incremental/cmd-3usn3y2iy1h21/s-fwzpq2djgj-1kym4gy-3ohhm8qr14hnn/3jh0d7z62f805s47.o:

Section Header[17]:  sh_name: set_linkme_DOERS
    sh_addr:      0                   sh_flags:   [ SHF_WRITE SHF_ALLOC ]
    sh_size:      0x8                 sh_type:    [ SHT_PROGBITS ]
    sh_offset:    0xf8                sh_entsize: 0
    sh_link:      0                   sh_info:    0
    sh_addralign: 0x8               

$ elfdump -c -N set_linkme_DOERS target/debug/cmd

Section Header[27]:  sh_name: set_linkme_DOERS
    sh_addr:      0x5cc0f8            sh_flags:   [ SHF_WRITE SHF_ALLOC ]
    sh_size:      0x8                 sh_type:    [ SHT_PROGBITS ]
    sh_offset:    0x1bc0f8            sh_entsize: 0
    sh_link:      0                   sh_info:    0
    sh_addralign: 0x8               

The final binary has a single section with the right length and size; we get the expected output from the program.

Linux

$ readelf -S target/debug/deps/liblib-*.rlib | grep -A1 linkme_DOERS
  [ 7] linkme_DOERS      PROGBITS         0000000000000000  000000d0
       0000000000000000  0000000000000000  WA       0     0     8
$ readelf -S target/debug/incremental/cmd-*/*/*.o | grep -A1 linkme_DOERS
  [13] linkme_DOERS      PROGBITS         0000000000000000  000000c8
       0000000000000008  0000000000000000  WA       0     0     8
$ readelf -S target/debug/cmd | grep -A1 linkme_DOERS
  [28] linkme_DOERS      PROGBITS         0000000000047050  00046050
       0000000000000008  0000000000000000  WA       0     0     8

The rlib section is now writable; the final binary is unchanged (i.e. same length and flags).


Note that since the modified line is only relevant on illumos and Linux, no other examination seems to be warranted.

I'm not sure how to incorporate a test for this, but would be happy to do so with some guidance.

dtolnay commented 3 years ago

Published in 0.2.5.