detcore_model/schedule.rs
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9use std::fmt;
10use std::num::NonZeroUsize;
11use std::str::FromStr;
12
13use nix::sys::signal::Signal;
14use reverie_syscalls::Sysno;
15use serde::Deserialize;
16use serde::Serialize;
17use serde::Serializer;
18use serde::de;
19
20use crate::pid::DetTid;
21use crate::time::DetTime;
22use crate::time::LogicalDuration;
23use crate::time::LogicalTime;
24
25pub mod boundary;
26// Scheduler events
27//--------------------------------------------------------------------------------
28
29/// A scheduled action by one thread in the system. This can be recorded, or replayed to guide the
30/// schedule.
31#[derive(PartialEq, Debug, Eq, Clone, Hash, Serialize, Deserialize)]
32pub struct SchedEvent {
33 /// The thread that originated the event.
34 pub dettid: DetTid,
35 /// The operation performed by the thread.
36 pub op: Op,
37 /// The consecutive count of that same operation (run length encoding).
38 pub count: u32,
39 /// The instruction pointer before this batch of operations.
40 pub start_rip: Option<InstructionPointer>,
41 /// The instruction pointer after this batch of operations.
42 pub end_rip: Option<InstructionPointer>,
43 /// An optional snapshot of the thread logical time at this point.
44 /// This includes time waiting on the global scheduler.
45 pub end_time: Option<LogicalTime>,
46}
47
48/// A more compact printing.
49impl fmt::Display for SchedEvent {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 write!(f, "(tid{}", self.dettid)?;
52 if self.count > 1 {
53 write!(f, " cnt={}", self.count)?;
54 }
55 if let Some(srip) = self.start_rip {
56 write!(f, " strt={:#x}", srip)?;
57 }
58 if let Some(erip) = self.end_rip {
59 write!(f, " end={:#x}", erip)?;
60 }
61 if let Some(time) = self.end_time {
62 write!(f, " time={}", time)?;
63 }
64 write!(f, " {:?})", self.op)?;
65 Ok(())
66 }
67}
68
69impl SchedEvent {
70 /// Add a syscall to the global scheduling history. This takes the instruction pointer of the
71 /// syscall itself.
72 pub fn syscall(dettid: DetTid, sysno: Sysno, phase: SyscallPhase) -> SchedEvent {
73 SchedEvent {
74 dettid,
75 op: Op::Syscall(sysno, phase),
76 count: 1,
77 start_rip: None,
78 end_rip: None,
79 end_time: None,
80 }
81 }
82
83 /// Add a batch of branches to the global scheduling history.
84 pub fn branches(dettid: DetTid, count: u32) -> SchedEvent {
85 SchedEvent {
86 dettid,
87 op: Op::Branch,
88 count,
89 start_rip: None, // TODO: track the start of the interval as well.
90 end_rip: None,
91 end_time: None,
92 }
93 }
94
95 /// Set the logical time directly.
96 pub fn with_time(mut self, time: LogicalDuration) -> SchedEvent {
97 self.end_time = Some(time);
98 self
99 }
100
101 /// Correctly set logical time based on the threads current time.
102 pub fn with_dettime(mut self, dt: &DetTime) -> SchedEvent {
103 self.end_time = Some(dt.without_starting());
104 self
105 }
106
107 /// Set the start_rip field. The instruction pointer before the event began executing.
108 pub fn with_start_rip(mut self, start_rip: InstructionPointer) -> Self {
109 self.start_rip = Some(start_rip);
110 self
111 }
112
113 /// Set the end_rip field. The instruction pointer after the event completed.
114 pub fn with_end_rip(mut self, end_rip: InstructionPointer) -> Self {
115 self.end_rip = Some(end_rip);
116 self
117 }
118}
119
120/// The type of the RIP value.
121pub type InstructionPointer = NonZeroUsize;
122
123/// Which phase of the syscall did we observe on a given event: the prehook or the posthook.
124#[derive(PartialEq, Debug, Eq, Copy, Clone, Hash, Serialize, Deserialize)]
125pub enum SyscallPhase {
126 /// The event was recorded before physically beginning the syscall.
127 Prehook,
128
129 /// An internal (nonblocking) retry of the syscall to check if its done yet (but it wasn't).
130 Polling,
131
132 /// The event was recorded after the syscall logically completed.
133 Posthook,
134}
135
136/// A signal the scheduler can name, INCLUDING the realtime signals `nix` cannot.
137///
138/// ⚠️ WHY THIS IS A RAW `i32` AND NOT A `nix::Signal`. It used to be
139/// `SigWrapper(pub Signal)`, and `nix`'s `Signal` models only 1..=31. Every
140/// cross-task notification path gated on `Signal::try_from(raw)`, so a
141/// `tgkill`/`tkill`/`rt_tgsigqueueinfo`/`rt_sigqueueinfo` carrying
142/// `SIGRTMIN..SIGRTMAX` delivered the signal to the target and then SILENTLY
143/// skipped `NotifySignalPending`. Measured in-tree: the gate admitted exactly
144/// 1..=31 and ZERO of the 31 realtime signals. A thread parked on
145/// `ResourceID::WaitChild` was therefore never woken and the wait hung —
146/// permanently, until the child exited or the thread was killed.
147///
148/// The two `rt_*sigqueueinfo` sites are the sharp end: they are reached from
149/// `sigqueue()`/`pthread_sigqueue()`, which are used with realtime signals in
150/// essentially all real code, so those notification call sites were wired up
151/// and inert for their only normal use.
152///
153/// ⚠️ THE SERIALIZED FORM IS DELIBERATELY UNCHANGED FOR EVERY SIGNAL THAT COULD
154/// ALREADY BE REPRESENTED. 1..=31 still render as the `nix` name (`"SIGUSR1"`),
155/// byte-for-byte as before, so existing schedule files and DETLOG output do not
156/// move. Only the previously-impossible values gain a spelling, `"SIG<n>"`, and
157/// the deserializer accepts both. This is what keeps a model-type widening from
158/// becoming a schedule-compatibility break.
159#[derive(PartialEq, Debug, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
160pub struct SigWrapper(pub i32);
161
162impl SigWrapper {
163 /// The raw signal number, always available.
164 pub fn raw(&self) -> i32 {
165 self.0
166 }
167
168 /// The `nix` signal, when one exists. `None` for realtime signals: they are
169 /// real, deliverable, and simply unnamed by `nix`.
170 pub fn signal(&self) -> Option<Signal> {
171 Signal::try_from(self.0).ok()
172 }
173
174 /// How this signal is spelled in schedule files and DETLOG.
175 pub fn as_string(&self) -> String {
176 match self.signal() {
177 Some(signal) => signal.as_str().to_string(),
178 None => format!("SIG{}", self.0),
179 }
180 }
181}
182
183impl std::fmt::Display for SigWrapper {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 write!(f, "{}", self.as_string())
186 }
187}
188
189impl Serialize for SigWrapper {
190 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
191 where
192 S: Serializer,
193 {
194 serializer.serialize_str(&self.as_string())
195 }
196}
197
198impl<'de> de::Deserialize<'de> for SigWrapper {
199 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
200 where
201 D: de::Deserializer<'de>,
202 {
203 struct SignalVisitor;
204 impl<'de> de::Visitor<'de> for SignalVisitor {
205 type Value = SigWrapper;
206
207 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
208 write!(f, "string representing a signal")
209 }
210
211 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
212 where
213 E: serde::de::Error,
214 {
215 SigWrapper::from_str(v).map_err(serde::de::Error::custom)
216 }
217 }
218
219 deserializer.deserialize_str(SignalVisitor)
220 }
221}
222
223impl FromStr for SigWrapper {
224 type Err = anyhow::Error;
225
226 fn from_str(s: &str) -> anyhow::Result<Self> {
227 // The `nix` name first, so every previously-valid spelling keeps
228 // parsing exactly as it did.
229 if let Ok(signal) = Signal::from_str(s) {
230 return Ok(SigWrapper(signal as i32));
231 }
232 // Then the realtime spelling this type adds.
233 if let Some(rest) = s.strip_prefix("SIG")
234 && let Ok(raw) = rest.parse::<i32>()
235 && raw > 0
236 {
237 return Ok(SigWrapper(raw));
238 }
239 anyhow::bail!("not a signal: {s}")
240 }
241}
242
243impl From<Signal> for SigWrapper {
244 fn from(signal: Signal) -> Self {
245 Self(signal as i32)
246 }
247}
248
249#[cfg(test)]
250mod sigwrapper_tests {
251 use super::*;
252
253 /// ⚠️ THE COMPATIBILITY CLAIM, DEMONSTRATED RATHER THAN ASSERTED.
254 ///
255 /// Widening a serialized model type is only safe if every value that could
256 /// ALREADY be written still round-trips to the same bytes. This walks all 31
257 /// signals `nix` can name and requires the serialized form to be exactly the
258 /// `nix` name — which is what the old `serialize_str(self.0.as_str())`
259 /// produced — so no existing schedule file or DETLOG line moves.
260 #[test]
261 fn every_previously_representable_signal_serializes_byte_identically() {
262 for raw in 1..=31i32 {
263 let Ok(signal) = Signal::try_from(raw) else {
264 continue;
265 };
266 let wrapper = SigWrapper::from(signal);
267 let json = serde_json::to_string(&wrapper).expect("serialize");
268 // Exactly what the pre-widening implementation emitted.
269 let expected = serde_json::to_string(signal.as_str()).expect("serialize name");
270 assert_eq!(
271 json,
272 expected,
273 "signal {raw} ({}) changed its serialized form",
274 signal.as_str()
275 );
276 }
277 }
278
279 /// The previously-impossible values gain a spelling, and it does not collide
280 /// with any `nix` name.
281 #[test]
282 fn realtime_signals_gain_a_distinct_spelling() {
283 for raw in 32..=64i32 {
284 let wrapper = SigWrapper(raw);
285 assert_eq!(wrapper.as_string(), format!("SIG{raw}"));
286 assert_eq!(wrapper.signal(), None, "nix must still not name {raw}");
287 }
288 }
289
290 /// Round-trip in both directions, over the whole space this type now models.
291 #[test]
292 fn every_signal_round_trips_through_serde_and_fromstr() {
293 for raw in 1..=64i32 {
294 let wrapper = SigWrapper(raw);
295 let json = serde_json::to_string(&wrapper).expect("serialize");
296 let back: SigWrapper = serde_json::from_str(&json).expect("deserialize");
297 assert_eq!(back, wrapper, "serde round-trip lost signal {raw}");
298 let parsed = SigWrapper::from_str(&wrapper.as_string()).expect("from_str");
299 assert_eq!(parsed, wrapper, "FromStr round-trip lost signal {raw}");
300 }
301 }
302
303 /// A reader written before the widening emitted names; a reader after it may
304 /// emit either. Both spellings must parse, or an old schedule file stops
305 /// loading.
306 #[test]
307 fn both_spellings_deserialize() {
308 let by_name: SigWrapper = serde_json::from_str("\"SIGUSR1\"").expect("name");
309 assert_eq!(by_name, SigWrapper::from(Signal::SIGUSR1));
310 let by_number: SigWrapper = serde_json::from_str("\"SIG40\"").expect("number");
311 assert_eq!(by_number, SigWrapper(40));
312 }
313}
314
315/// NOTE [Event Semantics]
316///
317/// The observable operations that happen on a guest thread.
318///
319/// Each Op event has a beginning and an end, containing an interval of zero or more instructions
320/// inbetween. Each beginning and end point in time can be thought of as an imaginary marker between
321/// two instructions. Start/end RIP values, if present in the containing `SchedEvent`, correspond
322/// to those beginning/end points and always point to the *next* instruction to execute.
323///
324/// If we speak of an event as an instantaneous thing, we're usually thinking of it as its end
325/// marker. Which is as follows for each:
326///
327/// - Branches: after the branch instruction has retired
328/// - Syscall prehooks: just before the syscall instruction, after whatever came before
329/// - Syscall posthooks: just after the syscall instruction completes
330/// - Rdtsc/Cpuid: just after the designated instruction
331/// - OtherInstructions: just after the region of zero or more branch-free, non-interceptable
332/// instructions.
333/// - SignalReceived: just after the last regular, pre-signal guest instruction, and just before the
334/// first instruction of the signal handler.
335///
336/// If we view each event as a series of instructions contained between its start/end markers, then
337/// the pattern of instructions for each would be as follows. Here we use simple regular
338/// expressions with "B" standing for branch instructions, "S" for syscall instructions, "R" for
339/// RDTSC, "C" for CPUID, and "O" for all other instructions.
340///
341/// - Branch "O*B"
342/// - Syscall prehook: ""
343/// - Syscall posthook: "S"
344/// - Rdtsc: "R"
345/// - Cpuid: "C"
346/// - OtherInstructions: "O*"
347/// - SignalReceived: ""
348///
349/// A few observations about the above:
350///
351/// - Some events always correspond to zero instructions.
352/// - OtherInstructions are omnipresent "dark matter" that we cannot intercept or count, so are
353/// implicitly present between other events.
354/// - Therefore the OtherInstructions event itself is only interesting insofar as it signals the
355/// absence of other events.
356/// - Branches include an implicit prefix of OtherInstructions. This is because for a branch count
357/// greater than 1 to make sense, we need to include the full between-branches O's: "..BO*B..". We
358/// could change this design by going to either extreme. (1) removing implicit O's and changing the
359/// count mechanism to allow repetition of entire sequences "(O*B)^3" instead of "B^3". Or (2),
360/// including implicit O's in all event types, and not recording them explicitly.
361#[derive(PartialEq, Debug, Eq, Copy, Clone, Hash, Serialize, Deserialize)]
362pub enum Op {
363 /// A single retired conditional branch, corresponding to one increment of the RCB counter.
364 Branch,
365
366 /// A nondeterministic rdtsc instruction.
367 Rdtsc,
368
369 /// A nondeterministic cpuid instruction.
370 Cpuid,
371
372 /// A system call performed by the thread. The bool is set to true when this is a syscall
373 /// PREHOOKh event, which is recorded BEFORE the syscall instruction executes, rather than
374 /// after.
375 Syscall(Sysno, SyscallPhase),
376
377 /// An unknown number of other instructions that occured BETWEEN hermit-interceptable events.
378 /// The only way to preempt in between these is expensive single-stepping.
379 OtherInstructions,
380
381 /// The point a signal handler is received, just after whatever regular user instruction
382 /// preceeded it, and just before the first instruction of the signal handler.
383 SignalReceived(SigWrapper),
384}