tombstoned: allow intercepts for java traces.
All intercept requests and crash dump requests must now specify a dump_type, which can be one of kDebuggerdNativeBacktrace, kDebuggerdTombstone or kDebuggerdJavaBacktrace. Each process can have only one outstanding intercept registered at a time. There's only one non-trivial change in this changeset; and that is to crash_dump. We now pass the type of dump via a command line argument instead of inferring it from the (resent) signal, this allows us to connect to tombstoned before we wait for the signal as the protocol requires. Test: debuggerd_test Change-Id: I189b215acfecd08ac52ab29117e3465da00e3a37
This commit is contained in:
parent
844940d751
commit
a73df601b7
16 changed files with 237 additions and 90 deletions
|
@ -11,6 +11,11 @@ cc_defaults {
|
|||
local_include_dirs: ["include"],
|
||||
}
|
||||
|
||||
cc_library_headers {
|
||||
name: "libdebuggerd_common_headers",
|
||||
export_include_dirs: ["common/include"]
|
||||
}
|
||||
|
||||
cc_library_shared {
|
||||
name: "libtombstoned_client",
|
||||
defaults: ["debuggerd_defaults"],
|
||||
|
@ -19,15 +24,18 @@ cc_library_shared {
|
|||
"util.cpp",
|
||||
],
|
||||
|
||||
header_libs: ["libdebuggerd_common_headers"],
|
||||
|
||||
static_libs: [
|
||||
"libasync_safe"
|
||||
"libasync_safe",
|
||||
],
|
||||
|
||||
shared_libs: [
|
||||
"libcutils",
|
||||
"libbase",
|
||||
"libcutils",
|
||||
],
|
||||
|
||||
export_header_lib_headers: ["libdebuggerd_common_headers"],
|
||||
export_include_dirs: ["tombstoned/include"]
|
||||
}
|
||||
|
||||
|
@ -40,12 +48,15 @@ cc_library_static {
|
|||
"util.cpp",
|
||||
],
|
||||
|
||||
header_libs: ["libdebuggerd_common_headers"],
|
||||
|
||||
whole_static_libs: [
|
||||
"libasync_safe",
|
||||
"libcutils",
|
||||
"libbase",
|
||||
],
|
||||
|
||||
export_header_lib_headers: ["libdebuggerd_common_headers"],
|
||||
export_include_dirs: ["tombstoned/include"]
|
||||
}
|
||||
|
||||
|
@ -55,11 +66,14 @@ cc_library_static {
|
|||
defaults: ["debuggerd_defaults"],
|
||||
srcs: ["handler/debuggerd_handler.cpp"],
|
||||
|
||||
header_libs: ["libdebuggerd_common_headers"],
|
||||
|
||||
whole_static_libs: [
|
||||
"libasync_safe",
|
||||
"libdebuggerd",
|
||||
],
|
||||
|
||||
export_header_lib_headers: ["libdebuggerd_common_headers"],
|
||||
export_include_dirs: ["include"],
|
||||
}
|
||||
|
||||
|
@ -107,11 +121,14 @@ cc_library {
|
|||
"util.cpp",
|
||||
],
|
||||
|
||||
header_libs: ["libdebuggerd_common_headers"],
|
||||
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"libcutils",
|
||||
],
|
||||
|
||||
export_header_lib_headers: ["libdebuggerd_common_headers"],
|
||||
export_include_dirs: ["include"],
|
||||
}
|
||||
|
||||
|
@ -191,7 +208,8 @@ cc_test {
|
|||
"libbase",
|
||||
"libcutils",
|
||||
"libdebuggerd_client",
|
||||
"liblog"
|
||||
"liblog",
|
||||
"libnativehelper"
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
|
@ -271,6 +289,8 @@ cc_binary {
|
|||
],
|
||||
defaults: ["debuggerd_defaults"],
|
||||
|
||||
header_libs: ["libdebuggerd_common_headers"],
|
||||
|
||||
static_libs: [
|
||||
"libbase",
|
||||
"libcutils",
|
||||
|
|
|
@ -40,10 +40,12 @@ using namespace std::chrono_literals;
|
|||
|
||||
using android::base::unique_fd;
|
||||
|
||||
static bool send_signal(pid_t pid, bool backtrace) {
|
||||
static bool send_signal(pid_t pid, const DebuggerdDumpType dump_type) {
|
||||
const int signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : DEBUGGER_SIGNAL;
|
||||
sigval val;
|
||||
val.sival_int = backtrace;
|
||||
if (sigqueue(pid, DEBUGGER_SIGNAL, val) != 0) {
|
||||
val.sival_int = (dump_type == kDebuggerdNativeBacktrace) ? 1 : 0;
|
||||
|
||||
if (sigqueue(pid, signal, val) != 0) {
|
||||
PLOG(ERROR) << "libdebuggerd_client: failed to send signal to pid " << pid;
|
||||
return false;
|
||||
}
|
||||
|
@ -58,8 +60,8 @@ static void populate_timeval(struct timeval* tv, const Duration& duration) {
|
|||
tv->tv_usec = static_cast<long>(microseconds.count());
|
||||
}
|
||||
|
||||
bool debuggerd_trigger_dump(pid_t pid, unique_fd output_fd, DebuggerdDumpType dump_type,
|
||||
unsigned int timeout_ms) {
|
||||
bool debuggerd_trigger_dump(pid_t pid, DebuggerdDumpType dump_type, unsigned int timeout_ms,
|
||||
unique_fd output_fd) {
|
||||
LOG(INFO) << "libdebuggerd_client: started dumping process " << pid;
|
||||
unique_fd sockfd;
|
||||
const auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms);
|
||||
|
@ -102,7 +104,7 @@ bool debuggerd_trigger_dump(pid_t pid, unique_fd output_fd, DebuggerdDumpType du
|
|||
return false;
|
||||
}
|
||||
|
||||
InterceptRequest req = {.pid = pid };
|
||||
InterceptRequest req = {.pid = pid, .dump_type = dump_type};
|
||||
if (!set_timeout(sockfd)) {
|
||||
PLOG(ERROR) << "libdebugger_client: failed to set timeout";
|
||||
return false;
|
||||
|
@ -140,8 +142,7 @@ bool debuggerd_trigger_dump(pid_t pid, unique_fd output_fd, DebuggerdDumpType du
|
|||
return false;
|
||||
}
|
||||
|
||||
bool backtrace = dump_type == kDebuggerdBacktrace;
|
||||
if (!send_signal(pid, backtrace)) {
|
||||
if (!send_signal(pid, dump_type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -210,15 +211,16 @@ bool debuggerd_trigger_dump(pid_t pid, unique_fd output_fd, DebuggerdDumpType du
|
|||
return true;
|
||||
}
|
||||
|
||||
int dump_backtrace_to_file(pid_t tid, int fd) {
|
||||
return dump_backtrace_to_file_timeout(tid, fd, 0);
|
||||
int dump_backtrace_to_file(pid_t tid, DebuggerdDumpType dump_type, int fd) {
|
||||
return dump_backtrace_to_file_timeout(tid, dump_type, 0, fd);
|
||||
}
|
||||
|
||||
int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs) {
|
||||
int dump_backtrace_to_file_timeout(pid_t tid, DebuggerdDumpType dump_type, int timeout_secs,
|
||||
int fd) {
|
||||
android::base::unique_fd copy(dup(fd));
|
||||
if (copy == -1) {
|
||||
return -1;
|
||||
}
|
||||
int timeout_ms = timeout_secs > 0 ? timeout_secs * 1000 : 0;
|
||||
return debuggerd_trigger_dump(tid, std::move(copy), kDebuggerdBacktrace, timeout_ms) ? 0 : -1;
|
||||
return debuggerd_trigger_dump(tid, dump_type, timeout_ms, std::move(copy)) ? 0 : -1;
|
||||
}
|
||||
|
|
|
@ -67,7 +67,8 @@ TEST(debuggerd_client, race) {
|
|||
// Wait for a bit to let the child spawn all of its threads.
|
||||
std::this_thread::sleep_for(250ms);
|
||||
|
||||
ASSERT_TRUE(debuggerd_trigger_dump(forkpid, std::move(pipe_write), kDebuggerdBacktrace, 10000));
|
||||
ASSERT_TRUE(
|
||||
debuggerd_trigger_dump(forkpid, kDebuggerdNativeBacktrace, 10000, std::move(pipe_write)));
|
||||
// Immediately kill the forked child, to make sure that the dump didn't return early.
|
||||
ASSERT_EQ(0, kill(forkpid, SIGKILL)) << strerror(errno);
|
||||
|
||||
|
@ -107,5 +108,6 @@ TEST(debuggerd_client, no_timeout) {
|
|||
|
||||
unique_fd output_read, output_write;
|
||||
ASSERT_TRUE(Pipe(&output_read, &output_write));
|
||||
ASSERT_TRUE(debuggerd_trigger_dump(forkpid, std::move(output_write), kDebuggerdBacktrace, 0));
|
||||
ASSERT_TRUE(
|
||||
debuggerd_trigger_dump(forkpid, kDebuggerdNativeBacktrace, 0, std::move(output_write)));
|
||||
}
|
||||
|
|
49
debuggerd/common/include/dump_type.h
Normal file
49
debuggerd/common/include/dump_type.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
/*
|
||||
* Copyright 2017, 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.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <ostream>
|
||||
|
||||
enum DebuggerdDumpType : uint8_t {
|
||||
kDebuggerdNativeBacktrace,
|
||||
kDebuggerdTombstone,
|
||||
kDebuggerdJavaBacktrace,
|
||||
kDebuggerdAnyIntercept
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const DebuggerdDumpType& rhs) {
|
||||
switch (rhs) {
|
||||
case kDebuggerdNativeBacktrace:
|
||||
stream << "kDebuggerdNativeBacktrace";
|
||||
break;
|
||||
case kDebuggerdTombstone:
|
||||
stream << "kDebuggerdTombstone";
|
||||
break;
|
||||
case kDebuggerdJavaBacktrace:
|
||||
stream << "kDebuggerdJavaBacktrace";
|
||||
break;
|
||||
case kDebuggerdAnyIntercept:
|
||||
stream << "kDebuggerdAnyIntercept";
|
||||
break;
|
||||
default:
|
||||
stream << "[unknown]";
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
|
@ -150,13 +150,13 @@ static void signal_handler(int) {
|
|||
_exit(1);
|
||||
}
|
||||
|
||||
static void abort_handler(pid_t target, const bool& tombstoned_connected,
|
||||
static void abort_handler(pid_t target, const bool tombstoned_connected,
|
||||
unique_fd& tombstoned_socket, unique_fd& output_fd,
|
||||
const char* abort_msg) {
|
||||
// If we abort before we get an output fd, contact tombstoned to let any
|
||||
// potential listeners know that we failed.
|
||||
if (!tombstoned_connected) {
|
||||
if (!tombstoned_connect(target, &tombstoned_socket, &output_fd)) {
|
||||
if (!tombstoned_connect(target, &tombstoned_socket, &output_fd, kDebuggerdAnyIntercept)) {
|
||||
// We failed to connect, not much we can do.
|
||||
LOG(ERROR) << "failed to connected to tombstoned to report failure";
|
||||
_exit(1);
|
||||
|
@ -207,12 +207,14 @@ int main(int argc, char** argv) {
|
|||
action.sa_handler = signal_handler;
|
||||
debuggerd_register_handlers(&action);
|
||||
|
||||
if (argc != 3) {
|
||||
if (argc != 4) {
|
||||
LOG(FATAL) << "Wrong number of args: " << argc << " (expected 4)";
|
||||
return 1;
|
||||
}
|
||||
|
||||
pid_t main_tid;
|
||||
pid_t pseudothread_tid;
|
||||
int dump_type;
|
||||
|
||||
if (!android::base::ParseInt(argv[1], &main_tid, 1, std::numeric_limits<pid_t>::max())) {
|
||||
LOG(FATAL) << "invalid main tid: " << argv[1];
|
||||
|
@ -222,6 +224,10 @@ int main(int argc, char** argv) {
|
|||
LOG(FATAL) << "invalid pseudothread tid: " << argv[2];
|
||||
}
|
||||
|
||||
if (!android::base::ParseInt(argv[3], &dump_type, 0, 1)) {
|
||||
LOG(FATAL) << "invalid requested dump type: " << argv[3];
|
||||
}
|
||||
|
||||
if (target == 1) {
|
||||
LOG(FATAL) << "target died before we could attach (received main tid = " << main_tid << ")";
|
||||
}
|
||||
|
@ -305,8 +311,9 @@ int main(int argc, char** argv) {
|
|||
// Drop our capabilities now that we've attached to the threads we care about.
|
||||
drop_capabilities();
|
||||
|
||||
LOG(INFO) << "obtaining output fd from tombstoned";
|
||||
tombstoned_connected = tombstoned_connect(target, &tombstoned_socket, &output_fd);
|
||||
const DebuggerdDumpType dump_type_enum = static_cast<DebuggerdDumpType>(dump_type);
|
||||
LOG(INFO) << "obtaining output fd from tombstoned, type: " << dump_type_enum;
|
||||
tombstoned_connected = tombstoned_connect(target, &tombstoned_socket, &output_fd, dump_type_enum);
|
||||
|
||||
// Write a '\1' to stdout to tell the crashing process to resume.
|
||||
// It also restores the value of PR_SET_DUMPABLE at this point.
|
||||
|
|
|
@ -72,8 +72,8 @@ int main(int argc, char* argv[]) {
|
|||
}
|
||||
|
||||
std::thread redirect_thread = spawn_redirect_thread(std::move(piperead));
|
||||
if (!debuggerd_trigger_dump(pid, std::move(pipewrite),
|
||||
backtrace_only ? kDebuggerdBacktrace : kDebuggerdTombstone, 0)) {
|
||||
if (!debuggerd_trigger_dump(pid, backtrace_only ? kDebuggerdNativeBacktrace : kDebuggerdTombstone,
|
||||
0, std::move(pipewrite))) {
|
||||
redirect_thread.join();
|
||||
errx(1, "failed to dump process %d", pid);
|
||||
}
|
||||
|
|
|
@ -88,14 +88,15 @@ constexpr char kWaitForGdbKey[] = "debug.debuggerd.wait_for_gdb";
|
|||
} \
|
||||
} while (0)
|
||||
|
||||
static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd) {
|
||||
static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd,
|
||||
DebuggerdDumpType intercept_type) {
|
||||
intercept_fd->reset(socket_local_client(kTombstonedInterceptSocketName,
|
||||
ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET));
|
||||
if (intercept_fd->get() == -1) {
|
||||
FAIL() << "failed to contact tombstoned: " << strerror(errno);
|
||||
}
|
||||
|
||||
InterceptRequest req = {.pid = target_pid};
|
||||
InterceptRequest req = {.pid = target_pid, .dump_type = intercept_type};
|
||||
|
||||
unique_fd output_pipe_write;
|
||||
if (!Pipe(output_fd, &output_pipe_write)) {
|
||||
|
@ -146,7 +147,7 @@ class CrasherTest : public ::testing::Test {
|
|||
CrasherTest();
|
||||
~CrasherTest();
|
||||
|
||||
void StartIntercept(unique_fd* output_fd);
|
||||
void StartIntercept(unique_fd* output_fd, DebuggerdDumpType intercept_type = kDebuggerdTombstone);
|
||||
|
||||
// Returns -1 if we fail to read a response from tombstoned, otherwise the received return code.
|
||||
void FinishIntercept(int* result);
|
||||
|
@ -172,12 +173,12 @@ CrasherTest::~CrasherTest() {
|
|||
android::base::SetProperty(kWaitForGdbKey, previous_wait_for_gdb ? "1" : "0");
|
||||
}
|
||||
|
||||
void CrasherTest::StartIntercept(unique_fd* output_fd) {
|
||||
void CrasherTest::StartIntercept(unique_fd* output_fd, DebuggerdDumpType intercept_type) {
|
||||
if (crasher_pid == -1) {
|
||||
FAIL() << "crasher hasn't been started";
|
||||
}
|
||||
|
||||
tombstoned_intercept(crasher_pid, &this->intercept_fd, output_fd);
|
||||
tombstoned_intercept(crasher_pid, &this->intercept_fd, output_fd, intercept_type);
|
||||
}
|
||||
|
||||
void CrasherTest::FinishIntercept(int* result) {
|
||||
|
@ -428,7 +429,7 @@ TEST_F(CrasherTest, backtrace) {
|
|||
StartProcess([]() {
|
||||
abort();
|
||||
});
|
||||
StartIntercept(&output_fd);
|
||||
StartIntercept(&output_fd, kDebuggerdNativeBacktrace);
|
||||
|
||||
std::this_thread::sleep_for(500ms);
|
||||
|
||||
|
@ -595,11 +596,11 @@ TEST(tombstoned, no_notify) {
|
|||
pid_t pid = 123'456'789 + i;
|
||||
|
||||
unique_fd intercept_fd, output_fd;
|
||||
tombstoned_intercept(pid, &intercept_fd, &output_fd);
|
||||
tombstoned_intercept(pid, &intercept_fd, &output_fd, kDebuggerdTombstone);
|
||||
|
||||
{
|
||||
unique_fd tombstoned_socket, input_fd;
|
||||
ASSERT_TRUE(tombstoned_connect(pid, &tombstoned_socket, &input_fd));
|
||||
ASSERT_TRUE(tombstoned_connect(pid, &tombstoned_socket, &input_fd, kDebuggerdTombstone));
|
||||
ASSERT_TRUE(android::base::WriteFully(input_fd.get(), &pid, sizeof(pid)));
|
||||
}
|
||||
|
||||
|
@ -627,7 +628,7 @@ TEST(tombstoned, stress) {
|
|||
pid_t pid = pid_base + dump;
|
||||
|
||||
unique_fd intercept_fd, output_fd;
|
||||
tombstoned_intercept(pid, &intercept_fd, &output_fd);
|
||||
tombstoned_intercept(pid, &intercept_fd, &output_fd, kDebuggerdTombstone);
|
||||
|
||||
// Pretend to crash, and then immediately close the socket.
|
||||
unique_fd sockfd(socket_local_client(kTombstonedCrashSocketName,
|
||||
|
@ -658,11 +659,11 @@ TEST(tombstoned, stress) {
|
|||
pid_t pid = pid_base + dump;
|
||||
|
||||
unique_fd intercept_fd, output_fd;
|
||||
tombstoned_intercept(pid, &intercept_fd, &output_fd);
|
||||
tombstoned_intercept(pid, &intercept_fd, &output_fd, kDebuggerdTombstone);
|
||||
|
||||
{
|
||||
unique_fd tombstoned_socket, input_fd;
|
||||
ASSERT_TRUE(tombstoned_connect(pid, &tombstoned_socket, &input_fd));
|
||||
ASSERT_TRUE(tombstoned_connect(pid, &tombstoned_socket, &input_fd, kDebuggerdTombstone));
|
||||
ASSERT_TRUE(android::base::WriteFully(input_fd.get(), &pid, sizeof(pid)));
|
||||
tombstoned_notify_completion(tombstoned_socket.get());
|
||||
}
|
||||
|
|
|
@ -151,7 +151,7 @@ static void trace_handler(siginfo_t* info, ucontext_t* ucontext) {
|
|||
|
||||
// Fetch output fd from tombstoned.
|
||||
unique_fd tombstone_socket, output_fd;
|
||||
if (!tombstoned_connect(getpid(), &tombstone_socket, &output_fd)) {
|
||||
if (!tombstoned_connect(getpid(), &tombstone_socket, &output_fd, kDebuggerdNativeBacktrace)) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
@ -215,7 +215,8 @@ static void crash_handler(siginfo_t* info, ucontext_t* ucontext, void* abort_mes
|
|||
}
|
||||
|
||||
unique_fd tombstone_socket, output_fd;
|
||||
bool tombstoned_connected = tombstoned_connect(getpid(), &tombstone_socket, &output_fd);
|
||||
bool tombstoned_connected =
|
||||
tombstoned_connect(getpid(), &tombstone_socket, &output_fd, kDebuggerdTombstone);
|
||||
debuggerd_fallback_tombstone(output_fd.get(), ucontext, info, abort_message);
|
||||
if (tombstoned_connected) {
|
||||
tombstoned_notify_completion(tombstone_socket.get());
|
||||
|
|
|
@ -50,6 +50,8 @@
|
|||
|
||||
#include <async_safe/log.h>
|
||||
|
||||
#include "dump_type.h"
|
||||
|
||||
// see man(2) prctl, specifically the section about PR_GET_NAME
|
||||
#define MAX_TASK_NAME_LEN (16)
|
||||
|
||||
|
@ -253,6 +255,14 @@ struct debugger_thread_info {
|
|||
// process.
|
||||
static void* pseudothread_stack;
|
||||
|
||||
static DebuggerdDumpType get_dump_type(const debugger_thread_info* thread_info) {
|
||||
if (thread_info->signal_number == DEBUGGER_SIGNAL && thread_info->info->si_value.sival_int) {
|
||||
return kDebuggerdNativeBacktrace;
|
||||
}
|
||||
|
||||
return kDebuggerdTombstone;
|
||||
}
|
||||
|
||||
static int debuggerd_dispatch_pseudothread(void* arg) {
|
||||
debugger_thread_info* thread_info = static_cast<debugger_thread_info*>(arg);
|
||||
|
||||
|
@ -285,11 +295,15 @@ static int debuggerd_dispatch_pseudothread(void* arg) {
|
|||
|
||||
char main_tid[10];
|
||||
char pseudothread_tid[10];
|
||||
char debuggerd_dump_type[10];
|
||||
async_safe_format_buffer(main_tid, sizeof(main_tid), "%d", thread_info->crashing_tid);
|
||||
async_safe_format_buffer(pseudothread_tid, sizeof(pseudothread_tid), "%d",
|
||||
thread_info->pseudothread_tid);
|
||||
async_safe_format_buffer(debuggerd_dump_type, sizeof(debuggerd_dump_type), "%d",
|
||||
get_dump_type(thread_info));
|
||||
|
||||
execl(CRASH_DUMP_PATH, CRASH_DUMP_NAME, main_tid, pseudothread_tid, nullptr);
|
||||
execl(CRASH_DUMP_PATH, CRASH_DUMP_NAME, main_tid, pseudothread_tid, debuggerd_dump_type,
|
||||
nullptr);
|
||||
|
||||
fatal_errno("exec failed");
|
||||
} else {
|
||||
|
|
|
@ -22,15 +22,13 @@
|
|||
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
enum DebuggerdDumpType {
|
||||
kDebuggerdBacktrace,
|
||||
kDebuggerdTombstone,
|
||||
};
|
||||
#include "dump_type.h"
|
||||
|
||||
// Trigger a dump of specified process to output_fd.
|
||||
// output_fd is consumed, timeout of 0 will wait forever.
|
||||
bool debuggerd_trigger_dump(pid_t pid, android::base::unique_fd output_fd,
|
||||
enum DebuggerdDumpType dump_type, unsigned int timeout_ms);
|
||||
bool debuggerd_trigger_dump(pid_t pid, enum DebuggerdDumpType dump_type, unsigned int timeout_ms,
|
||||
android::base::unique_fd output_fd);
|
||||
|
||||
int dump_backtrace_to_file(pid_t tid, int fd);
|
||||
int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs);
|
||||
int dump_backtrace_to_file(pid_t tid, enum DebuggerdDumpType dump_type, int output_fd);
|
||||
int dump_backtrace_to_file_timeout(pid_t tid, enum DebuggerdDumpType dump_type, int timeout_secs,
|
||||
int output_fd);
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "dump_type.h"
|
||||
|
||||
// Sockets in the ANDROID_SOCKET_NAMESPACE_RESERVED namespace.
|
||||
// Both sockets are SOCK_SEQPACKET sockets, so no explicit length field is needed.
|
||||
constexpr char kTombstonedCrashSocketName[] = "tombstoned_crash";
|
||||
|
@ -40,6 +42,7 @@ enum class CrashPacketType : uint8_t {
|
|||
};
|
||||
|
||||
struct DumpRequest {
|
||||
DebuggerdDumpType dump_type;
|
||||
int32_t pid;
|
||||
};
|
||||
|
||||
|
@ -54,10 +57,15 @@ struct TombstonedCrashPacket {
|
|||
// Comes with a file descriptor via SCM_RIGHTS.
|
||||
// This packet should be sent before an actual dump happens.
|
||||
struct InterceptRequest {
|
||||
DebuggerdDumpType dump_type;
|
||||
int32_t pid;
|
||||
};
|
||||
|
||||
enum class InterceptStatus : uint8_t {
|
||||
// Returned when an intercept of a different type has already been
|
||||
// registered (and is active) for a given PID.
|
||||
kFailedAlreadyRegistered,
|
||||
// Returned in all other failure cases.
|
||||
kFailed,
|
||||
kStarted,
|
||||
kRegistered,
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
#include "dump_type.h"
|
||||
|
||||
bool tombstoned_connect(pid_t pid, android::base::unique_fd* tombstoned_socket,
|
||||
android::base::unique_fd* output_fd, bool is_native_crash = true);
|
||||
android::base::unique_fd* output_fd, DebuggerdDumpType dump_type);
|
||||
|
||||
bool tombstoned_notify_completion(int tombstoned_socket);
|
||||
|
|
|
@ -61,11 +61,24 @@ static void intercept_close_cb(evutil_socket_t sockfd, short event, void* arg) {
|
|||
reason = "due to input";
|
||||
}
|
||||
|
||||
LOG(INFO) << "intercept for pid " << intercept->intercept_pid << " terminated " << reason;
|
||||
LOG(INFO) << "intercept for pid " << intercept->intercept_pid << " and type "
|
||||
<< intercept->dump_type << " terminated: " << reason;
|
||||
intercept_manager->intercepts.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_intercept_request_valid(const InterceptRequest& request) {
|
||||
if (request.pid <= 0 || request.pid > std::numeric_limits<pid_t>::max()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.dump_type < 0 || request.dump_type > kDebuggerdJavaBacktrace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void intercept_request_cb(evutil_socket_t sockfd, short ev, void* arg) {
|
||||
auto intercept = reinterpret_cast<Intercept*>(arg);
|
||||
InterceptManager* intercept_manager = intercept->intercept_manager;
|
||||
|
@ -103,23 +116,24 @@ static void intercept_request_cb(evutil_socket_t sockfd, short ev, void* arg) {
|
|||
rcv_fd.reset(moved_fd);
|
||||
|
||||
// We trust the other side, so only do minimal validity checking.
|
||||
if (intercept_request.pid <= 0 || intercept_request.pid > std::numeric_limits<pid_t>::max()) {
|
||||
if (!is_intercept_request_valid(intercept_request)) {
|
||||
InterceptResponse response = {};
|
||||
response.status = InterceptStatus::kFailed;
|
||||
snprintf(response.error_message, sizeof(response.error_message), "invalid pid %" PRId32,
|
||||
intercept_request.pid);
|
||||
snprintf(response.error_message, sizeof(response.error_message), "invalid intercept request");
|
||||
TEMP_FAILURE_RETRY(write(sockfd, &response, sizeof(response)));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
intercept->intercept_pid = intercept_request.pid;
|
||||
intercept->dump_type = intercept_request.dump_type;
|
||||
|
||||
// Check if it's already registered.
|
||||
if (intercept_manager->intercepts.count(intercept_request.pid) > 0) {
|
||||
InterceptResponse response = {};
|
||||
response.status = InterceptStatus::kFailed;
|
||||
response.status = InterceptStatus::kFailedAlreadyRegistered;
|
||||
snprintf(response.error_message, sizeof(response.error_message),
|
||||
"pid %" PRId32 " already intercepted", intercept_request.pid);
|
||||
"pid %" PRId32 " already intercepted, type %d", intercept_request.pid,
|
||||
intercept_request.dump_type);
|
||||
TEMP_FAILURE_RETRY(write(sockfd, &response, sizeof(response)));
|
||||
LOG(WARNING) << response.error_message;
|
||||
goto fail;
|
||||
|
@ -138,7 +152,8 @@ static void intercept_request_cb(evutil_socket_t sockfd, short ev, void* arg) {
|
|||
intercept_manager->intercepts[intercept_request.pid] = std::unique_ptr<Intercept>(intercept);
|
||||
intercept->registered = true;
|
||||
|
||||
LOG(INFO) << "tombstoned registered intercept for pid " << intercept_request.pid;
|
||||
LOG(INFO) << "registered intercept for pid " << intercept_request.pid << " and type "
|
||||
<< intercept_request.dump_type;
|
||||
|
||||
// Register a different read event on the socket so that we can remove intercepts if the socket
|
||||
// closes (e.g. if a user CTRL-C's the process that requested the intercept).
|
||||
|
@ -174,16 +189,27 @@ InterceptManager::InterceptManager(event_base* base, int intercept_socket) : bas
|
|||
intercept_socket);
|
||||
}
|
||||
|
||||
bool InterceptManager::GetIntercept(pid_t pid, android::base::unique_fd* out_fd) {
|
||||
bool InterceptManager::GetIntercept(pid_t pid, DebuggerdDumpType dump_type,
|
||||
android::base::unique_fd* out_fd) {
|
||||
auto it = this->intercepts.find(pid);
|
||||
if (it == this->intercepts.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dump_type == kDebuggerdAnyIntercept) {
|
||||
LOG(INFO) << "found registered intercept of type " << it->second->dump_type
|
||||
<< " for requested type kDebuggerdAnyIntercept";
|
||||
} else if (it->second->dump_type != dump_type) {
|
||||
LOG(WARNING) << "found non-matching intercept of type " << it->second->dump_type
|
||||
<< " for requested type: " << dump_type;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto intercept = std::move(it->second);
|
||||
this->intercepts.erase(it);
|
||||
|
||||
LOG(INFO) << "found intercept fd " << intercept->output_fd.get() << " for pid " << pid;
|
||||
LOG(INFO) << "found intercept fd " << intercept->output_fd.get() << " for pid " << pid
|
||||
<< " and type " << intercept->dump_type;
|
||||
InterceptResponse response = {};
|
||||
response.status = InterceptStatus::kStarted;
|
||||
TEMP_FAILURE_RETRY(write(intercept->sockfd, &response, sizeof(response)));
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
#include "dump_type.h"
|
||||
|
||||
struct InterceptManager;
|
||||
|
||||
struct Intercept {
|
||||
|
@ -39,6 +41,7 @@ struct Intercept {
|
|||
pid_t intercept_pid = -1;
|
||||
android::base::unique_fd output_fd;
|
||||
bool registered = false;
|
||||
DebuggerdDumpType dump_type = kDebuggerdNativeBacktrace;
|
||||
};
|
||||
|
||||
struct InterceptManager {
|
||||
|
@ -50,5 +53,5 @@ struct InterceptManager {
|
|||
InterceptManager(InterceptManager& copy) = delete;
|
||||
InterceptManager(InterceptManager&& move) = delete;
|
||||
|
||||
bool GetIntercept(pid_t pid, android::base::unique_fd* out_fd);
|
||||
bool GetIntercept(pid_t pid, DebuggerdDumpType dump_type, android::base::unique_fd* out_fd);
|
||||
};
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <cutils/sockets.h>
|
||||
|
||||
#include "debuggerd/handler.h"
|
||||
#include "dump_type.h"
|
||||
#include "protocol.h"
|
||||
#include "util.h"
|
||||
|
||||
|
@ -52,10 +53,10 @@ enum CrashStatus {
|
|||
|
||||
struct Crash;
|
||||
|
||||
class CrashType {
|
||||
class CrashQueue {
|
||||
public:
|
||||
CrashType(const std::string& dir_path, const std::string& file_name_prefix, size_t max_artifacts,
|
||||
size_t max_concurrent_dumps)
|
||||
CrashQueue(const std::string& dir_path, const std::string& file_name_prefix, size_t max_artifacts,
|
||||
size_t max_concurrent_dumps)
|
||||
: file_name_prefix_(file_name_prefix),
|
||||
dir_path_(dir_path),
|
||||
dir_fd_(open(dir_path.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC)),
|
||||
|
@ -114,8 +115,8 @@ class CrashType {
|
|||
|
||||
void on_crash_completed() { --num_concurrent_dumps_; }
|
||||
|
||||
static CrashType* const tombstone;
|
||||
static CrashType* const java_trace;
|
||||
static CrashQueue* const tombstone;
|
||||
static CrashQueue* const java_trace;
|
||||
|
||||
private:
|
||||
void find_oldest_artifact() {
|
||||
|
@ -158,19 +159,19 @@ class CrashType {
|
|||
|
||||
std::deque<Crash*> queued_requests_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashType);
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashQueue);
|
||||
};
|
||||
|
||||
// Whether java trace dumps are produced via tombstoned.
|
||||
static constexpr bool kJavaTraceDumpsEnabled = false;
|
||||
|
||||
/* static */ CrashType* const CrashType::tombstone =
|
||||
new CrashType("/data/tombstones", "tombstone_" /* file_name_prefix */, 10 /* max_artifacts */,
|
||||
1 /* max_concurrent_dumps */);
|
||||
/* static */ CrashQueue* const CrashQueue::tombstone =
|
||||
new CrashQueue("/data/tombstones", "tombstone_" /* file_name_prefix */, 10 /* max_artifacts */,
|
||||
1 /* max_concurrent_dumps */);
|
||||
|
||||
/* static */ CrashType* const CrashType::java_trace =
|
||||
(kJavaTraceDumpsEnabled ? new CrashType("/data/anr", "anr_" /* file_name_prefix */,
|
||||
64 /* max_artifacts */, 4 /* max_concurrent_dumps */)
|
||||
/* static */ CrashQueue* const CrashQueue::java_trace =
|
||||
(kJavaTraceDumpsEnabled ? new CrashQueue("/data/anr", "anr_" /* file_name_prefix */,
|
||||
64 /* max_artifacts */, 4 /* max_concurrent_dumps */)
|
||||
: nullptr);
|
||||
|
||||
// Ownership of Crash is a bit messy.
|
||||
|
@ -183,10 +184,17 @@ struct Crash {
|
|||
pid_t crash_pid;
|
||||
event* crash_event = nullptr;
|
||||
|
||||
// Not owned by |Crash|.
|
||||
CrashType* crash_type = nullptr;
|
||||
DebuggerdDumpType crash_type;
|
||||
};
|
||||
|
||||
static CrashQueue* get_crash_queue(const Crash* crash) {
|
||||
if (crash->crash_type == kDebuggerdJavaBacktrace) {
|
||||
return CrashQueue::java_trace;
|
||||
}
|
||||
|
||||
return CrashQueue::tombstone;
|
||||
}
|
||||
|
||||
// Forward declare the callbacks so they can be placed in a sensible order.
|
||||
static void crash_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int, void*);
|
||||
static void crash_request_cb(evutil_socket_t sockfd, short ev, void* arg);
|
||||
|
@ -194,10 +202,8 @@ static void crash_completed_cb(evutil_socket_t sockfd, short ev, void* arg);
|
|||
|
||||
static void perform_request(Crash* crash) {
|
||||
unique_fd output_fd;
|
||||
// Note that java traces are not interceptible.
|
||||
if ((crash->crash_type == CrashType::java_trace) ||
|
||||
!intercept_manager->GetIntercept(crash->crash_pid, &output_fd)) {
|
||||
output_fd = crash->crash_type->get_output_fd();
|
||||
if (!intercept_manager->GetIntercept(crash->crash_pid, crash->crash_type, &output_fd)) {
|
||||
output_fd = get_crash_queue(crash)->get_output_fd();
|
||||
}
|
||||
|
||||
TombstonedCrashPacket response = {
|
||||
|
@ -220,7 +226,7 @@ static void perform_request(Crash* crash) {
|
|||
event_add(crash->crash_event, &timeout);
|
||||
}
|
||||
|
||||
crash->crash_type->on_crash_started();
|
||||
get_crash_queue(crash)->on_crash_started();
|
||||
return;
|
||||
|
||||
fail:
|
||||
|
@ -228,22 +234,22 @@ fail:
|
|||
}
|
||||
|
||||
static void crash_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int,
|
||||
void* crash_type) {
|
||||
void*) {
|
||||
event_base* base = evconnlistener_get_base(listener);
|
||||
Crash* crash = new Crash();
|
||||
|
||||
// TODO: Make sure that only java crashes come in on the java socket
|
||||
// and only native crashes on the native socket.
|
||||
struct timeval timeout = { 1, 0 };
|
||||
event* crash_event = event_new(base, sockfd, EV_TIMEOUT | EV_READ, crash_request_cb, crash);
|
||||
crash->crash_fd.reset(sockfd);
|
||||
crash->crash_event = crash_event;
|
||||
crash->crash_type = static_cast<CrashType*>(crash_type);
|
||||
event_add(crash_event, &timeout);
|
||||
}
|
||||
|
||||
static void crash_request_cb(evutil_socket_t sockfd, short ev, void* arg) {
|
||||
ssize_t rc;
|
||||
Crash* crash = static_cast<Crash*>(arg);
|
||||
CrashType* type = crash->crash_type;
|
||||
|
||||
TombstonedCrashPacket request = {};
|
||||
|
||||
|
@ -271,7 +277,13 @@ static void crash_request_cb(evutil_socket_t sockfd, short ev, void* arg) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
if (type == CrashType::tombstone) {
|
||||
crash->crash_type = request.packet.dump_request.dump_type;
|
||||
if (crash->crash_type < 0 || crash->crash_type > kDebuggerdAnyIntercept) {
|
||||
LOG(WARNING) << "unexpected crash dump type: " << crash->crash_type;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (crash->crash_type != kDebuggerdJavaBacktrace) {
|
||||
crash->crash_pid = request.packet.dump_request.pid;
|
||||
} else {
|
||||
// Requests for java traces are sent from untrusted processes, so we
|
||||
|
@ -290,7 +302,7 @@ static void crash_request_cb(evutil_socket_t sockfd, short ev, void* arg) {
|
|||
|
||||
LOG(INFO) << "received crash request for pid " << crash->crash_pid;
|
||||
|
||||
if (type->maybe_enqueue_crash(crash)) {
|
||||
if (get_crash_queue(crash)->maybe_enqueue_crash(crash)) {
|
||||
LOG(INFO) << "enqueueing crash request for pid " << crash->crash_pid;
|
||||
} else {
|
||||
perform_request(crash);
|
||||
|
@ -307,7 +319,7 @@ static void crash_completed_cb(evutil_socket_t sockfd, short ev, void* arg) {
|
|||
Crash* crash = static_cast<Crash*>(arg);
|
||||
TombstonedCrashPacket request = {};
|
||||
|
||||
crash->crash_type->on_crash_completed();
|
||||
get_crash_queue(crash)->on_crash_completed();
|
||||
|
||||
if ((ev & EV_READ) == 0) {
|
||||
goto fail;
|
||||
|
@ -330,11 +342,11 @@ static void crash_completed_cb(evutil_socket_t sockfd, short ev, void* arg) {
|
|||
}
|
||||
|
||||
fail:
|
||||
CrashType* type = crash->crash_type;
|
||||
CrashQueue* queue = get_crash_queue(crash);
|
||||
delete crash;
|
||||
|
||||
// If there's something queued up, let them proceed.
|
||||
type->maybe_dequeue_crashes(perform_request);
|
||||
queue->maybe_dequeue_crashes(perform_request);
|
||||
}
|
||||
|
||||
int main(int, char* []) {
|
||||
|
@ -366,7 +378,7 @@ int main(int, char* []) {
|
|||
intercept_manager = new InterceptManager(base, intercept_socket);
|
||||
|
||||
evconnlistener* tombstone_listener = evconnlistener_new(
|
||||
base, crash_accept_cb, CrashType::tombstone, -1, LEV_OPT_CLOSE_ON_FREE, crash_socket);
|
||||
base, crash_accept_cb, CrashQueue::tombstone, -1, LEV_OPT_CLOSE_ON_FREE, crash_socket);
|
||||
if (!tombstone_listener) {
|
||||
LOG(FATAL) << "failed to create evconnlistener for tombstones.";
|
||||
}
|
||||
|
@ -379,7 +391,7 @@ int main(int, char* []) {
|
|||
|
||||
evutil_make_socket_nonblocking(java_trace_socket);
|
||||
evconnlistener* java_trace_listener = evconnlistener_new(
|
||||
base, crash_accept_cb, CrashType::java_trace, -1, LEV_OPT_CLOSE_ON_FREE, java_trace_socket);
|
||||
base, crash_accept_cb, CrashQueue::java_trace, -1, LEV_OPT_CLOSE_ON_FREE, java_trace_socket);
|
||||
if (!java_trace_listener) {
|
||||
LOG(FATAL) << "failed to create evconnlistener for java traces.";
|
||||
}
|
||||
|
|
|
@ -31,10 +31,11 @@
|
|||
using android::base::unique_fd;
|
||||
|
||||
bool tombstoned_connect(pid_t pid, unique_fd* tombstoned_socket, unique_fd* output_fd,
|
||||
bool is_native_crash) {
|
||||
unique_fd sockfd(socket_local_client(
|
||||
(is_native_crash ? kTombstonedCrashSocketName : kTombstonedJavaTraceSocketName),
|
||||
ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET));
|
||||
DebuggerdDumpType dump_type) {
|
||||
unique_fd sockfd(
|
||||
socket_local_client((dump_type != kDebuggerdJavaBacktrace ? kTombstonedCrashSocketName
|
||||
: kTombstonedJavaTraceSocketName),
|
||||
ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET));
|
||||
if (sockfd == -1) {
|
||||
async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to connect to tombstoned: %s",
|
||||
strerror(errno));
|
||||
|
@ -44,6 +45,7 @@ bool tombstoned_connect(pid_t pid, unique_fd* tombstoned_socket, unique_fd* outp
|
|||
TombstonedCrashPacket packet = {};
|
||||
packet.packet_type = CrashPacketType::kDumpRequest;
|
||||
packet.packet.dump_request.pid = pid;
|
||||
packet.packet.dump_request.dump_type = dump_type;
|
||||
if (TEMP_FAILURE_RETRY(write(sockfd, &packet, sizeof(packet))) != sizeof(packet)) {
|
||||
async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to write DumpRequest packet: %s",
|
||||
strerror(errno));
|
||||
|
|
Loading…
Reference in a new issue