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
