Skip to content

Instantly share code, notes, and snippets.

@qti3e
Created February 28, 2025 22:52
Show Gist options
  • Save qti3e/bcd1bff5b95f2b38f6f9f281a8197df4 to your computer and use it in GitHub Desktop.
Save qti3e/bcd1bff5b95f2b38f6f9f281a8197df4 to your computer and use it in GitHub Desktop.
attempt to use rseq in rust
use core::arch::asm;
use core::ptr::null_mut;
// https://github.com/torvalds/linux/blob/276f98efb64a2c31c099465ace78d3054c662a0f/arch/x86/entry/syscalls/syscall_64.tbl
const SYS_RSEQ: u64 = 334;
// https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h
const EINVAL: i64 = 22;
// https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h
const ENOSYS: i64 = 38;
// enum rseq_abi_cpu_id_state {
const RSEQ_ABI_CPU_ID_UNINITIALIZED: i32 = -1;
const RSEQ_ABI_CPU_ID_REGISTRATION_FAILED: i32 = -2;
// enum rseq_abi_flags {
const RSEQ_ABI_FLAG_UNREGISTER: u32 = (1 << 0);
// enum rseq_abi_cs_flags_bit {
const RSEQ_ABI_CS_FLAG_NO_RESTART_ON_PREEMPT_BIT: u32 = 0;
const RSEQ_ABI_CS_FLAG_NO_RESTART_ON_SIGNAL_BIT: u32 = 1;
const RSEQ_ABI_CS_FLAG_NO_RESTART_ON_MIGRATE_BIT: u32 = 2;
// enum rseq_abi_cs_flags
const RSEQ_ABI_CS_FLAG_NO_RESTART_ON_PREEMPT: u32 = 1 << RSEQ_ABI_CS_FLAG_NO_RESTART_ON_PREEMPT_BIT;
const RSEQ_ABI_CS_FLAG_NO_RESTART_ON_SIGNAL: u32 = 1 << RSEQ_ABI_CS_FLAG_NO_RESTART_ON_SIGNAL_BIT;
const RSEQ_ABI_CS_FLAG_NO_RESTART_ON_MIGRATE: u32 = 1 << RSEQ_ABI_CS_FLAG_NO_RESTART_ON_MIGRATE_BIT;
const RSEQ_SIG: u32 = 0x53053053;
/*
* struct rseq_abi is aligned on 4 * 8 bytes to ensure it is always
* contained within a single cache-line.
*
* A single struct rseq_abi per thread is allowed.
*/
#[repr(C, align(1024))]
#[derive(Default, Debug)]
pub struct RSeqAbi<T: ?Sized = ()> {
/*
* Restartable sequences cpu_id_start field. Updated by the
* kernel. Read by user-space with single-copy atomicity
* semantics. This field should only be read by the thread which
* registered this data structure. Aligned on 32-bit. Always
* contains a value in the range of possible CPUs, although the
* value may not be the actual current CPU (e.g. if rseq is not
* initialized). This CPU number value should always be compared
* against the value of the cpu_id field before performing a rseq
* commit or returning a value read from a data structure indexed
* using the cpu_id_start value.
*/
cpu_id_start: i32,
/*
* Restartable sequences cpu_id field. Updated by the kernel.
* Read by user-space with single-copy atomicity semantics. This
* field should only be read by the thread which registered this
* data structure. Aligned on 32-bit. Values
* RSEQ_CPU_ID_UNINITIALIZED and RSEQ_CPU_ID_REGISTRATION_FAILED
* have a special semantic: the former means "rseq uninitialized",
* and latter means "rseq initialization failed". This value is
* meant to be read within rseq critical sections and compared
* with the cpu_id_start value previously read, before performing
* the commit instruction, or read and compared with the
* cpu_id_start value before returning a value loaded from a data
* structure indexed using the cpu_id_start value.
*/
cpu_id: i32,
/*
* Restartable sequences rseq_cs field.
*
* Contains NULL when no critical section is active for the current
* thread, or holds a pointer to the currently active struct rseq_cs.
*
* Updated by user-space, which sets the address of the currently
* active rseq_cs at the beginning of assembly instruction sequence
* block, and set to NULL by the kernel when it restarts an assembly
* instruction sequence block, as well as when the kernel detects that
* it is preempting or delivering a signal outside of the range
* targeted by the rseq_cs. Also needs to be set to NULL by user-space
* before reclaiming memory that contains the targeted struct rseq_cs.
*
* Read and set by the kernel. Set by user-space with single-copy
* atomicity semantics. This field should only be updated by the
* thread which registered this data structure. Aligned on 64-bit.
*
* 32-bit architectures should update the low order bits of the
* rseq_cs field, leaving the high order bits initialized to 0.
*/
rseq_cs: i64,
/*
* Restartable sequences flags field.
*
* This field should only be updated by the thread which
* registered this data structure. Read by the kernel.
* Mainly used for single-stepping through rseq critical sections
* with debuggers.
*
* - RSEQ_CS_FLAG_NO_RESTART_ON_PREEMPT
* Inhibit instruction sequence block restart on preemption
* for this thread.
* - RSEQ_CS_FLAG_NO_RESTART_ON_SIGNAL
* Inhibit instruction sequence block restart on signal
* delivery for this thread.
* - RSEQ_CS_FLAG_NO_RESTART_ON_MIGRATE
* Inhibit instruction sequence block restart on migration for
* this thread.
*/
flags: u32,
/*
* Restartable sequences node_id field. Updated by the kernel. Read by
* user-space with single-copy atomicity semantics. This field should
* only be read by the thread which registered this data structure.
* Aligned on 32-bit. Contains the current NUMA node ID.
*/
node_id: i32,
/*
* Restartable sequences mm_cid field. Updated by the kernel. Read by
* user-space with single-copy atomicity semantics. This field should
* only be read by the thread which registered this data structure.
* Aligned on 32-bit. Contains the current thread's concurrency ID
* (allocated uniquely within a memory map).
*/
mm_cid: i32,
/*
* Flexible array member at end of structure, after last feature field.
*/
end: T,
}
/// Wrapper around a Linux syscall with three arguments. It returns
/// the syscall result (or error code) that gets stored in rax.
unsafe fn syscall_4(num: u64, arg1: u64, arg2: u64, arg3: u64, arg4: u64) -> Result<i64, i64> {
dbg!(arg1);
// https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/entry_64.S#L107
let res;
asm!(
// there is no need to write "mov"-instructions, see below
"syscall",
// from 'in("rax")' the compiler will
// generate corresponding 'mov'-instructions
in("rax") num,
in("rdi") arg1,
in("rsi") arg2,
in("rdx") arg3,
in("r10") arg3,
lateout("rax") res,
);
syscall_ret(res)
}
// https://git.musl-libc.org/cgit/musl/tree/src/internal/syscall_ret.c
fn syscall_ret(r: i64) -> Result<i64, i64> {
if r > -4096 { Err(-r) } else { Ok(r) }
}
unsafe fn sys_rseq<T: ?Sized>(
rseq_abi: *mut RSeqAbi<T>,
rseq_len: u32,
flags: u32,
sig: u32,
) -> Result<i64, i64> {
syscall_4(
SYS_RSEQ,
rseq_abi as *mut u8 as usize as u64,
rseq_len as u64,
flags as u64,
sig as u64,
)
}
fn rseq_available() -> bool {
match unsafe { sys_rseq::<()>(null_mut(), 0, 0, 0) } {
Err(EINVAL) => true,
Err(ENOSYS) => false,
Ok(_) | Err(_) => panic!("unexpected syscall result"),
}
}
fn rseq_register_current_thread() {
let mut abi = RSeqAbi {
cpu_id_start: RSEQ_ABI_CPU_ID_UNINITIALIZED,
cpu_id: 0,
rseq_cs: 0,
flags: 0,
node_id: 0,
mm_cid: 0,
end: (),
};
let x = unsafe { sys_rseq(&mut abi, 32, 0, RSEQ_SIG) };
dbg!(x);
dbg!(abi);
}
fn main() {
dbg!(rseq_available());
rseq_register_current_thread();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment