| // 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 |