Exemple #1
0
    def __init__(self,
                 build_directory,
                 package_name,
                 pyelftools_dir=None,
                 adb='adb'):
        self._build_directory = build_directory
        if not os.path.exists(self._build_directory):
            logging.fatal("Please pass a valid build directory")
            sys.exit(1)
        self._package_name = package_name
        self._adb = adb
        self._remote_file_cache = os.path.join(os.getenv('HOME'),
                                               '.mojosymbols')

        if pyelftools_dir != None:
            sys.path.append(pyelftools_dir)
        try:
            import elftools.elf.elffile as elffile
        except ImportError:
            logging.fatal("Unable to find elftools module; please install it "
                          "(for exmple, using 'pip install elftools')")
            sys.exit(1)

        self._elffile_module = elffile

        self._libraries = self._find_libraries(build_directory)
        self._rfc = RemoteFileConnection('localhost', 10000)
        self._remote_file_reader_process = None
        if not os.path.exists(self._remote_file_cache):
            os.makedirs(self._remote_file_cache)
        self._done_mapping = set()
        self._downloaded_files = []
Exemple #2
0
    def __init__(self, build_directory_list, package_name, pyelftools_dir,
                 adb):
        build_directories = build_directory_list.split(',')
        if len(build_directories) == 0 or not all(
                map(os.path.exists, build_directories)):
            logging.fatal("Please pass a list of valid build directories")
            sys.exit(1)
        self._package_name = package_name
        self._adb = adb
        self._remote_file_cache = os.path.join(os.getenv('HOME'),
                                               '.mojosymbols')

        if pyelftools_dir != None:
            sys.path.append(pyelftools_dir)
        try:
            import elftools.elf.elffile as elffile
        except ImportError:
            logging.fatal(
                "Unable to find elftools module; please install pyelftools "
                "and specify its path on the command line using "
                "--pyelftools-dir.")
            sys.exit(1)

        self._elffile_module = elffile

        self._libraries = self._find_libraries(build_directories)
        self._rfc = RemoteFileConnection('localhost', 10000)
        self._remote_file_reader_process = None
        if not os.path.exists(self._remote_file_cache):
            os.makedirs(self._remote_file_cache)
        self._done_mapping = set()
        self._downloaded_files = []
Exemple #3
0
    def __init__(self, build_directory, package_name, pyelftools_dir, adb):
        self._build_directory = build_directory
        if not os.path.exists(self._build_directory):
            logging.fatal("Please pass a valid build directory")
            sys.exit(1)
        self._package_name = package_name
        self._adb = adb
        self._remote_file_cache = os.path.join(os.getenv("HOME"), ".mojosymbols")

        if pyelftools_dir != None:
            sys.path.append(pyelftools_dir)
        try:
            import elftools.elf.elffile as elffile
        except ImportError:
            logging.fatal(
                "Unable to find elftools module; please install pyelftools "
                "and specify its path on the command line using "
                "--pyelftools-dir."
            )
            sys.exit(1)

        self._elffile_module = elffile

        self._libraries = self._find_libraries(build_directory)
        self._rfc = RemoteFileConnection("localhost", 10000)
        self._remote_file_reader_process = None
        if not os.path.exists(self._remote_file_cache):
            os.makedirs(self._remote_file_cache)
        self._done_mapping = set()
        self._downloaded_files = []
Exemple #4
0
  def __init__(self, build_directory_list, package_name, pyelftools_dir, adb):
    build_directories = build_directory_list.split(',')
    if len(build_directories) == 0 or not all(map(os.path.exists,
                                                  build_directories)):
      logging.fatal("Please pass a list of valid build directories")
      sys.exit(1)
    self._package_name = package_name
    self._adb = adb
    self._remote_file_cache = os.path.join(os.getenv('HOME'), '.mojosymbols')

    if pyelftools_dir is not None:
      sys.path.append(pyelftools_dir)
    try:
      import elftools.elf.elffile as elffile
    except ImportError:
      logging.fatal("Unable to find elftools module; please install pyelftools "
                    "and specify its path on the command line using "
                    "--pyelftools-dir.")
      sys.exit(1)

    self._elffile_module = elffile

    self._libraries = self._find_libraries(build_directories)
    self._rfc = RemoteFileConnection('localhost', 10000)
    self._remote_file_reader_process = None
    if not os.path.exists(self._remote_file_cache):
      os.makedirs(self._remote_file_cache)
    self._done_mapping = set()
    self._downloaded_files = []
Exemple #5
0
class DebugSession(object):
    def __init__(self, build_directory, package_name, pyelftools_dir, adb):
        self._build_directory = build_directory
        if not os.path.exists(self._build_directory):
            logging.fatal("Please pass a valid build directory")
            sys.exit(1)
        self._package_name = package_name
        self._adb = adb
        self._remote_file_cache = os.path.join(os.getenv("HOME"), ".mojosymbols")

        if pyelftools_dir != None:
            sys.path.append(pyelftools_dir)
        try:
            import elftools.elf.elffile as elffile
        except ImportError:
            logging.fatal(
                "Unable to find elftools module; please install pyelftools "
                "and specify its path on the command line using "
                "--pyelftools-dir."
            )
            sys.exit(1)

        self._elffile_module = elffile

        self._libraries = self._find_libraries(build_directory)
        self._rfc = RemoteFileConnection("localhost", 10000)
        self._remote_file_reader_process = None
        if not os.path.exists(self._remote_file_cache):
            os.makedirs(self._remote_file_cache)
        self._done_mapping = set()
        self._downloaded_files = []

    def __del__(self):
        # Note that, per python interpreter documentation, __del__ is not
        # guaranteed to be called when the interpreter (GDB, in our case) quits.
        # Also, most (all?) globals are no longer available at this time (launching
        # a subprocess does not work).
        self.stop()

    def stop(self, _unused_return_value=None):
        if self._remote_file_reader_process != None:
            self._remote_file_reader_process.kill()

    def _find_libraries(self, lib_dir):
        """Finds all libraries in |lib_dir| and key them by their signatures.
    """
        res = {}
        for fn in glob.glob("%s/*.so" % lib_dir):
            with open(fn, "r") as f:
                s = get_signature(f, self._elffile_module)
                if s is not None:
                    res[s] = fn
        return res

    def _associate_symbols(self, mapping, local_file):
        with open(local_file, "r") as f:
            elf = self._elffile_module.ELFFile(f)
            s = elf.get_section_by_name(".text")
            text_address = mapping[0].start + s["sh_offset"]
            _gdb_execute("add-symbol-file %s 0x%x" % (local_file, text_address))

    def _download_file(self, signature, remote):
        """Downloads a remote file either from the cloud or through GDB connection.

    Returns:
      The filename of the downloaded file
    """
        temp_file = tempfile.NamedTemporaryFile()
        logging.info("Trying to download symbols from the cloud.")
        symbols_url = "http://storage.googleapis.com/mojo/symbols/%s" % signature
        try:
            symbol_file = urllib2.urlopen(symbols_url)
            try:
                with open(temp_file.name, "w") as dst:
                    shutil.copyfileobj(symbol_file, dst)
                    logging.info("Getting symbols for %s at %s." % (remote, symbols_url))
                    # This allows the deletion of temporary files on disk when the
                    # debugging session terminates.
                    self._downloaded_files.append(temp_file)
                    return temp_file.name
            finally:
                symbol_file.close()
        except urllib2.HTTPError:
            pass
        logging.info("Downloading file %s" % remote)
        _gdb_execute("remote get %s %s" % (remote, temp_file.name))
        # This allows the deletion of temporary files on disk when the debugging
        # session terminates.
        self._downloaded_files.append(temp_file)
        return temp_file.name

    def _find_mapping_for_address(self, mappings, address):
        """Returns the list of all mappings of the file occupying the |address|
    memory address.
    """
        for file_mappings in mappings:
            for mapping in file_mappings:
                if address >= mapping.start and address <= mapping.end:
                    return file_mappings
        return None

    def _try_to_map(self, mapping):
        remote_file = mapping[0].filename
        if remote_file in self._done_mapping:
            return False
        self._done_mapping.add(remote_file)
        self._rfc.open(remote_file)
        signature = get_signature(self._rfc, self._elffile_module)
        if signature is not None:
            if signature in self._libraries:
                self._associate_symbols(mapping, self._libraries[signature])
            else:
                # This library file is not known locally. Download it from the device or
                # the cloud and put it in cache so, if it got symbols, we can see them.
                local_file = os.path.join(self._remote_file_cache, signature)
                if not os.path.exists(local_file):
                    tmp_output = self._download_file(signature, remote_file)
                    shutil.move(tmp_output, local_file)
                self._associate_symbols(mapping, local_file)
            return True
        return False

    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.

    If current_thread_only is True, only update symbols for the current thread.
    """
        logging.info("Updating symbols")
        mapped_files = _get_mapped_files()
        # Map all symbols from native libraries packages with the APK.
        for file_mappings in mapped_files:
            filename = file_mappings[0].filename
            if (
                (filename.startswith("/data/data/") or filename.startswith("/data/app"))
                and not filename.endswith(".apk")
                and not filename.endswith(".dex")
            ):
                logging.info("Pre-mapping: %s" % file_mappings[0].filename)
                self._try_to_map(file_mappings)

        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."""
        output = subprocess.check_output([self._adb, "shell", "ps"])
        for line in output.split("\n"):
            elements = line.split()
            if len(elements) > 0 and elements[-1] == application:
                return elements[1]
        return None

    def start(self):
        """Starts a debugging session."""
        gdbserver_pid = self._get_device_application_pid("gdbserver")
        if gdbserver_pid is not None:
            subprocess.check_call([self._adb, "shell", "kill", gdbserver_pid])
        shell_pid = self._get_device_application_pid(self._package_name)
        if shell_pid is None:
            raise Exception("Unable to find a running mojo shell.")
        subprocess.check_call([self._adb, "forward", "tcp:9999", "tcp:9999"])
        subprocess.Popen(
            [self._adb, "shell", "gdbserver", "--attach", ":9999", shell_pid],
            # os.setpgrp ensures signals passed to this file (such as SIGINT) are
            # not propagated to child processes.
            preexec_fn=os.setpgrp,
        )

        # Kill stray remote reader processes. See __del__ comment for more info.
        remote_file_reader_pid = self._get_device_application_pid(config.REMOTE_FILE_READER_DEVICE_PATH)
        if remote_file_reader_pid is not None:
            subprocess.check_call([self._adb, "shell", "kill", remote_file_reader_pid])
        self._remote_file_reader_process = subprocess.Popen(
            [self._adb, "shell", config.REMOTE_FILE_READER_DEVICE_PATH], stdout=subprocess.PIPE, preexec_fn=os.setpgrp
        )
        port = int(self._remote_file_reader_process.stdout.readline())
        subprocess.check_call([self._adb, "forward", "tcp:10000", "tcp:%d" % port])
        self._rfc.connect()

        _gdb_execute("target remote localhost:9999")

        self.update_symbols(current_thread_only=False)

        def on_stop(_):
            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)
