Merge "[MTE] write stack history into tombstone" into main
This commit is contained in:
commit
2d75f82aaf
9 changed files with 358 additions and 7 deletions
|
|
@ -359,6 +359,7 @@ cc_test {
|
|||
"libdebuggerd/test/dump_memory_test.cpp",
|
||||
"libdebuggerd/test/elf_fake.cpp",
|
||||
"libdebuggerd/test/log_fake.cpp",
|
||||
"libdebuggerd/test/mte_stack_record_test.cpp",
|
||||
"libdebuggerd/test/open_files_list_test.cpp",
|
||||
"libdebuggerd/test/tombstone_proto_to_text_test.cpp",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -662,6 +662,15 @@ int main(int argc, char** argv) {
|
|||
info.pac_enabled_keys = -1;
|
||||
}
|
||||
|
||||
#if defined(__aarch64__)
|
||||
struct iovec tls_iov = {
|
||||
&info.tls,
|
||||
sizeof(info.tls),
|
||||
};
|
||||
if (ptrace(PTRACE_GETREGSET, thread, NT_ARM_TLS, reinterpret_cast<void*>(&tls_iov)) == -1) {
|
||||
info.tls = 0;
|
||||
}
|
||||
#endif
|
||||
if (thread == g_target_thread) {
|
||||
// Read the thread's registers along with the rest of the crash info out of the pipe.
|
||||
ReadCrashInfo(input_pipe, &siginfo, &info.registers, &process_info, &recoverable_crash);
|
||||
|
|
|
|||
|
|
@ -73,5 +73,8 @@ bool tombstone_proto_to_text(
|
|||
|
||||
void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame);
|
||||
void set_human_readable_cause(Cause* cause, uint64_t fault_addr);
|
||||
|
||||
#if defined(__aarch64__)
|
||||
void dump_stack_history(unwindstack::AndroidUnwinder* unwinder, uintptr_t target_tls,
|
||||
StackHistoryBuffer& shb_ob, bool nounwind = false);
|
||||
#endif
|
||||
#endif // _DEBUGGERD_TOMBSTONE_H
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ struct ThreadInfo {
|
|||
siginfo_t* siginfo = nullptr;
|
||||
|
||||
std::unique_ptr<unwindstack::Regs> guest_registers;
|
||||
#if defined(__aarch64__)
|
||||
uintptr_t tls; // This is currently used for MTE stack history buffer.
|
||||
#endif
|
||||
};
|
||||
|
||||
// This struct is written into a pipe from inside the crashing process.
|
||||
|
|
|
|||
157
debuggerd/libdebuggerd/test/mte_stack_record_test.cpp
Normal file
157
debuggerd/libdebuggerd/test/mte_stack_record_test.cpp
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#if defined(__aarch64__)
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "bionic/mte.h"
|
||||
#include "bionic/page.h"
|
||||
#include "unwindstack/AndroidUnwinder.h"
|
||||
#include "unwindstack/Memory.h"
|
||||
|
||||
#include <android-base/test_utils.h>
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "libdebuggerd/tombstone.h"
|
||||
|
||||
struct ScopedUnmap {
|
||||
void* ptr;
|
||||
size_t size;
|
||||
~ScopedUnmap() { munmap(ptr, size); }
|
||||
};
|
||||
|
||||
class MteStackHistoryTest : public ::testing::TestWithParam<int> {
|
||||
void SetUp() override {
|
||||
#if !defined(__aarch64__)
|
||||
GTEST_SKIP();
|
||||
#endif
|
||||
SKIP_WITH_HWASAN;
|
||||
unwinder.emplace();
|
||||
unwindstack::ErrorData E;
|
||||
ASSERT_TRUE(unwinder->Initialize(E));
|
||||
}
|
||||
|
||||
protected:
|
||||
// std::optional so we don't construct it for the SKIP cases.
|
||||
std::optional<unwindstack::AndroidLocalUnwinder> unwinder;
|
||||
};
|
||||
|
||||
TEST(MteStackHistoryUnwindTest, TestOne) {
|
||||
#if !defined(__aarch64__)
|
||||
GTEST_SKIP();
|
||||
#endif
|
||||
SKIP_WITH_HWASAN;
|
||||
size_t size = stack_mte_ringbuffer_size(0);
|
||||
char* data = static_cast<char*>(stack_mte_ringbuffer_allocate(0, nullptr));
|
||||
ScopedUnmap s{data, size};
|
||||
|
||||
uintptr_t taggedfp = (1ULL << 56) | 1;
|
||||
uintptr_t pc = reinterpret_cast<uintptr_t>(&memcpy);
|
||||
memcpy(data, &pc, sizeof(pc));
|
||||
memcpy(data + 8, &taggedfp, sizeof(taggedfp));
|
||||
|
||||
// The MTE TLS is at TLS - 3, so we allocate 3 placeholders.
|
||||
void* tls[4] = {data + 16};
|
||||
|
||||
unwindstack::AndroidLocalUnwinder unwinder;
|
||||
unwindstack::ErrorData E;
|
||||
unwinder.Initialize(E);
|
||||
StackHistoryBuffer shb;
|
||||
dump_stack_history(&unwinder, reinterpret_cast<uintptr_t>(&tls[3]), shb, /* nounwind= */ false);
|
||||
ASSERT_EQ(shb.entries_size(), 1);
|
||||
const StackHistoryBufferEntry& e = shb.entries(0);
|
||||
EXPECT_EQ(e.addr().pc(), pc);
|
||||
EXPECT_EQ(e.addr().file_name(), "/apex/com.android.runtime/lib64/bionic/libc.so");
|
||||
EXPECT_EQ(e.fp(), 1ULL);
|
||||
EXPECT_EQ(e.tag(), 1ULL);
|
||||
}
|
||||
|
||||
TEST_P(MteStackHistoryTest, TestEmpty) {
|
||||
int size_cls = GetParam();
|
||||
size_t size = stack_mte_ringbuffer_size(size_cls);
|
||||
void* data = stack_mte_ringbuffer_allocate(size_cls, nullptr);
|
||||
ScopedUnmap s{data, size};
|
||||
// The MTE TLS is at TLS - 3, so we allocate 3 placeholders.
|
||||
void* tls[4] = {data};
|
||||
|
||||
StackHistoryBuffer shb;
|
||||
dump_stack_history(&*unwinder, reinterpret_cast<uintptr_t>(&tls[3]), shb, /* nounwind= */ true);
|
||||
EXPECT_EQ(shb.entries_size(), 0);
|
||||
}
|
||||
|
||||
TEST_P(MteStackHistoryTest, TestFull) {
|
||||
int size_cls = GetParam();
|
||||
size_t size = stack_mte_ringbuffer_size(size_cls);
|
||||
char* data = static_cast<char*>(stack_mte_ringbuffer_allocate(size_cls, nullptr));
|
||||
ScopedUnmap s{data, size};
|
||||
uintptr_t itr = 1;
|
||||
for (char* d = data; d < &data[size]; d += 16) {
|
||||
uintptr_t taggedfp = ((itr & 15) << 56) | itr;
|
||||
uintptr_t pc = itr;
|
||||
memcpy(d, &pc, sizeof(pc));
|
||||
memcpy(d + 8, &taggedfp, sizeof(taggedfp));
|
||||
++itr;
|
||||
}
|
||||
// The MTE TLS is at TLS - 3, so we allocate 3 placeholders.
|
||||
// Because the buffer is full, and we point at one past the last inserted element,
|
||||
// due to wrap-around we point at the beginning of the buffer.
|
||||
void* tls[4] = {data};
|
||||
|
||||
StackHistoryBuffer shb;
|
||||
dump_stack_history(&*unwinder, reinterpret_cast<uintptr_t>(&tls[3]), shb, /* nounwind= */ true);
|
||||
EXPECT_EQ(static_cast<size_t>(shb.entries_size()), size / 16);
|
||||
for (const auto& entry : shb.entries()) {
|
||||
EXPECT_EQ(entry.addr().pc(), --itr);
|
||||
EXPECT_EQ(entry.addr().pc(), entry.fp());
|
||||
EXPECT_EQ(entry.addr().pc() & 15, entry.tag());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(MteStackHistoryTest, TestHalfFull) {
|
||||
int size_cls = GetParam();
|
||||
size_t size = stack_mte_ringbuffer_size(size_cls);
|
||||
size_t half_size = size / 2;
|
||||
|
||||
char* data = static_cast<char*>(stack_mte_ringbuffer_allocate(size_cls, nullptr));
|
||||
ScopedUnmap s{data, size};
|
||||
|
||||
uintptr_t itr = 1;
|
||||
for (char* d = data; d < &data[half_size]; d += 16) {
|
||||
uintptr_t taggedfp = ((itr & 15) << 56) | itr;
|
||||
uintptr_t pc = itr;
|
||||
memcpy(d, &pc, sizeof(pc));
|
||||
memcpy(d + 8, &taggedfp, sizeof(taggedfp));
|
||||
++itr;
|
||||
}
|
||||
// The MTE TLS is at TLS - 3, so we allocate 3 placeholders.
|
||||
void* tls[4] = {&data[half_size]};
|
||||
|
||||
StackHistoryBuffer shb;
|
||||
dump_stack_history(&*unwinder, reinterpret_cast<uintptr_t>(&tls[3]), shb, /* nounwind= */ true);
|
||||
EXPECT_EQ(static_cast<size_t>(shb.entries_size()), half_size / 16);
|
||||
for (const auto& entry : shb.entries()) {
|
||||
EXPECT_EQ(entry.addr().pc(), --itr);
|
||||
EXPECT_EQ(entry.addr().pc(), entry.fp());
|
||||
EXPECT_EQ(entry.addr().pc() & 15, entry.tag());
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(MteStackHistoryTestInstance, MteStackHistoryTest, testing::Range(0, 8));
|
||||
|
||||
#endif
|
||||
|
|
@ -134,3 +134,31 @@ TEST_F(TombstoneProtoToTextTest, crash_detail_bytes) {
|
|||
ProtoToString();
|
||||
EXPECT_MATCH(text_, R"(CRASH_DETAIL_NAME: 'helloworld\\1\\255\\3')");
|
||||
}
|
||||
|
||||
TEST_F(TombstoneProtoToTextTest, stack_record) {
|
||||
auto* cause = tombstone_->add_causes();
|
||||
cause->set_human_readable("stack tag-mismatch on thread 123");
|
||||
auto* stack = tombstone_->mutable_stack_history_buffer();
|
||||
stack->set_tid(123);
|
||||
{
|
||||
auto* shb_entry = stack->add_entries();
|
||||
shb_entry->set_fp(0x1);
|
||||
shb_entry->set_tag(0xb);
|
||||
auto* addr = shb_entry->mutable_addr();
|
||||
addr->set_rel_pc(0x567);
|
||||
addr->set_file_name("foo.so");
|
||||
addr->set_build_id("ABC123");
|
||||
}
|
||||
{
|
||||
auto* shb_entry = stack->add_entries();
|
||||
shb_entry->set_fp(0x2);
|
||||
shb_entry->set_tag(0xc);
|
||||
auto* addr = shb_entry->mutable_addr();
|
||||
addr->set_rel_pc(0x678);
|
||||
addr->set_file_name("bar.so");
|
||||
}
|
||||
ProtoToString();
|
||||
EXPECT_MATCH(text_, "stack tag-mismatch on thread 123");
|
||||
EXPECT_MATCH(text_, "stack_record fp:0x1 tag:0xb pc:foo\\.so\\+0x567 \\(BuildId: ABC123\\)");
|
||||
EXPECT_MATCH(text_, "stack_record fp:0x2 tag:0xc pc:bar\\.so\\+0x678");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include <inttypes.h>
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
|
|
@ -49,9 +50,11 @@
|
|||
|
||||
#include <android/log.h>
|
||||
#include <android/set_abort_message.h>
|
||||
#include <bionic/macros.h>
|
||||
#include <bionic/reserved_signals.h>
|
||||
#include <bionic/crash_detail_internal.h>
|
||||
#include <bionic/macros.h>
|
||||
#include <bionic/mte.h>
|
||||
#include <bionic/reserved_signals.h>
|
||||
#include <bionic/tls_defines.h>
|
||||
#include <log/log.h>
|
||||
#include <log/log_read.h>
|
||||
#include <log/logprint.h>
|
||||
|
|
@ -202,8 +205,117 @@ void set_human_readable_cause(Cause* cause, uint64_t fault_addr) {
|
|||
error_type_str, diff, byte_suffix, location_str, heap_object.size(), heap_object.address()));
|
||||
}
|
||||
|
||||
#if defined(__aarch64__)
|
||||
void dump_stack_history(unwindstack::AndroidUnwinder* unwinder, uintptr_t target_tls,
|
||||
StackHistoryBuffer& shb_obj, bool nounwind) {
|
||||
auto process_memory = unwinder->GetProcessMemory();
|
||||
target_tls += sizeof(void*) * TLS_SLOT_STACK_MTE;
|
||||
uintptr_t stack_mte;
|
||||
if (!process_memory->ReadFully(target_tls, &stack_mte, sizeof(stack_mte))) {
|
||||
async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
|
||||
"dump_stack_history: failed to read TLS_SLOT_STACK_MTE: %m");
|
||||
return;
|
||||
}
|
||||
if (stack_mte == 0) {
|
||||
async_safe_format_log(ANDROID_LOG_DEBUG, LOG_TAG,
|
||||
"dump_stack_history: stack history buffer is null");
|
||||
return;
|
||||
}
|
||||
uintptr_t untagged_stack_mte = untag_address(stack_mte);
|
||||
uintptr_t buf_size = stack_mte_ringbuffer_size_from_pointer(stack_mte);
|
||||
if (buf_size == 0) {
|
||||
async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "dump_stack_history: empty size");
|
||||
return;
|
||||
}
|
||||
uintptr_t buf_start = untagged_stack_mte & ~(buf_size - 1ULL);
|
||||
std::vector<char> buf(buf_size);
|
||||
if (!process_memory->ReadFully(buf_start, buf.data(), buf.size())) {
|
||||
async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
|
||||
"dump_stack_history: failed to read stack history: %m");
|
||||
return;
|
||||
}
|
||||
uintptr_t original_off = untagged_stack_mte - buf_start;
|
||||
if (original_off % 16 || original_off > buf_size) {
|
||||
async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
|
||||
"dump_stack_history: invalid offset: %" PRIuPTR, original_off);
|
||||
return;
|
||||
}
|
||||
|
||||
// The original_off is the next slot that would have been written, so the last
|
||||
// slot that was written is the previous one.
|
||||
for (uintptr_t idx = 16; idx <= buf_size; idx += 16) {
|
||||
int64_t off = original_off - idx;
|
||||
if (off < 0) off += buf_size;
|
||||
uintptr_t pc, taggedfp;
|
||||
memcpy(&pc, &(buf[off]), sizeof(pc));
|
||||
memcpy(&taggedfp, &(buf[off + sizeof(pc)]), sizeof(taggedfp));
|
||||
|
||||
if (pc == 0) break;
|
||||
uintptr_t fp = untag_address(taggedfp);
|
||||
uintptr_t tag = taggedfp >> 56;
|
||||
|
||||
unwindstack::FrameData frame_data;
|
||||
|
||||
if (nounwind) {
|
||||
frame_data.pc = pc;
|
||||
} else {
|
||||
// +4 is to counteract the "pc adjustment" in BuildFrameFromPcOnly.
|
||||
// BuildFrameFromPcOnly assumes we are unwinding, so it needs to correct for that
|
||||
// the PC is the return address. That is not the case here.
|
||||
// It doesn't really matter, because either should be in the correct function, but
|
||||
// this is more correct (and consistent with the nounwind case).
|
||||
frame_data = unwinder->BuildFrameFromPcOnly(pc);
|
||||
frame_data.pc += 4;
|
||||
frame_data.rel_pc += 4;
|
||||
}
|
||||
|
||||
StackHistoryBufferEntry* entry = shb_obj.add_entries();
|
||||
fill_in_backtrace_frame(entry->mutable_addr(), frame_data);
|
||||
entry->set_fp(fp);
|
||||
entry->set_tag(tag);
|
||||
}
|
||||
}
|
||||
|
||||
static pid_t get_containing_thread(unwindstack::MapInfo* map_info, pid_t main_tid) {
|
||||
if (map_info == nullptr) return 0;
|
||||
|
||||
std::string name = map_info->name();
|
||||
if (name == "[stack]") {
|
||||
return main_tid;
|
||||
}
|
||||
int tid;
|
||||
if (sscanf(name.c_str(), "[anon:stack_and_tls:%d", &tid) != 1) {
|
||||
return 0;
|
||||
}
|
||||
return tid;
|
||||
}
|
||||
|
||||
static std::optional<std::string> maybe_stack_mte_cause(
|
||||
Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder, const ThreadInfo& target_thread,
|
||||
[[maybe_unused]] const std::map<pid_t, ThreadInfo>& threads, uint64_t fault_addr) {
|
||||
unwindstack::Maps* maps = unwinder->GetMaps();
|
||||
auto map_info = maps->Find(untag_address(fault_addr));
|
||||
pid_t tid = get_containing_thread(map_info.get(), target_thread.tid);
|
||||
if (!tid) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto it = threads.find(tid);
|
||||
if (it != threads.end()) {
|
||||
StackHistoryBuffer* shb = tombstone->mutable_stack_history_buffer();
|
||||
shb->set_tid(tid);
|
||||
dump_stack_history(unwinder, it->second.tls, *shb);
|
||||
} else {
|
||||
async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
|
||||
"dump_probable_cause: unknown target thread %d", tid);
|
||||
}
|
||||
return StringPrintf("stack tag-mismatch on thread %u", tid);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void dump_probable_cause(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder,
|
||||
const ProcessInfo& process_info, const ThreadInfo& target_thread) {
|
||||
const ProcessInfo& process_info, const ThreadInfo& target_thread,
|
||||
[[maybe_unused]] const std::map<pid_t, ThreadInfo>& threads) {
|
||||
#if defined(USE_SCUDO)
|
||||
ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info);
|
||||
if (scudo_crash_data.CrashIsMine()) {
|
||||
|
|
@ -247,7 +359,15 @@ static void dump_probable_cause(Tombstone* tombstone, unwindstack::AndroidUnwind
|
|||
} else {
|
||||
cause = get_stack_overflow_cause(fault_addr, target_thread.registers->sp(), maps);
|
||||
}
|
||||
} else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) {
|
||||
}
|
||||
#if defined(__aarch64__) && defined(SEGV_MTESERR)
|
||||
else if (si->si_signo == SIGSEGV && si->si_code == SEGV_MTESERR) {
|
||||
// If this was a heap MTE crash, it would have been handled by scudo. Checking whether it
|
||||
// is a stack one.
|
||||
cause = maybe_stack_mte_cause(tombstone, unwinder, target_thread, threads, fault_addr);
|
||||
}
|
||||
#endif
|
||||
else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) {
|
||||
cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING,
|
||||
si->si_syscall);
|
||||
}
|
||||
|
|
@ -787,7 +907,7 @@ void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::AndroidUnwinder*
|
|||
}
|
||||
}
|
||||
|
||||
dump_probable_cause(&result, unwinder, process_info, target_thread);
|
||||
dump_probable_cause(&result, unwinder, process_info, target_thread, threads);
|
||||
|
||||
dump_mappings(&result, unwinder->GetMaps(), unwinder->GetProcessMemory());
|
||||
|
||||
|
|
|
|||
|
|
@ -511,6 +511,19 @@ static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
|
|||
"order of likelihood.");
|
||||
}
|
||||
|
||||
if (tombstone.has_stack_history_buffer()) {
|
||||
for (const StackHistoryBufferEntry& shbe : tombstone.stack_history_buffer().entries()) {
|
||||
std::string stack_record_str = StringPrintf(
|
||||
"stack_record fp:0x%" PRIx64 " tag:0x%" PRIx64 " pc:%s+0x%" PRIx64, shbe.fp(), shbe.tag(),
|
||||
shbe.addr().file_name().c_str(), shbe.addr().rel_pc());
|
||||
if (!shbe.addr().build_id().empty()) {
|
||||
StringAppendF(&stack_record_str, " (BuildId: %s)", shbe.addr().build_id().c_str());
|
||||
}
|
||||
|
||||
CBL("%s", stack_record_str.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
for (const Cause& cause : tombstone.causes()) {
|
||||
if (tombstone.causes_size() > 1) {
|
||||
CBS("");
|
||||
|
|
|
|||
|
|
@ -22,6 +22,21 @@ message CrashDetail {
|
|||
reserved 3 to 999;
|
||||
}
|
||||
|
||||
message StackHistoryBufferEntry {
|
||||
BacktraceFrame addr = 1;
|
||||
uint64 fp = 2;
|
||||
uint64 tag = 3;
|
||||
|
||||
reserved 4 to 999;
|
||||
}
|
||||
|
||||
message StackHistoryBuffer {
|
||||
uint64 tid = 1;
|
||||
repeated StackHistoryBufferEntry entries = 2;
|
||||
|
||||
reserved 3 to 999;
|
||||
}
|
||||
|
||||
message Tombstone {
|
||||
Architecture arch = 1;
|
||||
Architecture guest_arch = 24;
|
||||
|
|
@ -53,7 +68,9 @@ message Tombstone {
|
|||
uint32 page_size = 22;
|
||||
bool has_been_16kb_mode = 23;
|
||||
|
||||
reserved 26 to 999;
|
||||
StackHistoryBuffer stack_history_buffer = 26;
|
||||
|
||||
reserved 27 to 999;
|
||||
}
|
||||
|
||||
enum Architecture {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue