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}