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}