| // Copyright Microsoft and CHERIoT Contributors. | |
| // SPDX-License-Identifier: MIT | |
| #pragma once | |
| #include "cdefs.h" | |
| #include <cheri.hh> | |
| #include <compartment.h> | |
| #include <concepts> | |
| #include <cstddef> | |
| #include <platform-uart.hh> | |
| #include <string.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 &); | |
| struct DebugFormatArgument | |
| { | |
| /** | |
| * The kind of value, for values that have special-cased handling. | |
| */ | |
| enum Kind : ptraddr_t | |
| { | |
| /// Boolean, printed as "true" or "false". | |
| Bool, | |
| /// Single character. | |
| Character, | |
| /// Signed 32-bit integer, printed as decimal. | |
| SignedNumber32, | |
| /// Unsigned 32-bit integer, printed as hexadecimal | |
| UnsignedNumber32, | |
| /// Signed 64-bit integer, printed as decimal. | |
| SignedNumber64, | |
| /// Unsigned 64-bit integer, printed as hexadecimal. | |
| UnsignedNumber64, | |
| /// Pointer, printed as a full capability. | |
| Pointer, | |
| /// Special case for permission sets, printed as in the capability | |
| /// format. | |
| PermissionSet, | |
| /// C string, printed as-is. | |
| CString, | |
| /// String view, printed as-is. | |
| StringView, | |
| }; | |
| /** | |
| * The value that is being written. | |
| */ | |
| uintptr_t value; | |
| /** | |
| * The kind of value that is being written. This is either a pointer | |
| * to a `DebugCallback` or one of the `Kind` enumeration, depending on | |
| * whether the tag is set or not. | |
| */ | |
| uintptr_t kind; | |
| }; | |
| /** | |
| * 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), DebugFormatArgument::Bool}; | |
| } | |
| }; | |
| /** | |
| * Character specialisation, prints the character. | |
| */ | |
| template<> | |
| struct DebugFormatArgumentAdaptor<char> | |
| { | |
| __always_inline static DebugFormatArgument construct(char value) | |
| { | |
| return {static_cast<uintptr_t>(value), DebugFormatArgument::Character}; | |
| } | |
| }; | |
| /** | |
| * 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), | |
| DebugFormatArgument::UnsignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * 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), | |
| DebugFormatArgument::UnsignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * 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), | |
| DebugFormatArgument::UnsignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * 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, DebugFormatArgument::UnsignedNumber64}; | |
| } | |
| }; | |
| /** | |
| * 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), | |
| DebugFormatArgument::SignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * 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), | |
| DebugFormatArgument::SignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * 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), | |
| DebugFormatArgument::SignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * 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, DebugFormatArgument::SignedNumber64}; | |
| } | |
| }; | |
| /** | |
| * 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), | |
| DebugFormatArgument::CString}; | |
| } | |
| }; | |
| /** | |
| * 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), | |
| DebugFormatArgument::StringView}; | |
| } | |
| }; | |
| /** | |
| * 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), | |
| DebugFormatArgument::UnsignedNumber32}; | |
| #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()), | |
| DebugFormatArgument::PermissionSet}; | |
| } | |
| }; | |
| /** | |
| * 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)), | |
| DebugFormatArgument::Pointer}; | |
| } | |
| }; | |
| /** | |
| * 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)), | |
| DebugFormatArgument::Pointer}; | |
| } | |
| }; | |
| /** | |
| * 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), | |
| DebugFormatArgument::UnsignedNumber32}; | |
| } | |
| }; | |
| /** | |
| * 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...>; | |
| }; | |
| } // namespace |