Skip to content

Commit

Permalink
[feat] Insert function into .init_array section to support Linux
Browse files Browse the repository at this point in the history
  • Loading branch information
Azure-stars committed Nov 20, 2024
1 parent 416a0db commit 624a9d8
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 33 deletions.
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ homepage = "https://github.com/arceos-org/arceos"
documentation = "https://arceos-org.github.io/constructor_array"
repository = "https://github.com/arceos-org/constructor_array"
keywords = ["arceos", "constructor"]
categories = ["development-tools::procedural-macro-helpers", "no-std"]
categories = ["development-tools::procedural-macro-helpers", "no-std"]

[packages]
constructor_array = { path = "constructor_array" }
constructor_array_macros = { path = "constructor_array_macros" }
39 changes: 32 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
# constructor_array

[![Crates.io](https://img.shields.io/crates/v/constructor_array)](https://crates.io/crates/constructor_array)
[![Docs.rs](https://docs.rs/constructor_array/badge.svg)](https://docs.rs/constructor_array)
[![CI](https://github.com/arceos-org/constructor_array/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/arceos-org/constructor_array/actions/workflows/ci.yml)


Module initialization functions for Rust (like __attribute__((constructor)) in C/C++) under no_std.


After registering a constructor function, a function pointer pointing to it will be stored in the `ctor` section.
After registering a constructor function, a function pointer pointing to it will be stored in the `.init_array` section.


It can support Linux, Windows, MacOS and other systems, and can be also used in `no_std` environments when developing your own kernel.


When the program starts, it can call all initialization functions in the `ctor` section in order.
In Linux, Windows, MacOS and other systems, the `.init_array` section is a default section to store initialization functions. When the program starts, the system will call all functions in the `.init_array` section in order.


When you are running your own operating system, you can call `constructor_array::invoke_ctors` to invoke all registered constructor functions.

## Usage

Expand All @@ -25,11 +36,25 @@ fn set_max_num() {
}

fn main() {
constructor_array::invoke_ctors();
println!(
"MAX_NUM: {}",
MAX_NUM.load(std::sync::atomic::Ordering::Relaxed)
);
assert_eq!(MAX_NUM.load(std::sync::atomic::Ordering::Relaxed), 20);
}
```

Because the `.init_array` section is a default section to store initialization functions in Linux and some other systems, it will be included in the linker script of compilers like GCC and Clang.


**However**, if you are using a custom linker script, you need to **add the `.init_array` section to the `.text` section manually**, so that these functions can be mapped into the page table and executed correctly. You can add the following line to your linker script as a reference:

```test, ignore
.text : ALIGN(4K) {
# other sections in the `.text` section
_init_array_start = .;
_init_array_end = _init_array_start + SIZEOF(.init_array);
*(.init_array .init_array.*)
. = _init_array_end;
# other sections in the `.text` section
}
```

Expand Down
39 changes: 32 additions & 7 deletions constructor_array/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
# constructor_array

[![Crates.io](https://img.shields.io/crates/v/constructor_array)](https://crates.io/crates/constructor_array)
[![Docs.rs](https://docs.rs/constructor_array/badge.svg)](https://docs.rs/constructor_array)
[![CI](https://github.com/arceos-org/constructor_array/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/arceos-org/constructor_array/actions/workflows/ci.yml)


Module initialization functions for Rust (like __attribute__((constructor)) in C/C++) under no_std.


After registering a constructor function, a function pointer pointing to it will be stored in the `ctor` section.
After registering a constructor function, a function pointer pointing to it will be stored in the `.init_array` section.


It can support Linux, Windows, MacOS and other systems, and can be also used in `no_std` environments when developing your own kernel.


When the program starts, it can call all initialization functions in the `ctor` section in order.
In Linux, Windows, MacOS and other systems, the `.init_array` section is a default section to store initialization functions. When the program starts, the system will call all functions in the `.init_array` section in order.


When you are running your own operating system, you can call `constructor_array::invoke_ctors` to invoke all registered constructor functions.

## Usage

Expand All @@ -25,11 +36,25 @@ fn set_max_num() {
}

fn main() {
constructor_array::invoke_ctors();
println!(
"MAX_NUM: {}",
MAX_NUM.load(std::sync::atomic::Ordering::Relaxed)
);
assert_eq!(MAX_NUM.load(std::sync::atomic::Ordering::Relaxed), 20);
}
```

Because the `.init_array` section is a default section to store initialization functions in Linux and some other systems, it will be included in the linker script of compilers like GCC and Clang.


**However**, if you are using a custom linker script, you need to **add the `.init_array` section to the `.text` section manually**, so that these functions can be mapped into the page table and executed correctly. You can add the following line to your linker script as a reference:

```test, ignore
.text : ALIGN(4K) {
# other sections in the `.text` section
_init_array_start = .;
_init_array_end = _init_array_start + SIZEOF(.init_array);
*(.init_array .init_array.*)
. = _init_array_end;
# other sections in the `.text` section
}
```

Expand Down
14 changes: 7 additions & 7 deletions constructor_array/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@

pub use constructor_array_macros::register_ctor;

/// Placeholder for the `ctors` section, so that
/// the `__start_ctors` and `__stop_ctors` symbols can be generated.
#[link_section = "ctors"]
/// Placeholder for the `.init_array` section, so that
/// the `__init_array_start` and `__init_array_end` symbols can be generated.
#[link_section = ".init_array"]
#[used]
static _SECTION_PLACE_HOLDER: [u8; 0] = [];

extern "C" {
fn __start_ctors();
fn __stop_ctors();
fn __init_array_start();
fn __init_array_end();
}

/// Invoke all constructor functions registered by the `register_ctor` attribute.
///
/// # Notes
/// Caller should ensure that the `ctor` section will not be disturbed by other sections.
/// Caller should ensure that the `.init_array` section will not be disturbed by other sections.
pub fn invoke_ctors() {
for ctor_ptr in (__start_ctors as usize..__stop_ctors as usize)
for ctor_ptr in (__init_array_start as usize..__init_array_end as usize)
.step_by(core::mem::size_of::<*const core::ffi::c_void>())
{
unsafe {
Expand Down
40 changes: 40 additions & 0 deletions constructor_array/tests/test_ctor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::sync::{atomic::AtomicUsize, Mutex};

use constructor_array::*;

static INIT_NUM: AtomicUsize = AtomicUsize::new(0);

#[register_ctor]
fn set_init_num() {
INIT_NUM.fetch_add(20, std::sync::atomic::Ordering::Relaxed);
}

static INIT_VEC: Mutex<Vec<usize>> = Mutex::new(Vec::new());

#[register_ctor]
fn init_vector() {
let mut vec = INIT_VEC.lock().unwrap();
vec.push(1);
vec.push(2);
vec.push(3);
}

#[test]
fn test_constructor_array() {
// The constructor functions will be called before the main function.
assert!(INIT_NUM.load(std::sync::atomic::Ordering::Relaxed) == 20);
let vec = INIT_VEC.lock().unwrap();
assert!(vec.len() == 3);
assert!(vec[0] == 1);
assert!(vec[1] == 2);
assert!(vec[2] == 3);
drop(vec);

// But we can invoke the constructor functions again manually.
init_vector();
let vec = INIT_VEC.lock().unwrap();
assert!(vec.len() == 6);
assert!(vec[3] == 1);
assert!(vec[4] == 2);
assert!(vec[5] == 3);
}
8 changes: 8 additions & 0 deletions constructor_array/tests/test_empty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[test]
fn test_empty() {
// Sometimes under certain conditions, we may not have any constructor functions.
// But the `invoke_ctors` function should still work, and the `__init_array_start` and
// `__init_array_end` symbols should be valid.
constructor_array::invoke_ctors();
println!("It should exit successfully when we don't specify any constructor functions.");
}
39 changes: 32 additions & 7 deletions constructor_array_macros/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
# constructor_array

[![Crates.io](https://img.shields.io/crates/v/constructor_array)](https://crates.io/crates/constructor_array)
[![Docs.rs](https://docs.rs/constructor_array/badge.svg)](https://docs.rs/constructor_array)
[![CI](https://github.com/arceos-org/constructor_array/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/arceos-org/constructor_array/actions/workflows/ci.yml)


Module initialization functions for Rust (like __attribute__((constructor)) in C/C++) under no_std.


After registering a constructor function, a function pointer pointing to it will be stored in the `ctor` section.
After registering a constructor function, a function pointer pointing to it will be stored in the `.init_array` section.


It can support Linux, Windows, MacOS and other systems, and can be also used in `no_std` environments when developing your own kernel.


When the program starts, it can call all initialization functions in the `ctor` section in order.
In Linux, Windows, MacOS and other systems, the `.init_array` section is a default section to store initialization functions. When the program starts, the system will call all functions in the `.init_array` section in order.


When you are running your own operating system, you can call `constructor_array::invoke_ctors` to invoke all registered constructor functions.

## Usage

Expand All @@ -25,11 +36,25 @@ fn set_max_num() {
}

fn main() {
constructor_array::invoke_ctors();
println!(
"MAX_NUM: {}",
MAX_NUM.load(std::sync::atomic::Ordering::Relaxed)
);
assert_eq!(MAX_NUM.load(std::sync::atomic::Ordering::Relaxed), 20);
}
```

Because the `.init_array` section is a default section to store initialization functions in Linux and some other systems, it will be included in the linker script of compilers like GCC and Clang.


**However**, if you are using a custom linker script, you need to **add the `.init_array` section to the `.text` section manually**, so that these functions can be mapped into the page table and executed correctly. You can add the following line to your linker script as a reference:

```test, ignore
.text : ALIGN(4K) {
# other sections in the `.text` section
_init_array_start = .;
_init_array_end = _init_array_start + SIZEOF(.init_array);
*(.init_array .init_array.*)
. = _init_array_end;
# other sections in the `.text` section
}
```

Expand Down
8 changes: 4 additions & 4 deletions constructor_array_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
//!
//! **DO NOT** use this crate directly. Use the [constructor_array](https://docs.rs/constructor_array) crate instead.
//!
//! After attching the `register_ctor` macro to the given function, a pointer pointing to it will be stored in the `ctors` section.
//! After attching the `register_ctor` macro to the given function, a pointer pointing to it will be stored in the `.init_array` section.
//! When the program is loaded, this section will be linked into the binary. The `invoke_ctors` function in the `constructor_array`
//! crate will call all the constructor functions in the `ctors` section.
//! crate will call all the constructor functions in the `.init_array` section.
//!
//! See the documentation of the [constructor_array](https://docs.rs/constructor_array) crate for more details.
Expand Down Expand Up @@ -33,7 +33,7 @@ pub fn register_ctor(attr: TokenStream, function: TokenStream) -> TokenStream {
if let Item::Fn(func) = item {
let name = &func.sig.ident;
let name_str = name.to_string();
let name_ident = format_ident!("_CTOR_{}", name_str);
let name_ident = format_ident!("_INIT_{}", name_str);
let output = &func.sig.output;
// Constructor functions should not have any return value.
if let syn::ReturnType::Type(_, _) = output {
Expand All @@ -57,7 +57,7 @@ pub fn register_ctor(attr: TokenStream, function: TokenStream) -> TokenStream {
let block = &func.block;

quote! {
#[link_section = "ctors"]
#[link_section = ".init_array"]
#[allow(non_upper_case_globals)]
static #name_ident: extern "C" fn() = #name;

Expand Down

0 comments on commit 624a9d8

Please sign in to comment.