blob: a89ea7f9af103103d3eba43a28595802ab623a0b [file] [log] [blame]
// 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