From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 6010 invoked by alias); 18 Jan 2011 17:10:23 -0000 Received: (qmail 5999 invoked by uid 22791); 18 Jan 2011 17:10:18 -0000 X-SWARE-Spam-Status: No, hits=-6.8 required=5.0 tests=AWL,BAYES_00,RCVD_IN_DNSWL_HI,SPF_HELO_PASS,TW_HP,T_RP_MATCHES_RCVD X-Spam-Check-By: sourceware.org Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Tue, 18 Jan 2011 17:10:09 +0000 Received: from int-mx10.intmail.prod.int.phx2.redhat.com (int-mx10.intmail.prod.int.phx2.redhat.com [10.5.11.23]) by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id p0IHA54A022300 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK); Tue, 18 Jan 2011 12:10:06 -0500 Received: from ns3.rdu.redhat.com (ns3.rdu.redhat.com [10.11.255.199]) by int-mx10.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id p0IHA5se018025; Tue, 18 Jan 2011 12:10:05 -0500 Received: from opsy.redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1]) by ns3.rdu.redhat.com (8.13.8/8.13.8) with ESMTP id p0IHA4x1009915; Tue, 18 Jan 2011 12:10:04 -0500 Received: by opsy.redhat.com (Postfix, from userid 500) id C5F7F378326; Tue, 18 Jan 2011 10:10:03 -0700 (MST) From: Tom Tromey To: Eli Zaretskii Cc: gdb-patches@sourceware.org Subject: Re: RFA: display thread names References: Date: Tue, 18 Jan 2011 22:49:00 -0000 In-Reply-To: (Eli Zaretskii's message of "Wed, 12 Jan 2011 11:31:41 -0500") Message-ID: User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/23.2 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org X-SW-Source: 2011-01/txt/msg00385.txt.bz2 >>>>> "Eli" == Eli Zaretskii writes: Eli> Is there a plan to add a possibility to switch to a thread by its Eli> name, and otherwise use the name in the same context where we Eli> currently allow only its number? It seems like a good idea but I have no concrete plan to do it. [...] Eli> The long lines in this example needs to be broken in two. Fixed. Thiago> I've been told that target macros are deprecated and new code should use Thiago> functions instead (as in target_fetch_registers). Thanks, I fixed this in this version. Please review. Tom 2011-01-18 Tom Tromey PR mi/8618: * thread.c (free_thread): Free 'name'. (print_thread_info): Emit thread name. Change CLI output. (thread_name_command): New function. (do_captured_thread_select): Emit newline. (_initialize_thread): Register 'thread name' command. * target.h (struct target_ops) : New field. (target_thread_name): New macro. * target.c (update_current_target): Handle to_thread_name. * python/py-infthread.c (thpy_get_name): New function. (thpy_set_name): Likewise. (thread_object_getset): Add "name". * linux-nat.c (linux_nat_thread_name): New function. (linux_nat_add_target): Set to_thread_name. * gdbthread.h (struct thread_info) : New field. 2011-01-18 Tom Tromey * gdb.texinfo (Threads): Document thread name output and `thread name' command. (Threads In Python): Document Thread.name attribute. (GDB/MI Thread Commands): Document thread attributes. 2011-01-18 Tom Tromey * gdb.python/py-infthread.exp: Add thread tests. diff --git a/gdb/NEWS b/gdb/NEWS index 1f02dfc..c2904be 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -15,6 +15,14 @@ It is like the "dir" command except that it replaces the source path list instead of augmenting it. +* GDB now understands thread names. + + On GNU/Linux, "info threads" will display the thread name as set by + prctl or pthread_setname_np. + + There is also a new command, "thread name", which can be used to + assign a name internally for GDB to display. + * OpenCL C Initial support for the OpenCL C language (http://www.khronos.org/opencl) has been integrated into GDB. @@ -46,6 +54,9 @@ ** New function gdb.newest_frame returns the newest frame in the selected thread. + ** The gdb.InferiorThread class has a new "name" attribute. This + holds the thread's name. + * C++ Improvements: ** GDB now puts template parameters in scope when debugging in an diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index b86e8d2..cbe6100 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -2718,6 +2718,11 @@ the thread number assigned by @value{GDBN} the target system's thread identifier (@var{systag}) @item +the thread's name, if one is known. A thread can either be named by +the user (see @code{thread name}, below), or, in some cases, by the +program itself. + +@item the current stack frame summary for that thread @end enumerate @@ -2787,6 +2792,19 @@ shown in the first field of the @samp{info threads} display; or it could be a range of thread numbers, as in @code{2-4}. To apply a command to all threads, type @kbd{thread apply all @var{command}}. +@kindex thread name +@cindex name a thread +@item thread name [@var{name}] +This command assigns a name to the current thread. If no argument is +given, any existing user-specified name is removed. The thread name +appears in the @samp{info threads} display. + +On some systems, such as @sc{gnu}/Linux, @value{GDBN} is able to +determine the name of the thread as given by the OS. On these +systems, a name specified with @samp{thread name} will override the +system-give name, and removing the user-specified name will cause +@value{GDBN} to once again display the system-specified name. + @kindex set print thread-events @cindex print messages on thread start and exit @item set print thread-events @@ -21865,6 +21883,17 @@ is no selected thread, this will return @code{None}. A @code{gdb.InferiorThread} object has the following attributes: @table @code +@defivar InferiorThread name +The name of the thread. If the user specified a name using +@code{thread name}, then this returns that name. Otherwise, if an +OS-supplied name is available, then it is returned. Otherwise, this +returns @code{None}. + +This attribute can be assigned to. The new value must be a string +object, which sets the new name, or @code{None}, which removes any +user-specified thread name. +@end defivar + @defivar InferiorThread num ID of the thread, as assigned by GDB. @end defivar @@ -25753,21 +25782,38 @@ also reports the current thread. The @samp{info thread} command prints the same information about all threads. -@subsubheading Example +@subsubheading Result -@smallexample --thread-info -^done,threads=[ -@{id="2",target-id="Thread 0xb7e14b90 (LWP 21257)", - frame=@{level="0",addr="0xffffe410",func="__kernel_vsyscall",args=[]@},state="running"@}, -@{id="1",target-id="Thread 0xb7e156b0 (LWP 21254)", - frame=@{level="0",addr="0x0804891f",func="foo",args=[@{name="i",value="10"@}], - file="/tmp/a.c",fullname="/tmp/a.c",line="158"@},state="running"@}], -current-thread-id="1" -(gdb) -@end smallexample +The result is a list of threads. The following attributes are +defined for a given thread: + +@table @samp +@item current +This field exists only for the current thread. It has the value @samp{*}. -The @samp{state} field may have the following values: +@item id +The identifier that @value{GDBN} uses to refer to the thread. + +@item target-id +The identifier that the target uses to refer to the thread. + +@item details +Extra information about the thread, in a target-specific format. This +field is optional. + +@item name +The name of the thread. If the user specified a name using the +@code{thread name} command, then this name is given. Otherwise, if +@value{GDBN} can extract the thread name from the target, then that +name is given. If @value{GDBN} cannot find the thread name, then this +field is omitted. + +@item frame +The stack frame currently executing in the thread. + +@item state +The thread's state. The @samp{state} field may have the following +values: @table @code @item stopped @@ -25780,6 +25826,28 @@ threads. @end table +@item core +If @value{GDBN} can find the CPU core on which this thread is running, +then this field is the core identifier. This field is optional. + +@end table + +@subsubheading Example + +@smallexample +-thread-info +^done,threads=[ +@{id="2",target-id="Thread 0xb7e14b90 (LWP 21257)", + frame=@{level="0",addr="0xffffe410",func="__kernel_vsyscall", + args=[]@},state="running"@}, +@{id="1",target-id="Thread 0xb7e156b0 (LWP 21254)", + frame=@{level="0",addr="0x0804891f",func="foo", + args=[@{name="i",value="10"@}], + file="/tmp/a.c",fullname="/tmp/a.c",line="158"@},state="running"@}], +current-thread-id="1" +(gdb) +@end smallexample + @subheading The @code{-thread-list-ids} Command @findex -thread-list-ids diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h index 9e122f6..ddb7b0f 100644 --- a/gdb/gdbthread.h +++ b/gdb/gdbthread.h @@ -125,6 +125,10 @@ struct thread_info kernel thread id, etc. */ int num; /* Convenient handle (GDB thread id) */ + /* The name of the thread, as specified by the user. This is NULL + if the thread does not have a user-given name. */ + char *name; + /* Non-zero means the thread is executing. Note: this is different from saying that there is an active target and we are stopped at a breakpoint, for instance. This is a real indicator whether the diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index 62a4538..a855219 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -4078,6 +4078,43 @@ linux_nat_pid_to_str (struct target_ops *ops, ptid_t ptid) return normal_pid_to_str (ptid); } +static char * +linux_nat_thread_name (struct thread_info *thr) +{ + int pid = ptid_get_pid (thr->ptid); + long lwp = ptid_get_lwp (thr->ptid); +#define FORMAT "/proc/%d/task/%ld/comm" + char buf[sizeof (FORMAT) + 30]; + FILE *comm_file; + char *result = NULL; + + snprintf (buf, sizeof (buf), FORMAT, pid, lwp); + comm_file = fopen (buf, "r"); + if (comm_file) + { + /* Not exported by the kernel, so we define it here. */ +#define COMM_LEN 16 + static char line[COMM_LEN + 1]; + + if (fgets (line, sizeof (line), comm_file)) + { + char *nl = strchr (line, '\n'); + + if (nl) + *nl = '\0'; + if (*line != '\0') + result = line; + } + + fclose (comm_file); + } + +#undef COMM_LEN +#undef FORMAT + + return result; +} + /* Accepts an integer PID; Returns a string representing a file that can be opened to get the symbols for the child process. */ @@ -5683,6 +5720,7 @@ linux_nat_add_target (struct target_ops *t) t->to_mourn_inferior = linux_nat_mourn_inferior; t->to_thread_alive = linux_nat_thread_alive; t->to_pid_to_str = linux_nat_pid_to_str; + t->to_thread_name = linux_nat_thread_name; t->to_has_thread_control = tc_schedlock; t->to_thread_address_space = linux_nat_thread_address_space; t->to_stopped_by_watchpoint = linux_nat_stopped_by_watchpoint; diff --git a/gdb/python/py-infthread.c b/gdb/python/py-infthread.c index 46637d0..059422d 100644 --- a/gdb/python/py-infthread.c +++ b/gdb/python/py-infthread.c @@ -64,6 +64,63 @@ thpy_dealloc (PyObject *self) } static PyObject * +thpy_get_name (PyObject *self, void *ignore) +{ + thread_object *thread_obj = (thread_object *) self; + char *name; + + THPY_REQUIRE_VALID (thread_obj); + + name = thread_obj->thread->name; + if (name == NULL) + name = target_thread_name (thread_obj->thread); + + if (name == NULL) + Py_RETURN_NONE; + + return PyString_FromString (name); +} + +static int +thpy_set_name (PyObject *self, PyObject *newvalue, void *ignore) +{ + thread_object *thread_obj = (thread_object *) self; + char *name; + + if (! thread_obj->thread) + { + PyErr_SetString (PyExc_RuntimeError, _("Thread no longer exists.")); + return -1; + } + + if (newvalue == NULL) + { + PyErr_SetString (PyExc_TypeError, + _("Cannot delete `name' attribute.")); + return -1; + } + else if (newvalue == Py_None) + name = NULL; + else if (! gdbpy_is_string (newvalue)) + { + PyErr_SetString (PyExc_TypeError, + _("The value of `name' must be a string.")); + return -1; + } + else + { + name = python_string_to_host_string (newvalue); + if (! name) + return -1; + } + + xfree (thread_obj->thread->name); + thread_obj->thread->name = name; + + return 0; +} + +static PyObject * thpy_get_num (PyObject *self, void *closure) { thread_object *thread_obj = (thread_object *) self; @@ -201,6 +258,8 @@ gdbpy_initialize_thread (void) static PyGetSetDef thread_object_getset[] = { + { "name", thpy_get_name, thpy_set_name, + "The name of the thread, as set by the user or the OS.", NULL }, { "num", thpy_get_num, NULL, "ID of the thread, as assigned by GDB.", NULL }, { "ptid", thpy_get_ptid, NULL, "ID of the thread, as assigned by the OS.", NULL }, diff --git a/gdb/target.c b/gdb/target.c index ff87931..03a94c4 100644 --- a/gdb/target.c +++ b/gdb/target.c @@ -640,6 +640,7 @@ update_current_target (void) /* Do not inherit to_find_new_threads. */ /* Do not inherit to_pid_to_str. */ INHERIT (to_extra_thread_info, t); + INHERIT (to_thread_name, t); INHERIT (to_stop, t); /* Do not inherit to_xfer_partial. */ INHERIT (to_rcmd, t); @@ -810,6 +811,9 @@ update_current_target (void) de_fault (to_extra_thread_info, (char *(*) (struct thread_info *)) return_zero); + de_fault (to_thread_name, + (char *(*) (struct thread_info *)) + return_zero); de_fault (to_stop, (void (*) (ptid_t)) target_ignore); @@ -2413,6 +2417,20 @@ target_pid_to_str (ptid_t ptid) return normal_pid_to_str (ptid); } +char * +target_thread_name (struct thread_info *info) +{ + struct target_ops *t; + + for (t = current_target.beneath; t != NULL; t = t->beneath) + { + if (t->to_thread_name != NULL) + return (*t->to_thread_name) (info); + } + + return NULL; +} + void target_resume (ptid_t ptid, int step, enum target_signal signal) { diff --git a/gdb/target.h b/gdb/target.h index a1288d0..3ead6cf 100644 --- a/gdb/target.h +++ b/gdb/target.h @@ -488,6 +488,7 @@ struct target_ops void (*to_find_new_threads) (struct target_ops *); char *(*to_pid_to_str) (struct target_ops *, ptid_t); char *(*to_extra_thread_info) (struct thread_info *); + char *(*to_thread_name) (struct thread_info *); void (*to_stop) (ptid_t); void (*to_rcmd) (char *command, struct ui_file *output); char *(*to_pid_to_exec_file) (int pid); @@ -1237,6 +1238,11 @@ extern char *normal_pid_to_str (ptid_t ptid); #define target_extra_thread_info(TP) \ (current_target.to_extra_thread_info (TP)) +/* Return the thread's name. A NULL result means that the target + could not determine this thread's name. */ + +extern char *target_thread_name (struct thread_info *); + /* Attempts to find the pathname of the executable file that was run to create a specified process. diff --git a/gdb/testsuite/gdb.python/py-infthread.exp b/gdb/testsuite/gdb.python/py-infthread.exp index 7b0c589..bbec4ec 100644 --- a/gdb/testsuite/gdb.python/py-infthread.exp +++ b/gdb/testsuite/gdb.python/py-infthread.exp @@ -50,6 +50,17 @@ gdb_test "python print t0" "\\ gdb_test "python print 'result =', t0.num" " = \[0-9\]+" "test Inferior.num" gdb_test "python print 'result =', t0.ptid" " = \\(\[0-9\]+, \[0-9\]+, \[0-9\]+\\)" "test InferiorThread.ptid" +gdb_py_test_silent_cmd "python name = gdb.selected_thread().name" \ + "get supplied name of current thread" 1 +gdb_py_test_silent_cmd "python gdb.selected_thread().name = 'hibob'" \ + "set name of current thread" 1 +gdb_test "python print gdb.selected_thread().name" "hibob" \ + "check name of current thread" +gdb_py_test_silent_cmd "python gdb.selected_thread().name = None" \ + "reset name of current thread" 1 +gdb_test "python print gdb.selected_thread().name == name" "True" \ + "check name of current thread again" + gdb_test "python print 'result =', t0.is_stopped ()" " = True" "test InferiorThread.is_stopped" gdb_test "python print 'result =', t0.is_running ()" " = False" "test InferiorThread.is_running" gdb_test "python print 'result =', t0.is_exited ()" " = False" "test InferiorThread.is_exited" diff --git a/gdb/testsuite/gdb.threads/manythreads.exp b/gdb/testsuite/gdb.threads/manythreads.exp index 1bd3e8d..12a25b9 100644 --- a/gdb/testsuite/gdb.threads/manythreads.exp +++ b/gdb/testsuite/gdb.threads/manythreads.exp @@ -99,6 +99,9 @@ gdb_test_multiple $cmd $cmd { } } +gdb_test_no_output "thread name zardoz" "give a name to the thread" +gdb_test "info threads" ".*zardoz.*" "check thread name" + set message "second continue" gdb_test_multiple "continue" "second continue" { -re "error:.*$gdb_prompt $" { diff --git a/gdb/thread.c b/gdb/thread.c index 5ced1fa..62455c2 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -141,6 +141,7 @@ free_thread (struct thread_info *tp) xfree (tp->private); } + xfree (tp->name); xfree (tp); } @@ -769,7 +770,7 @@ print_thread_info (struct ui_out *uiout, int requested_thread, int pid) struct thread_info *tp; ptid_t current_ptid; struct cleanup *old_chain; - char *extra_info; + char *extra_info, *name, *target_id; int current_thread = -1; update_thread_list (); @@ -811,12 +812,11 @@ print_thread_info (struct ui_out *uiout, int requested_thread, int pid) return; } - make_cleanup_ui_out_table_begin_end (uiout, 5, n_threads, "threads"); + make_cleanup_ui_out_table_begin_end (uiout, 4, n_threads, "threads"); ui_out_table_header (uiout, 1, ui_left, "current", ""); ui_out_table_header (uiout, 4, ui_left, "id", "Id"); ui_out_table_header (uiout, 17, ui_left, "target-id", "Target Id"); - ui_out_table_header (uiout, 1, ui_noalign, "details", ""); ui_out_table_header (uiout, 1, ui_left, "frame", "Frame"); ui_out_table_body (uiout); } @@ -861,17 +861,45 @@ print_thread_info (struct ui_out *uiout, int requested_thread, int pid) } ui_out_field_int (uiout, "id", tp->num); - ui_out_field_string (uiout, "target-id", target_pid_to_str (tp->ptid)); + /* For the CLI, we stuff everything into the target-id field. + This is a gross hack to make the output come out looking + correct. The underlying problem here is that ui-out has no + way to specify that a field's space allocation should be + shared by several fields. For MI, we do the right thing + instead. */ + + target_id = target_pid_to_str (tp->ptid); extra_info = target_extra_thread_info (tp); - if (extra_info) + name = tp->name ? tp->name : target_thread_name (tp); + + if (ui_out_is_mi_like_p (uiout)) { - ui_out_text (uiout, " ("); - ui_out_field_string (uiout, "details", extra_info); - ui_out_text (uiout, ")"); + ui_out_field_string (uiout, "target-id", target_id); + if (extra_info) + ui_out_field_string (uiout, "details", extra_info); + if (name) + ui_out_field_string (uiout, "name", name); + } + else + { + struct cleanup *str_cleanup; + char *contents; + + if (extra_info && name) + contents = xstrprintf ("%s \"%s\" (%s)", target_id, + name, extra_info); + else if (extra_info) + contents = xstrprintf ("%s (%s)", target_id, extra_info); + else if (name) + contents = xstrprintf ("%s \"%s\"", target_id, name); + else + contents = xstrdup (target_id); + str_cleanup = make_cleanup (xfree, contents); + + ui_out_field_string (uiout, "target-id", contents); + do_cleanups (str_cleanup); } - else if (! ui_out_is_mi_like_p (uiout)) - ui_out_field_skip (uiout, "details"); if (tp->state_ == THREAD_RUNNING) ui_out_text (uiout, "(running)\n"); @@ -1267,6 +1295,24 @@ thread_command (char *tidstr, int from_tty) gdb_thread_select (uiout, tidstr, NULL); } +/* Implementation of `thread name'. */ + +static void +thread_name_command (char *arg, int from_tty) +{ + struct thread_info *info; + + if (ptid_equal (inferior_ptid, null_ptid)) + error (_("No thread selected")); + + while (arg && isspace (*arg)) + ++arg; + + info = inferior_thread (); + xfree (info->name); + info->name = arg ? xstrdup (arg) : NULL; +} + /* Print notices when new threads are attached and detached. */ int print_thread_events = 1; static void @@ -1372,6 +1418,11 @@ The new thread ID must be currently known."), add_cmd ("all", class_run, thread_apply_all_command, _("Apply a command to all threads."), &thread_apply_list); + add_cmd ("name", class_run, thread_name_command, + _("Set the current thread's name.\n\ +Usage: thread name [NAME]\n\ +If NAME is not given, then any existing name is removed."), &thread_cmd_list); + if (!xdb_commands) add_com_alias ("t", "thread", class_run, 1);