1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2024.
45//! Tools for displaying process state.
67use core::fmt::Write;
89use kernel::process::Process;
10use kernel::process::{ProcessPrinter, ProcessPrinterContext};
11use kernel::utilities::binary_write::BinaryWrite;
12use kernel::utilities::binary_write::WriteToBinaryOffsetWrapper;
1314/// A Process Printer that displays a process as a human-readable string.
15pub struct ProcessPrinterText {}
1617impl ProcessPrinterText {
18pub fn new() -> ProcessPrinterText {
19 ProcessPrinterText {}
20 }
21}
2223impl ProcessPrinter for ProcessPrinterText {
24// `print_overview()` must be synchronous, but does not assume a synchronous
25 // writer or an infinite (or very large) underlying buffer in the writer. To
26 // do this, this implementation assumes the underlying writer _is_
27 // synchronous. This makes the printing code cleaner, as it does not need to
28 // be broken up into chunks of some length (which would need to match the
29 // underlying buffer length). However, not all writers are synchronous, so
30 // this implementation keeps track of how many bytes were sent on the last
31 // call, and only prints new bytes on the next call. This works by having
32 // the function start from the beginning each time, formats the entire
33 // overview message, and just drops bytes until getting back to where it
34 // left off on the last call.
35 //
36 // ### Assumptions
37 //
38 // This implementation makes two assumptions:
39 // 1. That `print_overview()` is not called in performance-critical code.
40 // Since each time it formats and "prints" the message starting from the
41 // beginning, it duplicates a fair bit of formatting work. Since this is
42 // for debugging, the performance impact of that shouldn't matter.
43 // 2. That `printer_overview()` will be called in a tight loop, and no
44 // process state will change between calls. That could change the length
45 // of the printed message, and lead to gaps or parts of the overview
46 // being duplicated. However, it does not make sense that the kernel
47 // would want to run the process while it is displaying debugging
48 // information about it, so this should be a safe assumption.
49fn print_overview(
50&self,
51 process: &dyn Process,
52 writer: &mut dyn BinaryWrite,
53 context: Option<ProcessPrinterContext>,
54 ) -> Option<ProcessPrinterContext> {
55let offset = context.map_or(0, |c| c.offset);
5657// Process statistics
58let events_queued = process.pending_tasks();
59let syscall_count = process.debug_syscall_count();
60let dropped_upcall_count = process.debug_dropped_upcall_count();
61let restart_count = process.get_restart_count();
6263let addresses = process.get_addresses();
64let sizes = process.get_sizes();
6566let process_struct_memory_location = addresses.sram_end
67 - sizes.grant_pointers
68 - sizes.upcall_list
69 - sizes.process_control_block;
70let sram_grant_size = process_struct_memory_location - addresses.sram_grant_start;
7172let mut bww = WriteToBinaryOffsetWrapper::new(writer);
73 bww.set_offset(offset);
7475let _ = bww.write_fmt(format_args!(
76"\
77 𝐀𝐩𝐩: {} - [{:?}]\
78 \r\n Events Queued: {} Syscall Count: {} Dropped Upcall Count: {}\
79 \r\n Restart Count: {}\
80 \r\n",
81 process.get_process_name(),
82 process.get_state(),
83 events_queued,
84 syscall_count,
85 dropped_upcall_count,
86 restart_count,
87 ));
8889let _ = match process.debug_syscall_last() {
90Some(syscall) => bww.write_fmt(format_args!(" Last Syscall: {:?}\r\n", syscall)),
91None => bww.write_str(" Last Syscall: None\r\n"),
92 };
9394let _ = match process.get_completion_code() {
95Some(opt_cc) => match opt_cc {
96Some(cc) => bww.write_fmt(format_args!(" Completion Code: {}\r\n", cc as isize)),
97None => bww.write_str(" Completion Code: Faulted\r\n"),
98 },
99None => bww.write_str(" Completion Code: None\r\n"),
100 };
101102let _ = bww.write_fmt(format_args!(
103"\
104 \r\n\
105 \r\n ╔═══════════╤══════════════════════════════════════════╗\
106 \r\n ║ Address │ Region Name Used | Allocated (bytes) ║\
107 \r\n ╚{:#010X}═╪══════════════════════════════════════════╝\
108 \r\n │ Grant Ptrs {:6}\
109 \r\n │ Upcalls {:6}\
110 \r\n │ Process {:6}\
111 \r\n {:#010X} ┼───────────────────────────────────────────\
112 \r\n │ ▼ Grant {:6}\
113 \r\n {:#010X} ┼───────────────────────────────────────────\
114 \r\n │ Unused\
115 \r\n {:#010X} ┼───────────────────────────────────────────",
116 addresses.sram_end,
117 sizes.grant_pointers,
118 sizes.upcall_list,
119 sizes.process_control_block,
120 process_struct_memory_location,
121 sram_grant_size,
122 addresses.sram_grant_start,
123 addresses.sram_app_brk,
124 ));
125126// We check to see if the underlying writer has more work to do. If it
127 // does, then its buffer is full and any additional writes are just
128 // going to be dropped. So, we skip doing more printing if there are
129 // bytes remaining as a slight performance optimization.
130if !bww.bytes_remaining() {
131match addresses.sram_heap_start {
132Some(sram_heap_start) => {
133let sram_heap_size = addresses.sram_app_brk - sram_heap_start;
134let sram_heap_allocated = addresses.sram_grant_start - sram_heap_start;
135136let _ = bww.write_fmt(format_args!(
137"\
138 \r\n │ ▲ Heap {:6} | {:6}{} S\
139 \r\n {:#010X} ┼─────────────────────────────────────────── R",
140 sram_heap_size,
141 sram_heap_allocated,
142 exceeded_check(sram_heap_size, sram_heap_allocated),
143 sram_heap_start,
144 ));
145 }
146None => {
147let _ = bww.write_str(
148"\
149 \r\n │ ▲ Heap ? | ? S\
150 \r\n ?????????? ┼─────────────────────────────────────────── R",
151 );
152 }
153 }
154 }
155156if !bww.bytes_remaining() {
157match (addresses.sram_heap_start, addresses.sram_stack_top) {
158 (Some(sram_heap_start), Some(sram_stack_top)) => {
159let sram_data_size = sram_heap_start - sram_stack_top;
160let sram_data_allocated = sram_data_size;
161162let _ = bww.write_fmt(format_args!(
163"\
164 \r\n │ Data {:6} | {:6} A",
165 sram_data_size, sram_data_allocated,
166 ));
167 }
168_ => {
169let _ = bww.write_str(
170"\
171 \r\n │ Data ? | ? A",
172 );
173 }
174 }
175 }
176177if !bww.bytes_remaining() {
178match (addresses.sram_stack_top, addresses.sram_stack_bottom) {
179 (Some(sram_stack_top), Some(sram_stack_bottom)) => {
180let sram_stack_size = sram_stack_top - sram_stack_bottom;
181let sram_stack_allocated = sram_stack_top - addresses.sram_start;
182183let _ = bww.write_fmt(format_args!(
184"\
185 \r\n {:#010X} ┼─────────────────────────────────────────── M\
186 \r\n │ ▼ Stack {:6} | {:6}{}",
187 sram_stack_top,
188 sram_stack_size,
189 sram_stack_allocated,
190 exceeded_check(sram_stack_size, sram_stack_allocated),
191 ));
192 }
193_ => {
194let _ = bww.write_str(
195"\
196 \r\n ?????????? ┼─────────────────────────────────────────── M\
197 \r\n │ ▼ Stack ? | ?",
198 );
199 }
200 }
201 }
202203if !bww.bytes_remaining() {
204let flash_protected_size = addresses.flash_non_protected_start - addresses.flash_start;
205let flash_app_size = addresses.flash_end - addresses.flash_non_protected_start;
206207let _ = bww.write_fmt(format_args!(
208"\
209 \r\n {:#010X} ┼───────────────────────────────────────────\
210 \r\n │ Unused\
211 \r\n {:#010X} ┴───────────────────────────────────────────\
212 \r\n .....\
213 \r\n {:#010X} ┬─────────────────────────────────────────── F\
214 \r\n │ App Flash {:6} L\
215 \r\n {:#010X} ┼─────────────────────────────────────────── A\
216 \r\n │ Protected {:6} S\
217 \r\n {:#010X} ┴─────────────────────────────────────────── H\
218 \r\n",
219 addresses.sram_stack_bottom.unwrap_or(0),
220 addresses.sram_start,
221 addresses.flash_end,
222 flash_app_size,
223 addresses.flash_non_protected_start,
224 flash_protected_size,
225 addresses.flash_start
226 ));
227 }
228229if bww.bytes_remaining() {
230// The underlying writer is indicating there are still bytes
231 // remaining to be sent. That means we want to return a context so
232 // the caller knows to call us again and we can keep printing until
233 // we have displayed the entire process overview.
234let new_context = ProcessPrinterContext {
235 offset: bww.get_index(),
236 };
237Some(new_context)
238 } else {
239None
240}
241 }
242}
243244/// If `size` is greater than `allocated` then it returns a warning string to
245/// help with debugging.
246fn exceeded_check(size: usize, allocated: usize) -> &'static str {
247if size > allocated {
248" EXCEEDED!"
249} else {
250" "
251}
252}