[MTE] write stack history into tombstone

We will change the symbolizer to use this information to output
something like:

Potentially referenced stack object:
  0 bytes inside a stack variable "variableName" in stack frame of function functionName
  at source.cc:1234

Bug: 309446520
Change-Id: I1163ac81ac6b5e184387eb9e058d97a7227e3671
This commit is contained in:
Florian Mayer 2024-04-26 09:38:50 -07:00
parent 095f292095
commit e3e7bc7d90
9 changed files with 358 additions and 7 deletions

View file

@ -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",
],

View file

@ -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);

View file

@ -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

View file

@ -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.

View 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

View file

@ -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");
}

View file

@ -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());

View file

@ -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("");

View file

@ -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 {