-
Notifications
You must be signed in to change notification settings - Fork 11
Move from an hardcoded extern _start to a dynamic proc_macro #36
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
Conversation
The macro itself is actually done, it successfully generates the code for all archs and the main function can actually be named anything. On that note I forgot to mention on the previous comment that this pr also removes all args from main and expects it to be called without any. I am not 100% if this is what we want but it was easier to get a working implementation that way so that we can start testing. |
Could you say more about the use case that motivates this? Would it be possible to implement the same syntax by building the proc macro on top of origin's existing API (though with |
I haven't tested it, but I think this might be a way to do what you're looking to do here, within the current API: #[proc_macro_attribute]
pub fn main(_attr: TokenStream, input: TokenStream) -> TokenStream {
let main_fn: ItemFn = parse_macro_input!(input);
let main_fn_ident = &main_fn.sig.ident;
quote! {
#main_fn
#[no_mangle]
unsafe fn origin_main(argc: usize, argv: *mut *mut u8, envp: *mut *mut u8) -> i32 {
#main_fn_ident()
}
}
.into()
} |
This could technically work but my intent was to actually avoid using the unnecessary extern all together. I don't think it makes sense for origin to link to some arbitrary symbols. That should a the job of a c-compatibility layer. Ideally origin would just provides the means to do so. The macro is meant for those rare use cases where only origin is wanted and instead of relaying on linking to other symbols it uses the actual main function directly. For a library that is included directly instead of linked against I am not to fond of having "random" linking requirements. After all origin is still young, there is no reason to define some arbitrary conventions when that can be left for an layer like mustang. |
By coincidence, I just encountered a technical reason why this macro approach would need to be significantly more complex. It turns out that the PIE relocation code had a bug (I called it!); it wasn't running early enough. It was relying on the linker doing ELF relaxation optimizations to eliminate GOT calls. But, Recent Rust nightly versions happened to disable ELF relaxation optimizations, causing calls that cross crate boundaries go through the GOT. GOT entries in PIEs need to be relocated, so if we were to expand We could put all of |
Can't say I'm surprised, we definetly need to find a proper way to implement relocation that doesn't feel hacky, I am not sure how it would fit with the proc_macro approach.
That doesn't feel right, the relocation code is very big and my goal was to avoid as much code from the macro as possible precisely for this reason. As I said before, I just want the macro to glue together some existing code instead of writing it itself. I am gonna go ahead and explain my use case so perhaps we can come to a better conclusion together Right now I have a bunch of no_std executable that target a generic linux platform without libc. There is no dynamic linking so everything gets compiled to completely a static executable. Most of this programs are very basic and don't even require allocations. In each one of them contains something that looks like this: fn main() -> i32 {
0
}
#[naked]
#[no_mangle]
unsafe extern "C" fn _start() -> ! {
use core::arch::asm;
fn entry() -> ! {
rustix::runtime::exit_group(main())
}
asm!(
"mov rdi, rsp",
"push rbp",
"jmp {entry}",
entry = sym entry,
options(noreturn),
);
} That is a simplified version of the code that I adapted from origin itself. Of course I don't want to write this for every file but just write and mantain one version. Ideally I'd love to just write something that looks like this: #[main]
fn main() -> i32 {
0
} There is obviously significant overlap with origin and It wouldn't make sense to write another crate that does just a subset of what origin does. I do only need the startup code which is very small compared to everything else origin does but I admit I am not confident enough about this low level stuff to maintain such code myself, especially when great libraries like origin already exist. My hope is to find what I'm trying to achieve in origin itself. Maybe the conclusion from all of this is that origin does to much and stuff like signal handling and maybe even relocation should live in sister crates appropriately named something like |
If you want to write code that looks like that, consider using my example above.
The signal code and relocation code are already behind cargo features; if you don't enable the I added the example-crates/tiny example to show how origin can be used to produce very small executables. A big part of how it achieves that is by disabling origin's various optional features, which reduces the amount of code in origin down to almost nothing. |
I'm open to ideas here, however the constraints of the relocate code mean we don't have much flexibility here. I think the current system works pretty well in practice, and it can support the end-user syntax you're discussing. So if you have further thoughts here, feel free to reopen this or file new issues. |
This pr is very complex as it attempts to do a bunch of things at once.
main
that generates the glue code to start the programorigin-stdio
ormustang
There are still some question that need to be answered mainly: