Re: [PATCH v3 4/7] Test case for Inferior.thread_from_thread_handle

Simon Marchi
2017-07-23T21:17:38Z

Hi Kevin,

On 2017-07-19 02:55, Kevin Buettner wrote:
> As the title says, this is a test case for
> Inferior.thread_from_thread_handle, a python method which will,
> given a thread library dependent thread handle, find the GDB thread
> which corresponds to that thread handle (in the inferior under
> consideration).
>
> The C file for this test case causes the thread handles for the
> main thread and two child threads to be placed into an array. The
> test case runs to one of the functions (do_something()) at which point,
> it retrieves the thread handles from the array and attempts to find the
> correponding thread in GDB's internal thread list.

"correponding"

>
> I use barriers to make sure that both threads have actually started;
> execution will stop when one of the threads breaks at do_something.
>
> Thanks to Simon Marchi for suggestions for forcing the thread
> numbering to be stable.
>
> gdb/testsuite/ChangeLog:
>
> * gdb.python/py-thrhandle.c, gdb.python/py-thrhandle.exp: New
> files.
> ---
> gdb/testsuite/gdb.python/py-thrhandle.c | 97
> +++++++++++++++++++++++++++++++
> gdb/testsuite/gdb.python/py-thrhandle.exp | 72 +++++++++++++++++++++++
> 2 files changed, 169 insertions(+)
>
> diff --git a/gdb/testsuite/gdb.python/py-thrhandle.c
> b/gdb/testsuite/gdb.python/py-thrhandle.c
> new file mode 100644
> index 0000000..10e0222
> --- /dev/null
> +++ b/gdb/testsuite/gdb.python/py-thrhandle.c
> @@ -0,0 +1,97 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + Copyright 2017 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/>. */
> +
> +#include <pthread.h>
> +#include <unistd.h>
> +#include <memory.h>
> +
> +#define NTHR 3
> +#define NBOGUSTHR 2
> +
> +int thr_data[NTHR];
> +
> +/* Thread handles for each thread plus some "bogus" threads. */
> +pthread_t thrs[NTHR + NBOGUSTHR];
> +
> +/* The thread children will meet at this barrier. */
> +pthread_barrier_t c_barrier;
> +
> +/* The main thread and child thread will meet at this barrier. */
> +pthread_barrier_t mc_barrier;
> +
> +void
> +do_something (int n)
> +{
> +}
> +
> +void *
> +do_work (void *data)
> +{
> + int num = * (int *) data;
> +
> + /* As the child threads are created, they'll meet the main thread
> + at this barrier. We do this to ensure that threads end up in
> + GDB's thread list in the order in which they were created.
> Having
> + this ordering makes it easier to write the test. */
> + pthread_barrier_wait (&mc_barrier);
> +
> + /* All of the child threads will meet at this barrier before
> proceeding.
> + This ensures that all threads will be active (not exited) and in
> + roughly the same state when the first one hits the breakpoint in
> + do_something(). */
> + pthread_barrier_wait (&c_barrier);
> +
> + do_something (num);
> +
> + pthread_exit (NULL);
> +}
> +
> +void
> +after_mc_barrier (void)
> +{
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> + int i;
> +
> + pthread_barrier_init (&c_barrier, NULL, NTHR - 1);
> +
> + thrs[0] = pthread_self ();
> + thr_data[0] = 1;
> +
> + /* Create two bogus thread handles. */
> + memset (&thrs[NTHR], 0, sizeof (pthread_t));
> + memset (&thrs[NTHR + 1], 0xaa, sizeof (pthread_t));
> +
> + for (i = 1; i < NTHR; i++)
> + {
> + thr_data[i] = i + 1;
> +
> + pthread_barrier_init (&mc_barrier, NULL, 2);

I don't think it's wrong to init/destroy the barrier repeatedly, but
it's not necessary. After the barrier has "opened", it is reset with
the count it was initialized with. So you can just call _wait again on
it.

> +
> + pthread_create (&thrs[i], NULL, do_work, &thr_data[i]);
> +
> + pthread_barrier_wait (&mc_barrier);
> + pthread_barrier_destroy (&mc_barrier);
> + after_mc_barrier ();
> + }
> +
> + for (i = 1; i < NTHR; i++)
> + pthread_join (thrs[i], NULL);
> +}
> diff --git a/gdb/testsuite/gdb.python/py-thrhandle.exp
> b/gdb/testsuite/gdb.python/py-thrhandle.exp
> new file mode 100644
> index 0000000..033737d
> --- /dev/null
> +++ b/gdb/testsuite/gdb.python/py-thrhandle.exp
> @@ -0,0 +1,72 @@
> +# Copyright (C) 2017 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/>.
> +
> +# Please email any bugs, comments, and/or additions to this file to:
> +# bug-gdb@gnu.org
> +
> +# This file verifies that gdb.thread_from_thread_handle works as
> expected.

