aboutsummaryrefslogtreecommitdiff
path: root/gdb/testsuite/gdb.threads/main-thread-exit-during-detach.exp
blob: 2a9320a691477f74a573865016d14cc53304e6c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# Copyright 2023-2024 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/>.

# Check for a race condition where in non-stop mode, the user might
# have a thread other than the main (original) thread selected and use
# the 'detach' command.
#
# As GDB tries to detach it is possible that the main thread might
# exit, the main thread is still running due to non-stop mode.
#
# GDB used to assume that the main thread would always exist when
# processing the detach, clearly this isn't the case, and this
# assumption would lead to assertion failures and segfaults.
#
# Triggering the precise timing is pretty hard, we need the main
# thread to exit after the user has entered the 'detach' command, but
# before GDB enters the detach implementation and stops all threads,
# the window of opportunity for this bug is actually tiny.
#
# However, we can trigger this bug 100% from Python, as GDB's
# event-loop only kicks in once we return from a Python function.
# Thus, if we have a single Python function that causes the main
# thread to exit, and then calls detach GDB will not have a chance to
# handle the main thread exiting before entering the detach code.

standard_testfile

require allow_python_tests

if {[build_executable "failed to prepare" $testfile $srcfile \
	 {debug pthreads}] == -1} {
    return -1
}

# Run the test.  When SPAWN_INFERIOR is true the inferior is started
# as a separate process which GDB then attaches too.  When
# SPAWN_INFERIOR is false the inferior is started directly within GDB.

proc run_test { spawn_inferior } {
    save_vars { ::GDBFLAGS } {
	append ::GDBFLAGS " -ex \"set non-stop on\""
	clean_restart $::binfile
    }

    # Setup the inferior.  When complete the main thread (#1) will
    # still be running (due to non-stop mode), while the worker thread
    # (#2) will be stopped.
    #
    # There are two setup modes, when SPAWN_INFERIOR is true we span a
    # separate process and attach to it, after the attach both threads
    # are stopped, so it is necessary to resume thread #1.
    #
    # When SPAWN_INFERIOR is false we just start the inferior within
    # GDB, in this case we place a breakpoint that will be hit by
    # thread #2.  When the breakpoint is hit thread #1 will remain
    # running.
    if {$spawn_inferior} {
	set test_spawn_id [spawn_wait_for_attach $::binfile]
	set testpid [spawn_id_get_pid $test_spawn_id]

	set escapedbinfile  [string_to_regexp $::binfile]
	gdb_test -no-prompt-anchor "attach $testpid" \
	    "Attaching to program.*`?$escapedbinfile'?, process $testpid.*" \
	    "attach to the inferior"

	# Attaching to a multi-threaded application in non-stop mode
	# can result in thread stops being reported after the prompt
	# is displayed.
	#
	# Send a simple command now just to resync the command prompt.
	gdb_test "p 1 + 2" " = 3"

	# Set thread 1 (the current thread) running again.
	gdb_test "continue&"
    } else {
	if {![runto_main]} {
	    return -1
	}

	gdb_breakpoint "breakpt"
	gdb_continue_to_breakpoint "run to breakpoint"
    }

    # Switch to thread 2.
    gdb_test "thread 2" \
	[multi_line \
	     "Switching to thread 2\[^\r\n\]*" \
	     "#0\\s+.*"]

    # Create a Python function that sets a variable in the inferior and
    # then detaches.  Setting the variable in the inferior will allow the
    # main thread to exit, we even sleep for a short while in order to
    # give the inferior a chance to exit.
    #
    # However, we don't want GDB to notice the exit before we call detach,
    # which is why we perform both these actions from a Python function.
    gdb_test_multiline "Create worker function" \
	"python" "" \
	"import time" "" \
	"def set_and_detach():" "" \
	"   gdb.execute(\"set variable dont_exit_just_yet=0\")" "" \
	"   time.sleep(1)" "" \
	"   gdb.execute(\"detach\")" "" \
	"end" ""

    # The Python function performs two actions, the first causes the
    # main thread to exit, while the second detaches from the inferior.
    #
    # In both cases the stop arrives while GDB is processing the
    # detach, however, for remote targets GDB doesn't report the stop,
    # while for local targets GDB does report the stop.
    if {![gdb_protocol_is_remote]} {
	set stop_re "\\\[Thread.*exited\\\]\r\n"
    } else {
	set stop_re ""
    }
    gdb_test "python set_and_detach()" \
	"${stop_re}\\\[Inferior.*detached\\\]"
}

foreach_with_prefix spawn_inferior { true false } {
    if {$spawn_inferior && ![can_spawn_for_attach]} {
	# If spawning (and attaching too) a separate inferior is not
	# supported for the current board, then skip this test.
	continue
    }

    run_test $spawn_inferior
}