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
134// Insert a dummy static key here, and use this at global_init function. This is
135// to avoid linker failure when there is no jump entries, and thus the __static_keys
136// section is never defined.
137//
138// It should work if we just use global_asm to define a dummy jump entry in __static_keys,
139// however, it seems a Rust bug to erase sections marked with "R" (retained). If we specify
140// --print-gc-sections for linker options, it's strange that linker itself does not
141// erase it. IT IS SO STRANGE.
142static DUMMY_STATIC_KEY: GenericStaticKey<code_manipulate::DummyCodeManipulator, false> =
143    GenericStaticKey::new(false);
144
145impl<M: CodeManipulator, const S: bool> GenericStaticKey<M, S> {
146    /// Whether initial status is `true`
147    #[inline(always)]
148    pub const fn initial_enabled(&self) -> bool {
149        S
150    }
151
152    /// Create a new static key with given default value.
153    const fn new(enabled: bool) -> Self {
154        Self {
155            enabled: core::sync::atomic::AtomicBool::new(enabled),
156            entries: 0,
157            phantom: core::marker::PhantomData,
158        }
159    }
160
161    /// Get pointer to the start of jump entries which associated with current static key
162    fn entries(&self) -> *const JumpEntry {
163        self.entries as *const _
164    }
165
166    /// Enable this static key (make the value to be `true`). Do nothing if current static key is already enabled.
167    ///
168    /// # Safety
169    ///
170    /// This method may be UB if called before [`global_init`] or called in parallel. Never call this method when
171    /// there are multi-threads running. Spawn threads after this method is called. This method may manipulate
172    /// code region memory protection, and if other threads are executing codes in the same code page, it may
173    /// lead to unexpected behaviors.
174    pub unsafe fn enable(&self) {
175        unsafe { static_key_update(self, true) }
176    }
177
178    /// Disable this static key (make the value to be `false`). Do nothing if current static key is already disabled.
179    ///
180    /// # Safety
181    ///
182    /// This method may be UB if called before [`global_init`] or called in parallel. Never call this method when
183    /// there are multi-threads running. Spawn threads after this method is called. This method may manipulate
184    /// code region memory protection, and if other threads are executing codes in the same code page, it may
185    /// lead to unexpected behaviors.
186    pub unsafe fn disable(&self) {
187        unsafe { static_key_update(self, false) }
188    }
189
190    /// Get the current status of this static key
191    pub fn is_enabled(&self) -> bool {
192        self.enabled.load(core::sync::atomic::Ordering::Relaxed)
193    }
194}
195
196/// Count of jump entries in __static_keys section. Note that
197/// there will be several dummy jump entries inside this section.
198pub fn jump_entries_count() -> usize {
199    let jump_entry_start_addr = &raw mut os::JUMP_ENTRY_START;
200    let jump_entry_stop_addr = &raw mut os::JUMP_ENTRY_STOP;
201    unsafe { jump_entry_stop_addr.offset_from(jump_entry_start_addr) as usize }
202}
203
204// ---------------------------- Create ----------------------------
205/// Global state to make sure [`global_init`] is called only once
206static GLOBAL_INIT_STATE: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
207const UNINITIALIZED: usize = 0;
208const INITIALIZING: usize = 1;
209const INITIALIZED: usize = 2;
210
211/// Initialize the static keys data. Always call this method at beginning of application, before using any static key related
212/// functionalities.
213///
214/// This function should be called only once. If calling this method multiple times in multi-threads, only the first invocation
215/// will take effect.
216pub fn global_init() {
217    // DUMMY_STATIC_KEY will never changed, and this will always be a NOP.
218    // Doing this to make sure there are at least one jump entry.
219    if static_branch_unlikely!(DUMMY_STATIC_KEY) {
220        return;
221    }
222
223    // This logic is taken from log::set_logger_inner
224    match GLOBAL_INIT_STATE.compare_exchange(
225        UNINITIALIZED,
226        INITIALIZING,
227        core::sync::atomic::Ordering::Acquire,
228        core::sync::atomic::Ordering::Relaxed,
229    ) {
230        Ok(UNINITIALIZED) => {
231            global_init_inner();
232            GLOBAL_INIT_STATE.store(INITIALIZED, core::sync::atomic::Ordering::Release);
233            // Successful init
234        }
235        Err(INITIALIZING) => {
236            while GLOBAL_INIT_STATE.load(core::sync::atomic::Ordering::Relaxed) == INITIALIZING {
237                core::hint::spin_loop();
238            }
239            // Other has inited
240        }
241        _ => {
242            // Other has inited
243        }
244    }
245}
246
247/// Inner function to [`global_init`]
248fn global_init_inner() {
249    let jump_entry_start_addr = &raw mut os::JUMP_ENTRY_START;
250    let jump_entry_stop_addr = &raw mut os::JUMP_ENTRY_STOP;
251    let jump_entry_len =
252        unsafe { jump_entry_stop_addr.offset_from(jump_entry_start_addr) as usize };
253    let jump_entries =
254        unsafe { core::slice::from_raw_parts_mut(jump_entry_start_addr, jump_entry_len) };
255    // Update jump entries to be absolute address
256    for jump_entry in jump_entries.iter_mut() {
257        if jump_entry.is_dummy() {
258            continue;
259        }
260        jump_entry.make_relative_address_absolute();
261    }
262    // The jump entries are sorted by key address and code address
263    jump_entries.sort_unstable_by_key(|jump_entry| (jump_entry.key_addr(), jump_entry.code_addr()));
264    // Update associated static keys
265    let mut last_key_addr = 0;
266    for jump_entry in jump_entries {
267        if jump_entry.is_dummy() {
268            continue;
269        }
270        let key_addr = jump_entry.key_addr();
271        if key_addr == last_key_addr {
272            continue;
273        }
274        let entries_start_addr = jump_entry as *mut _ as usize;
275        // The M and S generic is useless here
276        let key = jump_entry.key_mut::<code_manipulate::DummyCodeManipulator, true>();
277        // Here we assign associated static key with the start address of jump entries
278        key.entries = entries_start_addr;
279        last_key_addr = key_addr;
280    }
281}
282
283/// Create a new static key with `false` as initial value.
284///
285/// This method should be called to initialize a static mut static key. It is UB to use this method
286/// to create a static key on stack or heap, and use this static key to control branches.
287///
288/// Use [`define_static_key_false`] for short.
289pub const fn new_static_false_key() -> StaticFalseKey {
290    StaticFalseKey::new(false)
291}
292
293/// Create a new static key with `true` as initial value.
294///
295/// This method should be called to initialize a static mut static key. It is UB to use this method
296/// to create a static key on stack or heap, and use this static key to control branches.
297///
298/// Use [`define_static_key_true`] for short.
299pub const fn new_static_true_key() -> StaticTrueKey {
300    StaticTrueKey::new(true)
301}
302
303/// Define a static key with `false` as initial value.
304///
305/// This macro will define a static mut variable without documentations and visibility modifiers.
306/// Use [`new_static_false_key`] for customization.
307///
308/// # Usage
309///
310/// ```rust
311/// use static_keys::define_static_key_false;
312///
313/// define_static_key_false!(MY_FALSE_STATIC_KEY);
314/// ```
315#[macro_export]
316macro_rules! define_static_key_false {
317    ($key: ident) => {
318        #[used]
319        static $key: $crate::StaticFalseKey = $crate::new_static_false_key();
320    };
321}
322
323/// Define a static key with `true` as initial value.
324///
325/// This macro will define a static mut variable without documentations and visibility modifiers.
326/// Use [`new_static_true_key`] for customization.
327///
328/// # Usage
329///
330/// ```rust
331/// use static_keys::define_static_key_true;
332///
333/// define_static_key_true!(MY_TRUE_STATIC_KEY);
334/// ```
335#[macro_export]
336macro_rules! define_static_key_true {
337    ($key: ident) => {
338        #[used]
339        static $key: $crate::StaticTrueKey = $crate::new_static_true_key();
340    };
341}
342
343// ---------------------------- Update ----------------------------
344/// The internal method used for [`GenericStaticKey::enable`] and [`GenericStaticKey::disable`].
345///
346/// This method will update instructions recorded in each jump entries that associated with thie static key
347///
348/// # Safety
349///
350/// This method may be UB if called before [`global_init`] or called in parallel. Never call this method when
351/// there are multi-threads running. Spawn threads after this method is called. This method may manipulate
352/// code region memory protection, and if other threads are executing codes in the same code page, it may
353/// lead to unexpected behaviors.
354unsafe fn static_key_update<M: CodeManipulator, const S: bool>(
355    key: &GenericStaticKey<M, S>,
356    enabled: bool,
357) {
358    if key.enabled.load(core::sync::atomic::Ordering::Relaxed) == enabled {
359        return;
360    }
361    key.enabled
362        .store(enabled, core::sync::atomic::Ordering::Relaxed);
363    let jump_entry_stop_addr = &raw const os::JUMP_ENTRY_STOP;
364    let mut jump_entry_addr = key.entries();
365    if jump_entry_addr.is_null() {
366        // This static key is never used
367        return;
368    }
369    loop {
370        if jump_entry_addr >= jump_entry_stop_addr {
371            break;
372        }
373        let jump_entry = unsafe { &*jump_entry_addr };
374        // Not the same key
375        if key as *const _ as usize != jump_entry.key_addr() {
376            break;
377        }
378
379        unsafe {
380            jump_entry_update::<M>(jump_entry, enabled);
381            jump_entry_addr = jump_entry_addr.add(1);
382        }
383    }
384}
385
386/// Type of the instructions to be modified
387#[derive(Debug)]
388enum JumpLabelType {
389    /// 5 byte NOP
390    Nop = 0,
391    /// 5 byte JMP
392    Jmp = 1,
393}
394
395/// Update instruction recorded in a single jump entry. This is where magic happens
396///
397/// # Safety
398///
399/// This method may be UB if called in parallel. Never call this method when
400/// there are multi-threads running. Spawn threads after this method is called. This method may manipulate
401/// code region memory protection, and if other threads are executing codes in the same code page, it may
402/// lead to unexpected behaviors.
403unsafe fn jump_entry_update<M: CodeManipulator>(jump_entry: &JumpEntry, enabled: bool) {
404    let jump_label_type = if enabled ^ jump_entry.likely_branch_is_true() {
405        JumpLabelType::Jmp
406    } else {
407        JumpLabelType::Nop
408    };
409    let code_bytes = arch::arch_jump_entry_instruction(jump_label_type, jump_entry);
410
411    unsafe {
412        M::write_code(jump_entry.code_addr() as *mut _, &code_bytes);
413    }
414}
415
416// ---------------------------- Use ----------------------------
417/// With given branch as likely branch, initialize the instruction here as JMP instruction
418#[doc(hidden)]
419#[macro_export]
420macro_rules! static_key_init_jmp_with_given_branch_likely {
421    ($key:path, $branch:expr) => {'my_label: {
422        // This is an ugly workaround for https://github.com/rust-lang/rust/issues/128177
423        #[cfg(not(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64"))))]
424        ::core::arch::asm!(
425            $crate::arch_static_key_init_jmp_asm_template!(),
426            label {
427                break 'my_label !$branch;
428            },
429            sym $key,
430            const $branch as usize,
431        );
432        #[cfg(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64")))]
433        ::core::arch::asm!(
434            $crate::arch_static_key_init_jmp_asm_template!(),
435            label {
436                break 'my_label !$branch;
437            },
438            sym $key,
439            const $branch as usize,
440            options(att_syntax),
441        );
442
443        // This branch will be adjcent to the NOP/JMP instruction
444        break 'my_label $branch;
445    }};
446}
447
448/// With given branch as likely branch, initialize the instruction here as NOP instruction
449#[doc(hidden)]
450#[macro_export]
451macro_rules! static_key_init_nop_with_given_branch_likely {
452    ($key:path, $branch:expr) => {'my_label: {
453        // This is an ugly workaround for https://github.com/rust-lang/rust/issues/128177
454        #[cfg(not(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64"))))]
455        ::core::arch::asm!(
456            $crate::arch_static_key_init_nop_asm_template!(),
457            label {
458                break 'my_label !$branch;
459            },
460            sym $key,
461            const $branch as usize,
462        );
463        #[cfg(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64")))]
464        ::core::arch::asm!(
465            $crate::arch_static_key_init_nop_asm_template!(),
466            label {
467                break 'my_label !$branch;
468            },
469            sym $key,
470            const $branch as usize,
471            options(att_syntax),
472        );
473
474        // This branch will be adjcent to the NOP/JMP instruction
475        break 'my_label $branch;
476    }};
477}
478
479/// Use this in a `if` condition, just like the common [`likely`][core::intrinsics::likely]
480/// and [`unlikely`][core::intrinsics::unlikely] intrinsics
481#[macro_export]
482macro_rules! static_branch_unlikely {
483    ($key:path) => {{
484        unsafe {
485            if $key.initial_enabled() {
486                $crate::static_key_init_jmp_with_given_branch_likely! { $key, false }
487            } else {
488                $crate::static_key_init_nop_with_given_branch_likely! { $key, false }
489            }
490        }
491    }};
492}
493
494/// Use this in a `if` condition, just like the common [`likely`][core::intrinsics::likely]
495/// and [`unlikely`][core::intrinsics::unlikely] intrinsics
496#[macro_export]
497macro_rules! static_branch_likely {
498    ($key:path) => {{
499        unsafe {
500            if $key.initial_enabled() {
501                $crate::static_key_init_nop_with_given_branch_likely! { $key, true }
502            } else {
503                $crate::static_key_init_jmp_with_given_branch_likely! { $key, true }
504            }
505        }
506    }};
507}