Skip to content
New issue

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

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

Already on GitHub? # 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.
# for free to join this conversation on GitHub. Already have an account? # 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