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

RFC: Add no_entry attribute to omit entry point symbol #2735

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions text/0000-no-entry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
- Feature Name: `no_entry`
- Start Date: 2019-07-23
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)

# Summary
[summary]: #summary

Add a new top-level attribute `no_entry`, to omit the platform entry point
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
symbol, allowing the user to write their own.
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved

# Motivation
[motivation]: #motivation

For some low-level systems-programming use cases, a user needs to have full
control of a process from the very first instruction, by specifying the
entry-point symbol. Examples include kernels or firmware where the entry-point
symbol may represent initial boot code, applications running under an operating
system but with unusual startup requirements, or programs creating or running
in sandbox environments.

The target toolchain will commonly supply an entry-point symbol (e.g.
`_start`) by default. As a result, attempting to define an entry-point symbol
typically results in a link error. Addressing this currently requires linking
manually with an toolchain-specific argument like `-nostartfiles`.

This proposal introduces a top-level attribute `no_entry`, which will cause the
Rust toolchain to omit the entry-point symbol. Specifying `#![no_entry]` allows
(and requires) the user to supply such a symbol.

(Please note that this is entirely distinct from the Rust-supplied startup hook
`#[start]`.)
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

Specifying the attribute `#![no_entry]` at the top level of a Rust binary crate
will cause Rust to omit the entry-point symbol when compiling and linking the
binary. When linking, Rust will pass any necessary target-specific arguments to
the toolchain to omit the target's entry-point code, such as `-nostartfiles`.

Binaries built with this attribute set will typically not link unless the user
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elaborate on typically?

supplies their own entry-point symbol.

Libraries cannot use this attribute; attempting to do so will produce a
compile-time error stating that `no_entry` only applies to binaries.

A binary built with this attribute set could supply the entry-point symbol
itself, or have the entry-point symbol supplied by a library crate or by a
non-Rust library.

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

Until stabilized, the `no_entry` attribute will require a feature-gate named
`no_entry`.

The `no_entry` attribute may be specified as part of a `cfg_attr` declaration,
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
to omit the entry point in some configurations but not others.
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved

Specifying the `no_entry` attribute on anything other than the top-level module
of a binary crate will produce an error (`warning: crate-level attribute should
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
be in the root module`).

Declaring an entry-point symbol will typically require a declaration with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elaborate re. typically.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Centril I understood "typically" here to mean the same thing as it does, for example, when we say that main is typically a function. The symbol must exist, whether its a function, a static, or "something else" in some future version of Rust is up to the user.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gnzlbg sure; I'm asking for textual changes tho to clarify what it means. :)

`#[no_mangle] pub extern "C"`.
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved

To implement `no_entry`, the compiler can have a default linker argument based
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
on the type of linker in use, such as `-nostartfiles` for GCC or clang/LLD.
Specific targets which require more specific options can provide those options
via the target configuration structures.

Some targets may be able to support invoking the C library without running the
startup code provided by the C library; other targets may not support this, or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Examples of both include?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think any C library supports being invoked without its initialization code. One job of the initialization code is to initialize thread-local storage for the main thread, which is needed for errno in particular. This means that calling a C library function that sets errno (which is pretty much all of them) will most likely segfault due to TLS not being initialized.

have limitations on such support. Targets that do not support this should
document the target-specific limitations of `no_entry` for their target, may
require `no_std` in addition to `no_entry`, or may not support `no_entry` at
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also entails that supporting no_entry is optional for a Rust compiler in terms of static semantics and that the attribute has primarily implementation defined semantics, right? Would be good to spell that out if so.

Copy link
Contributor

@gnzlbg gnzlbg Oct 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also entails that supporting no_entry is optional for a Rust compiler in terms of static semantics and that the attribute has primarily implementation defined semantics, right?

More than "optional", I understand this document to specify the behavior of #![no_entry] as implementation-defined, meaning that the implementation must document what the behavior is.

Often, the language spec constraints the sets of behaviors that can be implemented for implementation-defined behavior, and I believe that it is in the "spirit" of this document to constraint the behaviors that a Rust implementation can provide to:

  • compilation error if the implementation does not support #![no_entry] for a target
  • compilation error if the implementation does support #![no_entry] for a target but using it requires #![no_std]
  • #![no_entry] does not instantaneously trigger a compilation error, but is handled according to the semantics of the target, which must be documented by the implementation for the particular target according to the text above. This might mean that compilation might fail later, e.g., if some symbol is missing.

That is, a Rust implementation cannot just choose to completely ignore #![no_entry] and, e.g., not being even able to parse it. It must explicitly support it in a minimal way at least, however, what #![no_entry] actually then does will depend on the implementation and the target.

all.

On targets that already don't provide an entry point, the compiler will
silently accept `no_entry`. On targets that cannot support `no_entry`, the
compiler will emit a compile-time error.

joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
# Drawbacks
[drawbacks]: #drawbacks

