From patchwork Wed May 6 19:58:03 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ted Mielczarek X-Patchwork-Id: 6602 Received: (qmail 115793 invoked by alias); 6 May 2015 19:58:15 -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 115774 invoked by uid 89); 6 May 2015 19:58:13 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-2.6 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_LOW autolearn=ham version=3.3.2 X-HELO: out4-smtp.messagingengine.com Received: from out4-smtp.messagingengine.com (HELO out4-smtp.messagingengine.com) (66.111.4.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES256-GCM-SHA384 encrypted) ESMTPS; Wed, 06 May 2015 19:58:10 +0000 Received: from compute1.internal (compute1.nyi.internal [10.202.2.41]) by mailout.nyi.internal (Postfix) with ESMTP id 337CB20F16; Wed, 6 May 2015 15:58:08 -0400 (EDT) Received: from frontend1 ([10.202.2.160]) by compute1.internal (MEProxy); Wed, 06 May 2015 15:58:08 -0400 Received: from localhost.localdomain (unknown [108.50.28.36]) by mail.messagingengine.com (Postfix) with ESMTPA id ACEF6C00017 for ; Wed, 6 May 2015 15:58:07 -0400 (EDT) From: Ted Mielczarek To: gdb-patches@sourceware.org Subject: [PATCH] Add a find_source hook for extension languages to locate missing source files. Date: Wed, 6 May 2015 15:58:03 -0400 Message-Id: <1430942283-20558-1-git-send-email-ted@mielczarek.org> This is implemented in Python as `gdb.find_source_hook`. If that property exists and is callable it will be called with the path to the missing source file, and if the callable returns a string that will be used as the new path to the source file. This can be used to fetch missing source files from a VCS or over HTTP, for example. --- Thanks to Tom Tromey for pointing me to where to start with this. I'm interested in using this to provide on-demand source fetching from Mozilla's public Mercurial server over HTTP for debugging Nightly or Release builds, as well as post-mortem debugging of crashes from our user population. Microsoft's debuggers have this functionality built in and it's really useful for those scenarios. gdb/doc/python.texi | 18 +++++ gdb/extension-priv.h | 12 +++ gdb/extension.c | 37 +++++++++ gdb/extension.h | 2 + gdb/python/python.c | 79 +++++++++++++++++++ gdb/source.c | 20 +++++ gdb/testsuite/gdb.python/py-find-source-hook.c | 20 +++++ gdb/testsuite/gdb.python/py-find-source-hook.exp | 96 ++++++++++++++++++++++++ gdb/testsuite/gdb.python/py-find-source-hook.py | 42 +++++++++++ 9 files changed, 326 insertions(+) create mode 100644 gdb/testsuite/gdb.python/py-find-source-hook.c create mode 100644 gdb/testsuite/gdb.python/py-find-source-hook.exp create mode 100644 gdb/testsuite/gdb.python/py-find-source-hook.py diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index fc3c745..4c94db8 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -441,6 +441,24 @@ such as those used by readline for command input, and annotation related prompts are prohibited from being changed. @end defun +@defun gdb.find_source_hook (source_file) +@anchor{find_source_hook} + +If @var{find_source_hook} is callable, @value{GDBN} will call the method +assigned to this operation when it cannot locate a requested source file. + +The parameter @code{source_file} contains the path to the source file +that @value{GDBN} cannot locate, as provided by the debug information. +This method must return a Python string or @code{None}. If a string is +returned, it must be an absolute path to the source file. If @code{None} +is returned, @value{GDBN} will proceed as usual without source. The path +returned must have the same basename as @code{source_file} or GDB will not +be able to match debug information to source. + +This hook may be used to fetch source on-demand from a version control system +or other location. +@end defun + @node Exception Handling @subsubsection Exception Handling @cindex python exceptions diff --git a/gdb/extension-priv.h b/gdb/extension-priv.h index d0242e2..9c45fc2 100644 --- a/gdb/extension-priv.h +++ b/gdb/extension-priv.h @@ -262,6 +262,18 @@ struct extension_language_ops enum ext_lang_rc (*before_prompt) (const struct extension_language_defn *, const char *current_gdb_prompt); + /* Called when gdb cannot locate a source file, giving extension languages an + opportunity to locate the file and provide a path. + If successful the found path is stored in *FOUND_FILENAME, and the caller + must free it. + Returns EXT_LANG_RC_OK if a path was found, EXT_LANG_RC_NOP if no path + was found, and EXT_LANG_RC_ERROR if an error was encountered. + Extension languages are called in order, and once a path is returned + or an error occurs no further languages are called. */ + enum ext_lang_rc (*find_source) (const struct extension_language_defn *, + const char *filename, + char **found_filename); + /* xmethod support: clone_xmethod_worker_data, free_xmethod_worker_data, get_matching_xmethod_workers, get_xmethod_arg_types, diff --git a/gdb/extension.c b/gdb/extension.c index dac203b..e0c8b01 100644 --- a/gdb/extension.c +++ b/gdb/extension.c @@ -1060,6 +1060,43 @@ ext_lang_before_prompt (const char *current_gdb_prompt) } } +/* Iterate over the extension languages giving them a chance to + locate the source file in FILENAME. The first one to return + a result wins, and no further languages are tried. + If there was an error, or if no extension succeeds, then NULL is returned. +*/ + +char * +ext_lang_find_source (const char* filename) +{ + int i; + const struct extension_language_defn *extlang; + + ALL_ENABLED_EXTENSION_LANGUAGES (i, extlang) + { + char *result = NULL; + enum ext_lang_rc rc; + + if (extlang->ops->find_source == NULL) + continue; + rc = extlang->ops->find_source (extlang, filename, &result); + switch (rc) + { + case EXT_LANG_RC_OK: + gdb_assert (result != NULL); + return result; + case EXT_LANG_RC_ERROR: + return NULL; + case EXT_LANG_RC_NOP: + break; + default: + gdb_assert_not_reached ("bad return from find_source"); + } + } + + return NULL; +} + extern initialize_file_ftype _initialize_extension; void diff --git a/gdb/extension.h b/gdb/extension.h index ea30035..97056dd 100644 --- a/gdb/extension.h +++ b/gdb/extension.h @@ -264,4 +264,6 @@ extern struct type *get_xmethod_result_type (struct xmethod_worker *, struct value *object, struct value **args, int nargs); +extern char *ext_lang_find_source (const char* filename); + #endif /* EXTENSION_H */ diff --git a/gdb/python/python.c b/gdb/python/python.c index 4f88b0e..934161e 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -149,6 +149,9 @@ static void gdbpy_set_quit_flag (const struct extension_language_defn *); static int gdbpy_check_quit_flag (const struct extension_language_defn *); static enum ext_lang_rc gdbpy_before_prompt_hook (const struct extension_language_defn *, const char *current_gdb_prompt); +static enum ext_lang_rc gdbpy_find_source_hook +(const struct extension_language_defn *, const char *filename, + char **found_filename); /* The interface between gdb proper and loading of python scripts. */ @@ -187,6 +190,7 @@ const struct extension_language_ops python_extension_ops = gdbpy_check_quit_flag, gdbpy_before_prompt_hook, + gdbpy_find_source_hook, gdbpy_clone_xmethod_worker_data, gdbpy_free_xmethod_worker_data, @@ -1104,6 +1108,81 @@ gdbpy_before_prompt_hook (const struct extension_language_defn *extlang, return EXT_LANG_RC_ERROR; } +/* This is the extension_language_ops.find_source "method". */ + +static enum ext_lang_rc +gdbpy_find_source_hook (const struct extension_language_defn *extlang, + const char *filename, + char **found_filename) +{ + struct cleanup *cleanup; + char *result = NULL; + + if (!gdb_python_initialized) + return EXT_LANG_RC_NOP; + + cleanup = ensure_python_env (get_current_arch (), current_language); + + if (gdb_python_module + && PyObject_HasAttrString (gdb_python_module, "find_source_hook")) + { + PyObject *hook; + + hook = PyObject_GetAttrString (gdb_python_module, "find_source_hook"); + if (hook == NULL) + goto fail; + + make_cleanup_py_decref (hook); + + if (PyCallable_Check (hook)) + { + PyObject *result_obj; + PyObject *in_filename; + + in_filename = PyString_FromString (filename); + if (in_filename == NULL) + goto fail; + + result_obj = PyObject_CallFunctionObjArgs (hook, in_filename, NULL); + + Py_DECREF (in_filename); + + if (result_obj == NULL) + goto fail; + + make_cleanup_py_decref (result_obj); + + /* Return type should be None, or a String. If it is None, + fall through. If it is a string, set FOUND_FILENAME. + Anything else, set an exception. */ + if (result_obj != Py_None && ! PyString_Check (result_obj)) + { + PyErr_Format (PyExc_RuntimeError, + _("Return from find_source_hook must " \ + "be either a Python string, or None")); + goto fail; + } + + if (result_obj != Py_None) + { + result = python_string_to_host_string (result_obj); + if (result == NULL) + goto fail; + + *found_filename = result; + } + } + } + + do_cleanups (cleanup); + return result != NULL ? EXT_LANG_RC_OK : EXT_LANG_RC_NOP; + + fail: + gdbpy_print_stack (); + do_cleanups (cleanup); + return EXT_LANG_RC_ERROR; +} + /* Printing. */ diff --git a/gdb/source.c b/gdb/source.c index fbec0f1..8bee855 100644 --- a/gdb/source.c +++ b/gdb/source.c @@ -20,6 +20,7 @@ #include "arch-utils.h" #include "symtab.h" #include "expression.h" +#include "extension.h" #include "language.h" #include "command.h" #include "source.h" @@ -1100,6 +1101,25 @@ find_and_open_source (const char *filename, OPEN_MODE, fullname); } + if (result < 0) + { + /* Didn't work. Ask extension languages if they can find it. */ + char *found_filename = ext_lang_find_source (filename); + + if (found_filename != NULL) + { + result = gdb_open_cloexec (found_filename, OPEN_MODE, 0); + if (result >= 0) + { + *fullname = found_filename; + } + else + { + make_cleanup (xfree, found_filename); + } + } + } + do_cleanups (cleanup); return result; } diff --git a/gdb/testsuite/gdb.python/py-find-source-hook.c b/gdb/testsuite/gdb.python/py-find-source-hook.c new file mode 100644 index 0000000..50db568 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-find-source-hook.c @@ -0,0 +1,20 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2011-2015 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 main (int argc, char** argv) { return 0; } + diff --git a/gdb/testsuite/gdb.python/py-find-source-hook.exp b/gdb/testsuite/gdb.python/py-find-source-hook.exp new file mode 100644 index 0000000..a63b0f8 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-find-source-hook.exp @@ -0,0 +1,96 @@ +# Copyright (C) 2015 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 . + +if [target_info exists use_gdb_stub] { + return 0 +} + +load_lib gdb-python.exp + +standard_testfile + +set orig_srcfile $srcdir/$subdir/$testfile.c +# Copy the source file to the test directory so +# we can remove it to test the find_source hook. +set srcfile [standard_output_file $testfile.c] +set result [catch "exec cp $orig_srcfile $srcfile" output] +if {$result == 1} { + return -1 +} + +if { [prepare_for_testing ${testfile}.exp ${binfile} ${srcfile}] } { + return -1 +} + +set pyfile ${srcdir}/${subdir}/${testfile}.py + +if { [skip_python_tests] } { continue } + +gdb_test_no_output "python exec (open ('${pyfile}').read ())" "" + +gdb_py_test_silent_cmd "python gdb.find_source_hook = find_source_do_nothing" "set find_source_hook" 0 + +# Since we haven't moved the source file yet, the hook should not be +# called. +gdb_test "python print(do_nothing_calls)" "0" +gdb_test "list 1" "10 This program is distributed in the hope that it will be useful," "find_source_do_nothing not called" +gdb_test "python print(do_nothing_calls)" "0" + +# Now remove the source file so gdb can't find it. +set result [catch "exec rm $srcfile" output] +if {$result == 1} { + return -1 +} + +# Reset dir to reset file paths. +# Note: do not use $subdir here because otherwise we'd find the file there! +gdb_reinitialize_dir $srcdir + +# The hook should be invoked here. +gdb_test "list 1" "1\[ \t\]+$srcfile: No such file or directory." "find_source_do_nothing gets called" +# It actually gets invoked 3 times when running list, but I'd rather +# not depend on that exact number. +gdb_test "python print(do_nothing_calls > 0)" "True" + +# Check that a hook that raises prints an exception +gdb_reinitialize_dir $srcdir + +gdb_py_test_silent_cmd "python gdb.find_source_hook = find_source_raise" "set find_source_hook" 0 +gdb_test "list 1" "Python Exception oops: +\r\n1\[ \t\]+$srcfile: No such file or directory." "find_source_raise output" + +# Check that a hook that returns a non-string prints an error. +gdb_reinitialize_dir $srcdir + +gdb_py_test_silent_cmd "python gdb.find_source_hook = find_source_bad_return" "set find_source_hook" 0 +gdb_test "list 1" "Python Exception Return from find_source_hook must be either a Python string, or None: +\r\n1\[ \t\]+$srcfile: No such file or directory." "find_source_bad_return output" + +# Finally check that a hook that returns a new, valid path works. +gdb_reinitialize_dir $srcdir +gdb_py_test_silent_cmd "python new_path = '$orig_srcfile'" "set find_source_hook" 0 +gdb_py_test_silent_cmd "python gdb.find_source_hook = find_source_new_path" "set find_source_hook" 0 +gdb_test "python print(new_path_calls)" "0" +# Should get the source back now +gdb_test "list 1" "10 This program is distributed in the hope that it will be useful," "find_source_new_path works" +# This is 2 in my testing, but again I would rather not rely on that. +gdb_test "python print(new_path_calls > 0)" "True" +set new_path_calls [get_python_valueof "new_path_calls" "None"] +if { ${new_path_calls} == "None" } { + fail "new_path_calls not found" +} +# Check that the returned path will be cached. +gdb_test "list 1" "10 This program is distributed in the hope that it will be useful," "find_source_new_path return value gets cached" +if { [get_python_valueof "new_path_calls" "None"] != ${new_path_calls} } { + fail "find_source hook shouldn't have been called again" +} diff --git a/gdb/testsuite/gdb.python/py-find-source-hook.py b/gdb/testsuite/gdb.python/py-find-source-hook.py new file mode 100644 index 0000000..90f7fe9 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-find-source-hook.py @@ -0,0 +1,42 @@ +# Copyright (C) 2010-2015 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 python's find_source_hook. +import gdb + +# This gets set by the test script. +new_path = None + +# Number of calls to do_nothing +do_nothing_calls = 0 + +# Number of calls to new_path +new_path_calls = 0 + +def find_source_do_nothing (source): + global do_nothing_calls + do_nothing_calls += 1 + +def find_source_raise (source): + raise Exception("oops") + +def find_source_bad_return (source): + return 1 + +def find_source_new_path (source): + global new_path_calls + new_path_calls += 1 + global new_path + return new_path