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.
236 lines
7.2 KiB
236 lines
7.2 KiB
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"
|
|
|