servo: Merge #4083 - Mach debugger flag (from mttr:mach_debugger_flag); r=mbrubeck

Borrows two python modules from mozilla-central to give mach the ability to detect and pass arguments to a system's preferred debugger, and adds `--debug` and `--debugger` flags to `./mach run`. This works almost like the functionality described [here](https://developer.mozilla.org/en-US/docs/Debugging_Mozilla_with_gdb), but at the moment it lacks a `--debugparams`  argument.

Links to borrowed files:
http://hg.mozilla.org/mozilla-central/file/c9cfa9b91dea/testing/mozbase/mozinfo/mozinfo/mozinfo.py
http://hg.mozilla.org/mozilla-central/file/c9cfa9b91dea/testing/mozbase/mozdebug/mozdebug/mozdebug.py

Source-Repo: https://github.com/servo/servo
Source-Revision: 989efd5a2820a152c5d5a2d42c9be3c30d79f469
This commit is contained in:
Matthew Rasmus 2014-11-24 10:15:46 -07:00
Родитель 196129611e
Коммит 7ae32f7f97
6 изменённых файлов: 524 добавлений и 3 удалений

Просмотреть файл

@ -11,6 +11,8 @@ import sys
SEARCH_PATHS = [
"python/mach",
"python/toml",
"python/mozinfo",
"python/mozdebug",
]
# Individual files providing mach commands.

Просмотреть файл

@ -0,0 +1,30 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
"""
This module contains a set of function to gather information about the
debugging capabilities of the platform. It allows to look for a specific
debugger or to query the system for a compatible/default debugger.
The following simple example looks for the default debugger on the
current platform and launches a debugger process with the correct
debugger-specific arguments:
::
import mozdebug
debugger = mozdebug.get_default_debugger_name()
debuggerInfo = mozdebug.get_debugger_info(debugger)
debuggeePath = "toDebug"
processArgs = [self.debuggerInfo.path] + self.debuggerInfo.args
processArgs.append(debuggeePath)
run_process(args, ...)
"""
from mozdebug import *

Просмотреть файл

@ -0,0 +1,168 @@
#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import os
import mozinfo
from collections import namedtuple
from distutils.spawn import find_executable
__all__ = ['get_debugger_info',
'get_default_debugger_name',
'DebuggerSearch']
'''
Map of debugging programs to information about them, like default arguments
and whether or not they are interactive.
To add support for a new debugger, simply add the relative entry in
_DEBUGGER_INFO and optionally update the _DEBUGGER_PRIORITIES.
'''
_DEBUGGER_INFO = {
# gdb requires that you supply the '--args' flag in order to pass arguments
# after the executable name to the executable.
'gdb': {
'interactive': True,
'args': ['-q', '--args']
},
'cgdb': {
'interactive': True,
'args': ['-q', '--args']
},
'lldb': {
'interactive': True,
'args': ['--'],
'requiresEscapedArgs': True
},
# Visual Studio Debugger Support.
'devenv.exe': {
'interactive': True,
'args': ['-debugexe']
},
# Visual C++ Express Debugger Support.
'wdexpress.exe': {
'interactive': True,
'args': ['-debugexe']
},
# valgrind doesn't explain much about leaks unless you set the
# '--leak-check=full' flag. But there are a lot of objects that are
# semi-deliberately leaked, so we set '--show-possibly-lost=no' to avoid
# uninteresting output from those objects. We set '--smc-check==all-non-file'
# and '--vex-iropt-register-updates=allregs-at-mem-access' so that valgrind
# deals properly with JIT'd JavaScript code.
'valgrind': {
'interactive': False,
'args': ['--leak-check=full',
'--show-possibly-lost=no',
'--smc-check=all-non-file',
'--vex-iropt-register-updates=allregs-at-mem-access']
}
}
# Maps each OS platform to the preferred debugger programs found in _DEBUGGER_INFO.
_DEBUGGER_PRIORITIES = {
'win': ['devenv.exe', 'wdexpress.exe'],
'linux': ['gdb', 'cgdb', 'lldb'],
'mac': ['lldb', 'gdb'],
'unknown': ['gdb']
}
def get_debugger_info(debugger, debuggerArgs = None, debuggerInteractive = False):
'''
Get the information about the requested debugger.
Returns a dictionary containing the |path| of the debugger executable,
if it will run in |interactive| mode, its arguments and whether it needs
to escape arguments it passes to the debugged program (|requiresEscapedArgs|).
If the debugger cannot be found in the system, returns |None|.
:param debugger: The name of the debugger.
:param debuggerArgs: If specified, it's the arguments to pass to the debugger,
as a string. Any debugger-specific separator arguments are appended after these
arguments.
:param debuggerInteractive: If specified, forces the debugger to be interactive.
'''
debuggerPath = None
if debugger:
# Append '.exe' to the debugger on Windows if it's not present,
# so things like '--debugger=devenv' work.
if (os.name == 'nt'
and not debugger.lower().endswith('.exe')):
debugger += '.exe'
debuggerPath = find_executable(debugger)
if not debuggerPath:
print 'Error: Could not find debugger %s.' % debugger
return None
debuggerName = os.path.basename(debuggerPath).lower()
def get_debugger_info(type, default):
if debuggerName in _DEBUGGER_INFO and type in _DEBUGGER_INFO[debuggerName]:
return _DEBUGGER_INFO[debuggerName][type]
return default
# Define a namedtuple to access the debugger information from the outside world.
DebuggerInfo = namedtuple(
'DebuggerInfo',
['path', 'interactive', 'args', 'requiresEscapedArgs']
)
debugger_arguments = []
if debuggerArgs:
# Append the provided debugger arguments at the end of the arguments list.
debugger_arguments += debuggerArgs.split()
debugger_arguments += get_debugger_info('args', [])
# Override the default debugger interactive mode if needed.
debugger_interactive = get_debugger_info('interactive', False)
if debuggerInteractive:
debugger_interactive = debuggerInteractive
d = DebuggerInfo(
debuggerPath,
debugger_interactive,
debugger_arguments,
get_debugger_info('requiresEscapedArgs', False)
)
return d
# Defines the search policies to use in get_default_debugger_name.
class DebuggerSearch:
OnlyFirst = 1
KeepLooking = 2
def get_default_debugger_name(search=DebuggerSearch.OnlyFirst):
'''
Get the debugger name for the default debugger on current platform.
:param search: If specified, stops looking for the debugger if the
default one is not found (|DebuggerSearch.OnlyFirst|) or keeps
looking for other compatible debuggers (|DebuggerSearch.KeepLooking|).
'''
# Find out which debuggers are preferred for use on this platform.
debuggerPriorities = _DEBUGGER_PRIORITIES[mozinfo.os if mozinfo.os in _DEBUGGER_PRIORITIES else 'unknown']
# Finally get the debugger information.
for debuggerName in debuggerPriorities:
debuggerPath = find_executable(debuggerName)
if debuggerPath:
return debuggerName
elif not search == DebuggerSearch.KeepLooking:
return None
return None

Просмотреть файл

@ -0,0 +1,56 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
"""
interface to transform introspected system information to a format palatable to
Mozilla
Module variables:
.. attribute:: bits
32 or 64
.. attribute:: isBsd
Returns ``True`` if the operating system is BSD
.. attribute:: isLinux
Returns ``True`` if the operating system is Linux
.. attribute:: isMac
Returns ``True`` if the operating system is Mac
.. attribute:: isWin
Returns ``True`` if the operating system is Windows
.. attribute:: os
Operating system [``'win'``, ``'mac'``, ``'linux'``, ...]
.. attribute:: processor
Processor architecture [``'x86'``, ``'x86_64'``, ``'ppc'``, ...]
.. attribute:: version
Operating system version string. For windows, the service pack information is also included
.. attribute:: info
Returns information identifying the current system.
* :attr:`bits`
* :attr:`os`
* :attr:`processor`
* :attr:`version`
"""
import mozinfo
from mozinfo import *
__all__ = mozinfo.__all__

233
servo/python/mozinfo/mozinfo.py Executable file
Просмотреть файл

@ -0,0 +1,233 @@
#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
# TODO: it might be a good idea of adding a system name (e.g. 'Ubuntu' for
# linux) to the information; I certainly wouldn't want anyone parsing this
# information and having behaviour depend on it
import os
import platform
import re
import sys
# keep a copy of the os module since updating globals overrides this
_os = os
class unknown(object):
"""marker class for unknown information"""
def __nonzero__(self):
return False
def __str__(self):
return 'UNKNOWN'
unknown = unknown() # singleton
# get system information
info = {'os': unknown,
'processor': unknown,
'version': unknown,
'os_version': unknown,
'bits': unknown,
'has_sandbox': unknown }
(system, node, release, version, machine, processor) = platform.uname()
(bits, linkage) = platform.architecture()
# get os information and related data
if system in ["Microsoft", "Windows"]:
info['os'] = 'win'
# There is a Python bug on Windows to determine platform values
# http://bugs.python.org/issue7860
if "PROCESSOR_ARCHITEW6432" in os.environ:
processor = os.environ.get("PROCESSOR_ARCHITEW6432", processor)
else:
processor = os.environ.get('PROCESSOR_ARCHITECTURE', processor)
system = os.environ.get("OS", system).replace('_', ' ')
(major, minor, _, _, service_pack) = os.sys.getwindowsversion()
info['service_pack'] = service_pack
os_version = "%d.%d" % (major, minor)
elif system == "Linux":
if hasattr(platform, "linux_distribution"):
(distro, os_version, codename) = platform.linux_distribution()
else:
(distro, os_version, codename) = platform.dist()
if not processor:
processor = machine
version = "%s %s" % (distro, os_version)
info['os'] = 'linux'
info['linux_distro'] = distro
elif system in ['DragonFly', 'FreeBSD', 'NetBSD', 'OpenBSD']:
info['os'] = 'bsd'
version = os_version = sys.platform
elif system == "Darwin":
(release, versioninfo, machine) = platform.mac_ver()
version = "OS X %s" % release
versionNums = release.split('.')[:2]
os_version = "%s.%s" % (versionNums[0], versionNums[1])
info['os'] = 'mac'
elif sys.platform in ('solaris', 'sunos5'):
info['os'] = 'unix'
os_version = version = sys.platform
else:
os_version = version = unknown
info['version'] = version
info['os_version'] = os_version
# processor type and bits
if processor in ["i386", "i686"]:
if bits == "32bit":
processor = "x86"
elif bits == "64bit":
processor = "x86_64"
elif processor.upper() == "AMD64":
bits = "64bit"
processor = "x86_64"
elif processor == "Power Macintosh":
processor = "ppc"
bits = re.search('(\d+)bit', bits).group(1)
info.update({'processor': processor,
'bits': int(bits),
})
if info['os'] == 'linux':
import ctypes
import errno
PR_SET_SECCOMP = 22
SECCOMP_MODE_FILTER = 2
ctypes.CDLL("libc.so.6", use_errno=True).prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, 0)
info['has_sandbox'] = ctypes.get_errno() == errno.EFAULT
else:
info['has_sandbox'] = True
# standard value of choices, for easy inspection
choices = {'os': ['linux', 'bsd', 'win', 'mac', 'unix'],
'bits': [32, 64],
'processor': ['x86', 'x86_64', 'ppc']}
def sanitize(info):
"""Do some sanitization of input values, primarily
to handle universal Mac builds."""
if "processor" in info and info["processor"] == "universal-x86-x86_64":
# If we're running on OS X 10.6 or newer, assume 64-bit
if release[:4] >= "10.6": # Note this is a string comparison
info["processor"] = "x86_64"
info["bits"] = 64
else:
info["processor"] = "x86"
info["bits"] = 32
# method for updating information
def update(new_info):
"""
Update the info.
:param new_info: Either a dict containing the new info or a path/url
to a json file containing the new info.
"""
if isinstance(new_info, basestring):
# lazy import
import mozfile
import json
f = mozfile.load(new_info)
new_info = json.loads(f.read())
f.close()
info.update(new_info)
sanitize(info)
globals().update(info)
# convenience data for os access
for os_name in choices['os']:
globals()['is' + os_name.title()] = info['os'] == os_name
# unix is special
if isLinux or isBsd:
globals()['isUnix'] = True
def find_and_update_from_json(*dirs):
"""
Find a mozinfo.json file, load it, and update the info with the
contents.
:param dirs: Directories in which to look for the file. They will be
searched after first looking in the root of the objdir
if the current script is being run from a Mozilla objdir.
Returns the full path to mozinfo.json if it was found, or None otherwise.
"""
# First, see if we're in an objdir
try:
from mozbuild.base import MozbuildObject, BuildEnvironmentNotFoundException
build = MozbuildObject.from_environment()
json_path = _os.path.join(build.topobjdir, "mozinfo.json")
if _os.path.isfile(json_path):
update(json_path)
return json_path
except ImportError:
pass
except BuildEnvironmentNotFoundException:
pass
for d in dirs:
d = _os.path.abspath(d)
json_path = _os.path.join(d, "mozinfo.json")
if _os.path.isfile(json_path):
update(json_path)
return json_path
return None
update({})
# exports
__all__ = info.keys()
__all__ += ['is' + os_name.title() for os_name in choices['os']]
__all__ += [
'info',
'unknown',
'main',
'choices',
'update',
'find_and_update_from_json',
]
def main(args=None):
# parse the command line
from optparse import OptionParser
parser = OptionParser(description=__doc__)
for key in choices:
parser.add_option('--%s' % key, dest=key,
action='store_true', default=False,
help="display choices for %s" % key)
options, args = parser.parse_args()
# args are JSON blobs to override info
if args:
# lazy import
import json
for arg in args:
if _os.path.exists(arg):
string = file(arg).read()
else:
string = arg
update(json.loads(string))
# print out choices if requested
flag = False
for key, value in options.__dict__.items():
if value is True:
print '%s choices: %s' % (key, ' '.join([str(choice)
for choice in choices[key]]))
flag = True
if flag: return
# otherwise, print out all info
for key, value in info.items():
print '%s: %s' % (key, value)
if __name__ == '__main__':
main()

Просмотреть файл

@ -1,10 +1,12 @@
from __future__ import print_function, unicode_literals
import argparse
import os.path as path
from os import chdir
import subprocess
import SimpleHTTPServer
import SocketServer
import mozdebug
from shutil import copytree, rmtree, ignore_patterns
from mach.decorators import (
@ -21,14 +23,44 @@ class MachCommands(CommandBase):
@Command('run',
description='Run Servo',
category='post-build')
@CommandArgument('--debug', action='store_true',
help='Enable the debugger. Not specifying a '
'--debugger option will result in the default '
'debugger being used. The following arguments '
'have no effect without this.')
@CommandArgument('--debugger', default=None, type=str,
help='Name of debugger to use.')
@CommandArgument(
'params', nargs='...',
help="Command-line arguments to be passed through to Servo")
def run(self, params):
def run(self, params, debug=False, debugger=None):
env = self.build_env()
env["RUST_BACKTRACE"] = "1"
subprocess.check_call([path.join("target", "servo")] + params,
env=env)
args = [path.join("target", "servo")]
# Borrowed and modified from:
# http://hg.mozilla.org/mozilla-central/file/c9cfa9b91dea/python/mozbuild/mozbuild/mach_commands.py#l883
if debug:
import mozdebug
if not debugger:
# No debugger name was provided. Look for the default ones on
# current OS.
debugger = mozdebug.get_default_debugger_name(
mozdebug.DebuggerSearch.KeepLooking)
self.debuggerInfo = mozdebug.get_debugger_info(debugger)
if not self.debuggerInfo:
print("Could not find a suitable debugger in your PATH.")
return 1
# Prepend the debugger args.
args = ([self.debuggerInfo.path] + self.debuggerInfo.args
+ args + params)
else:
args = args + params
subprocess.check_call(args, env=env)
@Command('doc',
description='Generate documentation',