You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
237 lines
7.2 KiB
237 lines
7.2 KiB
6 years ago
|
NOTE: This patch has been forwardported from RHEL-6.7.
|
||
|
|
||
|
From: Pedro Alves <palves@redhat.com>
|
||
|
Date: Fri, 20 Feb 2015 19:10:08 +0000
|
||
|
Subject: [PATCH] PR18006: internal error if threaded program calls
|
||
|
clone(CLONE_VM)
|
||
|
|
||
|
On GNU/Linux, if a pthreaded program has a thread call clone(CLONE_VM)
|
||
|
directly, and then that clone LWP hits a debug event (breakpoint,
|
||
|
etc.) GDB internal errors. Threaded programs shouldn't really be
|
||
|
calling clone directly, but GDB shouldn't crash either.
|
||
|
|
||
|
The crash looks like this:
|
||
|
|
||
|
(gdb) break clone_fn
|
||
|
Breakpoint 1 at 0x4007d8: file clone-thread_db.c, line 35.
|
||
|
(gdb) r
|
||
|
...
|
||
|
[Thread debugging using libthread_db enabled]
|
||
|
...
|
||
|
[New Thread 0x7ffff7fc2700 (LWP 3886)]
|
||
|
../../gdb/linux-thread-db.c:437: internal-error: thread_get_info_callback: Assertion `inout->thread_info != NULL' failed.
|
||
|
A problem internal to GDB has been detected,
|
||
|
further debugging may prove unreliable.
|
||
|
|
||
|
The problem is that 'clone' ends up clearing the parent thread's tid
|
||
|
field in glibc's thread data structure. For x86_64, the glibc code in
|
||
|
question is here:
|
||
|
|
||
|
sysdeps/unix/sysv/linux/x86_64/clone.S:
|
||
|
|
||
|
...
|
||
|
testq $CLONE_THREAD, %rdi
|
||
|
jne 1f
|
||
|
testq $CLONE_VM, %rdi
|
||
|
movl $-1, %eax <----
|
||
|
jne 2f
|
||
|
movl $SYS_ify(getpid), %eax
|
||
|
syscall
|
||
|
2: movl %eax, %fs:PID
|
||
|
movl %eax, %fs:TID <----
|
||
|
1:
|
||
|
|
||
|
When GDB refreshes the thread list out of libthread_db, it finds a
|
||
|
thread with LWP with pid -1 (the clone's parent), which naturally
|
||
|
isn't yet on the thread list. GDB then tries to attach to that bogus
|
||
|
LWP id, that fails, and then GDB gets confused.
|
||
|
|
||
|
The fix is to detect the bad PID early.
|
||
|
|
||
|
Tested on x86-64 Fedora 20. GDBserver doesn't need any fix.
|
||
|
|
||
|
gdb/ChangeLog:
|
||
|
2015-02-20 Pedro Alves <palves@redhat.com>
|
||
|
|
||
|
PR threads/18006
|
||
|
* linux-thread-db.c (thread_get_info_callback): Return early if
|
||
|
the thread's lwp id is -1.
|
||
|
(check_event): On TD_DEATH, if the thread is not on the thread
|
||
|
list, warn instead of erroring out.
|
||
|
|
||
|
gdb/testsuite/ChangeLog:
|
||
|
2015-02-20 Pedro Alves <palves@redhat.com>
|
||
|
|
||
|
PR threads/18006
|
||
|
* gdb.threads/clone-thread_db.c: New file.
|
||
|
* gdb.threads/clone-thread_db.exp: New file.
|
||
|
---
|
||
|
gdb/linux-thread-db.c | 14 +++--
|
||
|
gdb/testsuite/gdb.threads/clone-thread_db.c | 73 +++++++++++++++++++++++++++
|
||
|
gdb/testsuite/gdb.threads/clone-thread_db.exp | 44 ++++++++++++++++
|
||
|
3 files changed, 128 insertions(+), 3 deletions(-)
|
||
|
create mode 100644 gdb/testsuite/gdb.threads/clone-thread_db.c
|
||
|
create mode 100644 gdb/testsuite/gdb.threads/clone-thread_db.exp
|
||
|
|
||
|
Index: gdb-7.6.1/gdb/linux-thread-db.c
|
||
|
===================================================================
|
||
|
--- gdb-7.6.1.orig/gdb/linux-thread-db.c
|
||
|
+++ gdb-7.6.1/gdb/linux-thread-db.c
|
||
|
@@ -422,6 +422,14 @@ thread_get_info_callback (const td_thrha
|
||
|
error (_("thread_get_info_callback: cannot get thread info: %s"),
|
||
|
thread_db_err_str (err));
|
||
|
|
||
|
+ if (ti.ti_lid == -1)
|
||
|
+ {
|
||
|
+ /* We'll get this if a threaded program has a thread call clone
|
||
|
+ with CLONE_VM. `clone' sets the pthread LID of the new LWP
|
||
|
+ to -1, which ends up clearing the parent thread's LID. */
|
||
|
+ return 0;
|
||
|
+ }
|
||
|
+
|
||
|
/* Fill the cache. */
|
||
|
thread_ptid = ptid_build (info->pid, ti.ti_lid, 0);
|
||
|
inout->thread_info = find_thread_ptid (thread_ptid);
|
||
|
@@ -1454,9 +1462,9 @@ check_event (ptid_t ptid)
|
||
|
case TD_DEATH:
|
||
|
|
||
|
if (!in_thread_list (ptid))
|
||
|
- error (_("Spurious thread death event."));
|
||
|
-
|
||
|
- detach_thread (ptid);
|
||
|
+ warning (_("Spurious thread death event."));
|
||
|
+ else
|
||
|
+ detach_thread (ptid);
|
||
|
|
||
|
break;
|
||
|
|
||
|
Index: gdb-7.6.1/gdb/testsuite/gdb.threads/clone-thread_db.c
|
||
|
===================================================================
|
||
|
--- /dev/null
|
||
|
+++ gdb-7.6.1/gdb/testsuite/gdb.threads/clone-thread_db.c
|
||
|
@@ -0,0 +1,75 @@
|
||
|
+/* This testcase is part of GDB, the GNU debugger.
|
||
|
+
|
||
|
+ Copyright 2015 Free Software Foundation, 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 3 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, see <http://www.gnu.org/licenses/>.
|
||
|
+
|
||
|
+ Test that GDB doesn't lose an event for a thread it didn't know
|
||
|
+ about, until an event is reported for it. */
|
||
|
+
|
||
|
+#define _GNU_SOURCE
|
||
|
+#include <sched.h>
|
||
|
+#include <assert.h>
|
||
|
+#include <stdlib.h>
|
||
|
+#include <sys/types.h>
|
||
|
+#include <sys/wait.h>
|
||
|
+#include <unistd.h>
|
||
|
+#include <pthread.h>
|
||
|
+
|
||
|
+#define STACK_SIZE 0x1000
|
||
|
+
|
||
|
+int clone_pid;
|
||
|
+
|
||
|
+static int
|
||
|
+clone_fn (void *unused)
|
||
|
+{
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+void *
|
||
|
+thread_fn (void *arg)
|
||
|
+{
|
||
|
+ unsigned char *stack;
|
||
|
+ int res;
|
||
|
+
|
||
|
+ stack = malloc (STACK_SIZE);
|
||
|
+ assert (stack != NULL);
|
||
|
+
|
||
|
+#ifdef __ia64__
|
||
|
+ clone_pid = __clone2 (clone_fn, stack, STACK_SIZE, CLONE_VM, NULL);
|
||
|
+#else
|
||
|
+ clone_pid = clone (clone_fn, stack + STACK_SIZE, CLONE_VM, NULL);
|
||
|
+#endif
|
||
|
+
|
||
|
+ assert (clone_pid > 0);
|
||
|
+
|
||
|
+ /* Wait for child. */
|
||
|
+ res = waitpid (clone_pid, NULL, __WCLONE);
|
||
|
+ assert (res != -1);
|
||
|
+
|
||
|
+ return NULL;
|
||
|
+}
|
||
|
+
|
||
|
+int
|
||
|
+main (int argc, char **argv)
|
||
|
+{
|
||
|
+ pthread_t child;
|
||
|
+
|
||
|
+ alarm (300);
|
||
|
+
|
||
|
+ pthread_create (&child, NULL, thread_fn, NULL);
|
||
|
+ pthread_join (child, NULL);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
Index: gdb-7.6.1/gdb/testsuite/gdb.threads/clone-thread_db.exp
|
||
|
===================================================================
|
||
|
--- /dev/null
|
||
|
+++ gdb-7.6.1/gdb/testsuite/gdb.threads/clone-thread_db.exp
|
||
|
@@ -0,0 +1,44 @@
|
||
|
+# This testcase is part of GDB, the GNU debugger.
|
||
|
+
|
||
|
+# Copyright 2015 Free Software Foundation, 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 3 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, see <http://www.gnu.org/licenses/>.
|
||
|
+
|
||
|
+# This only works on targets with the Linux kernel.
|
||
|
+if ![istarget *-*-linux*] {
|
||
|
+ return
|
||
|
+}
|
||
|
+
|
||
|
+set testfile "clone-thread_db"
|
||
|
+set srcfile ${testfile}.c
|
||
|
+set binfile ${objdir}/${subdir}/${testfile}
|
||
|
+
|
||
|
+if { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
|
||
|
+ untested $testfile.exp
|
||
|
+ return -1
|
||
|
+}
|
||
|
+
|
||
|
+gdb_start
|
||
|
+gdb_reinitialize_dir $srcdir/$subdir
|
||
|
+gdb_load ${binfile}
|
||
|
+
|
||
|
+if ![runto_main] {
|
||
|
+ untested "could not run to main"
|
||
|
+ return -1
|
||
|
+}
|
||
|
+
|
||
|
+gdb_test "break clone_fn" "Breakpoint.*at.*file.*$srcfile.*line.*"
|
||
|
+gdb_test "continue" "clone_fn .* at .*" "continue to clone_fn"
|
||
|
+
|
||
|
+gdb_test "continue" "exited normally.*" "continue to end"
|