[ltrace] Fix detach from sleeping process, fix return from tail call

Petr Machata pmachata at fedoraproject.org
Mon Apr 30 13:01:02 UTC 2012


commit 055adede183b02d107ed3939d5f50181d5fa33ad
Author: Petr Machata <pmachata at redhat.com>
Date:   Mon Apr 30 15:00:35 2012 +0200

    Fix detach from sleeping process, fix return from tail call
    
    The latter in a limited way, true tail recursion is still broken.

 ltrace-0.6.0-detach-sleeping.patch |  141 ++++++++++++++++++++
 ltrace-0.6.0-tail-return.patch     |  256 ++++++++++++++++++++++++++++++++++++
 ltrace.spec                        |    6 +-
 3 files changed, 402 insertions(+), 1 deletions(-)
---
diff --git a/ltrace-0.6.0-detach-sleeping.patch b/ltrace-0.6.0-detach-sleeping.patch
new file mode 100644
index 0000000..bac1b7e
--- /dev/null
+++ b/ltrace-0.6.0-detach-sleeping.patch
@@ -0,0 +1,141 @@
+diff --git a/common.h b/common.h
+index 2399e29..b6e10f2 100644
+--- a/common.h
++++ b/common.h
+@@ -347,7 +347,6 @@ extern void continue_after_signal(pid_t pid, int signum);
+ extern void continue_after_syscall(Process *proc, int sysnum, int ret_p);
+ extern void continue_after_breakpoint(Process * proc, struct breakpoint *sbp);
+ extern void continue_after_vfork(Process * proc);
+-extern void ltrace_exiting(void);
+ extern long gimme_arg(enum tof type, Process * proc, int arg_num, arg_type_info * info);
+ extern void save_register_args(enum tof type, Process * proc);
+ extern int umovestr(Process * proc, void * addr, int len, void * laddr);
+@@ -363,5 +362,21 @@ extern int task_kill (pid_t pid, int sig);
+  * any platform-specific knowledge of why it could be so.  */
+ void trace_fail_warning(pid_t pid);
+ 
++/* A pair of functions called to initiate a detachment request when
++ * ltrace is about to exit.  Their job is to undo any effects that
++ * tracing had and eventually detach process, perhaps by way of
++ * installing a process handler.
++ *
++ * OS_LTRACE_EXITING_SIGHANDLER is called from a signal handler
++ * context right after the signal was captured.  It returns 1 if the
++ * request was handled or 0 if it wasn't.
++ *
++ * If the call to OS_LTRACE_EXITING_SIGHANDLER didn't handle the
++ * request, OS_LTRACE_EXITING is called when the next event is
++ * generated.  Therefore it's called in "safe" context, without
++ * re-entrancy concerns, but it's only called after an even is
++ * generated.  */
++int os_ltrace_exiting_sighandler(void);
++void os_ltrace_exiting(void);
+ 
+ extern struct ltelf main_lte;
+diff --git a/handle_event.c b/handle_event.c
+index e3d3d0a..725f50d 100644
+--- a/handle_event.c
++++ b/handle_event.c
+@@ -49,11 +49,12 @@ call_handler(Process * proc, Event * event)
+ }
+ 
+ void
+-handle_event(Event *event) {
++handle_event(Event *event)
++{
+ 	if (exiting == 1) {
+-		exiting = 2;
+ 		debug(1, "ltrace about to exit");
+-		ltrace_exiting();
++		os_ltrace_exiting();
++		exiting = 2;
+ 	}
+ 	debug(DEBUG_FUNCTION, "handle_event(pid=%d, type=%d)",
+ 	      event->proc ? event->proc->pid : -1, event->type);
+diff --git a/libltrace.c b/libltrace.c
+index 19bfafd..777ad1b 100644
+--- a/libltrace.c
++++ b/libltrace.c
+@@ -48,9 +48,14 @@ signal_alarm(int sig) {
+ }
+ 
+ static void
+-signal_exit(int sig) {
+-	exiting = 1;
++signal_exit(int sig)
++{
+ 	debug(1, "Received interrupt signal; exiting...");
++	if (exiting != 0)
++		return;
++
++	exiting = 1 + !!os_ltrace_exiting_sighandler();
++
+ 	signal(SIGINT, SIG_IGN);
+ 	signal(SIGTERM, SIG_IGN);
+ 	signal(SIGALRM, signal_alarm);
+diff --git a/sysdeps/linux-gnu/events.c b/sysdeps/linux-gnu/events.c
+index 021192f..b6c12ef 100644
+--- a/sysdeps/linux-gnu/events.c
++++ b/sysdeps/linux-gnu/events.c
+@@ -104,6 +104,8 @@ next_qd_event(void)
+ 	return each_qd_event(&event_process_not_reenabling, NULL);
+ }
+ 
++int linux_in_waitpid = 0;
++
+ Event *
+ next_event(void)
+ {
+@@ -124,7 +126,11 @@ next_event(void)
+ 		debug(DEBUG_EVENT, "event: No more traced programs: exiting");
+ 		exit(0);
+ 	}
++
++	linux_in_waitpid = 1;
+ 	pid = waitpid(-1, &status, __WALL);
++	linux_in_waitpid = 0;
++
+ 	if (pid == -1) {
+ 		if (errno == ECHILD) {
+ 			debug(DEBUG_EVENT, "event: No more traced programs: exiting");
+diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c
+index 82a4154..9ecea1e 100644
+--- a/sysdeps/linux-gnu/trace.c
++++ b/sysdeps/linux-gnu/trace.c
+@@ -314,8 +314,8 @@ task_stopped(Process * task, void * data)
+ 	case ps_invalid:
+ 	case ps_tracing_stop:
+ 	case ps_zombie:
+-	case ps_sleeping:
+ 		return pcb_cont;
++	case ps_sleeping:
+ 	case ps_stop:
+ 	case ps_other:
+ 		return pcb_stop;
+@@ -1005,7 +1005,7 @@ continue_after_syscall(Process * proc, int sysnum, int ret_p)
+  * detaches.
+  */
+ void
+-ltrace_exiting(void)
++os_ltrace_exiting(void)
+ {
+ 	struct opt_p_t * it;
+ 	for (it = opt_p; it != NULL; it = it->next) {
+@@ -1019,6 +1019,17 @@ ltrace_exiting(void)
+ 	}
+ }
+ 
++int
++os_ltrace_exiting_sighandler(void)
++{
++	extern int linux_in_waitpid;
++	if (linux_in_waitpid) {
++		os_ltrace_exiting();
++		return 1;
++	}
++	return 0;
++}
++
+ size_t
+ umovebytes(Process *proc, void *addr, void *laddr, size_t len) {
+ 
diff --git a/ltrace-0.6.0-tail-return.patch b/ltrace-0.6.0-tail-return.patch
new file mode 100644
index 0000000..1af5e61
--- /dev/null
+++ b/ltrace-0.6.0-tail-return.patch
@@ -0,0 +1,256 @@
+diff --git a/handle_event.c b/handle_event.c
+index 725f50d..8c3c7ce 100644
+--- a/handle_event.c
++++ b/handle_event.c
+@@ -565,11 +565,21 @@ void *get_count_register (Process *proc);
+ }
+ 
+ static void
++output_right_tos(struct Process *proc)
++{
++	size_t d = proc->callstack_depth;
++	struct callstack_element *elem = &proc->callstack[d - 1];
++	if (proc->state != STATE_IGNORED)
++		output_right(LT_TOF_FUNCTIONR, proc, elem->c_un.libfunc->name);
++}
++
++static void
+ handle_breakpoint(Event *event)
+ {
+ 	int i, j;
+ 	struct breakpoint *sbp;
+ 	Process *leader = event->proc->leader;
++	void *brk_addr = event->e_un.brk_addr;
+ 
+ 	/* The leader has terminated.  */
+ 	if (leader == NULL) {
+@@ -577,12 +587,14 @@ handle_breakpoint(Event *event)
+ 		return;
+ 	}
+ 
+-	debug(DEBUG_FUNCTION, "handle_breakpoint(pid=%d, addr=%p)", event->proc->pid, event->e_un.brk_addr);
+-	debug(2, "event: breakpoint (%p)", event->e_un.brk_addr);
++	debug(DEBUG_FUNCTION, "handle_breakpoint(pid=%d, addr=%p)",
++	      event->proc->pid, brk_addr);
++	debug(2, "event: breakpoint (%p)", brk_addr);
+ 
+ 	for (i = event->proc->callstack_depth - 1; i >= 0; i--) {
+-		if (event->e_un.brk_addr ==
+-		    event->proc->callstack[i].return_addr) {
++		if (brk_addr == event->proc->callstack[i].return_addr) {
++			struct library_symbol *libsym =
++			    event->proc->callstack[i].c_un.libfunc;
+ #ifdef __powerpc__
+ 			/*
+ 			 * PPC HACK! (XXX FIXME TODO)
+@@ -623,8 +625,6 @@ handle_breakpoint(Event *event)
+ 			 * so be sure to re-enable the breakpoint.
+ 			 */
+ 			unsigned long a;
+-			struct library_symbol *libsym =
+-			    event->proc->callstack[i].c_un.libfunc;
+ 			void *addr = sym2addr(event->proc, libsym);
+ 
+ 			if (libsym->plt_type != LS_TOPLT_POINT) {
+@@ -668,19 +679,37 @@ handle_breakpoint(Event *event)
+ 					calc_time_spent(event->proc);
+ 				}
+ 			}
+-			event->proc->return_addr = event->e_un.brk_addr;
+-			if (event->proc->state != STATE_IGNORED) {
+-				output_right(LT_TOF_FUNCTIONR, event->proc,
+-						event->proc->callstack[i].c_un.libfunc->name);
+-			}
++			event->proc->return_addr = brk_addr;
++
++			output_right_tos(event->proc);
+ 			callstack_pop(event->proc);
+-			sbp = address2bpstruct(leader, event->e_un.brk_addr);
++
++			/* Pop also any other entries that seem like
++			 * they are linked to the current one: they
++			 * have the same return address, but were made
++			 * for different symbols.  This should only
++			 * happen for entry point tracing, i.e. for -x
++			 * everywhere, or -x and -e on PPC64.  */
++			while (event->proc->callstack_depth > 0) {
++				struct callstack_element *prev;
++				size_t d = event->proc->callstack_depth;
++				prev = &event->proc->callstack[d - 1];
++
++				if (prev->c_un.libfunc == libsym
++				    || prev->return_addr != brk_addr)
++					break;
++
++				output_right_tos(event->proc);
++				callstack_pop(event->proc);
++			}
++
++			sbp = address2bpstruct(leader, brk_addr);
+ 			continue_after_breakpoint(event->proc, sbp);
+ 			return;
+ 		}
+ 	}
+ 
+-	if ((sbp = address2bpstruct(leader, event->e_un.brk_addr))) {
++	if ((sbp = address2bpstruct(leader, brk_addr))) {
+ 		breakpoint_on_hit(sbp, event->proc);
+ 
+ 		if (sbp->libsym == NULL) {
+@@ -711,7 +740,7 @@ handle_breakpoint(Event *event)
+ 
+ 	if (event->proc->state != STATE_IGNORED && !options.no_plt) {
+ 		output_line(event->proc, "unexpected breakpoint at %p",
+-				(void *)event->e_un.brk_addr);
++			    brk_addr);
+ 	}
+ 	continue_process(event->proc->pid);
+ }
+@@ -742,7 +771,7 @@ callstack_push_syscall(Process *proc, int sysnum) {
+ 
+ static void
+ callstack_push_symfunc(Process *proc, struct library_symbol *sym) {
+-	struct callstack_element *elem, *prev;
++	struct callstack_element *elem;
+ 
+ 	debug(DEBUG_FUNCTION, "callstack_push_symfunc(pid=%d, symbol=%s)", proc->pid, sym->name);
+ 	/* FIXME: not good -- should use dynamic allocation. 19990703 mortene. */
+@@ -752,8 +781,7 @@ callstack_push_symfunc(Process *proc, struct library_symbol *sym) {
+ 		return;
+ 	}
+ 
+-	prev = &proc->callstack[proc->callstack_depth-1];
+-	elem = &proc->callstack[proc->callstack_depth];
++	elem = &proc->callstack[proc->callstack_depth++];
+ 	elem->is_syscall = 0;
+ 	elem->c_un.libfunc = sym;
+ 
+@@ -763,8 +791,6 @@ callstack_push_symfunc(Process *proc, struct library_symbol *sym) {
+ 	}
+ 
+ 	/* handle functions like atexit() on mips which have no return */
+-	if (elem->return_addr != prev->return_addr)
+-		proc->callstack_depth++;
+ 	if (opt_T || options.summary) {
+ 		struct timezone tz;
+ 		gettimeofday(&elem->time_spent, &tz);
+diff --git a/testsuite/ltrace.main/branch_func.c b/testsuite/ltrace.main/branch_func.c
+new file mode 100644
+index 0000000..0ce311d
+--- /dev/null
++++ b/testsuite/ltrace.main/branch_func.c
+@@ -0,0 +1,51 @@
++/*
++ * This file is part of ltrace.
++ * Copyright (C) 2012 Petr Machata, Red Hat Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License as
++ * published by the Free Software Foundation; either version 2 of the
++ * License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful, but
++ * WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
++ * 02110-1301 USA
++ */
++
++/* This is specially made to produce tail calls.  We then trace them
++ * and see if ltrace handles it well, or whether its internal stack
++ * overflows.  */
++
++__attribute__((noinline, optimize(3))) int
++func3(int i)
++{
++	return i + 1;
++}
++
++__attribute__((noinline, optimize(3))) int
++func2(int i)
++{
++	return func3(i * 3);
++}
++
++__attribute__((noinline, optimize(3))) int
++func1(int i)
++{
++	return func2(i + 2);
++}
++
++__attribute__((optimize(0))) int
++main(int argc, char **argv)
++{
++	int counter = 0;
++	int i;
++	for (i = 0; i < 100; ++i)
++		counter += func1(i);
++	return counter;
++}
+diff --git a/testsuite/ltrace.main/branch_func.exp b/testsuite/ltrace.main/branch_func.exp
+new file mode 100644
+index 0000000..fec5700
+--- /dev/null
++++ b/testsuite/ltrace.main/branch_func.exp
+@@ -0,0 +1,57 @@
++# This file is part of ltrace.
++# Copyright (C) 2012 Petr Machata, Red Hat Inc.
++#
++# This program is free software; you can redistribute it and/or
++# modify it under the terms of the GNU General Public License as
++# published by the Free Software Foundation; either version 2 of the
++# License, or (at your option) any later version.
++#
++# This program is distributed in the hope that it will be useful, but
++# WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++# General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with this program; if not, write to the Free Software
++# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
++# 02110-1301 USA
++
++set testfile "branch_func"
++set srcfile ${testfile}.c
++set binfile ${testfile}
++
++if [get_compiler_info $binfile] {
++  return -1
++}
++
++verbose "compiling source file now....."
++if { [ltrace_compile $srcdir/$subdir/$srcfile $objdir/$subdir/$binfile executable {debug} ] != "" } {
++     send_user "Testcase compile failed, so all tests in this file will automatically fail\n."
++}
++
++# set options for ltrace.
++ltrace_options "-x" "func1" "-x" "func2" "-x" "func3"
++
++# Run PUT for ltarce.
++set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile]
++
++# Check the output of this program.
++verbose "ltrace runtest output: $exec_output\n"
++if [regexp {ELF from incompatible architecture} $exec_output] {
++	fail "32-bit ltrace can not perform on 64-bit PUTs and rebuild ltrace in 64 bit mode!"
++	return 
++} elseif [ regexp {Couldn't get .hash data} $exec_output ] {
++	fail "Couldn't get .hash data!"
++	return
++}
++
++set pattern "func1(.*unfinished"
++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 100
++set pattern "func2(.*unfinished"
++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 100
++set pattern "func3(.*)"
++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 100
++set pattern "func2.resumed"
++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 100
++set pattern "func1.resumed"
++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 100
diff --git a/ltrace.spec b/ltrace.spec
index f1c92a4..cb27ee3 100644
--- a/ltrace.spec
+++ b/ltrace.spec
@@ -1,7 +1,7 @@
 Summary: Tracks runtime library calls from dynamically linked executables
 Name: ltrace
 Version: 0.6.0
-Release: 10%{?dist}
+Release: 11%{?dist}
 URL: http://ltrace.alioth.debian.org/
 License: GPLv2+
 Group: Development/Debuggers
@@ -29,6 +29,8 @@ Patch11: ltrace-0.6.0-vfork.patch
 Patch12: ltrace-0.6.0-thread-races.patch
 Patch13: ltrace-0.6.0-process-start.patch
 Patch14: ltrace-0.6.0-selinux.patch
+Patch15: ltrace-0.6.0-detach-sleeping.patch
+Patch16: ltrace-0.6.0-tail-return.patch
 
 %description
 Ltrace is a debugging program which runs a specified command until the
@@ -56,6 +58,8 @@ execution of processes.
 %patch12 -p1
 %patch13 -p1
 %patch14 -p1
+%patch15 -p1
+%patch16 -p1
 sed -i -e 's/-o root -g root//' Makefile.in
 
 %build


More information about the scm-commits mailing list