mirror of
https://github.com/gperftools/gperftools
synced 2025-02-16 11:47:07 +00:00
reworked heap leak checker for more portability
In most practical terms, this expands "official" heap leak checker support to Linux/arm64 and Linux/riscv (mips-en and legacy arm are likely to work & pass tests too now). The code is now explicitly Linux-only, without trying to pretend otherwise. Main goal of this change is to finally amputate linux_syscall_support.h, which we historically had trouble maintaining well. Biggest challenge was around thread listing facility which uses clone (ptrace explicitly fails between threads) and that causes difficulties around parent and child tasks sharing errno. linux_syscall_support stuff had special feature to "redirect" errno accesses. But it caused us for more trouble. We switched to regular syscalls, and errno stamping avoidance is now simply via careful programming. A number of other cleanups is made (such us thread finding codes in procfs which clearly was built for some ages old and odd kernels). sem_post/sem_wait synchronization was previously potentially prone to deadlock (if parent died at bad time). We now use pipe pair for this synchronization and it is fully robust.
This commit is contained in:
parent
2186967987
commit
e78238d94d
@ -181,8 +181,6 @@ check_include_file("glob.h" HAVE_GLOB_H) # for heap-profile-table (cleaning up p
|
||||
check_include_file("execinfo.h" HAVE_EXECINFO_H) # for stacktrace? and heapchecker_unittest
|
||||
check_include_file("unwind.h" HAVE_UNWIND_H) # for stacktrace
|
||||
check_include_file("sched.h" HAVE_SCHED_H) # for being nice in our spinlock code
|
||||
check_include_file("sys/prctl.h" HAVE_SYS_PRCTL_H) # for thread_lister (needed by leak-checker)
|
||||
check_include_file("linux/ptrace.h" HAVE_LINUX_PTRACE_H) # also needed by leak-checker
|
||||
check_include_file("sys/syscall.h" HAVE_SYS_SYSCALL_H)
|
||||
check_include_file("sys/socket.h" HAVE_SYS_SOCKET_H) # optional; for forking out to symbolizer
|
||||
check_include_file("sys/wait.h" HAVE_SYS_WAIT_H) # optional; for forking out to symbolizer
|
||||
@ -1053,13 +1051,10 @@ if(GPERFTOOLS_BUILD_HEAP_CHECKER OR GPERFTOOLS_BUILD_HEAP_PROFILER)
|
||||
${LOGGING_INCLUDES}
|
||||
src/addressmap-inl.h
|
||||
src/raw_printer.h
|
||||
src/base/elfcore.h
|
||||
src/base/googleinit.h
|
||||
src/base/linux_syscall_support.h
|
||||
src/base/linuxthreads.h
|
||||
src/base/stl_allocator.h
|
||||
src/base/sysinfo.h
|
||||
src/base/thread_lister.h
|
||||
src/heap-profile-table.h
|
||||
src/heap-profile-stats.h
|
||||
src/maybe_emergency_malloc.h
|
||||
@ -1101,8 +1096,7 @@ if(GPERFTOOLS_BUILD_HEAP_CHECKER OR GPERFTOOLS_BUILD_HEAP_PROFILER)
|
||||
# (Note this is added to libtcmalloc.la, not libtcmalloc_internal.la,
|
||||
# but that's ok; the internal/external distinction is only useful for
|
||||
# cygwin, and cygwin doesn't use HEAP_CHECKER anyway.)
|
||||
set(HEAP_CHECKER_SOURCES src/base/thread_lister.c
|
||||
src/base/linuxthreads.cc
|
||||
set(HEAP_CHECKER_SOURCES src/base/linuxthreads.cc
|
||||
src/heap-checker.cc
|
||||
src/heap-checker-bcad.cc)
|
||||
list(APPEND libtcmalloc_la_SOURCES ${HEAP_CHECKER_SOURCES})
|
||||
|
@ -871,13 +871,10 @@ S_TCMALLOC_INCLUDES = $(S_TCMALLOC_MINIMAL_INCLUDES) \
|
||||
$(LOGGING_INCLUDES) \
|
||||
src/addressmap-inl.h \
|
||||
src/raw_printer.h \
|
||||
src/base/elfcore.h \
|
||||
src/base/googleinit.h \
|
||||
src/base/linux_syscall_support.h \
|
||||
src/base/linuxthreads.h \
|
||||
src/base/stl_allocator.h \
|
||||
src/base/sysinfo.h \
|
||||
src/base/thread_lister.h \
|
||||
src/heap-profile-table.h \
|
||||
src/heap-profile-stats.h \
|
||||
src/maybe_emergency_malloc.h \
|
||||
@ -904,8 +901,7 @@ if WITH_HEAP_CHECKER
|
||||
# (Note this is added to libtcmalloc.la, not libtcmalloc_internal.la,
|
||||
# but that's ok; the internal/external distinction is only useful for
|
||||
# cygwin, and cygwin doesn't use HEAP_CHECKER anyway.)
|
||||
HEAP_CHECKER_SOURCES = src/base/thread_lister.c \
|
||||
src/base/linuxthreads.cc \
|
||||
HEAP_CHECKER_SOURCES = src/base/linuxthreads.cc \
|
||||
src/heap-checker.cc \
|
||||
src/heap-checker-bcad.cc
|
||||
MAYBE_NO_HEAP_CHECK =
|
||||
|
@ -100,9 +100,6 @@
|
||||
|
||||
#cmakedefine USE_LIBUNWIND
|
||||
|
||||
/* Define to 1 if you have the <linux/ptrace.h> header file. */
|
||||
#cmakedefine HAVE_LINUX_PTRACE_H
|
||||
|
||||
/* Define if this is Linux that has SIGEV_THREAD_ID */
|
||||
#cmakedefine01 HAVE_LINUX_SIGEV_THREAD_ID
|
||||
|
||||
@ -161,9 +158,6 @@
|
||||
/* Define to 1 if you have the <sys/malloc.h> header file. */
|
||||
#cmakedefine HAVE_SYS_MALLOC_H
|
||||
|
||||
/* Define to 1 if you have the <sys/prctl.h> header file. */
|
||||
#cmakedefine HAVE_SYS_PRCTL_H
|
||||
|
||||
/* Define to 1 if you have the <sys/resource.h> header file. */
|
||||
#cmakedefine HAVE_SYS_RESOURCE_H
|
||||
|
||||
|
40
configure.ac
40
configure.ac
@ -42,17 +42,19 @@ AX_GENERATE_CHANGELOG
|
||||
# target system supports.
|
||||
default_enable_cpu_profiler=yes
|
||||
default_enable_heap_profiler=yes
|
||||
# heap checker is in practice Linux-only.
|
||||
# heap checker is Linux-only.
|
||||
default_enable_heap_checker=no
|
||||
heap_checker_supported=no
|
||||
default_enable_debugalloc=yes
|
||||
default_enable_minimal=no
|
||||
default_tcmalloc_alignment=16
|
||||
heap_checker_is_default=no
|
||||
need_nanosleep=yes # Used later, to decide if to run ACX_NANOSLEEP
|
||||
case "$host" in
|
||||
*-mingw*) default_enable_minimal=yes; default_enable_debugalloc=no;
|
||||
need_nanosleep=no;;
|
||||
*-cygwin*) default_enable_cpu_profiler=no;;
|
||||
*-linux*) default_enable_heap_checker=yes;;
|
||||
*-linux*) default_enable_heap_checker=yes; heap_checker_supported=yes;;
|
||||
esac
|
||||
|
||||
# Currently only backtrace works on s390 and OSX.
|
||||
@ -88,7 +90,7 @@ AC_ARG_ENABLE([heap-checker],
|
||||
[AS_HELP_STRING([--disable-heap-checker],
|
||||
[do not build the heap checker])],
|
||||
[],
|
||||
[enable_heap_checker="$default_enable_heap_checker"])
|
||||
[enable_heap_checker="$default_enable_heap_checker"; heap_checker_is_default=yes])
|
||||
AC_ARG_ENABLE([debugalloc],
|
||||
[AS_HELP_STRING([--disable-debugalloc],
|
||||
[do not build versions of libs with debugalloc])],
|
||||
@ -156,6 +158,36 @@ case "$with_tcmalloc_alignment" in
|
||||
AC_MSG_WARN([${with_tcmalloc_alignment} bytes not supported, using default tcmalloc allocation alignment.])
|
||||
esac
|
||||
|
||||
AS_IF([test "x$enable_heap_checker" = xyes],
|
||||
[AS_IF([test "x$heap_checker_supported" = xno],
|
||||
[AC_MSG_NOTICE([your system isn't Linux, and I won't build heap checker despite your request])]
|
||||
enable_heap_checker=no)])
|
||||
|
||||
dnl Heap checker is (and has always been) Linux-only, and also now
|
||||
dnl depends on clone support in libc. Most Linux libc's do ship clone()
|
||||
dnl (includes glibc, musl and even bionic), but not all. So we check.
|
||||
AS_IF([test "x$enable_heap_checker" = xyes],
|
||||
[AC_MSG_CHECKING([clone() support])
|
||||
AC_LINK_IFELSE([AC_LANG_PROGRAM([[
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include <sched.h>
|
||||
|
||||
static int fn(void *dummy) { return 0; }
|
||||
|
||||
]],[[
|
||||
char stk[16];
|
||||
return clone(fn, stk, CLONE_VM|CLONE_FS|CLONE_FILES, 0);
|
||||
]])], [AC_MSG_RESULT([yes])], [ dnl clone not found
|
||||
AC_MSG_RESULT([no])
|
||||
enable_heap_checker=no
|
||||
AS_IF([test "x$heap_checker_is_default" = xyes],
|
||||
[AC_MSG_NOTICE([your Linux system won't have heap checker built due to missing clone() support])],
|
||||
[AC_MSG_WARN([you requested heap checker built, but your libc doesn't have clone() support])])
|
||||
])])
|
||||
|
||||
# Checks for programs.
|
||||
AC_PROG_CXX
|
||||
AC_PROG_CC
|
||||
@ -205,8 +237,6 @@ AC_CHECK_HEADERS(glob.h) # for heap-profile-table (cleaning up profiles)
|
||||
AC_CHECK_HEADERS(execinfo.h) # for stacktrace? and heapchecker_unittest
|
||||
AC_CHECK_HEADERS(unwind.h) # for stacktrace
|
||||
AC_CHECK_HEADERS(sched.h) # for being nice in our spinlock code
|
||||
AC_CHECK_HEADERS(sys/prctl.h) # for thread_lister (needed by leak-checker)
|
||||
AC_CHECK_HEADERS(linux/ptrace.h)# also needed by leak-checker
|
||||
AC_CHECK_HEADERS(sys/syscall.h)
|
||||
AC_CHECK_HEADERS(sys/socket.h) # optional; for forking out to symbolizer
|
||||
AC_CHECK_HEADERS(sys/wait.h) # optional; for forking out to symbolizer
|
||||
|
@ -496,7 +496,7 @@ with recording or analyzing the state of the heap.</p>
|
||||
|
||||
<p>In general, leak checking works correctly in the presence of
|
||||
threads. However, thread stack data liveness determination (via
|
||||
<code>base/thread_lister.h</code>) does not work when the program is
|
||||
<code>base/linuxthreads.h</code>) does not work when the program is
|
||||
running under GDB, because the ptrace functionality needed for finding
|
||||
threads is already hooked to by GDB. Conversely, leak checker's
|
||||
ptrace attempts might also interfere with GDB. As a result, GDB can
|
||||
|
@ -1,135 +0,0 @@
|
||||
// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*-
|
||||
/* Copyright (c) 2005-2008, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ---
|
||||
* Author: Markus Gutschke, Carl Crous
|
||||
*/
|
||||
|
||||
#ifndef _ELFCORE_H
|
||||
#define _ELFCORE_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* We currently only support x86-32, x86-64, ARM, MIPS, PPC on Linux.
|
||||
* Porting to other related platforms should not be difficult.
|
||||
*/
|
||||
#if (defined(__i386__) || defined(__x86_64__) || defined(__arm__) || \
|
||||
defined(__mips__) || defined(__PPC__)) && defined(__linux)
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
/* Define the DUMPER symbol to make sure that there is exactly one
|
||||
* core dumper built into the library.
|
||||
*/
|
||||
#define DUMPER "ELF"
|
||||
|
||||
/* By the time that we get a chance to read CPU registers in the
|
||||
* calling thread, they are already in a not particularly useful
|
||||
* state. Besides, there will be multiple frames on the stack that are
|
||||
* just making the core file confusing. To fix this problem, we take a
|
||||
* snapshot of the frame pointer, stack pointer, and instruction
|
||||
* pointer at an earlier time, and then insert these values into the
|
||||
* core file.
|
||||
*/
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
typedef struct i386_regs { /* Normal (non-FPU) CPU registers */
|
||||
#ifdef __x86_64__
|
||||
#define BP rbp
|
||||
#define SP rsp
|
||||
#define IP rip
|
||||
uint64_t r15,r14,r13,r12,rbp,rbx,r11,r10;
|
||||
uint64_t r9,r8,rax,rcx,rdx,rsi,rdi,orig_rax;
|
||||
uint64_t rip,cs,eflags;
|
||||
uint64_t rsp,ss;
|
||||
uint64_t fs_base, gs_base;
|
||||
uint64_t ds,es,fs,gs;
|
||||
#else
|
||||
#define BP ebp
|
||||
#define SP esp
|
||||
#define IP eip
|
||||
uint32_t ebx, ecx, edx, esi, edi, ebp, eax;
|
||||
uint16_t ds, __ds, es, __es;
|
||||
uint16_t fs, __fs, gs, __gs;
|
||||
uint32_t orig_eax, eip;
|
||||
uint16_t cs, __cs;
|
||||
uint32_t eflags, esp;
|
||||
uint16_t ss, __ss;
|
||||
#endif
|
||||
} i386_regs;
|
||||
#elif defined(__arm__)
|
||||
typedef struct arm_regs { /* General purpose registers */
|
||||
#define BP uregs[11] /* Frame pointer */
|
||||
#define SP uregs[13] /* Stack pointer */
|
||||
#define IP uregs[15] /* Program counter */
|
||||
#define LR uregs[14] /* Link register */
|
||||
long uregs[18];
|
||||
} arm_regs;
|
||||
#elif defined(__mips__)
|
||||
typedef struct mips_regs {
|
||||
unsigned long pad[6]; /* Unused padding to match kernel structures */
|
||||
unsigned long uregs[32]; /* General purpose registers. */
|
||||
unsigned long hi; /* Used for multiplication and division. */
|
||||
unsigned long lo;
|
||||
unsigned long cp0_epc; /* Program counter. */
|
||||
unsigned long cp0_badvaddr;
|
||||
unsigned long cp0_status;
|
||||
unsigned long cp0_cause;
|
||||
unsigned long unused;
|
||||
} mips_regs;
|
||||
#elif defined (__PPC__)
|
||||
typedef struct ppc_regs {
|
||||
#define SP uregs[1] /* Stack pointer */
|
||||
#define IP rip /* Program counter */
|
||||
#define LR lr /* Link register */
|
||||
unsigned long uregs[32]; /* General Purpose Registers - r0-r31. */
|
||||
double fpr[32]; /* Floating-Point Registers - f0-f31. */
|
||||
unsigned long rip; /* Program counter. */
|
||||
unsigned long msr;
|
||||
unsigned long ccr;
|
||||
unsigned long lr;
|
||||
unsigned long ctr;
|
||||
unsigned long xeq;
|
||||
unsigned long mq;
|
||||
} ppc_regs;
|
||||
#endif
|
||||
|
||||
#endif // __linux and various arches
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* _ELFCORE_H */
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
||||
// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*-
|
||||
/* Copyright (c) 2005-2007, Google Inc.
|
||||
* Copyright (c) 2023, gperftools Contributors
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -30,37 +31,110 @@
|
||||
*
|
||||
* ---
|
||||
* Author: Markus Gutschke
|
||||
*
|
||||
* Substantial upgrades by Aliaksey Kandratsenka. All bugs are mine.
|
||||
*/
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include "base/linuxthreads.h"
|
||||
|
||||
#ifdef THREADS
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <semaphore.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/linux_syscall_support.h"
|
||||
#include "base/thread_lister.h"
|
||||
#include <atomic>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/logging.h"
|
||||
|
||||
#ifndef CLONE_UNTRACED
|
||||
#define CLONE_UNTRACED 0x00800000
|
||||
#endif
|
||||
|
||||
#ifndef PR_SET_PTRACER
|
||||
#define PR_SET_PTRACER 0x59616d61
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
class SetPTracerSetup {
|
||||
public:
|
||||
~SetPTracerSetup() {
|
||||
if (need_cleanup_) {
|
||||
prctl(PR_SET_PTRACER, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
void Prepare(int clone_pid) {
|
||||
if (prctl(PR_SET_PTRACER, clone_pid, 0, 0, 0) == 0) {
|
||||
need_cleanup_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool need_cleanup_ = false;
|
||||
};
|
||||
|
||||
class UniqueFD {
|
||||
public:
|
||||
explicit UniqueFD(int fd) : fd_(fd) {}
|
||||
|
||||
int ReleaseFD() {
|
||||
int retval = fd_;
|
||||
fd_ = -1;
|
||||
return retval;
|
||||
}
|
||||
|
||||
~UniqueFD() {
|
||||
if (fd_ < 0) {
|
||||
return;
|
||||
}
|
||||
(void)close(fd_);
|
||||
}
|
||||
private:
|
||||
int fd_;
|
||||
};
|
||||
|
||||
template <typename Body>
|
||||
struct SimpleCleanup {
|
||||
const Body body;
|
||||
|
||||
explicit SimpleCleanup(const Body& body) : body(body) {}
|
||||
|
||||
~SimpleCleanup() {
|
||||
body();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Body>
|
||||
SimpleCleanup<Body> MakeSimpleCleanup(const Body& body) {
|
||||
return SimpleCleanup<Body>{body};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/* Synchronous signals that should not be blocked while in the lister thread.
|
||||
*/
|
||||
static const int sync_signals[] = { SIGABRT, SIGILL, SIGFPE, SIGSEGV, SIGBUS,
|
||||
SIGXCPU, SIGXFSZ };
|
||||
static const int sync_signals[] = {
|
||||
SIGABRT, SIGILL,
|
||||
SIGFPE, SIGSEGV, SIGBUS,
|
||||
#ifdef SIGEMT
|
||||
SIGEMT,
|
||||
#endif
|
||||
SIGSYS, SIGTRAP,
|
||||
SIGXCPU, SIGXFSZ };
|
||||
|
||||
/* itoa() is not a standard function, and we cannot safely call printf()
|
||||
* after suspending threads. So, we just implement our own copy. A
|
||||
@ -79,33 +153,33 @@ static char *local_itoa(char *buf, int i) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Wrapper around clone() that runs "fn" on the same stack as the
|
||||
* caller! Unlike fork(), the cloned thread shares the same address space.
|
||||
* The caller must be careful to use only minimal amounts of stack until
|
||||
* the cloned thread has returned.
|
||||
* There is a good chance that the cloned thread and the caller will share
|
||||
* the same copy of errno!
|
||||
*/
|
||||
#ifdef __GNUC__
|
||||
#if __GNUC__ == 3 && __GNUC_MINOR__ >= 1 || __GNUC__ > 3
|
||||
/* Try to force this function into a separate stack frame, and make sure
|
||||
* that arguments are passed on the stack.
|
||||
*/
|
||||
static int local_clone (int (*fn)(void *), void *arg, ...)
|
||||
__attribute__ ((noinline));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* To avoid the gap cross page boundaries, increase by the large parge
|
||||
* size mostly PowerPC system uses. */
|
||||
ATTRIBUTE_NOINLINE
|
||||
static int local_clone (int (*fn)(void *), void *arg) {
|
||||
#ifdef __PPC64__
|
||||
#define CLONE_STACK_SIZE 65536
|
||||
/* To avoid the gap cross page boundaries, increase by the large parge
|
||||
* size mostly PowerPC system uses. */
|
||||
|
||||
// FIXME(alk): I don't really understand why ppc needs this and why
|
||||
// 64k pages matter. I.e. some other architectures have 64k pages,
|
||||
// so should we do the same there?
|
||||
uintptr_t clone_stack_size = 64 << 10;
|
||||
#else
|
||||
#define CLONE_STACK_SIZE 4096
|
||||
uintptr_t clone_stack_size = 4 << 10;
|
||||
#endif
|
||||
|
||||
bool grows_to_low = (&arg < arg);
|
||||
if (grows_to_low) {
|
||||
// Negate clone_stack_size if stack grows to lower addresses
|
||||
// (common for arch-es that matter).
|
||||
clone_stack_size = ~clone_stack_size + 1;
|
||||
}
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__) || defined(__riscv) || defined(__arm__) || defined(__aarch64__)
|
||||
// Sanity check code above. We know that those arch-es grow stack to
|
||||
// lower addresses.
|
||||
CHECK(grows_to_low);
|
||||
#endif
|
||||
|
||||
static int local_clone (int (*fn)(void *), void *arg, ...) {
|
||||
/* Leave 4kB of gap between the callers stack and the new clone. This
|
||||
* should be more than sufficient for the caller to call waitpid() until
|
||||
* the cloned thread terminates.
|
||||
@ -120,8 +194,10 @@ static int local_clone (int (*fn)(void *), void *arg, ...) {
|
||||
* is being debugged. This is OK and the error code will be reported
|
||||
* correctly.
|
||||
*/
|
||||
return sys_clone(fn, (char *)&arg - CLONE_STACK_SIZE,
|
||||
CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_UNTRACED, arg, 0, 0, 0);
|
||||
uintptr_t stack_addr = reinterpret_cast<uintptr_t>(&arg) + clone_stack_size;
|
||||
return clone(fn, reinterpret_cast<void*>(stack_addr),
|
||||
CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_UNTRACED,
|
||||
arg, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
@ -139,46 +215,14 @@ static int local_atoi(const char *s) {
|
||||
return neg ? -n : n;
|
||||
}
|
||||
|
||||
static int ptrace_detach(pid_t pid) {
|
||||
return ptrace(PTRACE_DETACH, pid, nullptr, nullptr);
|
||||
}
|
||||
|
||||
/* Re-runs fn until it doesn't cause EINTR
|
||||
*/
|
||||
#define NO_INTR(fn) do {} while ((fn) < 0 && errno == EINTR)
|
||||
|
||||
|
||||
/* Wrap a class around system calls, in order to give us access to
|
||||
* a private copy of errno. This only works in C++, but it has the
|
||||
* advantage of not needing nested functions, which are a non-standard
|
||||
* language extension.
|
||||
*/
|
||||
#ifdef __cplusplus
|
||||
namespace {
|
||||
class SysCalls {
|
||||
public:
|
||||
#define SYS_CPLUSPLUS
|
||||
#define SYS_ERRNO my_errno
|
||||
#define SYS_INLINE inline
|
||||
#define SYS_PREFIX -1
|
||||
#undef SYS_LINUX_SYSCALL_SUPPORT_H
|
||||
#include "linux_syscall_support.h"
|
||||
SysCalls() : my_errno(0) { }
|
||||
int my_errno;
|
||||
};
|
||||
}
|
||||
#define ERRNO sys.my_errno
|
||||
#else
|
||||
#define ERRNO my_errno
|
||||
#endif
|
||||
|
||||
|
||||
/* Wrapper for open() which is guaranteed to never return EINTR.
|
||||
*/
|
||||
static int c_open(const char *fname, int flags, int mode) {
|
||||
ssize_t rc;
|
||||
NO_INTR(rc = sys_open(fname, flags, mode));
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/* abort() is not safely reentrant, and changes it's behavior each time
|
||||
* it is called. This means, if the main application ever called abort()
|
||||
* we cannot safely call it again. This would happen if we were called
|
||||
@ -198,35 +242,32 @@ static int c_open(const char *fname, int flags, int mode) {
|
||||
* we are running a higher risk, though. So, try to avoid calling
|
||||
* abort() after calling TCMalloc_ResumeAllProcessThreads.
|
||||
*/
|
||||
static volatile int *sig_pids, sig_num_threads, sig_proc, sig_marker;
|
||||
static volatile int *sig_pids, sig_num_threads;
|
||||
|
||||
|
||||
/* Signal handler to help us recover from dying while we are attached to
|
||||
* other threads.
|
||||
*/
|
||||
static void SignalHandler(int signum, siginfo_t *si, void *data) {
|
||||
RAW_LOG(ERROR, "Got fatal signal %d inside ListerThread", signum);
|
||||
|
||||
if (sig_pids != NULL) {
|
||||
if (signum == SIGABRT) {
|
||||
prctl(PR_SET_PDEATHSIG, 0);
|
||||
while (sig_num_threads-- > 0) {
|
||||
/* Not sure if sched_yield is really necessary here, but it does not */
|
||||
/* hurt, and it might be necessary for the same reasons that we have */
|
||||
/* to do so in sys_ptrace_detach(). */
|
||||
sys_sched_yield();
|
||||
sys_ptrace(PTRACE_KILL, sig_pids[sig_num_threads], 0, 0);
|
||||
/* to do so in ptrace_detach(). */
|
||||
sched_yield();
|
||||
ptrace(PTRACE_KILL, sig_pids[sig_num_threads], 0, 0);
|
||||
}
|
||||
} else if (sig_num_threads > 0) {
|
||||
TCMalloc_ResumeAllProcessThreads(sig_num_threads, (int *)sig_pids);
|
||||
}
|
||||
}
|
||||
sig_pids = NULL;
|
||||
if (sig_marker >= 0)
|
||||
NO_INTR(sys_close(sig_marker));
|
||||
sig_marker = -1;
|
||||
if (sig_proc >= 0)
|
||||
NO_INTR(sys_close(sig_proc));
|
||||
sig_proc = -1;
|
||||
|
||||
sys__exit(signum == SIGABRT ? 1 : 2);
|
||||
syscall(SYS_exit, signum == SIGABRT ? 1 : 2);
|
||||
}
|
||||
|
||||
|
||||
@ -237,7 +278,7 @@ static void SignalHandler(int signum, siginfo_t *si, void *data) {
|
||||
static void DirtyStack(size_t amount) {
|
||||
char buf[amount];
|
||||
memset(buf, 0, amount);
|
||||
sys_read(-1, buf, amount);
|
||||
read(-1, buf, amount);
|
||||
}
|
||||
|
||||
|
||||
@ -247,66 +288,48 @@ static void DirtyStack(size_t amount) {
|
||||
|
||||
struct ListerParams {
|
||||
int result, err;
|
||||
pid_t ppid;
|
||||
int start_pipe_rd;
|
||||
int start_pipe_wr;
|
||||
char *altstack_mem;
|
||||
ListAllProcessThreadsCallBack callback;
|
||||
void *parameter;
|
||||
va_list ap;
|
||||
sem_t *lock;
|
||||
int proc_fd;
|
||||
};
|
||||
|
||||
struct kernel_dirent64 { // see man 2 getdents
|
||||
int64_t d_ino; /* 64-bit inode number */
|
||||
int64_t d_off; /* 64-bit offset to next structure */
|
||||
unsigned short d_reclen; /* Size of this dirent */
|
||||
unsigned char d_type; /* File type */
|
||||
char d_name[]; /* Filename (null-terminated) */
|
||||
};
|
||||
|
||||
static void ListerThread(struct ListerParams *args) {
|
||||
static const kernel_dirent64 *BumpDirentPtr(const kernel_dirent64 *ptr, uintptr_t by_bytes) {
|
||||
return reinterpret_cast<kernel_dirent64*>(reinterpret_cast<uintptr_t>(ptr) + by_bytes);
|
||||
}
|
||||
|
||||
static int ListerThread(struct ListerParams *args) {
|
||||
int found_parent = 0;
|
||||
pid_t clone_pid = sys_gettid(), ppid = sys_getppid();
|
||||
char proc_self_task[80], marker_name[48], *marker_path;
|
||||
const char *proc_paths[3];
|
||||
const char *const *proc_path = proc_paths;
|
||||
int proc = -1, marker = -1, num_threads = 0;
|
||||
pid_t clone_pid = syscall(SYS_gettid);
|
||||
int proc = args->proc_fd, num_threads = 0;
|
||||
int max_threads = 0, sig;
|
||||
struct kernel_stat marker_sb, proc_sb;
|
||||
struct stat proc_sb;
|
||||
stack_t altstack;
|
||||
|
||||
/* Wait for parent thread to set appropriate permissions
|
||||
* to allow ptrace activity
|
||||
/* Wait for parent thread to set appropriate permissions to allow
|
||||
* ptrace activity. Note we using pipe pair, so which ensures we
|
||||
* don't sleep past parent's death.
|
||||
*/
|
||||
if (sem_wait(args->lock) < 0) {
|
||||
goto failure;
|
||||
(void)close(args->start_pipe_wr);
|
||||
{
|
||||
char tmp;
|
||||
read(args->start_pipe_rd, &tmp, sizeof(tmp));
|
||||
}
|
||||
|
||||
/* Create "marker" that we can use to detect threads sharing the same
|
||||
* address space and the same file handles. By setting the FD_CLOEXEC flag
|
||||
* we minimize the risk of misidentifying child processes as threads;
|
||||
* and since there is still a race condition, we will filter those out
|
||||
* later, anyway.
|
||||
*/
|
||||
if ((marker = sys_socket(PF_LOCAL, SOCK_DGRAM, 0)) < 0 ||
|
||||
sys_fcntl(marker, F_SETFD, FD_CLOEXEC) < 0) {
|
||||
failure:
|
||||
args->result = -1;
|
||||
args->err = errno;
|
||||
if (marker >= 0)
|
||||
NO_INTR(sys_close(marker));
|
||||
sig_marker = marker = -1;
|
||||
if (proc >= 0)
|
||||
NO_INTR(sys_close(proc));
|
||||
sig_proc = proc = -1;
|
||||
sys__exit(1);
|
||||
}
|
||||
|
||||
/* Compute search paths for finding thread directories in /proc */
|
||||
local_itoa(strrchr(strcpy(proc_self_task, "/proc/"), '\000'), ppid);
|
||||
strcpy(marker_name, proc_self_task);
|
||||
marker_path = marker_name + strlen(marker_name);
|
||||
strcat(proc_self_task, "/task/");
|
||||
proc_paths[0] = proc_self_task; /* /proc/$$/task/ */
|
||||
proc_paths[1] = "/proc/"; /* /proc/ */
|
||||
proc_paths[2] = NULL;
|
||||
|
||||
/* Compute path for marker socket in /proc */
|
||||
local_itoa(strcpy(marker_path, "/fd/") + 4, marker);
|
||||
if (sys_stat(marker_name, &marker_sb) < 0) {
|
||||
goto failure;
|
||||
}
|
||||
// No point in continuing if parent dies before/during ptracing.
|
||||
prctl(PR_SET_PDEATHSIG, SIGKILL);
|
||||
|
||||
/* Catch signals on an alternate pre-allocated stack. This way, we can
|
||||
* safely execute the signal handler even if we ran out of memory.
|
||||
@ -315,7 +338,7 @@ static void ListerThread(struct ListerParams *args) {
|
||||
altstack.ss_sp = args->altstack_mem;
|
||||
altstack.ss_flags = 0;
|
||||
altstack.ss_size = ALT_STACKSIZE;
|
||||
sys_sigaltstack(&altstack, (const stack_t *)NULL);
|
||||
sigaltstack(&altstack, nullptr);
|
||||
|
||||
/* Some kernels forget to wake up traced processes, when the
|
||||
* tracer dies. So, intercept synchronous signals and make sure
|
||||
@ -323,32 +346,23 @@ static void ListerThread(struct ListerParams *args) {
|
||||
* responsibility to ensure that asynchronous signals do not
|
||||
* interfere with this function.
|
||||
*/
|
||||
sig_marker = marker;
|
||||
sig_proc = -1;
|
||||
for (sig = 0; sig < sizeof(sync_signals)/sizeof(*sync_signals); sig++) {
|
||||
struct kernel_sigaction sa;
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction_ = SignalHandler;
|
||||
sys_sigfillset(&sa.sa_mask);
|
||||
sa.sa_sigaction = SignalHandler;
|
||||
sigfillset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_ONSTACK|SA_SIGINFO|SA_RESETHAND;
|
||||
sys_sigaction(sync_signals[sig], &sa, (struct kernel_sigaction *)NULL);
|
||||
sigaction(sync_signals[sig], &sa, nullptr);
|
||||
}
|
||||
|
||||
/* Read process directories in /proc/... */
|
||||
for (;;) {
|
||||
/* Some kernels know about threads, and hide them in "/proc"
|
||||
* (although they are still there, if you know the process
|
||||
* id). Threads are moved into a separate "task" directory. We
|
||||
* check there first, and then fall back on the older naming
|
||||
* convention if necessary.
|
||||
*/
|
||||
if ((sig_proc = proc = c_open(*proc_path, O_RDONLY|O_DIRECTORY, 0)) < 0) {
|
||||
if (*++proc_path != NULL)
|
||||
continue;
|
||||
if (lseek(proc, 0, SEEK_SET) < 0) {
|
||||
goto failure;
|
||||
}
|
||||
if (sys_fstat(proc, &proc_sb) < 0)
|
||||
if (fstat(proc, &proc_sb) < 0) {
|
||||
goto failure;
|
||||
}
|
||||
|
||||
/* Since we are suspending threads, we cannot call any libc
|
||||
* functions that might acquire locks. Most notably, we cannot
|
||||
@ -361,8 +375,9 @@ static void ListerThread(struct ListerParams *args) {
|
||||
* should never need to do so, though, as our guestimate is very
|
||||
* conservative.
|
||||
*/
|
||||
if (max_threads < proc_sb.st_nlink + 100)
|
||||
if (max_threads < proc_sb.st_nlink + 100) {
|
||||
max_threads = proc_sb.st_nlink + 100;
|
||||
}
|
||||
|
||||
/* scope */ {
|
||||
pid_t pids[max_threads];
|
||||
@ -370,13 +385,17 @@ static void ListerThread(struct ListerParams *args) {
|
||||
sig_num_threads = num_threads;
|
||||
sig_pids = pids;
|
||||
for (;;) {
|
||||
struct KERNEL_DIRENT *entry;
|
||||
char buf[4096];
|
||||
ssize_t nbytes = GETDENTS(proc, (struct KERNEL_DIRENT *)buf,
|
||||
sizeof(buf));
|
||||
if (nbytes < 0)
|
||||
// lets make sure to align buf to store kernel_dirent64-s properly.
|
||||
int64_t buf[4096 / sizeof(int64_t)];
|
||||
|
||||
ssize_t nbytes = syscall(SYS_getdents64, proc, buf, sizeof(buf));
|
||||
// fprintf(stderr, "nbytes = %zd\n", nbytes);
|
||||
|
||||
if (nbytes < 0) {
|
||||
goto failure;
|
||||
else if (nbytes == 0) {
|
||||
}
|
||||
|
||||
if (nbytes == 0) {
|
||||
if (added_entries) {
|
||||
/* Need to keep iterating over "/proc" in multiple
|
||||
* passes until we no longer find any more threads. This
|
||||
@ -384,150 +403,145 @@ static void ListerThread(struct ListerParams *args) {
|
||||
* been suspended.
|
||||
*/
|
||||
added_entries = 0;
|
||||
sys_lseek(proc, 0, SEEK_SET);
|
||||
lseek(proc, 0, SEEK_SET);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
for (entry = (struct KERNEL_DIRENT *)buf;
|
||||
entry < (struct KERNEL_DIRENT *)&buf[nbytes];
|
||||
entry = (struct KERNEL_DIRENT *)((char *)entry+entry->d_reclen)) {
|
||||
if (entry->d_ino != 0) {
|
||||
const char *ptr = entry->d_name;
|
||||
pid_t pid;
|
||||
|
||||
/* Some kernels hide threads by preceding the pid with a '.' */
|
||||
if (*ptr == '.')
|
||||
ptr++;
|
||||
const kernel_dirent64 *entry = reinterpret_cast<kernel_dirent64*>(buf);
|
||||
const kernel_dirent64 *end = BumpDirentPtr(entry, nbytes);
|
||||
|
||||
/* If the directory is not numeric, it cannot be a
|
||||
* process/thread
|
||||
for (;entry < end; entry = BumpDirentPtr(entry, entry->d_reclen)) {
|
||||
if (entry->d_ino == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *ptr = entry->d_name;
|
||||
// fprintf(stderr, "name: %s\n", ptr);
|
||||
pid_t pid;
|
||||
|
||||
/* Some kernels hide threads by preceding the pid with a '.' */
|
||||
if (*ptr == '.')
|
||||
ptr++;
|
||||
|
||||
/* If the directory is not numeric, it cannot be a
|
||||
* process/thread
|
||||
*/
|
||||
if (*ptr < '0' || *ptr > '9')
|
||||
continue;
|
||||
pid = local_atoi(ptr);
|
||||
// fprintf(stderr, "pid = %d (%d)\n", pid, getpid());
|
||||
|
||||
if (!pid || pid == clone_pid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Attach (and suspend) all threads */
|
||||
long i, j;
|
||||
|
||||
/* Found one of our threads, make sure it is no duplicate */
|
||||
for (i = 0; i < num_threads; i++) {
|
||||
/* Linear search is slow, but should not matter much for
|
||||
* the typically small number of threads.
|
||||
*/
|
||||
if (*ptr < '0' || *ptr > '9')
|
||||
continue;
|
||||
pid = local_atoi(ptr);
|
||||
|
||||
/* Attach (and suspend) all threads */
|
||||
if (pid && pid != clone_pid) {
|
||||
struct kernel_stat tmp_sb;
|
||||
char fname[entry->d_reclen + 48];
|
||||
strcat(strcat(strcpy(fname, "/proc/"),
|
||||
entry->d_name), marker_path);
|
||||
|
||||
/* Check if the marker is identical to the one we created */
|
||||
if (sys_stat(fname, &tmp_sb) >= 0 &&
|
||||
marker_sb.st_ino == tmp_sb.st_ino) {
|
||||
long i, j;
|
||||
|
||||
/* Found one of our threads, make sure it is no duplicate */
|
||||
for (i = 0; i < num_threads; i++) {
|
||||
/* Linear search is slow, but should not matter much for
|
||||
* the typically small number of threads.
|
||||
*/
|
||||
if (pids[i] == pid) {
|
||||
/* Found a duplicate; most likely on second pass */
|
||||
goto next_entry;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check whether data structure needs growing */
|
||||
if (num_threads >= max_threads) {
|
||||
/* Back to square one, this time with more memory */
|
||||
NO_INTR(sys_close(proc));
|
||||
goto detach_threads;
|
||||
}
|
||||
|
||||
/* Attaching to thread suspends it */
|
||||
pids[num_threads++] = pid;
|
||||
sig_num_threads = num_threads;
|
||||
if (sys_ptrace(PTRACE_ATTACH, pid, (void *)0,
|
||||
(void *)0) < 0) {
|
||||
/* If operation failed, ignore thread. Maybe it
|
||||
* just died? There might also be a race
|
||||
* condition with a concurrent core dumper or
|
||||
* with a debugger. In that case, we will just
|
||||
* make a best effort, rather than failing
|
||||
* entirely.
|
||||
*/
|
||||
num_threads--;
|
||||
sig_num_threads = num_threads;
|
||||
goto next_entry;
|
||||
}
|
||||
while (sys_waitpid(pid, (int *)0, __WALL) < 0) {
|
||||
if (errno != EINTR) {
|
||||
sys_ptrace_detach(pid);
|
||||
num_threads--;
|
||||
sig_num_threads = num_threads;
|
||||
goto next_entry;
|
||||
}
|
||||
}
|
||||
|
||||
if (sys_ptrace(PTRACE_PEEKDATA, pid, &i, &j) || i++ != j ||
|
||||
sys_ptrace(PTRACE_PEEKDATA, pid, &i, &j) || i != j) {
|
||||
/* Address spaces are distinct, even though both
|
||||
* processes show the "marker". This is probably
|
||||
* a forked child process rather than a thread.
|
||||
*/
|
||||
sys_ptrace_detach(pid);
|
||||
num_threads--;
|
||||
sig_num_threads = num_threads;
|
||||
} else {
|
||||
found_parent |= pid == ppid;
|
||||
added_entries++;
|
||||
}
|
||||
}
|
||||
if (pids[i] == pid) {
|
||||
/* Found a duplicate; most likely on second pass */
|
||||
goto next_entry;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check whether data structure needs growing */
|
||||
if (num_threads >= max_threads) {
|
||||
/* Back to square one, this time with more memory */
|
||||
goto detach_threads;
|
||||
}
|
||||
|
||||
/* Attaching to thread suspends it */
|
||||
pids[num_threads++] = pid;
|
||||
sig_num_threads = num_threads;
|
||||
|
||||
if (ptrace(PTRACE_ATTACH, pid, (void *)0,
|
||||
(void *)0) < 0) {
|
||||
/* If operation failed, ignore thread. Maybe it
|
||||
* just died? There might also be a race
|
||||
* condition with a concurrent core dumper or
|
||||
* with a debugger. In that case, we will just
|
||||
* make a best effort, rather than failing
|
||||
* entirely.
|
||||
*/
|
||||
num_threads--;
|
||||
sig_num_threads = num_threads;
|
||||
goto next_entry;
|
||||
}
|
||||
while (waitpid(pid, (int *)0, __WALL) < 0) {
|
||||
if (errno != EINTR) {
|
||||
ptrace_detach(pid);
|
||||
num_threads--;
|
||||
sig_num_threads = num_threads;
|
||||
goto next_entry;
|
||||
}
|
||||
}
|
||||
|
||||
if (syscall(SYS_ptrace, PTRACE_PEEKDATA, pid, &i, &j) || i++ != j ||
|
||||
syscall(SYS_ptrace, PTRACE_PEEKDATA, pid, &i, &j) || i != j) {
|
||||
/* Address spaces are distinct. This is probably
|
||||
* a forked child process rather than a thread.
|
||||
*/
|
||||
ptrace_detach(pid);
|
||||
num_threads--;
|
||||
sig_num_threads = num_threads;
|
||||
goto next_entry;
|
||||
}
|
||||
|
||||
found_parent |= pid == args->ppid;
|
||||
added_entries++;
|
||||
|
||||
next_entry:;
|
||||
}
|
||||
}
|
||||
NO_INTR(sys_close(proc));
|
||||
sig_proc = proc = -1;
|
||||
} // entries iterations loop
|
||||
} // getdents loop
|
||||
|
||||
/* If we failed to find any threads, try looking somewhere else in
|
||||
* /proc. Maybe, threads are reported differently on this system.
|
||||
/* If we never found the parent process, something is very wrong.
|
||||
* Most likely, we are running in debugger. Any attempt to operate
|
||||
* on the threads would be very incomplete. Let's just report an
|
||||
* error to the caller.
|
||||
*/
|
||||
if (num_threads > 1 || !*++proc_path) {
|
||||
NO_INTR(sys_close(marker));
|
||||
sig_marker = marker = -1;
|
||||
|
||||
/* If we never found the parent process, something is very wrong.
|
||||
* Most likely, we are running in debugger. Any attempt to operate
|
||||
* on the threads would be very incomplete. Let's just report an
|
||||
* error to the caller.
|
||||
*/
|
||||
if (!found_parent) {
|
||||
TCMalloc_ResumeAllProcessThreads(num_threads, pids);
|
||||
sys__exit(3);
|
||||
}
|
||||
|
||||
/* Now we are ready to call the callback,
|
||||
* which takes care of resuming the threads for us.
|
||||
*/
|
||||
args->result = args->callback(args->parameter, num_threads,
|
||||
pids, args->ap);
|
||||
args->err = errno;
|
||||
|
||||
/* Callback should have resumed threads, but better safe than sorry */
|
||||
if (TCMalloc_ResumeAllProcessThreads(num_threads, pids)) {
|
||||
/* Callback forgot to resume at least one thread, report error */
|
||||
args->err = EINVAL;
|
||||
args->result = -1;
|
||||
}
|
||||
|
||||
sys__exit(0);
|
||||
if (!found_parent) {
|
||||
TCMalloc_ResumeAllProcessThreads(num_threads, pids);
|
||||
return 3;
|
||||
}
|
||||
|
||||
/* Now we are ready to call the callback,
|
||||
* which takes care of resuming the threads for us.
|
||||
*/
|
||||
args->result = args->callback(args->parameter, num_threads,
|
||||
pids, args->ap);
|
||||
args->err = errno;
|
||||
|
||||
/* Callback should have resumed threads, but better safe than sorry */
|
||||
if (TCMalloc_ResumeAllProcessThreads(num_threads, pids)) {
|
||||
/* Callback forgot to resume at least one thread, report error */
|
||||
args->err = EINVAL;
|
||||
args->result = -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
detach_threads:
|
||||
/* Resume all threads prior to retrying the operation */
|
||||
/* Resume all threads prior to retrying the operation */
|
||||
TCMalloc_ResumeAllProcessThreads(num_threads, pids);
|
||||
sig_pids = NULL;
|
||||
num_threads = 0;
|
||||
sig_num_threads = num_threads;
|
||||
max_threads += 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // pids[max_threads] scope
|
||||
} // for (;;)
|
||||
|
||||
failure:
|
||||
args->result = -1;
|
||||
args->err = errno;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* This function gets the list of all linux threads of the current process
|
||||
* passes them to the 'callback' along with the 'parameter' pointer; at the
|
||||
@ -553,9 +567,26 @@ int TCMalloc_ListAllProcessThreads(void *parameter,
|
||||
char altstack_mem[ALT_STACKSIZE];
|
||||
struct ListerParams args;
|
||||
pid_t clone_pid;
|
||||
int dumpable = 1, sig;
|
||||
struct kernel_sigset_t sig_blocked, sig_old;
|
||||
sem_t lock;
|
||||
int dumpable = 1;
|
||||
int need_sigprocmask = 0;
|
||||
sigset_t sig_blocked, sig_old;
|
||||
int status, rc;
|
||||
|
||||
SetPTracerSetup ptracer_setup;
|
||||
|
||||
auto cleanup = MakeSimpleCleanup([&] () {
|
||||
int old_errno = errno;
|
||||
|
||||
if (need_sigprocmask) {
|
||||
sigprocmask(SIG_SETMASK, &sig_old, nullptr);
|
||||
}
|
||||
|
||||
if (!dumpable) {
|
||||
prctl(PR_SET_DUMPABLE, dumpable);
|
||||
}
|
||||
|
||||
errno = old_errno;
|
||||
});
|
||||
|
||||
va_start(args.ap, callback);
|
||||
|
||||
@ -575,115 +606,116 @@ int TCMalloc_ListAllProcessThreads(void *parameter,
|
||||
/* Make this process "dumpable". This is necessary in order to ptrace()
|
||||
* after having called setuid().
|
||||
*/
|
||||
dumpable = sys_prctl(PR_GET_DUMPABLE, 0);
|
||||
if (!dumpable)
|
||||
sys_prctl(PR_SET_DUMPABLE, 1);
|
||||
dumpable = prctl(PR_GET_DUMPABLE, 0);
|
||||
if (!dumpable) {
|
||||
prctl(PR_SET_DUMPABLE, 1);
|
||||
}
|
||||
|
||||
/* Fill in argument block for dumper thread */
|
||||
args.result = -1;
|
||||
args.err = 0;
|
||||
args.ppid = getpid();
|
||||
args.altstack_mem = altstack_mem;
|
||||
args.parameter = parameter;
|
||||
args.callback = callback;
|
||||
args.lock = &lock;
|
||||
|
||||
NO_INTR(args.proc_fd = open("/proc/self/task/", O_RDONLY|O_DIRECTORY|O_CLOEXEC));
|
||||
UniqueFD proc_closer{args.proc_fd};
|
||||
|
||||
if (args.proc_fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int pipefds[2];
|
||||
if (pipe2(pipefds, O_CLOEXEC)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
UniqueFD pipe_rd_closer{pipefds[0]};
|
||||
UniqueFD pipe_wr_closer{pipefds[1]};
|
||||
|
||||
args.start_pipe_rd = pipefds[0];
|
||||
args.start_pipe_wr = pipefds[1];
|
||||
|
||||
/* Before cloning the thread lister, block all asynchronous signals, as we */
|
||||
/* are not prepared to handle them. */
|
||||
sys_sigfillset(&sig_blocked);
|
||||
for (sig = 0; sig < sizeof(sync_signals)/sizeof(*sync_signals); sig++) {
|
||||
sys_sigdelset(&sig_blocked, sync_signals[sig]);
|
||||
sigfillset(&sig_blocked);
|
||||
for (int sig = 0; sig < sizeof(sync_signals)/sizeof(*sync_signals); sig++) {
|
||||
sigdelset(&sig_blocked, sync_signals[sig]);
|
||||
}
|
||||
if (sys_sigprocmask(SIG_BLOCK, &sig_blocked, &sig_old)) {
|
||||
args.err = errno;
|
||||
args.result = -1;
|
||||
goto failed;
|
||||
if (sigprocmask(SIG_BLOCK, &sig_blocked, &sig_old)) {
|
||||
return -1;
|
||||
}
|
||||
need_sigprocmask = 1;
|
||||
|
||||
/* After cloning, both the parent and the child share the same
|
||||
* instance of errno. We deal with this by being very
|
||||
* careful. Specifically, child immediately calls into sem_wait
|
||||
* which never fails (cannot even EINTR), so doesn't touch errno.
|
||||
*
|
||||
* Parent sets up PR_SET_PTRACER prctl (if it fails, which usually
|
||||
* doesn't happen, we ignore that failure). Then parent does close
|
||||
* on write side of start pipe. After that child runs complex code,
|
||||
* including arbitrary callback. So parent avoids screwing with
|
||||
* errno by immediately calling waitpid with async signals disabled.
|
||||
*
|
||||
* I.e. errno is parent's up until close below. Then errno belongs
|
||||
* to child up until it exits.
|
||||
*/
|
||||
clone_pid = local_clone((int (*)(void *))ListerThread, &args);
|
||||
if (clone_pid < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* scope */ {
|
||||
/* After cloning, both the parent and the child share the same instance
|
||||
* of errno. We must make sure that at least one of these processes
|
||||
* (in our case, the parent) uses modified syscall macros that update
|
||||
* a local copy of errno, instead.
|
||||
*/
|
||||
#ifdef __cplusplus
|
||||
#define sys0_sigprocmask sys.sigprocmask
|
||||
#define sys0_waitpid sys.waitpid
|
||||
SysCalls sys;
|
||||
#else
|
||||
int my_errno;
|
||||
#define SYS_ERRNO my_errno
|
||||
#define SYS_INLINE inline
|
||||
#define SYS_PREFIX 0
|
||||
#undef SYS_LINUX_SYSCALL_SUPPORT_H
|
||||
#include "linux_syscall_support.h"
|
||||
#endif
|
||||
/* Most Linux kernels in the wild have Yama LSM enabled, so
|
||||
* requires us to explicitly give permission for child to ptrace
|
||||
* us. See man 2 ptrace for details. This then requires us to
|
||||
* synchronize with the child (see close on start pipe
|
||||
* below). I.e. so that child doesn't start ptracing before we've
|
||||
* completed this prctl call.
|
||||
*/
|
||||
ptracer_setup.Prepare(clone_pid);
|
||||
|
||||
/* Lock before clone so that parent can set
|
||||
* ptrace permissions (if necessary) prior
|
||||
* to ListerThread actually executing
|
||||
*/
|
||||
if (sem_init(&lock, 0, 0) == 0) {
|
||||
/* Closing write side of pipe works like releasing the lock. It
|
||||
* allows the ListerThread to run past read() call on read side of
|
||||
* pipe and ptrace us.
|
||||
*/
|
||||
close(pipe_wr_closer.ReleaseFD());
|
||||
|
||||
int clone_errno;
|
||||
clone_pid = local_clone((int (*)(void *))ListerThread, &args);
|
||||
clone_errno = errno;
|
||||
/* So here child runs (see ListerThread), it finds and ptraces all
|
||||
* threads, runs whatever callback is setup and then
|
||||
* detaches/resumes everything. In any case we wait for child's
|
||||
* completion to gather status and synchronize everything. */
|
||||
|
||||
sys_sigprocmask(SIG_SETMASK, &sig_old, &sig_old);
|
||||
rc = waitpid(clone_pid, &status, __WALL);
|
||||
|
||||
if (clone_pid >= 0) {
|
||||
#ifdef PR_SET_PTRACER
|
||||
/* In newer versions of glibc permission must explicitly
|
||||
* be given to allow for ptrace.
|
||||
*/
|
||||
prctl(PR_SET_PTRACER, clone_pid, 0, 0, 0);
|
||||
#endif
|
||||
/* Releasing the lock here allows the
|
||||
* ListerThread to execute and ptrace us.
|
||||
*/
|
||||
sem_post(&lock);
|
||||
int status, rc;
|
||||
while ((rc = sys0_waitpid(clone_pid, &status, __WALL)) < 0 &&
|
||||
ERRNO == EINTR) {
|
||||
/* Keep waiting */
|
||||
}
|
||||
if (rc < 0) {
|
||||
args.err = ERRNO;
|
||||
args.result = -1;
|
||||
} else if (WIFEXITED(status)) {
|
||||
switch (WEXITSTATUS(status)) {
|
||||
case 0: break; /* Normal process termination */
|
||||
case 2: args.err = EFAULT; /* Some fault (e.g. SIGSEGV) detected */
|
||||
args.result = -1;
|
||||
break;
|
||||
case 3: args.err = EPERM; /* Process is already being traced */
|
||||
args.result = -1;
|
||||
break;
|
||||
default:args.err = ECHILD; /* Child died unexpectedly */
|
||||
args.result = -1;
|
||||
break;
|
||||
}
|
||||
} else if (!WIFEXITED(status)) {
|
||||
args.err = EFAULT; /* Terminated due to an unhandled signal*/
|
||||
args.result = -1;
|
||||
}
|
||||
sem_destroy(&lock);
|
||||
} else {
|
||||
args.result = -1;
|
||||
args.err = clone_errno;
|
||||
}
|
||||
} else {
|
||||
args.result = -1;
|
||||
args.err = errno;
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR) {
|
||||
RAW_LOG(FATAL, "BUG: EINTR from waitpid shouldn't be possible!");
|
||||
}
|
||||
// Any error waiting for child is sign of some bug, so abort
|
||||
// asap. Continuing is unsafe anyways with child potentially writing to our
|
||||
// stack.
|
||||
RAW_LOG(FATAL, "BUG: waitpid inside TCMalloc_ListAllProcessThreads cannot fail, but it did. Raw errno: %d\n", errno);
|
||||
} else if (WIFEXITED(status)) {
|
||||
errno = args.err;
|
||||
switch (WEXITSTATUS(status)) {
|
||||
case 0: break; /* Normal process termination */
|
||||
case 2: args.err = EFAULT; /* Some fault (e.g. SIGSEGV) detected */
|
||||
args.result = -1;
|
||||
break;
|
||||
case 3: args.err = EPERM; /* Process is already being traced */
|
||||
args.result = -1;
|
||||
break;
|
||||
default:args.err = ECHILD; /* Child died unexpectedly */
|
||||
args.result = -1;
|
||||
break;
|
||||
}
|
||||
} else if (!WIFEXITED(status)) {
|
||||
args.err = EFAULT; /* Terminated due to an unhandled signal*/
|
||||
args.result = -1;
|
||||
}
|
||||
|
||||
/* Restore the "dumpable" state of the process */
|
||||
failed:
|
||||
if (!dumpable)
|
||||
sys_prctl(PR_SET_DUMPABLE, dumpable);
|
||||
|
||||
va_end(args.ap);
|
||||
|
||||
errno = args.err;
|
||||
return args.result;
|
||||
}
|
||||
@ -696,12 +728,7 @@ failed:
|
||||
int TCMalloc_ResumeAllProcessThreads(int num_threads, pid_t *thread_pids) {
|
||||
int detached_at_least_one = 0;
|
||||
while (num_threads-- > 0) {
|
||||
detached_at_least_one |= sys_ptrace_detach(thread_pids[num_threads]) >= 0;
|
||||
detached_at_least_one |= (ptrace_detach(thread_pids[num_threads]) >= 0);
|
||||
}
|
||||
return detached_at_least_one;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
@ -35,21 +35,41 @@
|
||||
#ifndef _LINUXTHREADS_H
|
||||
#define _LINUXTHREADS_H
|
||||
|
||||
/* Include thread_lister.h to get the interface that we implement for linux.
|
||||
*/
|
||||
#include <stdarg.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/* We currently only support certain platforms on Linux. Porting to other
|
||||
* related platforms should not be difficult.
|
||||
*/
|
||||
#if (defined(__i386__) || defined(__x86_64__) || defined(__arm__) || \
|
||||
defined(__mips__) || defined(__PPC__) || defined(__aarch64__) || defined(__loongarch64) || \
|
||||
defined(__s390__)) && defined(__linux)
|
||||
typedef int (*ListAllProcessThreadsCallBack)(void *parameter,
|
||||
int num_threads,
|
||||
pid_t *thread_pids,
|
||||
va_list ap);
|
||||
|
||||
/* Define the THREADS symbol to make sure that there is exactly one core dumper
|
||||
* built into the library.
|
||||
/* This function gets the list of all linux threads of the current process
|
||||
* passes them to the 'callback' along with the 'parameter' pointer; at the
|
||||
* call back call time all the threads are paused via
|
||||
* PTRACE_ATTACH.
|
||||
* The callback is executed from a separate thread which shares only the
|
||||
* address space, the filesystem, and the filehandles with the caller. Most
|
||||
* notably, it does not share the same pid and ppid; and if it terminates,
|
||||
* the rest of the application is still there. 'callback' is supposed to do
|
||||
* or arrange for TCMalloc_ResumeAllProcessThreads. This happens automatically, if
|
||||
* the thread raises a synchronous signal (e.g. SIGSEGV); asynchronous
|
||||
* signals are blocked. If the 'callback' decides to unblock them, it must
|
||||
* ensure that they cannot terminate the application, or that
|
||||
* TCMalloc_ResumeAllProcessThreads will get called.
|
||||
* It is an error for the 'callback' to make any library calls that could
|
||||
* acquire locks. Most notably, this means that most system calls have to
|
||||
* avoid going through libc. Also, this means that it is not legal to call
|
||||
* exit() or abort().
|
||||
* We return -1 on error and the return value of 'callback' on success.
|
||||
*/
|
||||
#define THREADS "Linux /proc"
|
||||
int TCMalloc_ListAllProcessThreads(void *parameter,
|
||||
ListAllProcessThreadsCallBack callback, ...);
|
||||
|
||||
#endif
|
||||
/* This function resumes the list of all linux threads that
|
||||
* TCMalloc_ListAllProcessThreads pauses before giving to its
|
||||
* callback. The function returns non-zero if at least one thread was
|
||||
* suspended and has now been resumed.
|
||||
*/
|
||||
int TCMalloc_ResumeAllProcessThreads(int num_threads, pid_t *thread_pids);
|
||||
|
||||
#endif /* _LINUXTHREADS_H */
|
||||
|
@ -1,84 +0,0 @@
|
||||
/* -*- Mode: c; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
/* Copyright (c) 2005-2007, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ---
|
||||
* Author: Markus Gutschke
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "base/thread_lister.h"
|
||||
|
||||
#include <stdio.h> /* needed for NULL on some powerpc platforms (?!) */
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h> /* for getpid */
|
||||
|
||||
#ifdef HAVE_SYS_PRCTL
|
||||
# include <sys/prctl.h>
|
||||
#endif
|
||||
|
||||
#include "base/linuxthreads.h"
|
||||
/* Include other thread listers here that define THREADS macro
|
||||
* only when they can provide a good implementation.
|
||||
*/
|
||||
|
||||
#ifndef THREADS
|
||||
|
||||
/* Default trivial thread lister for single-threaded applications,
|
||||
* or if the multi-threading code has not been ported, yet.
|
||||
*/
|
||||
|
||||
int TCMalloc_ListAllProcessThreads(void *parameter,
|
||||
ListAllProcessThreadsCallBack callback, ...) {
|
||||
int rc;
|
||||
va_list ap;
|
||||
pid_t pid;
|
||||
|
||||
#ifdef HAVE_SYS_PRCTL
|
||||
int dumpable = prctl(PR_GET_DUMPABLE, 0);
|
||||
if (!dumpable)
|
||||
prctl(PR_SET_DUMPABLE, 1);
|
||||
#endif
|
||||
va_start(ap, callback);
|
||||
pid = getpid();
|
||||
rc = callback(parameter, 1, &pid, ap);
|
||||
va_end(ap);
|
||||
#ifdef HAVE_SYS_PRCTL
|
||||
if (!dumpable)
|
||||
prctl(PR_SET_DUMPABLE, 0);
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
|
||||
int TCMalloc_ResumeAllProcessThreads(int num_threads, pid_t *thread_pids) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* ifndef THREADS */
|
@ -1,83 +0,0 @@
|
||||
/* -*- Mode: c; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
/* Copyright (c) 2005-2007, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ---
|
||||
* Author: Markus Gutschke
|
||||
*/
|
||||
|
||||
#ifndef _THREAD_LISTER_H
|
||||
#define _THREAD_LISTER_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef int (*ListAllProcessThreadsCallBack)(void *parameter,
|
||||
int num_threads,
|
||||
pid_t *thread_pids,
|
||||
va_list ap);
|
||||
|
||||
/* This function gets the list of all linux threads of the current process
|
||||
* passes them to the 'callback' along with the 'parameter' pointer; at the
|
||||
* call back call time all the threads are paused via
|
||||
* PTRACE_ATTACH.
|
||||
* The callback is executed from a separate thread which shares only the
|
||||
* address space, the filesystem, and the filehandles with the caller. Most
|
||||
* notably, it does not share the same pid and ppid; and if it terminates,
|
||||
* the rest of the application is still there. 'callback' is supposed to do
|
||||
* or arrange for TCMalloc_ResumeAllProcessThreads. This happens automatically, if
|
||||
* the thread raises a synchronous signal (e.g. SIGSEGV); asynchronous
|
||||
* signals are blocked. If the 'callback' decides to unblock them, it must
|
||||
* ensure that they cannot terminate the application, or that
|
||||
* TCMalloc_ResumeAllProcessThreads will get called.
|
||||
* It is an error for the 'callback' to make any library calls that could
|
||||
* acquire locks. Most notably, this means that most system calls have to
|
||||
* avoid going through libc. Also, this means that it is not legal to call
|
||||
* exit() or abort().
|
||||
* We return -1 on error and the return value of 'callback' on success.
|
||||
*/
|
||||
int TCMalloc_ListAllProcessThreads(void *parameter,
|
||||
ListAllProcessThreadsCallBack callback, ...);
|
||||
|
||||
/* This function resumes the list of all linux threads that
|
||||
* TCMalloc_ListAllProcessThreads pauses before giving to its
|
||||
* callback. The function returns non-zero if at least one thread was
|
||||
* suspended and has now been resumed.
|
||||
*/
|
||||
int TCMalloc_ResumeAllProcessThreads(int num_threads, pid_t *thread_pids);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _THREAD_LISTER_H */
|
@ -254,7 +254,7 @@ class PERFTOOLS_DLL_DECL HeapLeakChecker {
|
||||
// Helper for DoNoLeaks to ignore all objects reachable from all live data
|
||||
static void IgnoreAllLiveObjectsLocked(const void* self_stack_top);
|
||||
|
||||
// Callback we pass to TCMalloc_ListAllProcessThreads (see thread_lister.h)
|
||||
// Callback we pass to TCMalloc_ListAllProcessThreads (see linuxthreads.h)
|
||||
// that is invoked when all threads of our process are found and stopped.
|
||||
// The call back does the things needed to ignore live data reachable from
|
||||
// thread stacks and registers for all our threads
|
||||
|
@ -36,43 +36,35 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <fcntl.h> // for O_RDONLY (we use syscall to do actual reads)
|
||||
#include <string.h>
|
||||
#ifndef HAVE_TLS
|
||||
#error we only support non-ancient Linux-es with native TLS support
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#ifdef HAVE_MMAP
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#ifdef HAVE_PTHREAD
|
||||
#include <fcntl.h> // for O_RDONLY (we use syscall to do actual reads)
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h> // TODO: check if needed
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#if defined(HAVE_LINUX_PTRACE_H)
|
||||
#include <linux/ptrace.h>
|
||||
#endif
|
||||
#ifdef HAVE_SYS_SYSCALL_H
|
||||
#include <sys/syscall.h>
|
||||
#endif
|
||||
#if defined(_WIN32) || defined(__CYGWIN__) || defined(__CYGWIN32__) || defined(__MINGW32__)
|
||||
#include <wtypes.h>
|
||||
#include <winbase.h>
|
||||
#undef ERROR // windows defines these as macros, which can cause trouble
|
||||
#undef max
|
||||
#undef min
|
||||
#endif
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/ptrace.h>
|
||||
#include <sys/procfs.h>
|
||||
#include <sys/user.h>
|
||||
#include <elf.h> // NT_PRSTATUS
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <gperftools/heap-checker.h>
|
||||
|
||||
@ -81,8 +73,7 @@
|
||||
#include "base/logging.h"
|
||||
#include <gperftools/stacktrace.h>
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/elfcore.h" // for i386_regs
|
||||
#include "base/thread_lister.h"
|
||||
#include "base/linuxthreads.h"
|
||||
#include "heap-profile-table.h"
|
||||
#include "base/low_level_alloc.h"
|
||||
#include "malloc_hook-inl.h"
|
||||
@ -94,6 +85,74 @@
|
||||
#include "base/sysinfo.h"
|
||||
#include "base/stl_allocator.h"
|
||||
|
||||
// When dealing with ptrace-ed threads, we need to capture all thread
|
||||
// registers (as potential live pointers), and we need to capture
|
||||
// thread's stack pointer to scan stack. capture_regs function uses
|
||||
// ptrace API to grab and scan registers and fetch stack pointer.
|
||||
template <typename Body>
|
||||
static std::pair<bool, uintptr_t> CaptureRegs(pid_t tid, const Body& body) {
|
||||
uintptr_t sp_offset;
|
||||
#if defined(__aarch64__)
|
||||
sp_offset = offsetof(user_regs_struct, sp);
|
||||
#elif defined(__arm__)
|
||||
sp_offset = 13 * sizeof(uintptr_t); // reg 13 is sp on legacy arms
|
||||
#elif defined(__x86_64__)
|
||||
sp_offset = offsetof(user_regs_struct, rsp);
|
||||
#elif defined(__i386__)
|
||||
sp_offset = offsetof(user_regs_struct, esp);
|
||||
#elif defined(__riscv)
|
||||
sp_offset = 2 * sizeof(uintptr_t); // register #2 is SP on riscv-s
|
||||
#elif defined(__PPC__)
|
||||
sp_offset = 1 * sizeof(uintptr_t);
|
||||
#elif defined(__mips__)
|
||||
sp_offset = offsetof(struct user, regs[EF_REG29]);
|
||||
#else
|
||||
// unsupported HW architecture. Single-threaded programs don't run
|
||||
// this code, so we still have chance to be useful on less supported
|
||||
// architectures.
|
||||
abort();
|
||||
#endif
|
||||
|
||||
elf_gregset_t regs;
|
||||
int rv;
|
||||
|
||||
// PTRACE_GETREGSET is better interface, but manpage says it is
|
||||
// 2.6.34 or later. RHEL6's kernel is, sadly, older. Yet, I'd like
|
||||
// us to still support rhel6, so we handle this case too.
|
||||
#ifdef PTRACE_GETREGSET
|
||||
iovec iov = {®s, sizeof(regs)};
|
||||
rv = syscall(SYS_ptrace, PTRACE_GETREGSET, tid, NT_PRSTATUS, &iov);
|
||||
if (rv == 0) {
|
||||
if (iov.iov_len != sizeof(regs)) {
|
||||
abort(); // bug?!
|
||||
}
|
||||
}
|
||||
#else
|
||||
errno = ENOSYS;
|
||||
rv = -1;
|
||||
#endif
|
||||
|
||||
if (rv < 0 && errno == ENOSYS) {
|
||||
// Some newer Linux hw architectures don't have PTRACE_GETREGSET,
|
||||
// or perhaps we're dealing with older kernel.
|
||||
#ifdef PTRACE_GETREGS
|
||||
rv = syscall(SYS_ptrace, PTRACE_GETREGS, tid, nullptr, ®s);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (rv < 0) {
|
||||
return std::make_pair(false, 0);
|
||||
}
|
||||
|
||||
uintptr_t sp = *reinterpret_cast<uintptr_t*>(reinterpret_cast<uintptr_t>(®s) + sp_offset);
|
||||
for (void** p = reinterpret_cast<void**>(®s);
|
||||
p < reinterpret_cast<void**>(®s + 1); ++p) {
|
||||
body(*p);
|
||||
}
|
||||
|
||||
return std::make_pair(true, sp);
|
||||
}
|
||||
|
||||
using std::string;
|
||||
using std::basic_string;
|
||||
using std::pair;
|
||||
@ -1010,14 +1069,6 @@ static enum {
|
||||
// and in other ways).
|
||||
//
|
||||
|
||||
#if defined(HAVE_LINUX_PTRACE_H) && defined(HAVE_SYS_SYSCALL_H) && defined(DUMPER)
|
||||
# if (defined(__i386__) || defined(__x86_64))
|
||||
# define THREAD_REGS i386_regs
|
||||
# elif defined(__PPC__)
|
||||
# define THREAD_REGS ppc_regs
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*static*/ int HeapLeakChecker::IgnoreLiveThreadsLocked(void* parameter,
|
||||
int num_threads,
|
||||
pid_t* thread_pids,
|
||||
@ -1038,32 +1089,22 @@ static enum {
|
||||
for (int i = 0; i < num_threads; ++i) {
|
||||
// the leak checking thread itself is handled
|
||||
// specially via self_thread_stack, not here:
|
||||
if (thread_pids[i] == self_thread_pid) continue;
|
||||
if (thread_pids[i] == self_thread_pid) {
|
||||
continue;
|
||||
}
|
||||
RAW_VLOG(11, "Handling thread with pid %d", thread_pids[i]);
|
||||
#ifdef THREAD_REGS
|
||||
THREAD_REGS thread_regs;
|
||||
#define sys_ptrace(r, p, a, d) syscall(SYS_ptrace, (r), (p), (a), (d))
|
||||
// We use sys_ptrace to avoid thread locking
|
||||
// because this is called from TCMalloc_ListAllProcessThreads
|
||||
// when all but this thread are suspended.
|
||||
if (sys_ptrace(PTRACE_GETREGS, thread_pids[i], NULL, &thread_regs) == 0) {
|
||||
// Need to use SP to get all the data from the very last stack frame:
|
||||
COMPILE_ASSERT(sizeof(thread_regs.SP) == sizeof(void*),
|
||||
SP_register_does_not_look_like_a_pointer);
|
||||
RegisterStackLocked(reinterpret_cast<void*>(thread_regs.SP));
|
||||
// Make registers live (just in case PTRACE_ATTACH resulted in some
|
||||
// register pointers still being in the registers and not on the stack):
|
||||
for (void** p = reinterpret_cast<void**>(&thread_regs);
|
||||
p < reinterpret_cast<void**>(&thread_regs + 1); ++p) {
|
||||
RAW_VLOG(12, "Thread register %p", *p);
|
||||
thread_registers.push_back(*p);
|
||||
}
|
||||
auto add_reg = [&thread_registers] (void *reg) {
|
||||
RAW_VLOG(12, "Thread register %p", reg);
|
||||
thread_registers.push_back(reg);
|
||||
};
|
||||
uintptr_t sp;
|
||||
bool ok;
|
||||
std::tie(ok, sp) = CaptureRegs(thread_pids[i], add_reg);
|
||||
if (ok) {
|
||||
RegisterStackLocked(reinterpret_cast<void*>(sp));
|
||||
} else {
|
||||
failures += 1;
|
||||
}
|
||||
#else
|
||||
failures += 1;
|
||||
#endif
|
||||
}
|
||||
// Use all the collected thread (stack) liveness sources:
|
||||
IgnoreLiveObjectsLocked("threads stack data", "");
|
||||
|
@ -103,7 +103,7 @@
|
||||
#include "base/googleinit.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "base/thread_lister.h"
|
||||
#include "base/linuxthreads.h"
|
||||
#include <gperftools/heap-checker.h>
|
||||
#include "memory_region_map.h"
|
||||
#include <gperftools/malloc_extension.h>
|
||||
|
@ -107,9 +107,6 @@
|
||||
/* Define to 1 if you have the <libunwind.h> header file. */
|
||||
/* #undef HAVE_LIBUNWIND_H */
|
||||
|
||||
/* Define to 1 if you have the <linux/ptrace.h> header file. */
|
||||
/* #undef HAVE_LINUX_PTRACE_H */
|
||||
|
||||
/* Define if this is Linux that has SIGEV_THREAD_ID */
|
||||
/* #undef HAVE_LINUX_SIGEV_THREAD_ID */
|
||||
|
||||
@ -162,9 +159,6 @@
|
||||
/* Define to 1 if you have the <sys/cdefs.h> header file. */
|
||||
/* #undef HAVE_SYS_CDEFS_H */
|
||||
|
||||
/* Define to 1 if you have the <sys/prctl.h> header file. */
|
||||
/* #undef HAVE_SYS_PRCTL_H */
|
||||
|
||||
/* Define to 1 if you have the <sys/resource.h> header file. */
|
||||
/* #undef HAVE_SYS_RESOURCE_H */
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user