Auto merge of #129019 - kromych:master, r=workingjubilee · qinheping/verify-rust-std@55f602f (original) (raw)
``
1
`+
//! Debugging aids.
`
``
2
+
``
3
`+
/// Presence of a debugger. The debugger being concerned
`
``
4
`+
/// is expected to use the OS API to debug this process.
`
``
5
`+
#[derive(Copy, Clone, Debug)]
`
``
6
`+
#[allow(unused)]
`
``
7
`+
pub(crate) enum DebuggerPresence {
`
``
8
`+
/// The debugger is attached to this process.
`
``
9
`+
Detected,
`
``
10
`+
/// The debugger is not attached to this process.
`
``
11
`+
NotDetected,
`
``
12
`+
}
`
``
13
+
``
14
`+
#[cfg(target_os = "windows")]
`
``
15
`+
mod os {
`
``
16
`+
use super::DebuggerPresence;
`
``
17
+
``
18
`+
#[link(name = "kernel32")]
`
``
19
`+
extern "system" {
`
``
20
`+
fn IsDebuggerPresent() -> i32;
`
``
21
`+
}
`
``
22
+
``
23
`+
pub(super) fn is_debugger_present() -> Option {
`
``
24
`+
// SAFETY: No state is shared between threads. The call reads
`
``
25
`+
// a field from the Thread Environment Block using the OS API
`
``
26
`+
// as required by the documentation.
`
``
27
`+
if unsafe { IsDebuggerPresent() } != 0 {
`
``
28
`+
Some(DebuggerPresence::Detected)
`
``
29
`+
} else {
`
``
30
`+
Some(DebuggerPresence::NotDetected)
`
``
31
`+
}
`
``
32
`+
}
`
``
33
`+
}
`
``
34
+
``
35
`+
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
`
``
36
`+
mod os {
`
``
37
`+
use libc::{c_int, sysctl, CTL_KERN, KERN_PROC, KERN_PROC_PID};
`
``
38
+
``
39
`+
use super::DebuggerPresence;
`
``
40
`+
use crate::io::{Cursor, Read, Seek, SeekFrom};
`
``
41
`+
use crate::process;
`
``
42
+
``
43
`+
const P_TRACED: i32 = 0x00000800;
`
``
44
+
``
45
`+
// The assumption is that the kernel structures available to the
`
``
46
`+
// user space may not shrink or repurpose the existing fields over
`
``
47
`+
// time. The kernels normally adhere to that for the backward
`
``
48
`+
// compatibility of the user space.
`
``
49
+
``
50
`` +
// The macOS 14.5 SDK comes with a header MacOSX14.5.sdk/usr/include/sys/sysctl.h
``
``
51
`` +
// that defines struct kinfo_proc
be of 648
bytes on the 64-bit system. That has
``
``
52
`+
// not changed since macOS 10.13 (released in 2017) at least, validated by building
`
``
53
`+
// a C program in XCode while changing the build target. Apple provides this example
`
``
54
`+
// for reference: https://developer.apple.com/library/archive/qa/qa1361/_index.html.
`
``
55
`+
#[cfg(target_vendor = "apple")]
`
``
56
`+
const KINFO_PROC_SIZE: usize = if cfg!(target_pointer_width = "64") { 648 } else { 492 };
`
``
57
`+
#[cfg(target_vendor = "apple")]
`
``
58
`+
const KINFO_PROC_FLAGS_OFFSET: u64 = if cfg!(target_pointer_width = "64") { 32 } else { 16 };
`
``
59
+
``
60
`+
// Works for FreeBSD stable (13.3, 13.4) and current (14.0, 14.1).
`
``
61
`+
// The size of the structure has stayed the same for a long time,
`
``
62
`+
// at least since 2005:
`
``
63
`+
// https://lists.freebsd.org/pipermail/freebsd-stable/2005-November/019899.html
`
``
64
`+
#[cfg(target_os = "freebsd")]
`
``
65
`+
const KINFO_PROC_SIZE: usize = if cfg!(target_pointer_width = "64") { 1088 } else { 768 };
`
``
66
`+
#[cfg(target_os = "freebsd")]
`
``
67
`+
const KINFO_PROC_FLAGS_OFFSET: u64 = if cfg!(target_pointer_width = "64") { 368 } else { 296 };
`
``
68
+
``
69
`+
pub(super) fn is_debugger_present() -> Option {
`
``
70
`+
debug_assert_ne!(KINFO_PROC_SIZE, 0);
`
``
71
+
``
72
`` +
let mut flags = [0u8; 4]; // ki_flag
under FreeBSD and p_flag
under macOS.
``
``
73
`+
let mut mib = [CTL_KERN, KERN_PROC, KERN_PROC_PID, process::id() as c_int];
`
``
74
`+
let mut info_size = KINFO_PROC_SIZE;
`
``
75
`+
let mut kinfo_proc = [0u8; KINFO_PROC_SIZE];
`
``
76
+
``
77
`+
// SAFETY: No state is shared with other threads. The sysctl call
`
``
78
`+
// is safe according to the documentation.
`
``
79
`+
if unsafe {
`
``
80
`+
sysctl(
`
``
81
`+
mib.as_mut_ptr(),
`
``
82
`+
mib.len() as u32,
`
``
83
`+
kinfo_proc.as_mut_ptr().cast(),
`
``
84
`+
&mut info_size,
`
``
85
`+
core::ptr::null_mut(),
`
``
86
`+
0,
`
``
87
`+
)
`
``
88
`+
} != 0
`
``
89
`+
{
`
``
90
`+
return None;
`
``
91
`+
}
`
``
92
`+
debug_assert_eq!(info_size, KINFO_PROC_SIZE);
`
``
93
+
``
94
`+
let mut reader = Cursor::new(kinfo_proc);
`
``
95
`+
reader.seek(SeekFrom::Start(KINFO_PROC_FLAGS_OFFSET)).ok()?;
`
``
96
`+
reader.read_exact(&mut flags).ok()?;
`
``
97
`+
// Just in case, not limiting this to the little-endian systems.
`
``
98
`+
let flags = i32::from_ne_bytes(flags);
`
``
99
+
``
100
`+
if flags & P_TRACED != 0 {
`
``
101
`+
Some(DebuggerPresence::Detected)
`
``
102
`+
} else {
`
``
103
`+
Some(DebuggerPresence::NotDetected)
`
``
104
`+
}
`
``
105
`+
}
`
``
106
`+
}
`
``
107
+
``
108
`+
#[cfg(target_os = "linux")]
`
``
109
`+
mod os {
`
``
110
`+
use super::DebuggerPresence;
`
``
111
`+
use crate::fs::File;
`
``
112
`+
use crate::io::Read;
`
``
113
+
``
114
`+
pub(super) fn is_debugger_present() -> Option {
`
``
115
`+
// This function is crafted with the following goals:
`
``
116
`+
// * Memory efficiency: It avoids crashing the panicking process due to
`
``
117
`+
// out-of-memory (OOM) conditions by not using large heap buffers or
`
``
118
`+
// allocating significant stack space, which could lead to stack overflow.
`
``
119
`+
// * Minimal binary size: The function uses a minimal set of facilities
`
``
120
`+
// from the standard library to avoid increasing the resulting binary size.
`
``
121
`+
//
`
``
122
`` +
// To achieve these goals, the function does not use [std::io::BufReader]
``
``
123
`+
// and instead reads the file byte by byte using a sliding window approach.
`
``
124
`+
// It's important to note that the "/proc/self/status" pseudo-file is synthesized
`
``
125
`+
// by the Virtual File System (VFS), meaning it is not read from a slow or
`
``
126
`+
// non-volatile storage medium so buffering might not be as beneficial because
`
``
127
`+
// all data is read from memory, though this approach does incur a syscall for
`
``
128
`+
// each byte read.
`
``
129
`+
//
`
``
130
`+
// We cannot make assumptions about the file size or the position of the
`
``
131
`+
// target prefix ("TracerPid:"), so the function does not use
`
``
132
`` +
// [std::fs::read_to_string]
thus not employing UTF-8 to ASCII checking,
``
``
133
`+
// conversion, or parsing as we're looking for an ASCII prefix.
`
``
134
`+
//
`
``
135
`+
// These condiderations make the function deviate from the familiar concise pattern
`
``
136
`+
// of searching for a string in a text file.
`
``
137
+
``
138
`+
fn read_byte(file: &mut File) -> Option {
`
``
139
`+
let mut buffer = [0];
`
``
140
`+
file.read_exact(&mut buffer).ok()?;
`
``
141
`+
Some(buffer[0])
`
``
142
`+
}
`
``
143
+
``
144
`+
// The ASCII prefix of the datum we're interested in.
`
``
145
`+
const TRACER_PID: &[u8] = b"TracerPid:\t";
`
``
146
+
``
147
`+
let mut file = File::open("/proc/self/status").ok()?;
`
``
148
`+
let mut matched = 0;
`
``
149
+
``
150
`` +
// Look for the TRACER_PID
prefix.
``
``
151
`+
while let Some(byte) = read_byte(&mut file) {
`
``
152
`+
if byte == TRACER_PID[matched] {
`
``
153
`+
matched += 1;
`
``
154
`+
if matched == TRACER_PID.len() {
`
``
155
`+
break;
`
``
156
`+
}
`
``
157
`+
} else {
`
``
158
`+
matched = 0;
`
``
159
`+
}
`
``
160
`+
}
`
``
161
+
``
162
`+
// Was the prefix found?
`
``
163
`+
if matched != TRACER_PID.len() {
`
``
164
`+
return None;
`
``
165
`+
}
`
``
166
+
``
167
`+
// It was; get the ASCII representation of the first digit
`
``
168
`+
// of the PID. That is enough to see if there is a debugger
`
``
169
`+
// attached as the kernel does not pad the PID on the left
`
``
170
`+
// with the leading zeroes.
`
``
171
`+
let byte = read_byte(&mut file)?;
`
``
172
`+
if byte.is_ascii_digit() && byte != b'0' {
`
``
173
`+
Some(DebuggerPresence::Detected)
`
``
174
`+
} else {
`
``
175
`+
Some(DebuggerPresence::NotDetected)
`
``
176
`+
}
`
``
177
`+
}
`
``
178
`+
}
`
``
179
+
``
180
`+
#[cfg(not(any(
`
``
181
`+
target_os = "windows",
`
``
182
`+
target_vendor = "apple",
`
``
183
`+
target_os = "freebsd",
`
``
184
`+
target_os = "linux"
`
``
185
`+
)))]
`
``
186
`+
mod os {
`
``
187
`+
pub(super) fn is_debugger_present() -> Optionsuper::DebuggerPresence {
`
``
188
`+
None
`
``
189
`+
}
`
``
190
`+
}
`
``
191
+
``
192
`+
/// Detect the debugger presence.
`
``
193
`+
///
`
``
194
`+
/// The code does not try to detect the debugger at all costs (e.g., when anti-debugger
`
``
195
`+
/// tricks are at play), it relies on the interfaces provided by the OS.
`
``
196
`+
///
`
``
197
`+
/// Return value:
`
``
198
`` +
/// * None
: it's not possible to conclude whether the debugger is attached to this
``
``
199
`+
/// process or not. When checking for the presence of the debugger, the detection logic
`
``
200
`+
/// encountered an issue, such as the OS API throwing an error or the feature not being
`
``
201
`+
/// implemented.
`
``
202
`` +
/// * Some(DebuggerPresence::Detected)
: yes, the debugger is attached
``
``
203
`+
/// to this process.
`
``
204
`` +
/// * Some(DebuggerPresence::NotDetected)
: no, the debugger is not
``
``
205
`+
/// attached to this process.
`
``
206
`+
pub(crate) fn is_debugger_present() -> Option {
`
``
207
`+
if cfg!(miri) { None } else { os::is_debugger_present() }
`
``
208
`+
}
`
``
209
+
``
210
`+
/// Execute the breakpoint instruction if the debugger presence is detected.
`
``
211
`+
/// Useful for breaking into the debugger without the need to set a breakpoint
`
``
212
`+
/// in the debugger.
`
``
213
`+
///
`
``
214
`+
/// Note that there is a race between attaching or detaching the debugger, and running the
`
``
215
`` +
/// breakpoint instruction. This is nonetheless memory-safe, like [crate::process::abort
]
``
``
216
`+
/// is. In case the debugger is attached and the function is about
`
``
217
`+
/// to run the breakpoint instruction yet right before that the debugger detaches, the
`
``
218
`+
/// process will crash due to running the breakpoint instruction and the debugger not
`
``
219
`+
/// handling the trap exception.
`
``
220
`+
pub(crate) fn breakpoint_if_debugging() -> Option {
`
``
221
`+
let debugger_present = is_debugger_present();
`
``
222
`+
if let Some(DebuggerPresence::Detected) = debugger_present {
`
``
223
`+
// SAFETY: Executing the breakpoint instruction. No state is shared
`
``
224
`+
// or modified by this code.
`
``
225
`+
unsafe { core::intrinsics::breakpoint() };
`
``
226
`+
}
`
``
227
+
``
228
`+
debugger_present
`
``
229
`+
}
`