Skip to content

regression: UB with extern fn() -> ! and LTO #39253

@japaric

Description

@japaric

STR

$ cargo new --lib ub && cd $_

$ edit src/lib.rs && cat $_
#![feature(lang_items)]
#![no_std]

use core::ptr;

#[no_mangle]
pub unsafe fn _start() {
    extern "C" {
        fn main() -> !;
    }

    // Just to have "something" before `main`
    ptr::read_volatile(0x0 as *const usize);

    main();
}

// stubs
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}

#[lang = "panic_fmt"]
extern "C" fn panic_fmt() {}
$ edit examples/app.rs && cat $_
#![no_std]
#![no_main]

extern crate ub;

#[no_mangle]
pub fn main() -> ! {
    loop {}
}

Compiling with LTO produces:

$ cargo rustc --release --example app -- -C lto -C link-args=-nostartfiles

$ objdump -Cd target/release/examples/app
Disassembly of section .text:

00000000000002c0 <_start>:
 2c0:   48 8b 04 25 00 00 00    mov    0x0,%rax
 2c7:   00

Note that the infinite loop is missing. This program will execute stuff that's not in the .text section.

Whereas compiling without LTO produces:

$ cargo rustc --release --example app -- -C link-args=-nostartfiles

$ objdump -Cd target/release/examples/app
Disassembly of section .text:

00000000000002c0 <main>:
 2c0:   eb fe                   jmp    2c0 <main>
 2c2:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 2c9:   00 00 00
 2cc:   0f 1f 40 00             nopl   0x0(%rax)

00000000000002d0 <_start>:
 2d0:   50                      push   %rax
 2d1:   48 8b 04 25 00 00 00    mov    0x0,%rax
 2d8:   00
 2d9:   e8 e2 ff ff ff          callq  2c0 <main>

Both main and the infinite loop are preserved.

Meta

$ rustc -V
rustc 1.16.0-nightly (a52da95ce 2017-01-20)

This is a regression because nightly-2016-08-01 (I haven't bisected) preserved the infinite loop in presence of LTO.

$ cargo +nightly-2016-08-01 rustc --release --example app -- -C lto -C link-args=-nostartfiles

$ objdump -Cd target/release/examples/app
Disassembly of section .text:

00000000000002c0 <_start>:
 2c0:   48 8b 04 25 00 00 00    mov    0x0,%rax
 2c7:   00
 2c8:   0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
 2cf:   00
 2d0:   eb fe                   jmp    2d0 <_start+0x10>

Looking at the emitted object files, before they are sent to the linker, reveals more information:

$ cargo rustc --release --example app -- -C lto --emit=obj -C link-arg=-nostartfiles

$ objdump -Cd ./target/release/examples/app-e892a9c04992a722.o
Disassembly of section .text.main:

0000000000000000 <main>:
   0:   eb fe                   jmp    0 <main>

Disassembly of section .text._start:

0000000000000000 <_start>:
   0:   48 8b 04 25 00 00 00    mov    0x0,%rax
   7:   00

Note that today _start is simply not calling main at all.

$ cargo +nightly-2016-08-01 rustc --release --example app -- -C lto -C link-args=-nostartfiles --emit=obj

$ objdump -Cd ./target/release/examples/app.o
Disassembly of section .text.main:

0000000000000000 <main>:
   0:   eb fe                   jmp    0 <main>

Disassembly of section .text._start:

0000000000000000 <_start>:
   0:   48 8b 04 25 00 00 00    mov    0x0,%rax
   7:   00
   8:   0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
   f:   00
  10:   eb fe                   jmp    10 <_start+0x10>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions