static_keys/
lib.rs

1#![doc = include_str!("../docs/en/src/README.md")]
2#![no_std]
3#![allow(clippy::needless_doctest_main)]
4
5mod arch;
6pub mod code_manipulate;
7mod os;
8
9use code_manipulate::CodeManipulator;
10
11/// Entries in the __static_keys section, used for record addresses to modify JMP/NOP.
12///
13/// The fields of this struct are all **relative address** instead of absolute address considering ASLR.
14/// Specifically, it is the relative address between target address and the address of field that record it.
15///
16/// The relative addresses will be updated to absolute address after calling [`global_init`]. This
17/// is because we want to sort the jump entries in place.
18#[derive(Debug)]
19struct JumpEntry {
20    /// Address of the JMP/NOP instruction to be modified.
21    code: usize,
22    /// Address of the JMP destination
23    target: usize,
24    /// Address of associated static key.
25    ///
26    /// Since the static key has at least 8-byte alignment, the LSB bit of this address is used
27    /// to record whether the likely branch is true branch or false branch in order to get right instruction
28    /// to replace old one.
29    key: usize,
30}
31
32impl JumpEntry {
33    /// Update fields to be absolute address
34    #[cfg(not(all(target_os = "windows", target_arch = "x86_64")))]
35    fn make_relative_address_absolute(&mut self) {
36        self.code = ((&raw const self.code) as usize).wrapping_add(self.code);
37        self.target = ((&raw const self.target) as usize).wrapping_add(self.target);
38        self.key = ((&raw const self.key) as usize).wrapping_add(self.key);
39    }
40
41    // For Win64, the relative address is truncated into 32bit.
42    // See https://github.com/llvm/llvm-project/blob/862d837e483437b33f5588f89e62085de3a806b9/llvm/lib/Target/X86/MCTargetDesc/X86WinCOFFObjectWriter.cpp#L48-L51
43    /// Update fields to be absolute address
44    #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
45    fn make_relative_address_absolute(&mut self) {
46        let code = (self.code as i32) as i64 as usize;
47        self.code = ((&raw const self.code) as usize).wrapping_add(code);
48        let target = (self.target as i32) as i64 as usize;
49        self.target = ((&raw const self.target) as usize).wrapping_add(target);
50        let key = (self.key as i32) as i64 as usize;
51        self.key = ((&raw const self.key) as usize).wrapping_add(key);
52    }
53
54    /// Absolute address of the JMP/NOP instruction to be modified
55    fn code_addr(&self) -> usize {
56        self.code
57    }
58
59    /// Absolute address of the JMP destination
60    fn target_addr(&self) -> usize {
61        self.target
62    }
63
64    /// Absolute address of the associated static key
65    fn key_addr(&self) -> usize {
66        self.key & !1usize
67    }
68
69    /// Return `true` if the likely branch is true branch.
70    fn likely_branch_is_true(&self) -> bool {
71        (self.key & 1usize) != 0
72    }
73
74    /// Unique reference to associated key
75    fn key_mut<M: CodeManipulator, const S: bool>(&self) -> &'static mut GenericStaticKey<M, S> {
76        unsafe { &mut *(self.key_addr() as *mut GenericStaticKey<M, S>) }
77    }
78
79    /// Whether this jump entry is dummy
80    fn is_dummy(&self) -> bool {
81        self.code == 0
82    }
83
84    /// Create a dummy jump entry
85    #[allow(unused)]
86    const fn dummy() -> Self {
87        Self {
88            code: 0,
89            target: 0,
90            key: 0,
91        }
92    }
93}
94
95/// Static key generic over code manipulator.
96///
97/// The `M: CodeManipulator` is required since when toggling the static key, the instructions recorded
98/// at associated jump entries need to be modified, which reside in `.text` section, which is a normally
99/// non-writable memory region. As a result, we need to change the protection of such memory region.
100///
101/// The `const S: bool` indicates the initial status of this key. This value is determined
102/// at compile time, and only affect the initial generation of branch layout. All subsequent
103/// manually disabling and enabling will not be affected by the initial status. The struct
104/// layout is also consistent with different initial status. As a result, it is safe
105/// to assign arbitrary status to the static key generic when using.
106pub struct GenericStaticKey<M: CodeManipulator, const S: bool> {
107    /// Whether current key is true or false
108    ///
109    /// This field is defined as `AtomicBool` to allow interior mutability of static variables to avoid
110    /// creating mutable static.
111    enabled: core::sync::atomic::AtomicBool,
112    /// Start address of associated jump entries.
113    ///
114    /// The jump entries are sorted based on associated static key address in [`global_init`][Self::global_init]
115    /// function. As a result, all jump entries associated with this static key are adjcent to each other.
116    ///
117    /// This value is 0 at static. After calling [`global_init`][Self::global_init], the value will be assigned
118    /// correctly.
119    entries: usize,
120    /// Phantom data to hold `M`
121    phantom: core::marker::PhantomData<M>,
122}
123
124/// Static key to hold data about current status and which jump entries are associated with this key.
125///
126/// For now, it is not encouraged to modify static key in a multi-thread application (which I don't think
127/// is a common situation).
128pub type StaticKey<const S: bool> = GenericStaticKey<crate::os::ArchCodeManipulator, S>;
129/// A [`StaticKey`] with initial status `true`.
130pub type StaticTrueKey = StaticKey<true>;
131/// A [`StaticKey`] with initial status `false`.
132pub type StaticFalseKey = StaticKey<false>;
133/// A [`GenericStaticKey`] with initial status `true`.
134pub type RawStaticTrueKey<M> = GenericStaticKey<M, true>;
135/// A [`GenericStaticKey`] with initial status `false`.
136pub type RawStaticFalseKey<M> = GenericStaticKey<M, false>;
137
138// Insert a dummy static key here, and use this at global_init function. This is
139// to avoid linker failure when there is no jump entries, and thus the __static_keys
140// section is never defined.
141//
142// It should work if we just use global_asm to define a dummy jump entry in __static_keys,
143// however, it seems a Rust bug to erase sections marked with "R" (retained). If we specify
144// --print-gc-sections for linker options, it's strange that linker itself does not
145// erase it. IT IS SO STRANGE.
146static DUMMY_STATIC_KEY: GenericStaticKey<code_manipulate::DummyCodeManipulator, false> =
147    GenericStaticKey::new(false);
148
149impl<M: CodeManipulator, const S: bool> GenericStaticKey<M, S> {
150    /// Whether initial status is `true`
151    #[inline(always)]
152    pub const fn initial_enabled(&self) -> bool {
153        S
154    }
155
156    /// Create a new static key with given default value.
157    const fn new(enabled: bool) -> Self {
158        Self {
159            enabled: core::sync::atomic::AtomicBool::new(enabled),
160            entries: 0,
161            phantom: core::marker::PhantomData,
162        }
163    }
164
165    /// Get pointer to the start of jump entries which associated with current static key
166    fn entries(&self) -> *const JumpEntry {
167        self.entries as *const _
168    }
169
170    /// Enable this static key (make the value to be `true`). Do nothing if current static key is already enabled.
171    ///
172    /// # Safety
173    ///
174    /// This method may be UB if called before [`global_init`] or called in parallel. Never call this method when
175    /// there are multi-threads running. Spawn threads after this method is called. This method may manipulate
176    /// code region memory protection, and if other threads are executing codes in the same code page, it may
177    /// lead to unexpected behaviors.
178    pub unsafe fn enable(&self) {
179        unsafe { static_key_update(self, true) }
180    }
181
182    /// Disable this static key (make the value to be `false`). Do nothing if current static key is already disabled.
183    ///
184    /// # Safety
185    ///
186    /// This method may be UB if called before [`global_init`] or called in parallel. Never call this method when
187    /// there are multi-threads running. Spawn threads after this method is called. This method may manipulate
188    /// code region memory protection, and if other threads are executing codes in the same code page, it may
189    /// lead to unexpected behaviors.
190    pub unsafe fn disable(&self) {
191        unsafe { static_key_update(self, false) }
192    }
193
194    /// Get the current status of this static key
195    pub fn is_enabled(&self) -> bool {
196        self.enabled.load(core::sync::atomic::Ordering::Relaxed)
197    }
198}
199
200/// Count of jump entries in __static_keys section. Note that
201/// there will be several dummy jump entries inside this section.
202pub fn jump_entries_count() -> usize {
203    let jump_entry_start_addr = &raw mut os::JUMP_ENTRY_START;
204    let jump_entry_stop_addr = &raw mut os::JUMP_ENTRY_STOP;
205    unsafe { jump_entry_stop_addr.offset_from(jump_entry_start_addr) as usize }
206}
207
208// ---------------------------- Create ----------------------------
209/// Global state to make sure [`global_init`] is called only once
210static GLOBAL_INIT_STATE: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
211const UNINITIALIZED: usize = 0;
212const INITIALIZING: usize = 1;
213const INITIALIZED: usize = 2;
214
215/// Initialize the static keys data. Always call this method at beginning of application, before using any static key related
216/// functionalities.
217///
218/// This function should be called only once. If calling this method multiple times in multi-threads, only the first invocation
219/// will take effect.
220pub fn global_init() {
221    // DUMMY_STATIC_KEY will never changed, and this will always be a NOP.
222    // Doing this to make sure there are at least one jump entry.
223    if static_branch_unlikely!(DUMMY_STATIC_KEY) {
224        return;
225    }
226
227    // This logic is taken from log::set_logger_inner
228    match GLOBAL_INIT_STATE.compare_exchange(
229        UNINITIALIZED,
230        INITIALIZING,
231        core::sync::atomic::Ordering::Acquire,
232        core::sync::atomic::Ordering::Relaxed,
233    ) {
234        Ok(UNINITIALIZED) => {
235            global_init_inner();
236            GLOBAL_INIT_STATE.store(INITIALIZED, core::sync::atomic::Ordering::Release);
237            // Successful init
238        }
239        Err(INITIALIZING) => {
240            while GLOBAL_INIT_STATE.load(core::sync::atomic::Ordering::Relaxed) == INITIALIZING {
241                core::hint::spin_loop();
242            }
243            // Other has inited
244        }
245        _ => {
246            // Other has inited
247        }
248    }
249}
250
251/// Inner function to [`global_init`]
252fn global_init_inner() {
253    let jump_entry_start_addr = &raw mut os::JUMP_ENTRY_START;
254    let jump_entry_stop_addr = &raw mut os::JUMP_ENTRY_STOP;
255    let jump_entry_len =
256        unsafe { jump_entry_stop_addr.offset_from(jump_entry_start_addr) as usize };
257    let jump_entries =
258        unsafe { core::slice::from_raw_parts_mut(jump_entry_start_addr, jump_entry_len) };
259    // Update jump entries to be absolute address
260    for jump_entry in jump_entries.iter_mut() {
261        if jump_entry.is_dummy() {
262            continue;
263        }
264        jump_entry.make_relative_address_absolute();
265    }
266    // The jump entries are sorted by key address and code address
267    jump_entries.sort_unstable_by_key(|jump_entry| (jump_entry.key_addr(), jump_entry.code_addr()));
268    // Update associated static keys
269    let mut last_key_addr = 0;
270    for jump_entry in jump_entries {
271        if jump_entry.is_dummy() {
272            continue;
273        }
274        let key_addr = jump_entry.key_addr();
275        if key_addr == last_key_addr {
276            continue;
277        }
278        let entries_start_addr = jump_entry as *mut _ as usize;
279        // The M and S generic is useless here
280        let key = jump_entry.key_mut::<code_manipulate::DummyCodeManipulator, true>();
281        // Here we assign associated static key with the start address of jump entries
282        key.entries = entries_start_addr;
283        last_key_addr = key_addr;
284    }
285}
286
287/// Create a new static key with `false` as initial value.
288///
289/// This method should be called to initialize a static mut static key. It is UB to use this method
290/// to create a static key on stack or heap, and use this static key to control branches.
291///
292/// Use [`define_static_key_false`] for short.
293pub const fn new_static_false_key() -> StaticFalseKey {
294    StaticFalseKey::new(false)
295}
296
297/// Create a new static key with `true` as initial value.
298///
299/// This method should be called to initialize a static mut static key. It is UB to use this method
300/// to create a static key on stack or heap, and use this static key to control branches.
301///
302/// Use [`define_static_key_true`] for short.
303pub const fn new_static_true_key() -> StaticTrueKey {
304    StaticTrueKey::new(true)
305}
306
307/// Create a new static key generic over code manipulator with `false` as initial value.
308///
309/// This method should be called to initialize a static mut static key. It is UB to use this method
310/// to create a static key on stack or heap, and use this static key to control branches.
311///
312/// Use [`define_static_key_false_generic`] for short.
313pub const fn new_static_false_key_generic<M: CodeManipulator>() -> RawStaticFalseKey<M> {
314    RawStaticFalseKey::<M>::new(false)
315}
316
317/// Create a new static key generic over code manipulator with `true` as initial value.
318///
319/// This method should be called to initialize a static mut static key. It is UB to use this method
320/// to create a static key on stack or heap, and use this static key to control branches.
321///
322/// Use [`define_static_key_true_generic`] for short.
323pub const fn new_static_true_key_generic<M: CodeManipulator>() -> RawStaticTrueKey<M> {
324    RawStaticTrueKey::<M>::new(true)
325}
326
327/// Define a static key with `false` as initial value.
328///
329/// This macro will define a static mut variable without documentations and visibility modifiers.
330/// Use [`new_static_false_key`] for customization.
331///
332/// # Usage
333///
334/// ```rust
335/// use static_keys::define_static_key_false;
336///
337/// define_static_key_false!(MY_FALSE_STATIC_KEY);
338/// ```
339#[macro_export]
340macro_rules! define_static_key_false {
341    ($key: ident) => {
342        #[used]
343        static $key: $crate::StaticFalseKey = $crate::new_static_false_key();
344    };
345}
346
347/// Define a static key with `true` as initial value.
348///
349/// This macro will define a static mut variable without documentations and visibility modifiers.
350/// Use [`new_static_true_key`] for customization.
351///
352/// # Usage
353///
354/// ```rust
355/// use static_keys::define_static_key_true;
356///
357/// define_static_key_true!(MY_TRUE_STATIC_KEY);
358/// ```
359#[macro_export]
360macro_rules! define_static_key_true {
361    ($key: ident) => {
362        #[used]
363        static $key: $crate::StaticTrueKey = $crate::new_static_true_key();
364    };
365}
366
367/// Define a static key generic over code manipulator with `false` as initial value.
368///
369/// This macro will define a static mut variable without documentations and visibility modifiers.
370/// Use [`new_static_false_key_generic`] for customization.
371/// # Usage
372/// ```rust ignore
373/// use static_keys::{define_static_key_false_generic};
374/// define_static_key_false_generic!(MY_FALSE_STATIC_KEY, DummyCodeManipulator);
375/// ```
376#[macro_export]
377macro_rules! define_static_key_false_generic {
378    ($key: ident, $manipulator: ty) => {
379        #[used]
380        static $key: $crate::RawStaticFalseKey<$manipulator> =
381            $crate::new_static_false_key_generic::<$manipulator>();
382    };
383}
384
385/// Define a static key generic over code manipulator with `true` as initial value.
386///
387/// This macro will define a static mut variable without documentations and visibility modifiers.
388/// Use [`new_static_true_key_generic`] for customization.
389///
390/// # Usage
391/// ```rust ignore
392/// use static_keys::{define_static_key_true_generic};
393///
394/// define_static_key_true_generic!(MY_TRUE_STATIC_KEY, DummyCodeManipulator);
395/// ```
396#[macro_export]
397macro_rules! define_static_key_true_generic {
398    ($key: ident, $manipulator: ty) => {
399        #[used]
400        static $key: $crate::RawStaticTrueKey<$manipulator> =
401            $crate::new_static_true_key_generic::<$manipulator>();
402    };
403}
404
405// ---------------------------- Update ----------------------------
406/// The internal method used for [`GenericStaticKey::enable`] and [`GenericStaticKey::disable`].
407///
408/// This method will update instructions recorded in each jump entries that associated with thie static key
409///
410/// # Safety
411///
412/// This method may be UB if called before [`global_init`] or called in parallel. Never call this method when
413/// there are multi-threads running. Spawn threads after this method is called. This method may manipulate
414/// code region memory protection, and if other threads are executing codes in the same code page, it may
415/// lead to unexpected behaviors.
416unsafe fn static_key_update<M: CodeManipulator, const S: bool>(
417    key: &GenericStaticKey<M, S>,
418    enabled: bool,
419) {
420    if key.enabled.load(core::sync::atomic::Ordering::Relaxed) == enabled {
421        return;
422    }
423    key.enabled
424        .store(enabled, core::sync::atomic::Ordering::Relaxed);
425    let jump_entry_stop_addr = &raw const os::JUMP_ENTRY_STOP;
426    let mut jump_entry_addr = key.entries();
427    if jump_entry_addr.is_null() {
428        // This static key is never used
429        return;
430    }
431    loop {
432        if jump_entry_addr >= jump_entry_stop_addr {
433            break;
434        }
435        let jump_entry = unsafe { &*jump_entry_addr };
436        // Not the same key
437        if key as *const _ as usize != jump_entry.key_addr() {
438            break;
439        }
440
441        unsafe {
442            jump_entry_update::<M>(jump_entry, enabled);
443            jump_entry_addr = jump_entry_addr.add(1);
444        }
445    }
446}
447
448/// Type of the instructions to be modified
449#[derive(Debug)]
450enum JumpLabelType {
451    /// 5 byte NOP
452    Nop = 0,
453    /// 5 byte JMP
454    Jmp = 1,
455}
456
457/// Update instruction recorded in a single jump entry. This is where magic happens
458///
459/// # Safety
460///
461/// This method may be UB if called in parallel. Never call this method when
462/// there are multi-threads running. Spawn threads after this method is called. This method may manipulate
463/// code region memory protection, and if other threads are executing codes in the same code page, it may
464/// lead to unexpected behaviors.
465unsafe fn jump_entry_update<M: CodeManipulator>(jump_entry: &JumpEntry, enabled: bool) {
466    let jump_label_type = if enabled ^ jump_entry.likely_branch_is_true() {
467        JumpLabelType::Jmp
468    } else {
469        JumpLabelType::Nop
470    };
471    let code_bytes = arch::arch_jump_entry_instruction(jump_label_type, jump_entry);
472
473    unsafe {
474        M::write_code(jump_entry.code_addr() as *mut _, &code_bytes);
475    }
476}
477
478// ---------------------------- Use ----------------------------
479/// With given branch as likely branch, initialize the instruction here as JMP instruction
480#[doc(hidden)]
481#[macro_export]
482macro_rules! static_key_init_jmp_with_given_branch_likely {
483    ($key:path, $branch:expr) => {'my_label: {
484        // This is an ugly workaround for https://github.com/rust-lang/rust/issues/128177
485        #[cfg(not(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64"))))]
486        ::core::arch::asm!(
487            $crate::arch_static_key_init_jmp_asm_template!(),
488            label {
489                break 'my_label !$branch;
490            },
491            sym $key,
492            const $branch as usize,
493        );
494        #[cfg(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64")))]
495        ::core::arch::asm!(
496            $crate::arch_static_key_init_jmp_asm_template!(),
497            label {
498                break 'my_label !$branch;
499            },
500            sym $key,
501            const $branch as usize,
502            options(att_syntax),
503        );
504
505        // This branch will be adjcent to the NOP/JMP instruction
506        break 'my_label $branch;
507    }};
508}
509
510/// With given branch as likely branch, initialize the instruction here as NOP instruction
511#[doc(hidden)]
512#[macro_export]
513macro_rules! static_key_init_nop_with_given_branch_likely {
514    ($key:path, $branch:expr) => {'my_label: {
515        // This is an ugly workaround for https://github.com/rust-lang/rust/issues/128177
516        #[cfg(not(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64"))))]
517        ::core::arch::asm!(
518            $crate::arch_static_key_init_nop_asm_template!(),
519            label {
520                break 'my_label !$branch;
521            },
522            sym $key,
523            const $branch as usize,
524        );
525        #[cfg(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64")))]
526        ::core::arch::asm!(
527            $crate::arch_static_key_init_nop_asm_template!(),
528            label {
529                break 'my_label !$branch;
530            },
531            sym $key,
532            const $branch as usize,
533            options(att_syntax),
534        );
535
536        // This branch will be adjcent to the NOP/JMP instruction
537        break 'my_label $branch;
538    }};
539}
540
541/// Use this in a `if` condition, just like the common [`likely`][core::intrinsics::likely]
542/// and [`unlikely`][core::intrinsics::unlikely] intrinsics
543#[macro_export]
544macro_rules! static_branch_unlikely {
545    ($key:path) => {{
546        unsafe {
547            if $key.initial_enabled() {
548                $crate::static_key_init_jmp_with_given_branch_likely! { $key, false }
549            } else {
550                $crate::static_key_init_nop_with_given_branch_likely! { $key, false }
551            }
552        }
553    }};
554}
555
556/// Use this in a `if` condition, just like the common [`likely`][core::intrinsics::likely]
557/// and [`unlikely`][core::intrinsics::unlikely] intrinsics
558#[macro_export]
559macro_rules! static_branch_likely {
560    ($key:path) => {{
561        unsafe {
562            if $key.initial_enabled() {
563                $crate::static_key_init_nop_with_given_branch_likely! { $key, true }
564            } else {
565                $crate::static_key_init_jmp_with_given_branch_likely! { $key, true }
566            }
567        }
568    }};
569}