Only update symbols on current thread when breaking or stepping in GDB.

R=ppi@chromium.org

Review URL: https://codereview.chromium.org/1253573008 .

Cr-Mirrored-From: https://github.com/domokit/mojo
Cr-Mirrored-Commit: c02a28868825edfa57ab77947b8cb15e741c5598
diff --git a/README.md b/README.md
index 9e063f1..d9dee44 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@
 `about://tracing`.
 
 #### GDB
-It is possible to inspect a Mojo Shell process using GDB. The `debugger` script
+It is possible to inspect a Mojo Shell process using GDB. The `mojo_debug` script
 can be used to launch GDB and attach it to a running shell process (android
 only):
 
@@ -47,9 +47,26 @@
 mojo_debug gdb attach
 ```
 
+Once started, GDB will first stop the Mojo Shell execution, then load symbols
+from loaded Mojo applications. Please note that this initial step can take some
+time (up to several minutes in the worst case).
+
+After each execution pause, GDB will update the set of loaded symbols based on
+the selected thread only. If you need symbols for all threads, use the
+`update-symbols` GDB command:
+```sh
+(gdb) update-symbols
+```
+
+If you only want to update symbols for the current selected thread (for example,
+after changing threads), use the `current` option:
+```sh
+(gdb) update-symbols current
+```
+
 #### Android crash stacks
 When Mojo shell crashes on Android ("Unfortunately, Mojo shell has stopped.")
-due to a crash in native code, `debugger` can be used to find and symbolize the
+due to a crash in native code, `mojo_debug` can be used to find and symbolize the
 stack trace present in the device log:
 
 ```sh
diff --git a/android_gdb/session.py b/android_gdb/session.py
index bc05c29..b16fe55 100644
--- a/android_gdb/session.py
+++ b/android_gdb/session.py
@@ -190,13 +190,33 @@
       return True
     return False
 
-  def _update_symbols(self):
+  def _map_symbols_on_current_thread(self, mapped_files):
+    """Updates the symbols for the current thread using files from mapped_files.
+    """
+    frame = gdb.newest_frame()
+    while frame and frame.is_valid():
+      if frame.name() is None:
+        m = self._find_mapping_for_address(mapped_files, frame.pc())
+        if m is not None and self._try_to_map(m):
+          # Force gdb to recompute its frames.
+          _gdb_execute("info threads")
+          frame = gdb.newest_frame()
+          assert frame.is_valid()
+      if (frame.older() is not None and
+          frame.older().is_valid() and
+          frame.older().pc() != frame.pc()):
+        frame = frame.older()
+      else:
+        frame = None
+
+  def update_symbols(self, current_thread_only):
     """Updates the mapping between symbols as seen from GDB and local library
-    files."""
+    files.
+
+    If current_thread_only is True, only update symbols for the current thread.
+    """
     logging.info("Updating symbols")
     mapped_files = _get_mapped_files()
-    _gdb_execute("info threads")
-    nb_threads = len(_gdb_execute("info threads").split("\n")) - 2
     # Map all symbols from native libraries packages with the APK.
     for file_mappings in mapped_files:
       filename = file_mappings[0].filename
@@ -206,26 +226,20 @@
           not filename.endswith('.dex')):
         logging.info('Pre-mapping: %s' % file_mappings[0].filename)
         self._try_to_map(file_mappings)
-    for i in xrange(nb_threads):
-      try:
-        _gdb_execute("thread %d" % (i + 1))
-        frame = gdb.newest_frame()
-        while frame and frame.is_valid():
-          if frame.name() is None:
-            m = self._find_mapping_for_address(mapped_files, frame.pc())
-            if m is not None and self._try_to_map(m):
-              # Force gdb to recompute its frames.
-              _gdb_execute("info threads")
-              frame = gdb.newest_frame()
-              assert frame.is_valid()
-          if (frame.older() is not None and
-              frame.older().is_valid() and
-              frame.older().pc() != frame.pc()):
-            frame = frame.older()
-          else:
-            frame = None
-      except gdb.error:
-        traceback.print_exc()
+
+    if current_thread_only:
+      self._map_symbols_on_current_thread(mapped_files)
+    else:
+      logging.info('Updating all threads\' symbols')
+      current_thread = gdb.selected_thread()
+      nb_threads = len(_gdb_execute("info threads").split("\n")) - 2
+      for i in xrange(nb_threads):
+        try:
+          _gdb_execute("thread %d" % (i + 1))
+          self._map_symbols_on_current_thread(mapped_files)
+        except gdb.error:
+          traceback.print_exc()
+      current_thread.switch()
 
   def _get_device_application_pid(self, application):
     """Gets the PID of an application running on a device."""
@@ -266,8 +280,38 @@
 
     _gdb_execute('target remote localhost:9999')
 
-    self._update_symbols()
+    self.update_symbols(current_thread_only=False)
     def on_stop(_):
-      self._update_symbols()
+      self.update_symbols(current_thread_only=True)
     gdb.events.stop.connect(on_stop)
     gdb.events.exited.connect(self.stop)
+
+    # Register the update-symbols command.
+    UpdateSymbols(self)
+
+class UpdateSymbols(gdb.Command):
+  """Command to update symbols loaded into GDB.
+
+  GDB usage: update-symbols [all|current]
+  """
+  _UPDATE_COMMAND = "update-symbols"
+
+  def __init__(self, session):
+    super(UpdateSymbols, self).__init__(self._UPDATE_COMMAND, gdb.COMMAND_STACK)
+    self._session = session
+
+  def invoke(self, arg, _unused_from_tty):
+    if arg == 'current':
+      self._session.update_symbols(current_thread_only=True)
+    else:
+      self._session.update_symbols(current_thread_only=False)
+
+  def complete(self, text, _unused_word):
+    if text == self._UPDATE_COMMAND:
+      return ('all', 'current')
+    elif text in self._UPDATE_COMMAND + ' all':
+      return ['all']
+    elif text in self._UPDATE_COMMAND + ' current':
+      return ['current']
+    else:
+      return []