capsules_system/
process_printer.rs

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.
4
5//! Tools for displaying process state.
6
7use core::fmt::Write;
8
9use kernel::process::Process;
10use kernel::process::{ProcessPrinter, ProcessPrinterContext};
11use kernel::utilities::binary_write::BinaryWrite;
12use kernel::utilities::binary_write::WriteToBinaryOffsetWrapper;
13
14/// A Process Printer that displays a process as a human-readable string.
15pub struct ProcessPrinterText {}
16
17impl ProcessPrinterText {
18    pub fn new() -> ProcessPrinterText {
19        ProcessPrinterText {}
20    }
21}
22
23impl 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.
49    fn print_overview(
50        &self,
51        process: &dyn Process,
52        writer: &mut dyn BinaryWrite,
53        context: Option<ProcessPrinterContext>,
54    ) -> Option<ProcessPrinterContext> {
55        let offset = context.map_or(0, |c| c.offset);
56
57        // Process statistics
58        let events_queued = process.pending_tasks();
59        let syscall_count = process.debug_syscall_count();
60        let dropped_upcall_count = process.debug_dropped_upcall_count();
61        let restart_count = process.get_restart_count();
62
63        let addresses = process.get_addresses();
64        let sizes = process.get_sizes();
65
66        let process_struct_memory_location = addresses.sram_end
67            - sizes.grant_pointers
68            - sizes.upcall_list
69            - sizes.process_control_block;
70        let sram_grant_size = process_struct_memory_location - addresses.sram_grant_start;
71
72        let mut bww = WriteToBinaryOffsetWrapper::new(writer);
73        bww.set_offset(offset);
74
75        let _ = 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        ));
88
89        let _ = match process.debug_syscall_last() {
90            Some(syscall) => bww.write_fmt(format_args!(" Last Syscall: {:?}\r\n", syscall)),
91            None => bww.write_str(" Last Syscall: None\r\n"),
92        };
93
94        let _ = match process.get_completion_code() {
95            Some(opt_cc) => match opt_cc {
96                Some(cc) => bww.write_fmt(format_args!(" Completion Code: {}\r\n", cc as isize)),
97                None => bww.write_str(" Completion Code: Faulted\r\n"),
98            },
99            None => bww.write_str(" Completion Code: None\r\n"),
100        };
101
102        let _ = 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        ));
125
126        // 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.
130        if !bww.bytes_remaining() {
131            match addresses.sram_heap_start {
132                Some(sram_heap_start) => {
133                    let sram_heap_size = addresses.sram_app_brk - sram_heap_start;
134                    let sram_heap_allocated = addresses.sram_grant_start - sram_heap_start;
135
136                    let _ = 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                }
146                None => {
147                    let _ = bww.write_str(
148                        "\
149                         \r\n             │ ▲ Heap            ? |      ?               S\
150                         \r\n  ?????????? ┼─────────────────────────────────────────── R",
151                    );
152                }
153            }
154        }
155
156        if !bww.bytes_remaining() {
157            match (addresses.sram_heap_start, addresses.sram_stack_top) {
158                (Some(sram_heap_start), Some(sram_stack_top)) => {
159                    let sram_data_size = sram_heap_start - sram_stack_top;
160                    let sram_data_allocated = sram_data_size;
161
162                    let _ = bww.write_fmt(format_args!(
163                        "\
164                         \r\n             │ Data         {:6} | {:6}               A",
165                        sram_data_size, sram_data_allocated,
166                    ));
167                }
168                _ => {
169                    let _ = bww.write_str(
170                        "\
171                         \r\n             │ Data              ? |      ?               A",
172                    );
173                }
174            }
175        }
176
177        if !bww.bytes_remaining() {
178            match (addresses.sram_stack_top, addresses.sram_stack_bottom) {
179                (Some(sram_stack_top), Some(sram_stack_bottom)) => {
180                    let sram_stack_size = sram_stack_top - sram_stack_bottom;
181                    let sram_stack_allocated = sram_stack_top - addresses.sram_start;
182
183                    let _ = 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                _ => {
194                    let _ = bww.write_str(
195                        "\
196                         \r\n  ?????????? ┼─────────────────────────────────────────── M\
197                         \r\n             │ ▼ Stack           ? |      ?",
198                    );
199                }
200            }
201        }
202
203        if !bww.bytes_remaining() {
204            let flash_protected_size = addresses.flash_non_protected_start - addresses.flash_start;
205            let flash_app_size = addresses.flash_end - addresses.flash_non_protected_start;
206
207            let _ = 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        }
228
229        if 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.
234            let new_context = ProcessPrinterContext {
235                offset: bww.get_index(),
236            };
237            Some(new_context)
238        } else {
239            None
240        }
241    }
242}
243
244/// 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 {
247    if size > allocated {
248        " EXCEEDED!"
249    } else {
250        "          "
251    }
252}