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

`+

}

`