This is the mail archive of the
gdb-patches@sourceware.org
mailing list for the GDB project.
[PATCH] Add a find_source hook for extension languages to locate missing source files.
- From: Ted Mielczarek <ted at mielczarek dot org>
- To: gdb-patches at sourceware dot org
- Date: Wed, 6 May 2015 15:58:03 -0400
- Subject: [PATCH] Add a find_source hook for extension languages to locate missing source files.
- Authentication-results: sourceware.org; auth=none
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 <http://www.gnu.org/licenses/>. */
+
+
+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 <http://www.gnu.org/licenses/>.
+
+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 <type 'exceptions.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 <type 'exceptions.RuntimeError'> 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 <http://www.gnu.org/licenses/>.
+
+# 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
--
1.9.1