Skip to content
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

Building a no_std cdylib results in "syntax error in VERSION script" #63925

Closed
loganwendholt opened this issue Aug 26, 2019 · 13 comments
Closed
Labels
A-linkage Area: linking into static, shared libraries and binaries C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@loganwendholt
Copy link
Contributor

I'm trying to build a pure no_std cdylib with no external library dependencies. For an initial proof of concept, I tried to create such a library using the following code:

Cargo.toml:

[package]
name = "cdylib-no-std"
version = "0.1.0"
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]

src/lib.rs

#![no_std]
#![no_main]
#![feature(lang_items)]
use core::panic::PanicInfo;

pub fn foo(a: i32, b: i32) -> i32 {
  a + b
}

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

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

Built using the following command:
cargo +nightly build --lib

Results in the following output:

/usr/bin/ld:/tmp/rustcE1gs9P/list:4: syntax error in VERSION script
          collect2: error: ld returned 1 exit status

However, if crate-type is changed to ["dylib"], it builds properly.

Tested using the following tool versions:
cargo 1.39.0-nightly (3f700ec43 2019-08-19)

active toolchain
----------------

nightly-x86_64-unknown-linux-gnu (default)
rustc 1.39.0-nightly (521d78407 2019-08-25)

@loganwendholt
Copy link
Contributor Author

The closest related issue I could find was #50887, but it appears that the issue was closed without ever being directly addressed.

@jonas-schievink jonas-schievink added A-linkage Area: linking into static, shared libraries and binaries C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Aug 26, 2019
@jonas-schievink
Copy link
Contributor

Is this also random like #50887, or does it happen every time?

@loganwendholt
Copy link
Contributor Author

It happens every time for me. I would be interested in seeing if the issue is reproducible by others using the example code posted above.

@jonas-schievink
Copy link
Contributor

Does not happen for me, but I link with LLD

@loganwendholt
Copy link
Contributor Author

I installed LLD and ran RUSTFLAGS="-C linker-flavor=ld.lld" cargo +nightly build --lib, and this works for me as well.

@loganwendholt
Copy link
Contributor Author

I think the offending version script is being generated here:

fn export_symbols(&mut self, tmpdir: &Path, crate_type: CrateType) {
// Symbol visibility in object files typically takes care of this.
if crate_type == CrateType::Executable {
return;
}
// We manually create a list of exported symbols to ensure we don't expose any more.
// The object files have far more public symbols than we actually want to export,
// so we hide them all here.
if !self.sess.target.target.options.limit_rdylib_exports {
return;
}
if crate_type == CrateType::ProcMacro {
return
}
let mut arg = OsString::new();
let path = tmpdir.join("list");
debug!("EXPORTED SYMBOLS:");
if self.sess.target.target.options.is_like_osx {
// Write a plain, newline-separated list of symbols
let res: io::Result<()> = try {
let mut f = BufWriter::new(File::create(&path)?);
for sym in self.info.exports[&crate_type].iter() {
debug!(" _{}", sym);
writeln!(f, "_{}", sym)?;
}
};
if let Err(e) = res {
self.sess.fatal(&format!("failed to write lib.def file: {}", e));
}
} else {
// Write an LD version script
let res: io::Result<()> = try {
let mut f = BufWriter::new(File::create(&path)?);
writeln!(f, "{{\n global:")?;
for sym in self.info.exports[&crate_type].iter() {
debug!(" {};", sym);
writeln!(f, " {};", sym)?;
}
writeln!(f, "\n local:\n *;\n}};")?;
};
if let Err(e) = res {
self.sess.fatal(&format!("failed to write version script: {}", e));
}
}

@jonas-schievink
Copy link
Contributor

What does ld -v output? Can you grab the content of the generated file somehow?

@loganwendholt
Copy link
Contributor Author

I've been trying to come up with a way to grab the generated file, but it looks like the process cleans up the temp files before I can look at them.

ld version:

$ ld -v
GNU ld (GNU Binutils for Ubuntu) 2.30

@loganwendholt
Copy link
Contributor Author

I managed to set up an inotifywait script to grab any new files before they're deleted, and was able to capture the list file referenced in the error:

{
  global:

  local:
    *;
};

This is clearly being built in the following snippet:

// Write an LD version script
let res: io::Result<()> = try {
let mut f = BufWriter::new(File::create(&path)?);
writeln!(f, "{{\n global:")?;
for sym in self.info.exports[&crate_type].iter() {
debug!(" {};", sym);
writeln!(f, " {};", sym)?;
}
writeln!(f, "\n local:\n *;\n}};")?;
};

So it looks to me like some additional logic is needed to handle the case where

self.info.exports[&crate_type]

is empty.

@jonas-schievink
Copy link
Contributor

I can reproduce this when using normal binutils ld (2.32).

According to strace, the file only contains the bare minimum and no symbols:

3476  openat(AT_FDCWD, "/tmp/rustcxe3q7T/list", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 6
3476  write(6, "{\n  global:\n\n  local:\n    *;\n};\n", 32) = 32

@loganwendholt
Copy link
Contributor Author

The fact that no symbols were being exported threw me for a loop, until I realized that I needed to add some additional code to make the symbols come out correctly. I changed the original foo() function to

#[no_mangle]
pub extern "C" fn foo(a: i32, b: i32) -> i32 {
  a + b
}

Now, after building and running nm -g I can see the foo symbol:

$ nm -g target/debug/libmycdylib.so
                 w __cxa_finalize
0000000000000570 T foo
                 w __gmon_start__
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable

and, even better, I can now build without having to pass in RUSTFLAGS="-C linker-flavor=ld.lld" anymore. cargo +nightly build --lib works just fine now.

So this whole thing was caused by creating a cdylib with no symbols, which was definitely not my intent. Maybe some sort of warning or error message would be nice if this specific edge case is triggered?

@jonas-schievink
Copy link
Contributor

Yes, this should ideally just work (unless it isn't possible to link?)

@loganwendholt
Copy link
Contributor Author

Agreed, I'll try to get a PR put together in the next day or two.

Centril added a commit to Centril/rust that referenced this issue Aug 29, 2019
…michaelwoerister

Prevent syntax error in LD linker version script

As discussed in rust-lang#63925, there is an edge case in which an invalid LD version script is generated when building a `cdylib` with no exported symbols. This PR makes a slight modification to the  LD version script generation by first checking to see if any symbols need to be exported. If not, the `global` section of the linker script is simply omitted, and the syntax error is averted.
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 C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

2 participants