Skip to content

[PowerPC64] Rust performs improper function call linkage when using LLVM's linker #85589

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
DrChat opened this issue May 22, 2021 · 3 comments · Fixed by #142598
Closed

[PowerPC64] Rust performs improper function call linkage when using LLVM's linker #85589

DrChat opened this issue May 22, 2021 · 3 comments · Fixed by #142598
Labels
A-linkage Area: linking into static, shared libraries and binaries A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. C-bug Category: This is a bug. O-PowerPC Target: PowerPC processors T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@DrChat
Copy link
Contributor

DrChat commented May 22, 2021

With this code:

#![feature(no_core, lang_items, rustc_attrs, repr_simd)]
#![crate_type = "bin"]
#![no_core]
#![no_main]

#[lang = "sized"]
trait Sized {}
#[lang = "copy"]
trait Copy {}

#[no_mangle]
pub fn my_rad_unmangled_function() {
    loop {}
}

pub fn my_rad_function() {
    loop {}
}

#[no_mangle]
pub extern fn _start() {
    my_rad_unmangled_function();
    my_rad_function();
}

And this compiler invocation:

$ rustc --version
rustc 1.54.0-nightly (3e99439f4 2021-05-17)
$ rustc --target=powerpc64-unknown-linux-gnu -C linker=lld -C relocation-model=static .\min.rs

Rust will emit code with the ELFv1 ABI, which dispatches function calls to a function descriptor table in the .opd section for later fixup by the linker.

As per this LLVM thread, they mention lld does not support the ELFv1 ABI, and will not perform the necessary function call fixups.
As such, it appears that Rust somehow suffers from the same bug that clang suffered from, wherein it is not explicitly marking binaries as using the ELFv1 ABI, and LLVM is not detecting the ELFv1 ABI.

This causes Rust to output an invalid binary:

$ llvm-objdump -d .\min

.\min:  file format ELF64-ppc64


Disassembly of section .text:

0000000010010000 .text:
10010000: 48 00 00 04                   b .+4
10010004: 48 00 00 00                   b .+0
                ...
10010014: 48 00 00 04                   b .+4
10010018: 48 00 00 00                   b .+0
                ...
10010028: 7c 08 02 a6                   mflr 0
1001002c: f8 01 00 10                   std 0, 16(1)
10010030: f8 21 ff 91                   stdu 1, -112(1)
10010034: 48 01 ff cd                   bl .+131020 # <----------------- branch to .opd!
10010038: 60 00 00 00                   nop
1001003c: 48 01 ff dd                   bl .+131036 # <----------------- branch to .opd!
10010040: 60 00 00 00                   nop
10010044: 38 21 00 70                   addi 1, 1, 112
10010048: e8 01 00 10                   ld 0, 16(1)
1001004c: 7c 08 03 a6                   mtlr 0
10010050: 4e 80 00 20                   blr
                ...
$ llvm-nm .\min
0000000010028000 d .TOC.
0000000010030018 d _ZN3min15my_rad_function17h0209550c6e3677ccE
0000000010030030 D _start
0000000010030000 D my_rad_unmangled_function

Testing reveals that linking with GNU's powerpc64-linux-gnu-ld linker yields a correct binary:

$ llvm-objdump -d .\min                                                                                                        
.\min:  file format ELF64-ppc64


Disassembly of section .text:

0000000010000158 .text:
10000158: 48 00 00 04                   b .+4
1000015c: 48 00 00 00                   b .+0
                ...
1000016c: 48 00 00 04                   b .+4
10000170: 48 00 00 00                   b .+0
                ...
10000180: 7c 08 02 a6                   mflr 0
10000184: f8 01 00 10                   std 0, 16(1)
10000188: f8 21 ff 91                   stdu 1, -112(1)
1000018c: 4b ff ff cd                   bl .-52 # <--------------------- branches to code!
10000190: 60 00 00 00                   nop
10000194: 4b ff ff d9                   bl .-40 # <--------------------- branches to code!
10000198: 60 00 00 00                   nop
1000019c: 38 21 00 70                   addi 1, 1, 112
100001a0: e8 01 00 10                   ld 0, 16(1)
100001a4: 7c 08 03 a6                   mtlr 0
100001a8: 4e 80 00 20                   blr
                ...
$ llvm-nm .\min 
0000000010027f00 d .TOC.
000000001001ffd0 d _ZN3min15my_rad_function17h0209550c6e3677ccE
00000000100001b8 r __GNU_EH_FRAME_HDR
0000000010020000 D __bss_start
0000000010020000 D _edata
0000000010020000 D _end
000000001001ffe8 D _start
000000001001ffb8 D my_rad_unmangled_function
@DrChat DrChat added the C-bug Category: This is a bug. label May 22, 2021
@DrChat DrChat changed the title [PowerPC64] Rust performs improper linkage [PowerPC64] Rust performs improper function call linkage May 22, 2021
@DrChat DrChat changed the title [PowerPC64] Rust performs improper function call linkage [PowerPC64] Rust performs improper function call linkage when using LLVM's linker May 22, 2021
@workingjubilee workingjubilee added F-rustc_attrs Internal rustc attributes gated on the `#[rustc_attrs]` feature gate. O-PowerPC Target: PowerPC processors A-linkage Area: linking into static, shared libraries and binaries A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. and removed F-rustc_attrs Internal rustc attributes gated on the `#[rustc_attrs]` feature gate. labels Mar 14, 2023
@Noratrieb Noratrieb added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Apr 5, 2023
@ostylk
Copy link
Contributor

ostylk commented Jun 6, 2025

