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

funcs::posix::fcntl::open from libc magically becomes a public symbol #26640

Closed
Tobba opened this issue Jun 28, 2015 · 5 comments
Closed

funcs::posix::fcntl::open from libc magically becomes a public symbol #26640

Tobba opened this issue Jun 28, 2015 · 5 comments
Labels
T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@Tobba
Copy link
Contributor

Tobba commented Jun 28, 2015

Minimal test case:

#![feature(no_std, lang_items, core, libc)]
#![no_std]

extern crate core;
extern crate libc;

use core::fmt;

#[lang="panic_fmt"]
extern fn panic_fmt(msg: &fmt::Arguments, file: &'static str, line: u32) -> ! {
        loop { }
}

#[lang="stack_exhausted"]
extern fn stack_exhausted() {
        loop { }
}

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

Compile with:

rustc --crate-type staticlib lib.rs -C lto --emit asm -C opt-level=2
@geofft
Copy link
Contributor

geofft commented Aug 30, 2015

Seems to be because on UNIX, open is indirected through a compile-time shim because of #22862 (open is varargs on Darwin). Therefore libc::open is actually a Rust-language function, marked extern to maintain ABI with older versions of liblibc, and any pub extern functions are considered public symbols.

If instead of libc you import this crate:

#![feature(no_std, core)]
#![no_std]
extern crate core;

pub fn burger() {}
extern {
    pub fn fries();
}
pub extern fn drink() {}

the resulting assembly has a public symbol for drink, but not for burger or fries.

I suppose the real bug here is that if a library crate depends on a second library crate with a pub extern fn, it's assumed live. This is entirely reasonable for non-mangled functions, so that you can write a crate that helps you implement some C-ABI plugin interface that's expecting particular names. But there's probably no good reason for it for mangled ones: the only way to export such a function, I think, is through passing a pointer to it somewhere, so the symbol doesn't need to be live (and referencing the function pointer will keep the code live, which is all you care about).

I can think of the following workarounds (slash alternatives, if it's determined this is the right behavior for even for mangled pub extern fns):

  1. Instead of implementing the libc::open wrapper in Rust, implement it in C in rt/rust_builtin.c. There's precedent for this in for libc::opendir and libc::readdir_r, ten lines below.
  2. Implement the wrapper on Darwin but not other UNIXes, since it is technically only needed there (that is, replace the non-Darwin case with pub use open_shim::open. That will solve your bug everywhere except Darwin.

@steveklabnik steveklabnik added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Mar 8, 2017
@Mark-Simulacrum
Copy link
Member

I can't seem to reproduce today (assembly output here: https://gist.github.com/Mark-Simulacrum/be225370d9b7e52fd946cd4eccedb9da), so I'm going to close. I don't see any reference to open on both macOS and on Ubuntu. I'm going to close -- if this is wrong, let us know and we'll reopen. Ideally let us know with exact instructions to reproduce.

@geofft
Copy link
Contributor

geofft commented Jun 16, 2017

I forget the state of this in 2015 exactly, but it looks like this particular bug got fixed for cdylib libraries (which are new since then), but we have a much larger bug with staticlibs, which is what the bug was originally reported for: not only does drink get exported, but so does burger and all of libcore.

To put this in a "what I tried, what I expected, what happened" format: I want to be able to compile a C-ABI-compatible library that exports C functions for use by C (or languages that can FFI to C), and implement it in Rust, without the fact that it's written in Rust leaking out. In 2015, I think the only thing that leaked (either static or dynamic) was the mangled symbol for libc::open. Currently everything leaks in static libraries, but it looks fine for dynamic libraries built with cdylib.

Here's my reproducer:

meal.rs (simulating the libc crate, in the above context)

#![no_std]

pub fn burger() {}
extern {
    pub fn fries();
}
pub extern fn drink() {}

diner.rs (library written in Rust, using libmeal as a stand-in for liblibc)

#![feature(lang_items)]
#![no_std]
extern crate meal;

#[lang="panic_fmt"]
extern fn panic_fmt(_msg: &core::fmt::Arguments, _file: &'static str, _line: u32) -> ! {
    loop { }
}

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

#[no_mangle]
pub extern fn libdiner_actually_public_function() -> i32 {
    17
}
titan:/tmp geofft$ rustup run nightly rustc --version
rustc 1.19.0-nightly (258ae6dd9 2017-06-15)
titan:/tmp geofft$ rustup run nightly rustc --crate-type=lib meal.rs
titan:/tmp geofft$ rustup run nightly rustc --crate-type staticlib diner.rs -L.
titan:/tmp geofft$ nm libdiner.a | grep libdiner
0000000000000000 T libdiner_actually_public_function
titan:/tmp geofft$ nm libdiner.a | grep burger
0000000000000000 T _ZN4meal6burger17h3224d595766d9dc8E
titan:/tmp geofft$ nm libdiner.a | grep fries
titan:/tmp geofft$ nm libdiner.a | grep drink
0000000000000000 T _ZN4meal5drink17h7624a2a00a5c235dE
titan:/tmp geofft$ nm libdiner.a | grep -c _ZN4core
589
titan:/tmp geofft$ rustup run nightly rustc --crate-type cdylib diner.rs -L.
titan:/tmp geofft$ nm -D libdiner.so 
                 w __cxa_finalize
                 w __gmon_start__
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 w _Jv_RegisterClasses
0000000000000570 T libdiner_actually_public_function

That is, I'm compiling libdiner into a C-ABI-compatible static library and shared library, and expect my only public function to be libdiner_actually_public_function(). This works as expected for the cdylib, but not for the staticlib: all the symbols from libmeal and libcore are also exported by my static library.

For what it's worth, building a regular dylib causes similar output to the staticlib case (slightly fewer libcore symbols, but they still show up, as do both burger and drink). So maybe the answer is just that we need a cstaticlib, parallel to cdylib?

Given the fact that drink isn't showing up in the cdylib, my guess is that this particular bug about libc::open has been solved, but it's being masked by the fact that everything is getting exported into staticlibs, which wasn't previously the case.

@Mark-Simulacrum
Copy link
Member

I believe the libcore/libstd being exposed is #33221, so I'll leave this closed -- does that seem correct?

@geofft
Copy link
Contributor

geofft commented Jun 18, 2017

Yup, thanks for finding that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

4 participants