Skip to content

Commit 41646b1

Browse files
committed
kern: stack watermark support
Each task has grown a new field, `low_stack_pointer`, that tracks the lowest stack pointer value seen so far (because stacks grow down). We update this value on any kernel entry (interrupt, timer, fault, syscall), so while we will miss very brief excursions of stack _between_ syscalls or interrupts, we should still get a useful value. In addition, the field `past_low_stack_pointer` tracks the lowest stack pointer value across _all previous incarnations_ of the task. This way if the task is periodically restarting, we can still get useful stack stats, including at stack overflow.
1 parent 874c3b8 commit 41646b1

File tree

4 files changed

+74
-3
lines changed

4 files changed

+74
-3
lines changed

sys/kern/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ phash-gen = { path = "../../build/phash-gen" }
3636
[features]
3737
dump = []
3838
nano = []
39+
stack-watermark = []
3940

4041
[lib]
4142
test = false

sys/kern/src/arch/arm_m.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,17 @@ static TICKS: [AtomicU32; 2] = {
936936
#[no_mangle]
937937
pub unsafe extern "C" fn SysTick() {
938938
crate::profiling::event_timer_isr_enter();
939+
940+
// We don't need the current task pointer in this routine, but we'll access
941+
// it briefly to update the stack watermark:
942+
{
943+
let current = CURRENT_TASK_PTR.load(Ordering::Relaxed);
944+
// Safety: `CURRENT_TASK_PTR` is valid once the kernel is started, and
945+
// this interrupt is only enabled once the kernel is started.
946+
let t = unsafe { &mut *current };
947+
t.update_stack_watermark();
948+
}
949+
939950
with_task_table(|tasks| {
940951
// Load the time before this tick event.
941952
let t0 = TICKS[0].load(Ordering::Relaxed);
@@ -1108,6 +1119,15 @@ pub unsafe extern "C" fn DefaultHandler() {
11081119
ipsr & 0x1FF
11091120
};
11101121

1122+
let current = CURRENT_TASK_PTR.load(Ordering::Relaxed);
1123+
uassert!(!current.is_null()); // irq before kernel started?
1124+
1125+
let current_prio = {
1126+
let t = unsafe { &mut *current };
1127+
t.update_stack_watermark();
1128+
t.priority()
1129+
};
1130+
11111131
// The first 16 exceptions are architecturally defined; vendor hardware
11121132
// interrupts start at 16.
11131133
match exception_num {
@@ -1406,7 +1426,12 @@ unsafe extern "C" fn handle_fault(task: *mut task::Task) {
14061426
// assembly fault handler to pass us a legitimate one. We use it
14071427
// immediately and discard it because otherwise it would alias the task
14081428
// table below.
1409-
let t = unsafe { &(*task) };
1429+
let t = unsafe { &mut (*task) };
1430+
// Take the opportunity to update the stack watermark. This is
1431+
// technically wasted effort if the fault is in the kernel, but it's
1432+
// still nice to keep it updated -- and it's critical if the fault is in
1433+
// the task!
1434+
t.update_stack_watermark();
14101435
(
14111436
t.save().exc_return & 0b1000 != 0,
14121437
usize::from(t.descriptor().index),
@@ -1534,7 +1559,12 @@ unsafe extern "C" fn handle_fault(
15341559
// of dereferencing it, as it would otherwise alias the task table obtained
15351560
// later.
15361561
let (exc_return, psp, idx) = unsafe {
1537-
let t = &(*task);
1562+
let t = &mut (*task);
1563+
// Take the opportunity to update the stack watermark. This is
1564+
// technically wasted effort if the fault is in the kernel, but it's
1565+
// still nice to keep it updated -- and it's critical if the fault is in
1566+
// the task!
1567+
t.update_stack_watermark();
15381568
(
15391569
t.save().exc_return,
15401570
t.save().psp,

sys/kern/src/syscalls.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ pub unsafe extern "C" fn syscall_entry(nr: u32, task: *mut Task) {
7070
let idx = {
7171
// Safety: we're trusting the interrupt entry routine to pass us a valid
7272
// task pointer.
73-
let t = unsafe { &*task };
73+
let t = unsafe { &mut *task };
74+
75+
t.update_stack_watermark();
76+
7477
usize::from(t.descriptor().index)
7578
};
7679

sys/kern/src/task.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,20 @@ pub struct Task {
4949
/// Pointer to the ROM descriptor used to create this task, so it can be
5050
/// restarted.
5151
descriptor: &'static TaskDesc,
52+
53+
/// Tracks the lowest stack pointer value (e.g. fullest stack) observed on
54+
/// any kernel entry for this instance of this task.
55+
///
56+
/// Initialized to `u32::MAX` if the task has not yet run.
57+
#[cfg(feature = "stack-watermark")]
58+
stack_pointer_low: u32,
59+
60+
/// Tracks the lowest stack pointer value (e.g. fullest stack) observed on
61+
/// any kernel entry across *any* instance of this task.
62+
///
63+
/// Initialized to `u32::MAX` if the task has not yet run.
64+
#[cfg(feature = "stack-watermark")]
65+
past_stack_pointer_low: u32,
5266
}
5367

5468
impl Task {
@@ -69,6 +83,10 @@ impl Task {
6983
notifications: 0,
7084
save: crate::arch::SavedState::default(),
7185
timer: crate::task::TimerState::default(),
86+
#[cfg(feature = "stack-watermark")]
87+
stack_pointer_low: u32::MAX,
88+
#[cfg(feature = "stack-watermark")]
89+
past_stack_pointer_low: u32::MAX,
7290
}
7391
}
7492

@@ -321,9 +339,28 @@ impl Task {
321339
self.notifications = 0;
322340
self.state = TaskState::default();
323341

342+
#[cfg(feature = "stack-watermark")]
343+
{
344+
self.past_stack_pointer_low =
345+
u32::min(self.past_stack_pointer_low, self.stack_pointer_low);
346+
self.stack_pointer_low = u32::MAX;
347+
}
348+
324349
crate::arch::reinitialize(self);
325350
}
326351

352+
/// Updates the task's stack watermark stats, if enabled.
353+
///
354+
/// If not enabled, this does nothing, so it should be safe to call freely
355+
/// without checking for the feature.
356+
pub fn update_stack_watermark(&mut self) {
357+
#[cfg(feature = "stack-watermark")]
358+
{
359+
self.stack_pointer_low =
360+
u32::min(self.stack_pointer_low, self.save().stack_pointer());
361+
}
362+
}
363+
327364
/// Returns a reference to the `TaskDesc` that was used to initially create
328365
/// this task.
329366
pub fn descriptor(&self) -> &'static TaskDesc {

0 commit comments

Comments
 (0)