Fixup non-canonical fault addresses for amd64.

This uses DisassemblerObjdump to add a processing step in
MinidumpProcessor to compute the true faulting address from register
state and disassembly of the fault instruction when the fault address
is suspicious (-1).

Bug: 901847
Change-Id: Ia1f77d542c4055c82ce2504db8c84a9e52001866
Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/3932957
Reviewed-by: Ivan Penkov <ivanpe@chromium.org>
This commit is contained in:
Mark Brand 2022-10-07 10:44:20 +02:00 committed by Ivan Penkov
parent 6289830b67
commit 57d1743662
4 changed files with 110 additions and 4 deletions

View file

@ -31,6 +31,7 @@
#include <assert.h>
#include <algorithm>
#include <limits>
#include <map>
#include <string>
#include <utility>
@ -43,6 +44,7 @@
#include "google_breakpad/processor/process_state.h"
#include "google_breakpad/processor/exploitability.h"
#include "google_breakpad/processor/stack_frame_symbolizer.h"
#include "processor/disassembler_objdump.h"
#include "processor/logging.h"
#include "processor/stackwalker_x86.h"
#include "processor/symbolic_constants_win.h"
@ -117,7 +119,7 @@ ProcessResult MinidumpProcessor::Process(
has_requesting_thread = exception->GetThreadID(&requesting_thread_id);
process_state->crash_reason_ = GetCrashReason(
dump, &process_state->crash_address_);
dump, &process_state->crash_address_, enable_objdump_);
process_state->exception_record_.set_code(
exception->exception()->exception_record.exception_code,
@ -758,8 +760,82 @@ bool MinidumpProcessor::GetProcessCreateTime(Minidump* dump,
return true;
}
static bool IsCanonicalAddress(uint64_t address) {
uint64_t sign_bit = (address >> 63) & 1;
for (int shift = 48; shift < 63; ++shift) {
if (sign_bit != ((address >> shift) & 1)) {
return false;
}
}
return true;
}
static void CalculateFaultAddressFromInstruction(Minidump* dump,
uint64_t* address) {
MinidumpException* exception = dump->GetException();
if (exception == NULL) {
BPLOG(INFO) << "Failed to get exception.";
return;
}
MinidumpContext* context = exception->GetContext();
if (context == NULL) {
BPLOG(INFO) << "Failed to get exception context.";
return;
}
uint64_t instruction_ptr = 0;
if (!context->GetInstructionPointer(&instruction_ptr)) {
BPLOG(INFO) << "Failed to get instruction pointer.";
return;
}
// Get memory region containing instruction pointer.
MinidumpMemoryList* memory_list = dump->GetMemoryList();
MinidumpMemoryRegion* memory_region =
memory_list ?
memory_list->GetMemoryRegionForAddress(instruction_ptr) : NULL;
if (!memory_region) {
BPLOG(INFO) << "No memory region around instruction pointer.";
return;
}
DisassemblerObjdump disassembler(context->GetContextCPU(), memory_region,
instruction_ptr);
fprintf(stderr, "%s %s %s\n", disassembler.operation().c_str(),
disassembler.src().c_str(), disassembler.dest().c_str());
if (!disassembler.IsValid()) {
BPLOG(INFO) << "Disassembling fault instruction failed.";
return;
}
// It's possible that we reach here when the faulting address is already
// correct, so we only update it if we find that at least one of the src/dest
// addresses is non-canonical. If both are non-canonical, we arbitrarily set
// it to the larger of the two, as this is more likely to be a known poison
// value.
bool valid_read, valid_write;
uint64_t read_address, write_address;
valid_read = disassembler.CalculateSrcAddress(*context, read_address);
valid_read &= !IsCanonicalAddress(read_address);
valid_write = disassembler.CalculateDestAddress(*context, write_address);
valid_write &= !IsCanonicalAddress(write_address);
if (valid_read && valid_write) {
*address = read_address > write_address ? read_address : write_address;
} else if (valid_read) {
*address = read_address;
} else if (valid_write) {
*address = write_address;
}
}
// static
string MinidumpProcessor::GetCrashReason(Minidump* dump, uint64_t* address) {
string MinidumpProcessor::GetCrashReason(Minidump* dump, uint64_t* address,
bool enable_objdump) {
MinidumpException* exception = dump->GetException();
if (!exception)
return "";
@ -1985,6 +2061,15 @@ string MinidumpProcessor::GetCrashReason(Minidump* dump, uint64_t* address) {
*address = GetAddressForArchitecture(
static_cast<MDCPUArchitecture>(raw_system_info->processor_architecture),
*address);
// For invalid accesses to non-canonical addresses, amd64 cpus don't provide
// the fault address, so recover it from the disassembly and register state
// if possible.
if (enable_objdump
&& raw_system_info->processor_architecture == MD_CPU_ARCHITECTURE_AMD64
&& std::numeric_limits<uint64_t>::max() == *address) {
CalculateFaultAddressFromInstruction(dump, address);
}
}
return reason;