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