| // Copyright Microsoft and CHERIoT Contributors. | |
| // SPDX-License-Identifier: MIT | |
| #pragma once | |
| #include <cheri.hh> | |
| #include <compartment.h> | |
| #include <platform-uart.hh> | |
| #include <array> | |
| namespace | |
| { | |
| /** | |
| * Is the loader being debugged? | |
| */ | |
| static constexpr bool DebugLoader = DEBUG_LOADER; | |
| 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; | |
| }; | |
| } // namespace DebugConcepts | |
| /** | |
| * Helper class for writing debug output to the UART. This performs no | |
| * internal buffering and assumes interrupts are disabled for the duration | |
| * to avoid interference. | |
| * | |
| * This is based on snmalloc's class of the same name. | |
| */ | |
| class LoaderWriter | |
| { | |
| /** | |
| * The capability to the UART device. | |
| */ | |
| static inline volatile Uart *uart16550; | |
| /** | |
| * Append a character, delegating to `Output`. | |
| */ | |
| void append_char(char c) | |
| { | |
| uart16550->blocking_write(c); | |
| } | |
| /** | |
| * Append a null-terminated C string. | |
| */ | |
| void append(const char *str) | |
| { | |
| for (; *str; ++str) | |
| { | |
| append_char(*str); | |
| } | |
| } | |
| /** | |
| * Append a string view. | |
| */ | |
| void append(std::string_view str) | |
| { | |
| for (char c : str) | |
| { | |
| append_char(c); | |
| } | |
| } | |
| void append(char c) | |
| { | |
| append_char(c); | |
| } | |
| /** | |
| * Outputs the permission set using the format G RWcgml Xa SU0 as | |
| * described in [/docs/Debugging.md]. | |
| */ | |
| void append(CHERI::PermissionSet permissions) | |
| { | |
| using namespace CHERI; | |
| auto perm = [&](Permission p, char c) -> char { | |
| if (permissions.contains(p)) | |
| { | |
| return c; | |
| } | |
| return '-'; | |
| }; | |
| format("{} {}{}{}{}{}{} {}{} {}{}{}", | |
| perm(Permission::Global, 'G'), | |
| perm(Permission::Load, 'R'), | |
| perm(Permission::Store, 'W'), | |
| perm(Permission::LoadStoreCapability, 'c'), | |
| perm(Permission::LoadGlobal, 'g'), | |
| perm(Permission::LoadMutable, 'm'), | |
| perm(Permission::StoreLocal, 'l'), | |
| perm(Permission::Execute, 'X'), | |
| perm(Permission::AccessSystemRegisters, 'a'), | |
| perm(Permission::Seal, 'S'), | |
| perm(Permission::Unseal, 'U'), | |
| perm(Permission::User0, '0')); | |
| } | |
| /** | |
| * Append a raw pointer as a hex string. | |
| */ | |
| __noinline void append(const void *ptr) | |
| { | |
| const CHERI::Capability C{ptr}; | |
| format("{} (v:{} {}-{} l:{} o:{} p: {})", | |
| C.address(), | |
| C.is_valid(), | |
| C.base(), | |
| C.top(), | |
| C.length(), | |
| C.type(), | |
| C.permissions()); | |
| } | |
| /** | |
| * Append a capability. | |
| */ | |
| template<typename T> | |
| __always_inline void append(CHERI::Capability<T> capability) | |
| { | |
| append(static_cast<const void *>(capability.get())); | |
| } | |
| /** | |
| * Append a signed integer, as a decimal string. | |
| */ | |
| __noinline void append(int32_t s) | |
| { | |
| if (s < 0) | |
| { | |
| append_char('-'); | |
| s = 0 - s; | |
| } | |
| std::array<char, 10> buf; | |
| const char Digits[] = "0123456789"; | |
| for (int i = int(buf.size() - 1); i >= 0; i--) | |
| { | |
| buf.at(static_cast<size_t>(i)) = Digits[s % 10]; | |
| s /= 10; | |
| } | |
| bool skipZero = true; | |
| for (auto c : buf) | |
| { | |
| if (skipZero && (c == '0')) | |
| { | |
| continue; | |
| } | |
| skipZero = false; | |
| append_char(c); | |
| } | |
| if (skipZero) | |
| { | |
| append_char('0'); | |
| } | |
| } | |
| /** | |
| * Append a 32-bit unsigned integer to the buffer as hex with no prefix. | |
| */ | |
| __attribute__((noinline)) void append_hex_word(uint32_t s) | |
| { | |
| std::array<char, 8> buf; | |
| const char Hexdigits[] = "0123456789abcdef"; | |
| // Length of string including null terminator | |
| static_assert(sizeof(Hexdigits) == 0x11); | |
| for (long i = long(buf.size() - 1); i >= 0; i--) | |
| { | |
| buf.at(static_cast<size_t>(i)) = Hexdigits[s & 0xf]; | |
| s >>= 4; | |
| } | |
| bool skipZero = true; | |
| for (auto c : buf) | |
| { | |
| if (skipZero && (c == '0')) | |
| { | |
| continue; | |
| } | |
| skipZero = false; | |
| append_char(c); | |
| } | |
| if (skipZero) | |
| { | |
| append_char('0'); | |
| } | |
| } | |
| /** | |
| * Append a 32-bit unsigned integer to the buffer as hex. | |
| */ | |
| __attribute__((noinline)) void append(uint32_t s) | |
| { | |
| append_char('0'); | |
| append_char('x'); | |
| append_hex_word(s); | |
| } | |
| /** | |
| * Append a 64-bit unsigned integer to the buffer as hex. | |
| */ | |
| __attribute__((noinline)) void append(uint64_t s) | |
| { | |
| append_char('0'); | |
| append_char('x'); | |
| uint32_t hi = static_cast<uint32_t>(s >> 32); | |
| uint32_t lo = static_cast<uint32_t>(s); | |
| if (hi != 0) | |
| { | |
| append_hex_word(hi); | |
| } | |
| append_hex_word(lo); | |
| } | |
| /** | |
| * Append a 16-bit unsigned integer to the buffer as hex. | |
| */ | |
| __always_inline void append(uint16_t s) | |
| { | |
| append(static_cast<uint32_t>(s)); | |
| } | |
| /** | |
| * Append an 8-bit unsigned integer to the buffer as hex. | |
| */ | |
| __always_inline void append(uint8_t s) | |
| { | |
| append(static_cast<uint32_t>(s)); | |
| } | |
| /** | |
| * Append an enumerated type value. | |
| */ | |
| template<typename T> | |
| requires DebugConcepts::IsEnum<T> | |
| void append(T e) | |
| { | |
| // `magic_enum::enum_name` requires cap relocs, so don't use it in | |
| // components that want or need to avoid them. | |
| append(static_cast<int32_t>(e)); | |
| } | |
| public: | |
| /** | |
| * Base case for formatting: no format-string arguments. | |
| */ | |
| void format(const char *fmt) | |
| { | |
| append(fmt); | |
| } | |
| /** | |
| * Inductive case for formatting. Writes up to the instruction to | |
| * output the first argument, writes that argument, and then recurses. | |
| */ | |
| template<typename T, typename... Args> | |
| void format(const char *fmt, T firstArg, Args... args) | |
| { | |
| for (const char *s = fmt; *s != 0; ++s) | |
| { | |
| if (s[0] == '{' && s[1] == '}') | |
| { | |
| append(firstArg); | |
| return format(s + 2, args...); | |
| } | |
| append_char(*s); | |
| } | |
| } | |
| /** | |
| * Set the UART memory pointer. | |
| */ | |
| static void set_uart(volatile Uart *theUART) | |
| { | |
| uart16550 = theUART; | |
| } | |
| }; | |
| /** | |
| * 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. Writes output using `Writer`. | |
| * | |
| * If `DisableInterrupts` is true then this disables interrupts while | |
| * printing the message to avoid accidental interleaving. | |
| * | |
| * 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> | |
| class LoaderDebug | |
| { | |
| public: | |
| /// The (singleton) writer used for output. | |
| static inline LoaderWriter writer; | |
| /** | |
| * 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) | |
| { | |
| // Ensure that the compiler does not reorder messages. | |
| asm volatile("" ::: "memory"); | |
| writer.format("\x1b[35mloader\033[0m"); | |
| writer.format(": "); | |
| writer.format(fmt, args...); | |
| writer.format("\n"); | |
| 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> | |
| __noinline static 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. | |
| asm volatile("" ::: "memory"); | |
| writer.format( | |
| "\x1b[35m{}:{} \x1b[31m{} failure\x1b[35m in {}\x1b[36m\n", | |
| file, | |
| line, | |
| kind, | |
| function); | |
| writer.format(fmt, args...); | |
| writer.format("\033[0m\n"); | |
| asm volatile("" ::: "memory"); | |
| } | |
| /** | |
| * 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 (!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 (!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 (!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...>; | |
| }; | |
| /** | |
| * Debug interface for the loader. Enables verbose debugging if | |
| * `DebugLoader` is true and requires that the loader explicitly | |
| * initialises the UART. | |
| */ | |
| using Debug = LoaderDebug<DebugLoader>; | |
| } // namespace |