Merge "Allow calling GetFunctionName before unwinding."
This commit is contained in:
commit
435fc45103
9 changed files with 160 additions and 112 deletions
|
|
@ -36,7 +36,7 @@
|
||||||
class BacktraceMap;
|
class BacktraceMap;
|
||||||
|
|
||||||
class BacktraceCurrent : public Backtrace {
|
class BacktraceCurrent : public Backtrace {
|
||||||
public:
|
public:
|
||||||
BacktraceCurrent(pid_t pid, pid_t tid, BacktraceMap* map) : Backtrace(pid, tid, map) {}
|
BacktraceCurrent(pid_t pid, pid_t tid, BacktraceMap* map) : Backtrace(pid, tid, map) {}
|
||||||
virtual ~BacktraceCurrent() {}
|
virtual ~BacktraceCurrent() {}
|
||||||
|
|
||||||
|
|
@ -46,10 +46,10 @@ public:
|
||||||
|
|
||||||
bool Unwind(size_t num_ignore_frames, ucontext_t* ucontext) override;
|
bool Unwind(size_t num_ignore_frames, ucontext_t* ucontext) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool DiscardFrame(const backtrace_frame_data_t& frame);
|
bool DiscardFrame(const backtrace_frame_data_t& frame);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool UnwindThread(size_t num_ignore_frames);
|
bool UnwindThread(size_t num_ignore_frames);
|
||||||
|
|
||||||
virtual bool UnwindFromContext(size_t num_ignore_frames, ucontext_t* ucontext) = 0;
|
virtual bool UnwindFromContext(size_t num_ignore_frames, ucontext_t* ucontext) = 0;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
class BacktraceMap;
|
class BacktraceMap;
|
||||||
|
|
||||||
class BacktracePtrace : public Backtrace {
|
class BacktracePtrace : public Backtrace {
|
||||||
public:
|
public:
|
||||||
BacktracePtrace(pid_t pid, pid_t tid, BacktraceMap* map) : Backtrace(pid, tid, map) {}
|
BacktracePtrace(pid_t pid, pid_t tid, BacktraceMap* map) : Backtrace(pid, tid, map) {}
|
||||||
virtual ~BacktracePtrace() {}
|
virtual ~BacktracePtrace() {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
#include <ucontext.h>
|
#include <ucontext.h>
|
||||||
|
|
||||||
class ThreadEntry {
|
class ThreadEntry {
|
||||||
public:
|
public:
|
||||||
static ThreadEntry* Get(pid_t pid, pid_t tid, bool create = true);
|
static ThreadEntry* Get(pid_t pid, pid_t tid, bool create = true);
|
||||||
|
|
||||||
static void Remove(ThreadEntry* entry);
|
static void Remove(ThreadEntry* entry);
|
||||||
|
|
@ -47,7 +47,7 @@ public:
|
||||||
|
|
||||||
inline ucontext_t* GetUcontext() { return &ucontext_; }
|
inline ucontext_t* GetUcontext() { return &ucontext_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ThreadEntry(pid_t pid, pid_t tid);
|
ThreadEntry(pid_t pid, pid_t tid);
|
||||||
~ThreadEntry();
|
~ThreadEntry();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,23 @@
|
||||||
#define UNW_LOCAL_ONLY
|
#define UNW_LOCAL_ONLY
|
||||||
#include <libunwind.h>
|
#include <libunwind.h>
|
||||||
|
|
||||||
|
#include <android-base/logging.h>
|
||||||
#include <backtrace/Backtrace.h>
|
#include <backtrace/Backtrace.h>
|
||||||
|
|
||||||
#include "BacktraceLog.h"
|
#include "BacktraceLog.h"
|
||||||
#include "UnwindCurrent.h"
|
#include "UnwindCurrent.h"
|
||||||
|
|
||||||
std::string UnwindCurrent::GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) {
|
std::string UnwindCurrent::GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) {
|
||||||
|
if (!initialized_) {
|
||||||
|
// If init local is not called, then trying to get a function name will
|
||||||
|
// fail, so try to initialize first.
|
||||||
|
std::unique_ptr<unw_cursor_t> cursor(new unw_cursor_t);
|
||||||
|
if (unw_init_local(cursor.get(), &context_) < 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
initialized_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
*offset = 0;
|
*offset = 0;
|
||||||
char buf[512];
|
char buf[512];
|
||||||
unw_word_t value;
|
unw_word_t value;
|
||||||
|
|
@ -85,6 +96,7 @@ bool UnwindCurrent::UnwindFromContext(size_t num_ignore_frames, ucontext_t* ucon
|
||||||
error_ = BACKTRACE_UNWIND_ERROR_SETUP_FAILED;
|
error_ = BACKTRACE_UNWIND_ERROR_SETUP_FAILED;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
initialized_ = true;
|
||||||
|
|
||||||
size_t num_frames = 0;
|
size_t num_frames = 0;
|
||||||
do {
|
do {
|
||||||
|
|
@ -124,6 +136,11 @@ bool UnwindCurrent::UnwindFromContext(size_t num_ignore_frames, ucontext_t* ucon
|
||||||
num_frames++;
|
num_frames++;
|
||||||
} else {
|
} else {
|
||||||
num_ignore_frames--;
|
num_ignore_frames--;
|
||||||
|
// Set the number of frames to zero to remove the frame added
|
||||||
|
// above. By definition, if we still have frames to ignore
|
||||||
|
// there should only be one frame in the vector.
|
||||||
|
CHECK(num_frames == 0);
|
||||||
|
frames_.resize(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ret = unw_step (cursor.get());
|
ret = unw_step (cursor.get());
|
||||||
|
|
|
||||||
|
|
@ -32,18 +32,20 @@
|
||||||
#include <libunwind.h>
|
#include <libunwind.h>
|
||||||
|
|
||||||
class UnwindCurrent : public BacktraceCurrent {
|
class UnwindCurrent : public BacktraceCurrent {
|
||||||
public:
|
public:
|
||||||
UnwindCurrent(pid_t pid, pid_t tid, BacktraceMap* map) : BacktraceCurrent(pid, tid, map) {}
|
UnwindCurrent(pid_t pid, pid_t tid, BacktraceMap* map) : BacktraceCurrent(pid, tid, map) {}
|
||||||
virtual ~UnwindCurrent() {}
|
virtual ~UnwindCurrent() {}
|
||||||
|
|
||||||
std::string GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) override;
|
std::string GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void GetUnwContextFromUcontext(const ucontext_t* ucontext);
|
void GetUnwContextFromUcontext(const ucontext_t* ucontext);
|
||||||
|
|
||||||
bool UnwindFromContext(size_t num_ignore_frames, ucontext_t* ucontext) override;
|
bool UnwindFromContext(size_t num_ignore_frames, ucontext_t* ucontext) override;
|
||||||
|
|
||||||
unw_context_t context_;
|
unw_context_t context_;
|
||||||
|
|
||||||
|
bool initialized_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _LIBBACKTRACE_UNWIND_CURRENT_H
|
#endif // _LIBBACKTRACE_UNWIND_CURRENT_H
|
||||||
|
|
|
||||||
|
|
@ -28,28 +28,28 @@
|
||||||
// libunwind.h first then this header.
|
// libunwind.h first then this header.
|
||||||
|
|
||||||
class UnwindMap : public BacktraceMap {
|
class UnwindMap : public BacktraceMap {
|
||||||
public:
|
public:
|
||||||
explicit UnwindMap(pid_t pid);
|
explicit UnwindMap(pid_t pid);
|
||||||
|
|
||||||
unw_map_cursor_t* GetMapCursor() { return &map_cursor_; }
|
unw_map_cursor_t* GetMapCursor() { return &map_cursor_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
unw_map_cursor_t map_cursor_;
|
unw_map_cursor_t map_cursor_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class UnwindMapRemote : public UnwindMap {
|
class UnwindMapRemote : public UnwindMap {
|
||||||
public:
|
public:
|
||||||
explicit UnwindMapRemote(pid_t pid);
|
explicit UnwindMapRemote(pid_t pid);
|
||||||
virtual ~UnwindMapRemote();
|
virtual ~UnwindMapRemote();
|
||||||
|
|
||||||
bool Build() override;
|
bool Build() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool GenerateMap();
|
bool GenerateMap();
|
||||||
};
|
};
|
||||||
|
|
||||||
class UnwindMapLocal : public UnwindMap {
|
class UnwindMapLocal : public UnwindMap {
|
||||||
public:
|
public:
|
||||||
UnwindMapLocal();
|
UnwindMapLocal();
|
||||||
virtual ~UnwindMapLocal();
|
virtual ~UnwindMapLocal();
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ public:
|
||||||
void LockIterator() override { pthread_rwlock_rdlock(&map_lock_); }
|
void LockIterator() override { pthread_rwlock_rdlock(&map_lock_); }
|
||||||
void UnlockIterator() override { pthread_rwlock_unlock(&map_lock_); }
|
void UnlockIterator() override { pthread_rwlock_unlock(&map_lock_); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool GenerateMap();
|
bool GenerateMap();
|
||||||
|
|
||||||
bool map_created_;
|
bool map_created_;
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ UnwindPtrace::~UnwindPtrace() {
|
||||||
_UPT_destroy(upt_info_);
|
_UPT_destroy(upt_info_);
|
||||||
upt_info_ = nullptr;
|
upt_info_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addr_space_) {
|
if (addr_space_) {
|
||||||
// Remove the map from the address space before destroying it.
|
// Remove the map from the address space before destroying it.
|
||||||
// It will be freed in the UnwindMap destructor.
|
// It will be freed in the UnwindMap destructor.
|
||||||
|
|
@ -47,18 +48,14 @@ UnwindPtrace::~UnwindPtrace() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UnwindPtrace::Unwind(size_t num_ignore_frames, ucontext_t* ucontext) {
|
bool UnwindPtrace::Init() {
|
||||||
if (GetMap() == nullptr) {
|
if (upt_info_) {
|
||||||
// Without a map object, we can't do anything.
|
return true;
|
||||||
error_ = BACKTRACE_UNWIND_ERROR_MAP_MISSING;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
error_ = BACKTRACE_UNWIND_NO_ERROR;
|
if (addr_space_) {
|
||||||
|
// If somehow the addr_space_ gets initialized but upt_info_ doesn't,
|
||||||
if (ucontext) {
|
// then that indicates there is some kind of failure.
|
||||||
BACK_LOGW("Unwinding from a specified context not supported yet.");
|
|
||||||
error_ = BACKTRACE_UNWIND_ERROR_UNSUPPORTED_OPERATION;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,6 +76,28 @@ bool UnwindPtrace::Unwind(size_t num_ignore_frames, ucontext_t* ucontext) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnwindPtrace::Unwind(size_t num_ignore_frames, ucontext_t* ucontext) {
|
||||||
|
if (GetMap() == nullptr) {
|
||||||
|
// Without a map object, we can't do anything.
|
||||||
|
error_ = BACKTRACE_UNWIND_ERROR_MAP_MISSING;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_ = BACKTRACE_UNWIND_NO_ERROR;
|
||||||
|
|
||||||
|
if (ucontext) {
|
||||||
|
BACK_LOGW("Unwinding from a specified context not supported yet.");
|
||||||
|
error_ = BACKTRACE_UNWIND_ERROR_UNSUPPORTED_OPERATION;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Init()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
unw_cursor_t cursor;
|
unw_cursor_t cursor;
|
||||||
int ret = unw_init_remote(&cursor, addr_space_, upt_info_);
|
int ret = unw_init_remote(&cursor, addr_space_, upt_info_);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
|
@ -115,10 +134,10 @@ bool UnwindPtrace::Unwind(size_t num_ignore_frames, ucontext_t* ucontext) {
|
||||||
prev->stack_size = frame->sp - prev->sp;
|
prev->stack_size = frame->sp - prev->sp;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame->func_name = GetFunctionName(frame->pc, &frame->func_offset);
|
|
||||||
|
|
||||||
FillInMap(frame->pc, &frame->map);
|
FillInMap(frame->pc, &frame->map);
|
||||||
|
|
||||||
|
frame->func_name = GetFunctionName(frame->pc, &frame->func_offset);
|
||||||
|
|
||||||
num_frames++;
|
num_frames++;
|
||||||
} else {
|
} else {
|
||||||
num_ignore_frames--;
|
num_ignore_frames--;
|
||||||
|
|
@ -130,6 +149,10 @@ bool UnwindPtrace::Unwind(size_t num_ignore_frames, ucontext_t* ucontext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string UnwindPtrace::GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) {
|
std::string UnwindPtrace::GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) {
|
||||||
|
if (!Init()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
*offset = 0;
|
*offset = 0;
|
||||||
char buf[512];
|
char buf[512];
|
||||||
unw_word_t value;
|
unw_word_t value;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
#include "BacktracePtrace.h"
|
#include "BacktracePtrace.h"
|
||||||
|
|
||||||
class UnwindPtrace : public BacktracePtrace {
|
class UnwindPtrace : public BacktracePtrace {
|
||||||
public:
|
public:
|
||||||
UnwindPtrace(pid_t pid, pid_t tid, BacktraceMap* map);
|
UnwindPtrace(pid_t pid, pid_t tid, BacktraceMap* map);
|
||||||
virtual ~UnwindPtrace();
|
virtual ~UnwindPtrace();
|
||||||
|
|
||||||
|
|
@ -38,7 +38,9 @@ public:
|
||||||
|
|
||||||
std::string GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) override;
|
std::string GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool Init();
|
||||||
|
|
||||||
unw_addr_space_t addr_space_;
|
unw_addr_space_t addr_space_;
|
||||||
struct UPT_info* upt_info_;
|
struct UPT_info* upt_info_;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
#include <backtrace/BacktraceMap.h>
|
#include <backtrace/BacktraceMap.h>
|
||||||
|
|
||||||
#include <android-base/stringprintf.h>
|
#include <android-base/stringprintf.h>
|
||||||
|
#include <android-base/unique_fd.h>
|
||||||
#include <cutils/atomic.h>
|
#include <cutils/atomic.h>
|
||||||
#include <cutils/threads.h>
|
#include <cutils/threads.h>
|
||||||
|
|
||||||
|
|
@ -85,13 +86,13 @@ int test_level_one(int, int, int, int, void (*)(void*), void*);
|
||||||
int test_recursive_call(int, void (*)(void*), void*);
|
int test_recursive_call(int, void (*)(void*), void*);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t NanoTime() {
|
static uint64_t NanoTime() {
|
||||||
struct timespec t = { 0, 0 };
|
struct timespec t = { 0, 0 };
|
||||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||||
return static_cast<uint64_t>(t.tv_sec * NS_PER_SEC + t.tv_nsec);
|
return static_cast<uint64_t>(t.tv_sec * NS_PER_SEC + t.tv_nsec);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string DumpFrames(Backtrace* backtrace) {
|
static std::string DumpFrames(Backtrace* backtrace) {
|
||||||
if (backtrace->NumFrames() == 0) {
|
if (backtrace->NumFrames() == 0) {
|
||||||
return " No frames to dump.\n";
|
return " No frames to dump.\n";
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +104,7 @@ std::string DumpFrames(Backtrace* backtrace) {
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaitForStop(pid_t pid) {
|
static void WaitForStop(pid_t pid) {
|
||||||
uint64_t start = NanoTime();
|
uint64_t start = NanoTime();
|
||||||
|
|
||||||
siginfo_t si;
|
siginfo_t si;
|
||||||
|
|
@ -116,7 +117,28 @@ void WaitForStop(pid_t pid) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReadyLevelBacktrace(Backtrace* backtrace) {
|
static void CreateRemoteProcess(pid_t* pid) {
|
||||||
|
if ((*pid = fork()) == 0) {
|
||||||
|
while (true)
|
||||||
|
;
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
ASSERT_NE(-1, *pid);
|
||||||
|
|
||||||
|
ASSERT_TRUE(ptrace(PTRACE_ATTACH, *pid, 0, 0) == 0);
|
||||||
|
|
||||||
|
// Wait for the process to get to a stopping point.
|
||||||
|
WaitForStop(*pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FinishRemoteProcess(pid_t pid) {
|
||||||
|
ASSERT_TRUE(ptrace(PTRACE_DETACH, pid, 0, 0) == 0);
|
||||||
|
|
||||||
|
kill(pid, SIGKILL);
|
||||||
|
ASSERT_EQ(waitpid(pid, nullptr, 0), pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ReadyLevelBacktrace(Backtrace* backtrace) {
|
||||||
// See if test_level_four is in the backtrace.
|
// See if test_level_four is in the backtrace.
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (Backtrace::const_iterator it = backtrace->begin(); it != backtrace->end(); ++it) {
|
for (Backtrace::const_iterator it = backtrace->begin(); it != backtrace->end(); ++it) {
|
||||||
|
|
@ -129,7 +151,7 @@ bool ReadyLevelBacktrace(Backtrace* backtrace) {
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyLevelDump(Backtrace* backtrace) {
|
static void VerifyLevelDump(Backtrace* backtrace) {
|
||||||
ASSERT_GT(backtrace->NumFrames(), static_cast<size_t>(0))
|
ASSERT_GT(backtrace->NumFrames(), static_cast<size_t>(0))
|
||||||
<< DumpFrames(backtrace);
|
<< DumpFrames(backtrace);
|
||||||
ASSERT_LT(backtrace->NumFrames(), static_cast<size_t>(MAX_BACKTRACE_FRAMES))
|
ASSERT_LT(backtrace->NumFrames(), static_cast<size_t>(MAX_BACKTRACE_FRAMES))
|
||||||
|
|
@ -157,7 +179,7 @@ void VerifyLevelDump(Backtrace* backtrace) {
|
||||||
<< DumpFrames(backtrace);
|
<< DumpFrames(backtrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyLevelBacktrace(void*) {
|
static void VerifyLevelBacktrace(void*) {
|
||||||
std::unique_ptr<Backtrace> backtrace(
|
std::unique_ptr<Backtrace> backtrace(
|
||||||
Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD));
|
Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD));
|
||||||
ASSERT_TRUE(backtrace.get() != nullptr);
|
ASSERT_TRUE(backtrace.get() != nullptr);
|
||||||
|
|
@ -167,11 +189,11 @@ void VerifyLevelBacktrace(void*) {
|
||||||
VerifyLevelDump(backtrace.get());
|
VerifyLevelDump(backtrace.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReadyMaxBacktrace(Backtrace* backtrace) {
|
static bool ReadyMaxBacktrace(Backtrace* backtrace) {
|
||||||
return (backtrace->NumFrames() == MAX_BACKTRACE_FRAMES);
|
return (backtrace->NumFrames() == MAX_BACKTRACE_FRAMES);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyMaxDump(Backtrace* backtrace) {
|
static void VerifyMaxDump(Backtrace* backtrace) {
|
||||||
ASSERT_EQ(backtrace->NumFrames(), static_cast<size_t>(MAX_BACKTRACE_FRAMES))
|
ASSERT_EQ(backtrace->NumFrames(), static_cast<size_t>(MAX_BACKTRACE_FRAMES))
|
||||||
<< DumpFrames(backtrace);
|
<< DumpFrames(backtrace);
|
||||||
// Verify that the last frame is our recursive call.
|
// Verify that the last frame is our recursive call.
|
||||||
|
|
@ -179,7 +201,7 @@ void VerifyMaxDump(Backtrace* backtrace) {
|
||||||
<< DumpFrames(backtrace);
|
<< DumpFrames(backtrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyMaxBacktrace(void*) {
|
static void VerifyMaxBacktrace(void*) {
|
||||||
std::unique_ptr<Backtrace> backtrace(
|
std::unique_ptr<Backtrace> backtrace(
|
||||||
Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD));
|
Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD));
|
||||||
ASSERT_TRUE(backtrace.get() != nullptr);
|
ASSERT_TRUE(backtrace.get() != nullptr);
|
||||||
|
|
@ -189,7 +211,7 @@ void VerifyMaxBacktrace(void*) {
|
||||||
VerifyMaxDump(backtrace.get());
|
VerifyMaxDump(backtrace.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadSetState(void* data) {
|
static void ThreadSetState(void* data) {
|
||||||
thread_t* thread = reinterpret_cast<thread_t*>(data);
|
thread_t* thread = reinterpret_cast<thread_t*>(data);
|
||||||
android_atomic_acquire_store(1, &thread->state);
|
android_atomic_acquire_store(1, &thread->state);
|
||||||
volatile int i = 0;
|
volatile int i = 0;
|
||||||
|
|
@ -198,16 +220,7 @@ void ThreadSetState(void* data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyThreadTest(pid_t tid, void (*VerifyFunc)(Backtrace*)) {
|
static bool WaitForNonZero(int32_t* value, uint64_t seconds) {
|
||||||
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(getpid(), tid));
|
|
||||||
ASSERT_TRUE(backtrace.get() != nullptr);
|
|
||||||
ASSERT_TRUE(backtrace->Unwind(0));
|
|
||||||
ASSERT_EQ(BACKTRACE_UNWIND_NO_ERROR, backtrace->GetError());
|
|
||||||
|
|
||||||
VerifyFunc(backtrace.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WaitForNonZero(int32_t* value, uint64_t seconds) {
|
|
||||||
uint64_t start = NanoTime();
|
uint64_t start = NanoTime();
|
||||||
do {
|
do {
|
||||||
if (android_atomic_acquire_load(value)) {
|
if (android_atomic_acquire_load(value)) {
|
||||||
|
|
@ -240,9 +253,8 @@ TEST(libbacktrace, local_trace) {
|
||||||
ASSERT_NE(test_level_one(1, 2, 3, 4, VerifyLevelBacktrace, nullptr), 0);
|
ASSERT_NE(test_level_one(1, 2, 3, 4, VerifyLevelBacktrace, nullptr), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyIgnoreFrames(
|
static void VerifyIgnoreFrames(Backtrace* bt_all, Backtrace* bt_ign1, Backtrace* bt_ign2,
|
||||||
Backtrace* bt_all, Backtrace* bt_ign1,
|
const char* cur_proc) {
|
||||||
Backtrace* bt_ign2, const char* cur_proc) {
|
|
||||||
EXPECT_EQ(bt_all->NumFrames(), bt_ign1->NumFrames() + 1)
|
EXPECT_EQ(bt_all->NumFrames(), bt_ign1->NumFrames() + 1)
|
||||||
<< "All backtrace:\n" << DumpFrames(bt_all) << "Ignore 1 backtrace:\n" << DumpFrames(bt_ign1);
|
<< "All backtrace:\n" << DumpFrames(bt_all) << "Ignore 1 backtrace:\n" << DumpFrames(bt_ign1);
|
||||||
EXPECT_EQ(bt_all->NumFrames(), bt_ign2->NumFrames() + 2)
|
EXPECT_EQ(bt_all->NumFrames(), bt_ign2->NumFrames() + 2)
|
||||||
|
|
@ -266,7 +278,7 @@ void VerifyIgnoreFrames(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyLevelIgnoreFrames(void*) {
|
static void VerifyLevelIgnoreFrames(void*) {
|
||||||
std::unique_ptr<Backtrace> all(
|
std::unique_ptr<Backtrace> all(
|
||||||
Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD));
|
Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD));
|
||||||
ASSERT_TRUE(all.get() != nullptr);
|
ASSERT_TRUE(all.get() != nullptr);
|
||||||
|
|
@ -296,9 +308,8 @@ TEST(libbacktrace, local_max_trace) {
|
||||||
ASSERT_NE(test_recursive_call(MAX_BACKTRACE_FRAMES+10, VerifyMaxBacktrace, nullptr), 0);
|
ASSERT_NE(test_recursive_call(MAX_BACKTRACE_FRAMES+10, VerifyMaxBacktrace, nullptr), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyProcTest(pid_t pid, pid_t tid, bool share_map,
|
static void VerifyProcTest(pid_t pid, pid_t tid, bool share_map, bool (*ReadyFunc)(Backtrace*),
|
||||||
bool (*ReadyFunc)(Backtrace*),
|
void (*VerifyFunc)(Backtrace*)) {
|
||||||
void (*VerifyFunc)(Backtrace*)) {
|
|
||||||
pid_t ptrace_tid;
|
pid_t ptrace_tid;
|
||||||
if (tid < 0) {
|
if (tid < 0) {
|
||||||
ptrace_tid = pid;
|
ptrace_tid = pid;
|
||||||
|
|
@ -376,7 +387,7 @@ TEST(libbacktrace, ptrace_max_trace) {
|
||||||
ASSERT_EQ(waitpid(pid, &status, 0), pid);
|
ASSERT_EQ(waitpid(pid, &status, 0), pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyProcessIgnoreFrames(Backtrace* bt_all) {
|
static void VerifyProcessIgnoreFrames(Backtrace* bt_all) {
|
||||||
std::unique_ptr<Backtrace> ign1(Backtrace::Create(bt_all->Pid(), BACKTRACE_CURRENT_THREAD));
|
std::unique_ptr<Backtrace> ign1(Backtrace::Create(bt_all->Pid(), BACKTRACE_CURRENT_THREAD));
|
||||||
ASSERT_TRUE(ign1.get() != nullptr);
|
ASSERT_TRUE(ign1.get() != nullptr);
|
||||||
ASSERT_TRUE(ign1->Unwind(1));
|
ASSERT_TRUE(ign1->Unwind(1));
|
||||||
|
|
@ -404,12 +415,12 @@ TEST(libbacktrace, ptrace_ignore_frames) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a process with multiple threads and dump all of the threads.
|
// Create a process with multiple threads and dump all of the threads.
|
||||||
void* PtraceThreadLevelRun(void*) {
|
static void* PtraceThreadLevelRun(void*) {
|
||||||
EXPECT_NE(test_level_one(1, 2, 3, 4, nullptr, nullptr), 0);
|
EXPECT_NE(test_level_one(1, 2, 3, 4, nullptr, nullptr), 0);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetThreads(pid_t pid, std::vector<pid_t>* threads) {
|
static void GetThreads(pid_t pid, std::vector<pid_t>* threads) {
|
||||||
// Get the list of tasks.
|
// Get the list of tasks.
|
||||||
char task_path[128];
|
char task_path[128];
|
||||||
snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid);
|
snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid);
|
||||||
|
|
@ -461,11 +472,8 @@ TEST(libbacktrace, ptrace_threads) {
|
||||||
}
|
}
|
||||||
VerifyProcTest(pid, *it, false, ReadyLevelBacktrace, VerifyLevelDump);
|
VerifyProcTest(pid, *it, false, ReadyLevelBacktrace, VerifyLevelDump);
|
||||||
}
|
}
|
||||||
ASSERT_TRUE(ptrace(PTRACE_DETACH, pid, 0, 0) == 0);
|
|
||||||
|
|
||||||
kill(pid, SIGKILL);
|
FinishRemoteProcess(pid);
|
||||||
int status;
|
|
||||||
ASSERT_EQ(waitpid(pid, &status, 0), pid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyLevelThread(void*) {
|
void VerifyLevelThread(void*) {
|
||||||
|
|
@ -481,7 +489,7 @@ TEST(libbacktrace, thread_current_level) {
|
||||||
ASSERT_NE(test_level_one(1, 2, 3, 4, VerifyLevelThread, nullptr), 0);
|
ASSERT_NE(test_level_one(1, 2, 3, 4, VerifyLevelThread, nullptr), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyMaxThread(void*) {
|
static void VerifyMaxThread(void*) {
|
||||||
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(getpid(), gettid()));
|
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(getpid(), gettid()));
|
||||||
ASSERT_TRUE(backtrace.get() != nullptr);
|
ASSERT_TRUE(backtrace.get() != nullptr);
|
||||||
ASSERT_TRUE(backtrace->Unwind(0));
|
ASSERT_TRUE(backtrace->Unwind(0));
|
||||||
|
|
@ -494,7 +502,7 @@ TEST(libbacktrace, thread_current_max) {
|
||||||
ASSERT_NE(test_recursive_call(MAX_BACKTRACE_FRAMES+10, VerifyMaxThread, nullptr), 0);
|
ASSERT_NE(test_recursive_call(MAX_BACKTRACE_FRAMES+10, VerifyMaxThread, nullptr), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void* ThreadLevelRun(void* data) {
|
static void* ThreadLevelRun(void* data) {
|
||||||
thread_t* thread = reinterpret_cast<thread_t*>(data);
|
thread_t* thread = reinterpret_cast<thread_t*>(data);
|
||||||
|
|
||||||
thread->tid = gettid();
|
thread->tid = gettid();
|
||||||
|
|
@ -585,7 +593,7 @@ TEST(libbacktrace, thread_ignore_frames) {
|
||||||
android_atomic_acquire_store(0, &thread_data.state);
|
android_atomic_acquire_store(0, &thread_data.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void* ThreadMaxRun(void* data) {
|
static void* ThreadMaxRun(void* data) {
|
||||||
thread_t* thread = reinterpret_cast<thread_t*>(data);
|
thread_t* thread = reinterpret_cast<thread_t*>(data);
|
||||||
|
|
||||||
thread->tid = gettid();
|
thread->tid = gettid();
|
||||||
|
|
@ -616,7 +624,7 @@ TEST(libbacktrace, thread_max_trace) {
|
||||||
android_atomic_acquire_store(0, &thread_data.state);
|
android_atomic_acquire_store(0, &thread_data.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void* ThreadDump(void* data) {
|
static void* ThreadDump(void* data) {
|
||||||
dump_thread_t* dump = reinterpret_cast<dump_thread_t*>(data);
|
dump_thread_t* dump = reinterpret_cast<dump_thread_t*>(data);
|
||||||
while (true) {
|
while (true) {
|
||||||
if (android_atomic_acquire_load(dump->now)) {
|
if (android_atomic_acquire_load(dump->now)) {
|
||||||
|
|
@ -873,11 +881,9 @@ struct map_test_t {
|
||||||
uintptr_t end;
|
uintptr_t end;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool map_sort(map_test_t i, map_test_t j) {
|
static bool map_sort(map_test_t i, map_test_t j) { return i.start < j.start; }
|
||||||
return i.start < j.start;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VerifyMap(pid_t pid) {
|
static void VerifyMap(pid_t pid) {
|
||||||
char buffer[4096];
|
char buffer[4096];
|
||||||
snprintf(buffer, sizeof(buffer), "/proc/%d/maps", pid);
|
snprintf(buffer, sizeof(buffer), "/proc/%d/maps", pid);
|
||||||
|
|
||||||
|
|
@ -908,29 +914,15 @@ void VerifyMap(pid_t pid) {
|
||||||
|
|
||||||
TEST(libbacktrace, verify_map_remote) {
|
TEST(libbacktrace, verify_map_remote) {
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
|
CreateRemoteProcess(&pid);
|
||||||
if ((pid = fork()) == 0) {
|
|
||||||
while (true) {
|
|
||||||
}
|
|
||||||
_exit(0);
|
|
||||||
}
|
|
||||||
ASSERT_LT(0, pid);
|
|
||||||
|
|
||||||
ASSERT_TRUE(ptrace(PTRACE_ATTACH, pid, 0, 0) == 0);
|
|
||||||
|
|
||||||
// Wait for the process to get to a stopping point.
|
|
||||||
WaitForStop(pid);
|
|
||||||
|
|
||||||
// The maps should match exactly since the forked process has been paused.
|
// The maps should match exactly since the forked process has been paused.
|
||||||
VerifyMap(pid);
|
VerifyMap(pid);
|
||||||
|
|
||||||
ASSERT_TRUE(ptrace(PTRACE_DETACH, pid, 0, 0) == 0);
|
FinishRemoteProcess(pid);
|
||||||
|
|
||||||
kill(pid, SIGKILL);
|
|
||||||
ASSERT_EQ(waitpid(pid, nullptr, 0), pid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitMemory(uint8_t* memory, size_t bytes) {
|
static void InitMemory(uint8_t* memory, size_t bytes) {
|
||||||
for (size_t i = 0; i < bytes; i++) {
|
for (size_t i = 0; i < bytes; i++) {
|
||||||
memory[i] = i;
|
memory[i] = i;
|
||||||
if (memory[i] == '\0') {
|
if (memory[i] == '\0') {
|
||||||
|
|
@ -941,7 +933,7 @@ void InitMemory(uint8_t* memory, size_t bytes) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void* ThreadReadTest(void* data) {
|
static void* ThreadReadTest(void* data) {
|
||||||
thread_t* thread_data = reinterpret_cast<thread_t*>(data);
|
thread_t* thread_data = reinterpret_cast<thread_t*>(data);
|
||||||
|
|
||||||
thread_data->tid = gettid();
|
thread_data->tid = gettid();
|
||||||
|
|
@ -982,7 +974,7 @@ void* ThreadReadTest(void* data) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RunReadTest(Backtrace* backtrace, uintptr_t read_addr) {
|
static void RunReadTest(Backtrace* backtrace, uintptr_t read_addr) {
|
||||||
size_t pagesize = static_cast<size_t>(sysconf(_SC_PAGE_SIZE));
|
size_t pagesize = static_cast<size_t>(sysconf(_SC_PAGE_SIZE));
|
||||||
|
|
||||||
// Create a page of data to use to do quick compares.
|
// Create a page of data to use to do quick compares.
|
||||||
|
|
@ -1043,7 +1035,7 @@ TEST(libbacktrace, thread_read) {
|
||||||
volatile uintptr_t g_ready = 0;
|
volatile uintptr_t g_ready = 0;
|
||||||
volatile uintptr_t g_addr = 0;
|
volatile uintptr_t g_addr = 0;
|
||||||
|
|
||||||
void ForkedReadTest() {
|
static void ForkedReadTest() {
|
||||||
// Create two map pages.
|
// Create two map pages.
|
||||||
size_t pagesize = static_cast<size_t>(sysconf(_SC_PAGE_SIZE));
|
size_t pagesize = static_cast<size_t>(sysconf(_SC_PAGE_SIZE));
|
||||||
uint8_t* memory;
|
uint8_t* memory;
|
||||||
|
|
@ -1117,7 +1109,7 @@ TEST(libbacktrace, process_read) {
|
||||||
ASSERT_TRUE(test_executed);
|
ASSERT_TRUE(test_executed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyFunctionsFound(const std::vector<std::string>& found_functions) {
|
static void VerifyFunctionsFound(const std::vector<std::string>& found_functions) {
|
||||||
// We expect to find these functions in libbacktrace_test. If we don't
|
// We expect to find these functions in libbacktrace_test. If we don't
|
||||||
// find them, that's a bug in the memory read handling code in libunwind.
|
// find them, that's a bug in the memory read handling code in libunwind.
|
||||||
std::list<std::string> expected_functions;
|
std::list<std::string> expected_functions;
|
||||||
|
|
@ -1137,7 +1129,7 @@ void VerifyFunctionsFound(const std::vector<std::string>& found_functions) {
|
||||||
ASSERT_TRUE(expected_functions.empty()) << "Not all functions found in shared library.";
|
ASSERT_TRUE(expected_functions.empty()) << "Not all functions found in shared library.";
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* CopySharedLibrary() {
|
static const char* CopySharedLibrary() {
|
||||||
#if defined(__LP64__)
|
#if defined(__LP64__)
|
||||||
const char* lib_name = "lib64";
|
const char* lib_name = "lib64";
|
||||||
#else
|
#else
|
||||||
|
|
@ -1293,7 +1285,7 @@ TEST(libbacktrace, check_unreadable_elf_remote) {
|
||||||
VerifyFunctionsFound(found_functions);
|
VerifyFunctionsFound(found_functions);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FindFuncFrameInBacktrace(Backtrace* backtrace, uintptr_t test_func, size_t* frame_num) {
|
static bool FindFuncFrameInBacktrace(Backtrace* backtrace, uintptr_t test_func, size_t* frame_num) {
|
||||||
backtrace_map_t map;
|
backtrace_map_t map;
|
||||||
backtrace->FillInMap(test_func, &map);
|
backtrace->FillInMap(test_func, &map);
|
||||||
if (!BacktraceMap::IsValid(map)) {
|
if (!BacktraceMap::IsValid(map)) {
|
||||||
|
|
@ -1312,7 +1304,7 @@ bool FindFuncFrameInBacktrace(Backtrace* backtrace, uintptr_t test_func, size_t*
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyUnreadableElfFrame(Backtrace* backtrace, uintptr_t test_func, size_t frame_num) {
|
static void VerifyUnreadableElfFrame(Backtrace* backtrace, uintptr_t test_func, size_t frame_num) {
|
||||||
ASSERT_LT(backtrace->NumFrames(), static_cast<size_t>(MAX_BACKTRACE_FRAMES))
|
ASSERT_LT(backtrace->NumFrames(), static_cast<size_t>(MAX_BACKTRACE_FRAMES))
|
||||||
<< DumpFrames(backtrace);
|
<< DumpFrames(backtrace);
|
||||||
|
|
||||||
|
|
@ -1324,7 +1316,7 @@ void VerifyUnreadableElfFrame(Backtrace* backtrace, uintptr_t test_func, size_t
|
||||||
ASSERT_LT(diff, 200U) << DumpFrames(backtrace);
|
ASSERT_LT(diff, 200U) << DumpFrames(backtrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerifyUnreadableElfBacktrace(uintptr_t test_func) {
|
static void VerifyUnreadableElfBacktrace(uintptr_t test_func) {
|
||||||
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(BACKTRACE_CURRENT_PROCESS,
|
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(BACKTRACE_CURRENT_PROCESS,
|
||||||
BACKTRACE_CURRENT_THREAD));
|
BACKTRACE_CURRENT_THREAD));
|
||||||
ASSERT_TRUE(backtrace.get() != nullptr);
|
ASSERT_TRUE(backtrace.get() != nullptr);
|
||||||
|
|
@ -1418,12 +1410,38 @@ TEST(libbacktrace, unwind_thread_doesnt_exist) {
|
||||||
ASSERT_EQ(BACKTRACE_UNWIND_ERROR_THREAD_DOESNT_EXIST, backtrace->GetError());
|
ASSERT_EQ(BACKTRACE_UNWIND_ERROR_THREAD_DOESNT_EXIST, backtrace->GetError());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(libbacktrace, local_get_function_name_before_unwind) {
|
||||||
|
std::unique_ptr<Backtrace> backtrace(
|
||||||
|
Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD));
|
||||||
|
ASSERT_TRUE(backtrace.get() != nullptr);
|
||||||
|
|
||||||
|
// Verify that trying to get a function name before doing an unwind works.
|
||||||
|
uintptr_t cur_func_offset = reinterpret_cast<uintptr_t>(&test_level_one) + 1;
|
||||||
|
size_t offset;
|
||||||
|
ASSERT_NE(std::string(""), backtrace->GetFunctionName(cur_func_offset, &offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(libbacktrace, remote_get_function_name_before_unwind) {
|
||||||
|
pid_t pid;
|
||||||
|
CreateRemoteProcess(&pid);
|
||||||
|
|
||||||
|
// Now create an unwind object.
|
||||||
|
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, pid));
|
||||||
|
|
||||||
|
// Verify that trying to get a function name before doing an unwind works.
|
||||||
|
uintptr_t cur_func_offset = reinterpret_cast<uintptr_t>(&test_level_one) + 1;
|
||||||
|
size_t offset;
|
||||||
|
ASSERT_NE(std::string(""), backtrace->GetFunctionName(cur_func_offset, &offset));
|
||||||
|
|
||||||
|
FinishRemoteProcess(pid);
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(ENABLE_PSS_TESTS)
|
#if defined(ENABLE_PSS_TESTS)
|
||||||
#include "GetPss.h"
|
#include "GetPss.h"
|
||||||
|
|
||||||
#define MAX_LEAK_BYTES (32*1024UL)
|
#define MAX_LEAK_BYTES (32*1024UL)
|
||||||
|
|
||||||
void CheckForLeak(pid_t pid, pid_t tid) {
|
static void CheckForLeak(pid_t pid, pid_t tid) {
|
||||||
// Do a few runs to get the PSS stable.
|
// Do a few runs to get the PSS stable.
|
||||||
for (size_t i = 0; i < 100; i++) {
|
for (size_t i = 0; i < 100; i++) {
|
||||||
Backtrace* backtrace = Backtrace::Create(pid, tid);
|
Backtrace* backtrace = Backtrace::Create(pid, tid);
|
||||||
|
|
@ -1472,24 +1490,10 @@ TEST(libbacktrace, check_for_leak_local_thread) {
|
||||||
|
|
||||||
TEST(libbacktrace, check_for_leak_remote) {
|
TEST(libbacktrace, check_for_leak_remote) {
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
|
CreateRemoteProcess(&pid);
|
||||||
if ((pid = fork()) == 0) {
|
|
||||||
while (true) {
|
|
||||||
}
|
|
||||||
_exit(0);
|
|
||||||
}
|
|
||||||
ASSERT_LT(0, pid);
|
|
||||||
|
|
||||||
ASSERT_TRUE(ptrace(PTRACE_ATTACH, pid, 0, 0) == 0);
|
|
||||||
|
|
||||||
// Wait for the process to get to a stopping point.
|
|
||||||
WaitForStop(pid);
|
|
||||||
|
|
||||||
CheckForLeak(pid, BACKTRACE_CURRENT_THREAD);
|
CheckForLeak(pid, BACKTRACE_CURRENT_THREAD);
|
||||||
|
|
||||||
ASSERT_TRUE(ptrace(PTRACE_DETACH, pid, 0, 0) == 0);
|
FinishRemoteProcess(pid);
|
||||||
|
|
||||||
kill(pid, SIGKILL);
|
|
||||||
ASSERT_EQ(waitpid(pid, nullptr, 0), pid);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue