class Metasm::SerialStruct

a class representing a simple structure serialized in a binary

Constants

BITS
DECODE
DEFVAL
ENCODE
ENUM
NAME
SIZEOF

Public Class Methods

bitfield(inttype, h) click to toggle source

define a bitfield: many fields inside a single word/byte/whatever usage: bitfield :word, 0 => :lala, 1 => nil, 4 => :lolo, 8 => :foo

=> a bitfield read using exe.decode_word, containing 3 subfields:
 :lala (bits 0...1), (discard 3 bits), :lolo (bits 4...8), and :foo (bits 8..-1)
fields default to 0
# File metasm/exe_format/serialstruct.rb, line 98
def bitfield(inttype, h)
        # XXX encode/decode very not threadsafe ! this is a Georges Foreman Guarantee.
        # could use a me.instance_variable..

        # decode the value in a temp var
        d = lambda { |exe, me| @bitfield_val = exe.send("decode_#{inttype}") }
        # reset a temp var
        e = lambda { |exe, me, val| @bitfield_val = 0 ; nil }
        new_field(nil, d, e, 0, nil)

        h = h.sort
        h.length.times { |i|
                # yay closure !
                # get field parameters
                next if not name = h[i][1]
                off = h[i][0]
                nxt = h[i+1]
                mask = (nxt ? (1 << (nxt[0]-off))-1 : -1)
                # read the field value from the temp var
                d = lambda { |exe, me| (@bitfield_val >> off) & mask }
                # update the temp var with the field value, return nil
                e = lambda { |exe, me, val| @bitfield_val |= (val & mask) << off ; nil }
                new_field(name, d, e, 0, 0)
        }

        # free the temp var
        d = lambda { |exe, me| @bitfield_val = nil }
        # return encoded temp var
        e = lambda { |exe, me, val|
                val = @bitfield_val
                @bitfield_val = nil
                exe.send("encode_#{inttype}", val)
        }
        s = lambda { |exe, me| exe.send("sizeof_#{inttype}") }
        new_field(nil, d, e, s, nil)
end
decode(*a) click to toggle source

shortcut to create a new instance and decode it

# File metasm/exe_format/serialstruct.rb, line 258
def self.decode(*a)
        s = new
        s.decode(*a)
        s
end
decode_hook(before=nil, &b) click to toggle source

inject a hook to be run during the decoding process

# File metasm/exe_format/serialstruct.rb, line 136
def decode_hook(before=nil, &b)
        @@fields[self] ||= []
        idx = (before ? @@fields[self].index(fld_get(before)) : -1)
        @@fields[self].insert(idx, [nil, b])
end
fld_bits(name, bits=nil, &b) click to toggle source
# File metasm/exe_format/serialstruct.rb, line 91
def fld_bits(name, bits=nil, &b) fld_get(name)[BITS] = bits||b end
fld_default(name, default=nil, &b) click to toggle source

change the default for a field

# File metasm/exe_format/serialstruct.rb, line 86
def fld_default(name, default=nil, &b)
        default ||= b
        fld_get(name)[DEFVAL] = default
end
fld_enum(name, enum=nil, &b) click to toggle source
# File metasm/exe_format/serialstruct.rb, line 90
def fld_enum(name, enum=nil, &b) fld_get(name)[ENUM] = enum||b end
fld_get(name) click to toggle source

field access

# File metasm/exe_format/serialstruct.rb, line 80
def fld_get(name)
        name = "@#{name}".to_sym
        @@fields[self].find { |f| f[NAME] == name }
end
mem(name, len, defval='') click to toggle source

a fixed-size memory chunk

# File metasm/exe_format/serialstruct.rb, line 58
def mem(name, len, defval='')
        new_field(name, lambda { |exe, me| exe.curencoded.read(len) }, lambda { |exe, me, val| d = val[0, len].ljust(len, 0.chr) ; d.force_encoding('BINARY') if d.respond_to?(:force_encoding) ; d }, lambda { |exe, me| len }, defval)
end
new(*a) click to toggle source

set value of fields from argument list, runs int_to_hash if needed

# File metasm/exe_format/serialstruct.rb, line 147
def initialize(*a)
        if not a.empty?
                a.zip(struct_fields.reject { |f| not f[NAME] }).each { |v, f|
                        v = int_to_hash(v, f[ENUM]) if f[ENUM]
                        v = bits_to_hash(v, f[BITS]) if f[BITS]
                        instance_variable_set f[NAME], v
                }
        end
end
new_field(name, decode, encode, sizeof, defval, enum=nil, bits=nil) click to toggle source

defines a new field adds an accessor

# File metasm/exe_format/serialstruct.rb, line 24
def new_field(name, decode, encode, sizeof, defval, enum=nil, bits=nil)
        if name
                attr_accessor name
                name = "@#{name}".to_sym
        end
        (@@fields[self] ||= []) << [name, decode, encode, sizeof, defval, enum, bits]
end
new_int_field(*types) click to toggle source

creates a field constructor for a simple integer relies on exe implementing (en,de)code_#{type}

# File metasm/exe_format/serialstruct.rb, line 34
def new_int_field(*types)
        recv = class << self ; self ; end
        types.each { |type|
                recv.send(:define_method, type) { |name, *args|
                        new_field(name, "decode_#{type}".to_sym, "encode_#{type}".to_sym, "sizeof_#{type}".to_sym, args[0] || 0, args[1])
                }

                # shortcut to define multiple fields of this type with default values
                recv.send(:define_method, "#{type}s") { |*names|
                        names.each { |name| send type, name }
                }
        }
end
offsetof(exe, fld) click to toggle source
# File metasm/exe_format/serialstruct.rb, line 268
def self.offsetof(exe, fld)
        new.offsetof(exe, fld)
end
sizeof(exe) click to toggle source
# File metasm/exe_format/serialstruct.rb, line 264
def self.sizeof(exe)
        new.sizeof(exe)
end
str(name, len, defval='') click to toggle source

a fixed-size string, 0-padded

# File metasm/exe_format/serialstruct.rb, line 62
def str(name, len, defval='')
        d = lambda { |exe, me| v = exe.curencoded.read(len) ; v = v[0, v.index(\0)] if v.index(\0) ; v }
        e = lambda { |exe, me, val| val[0, len].ljust(len, 0.chr) }
        s = lambda { |exe, me| len }
        new_field(name, d, e, s, defval)
end
strz(name, defval='') click to toggle source

0-terminated string

# File metasm/exe_format/serialstruct.rb, line 69
def strz(name, defval='')
        d = lambda { |exe, me|
                ed = exe.curencoded
                ed.read(ed.data.index(\0, ed.ptr)-ed.ptr+1).chop
        }
        e = lambda { |exe, me, val| val + 0.chr }
        s = lambda { |exe, val| val.length + 1 }
        new_field(name, d, e, s, defval)
end
virtual(*a) click to toggle source

virtual field, handled explicitly in a custom encode/decode

# File metasm/exe_format/serialstruct.rb, line 51
def virtual(*a)
        a.each { |f|
                new_field(f, nil, nil, nil, nil)
        }
end

Public Instance Methods

decode(exe, *args) click to toggle source

decodes the fields from the exe

# File metasm/exe_format/serialstruct.rb, line 168
def decode(exe, *args)
        struct_fields(exe).each { |f|
                case d = f[DECODE]
                when Symbol; val = exe.send(d, *args)
                when Array; val = exe.send(*d)
                when Proc; val = d[exe, self]
                when nil; next
                end
                next if not f[NAME]
                if h = f[ENUM]; h = h[exe, self] if h.kind_of? Proc; val = int_to_hash( val, h) end
                if h = f[BITS]; h = h[exe, self] if h.kind_of? Proc; val = bits_to_hash(val, h) end
                instance_variable_set(f[NAME], val)
        }
end
dump(e, a) click to toggle source
# File metasm/exe_format/serialstruct.rb, line 272
def dump(e, a)
        case e
        when Integer; e >= 0x100 ? '0x%X'%e : e
        when String; e.length > 64 ? e[0, 62].inspect+'...' : e.inspect
        when Array; '[' + e.map { |i| dump(i, a) }.join(', ') + ']'
        when SerialStruct; a.include?(e) ? '...' : e.to_s(a)
        else e.inspect
        end
