Skip to content

feat: Support PROVIDE and PROVIDE_HIDDEN linker script directives - #1525

Merged
lapla-cogito merged 2 commits into
wild-linker:mainfrom
lapla-cogito:provide_directive
Feb 11, 2026
Merged

lapla-cogito merged 2 commits into
wild-linker:mainfrom
lapla-cogito:provide_directive

Conversation

@lapla-cogito

Copy link
Copy Markdown
Member

The PROVIDE directive defines a symbol with lower priority than even weak definitions. The symbol is only used if no other definition (strong or weak) exists. PROVIDE_HIDDEN additionally prevents the symbol from being exported.

close #1097

extern char __data_start __attribute__((weak));
extern char __data_end __attribute__((weak));

int test_data = 42;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the reason I'm keeping this PR in draft. If you comment out this line, the .data section becomes empty, and wild will fail to link. This is a clear issue that needs to be addressed.

---- integration_test::program_name_055___linker_script_provide_c__ stdout ----
Error: Test failed: `linker-script-provide.c` with linker `wild` config `default`
  Caused by:
    libwild reported error. Rerun command(s):
        cargo run --bin wild -- -dynamic-linker /lib64/ld-linux-x86-64.so.2 --gc-sections -shared -T /home/lapla/repos/wild/wild/tests/sources/linker-script-provide.ld -o /home/lapla/repos/wild/wild/tests/build/linker-script-provide.c/default-host/linker-script-provide.c.wild /home/lapla/repos/wild/wild/tests/build/linker-script-provide.c/default-host/linker-script-provide.aeb0bf64dfeb9435.o --validate-output --write-layout --write-trace --dependency-file=/home/lapla/repos/wild/wild/tests/build/linker-script-provide.c/default-host/linker-script-provide.c.wild.deps
    Failed copying from /home/lapla/repos/wild/wild/tests/sources/linker-script-provide.ld to output file
    symbol '__data_start' in section '`.data`' that we're not going to output Resolution { raw_value: 0, dynamic_symbol_index: Some(5), got_address: None, plt_address: None, flags: ValueFlags(NON_INTERPOSABLE | DIRECT | EXPORT_DYNAMIC | PROVIDE) }

After checking how other linkers handle this situation, I found that GNU ld appears to resolve symbols referenced by PROVIDE to point to the next section (=.dynamic in here) when the .data section is empty:

(▰╹◡╹)❯  gcc -c wild/tests/sources/linker-script-provide.c -o /tmp/ld-no-data.o && ld -shared -T wild/tests/sources/linker-script-provide.ld /tmp/no-data-test.o -o /tmp/ld-no-data.so && readelf -sSW /tmp/ld-no-data.so|grep -E "data_start|data_end|.dynamic"
/usr/bin/x86_64-linux-gnu-ld.bfd: warning: /tmp/ld-no-data.so has a LOAD segment with RWX permissions
  [ 8] .dynamic          DYNAMIC         0000000000000168 001168 0000e0 10  WA  4   0  8
     2: 0000000000000168     0 NOTYPE  GLOBAL DEFAULT    8 __data_start
     3: 0000000000000168     0 NOTYPE  GLOBAL DEFAULT    8 __data_end
     6: 0000000000000168     0 NOTYPE  GLOBAL DEFAULT    8 __data_end
     7: 0000000000000168     0 NOTYPE  GLOBAL DEFAULT    8 __data_start

However, this seems very strange to me - I don't see any positive motivation for doing it this way.

In fact, lld appears to output sections referenced via linker scripts even when they are empty. This approach seems natural to me.

(▰╹◡╹)❯  gcc -c wild/tests/sources/linker-script-provide.c -o /tmp/lld-no-data.o && ld.lld -shared -T /tmp/test-location.ld /tmp/lld-no-data.o -o /tmp/no-data-test-lld.so && readelf -sSW /tmp/no-data-test-lld.so | grep -E "data_start|data_end|.data"
  [11] .data             PROGBITS        00000000000004d0 0014d0 000000 00  WA  0   0  1
     7: 00000000000003d6    36 FUNC    GLOBAL DEFAULT    8 get_data_size
     8: 00000000000004d0     0 NOTYPE  GLOBAL DEFAULT   11 __data_end
     9: 00000000000004d0     0 NOTYPE  GLOBAL DEFAULT   11 __data_start
    10: 00000000000003fa     0 NOTYPE  GLOBAL DEFAULT    8 __before_data
    11: 00000000000004d0     0 NOTYPE  GLOBAL DEFAULT   11 __after_data
    10: 00000000000003d6    36 FUNC    GLOBAL DEFAULT    8 get_data_size
    11: 00000000000004d0     0 NOTYPE  GLOBAL DEFAULT   11 __data_end
    12: 00000000000004d0     0 NOTYPE  GLOBAL DEFAULT   11 __data_start
    13: 00000000000003fa     0 NOTYPE  GLOBAL DEFAULT    8 __before_data
    14: 00000000000004d0     0 NOTYPE  GLOBAL DEFAULT   11 __after_data

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that keeping sections associated with a PROVIDE symbol that is referenced makes sense. We have code that does that for regular symbols - https://github.com/davidlattimore/wild/blob/6a7660ba30b24b6214ab7fa301bde38ce52ac137/libwild/src/layout.rs#L3876

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All right, thanks for double-checking: ada108f

@davidlattimore davidlattimore left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great to have this implemented!

Comment thread libwild/src/value_flags.rs Outdated

/// This symbol is from a PROVIDE directive in a linker script. It should only be used if
/// the symbol is referenced but not defined elsewhere.
const PROVIDE = 1 << 15;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our last available bit. I guess if we need another bit we'll need to either expand to 32 bits and incur the associated performance penalty (about a 1.3% slowdown on the zed benchmark) or refactor to free a bit. Probably PROVIDE could be done without using a bit, since it looks like the only place the bit is used is when selecting alternatives.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way to achieve this without adding new bits to ValueFlags would be to add a method to SymbolDb to determine whether a symbol is a PROVIDE symbol. However, this would mean the method would be called every time select_symbol() is invoked, which raises concerns about potential performance impact. However indeed the current implementation consumes the last bit of ValueFlags, let me think a bit more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way would be the collect up a list of all PROVIDE symbols, then before alternative symbols are selected, look up each of the PROVIDEd symbols to see if there is a definition. If there is, do nothing, if there isn't, then add the symbol.

@lapla-cogito lapla-cogito Feb 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized that symbol_strength() returns SymbolStrength::Undefined for linker script symbols, making it possible to implement this much more simply. e0894b7

extern char __data_start __attribute__((weak));
extern char __data_end __attribute__((weak));

int test_data = 42;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that keeping sections associated with a PROVIDE symbol that is referenced makes sense. We have code that does that for regular symbols - https://github.com/davidlattimore/wild/blob/6a7660ba30b24b6214ab7fa301bde38ce52ac137/libwild/src/layout.rs#L3876

@lapla-cogito
lapla-cogito force-pushed the provide_directive branch 3 times, most recently from 594be36 to 749eb2c Compare February 10, 2026 11:29
@lapla-cogito
lapla-cogito marked this pull request as ready for review February 10, 2026 11:32

@davidlattimore davidlattimore left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@lapla-cogito
lapla-cogito merged commit db42208 into wild-linker:main Feb 11, 2026
20 checks passed
@lapla-cogito
lapla-cogito deleted the provide_directive branch February 11, 2026 04:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

linker-scripts: Add support for PROVIDE

2 participants