Skip to content

Experimental tracepoint support #1084

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

Open
wants to merge 3 commits into
base: rust-next
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions rust/bindings/bindings_helper.h
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@
#include <linux/refcount.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/tracepoint.h>
#include <linux/wait.h>
#include <linux/workqueue.h>

15 changes: 15 additions & 0 deletions rust/bindings/lib.rs
Original file line number Diff line number Diff line change
@@ -48,3 +48,18 @@ mod bindings_helper {
}

pub use bindings_raw::*;

/// Rust version of the C macro `rcu_dereference_raw`.
///
/// The rust helper only works with void pointers, but this wrapper method makes it work with any
/// pointer type using pointer casts.
///
/// # Safety
///
/// This method has the same safety requirements as the C macro of the same name.
#[inline(always)]
pub unsafe fn rcu_dereference_raw<T>(p: *const *mut T) -> *mut T {
// SAFETY: This helper calls into the C macro, so the caller promises to uphold the safety
// requirements.
unsafe { __rcu_dereference_raw(p as *mut *mut _) as *mut T }
}
18 changes: 18 additions & 0 deletions rust/helpers.c
Original file line number Diff line number Diff line change
@@ -165,6 +165,24 @@ rust_helper_krealloc(const void *objp, size_t new_size, gfp_t flags)
}
EXPORT_SYMBOL_GPL(rust_helper_krealloc);

void rust_helper_preempt_enable_notrace(void)
{
preempt_enable_notrace();
}
EXPORT_SYMBOL_GPL(rust_helper_preempt_enable_notrace);

void rust_helper_preempt_disable_notrace(void)
{
preempt_disable_notrace();
}
EXPORT_SYMBOL_GPL(rust_helper_preempt_disable_notrace);

void *rust_helper___rcu_dereference_raw(void **p)
{
return rcu_dereference_raw(p);
}
EXPORT_SYMBOL_GPL(rust_helper___rcu_dereference_raw);

