From patchwork Fri Nov 3 09:46:42 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Muldoon X-Patchwork-Id: 24066 Received: (qmail 70547 invoked by alias); 3 Nov 2017 09:46:51 -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 69672 invoked by uid 89); 3 Nov 2017 09:46:50 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-26.3 required=5.0 tests=BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_STOCKGEN, RCVD_IN_DNSWL_NONE, RCVD_IN_SORBS_SPAM autolearn=ham version=3.3.2 spammy=backing, incorporated, Simon, Marchi X-HELO: mail-wr0-f169.google.com Received: from mail-wr0-f169.google.com (HELO mail-wr0-f169.google.com) (209.85.128.169) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Fri, 03 Nov 2017 09:46:47 +0000 Received: by mail-wr0-f169.google.com with SMTP id y39so1979878wrd.4 for ; Fri, 03 Nov 2017 02:46:47 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:subject:to:cc:references:from:message-id:date :mime-version:in-reply-to:content-language:content-transfer-encoding; bh=lX56C/oLCVG/icBPPMOJEop6ns0nLYv7IvuhWbONg20=; b=VLPlhSG64YlVLlCoqF3PfjsBiWk0Oj/IHEAFSKC4FgK/6f/dP/GbdqYHG9gpvEUGTH 2SpZcY/jkdYt41I8M/0vKhm9v/rab6ZNxXUV9zusahD5bqKg0a5z4s4MO15LzgNB+Aw7 KEgivM+W2j9w+/EZ05plgqLuMYKvU1yN9G9imSYhg7eguVkQ7/5mYMYKCL7Ghf6Z/B3t piFY0sj2W5R/+Dv77XjTjlCa/hUw+z57ySDeA46aUJfCkXFiPwaP5qW8+rMxFdVZAZ1f St57PhqAaAJ5A7EfjZNoAHsbsGUSd2Y3hdQ5MtACGiKttcCoZ99744CwvCaTzB8UMeWM 6Y6Q== X-Gm-Message-State: AMCzsaXCuYnihzcnXk97iRVUjANU3PAZ91j5e9cV0D8bYXVRUY14yolt NkOp+jhnV6AStYJ4QbBG1JeDm06MRGA= X-Google-Smtp-Source: ABhQp+RHr67ccO4lxSxyGsTKvxb+trgdAmLrkjv5MZKUSMXbJZ2rjqcOLE2ee2EBbodK98TNXKUF5w== X-Received: by 10.223.135.108 with SMTP id 41mr5215109wrz.160.1509702404678; Fri, 03 Nov 2017 02:46:44 -0700 (PDT) Received: from ?IPv6:2a02:c7f:ae6a:ed00:4685:ff:fe66:9f4? ([2a02:c7f:ae6a:ed00:4685:ff:fe66:9f4]) by smtp.gmail.com with ESMTPSA id k30sm9003859wrf.52.2017.11.03.02.46.43 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 03 Nov 2017 02:46:43 -0700 (PDT) Subject: Re: [python][patch] Python rbreak To: Simon Marchi Cc: gdb-patches@sourceware.org References: <5e1ba7e3-5f6e-2478-30a5-7670ec7a9879@redhat.com> <3193f5c7a0c98c548722bb6c143f347e@polymtl.ca> From: Phil Muldoon Message-ID: <8ee8a4c0-4580-474f-a5aa-6f76a8d22960@redhat.com> Date: Fri, 3 Nov 2017 09:46:42 +0000 MIME-Version: 1.0 In-Reply-To: <3193f5c7a0c98c548722bb6c143f347e@polymtl.ca> X-IsSubscribed: yes On 17/10/17 01:24, Simon Marchi wrote: > On 2017-10-16 19:01, Phil Muldoon wrote: >>> I can't find a reference, but I think we want test names to start >>> with a lower case letter and not end with a dot. I'll see if we >>> can add this to the testcase cookbook wiki page. >> >> As I mentioned on IRC, I've not heard of it but will happily change >> the names to comply. Sorry this took a bit longer to get back out than I would have liked. Modified patch follows. I believe I have incorporated yours, Eli's and Kevin's comments. ChangeLogs remain the same (other than the new NEWS entry which I have added locally.) Cheers diff --git a/gdb/NEWS b/gdb/NEWS index 2bad096a86..1d26ea4af7 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -24,6 +24,10 @@ gdb.new_thread are emitted. See the manual for further description of these. + ** A new command, "rbreak" has been added to the Python API. This + command allows the setting of a large number of breakpoints via a + regex pattern in Python. See the manual for further details. + * New features in the GDB remote stub, GDBserver ** GDBserver is now able to start inferior processes with a diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index f661e489bb..f411f60d7e 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -243,6 +243,23 @@ were no breakpoints. This peculiarity was subsequently fixed, and now @code{gdb.breakpoints} returns an empty sequence in this case. @end defun +@defun gdb.rbreak (regex @r{[}, minsyms @r{[}, throttle, @r{[}, symtabs @r{]]]}) +Return a Python list holding a collection of newly set +@code{gdb.Breakpoint} objects matching function names defined by the +@var{regex} pattern. If the @var{minsyms} keyword is @code{True}, all +system functions (those not explicitly defined in the inferior) will +also be included in the match. The @var{throttle} keyword takes an +integer that defines the maximum number of pattern matches for +functions matched by the @var{regex} pattern. If the number of +matches exceeds the integer value of @var{throttle}, a +@code{RuntimeError} will be raised and no breakpoints will be created. +If @var{throttle} is not defined then there is no imposed limit on the +maximum number of matches and breakpoints to be created. The +@var{symtabs} keyword takes a Python iterable that yields a collection +of @code{gdb.Symtab} objects and will restrict the search to those +functions only contained within the @code{gdb.Symtab} objects. +@end defun + @findex gdb.parameter @defun gdb.parameter (parameter) Return the value of a @value{GDBN} @var{parameter} given by its name, diff --git a/gdb/python/python.c b/gdb/python/python.c index b04057ec4a..a044b8ff8b 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -642,6 +642,190 @@ gdbpy_solib_name (PyObject *self, PyObject *args) return str_obj; } +/* Implementation of Python rbreak command. Take a REGEX and + optionally a MINSYMS, THROTTLE and SYMTABS keyword and return a + Python list that contains newly set breakpoints that match that + criteria. REGEX refers to a GDB format standard regex pattern of + symbols names to search; MINSYMS is an optional boolean (default + False) that indicates if the function should search GDB's minimal + symbols; THROTTLE is an optional integer (default unlimited) that + indicates the maximum amount of breakpoints allowable before the + function exits (note, if the throttle bound is passed, no + breakpoints will be set and a runtime error returned); SYMTABS is + an optional Python iterable that contains a set of gdb.Symtabs to + constrain the search within. */ + +static PyObject * +gdbpy_rbreak (PyObject *self, PyObject *args, PyObject *kw) +{ + /* A simple type to ensure clean up of a vector of allocated strings + when a C interface demands a const char *array[] type + interface. */ + struct symtab_list_type + { + ~symtab_list_type () + { + for (const char *elem: vec) + xfree ((void *) elem); + } + std::vector vec; + }; + + char *regex = NULL; + std::vector symbols; + unsigned long count = 0; + PyObject *symtab_list = NULL; + PyObject *minsyms_p_obj = NULL; + int minsyms_p = 0; + unsigned int throttle = 0; + static const char *keywords[] = {"regex","minsyms", "throttle", + "symtabs", NULL}; + symtab_list_type symtab_paths; + + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|O!IO", keywords, + ®ex, &PyBool_Type, + &minsyms_p_obj, &throttle, + &symtab_list)) + return NULL; + + /* Parse minsyms keyword. */ + if (minsyms_p_obj != NULL) + { + int cmp = PyObject_IsTrue (minsyms_p_obj); + if (cmp < 0) + return NULL; + minsyms_p = cmp; + } + + /* The "symtabs" keyword is any Python iterable object that returns + a gdb.Symtab on each iteration. If specified, iterate through + the provided gdb.Symtabs and extract their full path. As + python_string_to_target_string returns a + gdb::unique_xmalloc_ptr and a vector containing these types + cannot be coerced to a const char **p[] via the vector.data call, + release the value from the unique_xmalloc_ptr and place it in a + simple type symtab_list_type (which holds the vector and a + destructor that frees the contents of the allocated strings. */ + if (symtab_list != NULL) + { + gdbpy_ref<> iter (PyObject_GetIter (symtab_list)); + + if (iter == NULL) + return NULL; + + while (true) + { + gdbpy_ref<> next (PyIter_Next (iter.get ())); + + if (next == NULL) + { + if (PyErr_Occurred ()) + return NULL; + break; + } + + gdbpy_ref<> obj_name (PyObject_GetAttrString (next.get (), + "filename")); + + if (obj_name == NULL) + return NULL; + + /* Is the object file still valid? */ + if (obj_name == Py_None) + continue; + + gdb::unique_xmalloc_ptr filename = + python_string_to_target_string (obj_name.get ()); + + if (filename == NULL) + return NULL; + + /* Make sure there is a definite place to store the value of + s before it is released. */ + symtab_paths.vec.push_back (nullptr); + symtab_paths.vec.back () = filename.release (); + } + } + + if (symtab_list) + { + const char **files = symtab_paths.vec.data (); + + symbols = search_symbols (regex, FUNCTIONS_DOMAIN, + symtab_paths.vec.size (), files); + } + else + symbols = search_symbols (regex, FUNCTIONS_DOMAIN, 0, NULL); + + /* Count the number of symbols (both symbols and optionally minimal + symbols) so we can correctly check the throttle limit. */ + for (const symbol_search &p : symbols) + { + /* Minimal symbols included? */ + if (minsyms_p) + { + if (p.msymbol.minsym != NULL) + count++; + } + + if (p.symbol != NULL) + count++; + } + + /* Check throttle bounds and exit if in excess. */ + if (throttle != 0 && count > throttle) + { + PyErr_SetString (PyExc_RuntimeError, + _("Number of breakpoints exceeds throttled maximum.")); + return NULL; + } + + gdbpy_ref<> return_list (PyList_New (0)); + + if (return_list == NULL) + return NULL; + + /* Construct full path names for symbols and call the Python + breakpoint constructor on the resulting names. Be tolerant of + individual breakpoint failures. */ + for (const symbol_search &p : symbols) + { + std::string symbol_name; + + /* Skipping minimal symbols? */ + if (minsyms_p == 0) + if (p.msymbol.minsym != NULL) + continue; + + if (p.msymbol.minsym == NULL) + { + struct symtab *symtab = symbol_symtab (p.symbol); + const char *fullname = symtab_to_fullname (symtab); + + symbol_name = fullname; + symbol_name += ":"; + symbol_name += SYMBOL_LINKAGE_NAME (p.symbol); + } + else + symbol_name = MSYMBOL_LINKAGE_NAME (p.msymbol.minsym); + + gdbpy_ref<> argList (Py_BuildValue("(s)", symbol_name.c_str ())); + gdbpy_ref<> obj (PyObject_CallObject ((PyObject *) + &breakpoint_object_type, + argList.get ())); + + /* Tolerate individual breakpoint failures. */ + if (obj == NULL) + gdbpy_print_stack (); + else + { + if (PyList_Append (return_list.get (), obj.get ()) == -1) + return NULL; + } + } + return return_list.release (); +} + /* A Python function which is a wrapper for decode_line_1. */ static PyObject * @@ -1912,7 +2096,9 @@ Return the name of the current target charset." }, { "target_wide_charset", gdbpy_target_wide_charset, METH_NOARGS, "target_wide_charset () -> string.\n\ Return the name of the current target wide charset." }, - + { "rbreak", (PyCFunction) gdbpy_rbreak, METH_VARARGS | METH_KEYWORDS, + "rbreak (Regex) -> List.\n\ +Return a Tuple containing gdb.Breakpoint objects that match the given Regex." }, { "string_to_argv", gdbpy_string_to_argv, METH_VARARGS, "string_to_argv (String) -> Array.\n\ Parse String and return an argv-like array.\n\ diff --git a/gdb/testsuite/gdb.python/py-rbreak-func2.c b/gdb/testsuite/gdb.python/py-rbreak-func2.c new file mode 100644 index 0000000000..2d24b6b557 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-rbreak-func2.c @@ -0,0 +1,34 @@ +/* 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 . */ + +int +efunc1 () +{ + return 1; +} + +int +efunc2 () +{ + return 2; +} + +int +efunc3 () +{ + return 3; +} diff --git a/gdb/testsuite/gdb.python/py-rbreak.c b/gdb/testsuite/gdb.python/py-rbreak.c new file mode 100644 index 0000000000..e79d2a34ae --- /dev/null +++ b/gdb/testsuite/gdb.python/py-rbreak.c @@ -0,0 +1,70 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2013-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 . */ + +int +func1 () +{ + return 1; +} + +int +func2 () +{ + return 2; +} + +int +func3 () +{ + return 3; +} + +int +func4 () +{ + return 4; +} + +int +func5 () +{ + return 5; +} + +void +func6 () +{ + return; +} + +void +outside_scope () +{ + return; +} + +int +main() +{ + func1 (); /* Break func1. */ + func2 (); + func3 (); + func4 (); + func5 (); + func6 (); + outside_scope (); +} diff --git a/gdb/testsuite/gdb.python/py-rbreak.exp b/gdb/testsuite/gdb.python/py-rbreak.exp new file mode 100644 index 0000000000..5aaf2975c9 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-rbreak.exp @@ -0,0 +1,61 @@ +# 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 . + +# This file is part of the GDB testsuite. It tests the mechanism +# exposing values to Python. + +load_lib gdb-python.exp + +standard_testfile py-rbreak.c py-rbreak-func2.c + +if {[prepare_for_testing "failed to prepare" ${testfile} [list $srcfile $srcfile2]] } { + return 1 +} + +# Skip all tests if Python scripting is not enabled. +if { [skip_python_tests] } { continue } + +if ![runto_main] then { + fail "can't run to main" + return 0 +} + +gdb_py_test_silent_cmd "py sl = gdb.rbreak(\"\",minsyms=False)" \ + "get all function breakpoints" 0 +gdb_test "py print(len(sl))" "11" \ + "check number of returned breakpoints is 11" +gdb_py_test_silent_cmd "py sl = gdb.rbreak(\"main\.\*\",minsyms=False)" \ + "get main function breakpoint" 0 +gdb_test "py print(len(sl))" "1" \ + "check number of returned breakpoints is 1" +gdb_py_test_silent_cmd "py sl = gdb.rbreak(\"func\.\*\",minsyms=False,throttle=10)" \ + "get functions matching func.*" 0 +gdb_test "py print(len(sl))" "9" \ + "check number of returned breakpoints is 9" +gdb_test "py gdb.rbreak(\"func\.\*\",minsyms=False,throttle=5)" \ + "Number of breakpoints exceeds throttled maximum.*" \ + "check throttle errors on too many breakpoints" +gdb_py_test_silent_cmd "py sl = gdb.rbreak(\"func1\",minsyms=True)" \ + "including minimal symbols, get functions matching func.*" 0 +gdb_test "py print(len(sl))" "2" \ + "check number of returned breakpoints is 2" +gdb_py_test_silent_cmd "python sym = gdb.lookup_symbol(\"efunc1\")" \ + "find a symbol in objfile" 1 +gdb_py_test_silent_cmd "python symtab = sym\[0\].symtab" \ + "get backing symbol table" 1 +gdb_py_test_silent_cmd "py sl = gdb.rbreak(\"func\.\*\",minsyms=False,throttle=10,symtabs=\[symtab\])" \ + "get functions matching func.* in one symtab only" 0 +gdb_test "py print(len(sl))" "3" \ + "check number of returned breakpoints is 3"