From patchwork Thu Dec 24 14:55:53 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pedro Alves X-Patchwork-Id: 10111 Received: (qmail 5486 invoked by alias); 24 Dec 2015 14:56:01 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 5475 invoked by uid 89); 24 Dec 2015 14:56:01 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-0.5 required=5.0 tests=AWL, BAYES_50, RP_MATCHES_RCVD, SPF_HELO_PASS autolearn=ham version=3.3.2 spammy=watchpoints, rwatch, awatch, thr2 X-HELO: mx1.redhat.com Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES256-GCM-SHA384 encrypted) ESMTPS; Thu, 24 Dec 2015 14:55:57 +0000 Received: from int-mx14.intmail.prod.int.phx2.redhat.com (int-mx14.intmail.prod.int.phx2.redhat.com [10.5.11.27]) by mx1.redhat.com (Postfix) with ESMTPS id 7C2AAA789; Thu, 24 Dec 2015 14:55:56 +0000 (UTC) Received: from [127.0.0.1] (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1]) by int-mx14.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id tBOEtsEV010202; Thu, 24 Dec 2015 09:55:54 -0500 Message-ID: <567C0779.909@redhat.com> Date: Thu, 24 Dec 2015 14:55:53 +0000 From: Pedro Alves User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Thunderbird/31.7.0 MIME-Version: 1.0 To: Eli Zaretskii CC: gdb-patches@sourceware.org Subject: [PATCH] thread ID ranges (Per-inferior thread IDs) References: <1450206316-25680-1-git-send-email-palves@redhat.com> <8337v333id.fsf@gnu.org> <5672B5D0.6020806@redhat.com> <83a8p9yq0p.fsf@gnu.org> <56744C3A.3040407@redhat.com> In-Reply-To: <56744C3A.3040407@redhat.com> On 12/18/2015 06:11 PM, Pedro Alves wrote: >>> >> +@var{thread-id}. It can be a single thread ID, as shown in the first >>> >> +field of the @samp{info threads} display, with or without an inferior >>> >> +qualifier (e.g., @samp{2.1} or @samp{1}); or it could be a range of >>> >> +thread numbers, as in @code{2-4}. To apply a command to all threads >>> >> +in descending order, type @kbd{thread apply all @var{command}}. To >>> >> +apply a command to all threads in ascending order, type @kbd{thread >>> >> +apply all -ascending @var{command}}. >> > >> > Can I use a range of qualified IDs, as in "2.1-2.4"? > I was thinking of adding support for ranges of threads in the "2.1-4" > form, that is, I didn't think it'd make sense to cross inferior, > like "2.1-3.3", but I haven't implemented it yet. > I did this now. See the patch below, which applies on top of the previous one (so you can easily see the new additions). I expect to squash it all into one patch and resubmit later on. (I also found a couple other "threadnum" references I had somehow misssed previously.) I (force) pushed this to the users/palves/thread-ids-per-inferior branch for testing convenience. From 2ff6a3cfdab76841f54ce657af381e6139b0a2df Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Thu, 24 Dec 2015 14:37:39 +0000 Subject: [PATCH] thread ID lists and ranges --- gdb/doc/gdb.texinfo | 56 ++++--- gdb/gdbthread.h | 91 +++++++++++ gdb/testsuite/gdb.multi/per-inferior-tids.c | 20 ++- gdb/testsuite/gdb.multi/per-inferior-tids.exp | 135 ++++++++++++---- gdb/testsuite/gdb.threads/pthreads.exp | 43 ++++++ gdb/testsuite/gdb.threads/thread-find.exp | 4 +- gdb/thread.c | 215 ++++++++++++++++++++++---- 7 files changed, 482 insertions(+), 82 deletions(-) diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 071212e..06981c3 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -2909,6 +2909,20 @@ Until you create a second inferior, @value{GDBN} does not show the the full @var{inferior-num}.@var{thread-num} form to refer to threads of inferior 1, the initial inferior. +@anchor{thread ID lists} +@cindex thread ID lists +Some commands accept a space-separated @dfn{thread ID list} as +argument. A list element can be a thread ID as shown in the first +field of the @samp{info threads} display, with or without an inferior +qualifier (e.g., @samp{2.1} or @samp{1}); or can be a range of thread +numbers, again with or without an inferior qualifier, as in +@var{inf1}.@var{thr1}-@var{thr2} or @var{thr1}-@var{thr2} (e.g., +@samp{1.2-4} or @samp{2-4}). For example, if the current inferior is +1, the thread list @samp{1 2-3 4.5 6.7-9} includes threads 1 to 3 of +inferior 1, thread 5 of inferior 4 and threads 7 to 9 of inferior 6. +That is, in expanded qualified form, the same as @samp{1.1 1.2 1.3 4.5 +6.7 6.8 6.9}. + @anchor{global thread numbers} @cindex global thread number @cindex global thread identifier (GDB) @@ -2931,10 +2945,13 @@ general information on convenience variables. @table @code @kindex info threads -@item info threads @r{[}@var{id}@dots{}@r{]} -Display a summary of all threads currently in your program. Optional -argument @var{id}@dots{} is one or more thread ids separated by spaces, and -means to print information only about the specified thread or threads. +@item info threads @r{[}@var{thread-id-list}@r{]} + +Display information about one or more threads. With no arguments +displays information about all threads. You can specify the list of +threads that you want to display using the thread ID list syntax +(@pxref{thread ID lists}). + @value{GDBN} displays for each thread (in this order): @enumerate @@ -3025,17 +3042,14 @@ threads. @kindex thread apply @cindex apply command to several threads -@item thread apply [@var{thread-id} | all [-ascending]] @var{command} +@item thread apply [@var{thread-id-list} | all [-ascending]] @var{command} The @code{thread apply} command allows you to apply the named -@var{command} to one or more threads. Specify the numbers of the -threads that you want affected with the command argument -@var{thread-id}. It can be a single thread ID, as shown in the first -field of the @samp{info threads} display, with or without an inferior -qualifier (e.g., @samp{2.1} or @samp{1}); or it could be a range of -thread numbers, as in @code{2-4}. To apply a command to all threads -in descending order, type @kbd{thread apply all @var{command}}. To -apply a command to all threads in ascending order, type @kbd{thread -apply all -ascending @var{command}}. +@var{command} to one or more threads. Specify the threads that you +want affected using the thread ID list syntax (@pxref{thread ID +lists}), or specify @code{all} to apply to all threads. To apply a +command to all threads in descending order, type @kbd{thread apply all +@var{command}}. To apply a command to all threads in ascending order, +type @kbd{thread apply all -ascending @var{command}}. @kindex thread name @@ -4033,7 +4047,7 @@ slow down the running of your program. @table @code @kindex watch -@item watch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{threadnum}@r{]} @r{[}mask @var{maskvalue}@r{]} +@item watch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]} Set a watchpoint for an expression. @value{GDBN} will break when the expression @var{expr} is written into by the program and its value changes. The simplest (and the most popular) use of this command is @@ -4043,9 +4057,9 @@ to watch the value of a single variable: (@value{GDBP}) watch foo @end smallexample -If the command includes a @code{@r{[}thread @var{threadnum}@r{]}} +If the command includes a @code{@r{[}thread @var{thread-id}@r{]}} argument, @value{GDBN} breaks only when the thread identified by -@var{threadnum} changes the value of @var{expr}. If any other threads +@var{thread-id} changes the value of @var{expr}. If any other threads change the value of @var{expr}, @value{GDBN} will not break. Note that watchpoints restricted to a single thread in this way only work with Hardware Watchpoints. @@ -4077,12 +4091,12 @@ Examples: @end smallexample @kindex rwatch -@item rwatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{threadnum}@r{]} @r{[}mask @var{maskvalue}@r{]} +@item rwatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]} Set a watchpoint that will break when the value of @var{expr} is read by the program. @kindex awatch -@item awatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{threadnum}@r{]} @r{[}mask @var{maskvalue}@r{]} +@item awatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]} Set a watchpoint that will break when @var{expr} is either read from or written into by the program. @@ -27781,10 +27795,10 @@ current-thread-id="1",number-of-threads="3" @subsubheading Synopsis @smallexample - -thread-select @var{threadnum} + -thread-select @var{thread-id} @end smallexample -Make thread with global thread number @var{threadnum} the current +Make thread with global thread number @var{thread-id} the current thread. It prints the number of the new current thread, and the topmost frame for that thread. diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h index 99fb03f..35911d7 100644 --- a/gdb/gdbthread.h +++ b/gdb/gdbthread.h @@ -30,6 +30,7 @@ struct symtab; #include "btrace.h" #include "common/vec.h" #include "target/waitstatus.h" +#include "cli/cli-utils.h" /* Frontend view of the thread state. Possible extensions: stepping, finishing, until(ling),... */ @@ -410,6 +411,96 @@ extern int valid_global_thread_id (int thread); thrown. */ struct thread_info *parse_thread_id (const char *tidstr, const char **end); +/* The possible states of the tid range parser's state machine. */ +enum tid_range_state +{ + /* Parsing the inferior number. */ + TID_RANGE_STATE_INFERIOR, + + /* Parsing the thread number or thread number range. */ + TID_RANGE_STATE_THREAD_RANGE, +}; + +/* An object of this type is passed to tid_range_parser_get_tid. It + must be initialized by calling tid_range_parser_init. This type is + defined here so that it can be stack-allocated, but all members + should be treated as opaque. */ +struct tid_range_parser +{ + /* What sub-component are we expecting. */ + enum tid_range_state state; + + /* The string being parsed. When parsing has finished, this points + past the last parsed token. */ + const char *string; + + /* The range parser state when we're parsing the thread number + sub-component. */ + struct get_number_or_range_state range_parser; + + /* Last inferior number returned. */ + int inf_num; + + /* True if the TID last parsed was explicitly inferior-qualified. + IOW, whether the spec specified an inferior number + explicitly. */ + int qualified; + + /* The inferior number to assume if the TID is not qualified. */ + int default_inferior; +}; + +/* Initialize a tid_range_parser for use with + tid_range_parser_get_tid. TIDLIST is the string to be parsed. + DEFAULT_INFERIOR is the inferior number to assume if a + non-qualified thread ID is found. */ +extern void tid_range_parser_init (struct tid_range_parser *parser, + const char *tidlist, + int default_inferior); + +/* Parse a thread ID or a thread range list. + + A range will be of the form + .- and will represent + all the threads of inferior inferior_num with number between + thread_number1 and thread_number2, inclusive. can + also be omitted, as in -, in which + case GDB infers the inferior number from the current inferior. + + While processing a thread ID range list, this function is called + iteratively; At each call it will return (in the INF_NUM and + THR_NUM output parameters) the next thread in the range. + + At the beginning of parsing a thread range, the char pointer + PARSER->string will be advanced past and left + pointing at the '-' token. Subsequent calls will not advance the + pointer until the range is completed. The call that completes the + range will advance the pointer past . */ +extern void tid_range_parser_get_tid (struct tid_range_parser *parser, + int *inf_num, int *thr_num); + +/* Returns non-zero if parsing has completed. */ +extern int tid_range_parser_finished (struct tid_range_parser *parser); + +/* Return the string being parsed. When parsing has finished, this + points past the last parsed token. */ +const char *tid_range_parser_string (struct tid_range_parser *parser); + +/* True if the TID last parsed was explicitly inferior-qualified. + IOW, whether the spec specified an inferior number explicitly. */ +extern int tid_range_parser_qualified (struct tid_range_parser *parser); + +/* Accept a string-form list of thread IDs such as is accepted by + tid_range_parser_get_tid. Return TRUE if the INF_NUM.THR.NUM + thread is in the list. DEFAULT_INFERIOR is the inferior number to + assume if a non-qualified thread ID is found in the list. + + By definition, an empty list includes all threads. This is to be + interpreted as typing a command such as "info threads" with no + arguments. */ +extern int tid_is_in_list (const char *list, int default_inferior, + int inf_num, int thr_num); + /* Search function to lookup a thread by 'pid'. */ extern struct thread_info *find_thread_ptid (ptid_t ptid); diff --git a/gdb/testsuite/gdb.multi/per-inferior-tids.c b/gdb/testsuite/gdb.multi/per-inferior-tids.c index 9e77031..00a8298 100644 --- a/gdb/testsuite/gdb.multi/per-inferior-tids.c +++ b/gdb/testsuite/gdb.multi/per-inferior-tids.c @@ -18,23 +18,35 @@ #include #include +pthread_t child_thread[2]; + void * -thread_function (void *arg) +thread_function2 (void *arg) { while (1) sleep (1); } +void * +thread_function1 (void *arg) +{ + pthread_create (&child_thread[1], NULL, thread_function2, NULL); + + while (1) + sleep (1); +} + int main (void) { - pthread_t child_thread; int i; alarm (300); - pthread_create (&child_thread, NULL, thread_function, NULL); - pthread_join (child_thread, NULL); + pthread_create (&child_thread[0], NULL, thread_function1, NULL); + + for (i = 0; i < 2; i++) + pthread_join (child_thread[i], NULL); return 0; } diff --git a/gdb/testsuite/gdb.multi/per-inferior-tids.exp b/gdb/testsuite/gdb.multi/per-inferior-tids.exp index e892758..d947fbc 100644 --- a/gdb/testsuite/gdb.multi/per-inferior-tids.exp +++ b/gdb/testsuite/gdb.multi/per-inferior-tids.exp @@ -17,6 +17,14 @@ load_lib gdb-python.exp standard_testfile +# Multiple inferiors are needed, therefore both native and extended +# gdbserver modes are supported. Only non-extended gdbserver is not +# supported. +if [target_info exists use_gdb_stub] { + untested ${testfile}.exp + return +} + if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} {pthreads debug}] } { return -1 } @@ -27,10 +35,24 @@ if { ![runto_main] } then { return -1 } +# Helper procedure. issue "info threads TID_LIST" and expect EXPECTED +# (a list of thread ids) to be displayed. +proc info_threads {tid_list expected {message ""}} { + set any "\[^\r\n\]*" + set expected [string_to_regexp $expected] + set r [join $expected " ${any}\r\n${any} "] + set r "${any} $r ${any}" + set cmd "info threads $tid_list" + if {$message == ""} { + set message $cmd + } + gdb_test $cmd $r $message +} + # "info threads" while there's only inferior 1 should show # single-number thread IDs. with_test_prefix "single inferior" { - gdb_test "info threads" " 1 .* main .* at .*$srcfile:.*" + info_threads "" "1" gdb_test "thread" "Current thread is 1 .*" } @@ -43,7 +65,7 @@ with_test_prefix "two inferiors" { # Now that we'd added another inferior, thread IDs now show the # inferior number. - gdb_test "info threads" " 1\.1 .* main .* at .*$srcfile:.*" + info_threads "" "1.1" gdb_test "thread" "Current thread is 1\.1 .*" @@ -54,36 +76,28 @@ with_test_prefix "two inferiors" { # Now that we'd added another inferior, thread IDs now show the # inferior number. - gdb_test "info threads" \ - [multi_line \ - " 1\.1 .* main .* at .*$srcfile:.*" \ - "\\* 2\.1 .* main .* at .*$srcfile:.*"] \ + info_threads "" "1.1 2.1" \ "info threads show inferior numbers" gdb_test "thread" "Current thread is 2\.1 .*" \ "switch to thread using extended thread ID" - gdb_breakpoint "thread_function" + gdb_breakpoint "thread_function1" gdb_continue_to_breakpoint "once" gdb_test "inferior 1" "Switching to inferior 1 .*" gdb_continue_to_breakpoint "twice" - gdb_test "info threads" \ - [multi_line \ - " 1\.1 .*" \ - "\\* 1\.2 .* thread_function .* at .*$srcfile:.*" \ - " 2\.1 .*" \ - " 2\.2 .* thread_function .* at .*$srcfile:.*"] \ + info_threads "" "1.1 1.2 2.1 2.2" \ "info threads again" # Same, but show the global ID. gdb_test "info threads -gid" \ [multi_line \ " 1\.1 +1 +.*" \ - "\\* 1\.2 +4 +.* thread_function .* at .*$srcfile:.*" \ + "\\* 1\.2 +4 +.* thread_function1 .* at .*$srcfile:.*" \ " 2\.1 +2 +.*" \ - " 2\.2 +3 +.* thread_function .* at .*$srcfile:.*"] + " 2\.2 +3 +.* thread_function1 .* at .*$srcfile:.*"] # Confirm the convenience variables show the expected numbers. gdb_test "p \$_thread == 2" " = 1" @@ -94,12 +108,85 @@ with_test_prefix "two inferiors" { # global ID by mistake. gdb_test "thread 4" "Unknown thread 1.4\\." + # Test thread ID list parsing. Test qualified and unqualified + # IDs; qualified and unqualified ranges; invalid IDs and invalid + # ranges. + + # First spawn a couple more threads so ranges includes more than + # two threads. + with_test_prefix "more threads" { + gdb_breakpoint "thread_function2" + + gdb_test "inferior 2" "Switching to inferior 2 .*" + gdb_continue_to_breakpoint "once" + + gdb_test "inferior 1" "Switching to inferior 1 .*" + gdb_continue_to_breakpoint "twice" + } + + info_threads "1 2 3" \ + "1.1 1.2 1.3" + + # Same, but with qualified thread IDs. + info_threads "1.1 1.2 1.3 2.1 2.2" \ + "1.1 1.2 1.3 2.1 2.2" + + # Test a thread number range. + info_threads "1-3" \ + "1.1 1.2 1.3" + + # Same, but using a qualified range. + info_threads "1.1-3" \ + "1.1 1.2 1.3" + + # A mix of qualified and unqualified thread IDs/ranges. + info_threads "1.1 2-3" \ + "1.1 1.2 1.3" + + info_threads "1 1.2-3" \ + "1.1 1.2 1.3" + + # Likewise, but mix inferiors too. + info_threads "2.1 2-3" \ + "1.2 1.3 2.1" + + # Multiple ranges with mixed explicit inferiors. + info_threads "1.1-2 2.2-3" \ + "1.1 1.2 2.2 2.3" + + # Now test a set of invalid thread IDs/ranges. + + gdb_test "info threads 1." \ + "Invalid thread ID: 1." + + gdb_test "info threads 1-3 1." \ + "Invalid thread ID: 1." + + gdb_test "info threads 1.1.1" \ + "Invalid thread ID: 1.1.1" + + gdb_test "info threads 2 1.1.1" \ + "Invalid thread ID: 1.1.1" + + gdb_test "info threads 1.1.1 2" \ + "Invalid thread ID: 1.1.1 2" + + gdb_test "info threads 1-2.1" \ + "Invalid thread ID: 1-2.1" + + # Check that we do parse the inferior number. + gdb_test "info threads 3.1" \ + "No threads match '3.1'\." + # If Python is configured, check that InferiorThread.global_num # returns the expected number. if { ![skip_python_tests] } { - gdb_py_test_silent_cmd "python t0 = gdb.selected_thread ()" "test gdb.selected_thread" 1 - gdb_test "python print ('result = %s' % t0.num)" " = 2" "test InferiorThread.num" - gdb_test "python print ('result = %s' % t0.global_num)" " = 4" "test InferiorThread.global_num" + gdb_py_test_silent_cmd "python t0 = gdb.selected_thread ()" \ + "test gdb.selected_thread" 1 + gdb_test "python print ('result = %s' % t0.num)" " = 3" \ + "test InferiorThread.num" + gdb_test "python print ('result = %s' % t0.global_num)" " = 6" \ + "test InferiorThread.global_num" } } @@ -112,10 +199,7 @@ with_test_prefix "back to one inferior" { # "info threads" while there's only inferior 1 should show # single-number thread IDs. - gdb_test "info threads" \ - [multi_line \ - "\\* 1 .*" \ - " 2 .* thread_function .* at .*$srcfile:.*"] + info_threads "" "1 2 3" gdb_test "thread" "Current thread is 1 .*" } @@ -129,10 +213,7 @@ with_test_prefix "single-inferior but not initial" { # Now that we'd added another inferior, thread IDs should show the # inferior number. - gdb_test "info threads" \ - [multi_line \ - "\\* 1\.1 .*" \ - " 1\.2 .* thread_function .* at .*$srcfile:.*"] \ + info_threads "" "1.1 1.2 1.3" \ "info threads with multiple inferiors" gdb_test "thread" "Current thread is 1\.1 .*" @@ -146,7 +227,7 @@ with_test_prefix "single-inferior but not initial" { # Even though we have a single inferior, its number is > 1, so # thread IDs should show the inferior number. - gdb_test "info threads" " 3\.1 .* main .* at .*$srcfile:.*" \ + info_threads "" "3.1" \ "info threads with single inferior" gdb_test "thread" "Current thread is 3\.1 .*" "thread again" diff --git a/gdb/testsuite/gdb.threads/pthreads.exp b/gdb/testsuite/gdb.threads/pthreads.exp index b456641..415e2e6 100644 --- a/gdb/testsuite/gdb.threads/pthreads.exp +++ b/gdb/testsuite/gdb.threads/pthreads.exp @@ -244,6 +244,49 @@ proc check_backtraces {} { ".* in main .* in thread1 .* in thread2.*" \ "apply backtrace command to all three threads" + # Same, but with qualified thread IDs. + gdb_test "thread apply 1.1 1.2 1.3 bt" \ + ".* in main .* in thread1 .* in thread2.*" + + # Test a thread number range. + gdb_test "thread apply 1-3 bt" \ + ".* in main .* in thread1 .* in thread2.*" + + # Same, but using a qualified range. + gdb_test "thread apply 1.1-3 bt" \ + ".* in main .* in thread1 .* in thread2.*" + + # A mix of qualified and unqualified thread IDs/ranges. + gdb_test "thread apply 1.1 2-3 bt" \ + ".* in main .* in thread1 .* in thread2.*" + + gdb_test "thread apply 1 1.2-3 bt" \ + ".* in main .* in thread1 .* in thread2.*" + + # Now test a set of invalid thread IDs/ranges. + + gdb_test "thread apply 1. bt" \ + "Invalid thread ID: 1. bt" + + gdb_test "thread apply 1-3 1. bt" \ + "Invalid thread ID: 1. bt" + + gdb_test "thread apply 1.1.1 bt" \ + "Invalid thread ID: 1.1.1 bt" + + gdb_test "thread apply 2 1.1.1 bt" \ + "Invalid thread ID: 1.1.1 bt" + + gdb_test "thread apply 1.1.1 2 bt" \ + "Invalid thread ID: 1.1.1 2 bt" + + gdb_test "thread apply 1-2.1 bt" \ + "Invalid thread ID: 1-2.1 bt" + + # Check that we do parse the inferior number. + gdb_test "thread apply 2.1 bt" \ + "Unknown thread 2.1" + # Check that we can do thread specific backtraces # This also tests that we can do thread specific breakpoints. diff --git a/gdb/testsuite/gdb.threads/thread-find.exp b/gdb/testsuite/gdb.threads/thread-find.exp index 1af6bbd..ad66e38 100644 --- a/gdb/testsuite/gdb.threads/thread-find.exp +++ b/gdb/testsuite/gdb.threads/thread-find.exp @@ -276,9 +276,9 @@ gdb_test_multiple "info threads 3-3" "info threads 3-3" { # Test bad input gdb_test "info thread foo" \ - "Args must be numbers or '.' variables." \ + "Invalid thread ID: foo" \ "info thread foo" gdb_test "info thread foo -1" \ - "Args must be numbers or '.' variables." \ + "Invalid thread ID: foo -1" \ "info thread foo -1" diff --git a/gdb/thread.c b/gdb/thread.c index 8fc7b25..21238c8 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -1126,8 +1126,8 @@ pc_in_thread_step_range (CORE_ADDR pc, struct thread_info *thread) } static int -should_print_thread (const char *requested_threads, int global_ids, - int pid, struct thread_info *thr) +should_print_thread (const char *requested_threads, int default_inferior, + int global_ids, int pid, struct thread_info *thr) { if (pid != -1 && ptid_get_pid (thr->ptid) != pid && requested_threads != NULL && *requested_threads != '\0') @@ -1135,9 +1135,14 @@ should_print_thread (const char *requested_threads, int global_ids, if (requested_threads != NULL && *requested_threads != '\0') { - int id = global_ids ? thr->global_num : thr->per_inf_num; + int in_list; - if (number_is_in_list (requested_threads, id)) + if (global_ids) + in_list = number_is_in_list (requested_threads, thr->global_num); + else + in_list = tid_is_in_list (requested_threads, default_inferior, + thr->inf->num, thr->per_inf_num); + if (in_list) { if (pid != -1 && ptid_get_pid (thr->ptid) != pid) error (_("Requested thread not found in requested process")); @@ -1170,6 +1175,7 @@ print_thread_info_1 (struct ui_out *uiout, char *requested_threads, const char *extra_info, *name, *target_id; int current_thread = -1; struct inferior *inf; + int current_inf_num = current_inferior ()->num; update_thread_list (); current_ptid = inferior_ptid; @@ -1188,7 +1194,8 @@ print_thread_info_1 (struct ui_out *uiout, char *requested_threads, for (tp = thread_list; tp; tp = tp->next) { - if (!should_print_thread (requested_threads, global_ids, pid, tp)) + if (!should_print_thread (requested_threads, current_inf_num, + global_ids, pid, tp)) continue; ++n_threads; @@ -1235,7 +1242,8 @@ print_thread_info_1 (struct ui_out *uiout, char *requested_threads, if (ptid_equal (tp->ptid, current_ptid)) current_thread = tp->global_num; - if (!should_print_thread (requested_threads, global_ids, pid, tp)) + if (!should_print_thread (requested_threads, current_inf_num, + global_ids, pid, tp)) continue; chain2 = make_cleanup_ui_out_tuple_begin_end (uiout, NULL); @@ -1774,13 +1782,16 @@ print_thread_id (struct thread_info *thr) return s; } + +/* Implementation of the "thread apply" command. */ + static void thread_apply_command (char *tidlist, int from_tty) { char *cmd; struct cleanup *old_chain; char *saved_cmd; - struct get_number_or_range_state state; + struct tid_range_parser parser; if (tidlist == NULL || *tidlist == '\000') error (_("Please specify a thread ID list")); @@ -1795,33 +1806,44 @@ thread_apply_command (char *tidlist, int from_tty) saved_cmd = xstrdup (cmd); old_chain = make_cleanup (xfree, saved_cmd); - init_number_or_range (&state, tidlist); - while (!state.finished && state.string < cmd) - { - struct thread_info *tp; - int start; - - start = get_number_or_range (&state); + make_cleanup_restore_current_thread (); - make_cleanup_restore_current_thread (); + tid_range_parser_init (&parser, tidlist, current_inferior ()->num); + while (!tid_range_parser_finished (&parser) + && tid_range_parser_string (&parser) < cmd) + { + struct thread_info *tp = NULL; + struct inferior *inf; + int inf_num, thr_num; - tp = find_thread_id (current_inferior (), start); + tid_range_parser_get_tid (&parser, &inf_num, &thr_num); + inf = find_inferior_id (inf_num); + if (inf != NULL) + tp = find_thread_id (inf, thr_num); + if (tp == NULL) + { + if (inferior_list->next != NULL || inferior_list->num != 1 + || tid_range_parser_qualified (&parser)) + warning (_("Unknown thread %d.%d"), inf_num, thr_num); + else + warning (_("Unknown thread %d"), thr_num); + continue; + } - if (!tp) - warning (_("Unknown thread %d."), start); - else if (!thread_alive (tp)) - warning (_("Thread %d has terminated."), start); - else + if (!thread_alive (tp)) { - switch_to_thread (tp->ptid); + warning (_("Thread %s has terminated."), print_thread_id (tp)); + continue; + } - printf_filtered (_("\nThread %s (%s):\n"), print_thread_id (tp), - target_pid_to_str (inferior_ptid)); - execute_command (cmd, from_tty); + switch_to_thread (tp->ptid); - /* Restore exact command used previously. */ - strcpy (cmd, saved_cmd); - } + printf_filtered (_("\nThread %s (%s):\n"), print_thread_id (tp), + target_pid_to_str (inferior_ptid)); + execute_command (cmd, from_tty); + + /* Restore exact command used previously. */ + strcpy (cmd, saved_cmd); } do_cleanups (old_chain); @@ -2003,6 +2025,143 @@ parse_thread_id (const char *tidstr, const char **end) return tp; } +/* See gdbthread.h. */ + +void +tid_range_parser_init (struct tid_range_parser *parser, const char *tidlist, + int default_inferior) +{ + parser->state = TID_RANGE_STATE_INFERIOR; + parser->string = tidlist; + parser->inf_num = 0; + parser->qualified = 0; + parser->default_inferior = default_inferior; +} + +/* See gdbthread.h. */ + +int +tid_range_parser_finished (struct tid_range_parser *parser) +{ + switch (parser->state) + { + case TID_RANGE_STATE_INFERIOR: + return *parser->string == '\0'; + case TID_RANGE_STATE_THREAD_RANGE: + return parser->range_parser.finished; + } + + gdb_assert_not_reached (_("unhandled state")); +} + +/* See gdbthread.h. */ + +const char * +tid_range_parser_string (struct tid_range_parser *parser) +{ + switch (parser->state) + { + case TID_RANGE_STATE_INFERIOR: + return parser->string; + case TID_RANGE_STATE_THREAD_RANGE: + return parser->range_parser.string; + } + + gdb_assert_not_reached (_("unhandled state")); +} + +/* See gdbthread.h. */ + +int +tid_range_parser_qualified (struct tid_range_parser *parser) +{ + return parser->qualified; +} + +/* See gdbthread.h. */ + +void +tid_range_parser_get_tid (struct tid_range_parser *parser, int *inf_num, + int *thr_num) +{ + if (parser->state == TID_RANGE_STATE_INFERIOR) + { + const char *p; + const char *space; + + space = skip_to_space (parser->string); + + p = parser->string; + while (p < space && *p != '.') + p++; + if (p < space) + { + const char *dot = p; + + /* Parse number to the left of the dot. */ + p = parser->string; + parser->inf_num = get_number_trailer (&p, '.'); + if (parser->inf_num == 0) + error (_("Invalid thread ID: %s"), parser->string); + + parser->qualified = 1; + p = dot + 1; + + if (isspace (*p)) + error (_("Invalid thread ID: %s"), parser->string); + } + else + { + parser->inf_num = parser->default_inferior; + parser->qualified = 0; + p = parser->string; + } + + init_number_or_range (&parser->range_parser, p); + parser->state = TID_RANGE_STATE_THREAD_RANGE; + } + + *inf_num = parser->inf_num; + *thr_num = get_number_or_range (&parser->range_parser); + if (*thr_num == 0) + error (_("Invalid thread ID: %s"), parser->string); + + /* If we successfully parsed a thread number or finished parsing a + thread range, switch back to assuming the next TID is + inferior-qualified. */ + if (parser->range_parser.end_ptr == NULL + || parser->range_parser.string == parser->range_parser.end_ptr) + { + parser->state = TID_RANGE_STATE_INFERIOR; + parser->string = parser->range_parser.string; + } +} + +/* See gdbthread.h. */ + +int +tid_is_in_list (const char *list, int default_inferior, + int inf_num, int thr_num) +{ + struct tid_range_parser parser; + + if (list == NULL || *list == '\0') + return 1; + + tid_range_parser_init (&parser, list, default_inferior); + while (!tid_range_parser_finished (&parser)) + { + int tmp_inf, tmp_thr; + + tid_range_parser_get_tid (&parser, &tmp_inf, &tmp_thr); + if (tmp_inf == 0 || tmp_thr == 0) + error (_("Invalid thread ID: %s"), parser.string); + if (tmp_inf == inf_num && tmp_thr == thr_num) + return 1; + } + return 0; +} + static int do_captured_thread_select (struct ui_out *uiout, void *tidstr_v) {