Exemple #6
0
class DebugSession(object):
    def __init__(self,
                 build_directory,
                 package_name,
                 pyelftools_dir=None,
                 adb='adb'):
        self._build_directory = build_directory
        if not os.path.exists(self._build_directory):
            logging.fatal("Please pass a valid build directory")
            sys.exit(1)
        self._package_name = package_name
        self._adb = adb
        self._remote_file_cache = os.path.join(os.getenv('HOME'),
                                               '.mojosymbols')

        if pyelftools_dir != None:
            sys.path.append(pyelftools_dir)
        try:
            import elftools.elf.elffile as elffile
        except ImportError:
            logging.fatal("Unable to find elftools module; please install it "
                          "(for exmple, using 'pip install elftools')")
            sys.exit(1)

        self._elffile_module = elffile

        self._libraries = self._find_libraries(build_directory)
        self._rfc = RemoteFileConnection('localhost', 10000)
        self._remote_file_reader_process = None
        if not os.path.exists(self._remote_file_cache):
            os.makedirs(self._remote_file_cache)
        self._done_mapping = set()
        self._downloaded_files = []

    def __del__(self):
        # Note that, per python interpreter documentation, __del__ is not
        # guaranteed to be called when the interpreter (GDB, in our case) quits.
        # Also, most (all?) globals are no longer available at this time (launching
        # a subprocess does not work).
        self.stop()

    def stop(self, _unused_return_value=None):
        if self._remote_file_reader_process != None:
            self._remote_file_reader_process.kill()

    def _find_libraries(self, lib_dir):
        """Finds all libraries in |lib_dir| and key them by their signatures.
    """
        res = {}
        for fn in glob.glob('%s/*.so' % lib_dir):
            with open(fn, 'r') as f:
                s = get_signature(f, self._elffile_module)
                if s is not None:
                    res[s] = fn
        return res

    def _associate_symbols(self, mapping, local_file):
        with open(local_file, "r") as f:
            elf = self._elffile_module.ELFFile(f)
            s = elf.get_section_by_name(".text")
            text_address = mapping[0].start + s['sh_offset']
            _gdb_execute("add-symbol-file %s 0x%x" %
                         (local_file, text_address))

    def _download_file(self, signature, remote):
        """Downloads a remote file either from the cloud or through GDB connection.

    Returns:
      The filename of the downloaded file
    """
        temp_file = tempfile.NamedTemporaryFile()
        logging.info("Trying to download symbols from the cloud.")
        symbols_url = "http://storage.googleapis.com/mojo/symbols/%s" % signature
        try:
            symbol_file = urllib2.urlopen(symbols_url)
            try:
                with open(temp_file.name, "w") as dst:
                    shutil.copyfileobj(symbol_file, dst)
                    logging.info("Getting symbols for %s at %s." %
                                 (remote, symbols_url))
                    # This allows the deletion of temporary files on disk when the
                    # debugging session terminates.
                    self._downloaded_files.append(temp_file)
                    return temp_file.name
            finally:
                symbol_file.close()
        except urllib2.HTTPError:
            pass
        logging.info("Downloading file %s" % remote)
        _gdb_execute("remote get %s %s" % (remote, temp_file.name))
        # This allows the deletion of temporary files on disk when the debugging
        # session terminates.
        self._downloaded_files.append(temp_file)
        return temp_file.name

    def _find_mapping_for_address(self, mappings, address):
        """Returns the list of all mappings of the file occupying the |address|
    memory address.
    """
        for file_mappings in mappings:
            for mapping in file_mappings:
                if address >= mapping.start and address <= mapping.end:
                    return file_mappings
        return None

    def _try_to_map(self, mapping):
        remote_file = mapping[0].filename
        if remote_file in self._done_mapping:
            return False
        self._done_mapping.add(remote_file)
        self._rfc.open(remote_file)
        signature = get_signature(self._rfc, self._elffile_module)
        if signature is not None:
            if signature in self._libraries:
                self._associate_symbols(mapping, self._libraries[signature])
            else:
                # This library file is not known locally. Download it from the device or
                # the cloud and put it in cache so, if it got symbols, we can see them.
                local_file = os.path.join(self._remote_file_cache, signature)
                if not os.path.exists(local_file):
                    tmp_output = self._download_file(signature, remote_file)
                    shutil.move(tmp_output, local_file)
                self._associate_symbols(mapping, local_file)
            return True
        return False

    def _update_symbols(self):
        """Updates the mapping between symbols as seen from GDB and local library
    files."""
        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
            if ((filename.startswith('/data/data/')
                 or filename.startswith('/data/app'))
                    and not filename.endswith('.apk')
                    and 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()

    def _get_device_application_pid(self, application):
        """Gets the PID of an application running on a device."""
        output = subprocess.check_output([self._adb, 'shell', 'ps'])
        for line in output.split('\n'):
            elements = line.split()
            if len(elements) > 0 and elements[-1] == application:
                return elements[1]
        return None

    def start(self):
        """Starts a debugging session."""
        gdbserver_pid = self._get_device_application_pid('gdbserver')
        if gdbserver_pid is not None:
            subprocess.check_call([self._adb, 'shell', 'kill', gdbserver_pid])
        shell_pid = self._get_device_application_pid(self._package_name)
        if shell_pid is None:
            raise Exception('Unable to find a running mojo shell.')
        subprocess.check_call([self._adb, 'forward', 'tcp:9999', 'tcp:9999'])
        subprocess.Popen(
            [self._adb, 'shell', 'gdbserver', '--attach', ':9999', shell_pid],
            # os.setpgrp ensures signals passed to this file (such as SIGINT) are
            # not propagated to child processes.
            preexec_fn=os.setpgrp)

        # Kill stray remote reader processes. See __del__ comment for more info.
        remote_file_reader_pid = self._get_device_application_pid(
            config.REMOTE_FILE_READER_DEVICE_PATH)
        if remote_file_reader_pid is not None:
            subprocess.check_call(
                [self._adb, 'shell', 'kill', remote_file_reader_pid])
        self._remote_file_reader_process = subprocess.Popen(
            [self._adb, 'shell', config.REMOTE_FILE_READER_DEVICE_PATH],
            stdout=subprocess.PIPE,
            preexec_fn=os.setpgrp)
        port = int(self._remote_file_reader_process.stdout.readline())
        subprocess.check_call(
            [self._adb, 'forward', 'tcp:10000',
             'tcp:%d' % port])
        self._rfc.connect()

        _gdb_execute('target remote localhost:9999')

        self._update_symbols()

        def on_stop(_):
            self._update_symbols()

        gdb.events.stop.connect(on_stop)
        gdb.events.exited.connect(self.stop)