Compare commits

...

4 Commits

Author SHA1 Message Date
b2e047f0d6 travis: run looping_threads.test under valgrind 2018-08-19 10:26:18 +00:00
Eugene Syromyatnikov
82f7fea522 Implement queueing of threads before dispatching them
It is possible that some tracees call a lot of cheap syscalls too fast,
and that can lead to starvation to the point some tracees are not served
for indefinite amount of time.  In order to solve that unfairness, try
to collect all the pending tracees first (along with the relevant
information) and only then dispatch the events.

* defs.h: Include "list.h".
(struct tcb): Add wait_data_idx, wait_extra_data_idx, wait_list fields.
* strace.c (tcb_wait_tab): New variable.
(expand_tcbtab): Resize tcb_wait_tab along with tcbtab, provide
an additional slot for extra event.
(droptcb): Remove tcp from wait_list.
(maybe_switch_tcbs): Get old pid from
tcb_wait_tab[tcp->wait_data_idx].msg.
(next_event): Add pending_tcps, extra_tcp, wait_nohang, elem, and
wait_tab_pos variables; check for elements in pending_tcps and skip
waiting if the list is not empty; check for extra_tcp and skip waiting
along with swapping wait_data_idx with wait_extra_data_idx;
after the initial wait, call wait4() in loop with WNOHANG flag set;
fetch siginfo on signal and eventmsg on PTRACE_EVENT_EXEC;
return the first tcp in pending_tcps list.
* tests/Makefile.am (XFAIL_TEST): Remove looping_threads.test.

Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=478419
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=526740
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=851457
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1609318
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1610774
Co-Authored-by: Dmitry Vlasenko <dvlasenk@redhat.com>
Co-Authored-by: Andreas Schwab <aschwab@redhat.com>
Co-Authored-by: Jeff Law <law@redhat.com>
Co-Authored-by: DJ Delorie <dj@redhat.com>
2018-08-19 10:26:18 +00:00
Eugene Syromyatnikov
49dbd3792d Add a generic list implementation
Similar to one used in the Linux kernel.

* macros.h (cast_ptr, containerof): New macros.
* list.h: New file.
* Makefile.am (strace_SOURCES): Add it.
2018-08-19 10:26:18 +00:00
42c11b5634 tests: check tracing of looping threads
* test/many_looping_threads.c: Remove.
* test/.gitignore: Remove many_looping_threads.
* test/Makefile (PROGS): Likewise.
(many_looping_threads): Remove.
* tests/looping_threads.c: New file.
* tests/looping_threads.test: New test.
* tests/.gitignore: Add looping_threads.
* tests/Makefile.am (check_PROGRAMS): Likewise.
(looping_threads_LDADD): New variable.
(MISC_TESTS, XFAIL_TESTS): Add looping_threads.test.
2018-08-19 10:26:18 +00:00
14 changed files with 521 additions and 229 deletions

View File

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

View File

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

View File

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

@ -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
View 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 */

View File

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

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

@ -1,7 +1,6 @@
childthread
clone
leaderkill
many_looping_threads
mmap_offset_decode
mtd
seccomp

View File

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

View File

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

@ -208,6 +208,7 @@ list_sigaction_signum
llseek
localtime
lookup_dcookie
looping_threads
lseek
lstat
lstat64

View File

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