This is now gdb.Inferior.thread_from_thread_handle.

> +
> +standard_testfile
> +
> +
> +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}"
> "${binfile}" executable debug] != "" } {
> + return -1
> +}
> +
> +clean_restart ${binfile}
> +runto_main
> +
> +gdb_test "break after_mc_barrier" \
> + "Breakpoint 2 at .*: file .*${srcfile}, line .*" \
> + "breakpoint on after_mc_barrier"
> +
> +gdb_test "break do_something" \
> + "Breakpoint 3 at .*: file .*${srcfile}, line .*" \
> + "breakpoint on do_something"
> +
> +gdb_test "continue" \
> + "Breakpoint 2, after_mc_barrier .*" \
> + "run to after_mc_barrier (1)"

Do not use parentheses at the end of the test name:
https://sourceware.org/gdb/wiki/GDBTestcaseCookbook#Do_not_use_.22tail_parentheses.22_on_test_messages

Alternatively, you can use gdb_continue_to_breakpoint or gdb_continue
(not sure why we have both).

> +
> +gdb_test_no_output "del 2" "delete after_mc_barrier breakpoint"
> +
> +gdb_test "continue" \
> + "Breakpoint 3, do_something .*" \
> + "run to do_something"
> +
> +# The current thread, indicated by '*' in the "info threads" output
> +# should be stopped in do_something() with a value of n which is the
> +# same as the number reported in the "Id" column. If it's not, then
> +# something went wrong with the start up sequence which should cause
> +# the main thread to be thread 1, the first child thread to be thread
> +# 2, and the second child thread to be thread 3.
> +
> +gdb_test "info threads" \
> + {.*[\r\n]+\* +([0-9]+) +Thread[^\r\n]* do_something \(n=\1\) at.*}

I didn't understand the comment when reading it at first, then wondered
for way too long how we could end up with n=1 :).

> +gdb_test "python print
> gdb.selected_inferior().thread_from_thread_handle(gdb.parse_and_eval('thrs\[0\]')).num"
> \
> + "1"
> +
> +gdb_test "python print
> gdb.selected_inferior().thread_from_thread_handle(gdb.parse_and_eval('thrs\[1\]')).num"
> \
> + "2"
> +
> +gdb_test "python print
> gdb.selected_inferior().thread_from_thread_handle(gdb.parse_and_eval('thrs\[2\]')).num"
> \
> + "3"
> +
> +gdb_test "python print
> gdb.selected_inferior().thread_from_thread_handle(gdb.parse_and_eval('thrs\[3\]'))"
> \
> + "None"
> +
> +gdb_test "python print
> gdb.selected_inferior().thread_from_thread_handle(gdb.parse_and_eval('thrs\[4\]'))"
> \
> + "None"

Please use parentheses with print, so it works with Python 3. But then,
your test name will have tail parentheses, so it might be a good idea to
give them explicit names.

Thanks,

Simon