Merge "Support MTE and GWP-ASan features in proto tombstones."

This commit is contained in:
Peter Collingbourne 2021-03-19 23:42:23 +00:00 committed by Gerrit Code Review
commit f4a40c0edd
12 changed files with 451 additions and 194 deletions

View file

@ -502,12 +502,11 @@ TEST_P(SizeParamCrasherTest, mte_uaf) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" +
std::to_string(GetParam()) + R"(-byte allocation.*
allocated by thread .*
#00 pc)");
std::to_string(GetParam()) + R"(-byte allocation)");
ASSERT_MATCH(result, R"(deallocated by thread .*
#00 pc)");
ASSERT_MATCH(result, R"(allocated by thread .*
#00 pc)");
#else
GTEST_SKIP() << "Requires aarch64";
#endif
@ -539,9 +538,8 @@ TEST_P(SizeParamCrasherTest, mte_overflow) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
std::to_string(GetParam()) + R"(-byte allocation.*
allocated by thread .*
std::to_string(GetParam()) + R"(-byte allocation)");
ASSERT_MATCH(result, R"(allocated by thread .*
#00 pc)");
#else
GTEST_SKIP() << "Requires aarch64";
@ -574,9 +572,8 @@ TEST_P(SizeParamCrasherTest, mte_underflow) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a )" +
std::to_string(GetParam()) + R"(-byte allocation.*
allocated by thread .*
std::to_string(GetParam()) + R"(-byte allocation)");
ASSERT_MATCH(result, R"(allocated by thread .*
#00 pc)");
#else
GTEST_SKIP() << "Requires aarch64";

View file

@ -15,6 +15,7 @@
*/
#include "libdebuggerd/gwp_asan.h"
#include "libdebuggerd/tombstone.h"
#include "libdebuggerd/utility.h"
#include "gwp_asan/common.h"
@ -25,6 +26,8 @@
#include <unwindstack/Regs.h>
#include <unwindstack/Unwinder.h>
#include "tombstone.pb.h"
// Retrieve GWP-ASan state from `state_addr` inside the process at
// `process_memory`. Place the state into `*state`.
static bool retrieve_gwp_asan_state(unwindstack::Memory* process_memory, uintptr_t state_addr,
@ -98,6 +101,67 @@ bool GwpAsanCrashData::CrashIsMine() const {
return is_gwp_asan_responsible_;
}
constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;
void GwpAsanCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const {
if (!CrashIsMine()) {
ALOGE("Internal Error: AddCauseProtos() on a non-GWP-ASan crash.");
return;
}
Cause* cause = tombstone->add_causes();
MemoryError* memory_error = cause->mutable_memory_error();
HeapObject* heap_object = memory_error->mutable_heap();
memory_error->set_tool(MemoryError_Tool_GWP_ASAN);
switch (error_) {
case gwp_asan::Error::USE_AFTER_FREE:
memory_error->set_type(MemoryError_Type_USE_AFTER_FREE);
break;
case gwp_asan::Error::DOUBLE_FREE:
memory_error->set_type(MemoryError_Type_DOUBLE_FREE);
break;
case gwp_asan::Error::INVALID_FREE:
memory_error->set_type(MemoryError_Type_INVALID_FREE);
break;
case gwp_asan::Error::BUFFER_OVERFLOW:
memory_error->set_type(MemoryError_Type_BUFFER_OVERFLOW);
break;
case gwp_asan::Error::BUFFER_UNDERFLOW:
memory_error->set_type(MemoryError_Type_BUFFER_UNDERFLOW);
break;
default:
memory_error->set_type(MemoryError_Type_UNKNOWN);
break;
}
heap_object->set_address(__gwp_asan_get_allocation_address(responsible_allocation_));
heap_object->set_size(__gwp_asan_get_allocation_size(responsible_allocation_));
unwinder->SetDisplayBuildID(true);
std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
heap_object->set_allocation_tid(__gwp_asan_get_allocation_thread_id(responsible_allocation_));
size_t num_frames =
__gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
for (size_t i = 0; i != num_frames; ++i) {
unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
BacktraceFrame* f = heap_object->add_allocation_backtrace();
fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
}
heap_object->set_deallocation_tid(__gwp_asan_get_deallocation_thread_id(responsible_allocation_));
num_frames =
__gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
for (size_t i = 0; i != num_frames; ++i) {
unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
BacktraceFrame* f = heap_object->add_deallocation_backtrace();
fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
}
set_human_readable_cause(cause, crash_address_);
}
void GwpAsanCrashData::DumpCause(log_t* log) const {
if (!CrashIsMine()) {
ALOGE("Internal Error: DumpCause() on a non-GWP-ASan crash.");
@ -150,8 +214,6 @@ void GwpAsanCrashData::DumpCause(log_t* log) const {
error_string_, diff, byte_suffix, location_str, alloc_size, alloc_address);
}
constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;
bool GwpAsanCrashData::HasDeallocationTrace() const {
assert(CrashIsMine() && "HasDeallocationTrace(): Crash is not mine!");
if (!responsible_allocation_ || !__gwp_asan_is_deallocated(responsible_allocation_)) {
@ -164,7 +226,7 @@ void GwpAsanCrashData::DumpDeallocationTrace(log_t* log, unwindstack::Unwinder*
assert(HasDeallocationTrace() && "DumpDeallocationTrace(): No dealloc trace!");
uint64_t thread_id = __gwp_asan_get_deallocation_thread_id(responsible_allocation_);
std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
size_t num_frames =
__gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
@ -176,7 +238,7 @@ void GwpAsanCrashData::DumpDeallocationTrace(log_t* log, unwindstack::Unwinder*
unwinder->SetDisplayBuildID(true);
for (size_t i = 0; i < num_frames; ++i) {
unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]);
unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
frame_data.num = i;
_LOG(log, logtype::BACKTRACE, " %s\n", unwinder->FormatFrame(frame_data).c_str());
}
@ -191,7 +253,7 @@ void GwpAsanCrashData::DumpAllocationTrace(log_t* log, unwindstack::Unwinder* un
assert(HasAllocationTrace() && "DumpAllocationTrace(): No dealloc trace!");
uint64_t thread_id = __gwp_asan_get_allocation_thread_id(responsible_allocation_);
std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
size_t num_frames =
__gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
@ -203,7 +265,7 @@ void GwpAsanCrashData::DumpAllocationTrace(log_t* log, unwindstack::Unwinder* un
unwinder->SetDisplayBuildID(true);
for (size_t i = 0; i < num_frames; ++i) {
unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]);
unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
frame_data.num = i;
_LOG(log, logtype::BACKTRACE, " %s\n", unwinder->FormatFrame(frame_data).c_str());
}

View file

@ -26,6 +26,9 @@
#include "types.h"
#include "utility.h"
class Cause;
class Tombstone;
class GwpAsanCrashData {
public:
GwpAsanCrashData() = delete;
@ -69,6 +72,8 @@ class GwpAsanCrashData {
// HasAllocationTrace() returns true.
void DumpAllocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const;
void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const;
protected:
// Is GWP-ASan responsible for this crash.
bool is_gwp_asan_responsible_ = false;

View file

@ -23,6 +23,9 @@
#include "scudo/interface.h"
class Cause;
class Tombstone;
class ScudoCrashData {
public:
ScudoCrashData() = delete;
@ -32,6 +35,7 @@ class ScudoCrashData {
bool CrashIsMine() const;
void DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const;
void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const;
private:
scudo_error_info error_info_ = {};
@ -39,4 +43,7 @@ class ScudoCrashData {
void DumpReport(const scudo_error_report* report, log_t* log,
unwindstack::Unwinder* unwinder) const;
void FillInCause(Cause* cause, const scudo_error_report* report,
unwindstack::Unwinder* unwinder) const;
};

View file

@ -31,9 +31,13 @@
#include "types.h"
// Forward declarations
class BacktraceFrame;
class Cause;
class Tombstone;
namespace unwindstack {
struct FrameData;
class Maps;
class Unwinder;
}
@ -64,4 +68,8 @@ bool tombstone_proto_to_text(
const Tombstone& tombstone,
std::function<void(const std::string& line, bool should_log)> callback);
void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame,
unwindstack::Maps* maps);
void set_human_readable_cause(Cause* cause, uint64_t fault_addr);
#endif // _DEBUGGERD_TOMBSTONE_H

View file

@ -81,7 +81,7 @@ class Memory;
void log_backtrace(log_t* log, unwindstack::Unwinder* unwinder, const char* prefix);
ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr,
ssize_t dump_memory(void* out, size_t len, uint8_t* tags, size_t tags_len, uint64_t* addr,
unwindstack::Memory* memory);
void dump_memory(log_t* log, unwindstack::Memory* backtrace, uint64_t addr, const std::string&);
@ -93,4 +93,7 @@ void get_signal_sender(char* buf, size_t n, const siginfo_t*);
const char* get_signame(const siginfo_t*);
const char* get_sigcode(const siginfo_t*);
// Number of bytes per MTE granule.
constexpr size_t kTagGranuleSize = 16;
#endif // _DEBUGGERD_UTILITY_H

View file

@ -15,13 +15,16 @@
*/
#include "libdebuggerd/scudo.h"
#include "libdebuggerd/gwp_asan.h"
#include "libdebuggerd/tombstone.h"
#include "unwindstack/Memory.h"
#include "unwindstack/Unwinder.h"
#include <android-base/macros.h>
#include <bionic/macros.h>
#include "tombstone.pb.h"
std::unique_ptr<char[]> AllocAndReadFully(unwindstack::Memory* process_memory, uint64_t addr,
size_t size) {
auto buf = std::make_unique<char[]>(size);
@ -31,8 +34,6 @@ std::unique_ptr<char[]> AllocAndReadFully(unwindstack::Memory* process_memory, u
return buf;
}
static const uintptr_t kTagGranuleSize = 16;
ScudoCrashData::ScudoCrashData(unwindstack::Memory* process_memory,
const ProcessInfo& process_info) {
if (!process_info.has_fault_address) {
@ -78,6 +79,58 @@ bool ScudoCrashData::CrashIsMine() const {
return error_info_.reports[0].error_type != UNKNOWN;
}
void ScudoCrashData::FillInCause(Cause* cause, const scudo_error_report* report,
unwindstack::Unwinder* unwinder) const {
MemoryError* memory_error = cause->mutable_memory_error();
HeapObject* heap_object = memory_error->mutable_heap();
memory_error->set_tool(MemoryError_Tool_SCUDO);
switch (report->error_type) {
case USE_AFTER_FREE:
memory_error->set_type(MemoryError_Type_USE_AFTER_FREE);
break;
case BUFFER_OVERFLOW:
memory_error->set_type(MemoryError_Type_BUFFER_OVERFLOW);
break;
case BUFFER_UNDERFLOW:
memory_error->set_type(MemoryError_Type_BUFFER_UNDERFLOW);
break;
default:
memory_error->set_type(MemoryError_Type_UNKNOWN);
break;
}
heap_object->set_address(report->allocation_address);
heap_object->set_size(report->allocation_size);
unwinder->SetDisplayBuildID(true);
heap_object->set_allocation_tid(report->allocation_tid);
for (size_t i = 0; i < arraysize(report->allocation_trace) && report->allocation_trace[i]; ++i) {
unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(report->allocation_trace[i]);
BacktraceFrame* f = heap_object->add_allocation_backtrace();
fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
}
heap_object->set_deallocation_tid(report->deallocation_tid);
for (size_t i = 0; i < arraysize(report->deallocation_trace) && report->deallocation_trace[i];
++i) {
unwindstack::FrameData frame_data =
unwinder->BuildFrameFromPcOnly(report->deallocation_trace[i]);
BacktraceFrame* f = heap_object->add_deallocation_backtrace();
fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
}
set_human_readable_cause(cause, untagged_fault_addr_);
}
void ScudoCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const {
size_t report_num = 0;
while (report_num < sizeof(error_info_.reports) / sizeof(error_info_.reports[0]) &&
error_info_.reports[report_num].error_type != UNKNOWN) {
FillInCause(tombstone->add_causes(), &error_info_.reports[report_num++], unwinder);
}
}
void ScudoCrashData::DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const {
if (error_info_.reports[1].error_type != UNKNOWN) {
_LOG(log, logtype::HEADER,
@ -140,7 +193,8 @@ void ScudoCrashData::DumpReport(const scudo_error_report* report, log_t* log,
if (report->allocation_trace[0]) {
_LOG(log, logtype::BACKTRACE, "\nallocated by thread %u:\n", report->allocation_tid);
unwinder->SetDisplayBuildID(true);
for (size_t i = 0; i < 64 && report->allocation_trace[i]; ++i) {
for (size_t i = 0; i < arraysize(report->allocation_trace) && report->allocation_trace[i];
++i) {
unwindstack::FrameData frame_data =
unwinder->BuildFrameFromPcOnly(report->allocation_trace[i]);
frame_data.num = i;
@ -151,7 +205,8 @@ void ScudoCrashData::DumpReport(const scudo_error_report* report, log_t* log,
if (report->deallocation_trace[0]) {
_LOG(log, logtype::BACKTRACE, "\ndeallocated by thread %u:\n", report->deallocation_tid);
unwinder->SetDisplayBuildID(true);
for (size_t i = 0; i < 64 && report->deallocation_trace[i]; ++i) {
for (size_t i = 0; i < arraysize(report->deallocation_trace) && report->deallocation_trace[i];
++i) {
unwindstack::FrameData frame_data =
unwinder->BuildFrameFromPcOnly(report->deallocation_trace[i]);
frame_data.num = i;

View file

@ -73,34 +73,14 @@ const char g_expected_partial_dump[] = \
" 0000000012345600 2726252423222120 2f2e2d2c2b2a2928 !\"#$%&'()*+,-./\n"
" 0000000012345610 3736353433323130 3f3e3d3c3b3a3938 0123456789:;<=>?\n"
" 0000000012345620 4746454443424140 4f4e4d4c4b4a4948 @ABCDEFGHIJKLMNO\n"
" 0000000012345630 5756555453525150 5f5e5d5c5b5a5958 PQRSTUVWXYZ[\\]^_\n"
" 0000000012345640 6766656463626160 ---------------- `abcdefg........\n"
" 0000000012345650 ---------------- ---------------- ................\n"
" 0000000012345660 ---------------- ---------------- ................\n"
" 0000000012345670 ---------------- ---------------- ................\n"
" 0000000012345680 ---------------- ---------------- ................\n"
" 0000000012345690 ---------------- ---------------- ................\n"
" 00000000123456a0 ---------------- ---------------- ................\n"
" 00000000123456b0 ---------------- ---------------- ................\n"
" 00000000123456c0 ---------------- ---------------- ................\n"
" 00000000123456d0 ---------------- ---------------- ................\n";
" 0000000012345630 5756555453525150 5f5e5d5c5b5a5958 PQRSTUVWXYZ[\\]^_\n";
#else
" 123455e0 03020100 07060504 0b0a0908 0f0e0d0c ................\n"
" 123455f0 13121110 17161514 1b1a1918 1f1e1d1c ................\n"
" 12345600 23222120 27262524 2b2a2928 2f2e2d2c !\"#$%&'()*+,-./\n"
" 12345610 33323130 37363534 3b3a3938 3f3e3d3c 0123456789:;<=>?\n"
" 12345620 43424140 47464544 4b4a4948 4f4e4d4c @ABCDEFGHIJKLMNO\n"
" 12345630 53525150 57565554 5b5a5958 5f5e5d5c PQRSTUVWXYZ[\\]^_\n"
" 12345640 63626160 67666564 -------- -------- `abcdefg........\n"
" 12345650 -------- -------- -------- -------- ................\n"
" 12345660 -------- -------- -------- -------- ................\n"
" 12345670 -------- -------- -------- -------- ................\n"
" 12345680 -------- -------- -------- -------- ................\n"
" 12345690 -------- -------- -------- -------- ................\n"
" 123456a0 -------- -------- -------- -------- ................\n"
" 123456b0 -------- -------- -------- -------- ................\n"
" 123456c0 -------- -------- -------- -------- ................\n"
" 123456d0 -------- -------- -------- -------- ................\n";
" 12345630 53525150 57565554 5b5a5958 5f5e5d5c PQRSTUVWXYZ[\\]^_\n";
#endif
class MemoryMock : public unwindstack::Memory {
@ -513,15 +493,7 @@ TEST_F(DumpMemoryTest, first_read_empty) {
const char* expected_dump = \
"\nmemory near r4:\n"
#if defined(__LP64__)
R"( 0000000010000f80 ---------------- ---------------- ................
0000000010000f90 ---------------- ---------------- ................
0000000010000fa0 ---------------- ---------------- ................
0000000010000fb0 ---------------- ---------------- ................
0000000010000fc0 ---------------- ---------------- ................
0000000010000fd0 ---------------- ---------------- ................
0000000010000fe0 ---------------- ---------------- ................
0000000010000ff0 ---------------- ---------------- ................
0000000010001000 8786858483828180 8f8e8d8c8b8a8988 ................
R"( 0000000010001000 8786858483828180 8f8e8d8c8b8a8988 ................
0000000010001010 9796959493929190 9f9e9d9c9b9a9998 ................
0000000010001020 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8 ................
0000000010001030 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8 ................
@ -531,15 +503,7 @@ R"( 0000000010000f80 ---------------- ---------------- ................
0000000010001070 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8 ................
)";
#else
R"( 10000f80 -------- -------- -------- -------- ................
10000f90 -------- -------- -------- -------- ................
10000fa0 -------- -------- -------- -------- ................
10000fb0 -------- -------- -------- -------- ................
10000fc0 -------- -------- -------- -------- ................
10000fd0 -------- -------- -------- -------- ................
10000fe0 -------- -------- -------- -------- ................
10000ff0 -------- -------- -------- -------- ................
10001000 83828180 87868584 8b8a8988 8f8e8d8c ................
R"( 10001000 83828180 87868584 8b8a8988 8f8e8d8c ................
10001010 93929190 97969594 9b9a9998 9f9e9d9c ................
10001020 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac ................
10001030 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc ................
@ -574,39 +538,11 @@ TEST_F(DumpMemoryTest, first_read_empty_second_read_stops) {
const char* expected_dump = \
"\nmemory near r4:\n"
#if defined(__LP64__)
" 0000000010000f40 ---------------- ---------------- ................\n"
" 0000000010000f50 ---------------- ---------------- ................\n"
" 0000000010000f60 ---------------- ---------------- ................\n"
" 0000000010000f70 ---------------- ---------------- ................\n"
" 0000000010000f80 ---------------- ---------------- ................\n"
" 0000000010000f90 ---------------- ---------------- ................\n"
" 0000000010000fa0 ---------------- ---------------- ................\n"
" 0000000010000fb0 ---------------- ---------------- ................\n"
" 0000000010000fc0 ---------------- ---------------- ................\n"
" 0000000010000fd0 ---------------- ---------------- ................\n"
" 0000000010000fe0 ---------------- ---------------- ................\n"
" 0000000010000ff0 ---------------- ---------------- ................\n"
" 0000000010001000 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8 ................\n"
" 0000000010001010 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8 ................\n"
" 0000000010001020 ---------------- ---------------- ................\n"
" 0000000010001030 ---------------- ---------------- ................\n";
" 0000000010001010 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8 ................\n";
#else
" 10000f40 -------- -------- -------- -------- ................\n"
" 10000f50 -------- -------- -------- -------- ................\n"
" 10000f60 -------- -------- -------- -------- ................\n"
" 10000f70 -------- -------- -------- -------- ................\n"
" 10000f80 -------- -------- -------- -------- ................\n"
" 10000f90 -------- -------- -------- -------- ................\n"
" 10000fa0 -------- -------- -------- -------- ................\n"
" 10000fb0 -------- -------- -------- -------- ................\n"
" 10000fc0 -------- -------- -------- -------- ................\n"
" 10000fd0 -------- -------- -------- -------- ................\n"
" 10000fe0 -------- -------- -------- -------- ................\n"
" 10000ff0 -------- -------- -------- -------- ................\n"
" 10001000 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc ................\n"
" 10001010 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc ................\n"
" 10001020 -------- -------- -------- -------- ................\n"
" 10001030 -------- -------- -------- -------- ................\n";
" 10001010 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc ................\n";
#endif
ASSERT_STREQ(expected_dump, tombstone_contents.c_str());

View file

@ -17,6 +17,8 @@
#define LOG_TAG "DEBUG"
#include "libdebuggerd/tombstone.h"
#include "libdebuggerd/gwp_asan.h"
#include "libdebuggerd/scudo.h"
#include <errno.h>
#include <fcntl.h>
@ -106,32 +108,120 @@ static std::optional<std::string> get_stack_overflow_cause(uint64_t fault_addr,
return {};
}
static void dump_probable_cause(Tombstone* tombstone, const siginfo_t* si, unwindstack::Maps* maps,
unwindstack::Regs* regs) {
void set_human_readable_cause(Cause* cause, uint64_t fault_addr) {
if (!cause->has_memory_error() || !cause->memory_error().has_heap()) {
return;
}
const MemoryError& memory_error = cause->memory_error();
const HeapObject& heap_object = memory_error.heap();
const char *tool_str;
switch (memory_error.tool()) {
case MemoryError_Tool_GWP_ASAN:
tool_str = "GWP-ASan";
break;
case MemoryError_Tool_SCUDO:
tool_str = "MTE";
break;
default:
tool_str = "Unknown";
break;
}
const char *error_type_str;
switch (memory_error.type()) {
case MemoryError_Type_USE_AFTER_FREE:
error_type_str = "Use After Free";
break;
case MemoryError_Type_DOUBLE_FREE:
error_type_str = "Double Free";
break;
case MemoryError_Type_INVALID_FREE:
error_type_str = "Invalid (Wild) Free";
break;
case MemoryError_Type_BUFFER_OVERFLOW:
error_type_str = "Buffer Overflow";
break;
case MemoryError_Type_BUFFER_UNDERFLOW:
error_type_str = "Buffer Underflow";
break;
default:
cause->set_human_readable(
StringPrintf("[%s]: Unknown error occurred at 0x%" PRIx64 ".", tool_str, fault_addr));
return;
}
uint64_t diff;
const char* location_str;
if (fault_addr < heap_object.address()) {
// Buffer Underflow, 6 bytes left of a 41-byte allocation at 0xdeadbeef.
location_str = "left of";
diff = heap_object.address() - fault_addr;
} else if (fault_addr - heap_object.address() < heap_object.size()) {
// Use After Free, 40 bytes into a 41-byte allocation at 0xdeadbeef.
location_str = "into";
diff = fault_addr - heap_object.address();
} else {
// Buffer Overflow, 6 bytes right of a 41-byte allocation at 0xdeadbeef.
location_str = "right of";
diff = fault_addr - heap_object.address() - heap_object.size();
}
// Suffix of 'bytes', i.e. 4 bytes' vs. '1 byte'.
const char* byte_suffix = "s";
if (diff == 1) {
byte_suffix = "";
}
cause->set_human_readable(StringPrintf(
"[%s]: %s, %" PRIu64 " byte%s %s a %" PRIu64 "-byte allocation at 0x%" PRIx64, tool_str,
error_type_str, diff, byte_suffix, location_str, heap_object.size(), heap_object.address()));
}
static void dump_probable_cause(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
const ProcessInfo& process_info, const ThreadInfo& main_thread) {
ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info);
if (scudo_crash_data.CrashIsMine()) {
scudo_crash_data.AddCauseProtos(tombstone, unwinder);
return;
}
GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info,
main_thread);
if (gwp_asan_crash_data.CrashIsMine()) {
gwp_asan_crash_data.AddCauseProtos(tombstone, unwinder);
return;
}
const siginfo *si = main_thread.siginfo;
auto fault_addr = reinterpret_cast<uint64_t>(si->si_addr);
unwindstack::Maps* maps = unwinder->GetMaps();
std::optional<std::string> cause;
if (si->si_signo == SIGSEGV && si->si_code == SEGV_MAPERR) {
if (si->si_addr < reinterpret_cast<void*>(4096)) {
if (fault_addr < 4096) {
cause = "null pointer dereference";
} else if (si->si_addr == reinterpret_cast<void*>(0xffff0ffc)) {
} else if (fault_addr == 0xffff0ffc) {
cause = "call to kuser_helper_version";
} else if (si->si_addr == reinterpret_cast<void*>(0xffff0fe0)) {
} else if (fault_addr == 0xffff0fe0) {
cause = "call to kuser_get_tls";
} else if (si->si_addr == reinterpret_cast<void*>(0xffff0fc0)) {
} else if (fault_addr == 0xffff0fc0) {
cause = "call to kuser_cmpxchg";
} else if (si->si_addr == reinterpret_cast<void*>(0xffff0fa0)) {
} else if (fault_addr == 0xffff0fa0) {
cause = "call to kuser_memory_barrier";
} else if (si->si_addr == reinterpret_cast<void*>(0xffff0f60)) {
} else if (fault_addr == 0xffff0f60) {
cause = "call to kuser_cmpxchg64";
} else {
cause = get_stack_overflow_cause(reinterpret_cast<uint64_t>(si->si_addr), regs->sp(), maps);
cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
}
} else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
uint64_t fault_addr = reinterpret_cast<uint64_t>(si->si_addr);
unwindstack::MapInfo* map_info = maps->Find(fault_addr);
if (map_info != nullptr && map_info->flags == PROT_EXEC) {
cause = "execute-only (no-read) memory access error; likely due to data in .text.";
} else {
cause = get_stack_overflow_cause(fault_addr, regs->sp(), maps);
cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
}
} else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) {
cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING,
@ -139,7 +229,8 @@ static void dump_probable_cause(Tombstone* tombstone, const siginfo_t* si, unwin
}
if (cause) {
tombstone->mutable_cause()->set_human_readable(*cause);
Cause *cause_proto = tombstone->add_causes();
cause_proto->set_human_readable(*cause);
}
}
@ -205,12 +296,49 @@ static void dump_open_fds(Tombstone* tombstone, const OpenFilesList* open_files)
}
}
void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame,
unwindstack::Maps* maps) {
f->set_rel_pc(frame.rel_pc);
f->set_pc(frame.pc);
f->set_sp(frame.sp);
if (!frame.function_name.empty()) {
// TODO: Should this happen here, or on the display side?
char* demangled_name = __cxa_demangle(frame.function_name.c_str(), nullptr, nullptr, nullptr);
if (demangled_name) {
f->set_function_name(demangled_name);
free(demangled_name);
} else {
f->set_function_name(frame.function_name);
}
}
f->set_function_offset(frame.function_offset);
if (frame.map_start == frame.map_end) {
// No valid map associated with this frame.
f->set_file_name("<unknown>");
} else if (!frame.map_name.empty()) {
f->set_file_name(frame.map_name);
} else {
f->set_file_name(StringPrintf("<anonymous:%" PRIx64 ">", frame.map_start));
}
f->set_file_map_offset(frame.map_elf_start_offset);
unwindstack::MapInfo* map_info = maps->Find(frame.map_start);
if (map_info) {
f->set_build_id(map_info->GetPrintableBuildID());
}
}
static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
const ThreadInfo& thread_info, bool memory_dump = false) {
Thread thread;
thread.set_id(thread_info.tid);
thread.set_name(thread_info.thread_name);
thread.set_tagged_addr_ctrl(thread_info.tagged_addr_ctrl);
unwindstack::Maps* maps = unwinder->GetMaps();
unwindstack::Memory* memory = unwinder->GetProcessMemory().get();
@ -225,20 +353,19 @@ static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
if (memory_dump) {
MemoryDump dump;
char buf[256];
size_t start_offset = 0;
ssize_t bytes = dump_memory(buf, sizeof(buf), &start_offset, &value, memory);
if (bytes == -1) {
return;
}
dump.set_register_name(name);
unwindstack::MapInfo* map_info = maps->Find(untag_address(value));
if (map_info) {
dump.set_mapping_name(map_info->name);
}
char buf[256];
uint8_t tags[256 / kTagGranuleSize];
size_t start_offset = 0;
ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory);
if (bytes == -1) {
return;
}
dump.set_begin_address(value);
if (start_offset + bytes > sizeof(buf)) {
@ -246,7 +373,8 @@ static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
start_offset, bytes);
}
dump.set_memory(buf, start_offset + bytes);
dump.set_memory(buf, bytes);
dump.set_tags(tags, bytes / kTagGranuleSize);
*thread.add_memory_dump() = std::move(dump);
}
@ -267,39 +395,7 @@ static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
unwinder->SetDisplayBuildID(true);
for (const auto& frame : unwinder->frames()) {
BacktraceFrame* f = thread.add_current_backtrace();
f->set_rel_pc(frame.rel_pc);
f->set_pc(frame.pc);
f->set_sp(frame.sp);
if (!frame.function_name.empty()) {
// TODO: Should this happen here, or on the display side?
char* demangled_name =
__cxa_demangle(frame.function_name.c_str(), nullptr, nullptr, nullptr);
if (demangled_name) {
f->set_function_name(demangled_name);
free(demangled_name);
} else {
f->set_function_name(frame.function_name);
}
}
f->set_function_offset(frame.function_offset);
if (frame.map_start == frame.map_end) {
// No valid map associated with this frame.
f->set_file_name("<unknown>");
} else if (!frame.map_name.empty()) {
f->set_file_name(frame.map_name);
} else {
f->set_file_name(StringPrintf("<anonymous:%" PRIx64 ">", frame.map_start));
}
f->set_file_map_offset(frame.map_elf_start_offset);
unwindstack::MapInfo* map_info = maps->Find(frame.map_start);
if (map_info) {
f->set_build_id(map_info->GetPrintableBuildID());
}
fill_in_backtrace_frame(f, frame, maps);
}
}
@ -458,7 +554,7 @@ void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwind
if (process_info.has_fault_address) {
sig.set_has_fault_address(true);
sig.set_fault_address(process_info.untagged_fault_address);
sig.set_fault_address(process_info.maybe_tagged_fault_address);
}
*result.mutable_signal_info() = sig;
@ -473,8 +569,7 @@ void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwind
}
}
dump_probable_cause(&result, main_thread.siginfo, unwinder->GetMaps(),
main_thread.registers.get());
dump_probable_cause(&result, unwinder, process_info, main_thread);
dump_mappings(&result, unwinder);

View file

@ -74,6 +74,9 @@ static void print_thread_header(CallbackType callback, const Tombstone& tombston
CB(should_log, "pid: %d, tid: %d, name: %s >>> %s <<<", tombstone.pid(), thread.id(),
thread.name().c_str(), tombstone.process_name().c_str());
CB(should_log, "uid: %d", tombstone.uid());
if (thread.tagged_addr_ctrl() != -1) {
CB(should_log, "tagged_addr_ctrl: %016" PRIx64, thread.tagged_addr_ctrl());
}
}
static void print_register_row(CallbackType callback, int word_size,
@ -136,12 +139,11 @@ static void print_thread_registers(CallbackType callback, const Tombstone& tombs
print_register_row(callback, word_size, special_row, should_log);
}
static void print_thread_backtrace(CallbackType callback, const Tombstone& tombstone,
const Thread& thread, bool should_log) {
CBS("");
CB(should_log, "backtrace:");
static void print_backtrace(CallbackType callback, const Tombstone& tombstone,
const google::protobuf::RepeatedPtrField<BacktraceFrame>& backtrace,
bool should_log) {
int index = 0;
for (const auto& frame : thread.current_backtrace()) {
for (const auto& frame : backtrace) {
std::string function;
if (!frame.function_name().empty()) {
@ -159,16 +161,32 @@ static void print_thread_backtrace(CallbackType callback, const Tombstone& tombs
}
}
static void print_thread_backtrace(CallbackType callback, const Tombstone& tombstone,
const Thread& thread, bool should_log) {
CBS("");
CB(should_log, "backtrace:");
print_backtrace(callback, tombstone, thread.current_backtrace(), should_log);
}
static void print_thread_memory_dump(CallbackType callback, const Tombstone& tombstone,
const Thread& thread) {
static constexpr size_t bytes_per_line = 16;
static_assert(bytes_per_line == kTagGranuleSize);
int word_size = pointer_width(tombstone);
for (const auto& mem : thread.memory_dump()) {
CBS("");
CBS("memory near %s (%s):", mem.register_name().c_str(), mem.mapping_name().c_str());
if (mem.mapping_name().empty()) {
CBS("memory near %s:", mem.register_name().c_str());
} else {
CBS("memory near %s (%s):", mem.register_name().c_str(), mem.mapping_name().c_str());
}
uint64_t addr = mem.begin_address();
for (size_t offset = 0; offset < mem.memory().size(); offset += bytes_per_line) {
std::string line = StringPrintf(" %0*" PRIx64, word_size * 2, addr + offset);
uint64_t tagged_addr = addr;
if (mem.tags().size() > offset / kTagGranuleSize) {
tagged_addr |= static_cast<uint64_t>(mem.tags()[offset / kTagGranuleSize]) << 56;
}
std::string line = StringPrintf(" %0*" PRIx64, word_size * 2, tagged_addr + offset);
size_t bytes = std::min(bytes_per_line, mem.memory().size() - offset);
for (size_t i = 0; i < bytes; i += word_size) {
@ -231,9 +249,8 @@ static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
sender_desc.c_str(), fault_addr_desc.c_str());
}
if (tombstone.has_cause()) {
const Cause& cause = tombstone.cause();
CBL("Cause: %s", cause.human_readable().c_str());
if (tombstone.causes_size() == 1) {
CBL("Cause: %s", tombstone.causes(0).human_readable().c_str());
}
if (!tombstone.abort_message().empty()) {
@ -242,6 +259,36 @@ static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
print_thread_registers(callback, tombstone, thread, true);
print_thread_backtrace(callback, tombstone, thread, true);
if (tombstone.causes_size() > 1) {
CBS("");
CBS("Note: multiple potential causes for this crash were detected, listing them in decreasing "
"order of probability.");
}
for (const Cause& cause : tombstone.causes()) {
if (tombstone.causes_size() > 1) {
CBS("");
CBS("Cause: %s", cause.human_readable().c_str());
}
if (cause.has_memory_error() && cause.memory_error().has_heap()) {
const HeapObject& heap_object = cause.memory_error().heap();
if (heap_object.deallocation_backtrace_size() != 0) {
CBS("");
CBS("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid());
print_backtrace(callback, tombstone, heap_object.deallocation_backtrace(), false);
}
if (heap_object.allocation_backtrace_size() != 0) {
CBS("");
CBS("allocated by thread %" PRIu64 ":", heap_object.allocation_tid());
print_backtrace(callback, tombstone, heap_object.allocation_backtrace(), false);
}
}
}
print_thread_memory_dump(callback, tombstone, thread);
CBS("");

View file

@ -125,8 +125,9 @@ void _VLOG(log_t* log, enum logtype ltype, const char* fmt, va_list ap) {
#define MEMORY_BYTES_TO_DUMP 256
#define MEMORY_BYTES_PER_LINE 16
static_assert(MEMORY_BYTES_PER_LINE == kTagGranuleSize);
ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr,
ssize_t dump_memory(void* out, size_t len, uint8_t* tags, size_t tags_len, uint64_t* addr,
unwindstack::Memory* memory) {
// Align the address to the number of bytes per line to avoid confusing memory tag output if
// memory is tagged and we start from a misaligned address. Start 32 bytes before the address.
@ -154,17 +155,17 @@ ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr,
bytes &= ~(sizeof(uintptr_t) - 1);
}
*start_offset = 0;
bool skip_2nd_read = false;
if (bytes == 0) {
// In this case, we might want to try another read at the beginning of
// the next page only if it's within the amount of memory we would have
// read.
size_t page_size = sysconf(_SC_PAGE_SIZE);
*start_offset = ((*addr + (page_size - 1)) & ~(page_size - 1)) - *addr;
if (*start_offset == 0 || *start_offset >= len) {
uint64_t next_page = (*addr + (page_size - 1)) & ~(page_size - 1);
if (next_page == *addr || next_page >= *addr + len) {
skip_2nd_read = true;
}
*addr = next_page;
}
if (bytes < len && !skip_2nd_read) {
@ -174,8 +175,7 @@ ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr,
// into a readable map. Only requires one extra read because a map has
// to contain at least one page, and the total number of bytes to dump
// is smaller than a page.
size_t bytes2 = memory->Read(*addr + *start_offset + bytes, static_cast<uint8_t*>(out) + bytes,
len - bytes - *start_offset);
size_t bytes2 = memory->Read(*addr + bytes, static_cast<uint8_t*>(out) + bytes, len - bytes);
bytes += bytes2;
if (bytes2 > 0 && bytes % sizeof(uintptr_t) != 0) {
// This should never happen, but we'll try and continue any way.
@ -190,15 +190,24 @@ ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr,
return -1;
}
for (uint64_t tag_granule = 0; tag_granule < bytes / kTagGranuleSize; ++tag_granule) {
long tag = memory->ReadTag(*addr + kTagGranuleSize * tag_granule);
if (tag_granule < tags_len) {
tags[tag_granule] = tag >= 0 ? tag : 0;
} else {
ALOGE("Insufficient space for tags");
}
}
return bytes;
}
void dump_memory(log_t* log, unwindstack::Memory* memory, uint64_t addr, const std::string& label) {
// Dump 256 bytes
uintptr_t data[MEMORY_BYTES_TO_DUMP / sizeof(uintptr_t)];
size_t start_offset = 0;
uint8_t tags[MEMORY_BYTES_TO_DUMP / kTagGranuleSize];
ssize_t bytes = dump_memory(data, sizeof(data), &start_offset, &addr, memory);
ssize_t bytes = dump_memory(data, sizeof(data), tags, sizeof(tags), &addr, memory);
if (bytes == -1) {
return;
}
@ -212,38 +221,27 @@ void dump_memory(log_t* log, unwindstack::Memory* memory, uint64_t addr, const s
// On 32-bit machines, there are still 16 bytes per line but addresses and
// words are of course presented differently.
uintptr_t* data_ptr = data;
size_t current = 0;
size_t total_bytes = start_offset + bytes;
for (size_t line = 0; line < MEMORY_BYTES_TO_DUMP / MEMORY_BYTES_PER_LINE; line++) {
uint64_t tagged_addr = addr;
long tag = memory->ReadTag(addr);
if (tag >= 0) {
tagged_addr |= static_cast<uint64_t>(tag) << 56;
}
uint8_t* tags_ptr = tags;
for (size_t line = 0; line < static_cast<size_t>(bytes) / MEMORY_BYTES_PER_LINE; line++) {
uint64_t tagged_addr = addr | static_cast<uint64_t>(*tags_ptr++) << 56;
std::string logline;
android::base::StringAppendF(&logline, " %" PRIPTR, tagged_addr);
addr += MEMORY_BYTES_PER_LINE;
std::string ascii;
for (size_t i = 0; i < MEMORY_BYTES_PER_LINE / sizeof(uintptr_t); i++) {
if (current >= start_offset && current + sizeof(uintptr_t) <= total_bytes) {
android::base::StringAppendF(&logline, " %" PRIPTR, static_cast<uint64_t>(*data_ptr));
android::base::StringAppendF(&logline, " %" PRIPTR, static_cast<uint64_t>(*data_ptr));
// Fill out the ascii string from the data.
uint8_t* ptr = reinterpret_cast<uint8_t*>(data_ptr);
for (size_t val = 0; val < sizeof(uintptr_t); val++, ptr++) {
if (*ptr >= 0x20 && *ptr < 0x7f) {
ascii += *ptr;
} else {
ascii += '.';
}
// Fill out the ascii string from the data.
uint8_t* ptr = reinterpret_cast<uint8_t*>(data_ptr);
for (size_t val = 0; val < sizeof(uintptr_t); val++, ptr++) {
if (*ptr >= 0x20 && *ptr < 0x7f) {
ascii += *ptr;
} else {
ascii += '.';
}
data_ptr++;
} else {
logline += ' ' + std::string(sizeof(uintptr_t) * 2, '-');
ascii += std::string(sizeof(uintptr_t), '.');
}
current += sizeof(uintptr_t);
data_ptr++;
}
_LOG(log, logtype::MEMORY, "%s %s\n", logline.c_str(), ascii.c_str());
}

View file

@ -21,7 +21,7 @@ message Tombstone {
Signal signal_info = 10;
string abort_message = 14;
Cause cause = 15;
repeated Cause causes = 15;
map<uint32, Thread> threads = 16;
repeated MemoryMapping memory_mappings = 17;
@ -57,10 +57,52 @@ message Signal {
reserved 10 to 999;
}
message HeapObject {
uint64 address = 1;
uint64 size = 2;
uint64 allocation_tid = 3;
repeated BacktraceFrame allocation_backtrace = 4;
uint64 deallocation_tid = 5;
repeated BacktraceFrame deallocation_backtrace = 6;
}
message MemoryError {
enum Tool {
GWP_ASAN = 0;
SCUDO = 1;
reserved 2 to 999;
}
Tool tool = 1;
enum Type {
UNKNOWN = 0;
USE_AFTER_FREE = 1;
DOUBLE_FREE = 2;
INVALID_FREE = 3;
BUFFER_OVERFLOW = 4;
BUFFER_UNDERFLOW = 5;
reserved 6 to 999;
}
Type type = 2;
oneof location {
HeapObject heap = 3;
}
reserved 4 to 999;
}
message Cause {
string human_readable = 1;
oneof details {
MemoryError memory_error = 2;
}
reserved 2 to 999;
reserved 3 to 999;
}
message Register {
@ -76,8 +118,9 @@ message Thread {
repeated Register registers = 3;
repeated BacktraceFrame current_backtrace = 4;
repeated MemoryDump memory_dump = 5;
int64 tagged_addr_ctrl = 6;
reserved 6 to 999;
reserved 7 to 999;
}
message BacktraceFrame {
@ -100,8 +143,9 @@ message MemoryDump {
string mapping_name = 2;
uint64 begin_address = 3;
bytes memory = 4;
bytes tags = 5;
reserved 5 to 999;
reserved 6 to 999;
}
message MemoryMapping {