| // Copyright Microsoft and CHERIoT Contributors. | |
| // SPDX-License-Identifier: MIT | |
| #pragma once | |
| #include <__debug.h> | |
| #include <cheri.hh> | |
| #include <compartment.h> | |
| #include <concepts> | |
| #include <cstddef> | |
| #include <platform-uart.hh> | |
| #include <string.h> | |
| #include <switcher.h> | |
| #include <array> | |
| #include <string_view> | |
| #include <type_traits> | |
| namespace DebugConcepts | |
| { | |
| /// Helper concept for matching booleans | |
| template<typename T> | |
| concept IsBool = std::is_same_v<T, bool>; | |
| /// Helper concept for matching enumerations. | |
| template<typename T> | |
| concept IsEnum = std::is_enum_v<T>; | |
| /// Concept for something that can be lazily called to produce a bool. | |
| template<typename T> | |
| concept LazyAssertion = requires(T v) | |
| { | |
| { | |
| v() | |
| } -> IsBool; | |
| }; | |
| template<typename T> | |
| concept IsPointerButNotCString = | |
| std::is_pointer_v<T> && !std::is_same_v<T, const char *>; | |
| template<typename T> | |
| concept IsConvertibleToAddress = | |
| std::convertible_to<T, ptraddr_t> && !IsEnum<T>; | |
| } // namespace DebugConcepts | |
| /** | |
| * Abstract class for writing debug output. This is used for custom output. | |
| * | |
| * This may be changed in the future to provide better support for custom | |
| * formatting. | |
| */ | |
| struct DebugWriter | |
| { | |
| /** | |
| * Write a single character. | |
| */ | |
| virtual void write(char) = 0; | |
| /** | |
| * Write a C string. | |
| */ | |
| virtual void write(const char *) = 0; | |
| /** | |
| * Write a string view. | |
| */ | |
| virtual void write(std::string_view) = 0; | |
| /** | |
| * Write a 32-bit unsigned integer. | |
| */ | |
| virtual void write(uint32_t) = 0; | |
| /** | |
| * Write a 32-bit signed integer. | |
| */ | |
| virtual void write(int32_t) = 0; | |
| /** | |
| * Write a 64-bit unsigned integer. | |
| */ | |
| virtual void write(uint64_t) = 0; | |
| /** | |
| * Write a 64-bit signed integer. | |
| */ | |
| virtual void write(int64_t) = 0; | |
| }; | |
| /** | |
| * Helper function for writing enumerations. Enumerations are written using | |
| * magic_enum to provide a string and then a numeric value. | |
| */ | |
| template<typename T> | |
| void debug_enum_helper(uintptr_t value, | |
| DebugWriter &writer) requires DebugConcepts::IsEnum<T> | |
| { | |
| writer.write(magic_enum::enum_name<T>(static_cast<T>(value))); | |
| writer.write('('); | |
| writer.write(uint32_t(value)); | |
| writer.write(')'); | |
| } | |
| /** | |
| * Callback for custom types in debug output. This should use the second | |
| * argument to write the first argument to the debug output. | |
| */ | |
| using DebugCallback = void (*)(uintptr_t, DebugWriter &); | |
| /** | |
| * Adaptor that turns an argument of type `T` into a `DebugFormatArgument`. | |
| * | |
| * Users may specialise these to provide custom formatters. See the | |
| * specialisation for enumerations for an example. | |
| */ | |
| template<typename T> | |
| struct DebugFormatArgumentAdaptor; | |
| /** | |
| * Boolean specialisation, prints "true" or "false". | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<bool> | |
| { | |
| __always_inline static DebugFormatArgument construct(bool value) | |
| { | |
| return {static_cast<uintptr_t>(value), | |
| DebugFormatArgumentKind::DebugFormatArgumentBool}; | |
| } | |
| }; | |
| /** | |
| * Character specialisation, prints the character. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<char> | |
| { | |
| __always_inline static DebugFormatArgument construct(char value) | |
| { | |
| return {static_cast<uintptr_t>(value), | |
| DebugFormatArgumentKind::DebugFormatArgumentCharacter}; | |
| } | |
| }; | |
| /** | |
| * Unsigned character specialisation, prints the character as a hex number. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<uint8_t> | |
| { | |
| __always_inline static DebugFormatArgument construct(uint8_t value) | |
| { | |
| return {static_cast<uintptr_t>(value), | |
| DebugFormatArgumentKind::DebugFormatArgumentUnsignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * Unsigned 16-bit integer specialisation, prints the integer as a hex number. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<uint16_t> | |
| { | |
| __always_inline static DebugFormatArgument construct(uint16_t value) | |
| { | |
| return {static_cast<uintptr_t>(value), | |
| DebugFormatArgumentKind::DebugFormatArgumentUnsignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * Unsigned 32-bit integer specialisation, prints the integer as a hex number. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<uint32_t> | |
| { | |
| __always_inline static DebugFormatArgument construct(uint32_t value) | |
| { | |
| return {static_cast<uintptr_t>(value), | |
| DebugFormatArgumentKind::DebugFormatArgumentUnsignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * Unsigned 64-bit integer specialisation, prints the integer as a hex number. | |
| * | |
| * All smaller sizes are handled by zero extending to 32 bits. We treat 64-bit | |
| * separately because it requires decomposing the two halves for printing and | |
| * that's redundant overhead for the majority of cases. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<uint64_t> | |
| { | |
| __always_inline static DebugFormatArgument construct(uint64_t value) | |
| { | |
| uintptr_t fudgedValue; | |
| memcpy(&fudgedValue, &value, sizeof(fudgedValue)); | |
| return {fudgedValue, | |
| DebugFormatArgumentKind::DebugFormatArgumentUnsignedNumber64}; | |
| } | |
| }; | |
| /** | |
| * Signed 8-bit integer specialisations, print the integer as a decimal number. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<int8_t> | |
| { | |
| __always_inline static DebugFormatArgument construct(int8_t value) | |
| { | |
| return {static_cast<uintptr_t>(value), | |
| DebugFormatArgumentKind::DebugFormatArgumentSignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * Signed 16-bit integer specialisations, print the integer as a decimal number. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<int16_t> | |
| { | |
| __always_inline static DebugFormatArgument construct(int16_t value) | |
| { | |
| return {static_cast<uintptr_t>(value), | |
| DebugFormatArgumentKind::DebugFormatArgumentSignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * Signed 32-bit integer specialisations, print the integer as a decimal number. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<int32_t> | |
| { | |
| __always_inline static DebugFormatArgument construct(int32_t value) | |
| { | |
| return {static_cast<uintptr_t>(value), | |
| DebugFormatArgumentKind::DebugFormatArgumentSignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * Signed 64-bit integer specialisations, print the integer as a decimal number. | |
| * | |
| * All smaller sizes are handled by sign extending to 32 bits. We treat 64-bit | |
| * separately because it requires 64-bit division to convert a 64-bit integer | |
| * to a decimal and that, in turn, requires libcalls. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<int64_t> | |
| { | |
| __always_inline static DebugFormatArgument construct(int64_t value) | |
| { | |
| static_assert(sizeof(uintptr_t) == sizeof(uint64_t)); | |
| uintptr_t fudgedValue; | |
| memcpy(&fudgedValue, &value, sizeof(fudgedValue)); | |
| return {fudgedValue, | |
| DebugFormatArgumentKind::DebugFormatArgumentSignedNumber64}; | |
| } | |
| }; | |
| /** | |
| * C string specialisation, prints the string as-is. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<const char *> | |
| { | |
| __always_inline static DebugFormatArgument construct(const char *value) | |
| { | |
| return {reinterpret_cast<uintptr_t>(value), | |
| DebugFormatArgumentKind::DebugFormatArgumentCString}; | |
| } | |
| }; | |
| /** | |
| * String view specialisation, prints the string as-is. | |
| * | |
| * Note that this relies on the string view persisting for the duration of the | |
| * call. It passes a pointer to the string-view argument. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<std::string_view> | |
| { | |
| __always_inline static DebugFormatArgument | |
| construct(std::string_view &value) | |
| { | |
| return {reinterpret_cast<uintptr_t>(&value), | |
| DebugFormatArgumentKind::DebugFormatArgumentStringView}; | |
| } | |
| }; | |
| /** | |
| * String view specialisation, use the C string handler. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<std::string> | |
| { | |
| __always_inline static DebugFormatArgument construct(std::string &value) | |
| { | |
| return DebugFormatArgumentAdaptor<const char *>::construct( | |
| value.c_str()); | |
| } | |
| }; | |
| /** | |
| * Enum specialisation, prints the enum as a string and then the numeric value. | |
| * | |
| * This specialisation uses the generic printing facility in the library call | |
| * and passes a callback that will map the enumeration to a string. | |
| */ | |
| template<DebugConcepts::IsEnum T> | |
| struct DebugFormatArgumentAdaptor<T> | |
| { | |
| __always_inline static DebugFormatArgument construct(T value) | |
| { | |
| #ifdef CHERIOT_AVOID_CAPRELOCS | |
| return {static_cast<uintptr_t>(value), | |
| DebugFormatArgumentKind::DebugFormatArgumentUnsignedNumber32}; | |
| #else | |
| return {static_cast<uintptr_t>(value), | |
| reinterpret_cast<uintptr_t>(&debug_enum_helper<T>)}; | |
| #endif | |
| } | |
| }; | |
| /** | |
| * Permission set specialisation. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<CHERI::PermissionSet> | |
| { | |
| __always_inline static DebugFormatArgument | |
| construct(CHERI::PermissionSet value) | |
| { | |
| return {static_cast<uintptr_t>(value.as_raw()), | |
| DebugFormatArgumentKind::DebugFormatArgumentPermissionSet}; | |
| } | |
| }; | |
| /** | |
| * Null pointer specialisation. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<std::nullptr_t> | |
| { | |
| __always_inline static DebugFormatArgument construct(std::nullptr_t) | |
| { | |
| return {0, DebugFormatArgumentKind::DebugFormatArgumentPointer}; | |
| } | |
| }; | |
| /** | |
| * Pointer specialisation, prints the pointer as a full capability. | |
| */ | |
| template<DebugConcepts::IsPointerButNotCString T> | |
| struct DebugFormatArgumentAdaptor<T> | |
| { | |
| __always_inline static DebugFormatArgument construct(T value) | |
| { | |
| return {reinterpret_cast<uintptr_t>( | |
| static_cast<const volatile void *>(value)), | |
| DebugFormatArgumentKind::DebugFormatArgumentPointer}; | |
| } | |
| }; | |
| /** | |
| * Specialisation for the CHERI capability wrapper class, prints the capability | |
| * in the same format as bare pointers. | |
| */ | |
| template<typename T> | |
| struct DebugFormatArgumentAdaptor<CHERI::Capability<T>> | |
| { | |
| __always_inline static DebugFormatArgument | |
| construct(CHERI::Capability<T> value) | |
| { | |
| return {reinterpret_cast<uintptr_t>( | |
| static_cast<const volatile void *>(value)), | |
| DebugFormatArgumentKind::DebugFormatArgumentPointer}; | |
| } | |
| }; | |
| /** | |
| * Specialisation for things that can be converted to addresses, prints as an | |
| * unsigned number. | |
| */ | |
| template<DebugConcepts::IsConvertibleToAddress T> | |
| struct DebugFormatArgumentAdaptor<T> | |
| { | |
| __always_inline static DebugFormatArgument construct(ptraddr_t value) | |
| { | |
| return {static_cast<uintptr_t>(value), | |
| DebugFormatArgumentKind::DebugFormatArgumentUnsignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * Recursive helper that maps from a tuple representing the arguments into a | |
| * type-erased array. | |
| */ | |
| template<size_t I> | |
| __always_inline inline void map_debug_argument(DebugFormatArgument *arguments, | |
| auto &&argumentTuple) | |
| { | |
| arguments[I] = | |
| DebugFormatArgumentAdaptor< | |
| std::remove_cvref_t<decltype(std::get<I>(argumentTuple))>>{} | |
| .construct(std::get<I>(argumentTuple)); | |
| if constexpr (I > 0) | |
| { | |
| map_debug_argument<I - 1>(arguments, argumentTuple); | |
| } | |
| } | |
| /** | |
| * Convert `args` into a type-erased array of `DebugFormatArgument`s in | |
| * `arguments`. | |
| */ | |
| template<typename... Args> | |
| __always_inline inline void | |
| make_debug_arguments_list(DebugFormatArgument *arguments, Args... args) | |
| { | |
| if constexpr (sizeof...(Args) > 0) | |
| { | |
| map_debug_argument<sizeof...(Args) - 1>(arguments, | |
| std::forward_as_tuple(args...)); | |
| } | |
| } | |
| /** | |
| * Library function that writes a debug message. This runs with interrupts | |
| * disabled (to avoid interleaving) and prints an array of debug messages. This | |
| * is intended to allow a single call to print multiple format strings without | |
| * requiring the format strings to be copied, so that the debugging APIs can | |
| * wrap a user-provided format string. | |
| */ | |
| __cheri_libcall void debug_log_message_write(const char *context, | |
| const char *format, | |
| DebugFormatArgument *messages, | |
| size_t messageCount); | |
| __cheri_libcall void debug_report_failure(const char *kind, | |
| const char *file, | |
| const char *function, | |
| int line, | |
| const char *fmt, | |
| DebugFormatArgument *arguments, | |
| size_t argumentCount); | |
| namespace | |
| { | |
| /** | |
| * Helper class wrapping a string for use as a template argument. This is | |
| * used to describe a context for conditional debugging that will be | |
| * prefixed to debug lines. | |
| */ | |
| template<size_t N> | |
| struct DebugContext | |
| { | |
| /** | |
| * Constructor, captures the string argument. | |
| */ | |
| constexpr DebugContext(const char (&str)[N]) | |
| { | |
| std::copy_n(str, N, value); | |
| } | |
| /** | |
| * Implicit conversion to a C string. | |
| */ | |
| constexpr operator const char *() const | |
| { | |
| return value; | |
| } | |
| /** | |
| * The captured string. Must be public for this class to meet the | |
| * requirements of a structural type for use as a template argument. | |
| */ | |
| char value[N]; | |
| }; | |
| /** | |
| * Our libc++ does not currently provide source_location, but our clang | |
| * provides the necessary builtins for one. | |
| */ | |
| struct SourceLocation | |
| { | |
| /** | |
| * Explicitly construct a source location. | |
| */ | |
| constexpr SourceLocation(int lineNumber, | |
| int columnNumber, | |
| const char *fileName, | |
| const char *functionName) | |
| : lineNumber(lineNumber), | |
| columnNumber(columnNumber), | |
| fileName(fileName), | |
| functionName(functionName) | |
| { | |
| } | |
| /** | |
| * Construct a source location for the caller. | |
| */ | |
| static constexpr SourceLocation __always_inline | |
| current(int lineNumber = __builtin_LINE(), | |
| int columnNumber = __builtin_COLUMN(), | |
| const char *fileName = __builtin_FILE(), | |
| const char *functionName = __builtin_FUNCTION()) noexcept | |
| { | |
| return {lineNumber, columnNumber, fileName, functionName}; | |
| } | |
| /// Returns the line number for this source location. | |
| [[nodiscard]] __always_inline constexpr int line() const noexcept | |
| { | |
| return lineNumber; | |
| } | |
| /// Returns the column number for this source location. | |
| [[nodiscard]] __always_inline constexpr int column() const noexcept | |
| { | |
| return columnNumber; | |
| } | |
| /// Returns the file name for this source location. | |
| [[nodiscard]] __always_inline constexpr const char * | |
| file_name() const noexcept | |
| { | |
| return fileName; | |
| } | |
| /// Returns the function name for this source location. | |
| [[nodiscard]] __always_inline constexpr const char * | |
| function_name() const noexcept | |
| { | |
| return functionName; | |
| } | |
| private: | |
| /// The line number of this source location. | |
| int lineNumber; | |
| /// The column number of this source location. | |
| int columnNumber; | |
| /// The file name of this source location. | |
| const char *fileName; | |
| /// The function name of this source location. | |
| const char *functionName; | |
| }; | |
| /** | |
| * Conditional debug class. Used to control conditional output and | |
| * assertion checking. Enables debug log messages and assertions if | |
| * `Enabled` is true. Uses `Context` to print additional detail on debug | |
| * lines. Each line is prefixed with the context string in magenta to make | |
| * it easy to see debug output from different subsystems in the same trace. | |
| * | |
| * This class is expected to be used as a type alias, something like: | |
| * | |
| * ```c++ | |
| * constexpr bool DebugFoo = DEBUG_FOO; | |
| * using Debug = ConditionalDebug<DebugFoo, "Foo">; | |
| * ``` | |
| */ | |
| template<bool Enabled, DebugContext Context> | |
| class ConditionalDebug | |
| { | |
| public: | |
| /** | |
| * Log a message. | |
| * | |
| * This function does nothing if the `Enabled` condition is false. | |
| */ | |
| template<typename... Args> | |
| static void log(const char *fmt, Args... args) | |
| { | |
| if constexpr (Enabled) | |
| { | |
| asm volatile("" ::: "memory"); | |
| DebugFormatArgument arguments[sizeof...(Args)]; | |
| make_debug_arguments_list(arguments, args...); | |
| const char *context = Context; | |
| debug_log_message_write( | |
| context, fmt, arguments, sizeof...(Args)); | |
| asm volatile("" ::: "memory"); | |
| } | |
| } | |
| /** | |
| * Helper to report failure. | |
| * | |
| * This must not take the `SourceLocation` directly because doing so | |
| * prevents the compiler from decomposing and subsequently | |
| * constant-propagating its fields in the caller, resulting in 24 bytes | |
| * of stack space consumed for every assert or invariant. | |
| */ | |
| template<typename... Args> | |
| static inline void report_failure(const char *kind, | |
| const char *file, | |
| const char *function, | |
| int line, | |
| const char *fmt, | |
| Args... args) | |
| { | |
| // Ensure that the compiler does not reorder messages. | |
| DebugFormatArgument arguments[sizeof...(Args)]; | |
| make_debug_arguments_list(arguments, args...); | |
| const char *context = Context; | |
| debug_report_failure( | |
| kind, file, function, line, fmt, arguments, sizeof...(Args)); | |
| } | |
| /** | |
| * Function-like class for invariants that is expected to be used via | |
| * the deduction guide as: | |
| * | |
| * ConditionalDebug::Invariant(someCondition, "A message...", ...); | |
| * | |
| * Invariants are checked unconditionally but will log a verbose | |
| * message only if `Enabled` is true. | |
| */ | |
| template<typename... Args> | |
| struct Invariant | |
| { | |
| /** | |
| * Constructor, performs the invariant check. | |
| */ | |
| __always_inline | |
| Invariant(bool condition, | |
| const char *fmt, | |
| Args... args, | |
| SourceLocation loc = SourceLocation::current()) | |
| { | |
| if (__predict_false(!condition)) | |
| { | |
| if constexpr (Enabled) | |
| { | |
| report_failure("Invariant", | |
| loc.file_name(), | |
| loc.function_name(), | |
| loc.line(), | |
| fmt, | |
| std::forward<Args>(args)...); | |
| } | |
| __builtin_trap(); | |
| } | |
| } | |
| }; | |
| /** | |
| * Function-like class for assertions that is expected to be used via | |
| * the deduction guide as: | |
| * | |
| * ConditionalDebug::Assert(someCondition, "A message...", ...); | |
| * | |
| * Assertions are checked only if `Enabled` is true. | |
| */ | |
| template<typename... Args> | |
| struct Assert | |
| { | |
| /** | |
| * Constructor, performs the assertion check. | |
| */ | |
| template<typename T> | |
| requires DebugConcepts::IsBool<T> __always_inline | |
| Assert(T condition, | |
| const char *fmt, | |
| Args... args, | |
| SourceLocation loc = SourceLocation::current()) | |
| { | |
| if constexpr (Enabled) | |
| { | |
| if (__predict_false(!condition)) | |
| { | |
| report_failure("Assertion", | |
| loc.file_name(), | |
| loc.function_name(), | |
| loc.line(), | |
| fmt, | |
| std::forward<Args>(args)...); | |
| __builtin_trap(); | |
| } | |
| } | |
| } | |
| /** | |
| * Constructor, performs an assertion check. | |
| * | |
| * This version is passed a lambda that returns a bool, rather than | |
| * a boolean condition. This allows the compiler to completely | |
| * elide the contents of the lambda in builds where this component | |
| * is not being debugged. This version should be used for places | |
| * where the assertion condition has side effects. | |
| */ | |
| template<typename T> | |
| requires DebugConcepts::LazyAssertion<T> __always_inline | |
| Assert(T &&condition, | |
| const char *fmt, | |
| Args... args, | |
| SourceLocation loc = SourceLocation::current()) | |
| { | |
| if constexpr (Enabled) | |
| { | |
| if (__predict_false(!condition())) | |
| { | |
| report_failure("Assertion", | |
| loc.file_name(), | |
| loc.function_name(), | |
| loc.line(), | |
| fmt, | |
| std::forward<Args>(args)...); | |
| __builtin_trap(); | |
| } | |
| } | |
| } | |
| }; | |
| /** | |
| * Deduction guide, allows `Invariant` to be used as if it were a | |
| * function. | |
| */ | |
| template<typename T, typename... Ts> | |
| Invariant(T, const char *, Ts &&...) -> Invariant<Ts...>; | |
| /** | |
| * Deduction guide, allows `Assert` to be used as if it were a | |
| * function. | |
| */ | |
| template<typename... Ts> | |
| Assert(bool, const char *, Ts &&...) -> Assert<Ts...>; | |
| /** | |
| * Deduction guide, allows `Assert` to be used as if it were a | |
| * function with a lambda argument. | |
| */ | |
| template<typename... Ts> | |
| Assert(auto, const char *, Ts &&...) -> Assert<Ts...>; | |
| }; | |
| enum class StackCheckMode | |
| { | |
| Disabled, | |
| Logging, | |
| Asserting, | |
| }; | |
| /** | |
| * Check the (dynamic) stack usage of a function, including all of its | |
| * callees. This is intended to be used in compartment entry points to | |
| * check the stack size that is required for that entry point. Use this | |
| * with some test vectors that explore different code paths to identify the | |
| * maximum stack usage. This can then be used with the | |
| * [[cheriot::minimum_stack]] attribute to ensure that the switcher will | |
| * never invoke the entry point with insufficient stack. | |
| * | |
| * Note that this records only the stack usage for the current compartment | |
| * invocation (including any invoked shared libraries). If this | |
| * compartment calls others, then a larger stack may be required. This | |
| * failure is recoverable: cross-compartment calls should always be assumed | |
| * to potentially fail. This check is to ensure that an attacker cannot | |
| * cause stack overflow to crash the compartment, not to ensure that an | |
| * attacker cannot cause stack overflow to make the operation that they | |
| * requested fail. | |
| * | |
| * Stack checks run in one of three modes: | |
| * | |
| * - Disabled: The checks do not run. | |
| * - Logging: The checks run and print a message if the stack usage | |
| * exceeds expectations. | |
| * - Asserting: After printing the message, the function will trap. | |
| * | |
| * In logging mode, one message will be printed for each invocation of the | |
| * function that exceeds the previous maximum stack usage. | |
| * | |
| * An instance of this should be created as a local variable on entry to a | |
| * function. The destructor (which prints the message) will then run after | |
| * any other destructors, ensuring the most accurate stack usage | |
| * measurement. | |
| * | |
| * Unfortunately, default arguments for templates are not evaluated in the | |
| * enclosing scope and so `__builtin_FUNCTION()` cannot be used here. | |
| * Instead, we must pass this explicitly, typically as something like: | |
| * | |
| * ```c++ | |
| * StackUsageCheck<DebugFlag, 128, __PRETTY_FUNCTION__> stackCheck; | |
| * ``` | |
| */ | |
| template<StackCheckMode Mode, size_t Expected, DebugContext Fn> | |
| class StackUsageCheck | |
| { | |
| /** | |
| * The expected maximum. This class is templated on the function name | |
| * and so there is one copy of this per function. | |
| */ | |
| static inline size_t stackUsage = Expected; | |
| public: | |
| /** | |
| * Default constructor, does nothing. | |
| */ | |
| StackUsageCheck() = default; | |
| /** | |
| * Destructor, runs at the end of the function to print the message. | |
| */ | |
| __always_inline ~StackUsageCheck() | |
| { | |
| if constexpr (Mode != StackCheckMode::Disabled) | |
| { | |
| ptraddr_t lowest = stack_lowest_used_address(); | |
| ptraddr_t highest = | |
| CHERI::Capability{__builtin_cheri_stack_get()}.top(); | |
| size_t used = highest - lowest; | |
| if (used > stackUsage) | |
| { | |
| stackUsage = used; | |
| ConditionalDebug<true, Fn>::log("Stack used: {} bytes", | |
| stackUsage); | |
| if constexpr (Mode == StackCheckMode::Asserting) | |
| { | |
| __builtin_trap(); | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| } // namespace |