The LLVM thread link is dead (it's also not archived by Wayback Machine :/).

Unfortunately today I have also run into this issue...

@workingjubilee
Copy link
Member

Should we just migrate PPC64 on Linux to the ELFv2 ABI?

@ostylk
Copy link
Contributor

ostylk commented Jun 7, 2025

The same issue already occurred for the guys at zig (ziglang/zig#5927). They solved it by generating ppc64 binaries only with ELFv2 ABI: https://github.com/ziglang/zig/pull/21310/files .

The real fix would most likely be to implement ELFv1 ABI in lld (especially because other llvm tools support ELFv1, causing this bug). However, since ELFv1 is basically dead it probably won't happen, see e.g. llvm/llvm-project#27630 (or llvm/llvm-project#50927).

I think forcing to use ELFv2 for ppc64 targets is fine. If that is not desired (too hacky?), it would be worth it so set the e_flags of ELFv1 object files correctly (e_flags=1) such that lld errors out instead of generating a broken binary (https://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/PPC64.cpp#L642). lld seems to interpret e_flags=0 as ELFv2.

jhpratt added a commit to jhpratt/rust that referenced this issue Jun 17, 2025
…workingjubilee

Set elf e_flags on ppc64 targets according to abi

(This PR contains the non user-facing changes of rust-lang#142321)

Fixes rust-lang#85589 by making sure that ld.lld errors out instead of generating a broken binary.

Basically the problem is that ld.lld assumes that all ppc64 object files with e_flags=0 are object files which use the ELFv2 ABI (this here is the check https://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/PPC64.cpp#L639).
This pull request sets the correct e_flags to indicate the used ABI so ld.lld errors out when encountering ELFv1 ABI files instead of generating a broken binary.

For example compare code generation for this program (file name ``min.rs``):
```rust
#![feature(no_core, lang_items, repr_simd)]
#![crate_type = "bin"]
#![no_core]
#![no_main]

#[lang = "sized"]
trait Sized {}
#[lang = "copy"]
trait Copy {}
#[lang = "panic_cannot_unwind"]
pub fn panic() -> ! {
    loop {}
}

pub fn my_rad_unmangled_function() {
    loop {}
}

pub fn my_rad_function() {
    loop {}
}

#[no_mangle]
pub fn _start() {
    my_rad_unmangled_function();
    my_rad_function();
}
```
Compile with ``rustc --target=powerpc64-unknown-linux-gnu -C linker=ld.lld -C relocation-model=static min.rs``

Before change:
```
$ llvm-objdump -d min
Disassembly of section .text:
000000001001030c <.text>:
		...
10010334: 7c 08 02 a6  	mflr 0
10010338: f8 21 ff 91  	stdu 1, -112(1)
1001033c: f8 01 00 80  	std 0, 128(1)
10010340: 48 02 00 39  	bl 0x10030378 <_ZN3min25my_rad_unmangled_function17h7471c49af58039f5E>
10010344: 60 00 00 00  	nop
10010348: 48 02 00 49  	bl 0x10030390 <_ZN3min15my_rad_function17h37112b8fd1008c9bE>
1001034c: 60 00 00 00  	nop
		...
```
The branch instructions ``bl 0x10030378`` and ``bl 0x10030390`` are jumping into the ``.opd`` section which is data. That is a broken binary (because fixing those branches is the task of the linker).

After change:
```
error: linking with `ld.lld` failed: exit status: 1
  |
  = note:  "ld.lld" "/tmp/rustcNYKZCS/symbols.o" "<1 object files omitted>" "--as-needed" "-L" "/tmp/rustcNYKZCS/raw-dylibs" "-Bdynamic" "--eh-frame-hdr" "-z" "noexecstack" "-L" "<sysroot>/lib/rustlib/powerpc64-unknown-linux-gnu/lib" "-o" "min" "--gc-sections" "-z" "relro" "-z" "now"
  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: ld.lld: error: /tmp/rustcNYKZCS/symbols.o: ABI version 1 is not supported
```
Which is correct because ld.lld doesn't support ELFv1 ABI.
workingjubilee added a commit to workingjubilee/rustc that referenced this issue Jun 17, 2025
…workingjubilee

Set elf e_flags on ppc64 targets according to abi

(This PR contains the non user-facing changes of rust-lang#142321)

Fixes rust-lang#85589 by making sure that ld.lld errors out instead of generating a broken binary.

Basically the problem is that ld.lld assumes that all ppc64 object files with e_flags=0 are object files which use the ELFv2 ABI (this here is the check https://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/PPC64.cpp#L639).
This pull request sets the correct e_flags to indicate the used ABI so ld.lld errors out when encountering ELFv1 ABI files instead of generating a broken binary.

For example compare code generation for this program (file name ``min.rs``):
```rust
#![feature(no_core, lang_items, repr_simd)]
#![crate_type = "bin"]
#![no_core]
#![no_main]

#[lang = "sized"]
trait Sized {}
#[lang = "copy"]
trait Copy {}
#[lang = "panic_cannot_unwind"]
pub fn panic() -> ! {
    loop {}
}

pub fn my_rad_unmangled_function() {
    loop {}
}

pub fn my_rad_function() {
    loop {}
}

#[no_mangle]
pub fn _start() {
    my_rad_unmangled_function();
    my_rad_function();
}
```
Compile with ``rustc --target=powerpc64-unknown-linux-gnu -C linker=ld.lld -C relocation-model=static min.rs``

Before change:
```
$ llvm-objdump -d min
Disassembly of section .text:
000000001001030c <.text>:
		...
10010334: 7c 08 02 a6  	mflr 0
10010338: f8 21 ff 91  	stdu 1, -112(1)
1001033c: f8 01 00 80  	std 0, 128(1)
10010340: 48 02 00 39  	bl 0x10030378 <_ZN3min25my_rad_unmangled_function17h7471c49af58039f5E>
10010344: 60 00 00 00  	nop
10010348: 48 02 00 49  	bl 0x10030390 <_ZN3min15my_rad_function17h37112b8fd1008c9bE>
1001034c: 60 00 00 00  	nop
		...
```
The branch instructions ``bl 0x10030378`` and ``bl 0x10030390`` are jumping into the ``.opd`` section which is data. That is a broken binary (because fixing those branches is the task of the linker).

After change:
```
error: linking with `ld.lld` failed: exit status: 1
  |
  = note:  "ld.lld" "/tmp/rustcNYKZCS/symbols.o" "<1 object files omitted>" "--as-needed" "-L" "/tmp/rustcNYKZCS/raw-dylibs" "-Bdynamic" "--eh-frame-hdr" "-z" "noexecstack" "-L" "<sysroot>/lib/rustlib/powerpc64-unknown-linux-gnu/lib" "-o" "min" "--gc-sections" "-z" "relro" "-z" "now"
  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: ld.lld: error: /tmp/rustcNYKZCS/symbols.o: ABI version 1 is not supported
```
Which is correct because ld.lld doesn't support ELFv1 ABI.
workingjubilee added a commit to workingjubilee/rustc that referenced this issue Jun 17, 2025
…workingjubilee

Set elf e_flags on ppc64 targets according to abi

(This PR contains the non user-facing changes of rust-lang#142321)

Fixes rust-lang#85589 by making sure that ld.lld errors out instead of generating a broken binary.

Basically the problem is that ld.lld assumes that all ppc64 object files with e_flags=0 are object files which use the ELFv2 ABI (this here is the check https://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/PPC64.cpp#L639).
This pull request sets the correct e_flags to indicate the used ABI so ld.lld errors out when encountering ELFv1 ABI files instead of generating a broken binary.

For example compare code generation for this program (file name ``min.rs``):
```rust
#![feature(no_core, lang_items, repr_simd)]
#![crate_type = "bin"]
#![no_core]
#![no_main]

#[lang = "sized"]
trait Sized {}
#[lang = "copy"]
trait Copy {}
#[lang = "panic_cannot_unwind"]
pub fn panic() -> ! {
    loop {}
}

pub fn my_rad_unmangled_function() {
    loop {}
}

pub fn my_rad_function() {
    loop {}
}

#[no_mangle]
pub fn _start() {
    my_rad_unmangled_function();
    my_rad_function();
}
```
Compile with ``rustc --target=powerpc64-unknown-linux-gnu -C linker=ld.lld -C relocation-model=static min.rs``

Before change:
```
$ llvm-objdump -d min
Disassembly of section .text:
000000001001030c <.text>:
		...
10010334: 7c 08 02 a6  	mflr 0
10010338: f8 21 ff 91  	stdu 1, -112(1)
1001033c: f8 01 00 80  	std 0, 128(1)
10010340: 48 02 00 39  	bl 0x10030378 <_ZN3min25my_rad_unmangled_function17h7471c49af58039f5E>
10010344: 60 00 00 00  	nop
10010348: 48 02 00 49  	bl 0x10030390 <_ZN3min15my_rad_function17h37112b8fd1008c9bE>
1001034c: 60 00 00 00  	nop
		...
```
The branch instructions ``bl 0x10030378`` and ``bl 0x10030390`` are jumping into the ``.opd`` section which is data. That is a broken binary (because fixing those branches is the task of the linker).

After change:
```
error: linking with `ld.lld` failed: exit status: 1
  |
  = note:  "ld.lld" "/tmp/rustcNYKZCS/symbols.o" "<1 object files omitted>" "--as-needed" "-L" "/tmp/rustcNYKZCS/raw-dylibs" "-Bdynamic" "--eh-frame-hdr" "-z" "noexecstack" "-L" "<sysroot>/lib/rustlib/powerpc64-unknown-linux-gnu/lib" "-o" "min" "--gc-sections" "-z" "relro" "-z" "now"
  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: ld.lld: error: /tmp/rustcNYKZCS/symbols.o: ABI version 1 is not supported
```
Which is correct because ld.lld doesn't support ELFv1 ABI.
workingjubilee added a commit to workingjubilee/rustc that referenced this issue Jun 17, 2025
…workingjubilee

Set elf e_flags on ppc64 targets according to abi

(This PR contains the non user-facing changes of rust-lang#142321)

Fixes rust-lang#85589 by making sure that ld.lld errors out instead of generating a broken binary.

Basically the problem is that ld.lld assumes that all ppc64 object files with e_flags=0 are object files which use the ELFv2 ABI (this here is the check https://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/PPC64.cpp#L639).
This pull request sets the correct e_flags to indicate the used ABI so ld.lld errors out when encountering ELFv1 ABI files instead of generating a broken binary.

For example compare code generation for this program (file name ``min.rs``):
```rust
#![feature(no_core, lang_items, repr_simd)]
#![crate_type = "bin"]
#![no_core]
#![no_main]

#[lang = "sized"]
trait Sized {}
#[lang = "copy"]
trait Copy {}
#[lang = "panic_cannot_unwind"]
pub fn panic() -> ! {
    loop {}
}

pub fn my_rad_unmangled_function() {
    loop {}
}

pub fn my_rad_function() {
    loop {}
}

#[no_mangle]
pub fn _start() {
    my_rad_unmangled_function();
    my_rad_function();
}
```
Compile with ``rustc --target=powerpc64-unknown-linux-gnu -C linker=ld.lld -C relocation-model=static min.rs``

Before change:
```
$ llvm-objdump -d min
Disassembly of section .text:
000000001001030c <.text>:
		...
10010334: 7c 08 02 a6  	mflr 0
10010338: f8 21 ff 91  	stdu 1, -112(1)
1001033c: f8 01 00 80  	std 0, 128(1)
10010340: 48 02 00 39  	bl 0x10030378 <_ZN3min25my_rad_unmangled_function17h7471c49af58039f5E>
10010344: 60 00 00 00  	nop
10010348: 48 02 00 49  	bl 0x10030390 <_ZN3min15my_rad_function17h37112b8fd1008c9bE>
1001034c: 60 00 00 00  	nop
		...
```
The branch instructions ``bl 0x10030378`` and ``bl 0x10030390`` are jumping into the ``.opd`` section which is data. That is a broken binary (because fixing those branches is the task of the linker).

After change:
```
error: linking with `ld.lld` failed: exit status: 1
  |
  = note:  "ld.lld" "/tmp/rustcNYKZCS/symbols.o" "<1 object files omitted>" "--as-needed" "-L" "/tmp/rustcNYKZCS/raw-dylibs" "-Bdynamic" "--eh-frame-hdr" "-z" "noexecstack" "-L" "<sysroot>/lib/rustlib/powerpc64-unknown-linux-gnu/lib" "-o" "min" "--gc-sections" "-z" "relro" "-z" "now"
  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: ld.lld: error: /tmp/rustcNYKZCS/symbols.o: ABI version 1 is not supported
```
Which is correct because ld.lld doesn't support ELFv1 ABI.
rust-timer added a commit that referenced this issue Jun 17, 2025
Rollup merge of #142598 - ostylk:fix/ppc64_llvmabi, r=nikic,workingjubilee

Set elf e_flags on ppc64 targets according to abi

(This PR contains the non user-facing changes of #142321)

Fixes #85589 by making sure that ld.lld errors out instead of generating a broken binary.

Basically the problem is that ld.lld assumes that all ppc64 object files with e_flags=0 are object files which use the ELFv2 ABI (this here is the check https://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/PPC64.cpp#L639).
This pull request sets the correct e_flags to indicate the used ABI so ld.lld errors out when encountering ELFv1 ABI files instead of generating a broken binary.

For example compare code generation for this program (file name ``min.rs``):
```rust
#![feature(no_core, lang_items, repr_simd)]
#![crate_type = "bin"]
#![no_core]
#![no_main]

#[lang = "sized"]
trait Sized {}
#[lang = "copy"]
trait Copy {}
#[lang = "panic_cannot_unwind"]
pub fn panic() -> ! {
    loop {}
}

pub fn my_rad_unmangled_function() {
    loop {}
}

pub fn my_rad_function() {
    loop {}
}

#[no_mangle]
pub fn _start() {
    my_rad_unmangled_function();
    my_rad_function();
}
```
Compile with ``rustc --target=powerpc64-unknown-linux-gnu -C linker=ld.lld -C relocation-model=static min.rs``

Before change:
```
$ llvm-objdump -d min
Disassembly of section .text:
000000001001030c <.text>:
		...
10010334: 7c 08 02 a6  	mflr 0
10010338: f8 21 ff 91  	stdu 1, -112(1)
1001033c: f8 01 00 80  	std 0, 128(1)
10010340: 48 02 00 39  	bl 0x10030378 <_ZN3min25my_rad_unmangled_function17h7471c49af58039f5E>
10010344: 60 00 00 00  	nop
10010348: 48 02 00 49  	bl 0x10030390 <_ZN3min15my_rad_function17h37112b8fd1008c9bE>
1001034c: 60 00 00 00  	nop
		...
```
The branch instructions ``bl 0x10030378`` and ``bl 0x10030390`` are jumping into the ``.opd`` section which is data. That is a broken binary (because fixing those branches is the task of the linker).

After change:
```
error: linking with `ld.lld` failed: exit status: 1
  |
  = note:  "ld.lld" "/tmp/rustcNYKZCS/symbols.o" "<1 object files omitted>" "--as-needed" "-L" "/tmp/rustcNYKZCS/raw-dylibs" "-Bdynamic" "--eh-frame-hdr" "-z" "noexecstack" "-L" "<sysroot>/lib/rustlib/powerpc64-unknown-linux-gnu/lib" "-o" "min" "--gc-sections" "-z" "relro" "-z" "now"
  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: ld.lld: error: /tmp/rustcNYKZCS/symbols.o: ABI version 1 is not supported
```
Which is correct because ld.lld doesn't support ELFv1 ABI.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-linkage Area: linking into static, shared libraries and binaries A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. C-bug Category: This is a bug. O-PowerPC Target: PowerPC processors T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants