| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/developer/debug/zxdb/console/format_exception.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <algorithm> |
| |
| #include "src/developer/debug/zxdb/client/frame.h" |
| #include "src/developer/debug/zxdb/client/process.h" |
| #include "src/developer/debug/zxdb/client/session.h" |
| #include "src/developer/debug/zxdb/client/stack.h" |
| #include "src/developer/debug/zxdb/client/system.h" |
| #include "src/developer/debug/zxdb/client/thread.h" |
| #include "src/developer/debug/zxdb/common/string_util.h" |
| #include "src/developer/debug/zxdb/console/console_context.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| std::string X64PageFaultToString(const debug_ipc::ExceptionRecord& record) { |
| // Bits in the error code for a page fault. |
| constexpr uint32_t kPresentBit = 1 << 0; |
| constexpr uint32_t kWriteBit = 1 << 1; |
| // constexpr uint32_t kUserBit = 1 << 2; // Currently unneeded. |
| // constexpr uint32_t kReservedWrite = 1 << 3; // Currently unneeded. |
| constexpr uint32_t kInstructionFetchBit = 1 << 4; |
| |
| std::string result = "Page fault "; |
| |
| // Decode read/write/execute. |
| if (record.arch.x64.err_code & kInstructionFetchBit) |
| result += "executing "; |
| else if (record.arch.x64.err_code & kWriteBit) |
| result += "writing "; |
| else |
| result += "reading "; |
| |
| result += "address "; |
| result += to_hex_string(record.arch.x64.cr2); |
| |
| // The page table can mark pages as explicitly protected. Otherwise the page isn't in the page |
| // table. |
| if (record.arch.x64.err_code & kPresentBit) |
| result += " (page protection violation)"; |
| |
| return result; |
| } |
| |
| std::string X64ExceptionRecordToString(const debug_ipc::ExceptionRecord& record) { |
| switch (record.arch.x64.vector) { |
| // clang-format off |
| case 0: return "Divide-by-zero exception"; |
| case 1: return "Debug exception"; |
| case 2: return "Non-maskable interrupt"; |
| case 3: return "Breakpoint exception"; |
| case 4: return "Overflow exception"; |
| case 5: return "Bound range exceeded exception"; |
| case 6: return "Invalid opcode exception"; |
| case 7: return "No math coprocessor present exception"; |
| case 8: return "Double fault"; |
| case 9: return "CoProcessor segment overrun exception"; |
| case 10: return "Invalid TSS exception"; |
| case 11: return "Segment not present exception"; |
| case 12: return "Stack segment fault"; |
| case 13: return "General protection fault"; |
| case 14: return X64PageFaultToString(record); |
| case 15: return "Reserved exception"; |
| case 16: return "Floating-point exception"; |
| case 17: return "Alignment check exception"; |
| case 18: return "Machine check exception"; |
| case 19: return "SIMD floating-point exception"; |
| case 20: return "Virtualization exception"; |
| case 21: return "Control protection exception"; |
| // clang-format on |
| default: |
| return "Unknown exception (" + std::to_string(record.arch.x64.vector) + ")"; |
| } |
| } |
| |
| std::string Arm64DataAbortToString(const debug_ipc::ExceptionRecord& record) { |
| constexpr uint32_t kWriteNotReadBit = 1 << 6; |
| |
| // Toplevel description. |
| std::string result = "Data fault "; |
| if (record.arch.arm64.esr & kWriteNotReadBit) |
| result += "writing "; |
| else |
| result += "reading "; |
| |
| result += "address "; |
| result += to_hex_string(record.arch.arm64.far); |
| |
| // The data fault status code is the low 6 bits of the ESR. |
| // Many of these we'll never see but it's easier to make the table complete. |
| uint32_t dfsc = record.arch.arm64.esr & 0b111111; |
| const char* status = nullptr; |
| switch (dfsc) { |
| // clang-format off |
| case 0b000000: status = "address size fault level 0"; break; |
| case 0b000001: status = "address size fault level 1"; break; |
| case 0b000010: status = "address size fault level 2"; break; |
| case 0b000011: status = "address size fault level 3"; break; |
| case 0b000100: status = "translation fault level 0"; break; |
| case 0b000101: status = "translation fault level 1"; break; |
| case 0b000110: status = "translation fault level 2"; break; |
| case 0b000111: status = "translation fault level 3"; break; |
| case 0b001001: status = "access fault level 1"; break; |
| case 0b001010: status = "access fault level 2"; break; |
| case 0b001011: status = "access fault level 3"; break; |
| case 0b001101: status = "permission fault level 1"; break; |
| case 0b001110: status = "permission fault level 2"; break; |
| case 0b001111: status = "permission fault level 3"; break; |
| case 0b010000: status = "external, not on translation table walk"; break; |
| case 0b010001: status = "synchronous tag check fail"; break; |
| case 0b010100: status = "external, on translation table walk level 0"; break; |
| case 0b010101: status = "external, on translation table walk level 1"; break; |
| case 0b010110: status = "external, on translation table walk level 2"; break; |
| case 0b010111: status = "external, on translation table walk level 3"; break; |
| case 0b011000: status = "parity/ECC error not on translation table walk"; break; |
| case 0b011100: status = "parity/ECC error on translation table walk level 0"; break; |
| case 0b011101: status = "parity/ECC error on translation table walk level 1"; break; |
| case 0b011110: status = "parity/ECC error on translation table walk level 2"; break; |
| case 0b011111: status = "parity/ECC error on translation table walk level 3"; break; |
| case 0b100001: status = "alignment fault"; break; |
| case 0b110000: status = "TLB conflict"; break; |
| case 0b110001: status = "unsupported atomic hardware updated"; break; |
| case 0b110100: status = "implementation defined - lockdown"; break; |
| case 0b110101: status = "implementation defined - unsupported exclusive or atomic"; break; |
| case 0b111101: status = "section domain fault"; break; |
| case 0b111110: status = "page domain fault"; break; |
| // clang-format on |
| } |
| if (status) { |
| result += " ("; |
| result += status; |
| result += ")"; |
| } |
| |
| return result; |
| } |
| |
| std::string Arm64ExceptionRecordToString(const debug_ipc::ExceptionRecord& record) { |
| // The exception class is bits 26-31 in the esr register. |
| uint32_t ec = (record.arch.arm64.esr >> 26) & 0b111111; |
| |
| // This is the list from: |
| // https://developer.arm.com/docs/ddi0595/e/aarch64-system-registers/esr_el1 |
| // Many of these we will never encounter at the user level but it's safer to be exhaustive. |
| switch (ec) { |
| // clang-format off |
| case 0b000000: return "Unknown exception"; |
| case 0b000001: return "Trapped WFI or WFE execution"; |
| case 0b000011: return "Wrapped MCR or MRC access"; |
| case 0b000100: return "Trapped MCRR or MRRC"; |
| case 0b000101: return "Trapped MCR or MRC access"; |
| case 0b000110: return "Trapped LDC or STC access"; |
| case 0b000111: return "SVE/SIMD/FP exception"; |
| case 0b001100: return "Trapped MRRC exception"; |
| case 0b001101: return "Branch target exception"; |
| case 0b001110: return "Illegal execution state exception"; |
| case 0b010001: // fall through |
| case 0b010101: return "SVC instruction execution"; |
| case 0b011000: return "Wrapped MSR, MRS, or system instruction exception"; |
| case 0b011001: return "Access to SVE exception"; |
| case 0b011100: return "Pointer authentication failure exception"; |
| case 0b100000: // fall through |
| case 0b100001: return "Instruction abort (MMU fault)"; |
| case 0b100010: return "PC alignment fault exception"; |
| case 0b100100: // fall through |
| case 0b100101: return Arm64DataAbortToString(record); |
| case 0b100110: return "SP alignment fault exception"; |
| case 0b101000: // fall through |
| case 0b101100: return "Wrapped floating-point exception"; |
| case 0b101111: return "SError interrupt"; |
| case 0b110000: // fall through |
| case 0b110001: return "Breakpoint exception"; |
| case 0b110010: // fall through |
| case 0b110011: return "Software step exception"; |
| case 0b110100: // fall through |
| case 0b110101: return "Watchpoint exception"; |
| case 0b111000: return "BKPT instruction"; |
| case 0b111100: return "BRK instruction"; |
| // clang-format on |
| } |
| return std::string(); |
| } |
| |
| } // namespace |
| |
| OutputBuffer FormatException(const ConsoleContext* context, const Thread* thread, |
| const debug_ipc::ExceptionRecord& record) { |
| std::string heading = ExceptionRecordToString(thread->session()->arch(), record); |
| |
| // Lines on each side of the exception string. Max out at 80 cols in the case of long strings. |
| // Leave two extra to indent the string a bit. |
| size_t divider_length = std::min<size_t>(heading.size() + 2, 80); |
| std::string divider; |
| for (size_t i = 0; i < divider_length; i++) |
| divider += "═"; // UTF-8 character, can't use the string repeated constructor. |
| |
| OutputBuffer out; |
| out.Append(Syntax::kError, divider); |
| out.Append("\n "); // Extra space to indent heading inside dividers. |
| out.Append(Syntax::kHeading, heading); |
| out.Append("\n"); |
| out.Append(Syntax::kError, divider); |
| out.Append("\n"); |
| |
| // Output process record. |
| out.Append(" Process "); |
| out.Append(Syntax::kSpecial, |
| std::to_string(context->IdForTarget(thread->GetProcess()->GetTarget()))); |
| out.Append(" ("); |
| out.Append(Syntax::kVariable, "koid"); |
| out.Append("="); |
| out.Append(std::to_string(thread->GetProcess()->GetKoid())); |
| out.Append(") "); |
| |
| // Output thread record. |
| out.Append("thread "); |
| out.Append(Syntax::kSpecial, std::to_string(context->IdForThread(thread))); |
| out.Append(" ("); |
| out.Append(Syntax::kVariable, "koid"); |
| out.Append("="); |
| out.Append(std::to_string(thread->GetKoid())); |
| out.Append(")\n"); |
| |
| // Output exception address. |
| const Stack& stack = thread->GetStack(); |
| if (!stack.empty()) { |
| out.Append(" Faulting instruction: " + to_hex_string(stack[0]->GetAddress())); |
| out.Append("\n"); |
| } |
| |
| return out; |
| } |
| |
| std::string ExceptionRecordToString(debug::Arch arch, const debug_ipc::ExceptionRecord& record) { |
| if (!record.valid) |
| return "No exception information"; |
| |
| std::string suffix = |
| (record.strategy == debug_ipc::ExceptionStrategy::kSecondChance) ? " (second chance)" : ""; |
| switch (arch) { |
| case debug::Arch::kUnknown: |
| return "Unknown architecture"; |
| case debug::Arch::kX64: |
| return X64ExceptionRecordToString(record) + suffix; |
| case debug::Arch::kArm64: |
| return Arm64ExceptionRecordToString(record) + suffix; |
| } |
| FX_NOTREACHED(); |
| return std::string(); |
| } |
| |
| } // namespace zxdb |