CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
Loading...
Searching...
No Matches
debug.hh
Go to the documentation of this file.
1// Copyright Microsoft and CHERIoT Contributors.
2// SPDX-License-Identifier: MIT
3
4#pragma once
5#include <__debug.h>
6#include <cheri.hh>
7#include <compartment.h>
8#include <cstddef>
9#include <platform-uart.hh>
10#include <string.h>
11#include <switcher.h>
12#include <timeout.h>
13
14#include <string_view>
15#include <type_traits>
16
17/**
18 * \file
19 *
20 * C++ APIs for assertions, invariants, and writing formatted debug messages to
21 * a UART.
22 *
23 * When enabled (for template parameter) the implementations of these are in
24 * the `debug` library, which must be linked into the firmware image.
25 *
26 * Most of the functionality in this file is exposed via the `ConditionalDebug`
27 * template.
28 */
29
30/**
31 * Concepts used for matching types in the debug code.
32 */
34{
35 /// Helper concept for matching booleans
36 template<typename T>
37 concept IsBool = std::is_same_v<T, bool>;
38
39 /// Helper concept for matching enumerations.
40 template<typename T>
41 concept IsEnum = std::is_enum_v<T>;
42
43 /// Concept for something that can be lazily called to produce a bool.
44 template<typename T>
45 concept LazyAssertion = requires(T v) {
46 { v() } -> IsBool;
47 };
48
49 /// Helper for identifying pointers that are (probably) not C strings.
50 template<typename T>
52 std::is_pointer_v<T> && !std::is_same_v<T, const char *>;
53
54 /**
55 * Helper concept for things that can be converted to addresses but are
56 * not enumerations.
57 */
58 template<typename T>
60 std::convertible_to<T, ptraddr_t> && !IsEnum<T>;
61
62} // namespace DebugConcepts
63
64/**
65 * Abstract class for writing debug output. This is used for custom output.
66 *
67 * This may be changed in the future to provide better support for custom
68 * formatting.
69 */
71{
72 /**
73 * Write a single character.
74 */
75 virtual void write(char) = 0;
76 /**
77 * Write a C string.
78 */
79 virtual void write(const char *) = 0;
80 /**
81 * Write a string view.
82 */
83 virtual void write(std::string_view) = 0;
84 /**
85 * Write a 32-bit unsigned integer.
86 */
87 virtual void write(uint32_t) = 0;
88 /**
89 * Write a 32-bit signed integer.
90 */
91 virtual void write(int32_t) = 0;
92 /**
93 * Write a 64-bit unsigned integer.
94 */
95 virtual void write(uint64_t) = 0;
96 /**
97 * Write a 64-bit signed integer.
98 */
99 virtual void write(int64_t) = 0;
100 /**
101 * Write a single byte as hex with no leading 0x.
102 */
103 virtual void write_hex_byte(uint8_t) = 0;
104 /**
105 * Write a single-precision floating-point value.
106 *
107 * If floating-point support is not compiled into the debug library, this
108 * may print a placeholder.
109 */
110 virtual void write(float) = 0;
111 /**
112 * Write a double-precision floating-point value.
113 *
114 * If floating-point support is not compiled into the debug library, this
115 * may print a placeholder.
116 */
117 virtual void write(double) = 0;
118 /**
119 * Write an integer as hex.
120 */
121 template<typename T>
122 __always_inline void write_hex(T x)
123 requires(std::integral<T>)
124 {
125 if constexpr (sizeof(T) <= 4)
126 {
127 write(static_cast<uint32_t>(x));
128 }
129 else
130 {
131 write(static_cast<uint64_t>(x));
132 }
133 }
134 /**
135 * Write an integer as decimal.
136 */
137 template<typename T>
138 __always_inline void write_decimal(T x)
139 requires(std::integral<T>)
140 {
141 if constexpr (sizeof(T) <= 4)
142 {
143 write(
144 static_cast<int32_t>(static_cast<std::make_unsigned_t<T>>(x)));
145 }
146 else
147 {
148 write(
149 static_cast<int64_t>(static_cast<std::make_unsigned_t<T>>(x)));
150 }
151 }
152};
153
154/**
155 * Helper function for writing enumerations. Enumerations are written using
156 * magic_enum to provide a string and then a numeric value.
157 */
158template<typename T>
159void debug_enum_helper(uintptr_t value, DebugWriter &writer)
161{
162 writer.write(magic_enum::enum_name<T>(static_cast<T>(value)));
163 writer.write('(');
164 writer.write(static_cast<uint32_t>(value));
165 writer.write(')');
166}
167
168/**
169 * Callback for custom types in debug output. This should use the second
170 * argument to write the first argument to the debug output.
171 */
172using DebugCallback = void (*)(uintptr_t, DebugWriter &);
173
174/**
175 * Adaptor that turns an argument of type `T` into a `DebugFormatArgument`.
176 *
177 * Users may specialise these to provide custom formatters. See the
178 * specialisation for enumerations for an example.
179 */
180template<typename T>
182
183/**
184 * Boolean specialisation, prints "true" or "false".
185 */
186template<>
188{
189 __always_inline static DebugFormatArgument construct(bool value)
190 {
191 return {static_cast<uintptr_t>(value),
192 DebugFormatArgumentKind::DebugFormatArgumentBool};
193 }
194};
195
196/**
197 * Character specialisation, prints the character.
198 */
199template<>
201{
202 __always_inline static DebugFormatArgument construct(char value)
203 {
204 return {static_cast<uintptr_t>(value),
205 DebugFormatArgumentKind::DebugFormatArgumentCharacter};
206 }
207};
208
209/**
210 * Unsigned character specialisation, prints the character as a hex number.
211 */
212template<>
214{
215 __always_inline static DebugFormatArgument construct(uint8_t value)
216 {
217 return {static_cast<uintptr_t>(value),
218 DebugFormatArgumentKind::DebugFormatArgumentUnsignedNumber32};
219 }
220};
221
222/**
223 * Unsigned 16-bit integer specialisation, prints the integer as a hex number.
224 */
225template<>
227{
228 __always_inline static DebugFormatArgument construct(uint16_t value)
229 {
230 return {static_cast<uintptr_t>(value),
231 DebugFormatArgumentKind::DebugFormatArgumentUnsignedNumber32};
232 }
233};
234
235/**
236 * Unsigned 32-bit integer specialisation, prints the integer as a hex number.
237 */
238template<>
240{
241 __always_inline static DebugFormatArgument construct(uint32_t value)
242 {
243 return {static_cast<uintptr_t>(value),
244 DebugFormatArgumentKind::DebugFormatArgumentUnsignedNumber32};
245 }
246};
247
248/**
249 * Unsigned 64-bit integer specialisation, prints the integer as a hex number.
250 *
251 * All smaller sizes are handled by zero extending to 32 bits. We treat 64-bit
252 * separately because it requires decomposing the two halves for printing and
253 * that's redundant overhead for the majority of cases.
254 */
255template<>
257{
258 __always_inline static DebugFormatArgument construct(uint64_t value)
259 {
260 uintptr_t fudgedValue;
261 memcpy(&fudgedValue, &value, sizeof(fudgedValue));
262 return {fudgedValue,
263 DebugFormatArgumentKind::DebugFormatArgumentUnsignedNumber64};
264 }
265};
266
267/**
268 * Signed 8-bit integer specialisations, print the integer as a decimal number.
269 */
270template<>
272{
273 __always_inline static DebugFormatArgument construct(int8_t value)
274 {
275 return {static_cast<uintptr_t>(value),
276 DebugFormatArgumentKind::DebugFormatArgumentSignedNumber32};
277 }
278};
279
280/**
281 * Signed 16-bit integer specialisations, print the integer as a decimal number.
282 */
283template<>
285{
286 __always_inline static DebugFormatArgument construct(int16_t value)
287 {
288 return {static_cast<uintptr_t>(value),
289 DebugFormatArgumentKind::DebugFormatArgumentSignedNumber32};
290 }
291};
292
293/**
294 * Signed 32-bit integer specialisations, print the integer as a decimal number.
295 */
296template<>
298{
299 __always_inline static DebugFormatArgument construct(int32_t value)
300 {
301 return {static_cast<uintptr_t>(value),
302 DebugFormatArgumentKind::DebugFormatArgumentSignedNumber32};
303 }
304};
305
306/**
307 * Signed 64-bit integer specialisations, print the integer as a decimal number.
308 *
309 * All smaller sizes are handled by sign extending to 32 bits. We treat 64-bit
310 * separately because it requires 64-bit division to convert a 64-bit integer
311 * to a decimal and that, in turn, requires libcalls.
312 */
313template<>
315{
316 __always_inline static DebugFormatArgument construct(int64_t value)
317 {
318 static_assert(sizeof(uintptr_t) == sizeof(uint64_t));
319 uintptr_t fudgedValue;
320 memcpy(&fudgedValue, &value, sizeof(fudgedValue));
321 return {fudgedValue,
322 DebugFormatArgumentKind::DebugFormatArgumentSignedNumber64};
323 }
324};
325
326template<>
328{
329 __always_inline static DebugFormatArgument construct(float value)
330 {
331 uintptr_t fudgedValue = 0;
332 memcpy(&fudgedValue, &value, sizeof(value));
333 return {fudgedValue, DebugFormatArgumentKind::DebugFormatArgumentFloat};
334 }
335};
336
337template<>
339{
340 __always_inline static DebugFormatArgument construct(double value)
341 {
342 uintptr_t fudgedValue = 0;
343 memcpy(&fudgedValue, &value, sizeof(value));
344 return {fudgedValue,
345 DebugFormatArgumentKind::DebugFormatArgumentDouble};
346 }
347};
348
349/**
350 * C string specialisation, prints the string as-is.
351 */
352template<>
353struct DebugFormatArgumentAdaptor<const char *>
354{
355 __always_inline static DebugFormatArgument construct(const char *value)
356 {
357 return {reinterpret_cast<uintptr_t>(value),
358 DebugFormatArgumentKind::DebugFormatArgumentCString};
359 }
360};
361
362/**
363 * String view specialisation, prints the string as-is.
364 *
365 * Note that this relies on the string view persisting for the duration of the
366 * call. It passes a pointer to the string-view argument.
367 */
368template<>
369struct DebugFormatArgumentAdaptor<std::string_view>
370{
371 __always_inline static DebugFormatArgument
372 construct(std::string_view &value)
373 {
374 return {reinterpret_cast<uintptr_t>(&value),
375 DebugFormatArgumentKind::DebugFormatArgumentStringView};
376 }
377};
378
379/**
380 * String view specialisation, use the C string handler.
381 */
382template<>
383struct DebugFormatArgumentAdaptor<std::string>
384{
385 __always_inline static DebugFormatArgument construct(std::string &value)
386 {
388 value.c_str());
389 }
390};
391
392/**
393 * Enum specialisation, prints the enum as a string and then the numeric value.
394 *
395 * This specialisation uses the generic printing facility in the library call
396 * and passes a callback that will map the enumeration to a string.
397 */
398template<DebugConcepts::IsEnum T>
400{
401 __always_inline static DebugFormatArgument construct(T value)
402 {
403#ifdef CHERIOT_AVOID_CAPRELOCS
404 return {static_cast<uintptr_t>(value),
405 DebugFormatArgumentKind::DebugFormatArgumentUnsignedNumber32};
406#else
407 return {static_cast<uintptr_t>(value),
408 reinterpret_cast<uintptr_t>(&debug_enum_helper<T>)};
409#endif
410 }
411};
412
413/**
414 * Helper for printing timeouts.
415 */
416template<>
417struct DebugFormatArgumentAdaptor<TimeoutArgument>
418{
419 __always_inline static DebugFormatArgument construct(TimeoutArgument value)
420 {
421 uintptr_t v;
422 static_assert(sizeof(v) == sizeof(value));
423 memcpy(&v, &value, sizeof(v));
424 return {v, reinterpret_cast<uintptr_t>(&print)};
425 }
426
427 private:
428 static void print(uintptr_t value, DebugWriter &writer)
429 {
430 TimeoutArgument t(0ULL);
431 static_assert(sizeof(t) == sizeof(value));
432 memcpy(&t, &value, sizeof(t));
433 if (t.is_relative())
434 {
435 writer.write("{ elapsed: ");
436 writer.write(t.relativeTimeout->elapsed);
437 writer.write(", remaining: ");
438 writer.write(t.relativeTimeout->remaining);
439 writer.write(" }");
440 }
441 else
442 {
443 writer.write(t.absoluteTimeout);
444 writer.write(" cycles");
445 }
446 }
447};
448
449/**
450 * Permission set specialisation.
451 */
452template<>
453struct DebugFormatArgumentAdaptor<CHERI::PermissionSet>
454{
455 __always_inline static DebugFormatArgument
456 construct(CHERI::PermissionSet value)
457 {
458 return {static_cast<uintptr_t>(value.as_raw()),
459 DebugFormatArgumentKind::DebugFormatArgumentPermissionSet};
460 }
461};
462
463/**
464 * Null pointer specialisation.
465 */
466template<>
467struct DebugFormatArgumentAdaptor<std::nullptr_t>
468{
469 __always_inline static DebugFormatArgument construct(std::nullptr_t)
470 {
471 return {0, DebugFormatArgumentKind::DebugFormatArgumentPointer};
472 }
473};
474
475/**
476 * Pointer specialisation, prints the pointer as a full capability.
477 */
478template<DebugConcepts::IsPointerButNotCString T>
480{
481 __always_inline static DebugFormatArgument construct(T value)
482 {
483 return {reinterpret_cast<uintptr_t>(
484 reinterpret_cast<const volatile void *>(value)),
485 DebugFormatArgumentKind::DebugFormatArgumentPointer};
486 }
487};
488
489/**
490 * Specialisation for the CHERI capability wrapper class, prints the capability
491 * in the same format as bare pointers.
492 */
493template<typename T, bool Sealed>
494struct DebugFormatArgumentAdaptor<CHERI::Capability<T, Sealed>>
495{
496 __always_inline static DebugFormatArgument
497 construct(CHERI::Capability<T, Sealed> value)
498 {
499 return {reinterpret_cast<uintptr_t>(
500 static_cast<const volatile void *>(value)),
501 DebugFormatArgumentKind::DebugFormatArgumentPointer};
502 }
503};
504
505/**
506 * Specialisation for things that can be converted to addresses, prints as an
507 * unsigned number.
508 */
509template<DebugConcepts::IsConvertibleToAddress T>
511{
512 __always_inline static DebugFormatArgument construct(ptraddr_t value)
513 {
514 return {static_cast<uintptr_t>(value),
515
516 DebugFormatArgumentKind::DebugFormatArgumentUnsignedNumber32};
517 }
518};
519
520/**
521 * Recursive helper that maps from a tuple representing the arguments into a
522 * type-erased array.
523 */
524template<size_t I>
525__always_inline inline void map_debug_argument(DebugFormatArgument *arguments,
526 auto &&argumentTuple)
527{
528 arguments[I] =
530 std::remove_cvref_t<decltype(std::get<I>(argumentTuple))>>{}
531 .construct(std::get<I>(argumentTuple));
532 if constexpr (I > 0)
533 {
534 map_debug_argument<I - 1>(arguments, argumentTuple);
535 }
536}
537
538/**
539 * Convert `args` into a type-erased array of `DebugFormatArgument`s in
540 * `arguments`.
541 */
542template<typename... Args>
543__always_inline inline void
545{
546 if constexpr (sizeof...(Args) > 0)
547 {
548 map_debug_argument<sizeof...(Args) - 1>(arguments,
549 std::forward_as_tuple(args...));
550 }
551}
552
553/**
554 * Library function that writes a debug message. This runs with interrupts
555 * disabled (to avoid interleaving) and prints an array of debug messages. This
556 * is intended to allow a single call to print multiple format strings without
557 * requiring the format strings to be copied, so that the debugging APIs can
558 * wrap a user-provided format string.
559 */
560__cheri_libcall void debug_log_message_write(const char *context,
561 const char *format,
562 DebugFormatArgument *messages,
563 size_t messageCount);
564
565__cheri_libcall void debug_report_failure(const char *kind,
566 const char *file,
567 const char *function,
568 int line,
569 const char *fmt,
570 DebugFormatArgument *arguments,
571 size_t argumentCount);
572
573namespace
574{
575 /**
576 * Helper class wrapping a string for use as a template argument. This is
577 * used to describe a context for conditional debugging that will be
578 * prefixed to debug lines.
579 */
580 template<size_t N>
582 {
583 /**
584 * Constructor, captures the string argument.
585 */
586 constexpr DebugContext(const char (&str)[N])
587 {
588 std::copy_n(str, N, value);
589 }
590
591 /**
592 * Implicit conversion to a C string.
593 */
594 constexpr operator const char *() const
595 {
596 return value;
597 }
598
599 /**
600 * The captured string. Must be public for this class to meet the
601 * requirements of a structural type for use as a template argument.
602 */
603 char value[N];
604 };
605
606 /**
607 * Our libc++ does not currently provide source_location, but our clang
608 * provides the necessary builtins for one.
609 */
611 {
612 /**
613 * Explicitly construct a source location.
614 */
615 constexpr SourceLocation(int lineNumber,
616 int columnNumber,
617 const char *fileName,
618 const char *functionName)
619 : lineNumber(lineNumber),
620 columnNumber(columnNumber),
621 fileName(fileName),
622 functionName(functionName)
623 {
624 }
625
626 /**
627 * Construct a source location for the caller.
628 */
629 static constexpr SourceLocation __always_inline
630 current(int lineNumber = __builtin_LINE(),
631 int columnNumber = __builtin_COLUMN(),
632 const char *fileName = __builtin_FILE(),
633 const char *functionName = __builtin_FUNCTION()) noexcept
634 {
635 return {lineNumber, columnNumber, fileName, functionName};
636 }
637
638 /// Returns the line number for this source location.
639 [[nodiscard]] __always_inline constexpr int line() const noexcept
640 {
641 return lineNumber;
642 }
643 /// Returns the column number for this source location.
644 [[nodiscard]] __always_inline constexpr int column() const noexcept
645 {
646 return columnNumber;
647 }
648 /// Returns the file name for this source location.
649 [[nodiscard]] __always_inline constexpr const char *
650 file_name() const noexcept
651 {
652 return fileName;
653 }
654 /// Returns the function name for this source location.
655 [[nodiscard]] __always_inline constexpr const char *
656 function_name() const noexcept
657 {
658 return functionName;
659 }
660
661 private:
662 /// The line number of this source location.
663 int lineNumber;
664 /// The column number of this source location.
665 int columnNumber;
666 /// The file name of this source location.
667 const char *fileName;
668 /// The function name of this source location.
669 const char *functionName;
670 };
671
672 /**
673 * Debug level.
674 */
675 enum class DebugLevel
676 {
677 /**
678 * This is informative and may be helpful for debugging.
679 */
681 /**
682 * This is probably wrong but the component issuing the warning can
683 * recover. For example, you called an API with an invalid argument.
684 */
686 /**
687 * This is definitely wrong and may cause problems but the rest of the
688 * system is not impacted. For example, you have called an API in a
689 * way that caused the compartment to raise an error, but the
690 * compartment has some form of isolation between callers and so this
691 * doesn't affect anyone else.
692 */
694 /**
695 * Something is completely broken. For example, a compartment with
696 * state shared between callers has detected internal consistency
697 * errors and may not be able to proceed at all.
698 */
700 /**
701 * No debugging information should be reported.
702 */
703 None,
704 };
705
706 /**
707 * Compatibility wrapper that can be constructed from a boolean or a
708 * `DebugLevel`.
709 */
711 {
712 /**
713 * The `DebugLevel` that this encapsulates.
714 */
716
717 /**
718 * Construct from a boolean, enables all debug reporting for `true`.
719 */
720 consteval DebugLevelTemplateArgument(bool enabled)
721 : Value(enabled ? DebugLevel::Information : DebugLevel::None)
722 {
723 }
724
725 /**
726 * Construct from a `DebugLevel`, captures the argument.
727 */
728 consteval DebugLevelTemplateArgument(DebugLevel level) : Value(level) {}
729
730 consteval operator DebugLevel() const
731 {
732 return Value;
733 }
734 };
735
736 /**
737 * Comparison operator to determine whether a provided debug level is above
738 * the threshold at which it should be reported.
739 */
740 consteval std::strong_ordering operator<=>(const DebugLevel Level,
741 const DebugLevel Threshold)
742 {
743 return static_cast<int>(Level) <=> static_cast<int>(Threshold);
744 }
745
746 /**
747 * Conditional debug class. Used to control conditional output and
748 * assertion checking. The first template parameter is a threshold for
749 * reporting. Log messages will be printed if they are at or above this
750 * threshold.
751 *
752 * By default, assertions will be checked if the threshold is Warning or
753 * lower. This can be overridden by passing `true` or `false` as the third
754 * template argument, to explicitly enable or disable assertions.
755 *
756 * Invariants are always checked but, by default, will log a message only if
757 * the threshold is warning or lower. This can be overridden by passing
758 * `true` or `false` as the fourth template argument, to explicitly enable
759 * or disable verbose messages on invariant failure.
760 *
761 * If you wish to reduce verbosity of log messages but still have verbose
762 * error messages for invariant failure, you can pass `true` as the third
763 * template argument. This will unconditionally enable assertions and
764 * verbose messages from invariants, even if
765 *
766 * Uses `Context` to print additional detail on debug lines. Each line is
767 * prefixed with the context string in magenta to make it easy to see debug
768 * output from different subsystems in the same trace.
769 *
770 * This class is expected to be used as a type alias, something like:
771 *
772 * ```c++
773 * using Debug = ConditionalDebug<DEBUG_FOO, "Foo">;
774 * ```
775 *
776 * The xmake build system exposes `debugOption` and `debugLevelOption`
777 * values to define these macros. If `debugOption(foo)` is used, the
778 * option will be exposed as `--debug-foo=[y,n]`, where setting the option
779 * to true defines the threshold as `Information` and false (the default)
780 * as `None`. If `debugLevelOption(foo)` is used, the user can specify any
781 * level from `none` to `critical` on the command line.
782 */
783 template<DebugLevelTemplateArgument Threshold,
784 DebugContext Context,
785 bool EnableAssertions = (Threshold <= DebugLevel::Warning),
786 bool VerboseInvariants = (Threshold <= DebugLevel::Warning)>
788 {
789 public:
790 /**
791 * Log a message.
792 *
793 * This function does nothing if the level is less than the threshold.
794 */
795 template<DebugLevel Level = DebugLevel::Information>
796 static void log(const char *fmt, auto... args)
797 {
798 static_assert(
799 Level != DebugLevel::None,
800 "None is valid only as a threshold, not as a reporting level");
801 if constexpr (Level >= Threshold)
802 {
803 asm volatile("" ::: "memory");
804 const char *context = Context;
805 // Don't create a zero-length on-stack allocation if there are
806 // no arguments.
807 if constexpr (sizeof...(args) == 0)
808 {
809 debug_log_message_write(context, fmt, nullptr, 0);
810 }
811 else
812 {
813 DebugFormatArgument arguments[sizeof...(args)];
814 make_debug_arguments_list(arguments, args...);
816 context, fmt, arguments, sizeof...(args));
817 }
818 asm volatile("" ::: "memory");
819 }
820 }
821
822 template<DebugLevel Level = DebugLevel::Information>
823 static __always_inline void
824 log_if(bool condition, const char *fmt, auto... args)
825 {
826 static_assert(
827 Level != DebugLevel::None,
828 "None is valid only as a threshold, not as a reporting level");
829 if constexpr (Level >= Threshold)
830 {
831 if (condition)
832 {
833 log<Level>(fmt, args...);
834 }
835 }
836 }
837
838 template<DebugLevel Level = DebugLevel::Information,
840 static __always_inline void
841 log_if(Condition condition, const char *fmt, auto... args)
842 {
843 static_assert(
844 Level != DebugLevel::None,
845 "None is valid only as a threshold, not as a reporting level");
846 if constexpr (Level >= Threshold)
847 {
848 if (condition())
849 {
850 log<Level>(fmt, args...);
851 }
852 }
853 }
854
855 /**
856 * Helper to report failure.
857 *
858 * This must not take the `SourceLocation` directly because doing so
859 * prevents the compiler from decomposing and subsequently
860 * constant-propagating its fields in the caller, resulting in 24 bytes
861 * of stack space consumed for every assert or invariant.
862 */
863 template<typename... Args>
864 static inline void report_failure(const char *kind,
865 const char *file,
866 const char *function,
867 int line,
868 const char *fmt,
869 Args... args)
870 {
871 // Ensure that the compiler does not reorder messages.
872 DebugFormatArgument arguments[sizeof...(Args)];
873 make_debug_arguments_list(arguments, args...);
874 const char *context = Context;
875 debug_report_failure(
876 kind, file, function, line, fmt, arguments, sizeof...(Args));
877 }
878
879 /**
880 * Function-like class for invariants that is expected to be used via
881 * the deduction guide as:
882 *
883 * ConditionalDebug::Invariant(someCondition, "A message...", ...);
884 *
885 * Invariants are checked unconditionally but will log a verbose
886 * message only if `VerboseInvariants` is true (by default, if the
887 * threshold is less than or equal to `Warning`).
888 */
889 template<typename... Args>
891 {
892 /**
893 * Constructor, performs the invariant check.
894 */
895 __always_inline
896 Invariant(bool condition,
897 const char *fmt,
898 Args... args,
900 {
901 if (__predict_false(!condition))
902 {
903 if constexpr (VerboseInvariants)
904 {
905 report_failure("Invariant",
906 loc.file_name(),
907 loc.function_name(),
908 loc.line(),
909 fmt,
910 std::forward<Args>(args)...);
911 }
912 __builtin_trap();
913 }
914 }
915 };
916
917 /**
918 * Function-like class for assertions that is expected to be used via
919 * the deduction guide as:
920 *
921 * ConditionalDebug::Assert(someCondition, "A message...", ...);
922 *
923 * Assertions are checked only if `EnableAssertions` is true (by
924 * default, if the threshold is lower than or equal to `Warning`).
925 */
926 template<typename... Args>
927 struct Assert
928 {
929 /**
930 * Constructor, performs the assertion check.
931 */
932 template<typename T>
934 __always_inline
935 Assert(T condition,
936 const char *fmt,
937 Args... args,
939 {
940 if constexpr (EnableAssertions)
941 {
942 if (__predict_false(!condition))
943 {
944 report_failure("Assertion",
945 loc.file_name(),
946 loc.function_name(),
947 loc.line(),
948 fmt,
949 std::forward<Args>(args)...);
950 __builtin_trap();
951 }
952 }
953 }
954
955 /**
956 * Constructor, performs an assertion check.
957 *
958 * This version is passed a lambda that returns a bool, rather than
959 * a boolean condition. This allows the compiler to completely
960 * elide the contents of the lambda in builds where this component
961 * is not being debugged. This version should be used for places
962 * where the assertion condition has side effects.
963 */
964 template<typename T>
966 __always_inline
967 Assert(T &&condition,
968 const char *fmt,
969 Args... args,
971 {
972 if constexpr (EnableAssertions)
973 {
974 if (__predict_false(!condition()))
975 {
976 report_failure("Assertion",
977 loc.file_name(),
978 loc.function_name(),
979 loc.line(),
980 fmt,
981 std::forward<Args>(args)...);
982 __builtin_trap();
983 }
984 }
985 }
986 };
987
988 /**
989 * Deduction guide, allows `Invariant` to be used as if it were a
990 * function.
991 */
992 template<typename T, typename... Ts>
993 Invariant(T, const char *, Ts &&...) -> Invariant<Ts...>;
994
995 /**
996 * Overt wrapper function around Invariant. Sometimes template
997 * deduction guides just don't cut it. At the cost of a
998 * std::make_tuple at the call site, we can still take advantage
999 * of much of the machinery here.
1000 */
1001 template<typename T, typename... Args>
1002 __always_inline static void
1003 invariant(T condition,
1004 const char *fmt,
1005 std::tuple<Args...> args = std::make_tuple(),
1007 {
1008 std::apply(
1009 [&](Args... iargs) {
1010 Invariant<Args...>(
1011 condition, fmt, std::forward<Args>(iargs)..., loc);
1012 },
1013 args);
1014 }
1015
1016 /**
1017 * Deduction guide, allows `Assert` to be used as if it were a
1018 * function.
1019 */
1020 template<typename... Ts>
1021 Assert(bool, const char *, Ts &&...) -> Assert<Ts...>;
1022
1023 /**
1024 * Deduction guide, allows `Assert` to be used as if it were a
1025 * function with a lambda argument.
1026 */
1027 template<typename... Ts>
1028 Assert(auto, const char *, Ts &&...) -> Assert<Ts...>;
1029
1030 /**
1031 * Overt wrapper function around Assert. Sometimes template
1032 * deduction guides just don't cut it. At the cost of a
1033 * std::make_tuple at the call site, we can still take advantage
1034 * of much of the machinery here.
1035 */
1036 template<typename T, typename... Args>
1037 __always_inline static void
1038 assertion(T condition,
1039 const char *fmt,
1040 std::tuple<Args...> args = std::make_tuple(),
1042 {
1043 std::apply(
1044 [&](Args... iargs) {
1045 Assert<Args...>(
1046 condition, fmt, std::forward<Args>(iargs)..., loc);
1047 },
1048 args);
1049 }
1050 };
1051
1052 enum class StackCheckMode
1053 {
1054 Disabled,
1055 LoggingMonotonic,
1056 Logging,
1057 AssertingMonotonic,
1058 Asserting,
1059 };
1060
1061 /**
1062 * Check the (dynamic) stack usage of a function, including all of its
1063 * callees. This is intended to be used in compartment entry points to
1064 * check the stack size that is required for that entry point. Use this
1065 * with some test vectors that explore different code paths to identify the
1066 * maximum stack usage. This can then be used with the
1067 * [[cheriot::minimum_stack]] attribute to ensure that the switcher will
1068 * never invoke the entry point with insufficient stack.
1069 *
1070 * Note that this records only the stack usage for the current compartment
1071 * invocation (including any invoked shared libraries). If this
1072 * compartment calls others, then a larger stack may be required. This
1073 * failure is recoverable: cross-compartment calls should always be assumed
1074 * to potentially fail. This check is to ensure that an attacker cannot
1075 * cause stack overflow to crash the compartment, not to ensure that an
1076 * attacker cannot cause stack overflow to make the operation that they
1077 * requested fail.
1078 *
1079 * Stack checks run in one of three modes:
1080 *
1081 * - Disabled: The checks do not run.
1082 * - Logging: The checks run and print a message if the stack usage
1083 * exceeds expectations.
1084 * - Asserting: After printing the message, the function will trap.
1085 *
1086 * In logging mode, one message will be printed for each invocation of the
1087 * function that exceeds the previous maximum stack usage.
1088 *
1089 * An instance of this should be created as a local variable on entry to a
1090 * function. The destructor (which prints the message) will then run after
1091 * any other destructors, ensuring the most accurate stack usage
1092 * measurement.
1093 *
1094 * Unfortunately, default arguments for templates are not evaluated in the
1095 * enclosing scope and so `__builtin_FUNCTION()` cannot be used here.
1096 * Instead, we must pass this explicitly, typically as something like:
1097 *
1098 * ```c++
1099 * StackUsageCheck<DebugFlag, 128, __PRETTY_FUNCTION__> stackCheck;
1100 * ```
1101 */
1102 template<StackCheckMode Mode, size_t Expected, DebugContext Fn>
1104 {
1105 /**
1106 * The expected maximum. This class is templated on the function name
1107 * and so there is one copy of this per function.
1108 */
1109 static inline size_t stackUsage = Expected;
1110
1111 public:
1112 /**
1113 * Default constructor, does nothing.
1114 */
1115 StackUsageCheck() = default;
1116
1117 /**
1118 * Destructor, runs at the end of the function to print the message.
1119 */
1120 __always_inline ~StackUsageCheck()
1121 {
1122 if constexpr (Mode != StackCheckMode::Disabled)
1123 {
1124 ptraddr_t lowest = stack_lowest_used_address();
1125 ptraddr_t highest =
1126 CHERI::Capability{__builtin_cheri_stack_get()}.top();
1127 size_t used = highest - lowest;
1128
1129 bool tripped = false;
1130 if constexpr ((Mode == StackCheckMode::LoggingMonotonic) ||
1131 (Mode == StackCheckMode::AssertingMonotonic))
1132 {
1133 if (used > stackUsage)
1134 {
1135 tripped = true;
1136 stackUsage = used;
1137 }
1138 }
1139 else if constexpr ((Mode == StackCheckMode::Logging) ||
1140 (Mode == StackCheckMode::Asserting))
1141 {
1142 if (used > Expected)
1143 {
1144 tripped = true;
1145 }
1146 if (used > stackUsage)
1147 {
1148 stackUsage = used;
1149 }
1150 }
1151
1152 if (tripped)
1153 {
1155 "Stack used: {} bytes (max {})", used, stackUsage);
1156 if constexpr ((Mode == StackCheckMode::Asserting) ||
1157 (Mode == StackCheckMode::AssertingMonotonic))
1158 {
1159 __builtin_trap();
1160 }
1161 }
1162 }
1163 }
1164 };
1165
1166} // namespace
C++ helpers for operating on capabilities.
Helper class for accessing capability properties on pointers.
Definition cheri.hh:482
ptraddr_t top() const
Returns the address of the top of this capability.
Definition cheri.hh:1025
Class encapsulating a set of permissions.
Definition cheri.hh:90
constexpr uint32_t as_raw() const
Returns the raw permission mask as an integer containing a bitfield of permissions.
Definition cheri.hh:307
Assert(auto, const char *, Ts &&...) -> Assert< Ts... >
Deduction guide, allows Assert to be used as if it were a function with a lambda argument.
static void invariant(T condition, const char *fmt, std::tuple< Args... > args=std::make_tuple(), SourceLocation loc=SourceLocation::current())
Overt wrapper function around Invariant.
Definition debug.hh:1003
static void report_failure(const char *kind, const char *file, const char *function, int line, const char *fmt, Args... args)
Helper to report failure.
Definition debug.hh:864
Invariant(T, const char *, Ts &&...) -> Invariant< Ts... >
Deduction guide, allows Invariant to be used as if it were a function.
Assert(bool, const char *, Ts &&...) -> Assert< Ts... >
Deduction guide, allows Assert to be used as if it were a function.
static void assertion(T condition, const char *fmt, std::tuple< Args... > args=std::make_tuple(), SourceLocation loc=SourceLocation::current())
Overt wrapper function around Assert.
Definition debug.hh:1038
static void log(const char *fmt, auto... args)
Log a message.
Definition debug.hh:796
~StackUsageCheck()
Destructor, runs at the end of the function to print the message.
Definition debug.hh:1120
StackUsageCheck()=default
Default constructor, does nothing.
Helper concept for matching booleans.
Definition debug.hh:37
Helper concept for things that can be converted to addresses but are not enumerations.
Definition debug.hh:59
Helper concept for matching enumerations.
Definition debug.hh:41
Helper for identifying pointers that are (probably) not C strings.
Definition debug.hh:51
Concept for something that can be lazily called to produce a bool.
Definition debug.hh:45
void(*)(uintptr_t, DebugWriter &) DebugCallback
Callback for custom types in debug output.
Definition debug.hh:172
__cheri_libcall void debug_log_message_write(const char *context, const char *format, DebugFormatArgument *messages, size_t messageCount)
Library function that writes a debug message.
void make_debug_arguments_list(DebugFormatArgument *arguments, Args &...args)
Convert args into a type-erased array of DebugFormatArguments in arguments.
Definition debug.hh:544
void debug_enum_helper(uintptr_t value, DebugWriter &writer)
Helper function for writing enumerations.
Definition debug.hh:159
void map_debug_argument(DebugFormatArgument *arguments, auto &&argumentTuple)
Recursive helper that maps from a tuple representing the arguments into a type-erased array.
Definition debug.hh:525
Concepts used for matching types in the debug code.
Definition debug.hh:34
consteval std::strong_ordering operator<=>(const DebugLevel Level, const DebugLevel Threshold)
Comparison operator to determine whether a provided debug level is above the threshold at which it sh...
Definition debug.hh:740
@ Warning
This is probably wrong but the component issuing the warning can recover.
Definition debug.hh:685
@ Critical
Something is completely broken.
Definition debug.hh:699
@ None
No debugging information should be reported.
Definition debug.hh:703
@ Error
This is definitely wrong and may cause problems but the rest of the system is not impacted.
Definition debug.hh:693
@ Information
This is informative and may be helpful for debugging.
Definition debug.hh:680
Adaptor that turns an argument of type T into a DebugFormatArgument.
Definition debug.hh:181
Abstract class for writing debug output.
Definition debug.hh:71
virtual void write(int32_t)=0
Write a 32-bit signed integer.
virtual void write(float)=0
Write a single-precision floating-point value.
virtual void write(std::string_view)=0
Write a string view.
void write_decimal(T x)
Write an integer as decimal.
Definition debug.hh:138
virtual void write(char)=0
Write a single character.
virtual void write(uint32_t)=0
Write a 32-bit unsigned integer.
virtual void write_hex_byte(uint8_t)=0
Write a single byte as hex with no leading 0x.
virtual void write(int64_t)=0
Write a 64-bit signed integer.
virtual void write(double)=0
Write a double-precision floating-point value.
virtual void write(uint64_t)=0
Write a 64-bit unsigned integer.
void write_hex(T x)
Write an integer as hex.
Definition debug.hh:122
virtual void write(const char *)=0
Write a C string.
Function-like class for assertions that is expected to be used via the deduction guide as:
Definition debug.hh:928
Assert(T condition, const char *fmt, Args... args, SourceLocation loc=SourceLocation::current())
Constructor, performs the assertion check.
Definition debug.hh:935
Assert(T &&condition, const char *fmt, Args... args, SourceLocation loc=SourceLocation::current())
Constructor, performs an assertion check.
Definition debug.hh:967
Function-like class for invariants that is expected to be used via the deduction guide as:
Definition debug.hh:891
Invariant(bool condition, const char *fmt, Args... args, SourceLocation loc=SourceLocation::current())
Constructor, performs the invariant check.
Definition debug.hh:896
char value[N]
The captured string.
Definition debug.hh:603
constexpr DebugContext(const char(&str)[N])
Constructor, captures the string argument.
Definition debug.hh:586
consteval DebugLevelTemplateArgument(DebugLevel level)
Construct from a DebugLevel, captures the argument.
Definition debug.hh:728
consteval DebugLevelTemplateArgument(bool enabled)
Construct from a boolean, enables all debug reporting for true.
Definition debug.hh:720
const DebugLevel Value
The DebugLevel that this encapsulates.
Definition debug.hh:715
Our libc++ does not currently provide source_location, but our clang provides the necessary builtins ...
Definition debug.hh:611
constexpr int column() const noexcept
Returns the column number for this source location.
Definition debug.hh:644
constexpr const char * function_name() const noexcept
Returns the function name for this source location.
Definition debug.hh:656
constexpr int line() const noexcept
Returns the line number for this source location.
Definition debug.hh:639
constexpr const char * file_name() const noexcept
Returns the file name for this source location.
Definition debug.hh:650
static constexpr SourceLocation current(int lineNumber=__builtin_LINE(), int columnNumber=__builtin_COLUMN(), const char *fileName=__builtin_FILE(), const char *functionName=__builtin_FUNCTION()) noexcept
Construct a source location for the caller.
Definition debug.hh:630
constexpr SourceLocation(int lineNumber, int columnNumber, const char *fileName, const char *functionName)
Explicitly construct a source location.
Definition debug.hh:615
APIs for interacting with the switcher.
__cheri_libcall ptraddr_t stack_lowest_used_address(void)
Returns the lowest address that has been stored to on the stack in this compartment invocation.
This file contains the types used for timeouts across scheduler APIs.