# Copyright (c) 2018 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Helper library for defining XMethod implementations on C++ classes. Include this library and then define python implementations of C++ methods using the Class and member_function decorator functions. """ import gdb import gdb.xmethod import operator import re class MemberFunction(object): def __init__(self, return_type, name, arguments, wrapped_function): self.return_type = return_type self.name = name self.arguments = arguments self.function_ = wrapped_function def __call__(self, *args): self.function_(*args) def member_function(return_type, name, arguments): """Decorate a member function. See Class decorator for example usage within a class. Args: return_type: The return type of the function (e.g. 'int') name: The function name (e.g. 'sum') arguments: The argument types for this function (e.g. ['int', 'int']) Each type can be a string (e.g. 'int', 'std::string', 'T*') or a function which constructs the return type. See CreateTypeResolver for details about type resolution. """ def DefineMember(fn): return MemberFunction(return_type, name, arguments, fn) return DefineMember def Class(class_name, template_types): """Decorate a python class with its corresponding C++ type. Args: class_name: The canonical string identifier for the class (e.g. base::Foo) template_types: An array of names for each templated type (e.g. ['K', 'V']) Example: As an example, the following is an implementation of size() and operator[] on std::__1::vector, functions which are normally inlined and not normally callable from gdb. @class_methods.Class('std::__1::vector', template_types=['T']) class LibcppVector(object): @class_methods.member_function('T&', 'operator[]', ['int']) def element(obj, i): return obj['__begin_'][i] @class_methods.member_function('size_t', 'size', []) def size(obj): return obj['__end_'] - obj['__begin_'] Note: Note that functions are looked up by the function name, which means that functions cannot currently have overloaded implementations for different arguments. """ class MethodWorkerWrapper(gdb.xmethod.XMethod): """Wrapper of an XMethodWorker class as an XMethod.""" def __init__(self, name, worker_class): super(MethodWorkerWrapper, self).__init__(name) self.name = name self.worker_class = worker_class class ClassMatcher(gdb.xmethod.XMethodMatcher): """Matches member functions of one class template.""" def __init__(self, obj): super(ClassMatcher, self).__init__(class_name) # Constructs a regular expression to match this type. self._class_regex = re.compile( '^' + re.escape(class_name) + ('<.*>' if len(template_types) > 0 else '') + '$') # Construct a dictionary and array of methods self.dict = {} self.methods = [] for name in dir(obj): attr = getattr(obj, name) if not isinstance(attr, MemberFunction): continue name = attr.name return_type = CreateTypeResolver(attr.return_type) arguments = [CreateTypeResolver(arg) for arg in attr.arguments] method = MethodWorkerWrapper( attr.name, CreateTemplatedMethodWorker(return_type, arguments, attr.function_)) self.methods.append(method) def match(self, class_type, method_name): if not re.match(self._class_regex, class_type.tag): return None templates = [class_type.template_argument(i) for i in range(len(template_types))] return [method.worker_class(templates) for method in self.methods if method.name == method_name and method.enabled] def CreateTypeResolver(type_desc): """Creates a callback which resolves to the appropriate type when invoked. This is a helper to allow specifying simple types as strings when writing function descriptions. For complex cases, a callback can be passed which will be invoked when template instantiation is known. Args: type_desc: A callback generating the type or a string description of the type to lookup. Supported types are classes in the template_classes array (e.g. T) which will be looked up when those templated classes are known, or globally visible type names (e.g. int, base::Foo). Types can be modified by appending a '*' or '&' to denote a pointer or reference. If a callback is used, the callback will be passed an array of the instantiated template types. Note: This does not parse complex types such as std::vector::iterator, to refer to types like these you must currently write a callback which constructs the appropriate type. """ if callable(type_desc): return type_desc if type_desc == 'void': return lambda T: None if type_desc[-1] == '&': inner_resolver = CreateTypeResolver(type_desc[:-1]) return lambda template_types: inner_resolver(template_types).reference() if type_desc[-1] == '*': inner_resolver = CreateTypeResolver(type_desc[:-1]) return lambda template_types: inner_resolver(template_types).pointer() try: template_index = template_types.index(type_desc) return operator.itemgetter(template_index) except ValueError: return lambda template_types: gdb.lookup_type(type_desc) def CreateTemplatedMethodWorker(return_callback, args_callbacks, method_callback): class TemplatedMethodWorker(gdb.xmethod.XMethodWorker): def __init__(self, templates): super(TemplatedMethodWorker, self).__init__() self._templates = templates def get_arg_types(self): return [cb(self._templates) for cb in args_callbacks] def get_result_type(self, obj): return return_callback(self._templates) def __call__(self, *args): return method_callback(*args) return TemplatedMethodWorker def DefineClass(obj): matcher = ClassMatcher(obj) gdb.xmethod.register_xmethod_matcher(None, matcher) return matcher return DefineClass