/*
* `bindgen` binds the C `size_t` type as the Rust `usize` type, so we can
* use it in contexts where Rust expects a `usize` like slice (array) indices.
3 changes: 3 additions & 0 deletions rust/kernel/lib.rs
Original file line number Diff line number Diff line change
@@ -38,12 +38,15 @@ pub mod net;
pub mod prelude;
pub mod print;
mod static_assert;
pub mod static_call;
pub mod static_key;
#[doc(hidden)]
pub mod std_vendor;
pub mod str;
pub mod sync;
pub mod task;
pub mod time;
pub mod tracepoint;
pub mod types;
pub mod workqueue;

90 changes: 90 additions & 0 deletions rust/kernel/static_call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0

// Copyright (C) 2024 Google LLC.

//! Logic for static calls.
#[macro_export]
#[doc(hidden)]
macro_rules! ty_underscore_for {
($arg:expr) => {
_
};
}

#[doc(hidden)]
#[repr(transparent)]
pub struct AddressableStaticCallKey {
_ptr: *const bindings::static_call_key,
}
unsafe impl Sync for AddressableStaticCallKey {}
impl AddressableStaticCallKey {
pub const fn new(ptr: *const bindings::static_call_key) -> Self {
Self { _ptr: ptr }
}
}

#[cfg(CONFIG_HAVE_STATIC_CALL)]
#[doc(hidden)]
#[macro_export]
macro_rules! _static_call {
($name:ident($($args:expr),* $(,)?)) => {{
// Symbol mangling will give this symbol a unique name.
#[cfg(CONFIG_HAVE_STATIC_CALL_INLINE)]
#[link_section = ".discard.addressable"]
#[used]
static __ADDRESSABLE: $crate::static_call::AddressableStaticCallKey = unsafe {
$crate::static_call::AddressableStaticCallKey::new(::core::ptr::addr_of!(
$crate::macros::paste! { $crate::bindings:: [<__SCK__ $name >]; }
))
};

let fn_ptr: unsafe extern "C" fn($($crate::static_call::ty_underscore_for!($args)),*) -> _ =
$crate::macros::paste! { $crate::bindings:: [<__SCT__ $name >]; };
(fn_ptr)($($args),*)
}};
}

#[cfg(not(CONFIG_HAVE_STATIC_CALL))]
#[doc(hidden)]
#[macro_export]
macro_rules! _static_call {
($name:ident($($args:expr),* $(,)?)) => {{
let void_ptr_fn: *mut ::core::ffi::c_void = $crate::macros::paste! { $crate::bindings:: [<__SCK__ $name >]; }.func;

let fn_ptr: unsafe extern "C" fn($($crate::static_call::ty_underscore_for!($args)),*) -> _ = if true {
::core::mem::transmute(void_ptr_fn)
} else {
// This is dead code, but it influences type inference on `fn_ptr` so that we transmute
// the function pointer to the right type.
$crate::macros::paste! { $crate::bindings:: [<__SCT__ $name >]; }
};

(fn_ptr)($($args),*)
}};
}

/// Statically call a global function.
///
/// # Safety
///
/// This macro will call the provided function. It is up to the caller to uphold the safety
/// guarantees of the function.
///
/// # Examples
///
/// ```ignore
/// fn call_static() {
/// unsafe {
/// static_call! { your_static_call() };
/// }
/// }
/// ```
#[macro_export]
macro_rules! static_call {
// Forward to the real implementation. Separated like this so that we don't have to duplicate
// the documentation.
($($args:tt)*) => { $crate::static_call::_static_call! { $($args)* } };
}

pub use {_static_call, static_call, ty_underscore_for};
150 changes: 150 additions & 0 deletions rust/kernel/static_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// SPDX-License-Identifier: GPL-2.0

// Copyright (C) 2024 Google LLC.

//! Logic for static keys.
use crate::bindings::*;

#[doc(hidden)]
#[macro_export]
#[cfg(target_arch = "x86_64")]
#[cfg(not(CONFIG_HAVE_RUST_ASM_GOTO))]
macro_rules! _static_key_false {
($key:path, $keytyp:ty, $field:ident) => {{
let mut output = 1u32;

core::arch::asm!(
r#"
1: .byte 0x0f,0x1f,0x44,0x00,0x00
.pushsection __jump_table, "aw"
.balign 8
.long 1b - .
.long 3f - .
.quad {0} + {1} - .
.popsection
2: mov {2:e}, 0
Copy link
Member

Choose a reason for hiding this comment

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

you may want to use att_syntax ;-)

Copy link
Collaborator Author

@Darksonn Darksonn May 31, 2024

Choose a reason for hiding this comment

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

Could you show me how to do that?

Copy link
Member

Choose a reason for hiding this comment

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

options(att_syntax), here is an example:

unsafe fn i32_xadd(v: *mut i32, mut i: i32) -> i32 {
    // SAFETY: Per function safety requirement, the address of `v` is valid for "xadd".
    unsafe {
        asm!(
            lock_instr!("xaddl {i:e}, ({v})"),
            i = inout(reg) i,
            v = in(reg) v,
            options(att_syntax, preserves_flags),
        );
    }

    i
}

in https://lore.kernel.org/rust-for-linux/[email protected]/

Copy link
Member

Choose a reason for hiding this comment

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

Kernel developers will prefer that, yeah... :)

Sadly, specifying every single time that may be painful. Perhaps we could have our own asm! that expands to the actual one plus that option, if that is possible, or otherwise it would be nice to have a rustc flag to always use that by default.

Copy link
Member

Choose a reason for hiding this comment

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

Added to our wishlist.

Copy link
Member

Choose a reason for hiding this comment

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

A asm! wrapper should be fairly straightforward. The asm macro deliberately allows multiple options to simplify cases where people want to always add options(att_syntax). See rust-lang/rust#73227.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks Gary, then we should probably do it soon if we are going to start adding more asm! blocks. I would still like to have a flag (or similar) nevertheless, to avoid extra macros and to match the C compilers.

3:
"#,
sym $key,
const ::core::mem::offset_of!($keytyp, $field),
inout(reg) output,
);

output != 0
}};
}

#[doc(hidden)]
#[macro_export]
#[cfg(target_arch = "x86_64")]
#[cfg(CONFIG_HAVE_RUST_ASM_GOTO)]
macro_rules! _static_key_false {
($key:path, $keytyp:ty, $field:ident) => {'my_label: {
core::arch::asm!(
r#"
1: .byte 0x0f,0x1f,0x44,0x00,0x00
.pushsection __jump_table, "aw"
.balign 8
.long 1b - .
.long {0} - .
.quad {1} + {2} - .
.popsection
"#,
label {
break 'my_label true;
},
sym $key,
const ::core::mem::offset_of!($keytyp, $field),
);

break 'my_label false;
}};
}

#[doc(hidden)]
#[macro_export]
#[cfg(target_arch = "aarch64")]
#[cfg(not(CONFIG_HAVE_RUST_ASM_GOTO))]
macro_rules! _static_key_false {
($key:path, $keytyp:ty, $field:ident) => {{
let mut output = 1u32;

core::arch::asm!(
r#"
1: nop
.pushsection __jump_table, "aw"
.align 3
.long 1b - ., 3f - .
.quad {0} + {1} - .
.popsection
2: mov {2:w}, 0
3:
"#,
sym $key,
const ::core::mem::offset_of!($keytyp, $field),
inout(reg) output
);

output != 0
}};
}

#[doc(hidden)]
#[macro_export]
#[cfg(target_arch = "aarch64")]
#[cfg(CONFIG_HAVE_RUST_ASM_GOTO)]
macro_rules! _static_key_false {
($key:path, $keytyp:ty, $field:ident) => {'my_label: {
core::arch::asm!(
r#"
1: nop
.pushsection __jump_table, "aw"
.align 3
.long 1b - ., {0} - .
.quad {1} + {2} - .
.popsection
"#,
label {
break 'my_label true;
},
sym $key,
const ::core::mem::offset_of!($keytyp, $field),
);

break 'my_label false;
}};
}

/// Branch based on a static key.
///
/// Takes three arguments:
///
/// * `key` - the path to the static variable containing the `static_key`.
/// * `keytyp` - the type of `key`.
/// * `field` - the name of the field of `key` that contains the `static_key`.
#[macro_export]
macro_rules! static_key_false {
// Forward to the real implementation. Separated like this so that we don't have to duplicate
// the documentation.
($key:path, $keytyp:ty, $field:ident) => {{
// Assert that `$key` has type `$keytyp` and that `$key.$field` has type `static_key`.
//
// SAFETY: We know that `$key` is a static because otherwise the inline assembly will not
// compile. The raw pointers created in this block are in-bounds of `$key`.
static _TY_ASSERT: () = unsafe {
let key: *const $keytyp = ::core::ptr::addr_of!($key);
let _: *const $crate::bindings::static_key = ::core::ptr::addr_of!((*key).$field);
};

$crate::static_key::_static_key_false! { $key, $keytyp, $field }
}};
}

pub use {_static_key_false, static_key_false};
94 changes: 94 additions & 0 deletions rust/kernel/tracepoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0

// Copyright (C) 2024 Google LLC.

//! Logic for tracepoints.
/// Declare the Rust entry point for a tracepoint.
#[macro_export]
macro_rules! declare_trace {
($($(#[$attr:meta])* $pub:vis fn $name:ident($($argname:ident : $argtyp:ty),* $(,)?);)*) => {$(
$( #[$attr] )*
#[inline(always)]
$pub unsafe fn $name($($argname : $argtyp),*) {
#[cfg(CONFIG_TRACEPOINTS)]
{
use $crate::bindings::*;

// SAFETY: This macro only compiles if $name is a real tracepoint, and if it is a
// real tracepoint, then it is okay to query the static key.
let should_trace = unsafe {
$crate::macros::paste! {
$crate::static_key::static_key_false!(
[< __tracepoint_ $name >],
$crate::bindings::tracepoint,
key
)
}
};

if should_trace {
// TODO: cpu_online(raw_smp_processor_id())
let cond = true;
$crate::tracepoint::do_trace!($name($($argname : $argtyp),*), cond);
}
}

#[cfg(not(CONFIG_TRACEPOINTS))]
{
// If tracepoints are disabled, insert a trivial use of each argument
// to avoid unused argument warnings.
$( let _unused = $argname; )*
}
}
)*}
}

#[doc(hidden)]
#[macro_export]
macro_rules! do_trace {
($name:ident($($argname:ident : $argtyp:ty),* $(,)?), $cond:expr) => {{
if !$cond {
return;
}

// SAFETY: This call is balanced with the call below.
unsafe { $crate::bindings::preempt_disable_notrace() };

// SAFETY: This calls the tracepoint with the provided arguments. The caller of the Rust
// wrapper guarantees that this is okay.
#[cfg(CONFIG_HAVE_STATIC_CALL)]
unsafe {
let it_func_ptr: *mut $crate::bindings::tracepoint_func =
$crate::bindings::rcu_dereference_raw(
::core::ptr::addr_of!(
$crate::macros::concat_idents!(__tracepoint_, $name).funcs
)
);

if !it_func_ptr.is_null() {
let __data = (*it_func_ptr).data;
$crate::macros::paste! {
$crate::static_call::static_call! {
[< tp_func_ $name >] (__data, $($argname),*)
};
}
}
}

// SAFETY: This calls the tracepoint with the provided arguments. The caller of the Rust
// wrapper guarantees that this is okay.
#[cfg(not(CONFIG_HAVE_STATIC_CALL))]
unsafe {
$crate::macros::concat_idents!(__traceiter_, $name)(
::core::ptr::null_mut(),
$($argname),*
);
}

// SAFETY: This call is balanced with the call above.
unsafe { $crate::bindings::preempt_enable_notrace() };
}}
}

pub use {declare_trace, do_trace};
2 changes: 1 addition & 1 deletion scripts/Makefile.build
Original file line number Diff line number Diff line change
@@ -263,7 +263,7 @@ $(obj)/%.lst: $(obj)/%.c FORCE
# Compile Rust sources (.rs)
# ---------------------------------------------------------------------------

rust_allowed_features := new_uninit
rust_allowed_features := asm_const,asm_goto,new_uninit

# `--out-dir` is required to avoid temporaries being created by `rustc` in the
# current working directory, which may be not accessible in the out-of-tree