커스텀 진입점에서 Rust 메인 사용하기
main()
함수의 핵심에 대해 자세히 알아보고 배후를 살펴보겠습니다. 마지막에는 Rust 런타임에 대해 어느 정도 이해하게 될 것입니다. 주로 Rust 런타임에 연결하기 위해 efi_main
의 현재 구현을 설명할 것입니다.C 런타임
먼저, 우리는 방해가 되는 것들을 제거해야 합니다. "C"에서 첫 번째 함수는
int main(int argc, char *argv[])
이 아닙니다. 이것이 여러분 중 일부를 놀라게 할 수도 있지만 C에는 런타임이 있으며 Crt0 이라고 합니다. 대부분 어셈블리로 작성되며 거의 모든 C 프로그램에 연결됩니다. 이 예제는 실제 C 런타임 없이 AT&T 구문을 사용하는 Linux x86-64용입니다..text
.globl _start
_start: # _start is the entry point known to the linker
xor %ebp, %ebp # effectively RBP := 0, mark the end of stack frames
mov (%rsp), %edi # get argc from the stack (implicitly zero-extended to 64-bit)
lea 8(%rsp), %rsi # take the address of argv from the stack
lea 16(%rsp,%rdi,8), %rdx # take the address of envp from the stack
xor %eax, %eax # per ABI and compatibility with icc
call main # %edi, %rsi, %rdx are the three args (of which first two are C standard) to main
mov %eax, %edi # transfer the return of main to the first argument of _exit
xor %eax, %eax # per ABI and compatibility with icc
call _exit # terminate the program
그런 다음 이
_start
함수는 C에서 너무 친숙한main
함수를 호출합니다. 우리는 이제 Rust Runtime과 간단한 "Hello World"프로그램이 작동하도록 만드는 비하인드 스토리에 대해 이야기할 것입니다.러스트 런타임
누구나 이미 Rust 런타임이 C 런타임보다 훨씬 더 복잡하다는 것을 짐작할 수 있습니다. 또한 거의 모든 OS는 C와 매우 잘 통합되어 있는 반면 Rust는 OS 자체와의 통합이라는 어려운 작업을 대부분 수행해야 합니다. 자세한 설명을 원하시면 Michael Gattozzi’s blog post에 대해 자세히 설명되어 있습니다.
간단한 설명을 드리겠습니다. ”
main()
.모두가 아직 나와 함께 있습니까? 좋은. 이제 방금 언급한 모든 기능을 간략하게 설명하겠습니다.
C 메인()
이것은 rustc에 의해 생성됩니다.
lang_start
의 코드를 살펴보면 다음과 같습니다. fn create_entry_fn<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
cx: &'a Bx::CodegenCx,
rust_main: Bx::Value,
rust_main_def_id: DefId,
use_start_lang_item: bool,
) -> Bx::Function {
// The entry function is either `int main(void)` or `int main(int argc, char **argv)`,
// depending on whether the target needs `argc` and `argv` to be passed in.
let llfty = if cx.sess().target.main_needs_argc_argv {
cx.type_func(&[cx.type_int(), cx.type_ptr_to(cx.type_i8p())], cx.type_int())
} else {
cx.type_func(&[], cx.type_int())
};
...
As we can see, one of the two signatures of `main` is used. Incidentally, as you can see, neither of these main functions has a signature valid for UEFI, but that’s not too important right now.
This generated `main` basically calls the following function on the list, i.e., `lang_start`.
### Rust lang\_start()
This function is pretty simple, it just calls the `lang_start_internal`. Incidently, this can also be defined by us if we want. The issue tracking this can be found [here](https://github.com/rust-lang/rust/issues/29633). This function signature is as follows:
녹
fn lang_start(
메인: fn() -> T,
argc: isize,
인수: *const *const u8,
) -> 크기 조정;
### Rust lang\_start\_internal()
It basically calls the `init` and then the `main` function. It also prevents unwinding in the `init` and `main` functions, which is a requirement. The function signature is as follows:
녹
fn lang_start_internal(
메인: &(dyn Fn() -> i32 + Sync + crate::panic::RefUnwindSafe),
argc: isize,
인수: *const *const u8,
) -> 결과 {
### Rust init()
This function sets up stack\_guard for the current thread. It also calls the `sys::init()` function. The signature for it is the following:
녹
안전하지 않은 fn init(argc: isize, argv: *const *const u8);
This is also the function where heap memory starts coming to play.
### Rust sys::init()
This function sets up platform-specific stuff. This is just an empty function on some platforms, while it does a lot of stuff on others. It is generally defined under `std/sys/<platform>/mod.rs` The function signature is as follows:
녹
pub fn init(argc: isize, argv: *const *const u8) {
### Rust main()
This is the function where most normal programs start execution. By this point, it is assumed that all the `std` stuff that needs initialization must be done and available for the user.
## But wait, who calls the C main()?
This is precisely the question I had after reading about all these functions. The answer, though, is a bit less clear. It depends on the platform. The OS `crt0` on most platforms calls the C `main`. On others, well, people just seem to use a custom entry point like `efi_main` and not use `main()`. Since I wanted to use `main` but had a custom entry point with a custom signature, I had to do a hacky implementation to make things work.
## Using efi\_main() with Rust runtime
Since we have established that Rust generates a C `main` function for every Rust program, all we need to do is call `main` and be done with it. However, the problem is how to pass SystemTable and SystemHandle to Rust. Without those pointers, we cannot do much in UEFI. Thus they need to be stored globally somewhere.
After some thought, I have concluded that I will do it in the `sys::init()` function rather than the `efi_main`. The reasons for this are as follows:
1. The `efi_main` currently calls to “C” `main`, so we are jumping language boundaries here.
2. At some point, I would like to replace the `efi_main` with an autogenerated function or even a function written in assembly. For now, I am writing it in Rust, but that might not be the case in the future. Thus it should be as less complicated as possible.
3. `sys::init` seems kinda the natural place for it.
Now, the question is how to get it to reach `sys::init()`. The answer is pretty simple. We can use pointers.
My current implementation looks like the following:
녹
[no_mangle]
pub 안전하지 않은 extern "efiapi"fn efi_main(
핸들: efi::핸들,
st: *mut efi::SystemTable,
) -> efi::상태 {
const argc: isize = 2;
let handle_ptr: *const u8 = handle.cast();
let st_ptr: *const u8 = st.cast();
let argv: *const *const u8 = [handle_ptr, st_ptr, core::ptr::null()].as_ptr();
match main(argc, argv) {
0 => {
print_pass(st);
efi::Status::SUCCESS
}
_ => {
print_fail(st);
efi::Status::ABORTED
} // Or some other status code
}
}
I just cast both SystemTable and SystemHandle pointers as `*const u8` in the `efi_main`. Then in the `sys::init()`, I cast them back to their original selves. The null at the end is something someone in zulipchat suggested.
And well, it kind of works. This simple hack allows us to get the SystemTable and SystemHandle all the way to `sys::init()`. They can be accessed in the following way:
녹
pub unsafe fn init(argc: isize, argv: *const *const u8) {
let args: &[*const u8] = unsafe { crate::slice::from_raw_parts(argv, argc as usize) };
let handle: r_efi::efi::Handle = args[0] as r_efi::efi::Handle;
let st: *mut r_efi::efi::SystemTable = args[1] as *mut r_efi::efi::SystemTable;
}
Now comes the catch, if we look at the function calling this, the line where the new Thread is created needs an allocator, or else it panics.
녹
//일회성 런타임 초기화.
//이전에 실행lang_start_internal
.
//안전: 런타임 초기화 중에 한 번만 호출해야 합니다.
//참고: 예를 들어 Rust 코드가 외부에서 호출될 때 실행이 보장되지 않습니다.
[cfg_attr(테스트, 허용(dead_code))]
안전하지 않은 fn init(argc: isize, argv: *const *const u8) {
안전하지 않은 {
sys::init(argc, argv);
let main_guard = sys::thread::guard::init();
// Next, set up the current Thread with the guard information we just
// created. Note that this isn't necessary in general for new threads,
// but we just do this to name the main thread and to give it correct
// info about the stack bounds.
let thread = Thread::new(Some(rtunwrap!(Ok, CString::new("main"))));
thread_info::set(main_guard, thread);
}
}
We can add a `return` before the thread creation and get to `main` perfectly. However, that is a bit of cheating, so this is where I will leave it for now.
## Conclusion
Initially, I set out to print “Hello World” from `main` in this post. However, after getting burned multiple times, I have finally decided to save it for later. The following post will look at creating and initializing the System Allocator. Spoiler, the `thread_info::set` will start panicking after that, so we will not be able to print “Hello World” even in the next post. Still, we are one step closer to a usable std for UEFI.
## Helpful Links
1. [Rust’s Runtime Post by Michael Gattozzi](https://blog.mgattozzi.dev/rusts-runtime/)
Reference
이 문제에 관하여(커스텀 진입점에서 Rust 메인 사용하기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/ayush1325/using-rust-main-from-a-custom-entry-point-4ba0
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
match main(argc, argv) {
0 => {
print_pass(st);
efi::Status::SUCCESS
}
_ => {
print_fail(st);
efi::Status::ABORTED
} // Or some other status code
}
I just cast both SystemTable and SystemHandle pointers as `*const u8` in the `efi_main`. Then in the `sys::init()`, I cast them back to their original selves. The null at the end is something someone in zulipchat suggested.
And well, it kind of works. This simple hack allows us to get the SystemTable and SystemHandle all the way to `sys::init()`. They can be accessed in the following way:
let handle: r_efi::efi::Handle = args[0] as r_efi::efi::Handle;
let st: *mut r_efi::efi::SystemTable = args[1] as *mut r_efi::efi::SystemTable;
Now comes the catch, if we look at the function calling this, the line where the new Thread is created needs an allocator, or else it panics.
안전하지 않은 fn init(argc: isize, argv: *const *const u8) {
안전하지 않은 {
sys::init(argc, argv);
let main_guard = sys::thread::guard::init();
// Next, set up the current Thread with the guard information we just
// created. Note that this isn't necessary in general for new threads,
// but we just do this to name the main thread and to give it correct
// info about the stack bounds.
let thread = Thread::new(Some(rtunwrap!(Ok, CString::new("main"))));
thread_info::set(main_guard, thread);
}
}
We can add a `return` before the thread creation and get to `main` perfectly. However, that is a bit of cheating, so this is where I will leave it for now.
## Conclusion
Initially, I set out to print “Hello World” from `main` in this post. However, after getting burned multiple times, I have finally decided to save it for later. The following post will look at creating and initializing the System Allocator. Spoiler, the `thread_info::set` will start panicking after that, so we will not be able to print “Hello World” even in the next post. Still, we are one step closer to a usable std for UEFI.
## Helpful Links
1. [Rust’s Runtime Post by Michael Gattozzi](https://blog.mgattozzi.dev/rusts-runtime/)
Reference
이 문제에 관하여(커스텀 진입점에서 Rust 메인 사용하기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/ayush1325/using-rust-main-from-a-custom-entry-point-4ba0텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)