|
|
#!/usr/bin/env python
|
|
|
# -*- coding: utf-8 -*-
|
|
|
"""
|
|
|
Dynamic configuration system for pwndbg, using GDB's built-in Parameter
|
|
|
mechanism.
|
|
|
|
|
|
To create a new pwndbg configuration point, create an instance of
|
|
|
``pwndbg.config.Parameter``.
|
|
|
|
|
|
Parameters should be declared in the module in which they are primarily
|
|
|
used, or in this module for general-purpose parameters.
|
|
|
|
|
|
All pwndbg Parameter types are accessible via property access on this
|
|
|
module, for example:
|
|
|
|
|
|
>>> pwndbg.config.Parameter('example-value', 7, 'an example')
|
|
|
>>> int(pwndbg.config.example_value)
|
|
|
7
|
|
|
"""
|
|
|
from __future__ import absolute_import
|
|
|
from __future__ import division
|
|
|
from __future__ import print_function
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
|
import codecs
|
|
|
import collections
|
|
|
import re
|
|
|
import sys
|
|
|
import types
|
|
|
from functools import total_ordering
|
|
|
|
|
|
import gdb
|
|
|
import six
|
|
|
|
|
|
import pwndbg.decorators
|
|
|
|
|
|
TYPES = collections.OrderedDict()
|
|
|
|
|
|
# The value is a plain boolean.
|
|
|
# The Python boolean values, True and False are the only valid values.
|
|
|
TYPES[bool] = gdb.PARAM_BOOLEAN
|
|
|
|
|
|
# The value is an integer.
|
|
|
# This is like PARAM_INTEGER, except 0 is interpreted as itself.
|
|
|
for typ in six.integer_types:
|
|
|
TYPES[typ] = gdb.PARAM_ZINTEGER
|
|
|
|
|
|
# The value is a string.
|
|
|
# When the user modifies the string, any escape sequences,
|
|
|
# such as ‘\t’, ‘\f’, and octal escapes, are translated into
|
|
|
# corresponding characters and encoded into the current host charset.
|
|
|
for typ in six.string_types:
|
|
|
TYPES[typ] = gdb.PARAM_STRING
|
|
|
|
|
|
triggers = collections.defaultdict(lambda: [])
|
|
|
|
|
|
|
|
|
class Trigger(object):
|
|
|
def __init__(self, names):
|
|
|
if not isinstance(names, list):
|
|
|
names = [names]
|
|
|
names = list(map(lambda n: n.name if isinstance(n, Parameter) else n, names))
|
|
|
self.names = list(map(lambda n: n.replace('-', '_'), names))
|
|
|
|
|
|
def __call__(self, function):
|
|
|
global triggers
|
|
|
for name in self.names:
|
|
|
triggers[name].append(function)
|
|
|
return function
|
|
|
|
|
|
|
|
|
def get_param(value):
|
|
|
for k, v in TYPES.items():
|
|
|
if isinstance(value, k):
|
|
|
return v
|
|
|
|
|
|
|
|
|
def get_params(scope):
|
|
|
module_attributes = globals()['module'].__dict__.values()
|
|
|
return sorted(filter(lambda p: isinstance(p, Parameter) and p.scope == scope, module_attributes))
|
|
|
|
|
|
|
|
|
def value_to_gdb_native(value):
|
|
|
"""Translates Python value into native GDB syntax string."""
|
|
|
mapping = collections.OrderedDict()
|
|
|
mapping[bool] = lambda value: 'on' if value else 'off'
|
|
|
|
|
|
for k, v in mapping.items():
|
|
|
if isinstance(value, k):
|
|
|
return v(value)
|
|
|
return value
|
|
|
|
|
|
|
|
|
member_remap = {
|
|
|
'value': '_value',
|
|
|
'raw_value': 'value'
|
|
|
}
|
|
|
@total_ordering
|
|
|
class Parameter(gdb.Parameter):
|
|
|
"""
|
|
|
For python2, we can not store unicode type in self.value since the implementation limitation of gdb python.
|
|
|
We use self._value as the converted cache and set __getattribute__() and __setattr__() to remap variables.
|
|
|
|
|
|
Since GDB will set gdb.Parameter.value to user input and call get_set_string(),
|
|
|
we use self.raw_value to map back to gdb.Parameter.value
|
|
|
|
|
|
That is, we remap
|
|
|
* Parameter.value -> gdb.Parameter._value (if it is string type, always keep unicode)
|
|
|
All getter return this
|
|
|
* Parameter.raw_value -> gdb.Parameter.value
|
|
|
Only used in get_set_string()
|
|
|
"""
|
|
|
def __init__(self, name, default, docstring, scope='config'):
|
|
|
self.docstring = docstring.strip()
|
|
|
self.optname = name
|
|
|
self.name = name.replace('-', '_')
|
|
|
self.default = default
|
|
|
self.set_doc = 'Set ' + docstring
|
|
|
self.show_doc = docstring + ':'
|
|
|
super(Parameter, self).__init__(name,
|
|
|
gdb.COMMAND_SUPPORT,
|
|
|
get_param(default))
|
|
|
self.value = default
|
|
|
self.scope = scope
|
|
|
setattr(module, self.name, self)
|
|
|
|
|
|
@property
|
|
|
def native_value(self):
|
|
|
return value_to_gdb_native(self.value)
|
|
|
|
|
|
@property
|
|
|
def native_default(self):
|
|
|
return value_to_gdb_native(self.default)
|
|
|
|
|
|
@property
|
|
|
def is_changed(self):
|
|
|
return self.value != self.default
|
|
|
|
|
|
def __setattr__(self, name, value):
|
|
|
new_name = member_remap.get(name, name)
|
|
|
new_name = str(new_name) # Python2 only accept str type as key
|
|
|
return super(Parameter, self).__setattr__(new_name, value)
|
|
|
|
|
|
def __getattribute__(self, name):
|
|
|
new_name = member_remap.get(name, name)
|
|
|
new_name = str(new_name) # Python2 only accept str type as key
|
|
|
return super(Parameter, self).__getattribute__(new_name)
|
|
|
|
|
|
def get_set_string(self):
|
|
|
value = self.raw_value
|
|
|
|
|
|
# For string value, convert utf8 byte string to unicode.
|
|
|
if isinstance(value, six.binary_type):
|
|
|
value = codecs.decode(value, 'utf-8')
|
|
|
|
|
|
# Remove surrounded ' and " characters
|
|
|
if isinstance(value, six.string_types):
|
|
|
# The first character must be ' or " and ends with the same character.
|
|
|
# See PR #404 for more information
|
|
|
pattern = r"^(?P<quote>[\"'])(?P<content>.*?)(?P=quote)$"
|
|
|
|
|
|
value = re.sub(pattern, r"\g<content>", value)
|
|
|
|
|
|
# Write back to self.value
|
|
|
self.value = value
|
|
|
|
|
|
for trigger in triggers[self.name]:
|
|
|
trigger()
|
|
|
|
|
|
if not pwndbg.decorators.first_prompt:
|
|
|
# Remove the newline that gdb adds automatically
|
|
|
return '\b'
|
|
|
return 'Set %s to %r' % (self.docstring, self.value)
|
|
|
|
|
|
def get_show_string(self, svalue):
|
|
|
return 'Sets %s (currently: %r)' % (self.docstring, self.value)
|
|
|
|
|
|
def revert_default(self):
|
|
|
self.value = self.default
|
|
|
|
|
|
# TODO: use __getattribute__ to remapping all member function to self.value's member?
|
|
|
# Then, we can use param.member() just like param.value.member()
|
|
|
|
|
|
# The str type member function, used in color/__init__.py
|
|
|
def split(self, *args, **kargs):
|
|
|
return str(self).split(*args, **kargs)
|
|
|
|
|
|
# Casting
|
|
|
def __int__(self):
|
|
|
return int(self.value)
|
|
|
|
|
|
def __str__(self):
|
|
|
return str(self.value)
|
|
|
|
|
|
def __bool__(self):
|
|
|
return bool(self.value)
|
|
|
|
|
|
# Compare operators
|
|
|
# Ref: http://portingguide.readthedocs.io/en/latest/comparisons.html
|
|
|
# If other is Parameter, comparing by optname. Used in `sorted` in `config` command.
|
|
|
# Otherwise, compare `self.value` with `other`
|
|
|
def __eq__(self, other):
|
|
|
if isinstance(other, gdb.Parameter):
|
|
|
return self.optname == other.optname
|
|
|
else:
|
|
|
return self.value == other
|
|
|
|
|
|
def __lt__(self, other):
|
|
|
if isinstance(other, gdb.Parameter):
|
|
|
return self.optname < other.optname
|
|
|
else:
|
|
|
return self.value < other
|
|
|
|
|
|
# Operators
|
|
|
def __add__(self, other):
|
|
|
return self.value + other
|
|
|
|
|
|
def __radd__(self, other):
|
|
|
return other + self.value
|
|
|
|
|
|
def __sub__(self, other):
|
|
|
return self.value - other
|
|
|
|
|
|
def __rsub__(self, other):
|
|
|
return other - self.value
|
|
|
|
|
|
def __mul__(self, other):
|
|
|
return self.value * other
|
|
|
|
|
|
def __rmul__(self, other):
|
|
|
return other * self.value
|
|
|
|
|
|
def __div__(self, other):
|
|
|
return self.value / other
|
|
|
|
|
|
def __floordiv__(self, other):
|
|
|
return self.value // other
|
|
|
|
|
|
def __pow__(self, other):
|
|
|
return self.value ** other
|
|
|
|
|
|
def __mod__(self, other):
|
|
|
return self.value % other
|
|
|
|
|
|
def __len__(self):
|
|
|
return len(self.value)
|
|
|
|
|
|
# Python2 compatibility
|
|
|
__nonzero__ = __bool__
|
|
|
|
|
|
|
|
|
class ConfigModule(types.ModuleType):
|
|
|
def __init__(self, name, module):
|
|
|
super(ConfigModule, self).__init__(name)
|
|
|
self.__dict__.update(module.__dict__)
|
|
|
|
|
|
Parameter = Parameter
|
|
|
|
|
|
|
|
|
# To prevent garbage collection
|
|
|
tether = sys.modules[__name__]
|
|
|
|
|
|
# Create the module structure
|
|
|
module = ConfigModule(__name__, tether)
|
|
|
sys.modules[__name__] = module
|