From 9d12aa5cb1defcf10a77fb3db26c8a4f6fdd2554 Mon Sep 17 00:00:00 2001 From: souldbminersmwc Date: Sat, 25 Jul 2026 12:39:31 -0400 Subject: [PATCH] add uart logging feature --- .../loader/source/ldr_process_creation.cpp | 10 + .../loader/source/oc/oc_common.hpp | 13 +- .../stratosphere/loader/source/oc/oc_log.cpp | 23 +++ .../stratosphere/loader/source/oc/oc_log.hpp | 4 + .../loader/source/oc/pcv/pcv_asm.hpp | 76 ++++++++ .../loader/source/oc/pcv/pcv_common.hpp | 3 + .../loader/source/oc/pcv/pcv_mariko.cpp | 184 ++++++++++++++++++ .../hoc-clk/sysmodule/src/file/file_utils.cpp | 23 ++- 8 files changed, 330 insertions(+), 6 deletions(-) diff --git a/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp b/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp index 3ec6ac0d..cf940e3b 100644 --- a/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp @@ -730,6 +730,16 @@ namespace ams::ldr { /* Apply PCV and PTM patches */ if (g_is_pcv) { + /* Hand the pcv patcher the zero-padding after .text (memset to 0 above, mapped + ReadExecute below) as a code cave for out-of-line trampolines. text_dst_offset + is validated == 0. Clamp to the page-aligned .text mapping so the cave can only + live inside the RX region. */ + const size_t pcv_text_end = static_cast(nso_header->text_size); + const size_t pcv_rx_end = util::AlignUp(pcv_text_end, os::MemoryPageSize); + const size_t pcv_ro_start = static_cast(nso_header->ro_dst_offset); + const size_t pcv_cave_end = (pcv_rx_end < pcv_ro_start) ? pcv_rx_end : pcv_ro_start; + hoc::pcv::g_pcv_cave = map_address + pcv_text_end; + hoc::pcv::g_pcv_cave_size = (pcv_cave_end > pcv_text_end) ? (pcv_cave_end - pcv_text_end) : 0; hoc::pcv::Patch(map_address, nso_size); } diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp index 8254785d..8ba07d57 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp @@ -20,12 +20,21 @@ #include #include -#define LOGGING(fmt, ...) ((void)0) -#define CRASH(msg, ...) { ams::diag::AbortImpl(msg, __PRETTY_FUNCTION__, "", 0); __builtin_unreachable(); } + +#define HOC_UART_LOG 1 +#define HOC_PCV_NVLOG_PATCH 1 +#define HOC_PCV_FORCE_VERBOSITY 1 #include "customize.hpp" #include "oc_log.hpp" +#if HOC_UART_LOG +#define LOGGING(fmt, ...) ::ams::ldr::hoc::UartLog(fmt, ##__VA_ARGS__) +#else +#define LOGGING(fmt, ...) ((void)0) +#endif +#define CRASH(msg, ...) { ams::diag::AbortImpl(msg, __PRETTY_FUNCTION__, "", 0); __builtin_unreachable(); } + #define PATCH_OFFSET(offset, value) \ static_assert(sizeof(__typeof__(offset)) <= sizeof(u64)); \ *(offset) = value; diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.cpp index 660b1f50..27027e9a 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.cpp @@ -70,6 +70,29 @@ namespace ams::ldr::hoc { return rc; } + void UartLog(const char *fmt, ...) { + char line[256]; + constexpr size_t PrefixLen = 13; /* "[HOC] " */ + std::memcpy(line, "[Horizon OC] ", PrefixLen); + + va_list args; + va_start(args, fmt); + int n = vsnprintf(line + PrefixLen, sizeof(line) - PrefixLen - 1, fmt, args); + va_end(args); + if (n < 0) { + return; + } + + size_t body = static_cast(n); + if (body > sizeof(line) - PrefixLen - 2) { /* vsnprintf returns the untruncated length */ + body = sizeof(line) - PrefixLen - 2; + } + size_t len = PrefixLen + body; + line[len++] = '\n'; + + svc::OutputDebugString(line, len); + } + struct log_ctx_t { u32 magic; u32 sz; diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.hpp index 8f5dadb2..6522ee13 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.hpp @@ -25,4 +25,8 @@ namespace ams::ldr::hoc { void Log(const char *data, ...); void ViewLog(); + /* Emit a formatted line over the kernel debug UART via svcOutputDebugString. + No-op unless the running kernel enables debug logging (audit/debug mesosphere). */ + void UartLog(const char *fmt, ...) __attribute__((format(printf, 1, 2))); + } diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp index b297a21d..9d9a909e 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp @@ -159,6 +159,82 @@ namespace ams::ldr::hoc::pcv { return 0xF9400000u | (((byteOff / 8u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu); }; + /* b (PC-relative, +-128MB). */ + inline auto AsmMakeB = [](uintptr_t pc, uintptr_t target) -> u32 { + const s64 off = (static_cast(target) - static_cast(pc)) >> 2; + return 0x14000000u | (static_cast(off) & 0x03FFFFFFu); + }; + + /* b. (cond: LO/CC=0x3, LE=0xD, NE=0x1, ...). */ + inline auto AsmMakeBCond = [](uintptr_t pc, uintptr_t target, u32 cond) -> u32 { + const s64 off = (static_cast(target) - static_cast(pc)) >> 2; + return 0x54000000u | ((static_cast(off) & 0x7FFFFu) << 5) | (cond & 0xFu); + }; + + /* sub Xd,Xn,#imm12 (shift 0). */ + inline auto AsmMakeSubImm64 = [](u32 rd, u32 rn, u32 imm12) -> u32 { + return 0xD1000000u | ((imm12 & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rd & 0x1Fu); + }; + + /* cmp Wn,#imm12 == subs WZR,Wn,#imm12. */ + inline auto AsmMakeCmpImm32 = [](u32 rn, u32 imm12) -> u32 { + return 0x7100001Fu | ((imm12 & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5); + }; + + /* str Wt,[Xn,#byteOff] (32-bit, unsigned scaled by 4). */ + inline auto AsmMakeStrImm32 = [](u32 rt, u32 rn, u32 byteOff) -> u32 { + return 0xB9000000u | (((byteOff / 4u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu); + }; + + /* stp Xt1,Xt2,[Xn,#imm] (signed offset, scaled by 8). */ + inline auto AsmMakeStpImm64 = [](u32 rt1, u32 rt2, u32 rn, s32 imm) -> u32 { + return 0xA9000000u | ((static_cast(imm / 8) & 0x7Fu) << 15) | ((rt2 & 0x1Fu) << 10) | ((rn & 0x1Fu) << 5) | (rt1 & 0x1Fu); + }; + + /* stp Qt1,Qt2,[Xn,#imm] (128-bit SIMD, signed offset scaled by 16). */ + inline auto AsmMakeStpqImm = [](u32 qt1, u32 qt2, u32 rn, s32 imm) -> u32 { + return 0xAD000000u | ((static_cast(imm / 16) & 0x7Fu) << 15) | ((qt2 & 0x1Fu) << 10) | ((rn & 0x1Fu) << 5) | (qt1 & 0x1Fu); + }; + + /* str Xt,[Xn,#byteOff] (unsigned scaled by 8). */ + inline auto AsmMakeStrImm64 = [](u32 rt, u32 rn, u32 byteOff) -> u32 { + return 0xF9000000u | (((byteOff / 8u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu); + }; + + /* movn Wd,#imm16 (no shift): loads ~imm16, e.g. movn Wd,#0x37 == -56. */ + inline auto AsmMakeMovnW = [](u32 rd, u16 imm16) -> u32 { + return 0x12800000u | (static_cast(imm16) << 5) | (rd & 0x1Fu); + }; + + /* bl (PC-relative, +-128MB). */ + inline auto AsmMakeBl = [](uintptr_t pc, uintptr_t target) -> u32 { + const s64 off = (static_cast(target) - static_cast(pc)) >> 2; + return 0x94000000u | (static_cast(off) & 0x03FFFFFFu); + }; + + /* svc #imm16. */ + inline auto AsmMakeSvc = [](u16 imm16) -> u32 { + return 0xD4000001u | (static_cast(imm16) << 5); + }; + + constexpr u32 RetIns = 0xD65F03C0u; /* ret (x30) */ + + /* ldrb Wt,[Xn,#imm] (unsigned byte, scale 1). */ + inline auto AsmMakeLdrbImm = [](u32 rt, u32 rn, u32 byteOff) -> u32 { + return 0x39400000u | ((byteOff & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu); + }; + + /* cbz Wt,. */ + inline auto AsmMakeCbz = [](uintptr_t pc, uintptr_t target, u32 rt) -> u32 { + const s64 off = (static_cast(target) - static_cast(pc)) >> 2; + return 0x34000000u | ((static_cast(off) & 0x7FFFFu) << 5) | (rt & 0x1Fu); + }; + + /* strb Wt,[Xn,#imm] (unsigned byte, scale 1). */ + inline auto AsmMakeStrbImm = [](u32 rt, u32 rn, u32 byteOff) -> u32 { + return 0x39000000u | ((byteOff & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu); + }; + /* mov Xd,X == orr Xd,XZR,X (matches any Rd). */ inline auto AsmIsMovReg = [](u32 ins, u32 rm) -> bool { return (ins & 0xFFFFFFE0u) == (0xAA0003E0u | ((rm & 0x1Fu) << 16)); diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_common.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_common.hpp index 26e3aa98..1d3b9232 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_common.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_common.hpp @@ -183,6 +183,9 @@ namespace ams::ldr::hoc::pcv { // extra .bss location, 0 until Patch() runs. inline uintptr_t g_pcv_scratch = 0; + inline uintptr_t g_pcv_cave = 0; + inline size_t g_pcv_cave_size = 0; + template size_t GetDvfsTableEntryCount(T *table_head) { using NT = std::remove_const_t>; diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp index eb76f21f..338a540e 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp @@ -620,6 +620,186 @@ namespace ams::ldr::hoc::pcv::mariko { namespace { std::vector newEmcList; u32 *nsoStart; + uintptr_t g_cave_cursor = 0; + } + + static uintptr_t CaveReserve(size_t count) { + if (g_pcv_cave == 0 || g_cave_cursor == 0) { + return 0; + } + if (g_cave_cursor + count * sizeof(u32) > g_pcv_cave + g_pcv_cave_size) { + return 0; + } + const uintptr_t entry = g_cave_cursor; + g_cave_cursor += count * sizeof(u32); + return entry; + } + + static void NvLogUartRedirect(uintptr_t mapped_nso, size_t nso_size) { + const uintptr_t textEnd = g_pcv_cave; /* .text ends where the cave begins */ + if (textEnd == 0) { + return; + } + + /* vsnprintf(buf,size,fmt,va_list) */ + static const u32 VsnSig[] = { 0xD10483FFu, 0xA9107BFDu, 0xF9008BFCu, 0x910403FDu, 0xF100003Fu }; + uintptr_t vsnprintf_addr = 0; + for (u32 *p = nsoStart; reinterpret_cast(p + std::size(VsnSig)) <= textEnd; ++p) { + bool ok = true; + for (size_t k = 0; k < std::size(VsnSig); ++k) { + if (p[k] != VsnSig[k]) { ok = false; break; } + } + if (ok) { vsnprintf_addr = reinterpret_cast(p); break; } + } + if (vsnprintf_addr == 0) { + LOGGING("NvLogRedirect: vsnprintf not found"); + return; + } + + /* NvLog via the VDD_SOC log */ + static const char Fmt[] = "%s(%s): DVFS request VDD_SOC %d mV\n"; + constexpr size_t FmtLen = sizeof(Fmt) - 1; + uintptr_t strAddr = 0; + { + const char *hay = reinterpret_cast(mapped_nso); + for (size_t i = 0; i + FmtLen <= nso_size; ++i) { + if (std::memcmp(hay + i, Fmt, FmtLen) == 0) { strAddr = mapped_nso + i; break; } + } + } + if (strAddr == 0) { + LOGGING("NvLogRedirect: fmt string not found (vsnprintf@+%lx)", vsnprintf_addr - mapped_nso); + return; + } + + uintptr_t nvlog_addr = 0; + for (u32 *p = nsoStart; reinterpret_cast(p + 2) <= textEnd; ++p) { + const uintptr_t pc = reinterpret_cast(p); + if (!AsmIsAdrp(p[0])) { + continue; + } + const uintptr_t adrpPage = (pc & ~static_cast(0xFFFu)) + static_cast(AsmAdrpPageOffset(p[0])); + if (adrpPage != (strAddr & ~static_cast(0xFFFu))) { + continue; + } + const u32 reg = asm_get_rd(p[0]); + if (!(AsmIsAddImm64(p[1]) && asm_get_rd(p[1]) == reg && AsmGetRn(p[1]) == reg && AsmGetImm12(p[1]) == (strAddr & 0xFFFu))) { + continue; + } + for (u32 k = 2; k <= 12 && (pc + (k + 1) * 4) <= textEnd; ++k) { + if (AsmIsBl(p[k])) { nvlog_addr = AsmBranchTarget(p[k], pc + k * 4); break; } + } + if (nvlog_addr != 0) { + break; + } + } + if (nvlog_addr == 0 || nvlog_addr < mapped_nso || nvlog_addr >= textEnd) { + LOGGING("NvLogRedirect: NvLog entry not found (fmt@+%lx)", strAddr - mapped_nso); + return; + } + + const uintptr_t helper = CaveReserve(40); + if (helper == 0) { + LOGGING("NvLogRedirect: cave unavailable (cave=%lx size=%lx)", + static_cast(g_pcv_cave), static_cast(g_pcv_cave_size)); + return; + } + u32 *t = reinterpret_cast(helper); + size_t n = 0; + auto emit = [&](u32 ins) { t[n] = ins; ++n; }; + + emit(AsmMakeSubImm64(31, 31, 0x200)); + emit(AsmMakeStpImm64(0, 1, 31, 0x100)); + emit(AsmMakeStpImm64(2, 3, 31, 0x110)); + emit(AsmMakeStpImm64(4, 5, 31, 0x120)); + emit(AsmMakeStpImm64(6, 7, 31, 0x130)); + emit(AsmMakeStpqImm(0, 1, 31, 0x140)); + emit(AsmMakeStpqImm(2, 3, 31, 0x160)); + emit(AsmMakeStpqImm(4, 5, 31, 0x180)); + emit(AsmMakeStpqImm(6, 7, 31, 0x1A0)); + emit(AsmMakeStrImm64(30, 31, 0x1E0)); + emit(AsmMakeAddImm64(9, 31, 0x200)); emit(AsmMakeStrImm64(9, 31, 0x1C0)); /* __stack */ + emit(AsmMakeAddImm64(9, 31, 0x140)); emit(AsmMakeStrImm64(9, 31, 0x1C8)); /* __gr_top */ + emit(AsmMakeAddImm64(9, 31, 0x1C0)); emit(AsmMakeStrImm64(9, 31, 0x1D0)); /* __vr_top */ + emit(AsmMakeMovnW(9, 0x37)); emit(AsmMakeStrImm32(9, 31, 0x1D8)); /* __gr_offs = -56 */ + emit(AsmMakeMovnW(9, 0x7F)); emit(AsmMakeStrImm32(9, 31, 0x1DC)); /* __vr_offs = -128 */ + emit(AsmMakeAddImm64(0, 31, 0x00)); /* mov x0,sp (buf) */ + emit(AsmMakeMovzW(1, 0x100)); /* size = 0x100 */ + emit(AsmMakeLdrImm64(2, 31, 0x100)); /* fmt (saved x0) */ + emit(AsmMakeAddImm64(3, 31, 0x1C0)); /* ap */ + emit(AsmMakeBl(helper + n * 4, vsnprintf_addr)); + emit(AsmMakeMovReg(1, 0)); /* len = retval */ + emit(AsmMakeCmpImm32(1, 0x100)); + { const size_t at = n; emit(AsmMakeBCond(helper + at * 4, helper + (at + 2) * 4, 0x3u)); } /* b.lo +2 */ + emit(AsmMakeMovzW(1, 0xFF)); /* clamp len */ + emit(AsmMakeAddImm64(0, 31, 0x00)); /* mov x0,sp (str) */ + emit(AsmMakeSvc(0x27)); /* svcOutputDebugString */ + emit(AsmMakeLdrImm64(30, 31, 0x1E0)); + emit(AsmMakeAddImm64(31, 31, 0x200)); + emit(RetIns); + + /* Redirect the call sites as patching the actual function causes crash */ + const uintptr_t roStart = g_pcv_cave + g_pcv_cave_size; /* module .rodata start */ + size_t patchedSites = 0; + if (HOC_PCV_NVLOG_PATCH) { + for (u32 *p = nsoStart; reinterpret_cast(p + 1) <= textEnd; ++p) { + if (!AsmIsBl(*p)) { + continue; + } + const uintptr_t pc = reinterpret_cast(p); + if (AsmBranchTarget(*p, pc) != nvlog_addr) { + continue; + } + bool isFmtCall = false; + for (u32 j = 1; j <= 8 && reinterpret_cast(p - j) >= reinterpret_cast(nsoStart); ++j) { + const u32 w = *(p - j); + if (AsmIsAdrp(w) && asm_get_rd(w) == 0) { /* adrp x0, */ + const uintptr_t wpc = pc - j * 4; + const uintptr_t tgtPage = (wpc & ~static_cast(0xFFFu)) + static_cast(AsmAdrpPageOffset(w)); + if (tgtPage >= (roStart & ~static_cast(0xFFFu))) { isFmtCall = true; break; } + } + } + if (isFmtCall) { + PATCH_OFFSET(p, AsmMakeBl(pc, helper)); + ++patchedSites; + } + } + } + + LOGGING("NvLogRedirect: stub@+%lx vsnprintf@+%lx helper@+%lx instr=%zu sites=%zu", + nvlog_addr - mapped_nso, vsnprintf_addr - mapped_nso, helper - mapped_nso, n, patchedSites); + } + + /* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs */ + static void ForceVerbosity() { + const uintptr_t textEnd = g_pcv_cave; + if (textEnd == 0 || HOC_PCV_FORCE_VERBOSITY == 0) { + return; + } + size_t patched = 0; + for (u32 *p = nsoStart; reinterpret_cast(p + 11) <= textEnd; ++p) { + if (p[0] != 0xA9BE7BFDu || p[1] != 0xF9000BF3u || p[2] != 0x910003FDu) { /* stp/str/mov x29,sp */ + continue; + } + if (!(AsmIsAddImm64(p[3]) && asm_get_rd(p[3]) == 0 && AsmGetRn(p[3]) == 29)) continue; /* add x0,x29,#imm */ + if (!(AsmIsAddImm64(p[4]) && asm_get_rd(p[4]) == 19 && AsmGetRn(p[4]) == 29)) continue; /* add x19,x29,#imm */ + if (AsmGetImm12(p[3]) != AsmGetImm12(p[4]) || !AsmIsBl(p[5])) { + continue; + } + bool hasCmp = false; + for (u32 j = 6; j <= 10; ++j) { + if (p[j] == 0x7100001Fu) { /* cmp w0,#0 */ + hasCmp = true; + break; + } + } + if (!hasCmp) { + continue; + } + PATCH_OFFSET(&p[0], AsmMakeMovzW(0, static_cast(HOC_PCV_FORCE_VERBOSITY))); + PATCH_OFFSET(&p[1], RetIns); + ++patched; + } + LOGGING("ForceVerbosity: patched %zu getter(s) to level %d", patched, HOC_PCV_FORCE_VERBOSITY); } /* Widen InitDram for a >32-entry EMC DVFS list. Freq array can be dropped to free 264 bytes, relocate the Soc LUT to that space */ @@ -1293,6 +1473,10 @@ namespace ams::ldr::hoc::pcv::mariko { nsoStart = reinterpret_cast(mapped_nso); g_pcv_scratch = mapped_nso + nso_size - HocPcvScratchSize; + g_cave_cursor = g_pcv_cave; /* start the .text-cave bump allocator (0 if unavailable) */ + + NvLogUartRedirect(mapped_nso, nso_size); + ForceVerbosity(); MtcGenerateFreqTables(); diff --git a/Source/hoc-clk/sysmodule/src/file/file_utils.cpp b/Source/hoc-clk/sysmodule/src/file/file_utils.cpp index efa651a4..1de9ae5c 100644 --- a/Source/hoc-clk/sysmodule/src/file/file_utils.cpp +++ b/Source/hoc-clk/sysmodule/src/file/file_utils.cpp @@ -83,6 +83,24 @@ namespace fileUtils { va_list args; va_start(args, format); + + char buff[0xfff]; + int len = vsnprintf(buff, sizeof(buff), format, args); + va_end(args); + + if (len < 0) { + return; + } + // Leave room for the newline + NUL. + if ((size_t)len >= sizeof(buff) - 1) { + len = sizeof(buff) - 2; + } + buff[len++] = '\n'; + buff[len] = '\0'; + + // Debug UART log + svcOutputDebugString(buff, len); + if (g_has_initialized) { RefreshFlags(false); @@ -93,14 +111,11 @@ namespace fileUtils { timespec now = {}; clock_gettime(CLOCK_REALTIME, &now); - fprintf(file, "[%luls] ", now.tv_sec - bootTimeS); - vfprintf(file, format, args); - fprintf(file, "\n"); + fprintf(file, "[%luls] %s", now.tv_sec - bootTimeS, buff); fclose(file); } } } - va_end(args); } void WriteContextToCsv(const HocClkContext *context) {