llvm / llvm/llvm-project

[lld] confusing STT_FILE entries when symbols in relocatable objects are localized

Open
#191,478 3 comments 0 reactions 0 assignees View on GitHub
lld:ELF
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

I have a project where symbols are localized with a linker version script at link time, and also, some objects are combined using `ld -r` before the final link. After the build, there's a tool that looks at symbol tables using `readelf -Ws` and tries to match up local symbols with the file they came from. In some cases, this tool sees an incorrect file.

A reproducer example is below. Here I was trying to compare what the symbol table looks like with separate .o vs combined .o, and also the behavior of the bfd linker vs lld.

My impression is that with lld, localized symbols are placed together with originally local symbols from the input file they came from, but if the input file was the result of relocatable link and contains multiple FILE entries, it leads to this confusing output below where `foo_localized` looks like it came from `bar.c`.

We see that the bfd linker creates synthetic empty FILE entries and keeps localized symbols together. In that case we can't see which file the localized symbols came from, but at least we don't get this confusing output. Plus I guess the empty FILE entry lets us know which symbols were originally global, which could be useful information.

I'm not sure if this would be considered a bug, but just thought I would report it, I found it surprising.

### Example

foo.c

```c
static void foo_originally_local(void) {}
void foo_localized(void) { foo_originally_local(); }
void foo_exported(void) {}
```

bar.c
```c
static void bar_originally_local(void) {}
void bar_localized(void) { bar_originally_local(); }
void bar_exported(void) {}
```

my.ldvs

```
my {
global:
*_exported;
local: *;
};
```

Makefile

```make
CLANG := clang # tested with 22.1.0

foo.o: foo.c
$(CLANG) -g -c -fPIC $^ -o $@

bar.o: bar.c
$(CLANG) -g -c -fPIC $^ -o $@

foobar.o: foo.o bar.o
$(CLANG) -fuse-ld=lld -r $^ -o $@

LDFLAGS := -shared -Wl,--version-script=my.ldvs

libfoobar-individual-lld.so: foo.o bar.o
$(CLANG) $(LDFLAGS) -fuse-ld=lld $^ -o $@

libfoobar-individual-ld.so: foo.o bar.o
$(CLANG) $(LDFLAGS) $^ -o $@

libfoobar-combined-lld.so: foobar.o
$(CLANG) $(LDFLAGS) -fuse-ld=lld $^ -o $@

libfoobar-combined-ld.so: foobar.o
$(CLANG) $(LDFLAGS) $^ -o $@
```

Summary of the results:

`readelf -Ws libfoobar-individual-lld.so`
```
11: 0000000000000000 0 FILE LOCAL DEFAULT ABS foo.c
12: 0000000000001700 6 FUNC LOCAL DEFAULT 12 foo_originally_local
13: 00000000000016f0 11 FUNC LOCAL DEFAULT 12 foo_localized
14: 0000000000000000 0 FILE LOCAL DEFAULT ABS bar.c
15: 0000000000001730 6 FUNC LOCAL DEFAULT 12 bar_originally_local
16: 0000000000001720 11 FUNC LOCAL DEFAULT 12 bar_localized
28: 0000000000001710 6 FUNC GLOBAL DEFAULT 12 foo_exported
29: 0000000000001740 6 FUNC GLOBAL DEFAULT 12 bar_exported
```

`readelf -Ws libfoobar-combined-lld.so`
```
11: 0000000000000000 0 FILE LOCAL DEFAULT ABS foo.c
12: 0000000000001700 6 FUNC LOCAL DEFAULT 12 foo_originally_local
13: 0000000000000000 0 FILE LOCAL DEFAULT ABS bar.c
14: 0000000000001730 6 FUNC LOCAL DEFAULT 12 bar_originally_local
15: 00000000000016f0 11 FUNC LOCAL DEFAULT 12 foo_localized
16: 0000000000001720 11 FUNC LOCAL DEFAULT 12 bar_localized
28: 0000000000001710 6 FUNC GLOBAL DEFAULT 12 foo_exported
29: 0000000000001740 6 FUNC GLOBAL DEFAULT 12 bar_exported
```

`readelf -Ws libfoobar-individual-ld.so`
```
40: 0000000000000000 0 FILE LOCAL DEFAULT ABS foo.c
41: 00000000000005a0 6 FUNC LOCAL DEFAULT 12 foo_originally_local
42: 0000000000000000 0 FILE LOCAL DEFAULT ABS bar.c
43: 00000000000005d0 6 FUNC LOCAL DEFAULT 12 bar_originally_local
46: 0000000000000000 0 FILE LOCAL DEFAULT ABS
47: 00000000000005c0 11 FUNC LOCAL DEFAULT 12 bar_localized
50: 0000000000000590 11 FUNC LOCAL DEFAULT 12 foo_localized
60: 00000000000005e0 6 FUNC GLOBAL DEFAULT 12 bar_exported
62: 00000000000005b0 6 FUNC GLOBAL DEFAULT 12 foo_exported
```

`readelf -Ws libfoobar-combined-ld.so`
```
40: 0000000000000000 0 FILE LOCAL DEFAULT ABS foo.c
41: 00000000000005a0 6 FUNC LOCAL DEFAULT 12 foo_originally_local
42: 0000000000000000 0 FILE LOCAL DEFAULT ABS bar.c
43: 00000000000005d0 6 FUNC LOCAL DEFAULT 12 bar_originally_local
46: 0000000000000000 0 FILE LOCAL DEFAULT ABS
47: 00000000000005c0 11 FUNC LOCAL DEFAULT 12 bar_localized
50: 0000000000000590 11 FUNC LOCAL DEFAULT 12 foo_localized
60: 00000000000005e0 6 FUNC GLOBAL DEFAULT 12 bar_exported
62: 00000000000005b0 6 FUNC GLOBAL DEFAULT 12 foo_exported
```

Contributor guide

Open the contributing guide

Research direction

Start by running the supplied Makefile reproducer with lld and readelf -Ws, comparing the individual and combined outputs. Trace lld's handling of STT_FILE entries and localized symbols from relocatable inputs; done when the shown cases no longer produce a misleading symbol-to-file association and are covered by tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.