end
encode(exe, *a) click to toggle source

sets default values, then encodes the fields, returns an EData

# File metasm/exe_format/serialstruct.rb, line 201
def encode(exe, *a)
        set_default_values(exe, *a)

        ed = EncodedData.new
        struct_fields(exe).each { |f|
                if not f[NAME]
                        ed << f[ENCODE][exe, self, nil] if f[ENCODE]
                        next
                end
                val = instance_variable_get(f[NAME])
                if h = f[ENUM]; h = h[exe, self] if h.kind_of? Proc; val = int_from_hash( val, h) end
                if h = f[BITS]; h = h[exe, self] if h.kind_of? Proc; val = bits_from_hash(val, h) end
                case e = f[ENCODE]
                when Symbol; val = exe.send(e, val)
                when Array; val = exe.send(e, *val)
                when Proc; val = e[exe, self, val]
                when nil; next
                end
                ed << val
        }
        ed
end
offsetof(exe, fld) click to toggle source

offset (in bytes) of the structure member for bitfields, return the byte offset of the whole bitfield

# File metasm/exe_format/serialstruct.rb, line 239
def offsetof(exe, fld)
        fld2 = fld
        fld2 = "@#{fld}".to_sym if fld.to_s[0] != @
        off = 0
        struct_fields(exe).each { |f|
                return off if f[NAME] == fld or f[NAME] == fld2

                case sz = f[SIZEOF]
                when Proc; sz = sz[exe, self]
                when Symbol; sz = exe.send(sz)
                when Array; sz = exe.send(*sz)
                when nil; sz = 0
                end
                off += sz
        }
        raise 'unknown field'
end
set_default_values(exe) click to toggle source

initialize uninitialized fields

# File metasm/exe_format/serialstruct.rb, line 184
def set_default_values(exe)
        struct_fields(exe).each { |f|
                if not f[NAME]
                        f[DEFVAL][exe, self] if f[DEFVAL]
                        next
                end
                # check existence to avoid a "warning: ivar @bla not initialized"
                next if instance_variables.map { |ivn| ivn.to_sym }.include?(f[NAME]) and instance_variable_get(f[NAME])
                val = f[DEFVAL]
                val = val[exe, self] if val.kind_of? Proc
                if val.kind_of? Integer and h = f[ENUM]; h = h[exe, self] if h.kind_of? Proc; val = int_to_hash( val, h) end
                if val.kind_of? Integer and h = f[BITS]; h = h[exe, self] if h.kind_of? Proc; val = bits_to_hash(val, h) end
                instance_variable_set(f[NAME], val)
        }
end
sizeof(exe) click to toggle source

size of the structure = fields.sum { size of field }

# File metasm/exe_format/serialstruct.rb, line 225
def sizeof(exe)
        struct_fields(exe).inject(0) { |off, f|
                case sz = f[SIZEOF]
                when Proc; sz = sz[exe, self]
                when Symbol; sz = exe.send(sz)
                when Array; sz = exe.send(*sz)
                when nil; sz = 0
                end
                off + sz
        }
end
struct_fields(exe=nil) click to toggle source

returns this classes' field array uses struct_specialized if defined (a method that returns another

SerialStruct class whose fields should be used)
# File metasm/exe_format/serialstruct.rb, line 160
def struct_fields(exe=nil)
        klass = self.class
        klass = struct_specialized(exe) if respond_to? :struct_specialized
        raise "SerialStruct: no fields for #{klass}" if $DEBUG and not @@fields[klass]
        @@fields[klass]
end
to_s(a=[]) click to toggle source

displays the struct content, ordered by fields

# File metasm/exe_format/serialstruct.rb, line 283
def to_s(a=[])
        ivs = instance_variables.map { |iv| iv.to_sym }
        ivs = (struct_fields.to_a.map { |f| f[NAME] }.compact & ivs) | ivs
        "<#{self.class} " + ivs.map { |iv| "#{iv}=#{dump(instance_variable_get(iv), a+[self])}" }.join(' ') + ">"
end