From d19f4c1dbdd2f1f451d03551abb0e5ebf4d455be Mon Sep 17 00:00:00 2001 From: Florent Monjalet Date: Sun, 29 Nov 2015 22:03:11 +0100 Subject: MemStruct: big refactor in process Doc is currently incoherent, impl will also be completed --- miasm2/analysis/mem.py | 480 ++++++++++++++++++++++++++----------------------- 1 file changed, 258 insertions(+), 222 deletions(-) (limited to 'miasm2') diff --git a/miasm2/analysis/mem.py b/miasm2/analysis/mem.py index 2c5e488c..2b2b3a72 100644 --- a/miasm2/analysis/mem.py +++ b/miasm2/analysis/mem.py @@ -72,7 +72,7 @@ of PinnedType with a custom implementation: - PinnedSizedArray: a sized PinnedArray, can be automatically allocated in memory and allows more operations than PinnedArray - pin: a function that dynamically generates a PinnedStruct subclass from a - Type. This class has only one field named "value". + Type. This class has only one field named "val". A PinnedType do not always have a static size (cls.sizeof()) nor a dynamic size (self.get_size()). @@ -171,14 +171,14 @@ def set_str_utf16(vm, addr, s): def pin(field): """Generate a PinnedStruct subclass from a field. The field's value can - be accessed through self.value or self.deref_value if field is a Ptr. + be accessed through self.val or self.deref_val if field is a Ptr. @field: a Type instance. """ if field in DYN_MEM_STRUCT_CACHE: return DYN_MEM_STRUCT_CACHE[field] - fields = [("value", field)] + fields = [("val", field)] # Build a type to contain the field type mem_type = type("Pinned%r" % field, (PinnedStruct,), {'fields': fields}) DYN_MEM_STRUCT_CACHE[field] = mem_type @@ -316,7 +316,11 @@ class Num(RawStruct): class Ptr(Num): """Special case of number of which value indicates the address of a - PinnedType. Provides deref_ as well as when used, to set and + PinnedType. + + FIXME: DOC + + Provides deref_ as well as when used, to set and get the pointed PinnedType. """ @@ -360,7 +364,7 @@ class Ptr(Num): else: raise ValueError("Unsupported usecase for PinnedSelf, sorry") if isinstance(self._dst_type, Type): - self._dst_type = pin(self._dst_type) + self._dst_type = self._dst_type.pinned @property def dst_type(self): @@ -368,11 +372,28 @@ class Ptr(Num): self._fix_dst_type() return self._dst_type + def set(self, vm, addr, val): + if isinstance(val, PinnedType) and isinstance(val.get_type(), Ptr): + self.set_val(vm, addr, val.val) + else: + super(Ptr, self).set(vm, addr, val) + + def get(self, vm, addr): + return self.pinned(vm, addr) + + def get_val(self, vm, addr): + return super(Ptr, self).get(vm, addr) + + def set_val(self, vm, addr, val): + return super(Ptr, self).set(vm, addr, val) + def deref_get(self, vm, addr): """Deserializes the data in @vm (VmMngr) at @addr to self.dst_type. Equivalent to a pointer dereference rvalue in C. """ - return self.dst_type(vm, addr, *self._type_args, **self._type_kwargs) + dst_addr = self.get_val(vm, addr) + return self.dst_type(vm, dst_addr, + *self._type_args, **self._type_kwargs) def deref_set(self, vm, addr, val): """Serializes the @val PinnedType subclass instance in @vm (VmMngr) at @@ -384,7 +405,8 @@ class Ptr(Num): self._dst_type.__name__, val.__class__.__name__) # Actual job - vm.set_mem(addr, str(val)) + dst_addr = self.get_val(vm, addr) + vm.set_mem(dst_addr, str(val)) def _get_pinned_base_class(self): return PinnedPtr @@ -403,7 +425,7 @@ class Ptr(Num): self._type_args)) -class Inline(Type): +class Struct(Type): """Field used to inline a PinnedType in another PinnedType. Equivalent to having a struct field in a C struct. @@ -424,34 +446,145 @@ class Inline(Type): TODO: make the Inline implicit when setting a field to be a PinnedStruct """ - def __init__(self, inlined_type, *type_args, **type_kwargs): - if not issubclass(inlined_type, PinnedStruct): - raise ValueError("inlined type if Inline must be a PinnedStruct") - self._il_type = inlined_type - self._type_args = type_args - self._type_kwargs = type_kwargs + def __init__(self, name, fields): + self.name = name + # fields is immutable + self._fields = tuple(fields) + self._gen_fields() + + def _gen_fields(self): + """Precompute useful metadata on self.fields.""" + self._fields_desc = {} + offset = 0 + for name, field in self._fields: + # For reflexion + field._set_self_type(self) + self._gen_field(name, field, offset) + offset += field.size() + self._size = offset + + def _gen_field(self, name, field, offset): + """Generate only one field + + @name: (str) the name of the field + @field: (Type instance) the field type + @offset: (int) the offset of the field in the structure + """ + self._fields_desc[name] = {"field": field, "offset": offset} + + @property + def fields(self): + return self._fields def set(self, vm, addr, val): raw = str(val) vm.set_mem(addr, raw) def get(self, vm, addr): - return self._il_type(vm, addr) + return self.pinned(vm, addr) + + def get_field(self, vm, addr, name): + """get a field value by name. + + useless most of the time since fields are accessible via self.. + """ + if name not in self._fields_desc: + raise ValueError("'%s' type has no field '%s'" + % (self, name)) + field = self.get_field_type(name) + offset = self.get_offset(name) + return field.get(vm, addr + offset) + + def set_field(self, vm, addr, name, val): + """set a field value by name. @val is the python value corresponding to + this field type. + + useless most of the time since fields are accessible via self.. + """ + if name not in self._fields_desc: + raise AttributeError("'%s' object has no attribute '%s'" + % (self.__class__.__name__, name)) + field = self.get_field_type(name) + offset = self.get_offset(name) + field.set(vm, addr + offset, val) def size(self): - return self._il_type.sizeof() + # Child classes can set self._size if their size is not the sum of + # their fields + return sum(a["field"].size() for a in self._fields_desc.itervalues()) + + def get_offset(self, field_name): + """ + @field_name: (str, optional) the name of the field to get the + offset of + """ + if field_name not in self._fields_desc: + raise ValueError("This structure has no %s field" % field_name) + return self._fields_desc[field_name]['offset'] + + def get_field_type(self, name): + """return the type subclass instance describing field @name.""" + # TODO: move it to Struct + return self._fields_desc[name]['field'] + + #def _build_pinned_type(self): + # mem_type = type("PinnedStruct%s" % self.name, + # (PinnedStruct,), + # {'_type': self}) + # return mem_type + + def _get_pinned_base_class(self): + return PinnedStruct def __repr__(self): - return "%s(%r)" % (self.__class__.__name__, self._il_type) + return "Struct%s" % self.name def __eq__(self, other): return self.__class__ == other.__class__ and \ - self._il_type == other._il_type and \ - self._type_args == other._type_args and \ - self._type_kwargs == other._type_kwargs + self.fields == other.fields and \ + self.name == other.name def __hash__(self): - return hash((self.__class__, self._il_type, self._type_args)) + # Only hash name, not fields, because if a field is a Ptr to this + # Struct type, an infinite loop occurs + return hash((self.__class__, self.name)) + + +class Union(Struct): + """Allows to put multiple fields at the same offset in a PinnedStruct, similar + to unions in C. The Union will have the size of the largest of its fields. + + Example: + + class Example(PinnedStruct): + fields = [("uni", Union([ + ("f1", Num(". """ - if name not in self._attrs: - raise attributeerror("'%s' object has no attribute '%s'" - % (self.__class__.__name__, name)) - field = self._attrs[name]["field"] - offset = self._attrs[name]["offset"] - return field.get(self._vm, self.get_addr() + offset) + return self._type.get_field(self._vm, self.get_addr(), name) def set_field(self, name, val): """set a field value by name. @val is the python value corresponding to @@ -941,37 +1001,7 @@ class PinnedStruct(PinnedType): useless most of the time since fields are accessible via self.. """ - if name not in self._attrs: - raise attributeerror("'%s' object has no attribute '%s'" - % (self.__class__.__name__, name)) - field = self._attrs[name]["field"] - offset = self._attrs[name]["offset"] - field.set(self._vm, self.get_addr() + offset, val) - - def deref_field(self, name): - """get the memstruct pointed by field. - - useless most of the time since fields are accessible via - self.deref_. - """ - addr = self.get_field(name) - field = self._attrs[name]["field"] - assert isinstance(field, Ptr),\ - "programming error: field should be a Ptr" - return field.deref_get(self._vm, addr) - - def set_deref_field(self, name, val): - """set the memstruct pointed by field. @val should be of the - type of the pointed memstruct. the field must be a Ptr. - - useless most of the time since fields are accessible via - self.deref_. - """ - addr = self.get_field(name) - field = self._attrs[name]["field"] - assert isinstance(field, Ptr),\ - "programming error: field should be a Ptr" - field.deref_set(self._vm, addr, val) + return self._type.set_field(self._vm, self.get_addr(), name, val) def cast_field(self, field, other_type, *type_args, **type_kwargs): """ @@ -981,7 +1011,7 @@ class PinnedStruct(PinnedType): *type_args, **type_kwargs) - # Field generation methods, voluntarily public to be able to regen fields + # Field generation methods, voluntarily public to be able to gen fields # after class definition @classmethod @@ -1006,56 +1036,59 @@ class PinnedStruct(PinnedType): class B(PinnedStruct): fields = [("a", Ptr("I", A))] - A.fields = [("b", Ptr("I", B))] - a.gen_field() + A.gen_fields([("b", Ptr("I", B))]) """ - if fields is None: - fields = cls.fields - cls._attrs = {} - offset = 0 - for name, field in cls.fields: - # For reflexion - field._set_self_type(cls) - cls.gen_field(name, field, offset) - offset += field.size() - cls._size = offset + if fields is not None: + if cls.fields is not None: + raise ValueError("Cannot regen fields of a class. Setting " + "cls.fields at class definition and calling " + "gen_fields are mutually exclusive.") + cls.fields = fields + + if cls._type is None: + if cls.fields is None: + raise ValueError("Cannot create a PinnedStruct subclass without" + " a cls._type or a cls.fields") + cls._type = cls._gen_type(cls.fields) + + if cls._type in DYN_MEM_STRUCT_CACHE: + # FIXME: Maybe a warning would be better? + raise RuntimeError("Another PinnedType has the same type as this " + "one. Use it instead.") + + # Register this class so that another one will not be created when + # calling cls._type.pinned + DYN_MEM_STRUCT_CACHE[cls._type] = cls + + cls._gen_attributes() @classmethod - def gen_field(cls, name, field, offset): - """Generate only one field - - @name: (str) the name of the field - @field: (Type instance) the field type - @offset: (int) the offset of the field in the structure - """ - cls._gen_simple_attr(name, field, offset) - if isinstance(field, Union): - cls._gen_union_attr(field, offset) + def _gen_attributes(cls): + # Generate self. getter and setters + for name, field in cls._type.fields: + setattr(cls, name, property( + lambda self, name=name: self.get_field(name), + lambda self, val, name=name: self.set_field(name, val) + )) @classmethod - def _gen_simple_attr(cls, name, field, offset): - cls._attrs[name] = {"field": field, "offset": offset} - - # Generate self. getter and setter - setattr(cls, name, property( - lambda self: self.get_field(name), - lambda self, val: self.set_field(name, val) - )) - - # Generate self.deref_ getter and setter if this field is a - # Ptr - if isinstance(field, Ptr): - setattr(cls, "deref_%s" % name, property( - lambda self: self.deref_field(name), - lambda self, val: self.set_deref_field(name, val) - )) + def _gen_type(cls, fields): + return Struct(cls.__name__, fields) + + def __repr__(self): + out = [] + for name, field in self._type.fields: + val_repr = repr(self.get_field(name)) + if '\n' in val_repr: + val_repr = '\n' + indent(val_repr, 4) + out.append("%s: %r = %s" % (name, field, val_repr)) + return '%r:\n' % self.__class__ + indent('\n'.join(out), 2) + +class PinnedUnion(PinnedStruct): @classmethod - def _gen_union_attr(cls, union_field, offset): - if not isinstance(union_field, Union): - raise ValueError("field should be an Union instance") - for name, field in union_field.field_list: - cls.gen_field(name, field, offset) + def _gen_type(cls, fields): + return Union(fields) class PinnedSelf(PinnedStruct): @@ -1069,19 +1102,30 @@ class PinnedSelf(PinnedStruct): ("data", Ptr("