Rust has several existing attributes related to program startup, and this would
require further careful documentation to distinguish them. However, no existing
attribute serves the function that `no_entry` does.

# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives

Today, people do this either by linking their binary manually, or by specifying
non-portable linker arguments in `.cargo/config`. Introducing the `no_entry`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To ascertain the motivation/rationale of this proposal relative to the complexity cost to the compiler (not users) it would be good to include examples of both to see how much of a hassle existing solutions are. Examples could be included in an appendix to this RFC or inline depending on how much space it takes textually.

attribute substantially simplifies creating such programs, and avoids
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
attribute substantially simplifies creating such programs, and avoids
attribute simplifies creating such programs, and avoids

Provide said examples and let them speak for themselves. :)

target-specific build steps.

The name `no_entry` was selected specifically to minimize conflict and
confusion with other Rust features such as `start`, `no_main`, and `main`, none
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To better understand the cost of this proposal, I would like to see an appendix of all the current knobs (stable and unstable, with a note of which is which) regarding startup / entrypoint behavior. Ideally appendix would include a "flow chart" description.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To better understand the cost of this proposal, I would like to see an appendix of all the current knobs (stable and unstable, with a note of which is which) regarding startup / entrypoint behavior

I think it would be reasonable to have all the current knobs documented in a central place somewhere (e.g. the rustc user guide). I think it would be worth it to open an issue in rust-lang/rust about this. Once that happens, this and future RFCs can just reference to those docs and mention the additions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that also works.

of which relate to the entry-point symbol.

We could potentially integrate this mechanism with specification of an
alternate entry point. However, this would make it difficult to flexibly
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elaborate re. what that might look like?

provide the entry point from a library or other mechanism.

We could make this a rustc command-line option, rather than an attribute.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
We could make this a rustc command-line option, rather than an attribute.
We could make this a `rustc` command-line option, rather than an attribute.

However, that would separate the configuration required to build a crate from
the crate itself. We have substantial precedent for including such
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is true but enumerate some examples nonetheless?

configuration directly in the crate as an attribute.

# Prior art
[prior-art]: #prior-art

We currently have the `no_main` attribute to omit a C `main` function (not the
entry point symbol), the `main` attribute to specify a *Rust* `main` function
other than `fn main()`, and the `start` attribute to specify a Rust-specific
portable startup function that runs after some existing Rust startup code.

We do not currently have any mechanisms to address handling of entry-point
symbols.

Existing programming languages rely entirely on the toolchain for this
functionality. Introducing `no_entry` makes it possible to gain control from
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you provide a minimal example of how C (e.g. using clang) handles this? Do C compilers offer compiler extensions to make this simpler ?

the initial binary entry point without toolchain-specific code, consistent with
other Rust efforts to provide some uniformity across targets and toolchains.

# Unresolved questions
[unresolved-questions]: #unresolved-questions

Can we use common code to implement this for all targets using a given
toolchain family (`LinkerFlavor`), and minimize target-specific implementation
code?

# Future possibilities
[future-possibilities]: #future-possibilities

We should simplify the declaration of an entry-point symbol, such as by
providing an `#[entry]` attribute. The symbol itself would still have a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have #[entry] on unsafe ... fn foobar(...) do we then also need #![no_entry]?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Centril Yes, we do. entry could be used in a library, for instance, with a binary crate using no_entry and using that library. I'll add a note that it would make sense to have a lint if you use entry in a binary crate without using no_entry, though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joshtriplett Ah I see; so this is akin to "needs / provides" of #![requires_allocator] (because every program requires an entry point) and #[global_allocator]. -- Please mention this similarity explicitly as well as the use-case for library/binary division in the text also. :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by requires_allocator? I don't see any references to that or similar names anywhere on the web.

The RFC already mentions the use case of library/binary division, in the rationale and alternatives section: "However, this would make it difficult to flexibly provide the entry point from a library or other mechanism."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by requires_allocator? I don't see any references to that or similar names anywhere on the web.

Ooops; It's called #![needs_allocator] in https://github.com/rust-lang/rust/blob/master/src/liballoc/lib.rs#L65 apparently.

The RFC already mentions the use case of library/binary division, in the rationale and alternatives section: "However, this would make it difficult to flexibly provide the entry point from a library or other mechanism."

Yes, but connecting this to entry and no_entry is another matter, it would be helpful to clarify that this is what is referred to by that sentence.

non-portable signature, but having #[entry] would allow using exclusively Rust
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
cfg to handle portability, rather than also needing linker arguments. Rust
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
could also (optionally) detect and warn about entry points with an unexpected
signature for the target. This would also make it easier for Rust library
crates to provide entry points. (This differs from #[start], which provides a
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
semi-portable entry point that still has Rust code run before it.)

I'd also like to add an attribute to pass `-nodefaultlibs` to the linker.
Unlike `no_entry`, this would typically require `no_std` on many targets, as
std often depends on the C library.