Compare commits
4 Commits
master
...
ldv/travis
Author | SHA1 | Date | |
---|---|---|---|
b2e047f0d6 | |||
|
82f7fea522 | ||
|
49dbd3792d | ||
42c11b5634 |
58
.travis.yml
58
.travis.yml
@ -4,7 +4,7 @@ dist: trusty
|
||||
|
||||
before_install: ci/install-dependencies.sh
|
||||
|
||||
script: ci/run-build-and-tests.sh
|
||||
script: travis_wait 60 ci/run-build-and-tests.sh
|
||||
|
||||
sudo: required
|
||||
|
||||
@ -22,54 +22,8 @@ matrix:
|
||||
env:
|
||||
- TARGET=x86_64
|
||||
- STACKTRACE=libunwind
|
||||
- CHECK=coverage
|
||||
- KHEADERS=torvalds/linux
|
||||
- compiler: gcc-8
|
||||
env:
|
||||
- TARGET=x86_64
|
||||
- STACKTRACE=libunwind
|
||||
- KHEADERS=torvalds/linux
|
||||
- compiler: gcc
|
||||
env:
|
||||
- TARGET=x86_64
|
||||
- STACKTRACE=libunwind
|
||||
- compiler: gcc
|
||||
env:
|
||||
- TARGET=x32
|
||||
- STACKTRACE=no
|
||||
- compiler: gcc
|
||||
env:
|
||||
- TARGET=x86
|
||||
- STACKTRACE=no
|
||||
- compiler: clang
|
||||
env:
|
||||
- TARGET=x86_64
|
||||
- STACKTRACE=libunwind
|
||||
- compiler: musl-gcc
|
||||
env:
|
||||
- TARGET=x86_64
|
||||
- STACKTRACE=no
|
||||
- compiler: clang
|
||||
env:
|
||||
- TARGET=x86
|
||||
- STACKTRACE=no
|
||||
- compiler: musl-gcc
|
||||
env:
|
||||
- TARGET=x86
|
||||
- STACKTRACE=no
|
||||
- compiler: gcc-8
|
||||
env:
|
||||
- TARGET=x86_64
|
||||
- STACKTRACE=libunwind
|
||||
- compiler: gcc-8
|
||||
env:
|
||||
- TARGET=x32
|
||||
- STACKTRACE=no
|
||||
- compiler: gcc-8
|
||||
env:
|
||||
- TARGET=x86
|
||||
- STACKTRACE=no
|
||||
- compiler: gcc
|
||||
env:
|
||||
- TARGET=x86_64
|
||||
- STACKTRACE=no
|
||||
- CHECK=valgrind
|
||||
- TIMEOUT_DURATION=1200
|
||||
- VALGRIND_TOOLS=memcheck
|
||||
- VALGRIND_TESTDIR=tests
|
||||
- VALGRIND_TESTS=looping_threads.test
|
||||
|
@ -185,6 +185,7 @@ strace_SOURCES = \
|
||||
linux/asm_stat.h \
|
||||
linux/x32/asm_stat.h \
|
||||
linux/x86_64/asm_stat.h \
|
||||
list.h \
|
||||
listen.c \
|
||||
lookup_dcookie.c \
|
||||
loop.c \
|
||||
|
@ -97,11 +97,12 @@ case "${CHECK-}" in
|
||||
rc=$?
|
||||
for n in ${VALGRIND_TOOLS:-memcheck helgrind drd}; do
|
||||
make -k $j -C "${VALGRIND_TESTDIR:-.}" \
|
||||
check-valgrind-$n VERBOSE=${VERBOSE-} ||
|
||||
check-valgrind-$n V=1 VERBOSE=${VERBOSE-} \
|
||||
${VALGRIND_TESTS:+TESTS="$VALGRIND_TESTS"} ||
|
||||
rc=$?
|
||||
done
|
||||
echo 'BEGIN OF TEST SUITE INFORMATION'
|
||||
tail -n 99999 -- tests*/test-suite*.log tests*/ksysent.log ||
|
||||
tail -n 99999 -- tests*/test-suite*.log ||
|
||||
rc=$?
|
||||
echo 'END OF TEST SUITE INFORMATION'
|
||||
[ "$rc" -eq 0 ]
|
||||
|
11
defs.h
11
defs.h
@ -57,6 +57,7 @@
|
||||
#include "error_prints.h"
|
||||
#include "gcc_compat.h"
|
||||
#include "kernel_types.h"
|
||||
#include "list.h"
|
||||
#include "macros.h"
|
||||
#include "mpers_type.h"
|
||||
#include "string_to_uint.h"
|
||||
@ -236,6 +237,16 @@ struct tcb {
|
||||
|
||||
struct mmap_cache_t *mmap_cache;
|
||||
|
||||
/*
|
||||
* Data that is stored during process wait traversal.
|
||||
* We use indices as the actual data is stored in an array
|
||||
* that is realloc'ed in runtime.
|
||||
*/
|
||||
size_t wait_data_idx;
|
||||
size_t wait_extra_data_idx;
|
||||
struct list_item wait_list;
|
||||
|
||||
|
||||
#ifdef HAVE_LINUX_KVM_H
|
||||
struct vcpu_info *vcpu_info_list;
|
||||
#endif
|
||||
|
137
list.h
Normal file
137
list.h
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Some simple implementation of a list similar to one used in the kernel.
|
||||
*
|
||||
* Copyright (c) 2016-2018 The strace developers.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. 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.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
|
||||
*/
|
||||
|
||||
#ifndef STRACE_LIST_H
|
||||
#define STRACE_LIST_H
|
||||
|
||||
#include "macros.h"
|
||||
|
||||
struct list_item {
|
||||
struct list_item *prev;
|
||||
struct list_item *next;
|
||||
};
|
||||
|
||||
#define EMPTY_LIST(l_) struct list_item l_ = { &l_, &l_ }
|
||||
|
||||
static inline void
|
||||
list_init(struct list_item *l)
|
||||
{
|
||||
l->prev = l;
|
||||
l->next = l;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
list_is_empty(struct list_item *l)
|
||||
{
|
||||
return (l->next == l) && (l->prev == l);
|
||||
}
|
||||
|
||||
#define list_elem(var, type, field) containerof((var), type, field)
|
||||
|
||||
#define list_head(head, type, field) \
|
||||
(list_is_empty(head) ? NULL : list_elem((head)->next, type, field))
|
||||
#define list_tail(head, type, field) \
|
||||
(list_is_empty(head) ? NULL : list_elem((head)->prev, type, field))
|
||||
|
||||
#define list_next(val, field) \
|
||||
list_elem((val)->field.next, typeof(*(val)), field)
|
||||
#define list_prev(val, field) \
|
||||
list_elem((val)->field.prev, typeof(*(val)), field)
|
||||
|
||||
static inline void
|
||||
list_insert(struct list_item *head, struct list_item *item)
|
||||
{
|
||||
item->next = head->next;
|
||||
item->prev = head;
|
||||
head->next->prev = item;
|
||||
head->next = item;
|
||||
}
|
||||
|
||||
static inline void
|
||||
list_append(struct list_item *head, struct list_item *item)
|
||||
{
|
||||
item->next = head;
|
||||
item->prev = head->prev;
|
||||
head->prev->next = item;
|
||||
head->prev = item;
|
||||
}
|
||||
|
||||
static inline void
|
||||
list_remove(struct list_item *item)
|
||||
{
|
||||
if (!item->next || !item->prev)
|
||||
return;
|
||||
|
||||
item->prev->next = item->next;
|
||||
item->next->prev = item->prev;
|
||||
item->next = item->prev = NULL;
|
||||
}
|
||||
|
||||
static inline struct list_item *
|
||||
list_remove_tail(struct list_item *head)
|
||||
{
|
||||
struct list_item *t = list_is_empty(head) ? NULL : head->prev;
|
||||
|
||||
if (t)
|
||||
list_remove(t);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static inline struct list_item *
|
||||
list_remove_head(struct list_item *head)
|
||||
{
|
||||
struct list_item *h = list_is_empty(head) ? NULL : head->next;
|
||||
|
||||
if (h)
|
||||
list_remove(h);
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
static inline void
|
||||
list_replace(struct list_item *old, struct list_item *new)
|
||||
{
|
||||
new->next = old->next;
|
||||
new->prev = old->prev;
|
||||
old->prev->next = new;
|
||||
old->next->prev = new;
|
||||
old->next = old->prev = NULL;
|
||||
}
|
||||
|
||||
#define list_foreach(var_, head_, field_) \
|
||||
for (var_ = list_elem((head_)->next, typeof(*var_), field_); \
|
||||
&(var_->field_) != (head_); var_ = list_next(var_, field_))
|
||||
|
||||
#define list_foreach_safe(var_, head_, field_, _tmp) \
|
||||
for (var_ = list_elem((head_)->next, typeof(*var_), field_), \
|
||||
_tmp = list_elem((var_)->field_.next, typeof(*var_), field_); \
|
||||
&var_->field_ != head_; var_ = _tmp, _tmp = list_next(_tmp, field_))
|
||||
|
||||
#endif /* !STRACE_LIST_H */
|
9
macros.h
9
macros.h
@ -53,6 +53,15 @@
|
||||
(offsetof(type_, member_) + sizeof(((type_ *)0)->member_))
|
||||
#endif
|
||||
|
||||
#ifndef cast_ptr
|
||||
# define cast_ptr(type, var) ((type) (uintptr_t) (const volatile void *) (var))
|
||||
#endif
|
||||
|
||||
#ifndef containerof
|
||||
# define containerof(x, s, m) \
|
||||
cast_ptr(s *, (const volatile char *) (x) - offsetof(s, m))
|
||||
#endif
|
||||
|
||||
static inline bool
|
||||
is_filled(const char *ptr, char fill, size_t size)
|
||||
{
|
||||
|
323
strace.c
323
strace.c
@ -161,10 +161,17 @@ static struct tcb *current_tcp;
|
||||
struct tcb_wait_data {
|
||||
enum trace_event te; /**< Event passed to dispatch_event() */
|
||||
int status; /**< status, returned by wait4() */
|
||||
unsigned long msg; /**< Value returned by PTRACE_GETEVENTMSG */
|
||||
siginfo_t si; /**< siginfo, returned by PTRACE_GETSIGINFO */
|
||||
};
|
||||
|
||||
static struct tcb **tcbtab;
|
||||
/*
|
||||
* Since the queueing of tracees stops as soon as wait4() returns EAGAIN,
|
||||
* or at least two events for a single tracee, tab_wait_tab size shouldn't
|
||||
* exceed tcbtabsize + 1.
|
||||
*/
|
||||
static struct tcb_wait_data *tcb_wait_tab;
|
||||
static unsigned int nprocs;
|
||||
static size_t tcbtabsize;
|
||||
|
||||
@ -750,6 +757,9 @@ expand_tcbtab(void)
|
||||
for (tcb_ptr = tcbtab + old_tcbtabsize;
|
||||
tcb_ptr < tcbtab + tcbtabsize; tcb_ptr++, newtcbs++)
|
||||
*tcb_ptr = newtcbs;
|
||||
|
||||
tcb_wait_tab = xreallocarray(tcb_wait_tab, sizeof(*tcb_wait_tab),
|
||||
tcbtabsize + 1);
|
||||
}
|
||||
|
||||
static struct tcb *
|
||||
@ -769,6 +779,7 @@ alloctcb(int pid)
|
||||
#if SUPPORTED_PERSONALITIES > 1
|
||||
tcp->currpers = current_personality;
|
||||
#endif
|
||||
tcp->wait_extra_data_idx = (size_t) -1LLU;
|
||||
nprocs++;
|
||||
debug_msg("new tcb for pid %d, active tcbs:%d",
|
||||
tcp->pid, nprocs);
|
||||
@ -853,6 +864,8 @@ droptcb(struct tcb *tcp)
|
||||
if (printing_tcp == tcp)
|
||||
printing_tcp = NULL;
|
||||
|
||||
list_remove(&tcp->wait_list);
|
||||
|
||||
memset(tcp, 0, sizeof(*tcp));
|
||||
}
|
||||
|
||||
@ -2071,10 +2084,8 @@ maybe_switch_tcbs(struct tcb *tcp, const int pid)
|
||||
{
|
||||
FILE *fp;
|
||||
struct tcb *execve_thread;
|
||||
long old_pid = 0;
|
||||
long old_pid = tcb_wait_tab[tcp->wait_data_idx].msg;
|
||||
|
||||
if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, &old_pid) < 0)
|
||||
return tcp;
|
||||
/* Avoid truncation in pid2tcb() param passing */
|
||||
if (old_pid <= 0 || old_pid == pid)
|
||||
return tcp;
|
||||
@ -2235,17 +2246,38 @@ print_event_exit(struct tcb *tcp)
|
||||
static const struct tcb_wait_data *
|
||||
next_event(void)
|
||||
{
|
||||
static struct tcb_wait_data wait_data;
|
||||
static EMPTY_LIST(pending_tcps);
|
||||
static struct tcb *extra_tcp;
|
||||
|
||||
int pid;
|
||||
int status;
|
||||
struct tcb *tcp;
|
||||
struct tcb_wait_data *wd = &wait_data;
|
||||
int wait_errno;
|
||||
bool wait_nohang = false;
|
||||
struct list_item *elem;
|
||||
struct tcb *tcp = NULL;
|
||||
size_t wait_tab_pos = 0;
|
||||
struct rusage ru;
|
||||
|
||||
if (interrupted)
|
||||
return NULL;
|
||||
|
||||
if (!list_is_empty(&pending_tcps))
|
||||
goto next_event_get_tcp;
|
||||
|
||||
if (extra_tcp) {
|
||||
tcp = extra_tcp;
|
||||
extra_tcp = NULL;
|
||||
|
||||
if (tcp->wait_extra_data_idx != (size_t) -1LLU) {
|
||||
tcp->wait_data_idx = tcp->wait_extra_data_idx;
|
||||
tcp->wait_extra_data_idx = (size_t) -1LLU;
|
||||
|
||||
debug_msg("dequeued extra event from pid %u", tcp->pid);
|
||||
|
||||
goto next_event_exit;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Used to exit simply when nprocs hits zero, but in this testcase:
|
||||
* int main(void) { _exit(!!fork()); }
|
||||
@ -2288,7 +2320,7 @@ next_event(void)
|
||||
* the expiration will be handled by the signal handler.
|
||||
*/
|
||||
pid = wait4(-1, &status, __WALL, (cflag ? &ru : NULL));
|
||||
const int wait_errno = errno;
|
||||
wait_errno = errno;
|
||||
|
||||
/*
|
||||
* The window of opportunity to handle expirations
|
||||
@ -2304,135 +2336,184 @@ next_event(void)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (pid < 0) {
|
||||
if (wait_errno == EINTR) {
|
||||
wd->te = TE_NEXT;
|
||||
return wd;
|
||||
while (true) {
|
||||
struct tcb_wait_data *wd;
|
||||
|
||||
if (pid < 0) {
|
||||
if (wait_errno == EINTR)
|
||||
break;
|
||||
if (wait_nohang)
|
||||
break;
|
||||
if (nprocs == 0 && wait_errno == ECHILD)
|
||||
return NULL;
|
||||
/*
|
||||
* If nprocs > 0, ECHILD is not expected,
|
||||
* treat it as any other error here:
|
||||
*/
|
||||
errno = wait_errno;
|
||||
perror_msg_and_die("wait4(__WALL)");
|
||||
}
|
||||
if (nprocs == 0 && wait_errno == ECHILD)
|
||||
return NULL;
|
||||
/*
|
||||
* If nprocs > 0, ECHILD is not expected,
|
||||
* treat it as any other error here:
|
||||
*/
|
||||
errno = wait_errno;
|
||||
perror_msg_and_die("wait4(__WALL)");
|
||||
}
|
||||
|
||||
wd->status = status;
|
||||
if (!pid)
|
||||
break;
|
||||
|
||||
if (pid == popen_pid) {
|
||||
if (!WIFSTOPPED(status))
|
||||
popen_pid = 0;
|
||||
wd->te = TE_NEXT;
|
||||
return wd;
|
||||
}
|
||||
if (pid == popen_pid) {
|
||||
if (!WIFSTOPPED(status))
|
||||
popen_pid = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (debug_flag)
|
||||
print_debug_info(pid, status);
|
||||
if (debug_flag)
|
||||
print_debug_info(pid, status);
|
||||
|
||||
/* Look up 'pid' in our table. */
|
||||
tcp = pid2tcb(pid);
|
||||
/* Look up 'pid' in our table. */
|
||||
tcp = pid2tcb(pid);
|
||||
|
||||
if (!tcp) {
|
||||
tcp = maybe_allocate_tcb(pid, status);
|
||||
if (!tcp) {
|
||||
wd->te = TE_NEXT;
|
||||
return wd;
|
||||
tcp = maybe_allocate_tcb(pid, status);
|
||||
if (!tcp)
|
||||
break;
|
||||
}
|
||||
|
||||
if (cflag) {
|
||||
struct timespec stime = {
|
||||
.tv_sec = ru.ru_stime.tv_sec,
|
||||
.tv_nsec = ru.ru_stime.tv_usec * 1000
|
||||
};
|
||||
ts_sub(&tcp->dtime, &stime, &tcp->stime);
|
||||
tcp->stime = stime;
|
||||
}
|
||||
|
||||
if (wait_tab_pos > tcbtabsize)
|
||||
error_func_msg_and_die("Wait data storage overflow "
|
||||
"(wait_tab_pos %zu, nprocs %u, "
|
||||
"tcbtabsize %zu)", wait_tab_pos,
|
||||
nprocs, tcbtabsize);
|
||||
|
||||
wd = tcb_wait_tab + wait_tab_pos;
|
||||
memset(wd, 0, sizeof(*wd));
|
||||
|
||||
if (tcp->wait_list.next) {
|
||||
tcp->wait_extra_data_idx = wait_tab_pos;
|
||||
extra_tcp = tcp;
|
||||
} else {
|
||||
tcp->wait_data_idx = wait_tab_pos;
|
||||
list_append(&pending_tcps, &tcp->wait_list);
|
||||
}
|
||||
|
||||
wait_tab_pos++;
|
||||
wd->status = status;
|
||||
|
||||
if (WIFSIGNALED(status)) {
|
||||
wd->te = TE_SIGNALLED;
|
||||
} else if (WIFEXITED(status)) {
|
||||
wd->te = TE_EXITED;
|
||||
} else {
|
||||
/*
|
||||
* As WCONTINUED flag has not been specified to wait4,
|
||||
* it cannot be WIFCONTINUED(status), so the only case
|
||||
* that remains is WIFSTOPPED(status).
|
||||
*/
|
||||
|
||||
const unsigned int sig = WSTOPSIG(status);
|
||||
const unsigned int event = (unsigned int) status >> 16;
|
||||
|
||||
switch (event) {
|
||||
case 0:
|
||||
/*
|
||||
* Is this post-attach SIGSTOP?
|
||||
* Interestingly, the process may stop
|
||||
* with STOPSIG equal to some other signal
|
||||
* than SIGSTOP if we happened to attach
|
||||
* just before the process takes a signal.
|
||||
*/
|
||||
if (sig == SIGSTOP &&
|
||||
(tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
|
||||
debug_func_msg("ignored SIGSTOP on "
|
||||
"pid %d", tcp->pid);
|
||||
tcp->flags &= ~TCB_IGNORE_ONE_SIGSTOP;
|
||||
wd->te = TE_RESTART;
|
||||
} else if (sig == syscall_trap_sig) {
|
||||
wd->te = TE_SYSCALL_STOP;
|
||||
} else {
|
||||
/*
|
||||
* True if tracee is stopped by signal
|
||||
* (as opposed to "tracee received
|
||||
* signal").
|
||||
* TODO: shouldn't we check for
|
||||
* errno == EINVAL too?
|
||||
* We can get ESRCH instead, you know...
|
||||
*/
|
||||
bool stopped = ptrace(PTRACE_GETSIGINFO,
|
||||
pid, 0, &wd->si) < 0;
|
||||
|
||||
wd->te = stopped ? TE_GROUP_STOP
|
||||
: TE_SIGNAL_DELIVERY_STOP;
|
||||
}
|
||||
break;
|
||||
case PTRACE_EVENT_STOP:
|
||||
/*
|
||||
* PTRACE_INTERRUPT-stop or group-stop.
|
||||
* PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
|
||||
*/
|
||||
switch (sig) {
|
||||
case SIGSTOP:
|
||||
case SIGTSTP:
|
||||
case SIGTTIN:
|
||||
case SIGTTOU:
|
||||
wd->te = TE_GROUP_STOP;
|
||||
break;
|
||||
default:
|
||||
wd->te = TE_RESTART;
|
||||
}
|
||||
break;
|
||||
case PTRACE_EVENT_EXEC:
|
||||
if (ptrace(PTRACE_GETEVENTMSG, pid, NULL,
|
||||
&wd->msg) < 0)
|
||||
wd->msg = 0;
|
||||
|
||||
wd->te = TE_STOP_BEFORE_EXECVE;
|
||||
break;
|
||||
case PTRACE_EVENT_EXIT:
|
||||
wd->te = TE_STOP_BEFORE_EXIT;
|
||||
break;
|
||||
default:
|
||||
wd->te = TE_RESTART;
|
||||
}
|
||||
}
|
||||
|
||||
if (extra_tcp)
|
||||
break;
|
||||
|
||||
pid = wait4(-1, &status, __WALL | WNOHANG, (cflag ? &ru : NULL));
|
||||
wait_errno = errno;
|
||||
wait_nohang = true;
|
||||
}
|
||||
|
||||
next_event_get_tcp:
|
||||
elem = list_remove_head(&pending_tcps);
|
||||
|
||||
if (!elem) {
|
||||
memset(tcb_wait_tab, 0, sizeof(*tcb_wait_tab));
|
||||
tcb_wait_tab->te = TE_NEXT;
|
||||
|
||||
return tcb_wait_tab;
|
||||
} else {
|
||||
tcp = list_elem(elem, struct tcb, wait_list);
|
||||
debug_func_msg("dequeued pid %d", tcp->pid);
|
||||
}
|
||||
|
||||
next_event_exit:
|
||||
/* Is this the very first time we see this tracee stopped? */
|
||||
if (tcp->flags & TCB_STARTUP)
|
||||
startup_tcb(tcp);
|
||||
|
||||
clear_regs(tcp);
|
||||
|
||||
/* Set current output file */
|
||||
set_current_tcp(tcp);
|
||||
|
||||
if (cflag) {
|
||||
struct timespec stime = {
|
||||
.tv_sec = ru.ru_stime.tv_sec,
|
||||
.tv_nsec = ru.ru_stime.tv_usec * 1000
|
||||
};
|
||||
ts_sub(&tcp->dtime, &stime, &tcp->stime);
|
||||
tcp->stime = stime;
|
||||
}
|
||||
|
||||
if (WIFSIGNALED(status)) {
|
||||
wd->te = TE_SIGNALLED;
|
||||
return wd;
|
||||
}
|
||||
|
||||
if (WIFEXITED(status)) {
|
||||
wd->te = TE_EXITED;
|
||||
return wd;
|
||||
}
|
||||
|
||||
/*
|
||||
* As WCONTINUED flag has not been specified to wait4,
|
||||
* it cannot be WIFCONTINUED(status), so the only case
|
||||
* that remains is WIFSTOPPED(status).
|
||||
*/
|
||||
|
||||
/* Is this the very first time we see this tracee stopped? */
|
||||
if (tcp->flags & TCB_STARTUP)
|
||||
startup_tcb(tcp);
|
||||
|
||||
const unsigned int sig = WSTOPSIG(status);
|
||||
const unsigned int event = (unsigned int) status >> 16;
|
||||
|
||||
switch (event) {
|
||||
case 0:
|
||||
/*
|
||||
* Is this post-attach SIGSTOP?
|
||||
* Interestingly, the process may stop
|
||||
* with STOPSIG equal to some other signal
|
||||
* than SIGSTOP if we happened to attach
|
||||
* just before the process takes a signal.
|
||||
*/
|
||||
if (sig == SIGSTOP && (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
|
||||
debug_func_msg("ignored SIGSTOP on pid %d", tcp->pid);
|
||||
tcp->flags &= ~TCB_IGNORE_ONE_SIGSTOP;
|
||||
wd->te = TE_RESTART;
|
||||
} else if (sig == syscall_trap_sig) {
|
||||
wd->te = TE_SYSCALL_STOP;
|
||||
} else {
|
||||
memset(&wd->si, 0, sizeof(wd->si));
|
||||
/*
|
||||
* True if tracee is stopped by signal
|
||||
* (as opposed to "tracee received signal").
|
||||
* TODO: shouldn't we check for errno == EINVAL too?
|
||||
* We can get ESRCH instead, you know...
|
||||
*/
|
||||
bool stopped = ptrace(PTRACE_GETSIGINFO, pid, 0, &wd->si) < 0;
|
||||
wd->te = stopped ? TE_GROUP_STOP : TE_SIGNAL_DELIVERY_STOP;
|
||||
}
|
||||
break;
|
||||
case PTRACE_EVENT_STOP:
|
||||
/*
|
||||
* PTRACE_INTERRUPT-stop or group-stop.
|
||||
* PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
|
||||
*/
|
||||
switch (sig) {
|
||||
case SIGSTOP:
|
||||
case SIGTSTP:
|
||||
case SIGTTIN:
|
||||
case SIGTTOU:
|
||||
wd->te = TE_GROUP_STOP;
|
||||
break;
|
||||
default:
|
||||
wd->te = TE_RESTART;
|
||||
}
|
||||
break;
|
||||
case PTRACE_EVENT_EXEC:
|
||||
wd->te = TE_STOP_BEFORE_EXECVE;
|
||||
break;
|
||||
case PTRACE_EVENT_EXIT:
|
||||
wd->te = TE_STOP_BEFORE_EXIT;
|
||||
break;
|
||||
default:
|
||||
wd->te = TE_RESTART;
|
||||
}
|
||||
|
||||
return wd;
|
||||
return tcb_wait_tab + tcp->wait_data_idx;
|
||||
}
|
||||
|
||||
static int
|
||||
|
1
test/.gitignore
vendored
1
test/.gitignore
vendored
@ -1,7 +1,6 @@
|
||||
childthread
|
||||
clone
|
||||
leaderkill
|
||||
many_looping_threads
|
||||
mmap_offset_decode
|
||||
mtd
|
||||
seccomp
|
||||
|
@ -3,8 +3,7 @@ CFLAGS += -Wall
|
||||
PROGS = \
|
||||
sig skodic clone leaderkill childthread \
|
||||
sigkill_rain wait_must_be_interruptible threaded_execve \
|
||||
mtd ubi seccomp sfd mmap_offset_decode x32_lseek x32_mmap \
|
||||
many_looping_threads
|
||||
mtd ubi seccomp sfd mmap_offset_decode x32_lseek x32_mmap
|
||||
|
||||
all: $(PROGS)
|
||||
|
||||
@ -12,8 +11,6 @@ leaderkill: LDFLAGS += -pthread
|
||||
|
||||
childthread: LDFLAGS += -pthread
|
||||
|
||||
many_looping_threads: LDFLAGS += -pthread
|
||||
|
||||
clean distclean:
|
||||
rm -f *.o core $(PROGS) *.gdb
|
||||
|
||||
|
@ -1,49 +0,0 @@
|
||||
// This testcase, when run with large number of threads
|
||||
// under stace -f, may never finish because strace does not
|
||||
// ensure any fairness in thread scheduling:
|
||||
// it restarts threads as they stop. If daughter threads crowd out
|
||||
// the "mother" and _they_ get continually restarted by strace,
|
||||
// the end of spawning loop will never be reached.
|
||||
//
|
||||
// Also, it is a testcase which triggers the
|
||||
// "strace: Exit of unknown pid 32457 seen"
|
||||
// message when on testcase exit, strace sees deaths of newly-attached
|
||||
// threads _before_ their first syscall stop.
|
||||
//
|
||||
#include <stdio.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int thd_no;
|
||||
|
||||
static void *sub_thd(void *c)
|
||||
{
|
||||
dprintf(1, "sub-thread %d created\n", ++thd_no);
|
||||
for (;;)
|
||||
getuid();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i;
|
||||
pthread_t *thd;
|
||||
int num_threads = 1;
|
||||
|
||||
if (argv[1])
|
||||
num_threads = atoi(argv[1]);
|
||||
|
||||
thd = malloc(num_threads * sizeof(thd[0]));
|
||||
dprintf(1, "test start, num_threads:%d...\n", num_threads);
|
||||
|
||||
for (i = 0; i < num_threads; i++) {
|
||||
pthread_create(&thd[i], NULL, sub_thd, NULL);
|
||||
dprintf(1, "after pthread_create\n");
|
||||
}
|
||||
|
||||
/* Exit. This kills all threads */
|
||||
return 0;
|
||||
}
|
1
tests/.gitignore
vendored
1
tests/.gitignore
vendored
@ -208,6 +208,7 @@ list_sigaction_signum
|
||||
llseek
|
||||
localtime
|
||||
lookup_dcookie
|
||||
looping_threads
|
||||
lseek
|
||||
lstat
|
||||
lstat64
|
||||
|
@ -129,6 +129,7 @@ check_PROGRAMS = $(PURE_EXECUTABLES) \
|
||||
ksysent \
|
||||
list_sigaction_signum \
|
||||
localtime \
|
||||
looping_threads \
|
||||
mmsg-silent \
|
||||
mmsg_name-v \
|
||||
msg_control-v \
|
||||
@ -192,6 +193,7 @@ fstat64_CPPFLAGS = $(AM_CPPFLAGS) -D_FILE_OFFSET_BITS=64
|
||||
fstatat64_CPPFLAGS = $(AM_CPPFLAGS) -D_FILE_OFFSET_BITS=64
|
||||
ftruncate64_CPPFLAGS = $(AM_CPPFLAGS) -D_FILE_OFFSET_BITS=64
|
||||
localtime_LDADD = $(clock_LIBS) $(LDADD)
|
||||
looping_threads_LDADD = -lpthread $(LDADD)
|
||||
lstat64_CPPFLAGS = $(AM_CPPFLAGS) -D_FILE_OFFSET_BITS=64
|
||||
mmap64_CPPFLAGS = $(AM_CPPFLAGS) -D_FILE_OFFSET_BITS=64
|
||||
mmap64_Xabbrev_CPPFLAGS = $(AM_CPPFLAGS) -D_FILE_OFFSET_BITS=64
|
||||
@ -325,6 +327,7 @@ MISC_TESTS = \
|
||||
interactive_block.test \
|
||||
ksysent.test \
|
||||
localtime.test \
|
||||
looping_threads.test \
|
||||
opipe.test \
|
||||
options-syntax.test \
|
||||
pc.test \
|
||||
|
110
tests/looping_threads.c
Normal file
110
tests/looping_threads.c
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Check tracing of looping threads.
|
||||
*
|
||||
* Copyright (c) 2009-2018 The strace developers.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. 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.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
|
||||
*/
|
||||
|
||||
#include "tests.h"
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static void *
|
||||
thread(void *arg)
|
||||
{
|
||||
for (;;)
|
||||
getuid();
|
||||
return arg;
|
||||
}
|
||||
|
||||
int
|
||||
main(int ac, const char *av[])
|
||||
{
|
||||
assert(ac == 3);
|
||||
|
||||
int timeout = atoi(av[1]);
|
||||
assert(timeout > 0);
|
||||
|
||||
int num_threads = atoi(av[2]);
|
||||
assert(num_threads > 0);
|
||||
|
||||
/* Create a new process group. */
|
||||
if (setpgid(0, 0))
|
||||
perror_msg_and_fail("setpgid");
|
||||
|
||||
/*
|
||||
* When the main process terminates, the process group becomes orphaned.
|
||||
* If any member of the orphaned process group is stopped, then
|
||||
* a SIGHUP signal followed by a SIGCONT signal is sent to each process
|
||||
* in the orphaned process group.
|
||||
* Create a process in a stopped state to activate this behaviour.
|
||||
*/
|
||||
pid_t stopped = fork();
|
||||
if (stopped < 0)
|
||||
perror_msg_and_fail("fork");
|
||||
if (!stopped) {
|
||||
raise(SIGSTOP);
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
const sigset_t set = {};
|
||||
const struct sigaction act = { .sa_handler = SIG_DFL };
|
||||
if (sigaction(SIGALRM, &act, NULL))
|
||||
perror_msg_and_fail("sigaction");
|
||||
if (sigprocmask(SIG_SETMASK, &set, NULL))
|
||||
perror_msg_and_fail("sigprocmask");
|
||||
alarm(timeout);
|
||||
|
||||
/*
|
||||
* Create all threads in a subprocess, this guarantees that
|
||||
* their tracer will not be their parent.
|
||||
*/
|
||||
pid_t pid = fork();
|
||||
if (pid < 0)
|
||||
perror_msg_and_fail("fork");
|
||||
if (!pid) {
|
||||
for (int i = 0; i < num_threads; i++) {
|
||||
pthread_t t;
|
||||
if ((errno = pthread_create(&t, NULL, thread, NULL)))
|
||||
perror_msg_and_fail("pthread_create #%d", i);
|
||||
}
|
||||
|
||||
/* This terminates all threads. */
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
int s;
|
||||
if (waitpid(pid, &s, 0) != pid)
|
||||
perror_msg_and_fail("waitpid");
|
||||
|
||||
assert(WIFEXITED(s));
|
||||
return WEXITSTATUS(s);
|
||||
}
|
37
tests/looping_threads.test
Executable file
37
tests/looping_threads.test
Executable file
@ -0,0 +1,37 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Check tracing of looping threads.
|
||||
#
|
||||
# Copyright (c) 2009-2018 The strace developers.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. 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.
|
||||
# 3. The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
|
||||
|
||||
. "${srcdir=.}/init.sh"
|
||||
|
||||
check_prog nproc
|
||||
timeout="$(($TIMEOUT_DURATION/10))"
|
||||
nproc="$((64+$timeout+$(nproc)))"
|
||||
|
||||
run_prog "../$NAME" "$timeout" "$nproc"
|
||||
run_strace -f -qq -enone -esignal=none $args
|
Loading…
Reference in New Issue
Block a user