From 198538a250dfd1f4057b06d5c982f56fa819f926 Mon Sep 17 00:00:00 2001 From: ebenders Date: Wed, 7 Sep 2011 11:12:47 +0300 Subject: [PATCH 1/1] something basic --- .hgignore | 3 + binfiles/z.elf | Bin 0 -> 12333 bytes elftools/__init__.py | 0 elftools/construct/README | 5 + elftools/construct/__init__.py | 81 ++ elftools/construct/adapters.py | 482 +++++++++++ elftools/construct/core.py | 1217 +++++++++++++++++++++++++++ elftools/construct/debug.py | 160 ++++ elftools/construct/lib/__init__.py | 10 + elftools/construct/lib/binary.py | 61 ++ elftools/construct/lib/bitstream.py | 80 ++ elftools/construct/lib/container.py | 258 ++++++ elftools/construct/lib/hex.py | 34 + elftools/construct/lib/utils.py | 22 + elftools/construct/macros.py | 514 +++++++++++ elftools/construct/text.py | 286 +++++++ elftools/elf/__init__.py | 0 elftools/elf/enums.py | 0 elftools/elf/structs.py | 37 + z.py | 2 + 20 files changed, 3252 insertions(+) create mode 100644 .hgignore create mode 100644 binfiles/z.elf create mode 100644 elftools/__init__.py create mode 100644 elftools/construct/README create mode 100644 elftools/construct/__init__.py create mode 100644 elftools/construct/adapters.py create mode 100644 elftools/construct/core.py create mode 100644 elftools/construct/debug.py create mode 100644 elftools/construct/lib/__init__.py create mode 100644 elftools/construct/lib/binary.py create mode 100644 elftools/construct/lib/bitstream.py create mode 100644 elftools/construct/lib/container.py create mode 100644 elftools/construct/lib/hex.py create mode 100644 elftools/construct/lib/utils.py create mode 100644 elftools/construct/macros.py create mode 100644 elftools/construct/text.py create mode 100644 elftools/elf/__init__.py create mode 100644 elftools/elf/enums.py create mode 100644 elftools/elf/structs.py create mode 100644 z.py diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..bb6d589 --- /dev/null +++ b/.hgignore @@ -0,0 +1,3 @@ +syntax: glob + +*.pyc diff --git a/binfiles/z.elf b/binfiles/z.elf new file mode 100644 index 0000000000000000000000000000000000000000..ccfa6ae0f448b64e14d34cf719c37828534e1095 GIT binary patch literal 12333 zcmeHNeQ;Y>mA~&j%a&usmScyeNg!&g(6o)8?KBMzP1EN%PNYGnmIWLqFh z9(|-qDbT@fLAarA`P>}_I?L=#VTYOQ@DD80#o2UeyIp2j=yXe$flQ!a=*OgmLKtYx z@7#Bf^z**t-v3M=+#YGQpJ1F=Y>llD%rMRMMf3lS2qh zg$PNWaFn&KQ?j5(WrAso@?)K%j3btCmcm&I$Mg~97*nnv)s3EURo=L6fg|1`B5s#) zm69%nLoXDQln$tR1X?W;gLi0HazZd|QT%&=QxGNj^Mw68tn4*wmt|EyrivB)ne^n2 z?fsc#UnZR`9q&6nw4-mwcC(N(x5;`3VV-pN-!dT`;dUf{ssD(MmBvQtAq7pW(n;2f zhsFlsK6&W7kDj~jQ_+8V{HuK@fA#vw_UJPoqB1GJ(oIN{%>96U%YksA2HNW(Thl-; zYJ|B`<>0pap%6uk^gWd3pgnU(9-1VUnEb1&kq zO%_tb&rlh^JiD^8LJ10kFGtJ&dq<>vE;9eK^M}TxPb`IemWVuYdO(sVUTU$#^0xu_ z&Eg{2iY)lP(h(Ao`<9CO%J-zHMR%BplwTMl#d~g8f}SYd^o;?$GLeO~|4y*JEySF@ zU_?&dx)Ry>d|`9frhCYb$jLogr0n|)lvaBGOKL5w{TM-ZKA%7L9bW-Sjm#Z+>de&C zl-afEUV@2<$ovht_7DmvzffHN4HA)W!EX=2P_%sJso|BMQvIJgv$#mUl|S^yNV#-A zvM_qS{GSWHwJ7#vi(d8xkXZ1wA~r_;R}(IeLwJrMC{}Z z|3jpl44;i5b2(NnhR;XJw};U>FAc@6dSkf!X0-fzeHOMDBaJxN-3R z$*)-XT&(=c-tzB;S9*UEnLndNcD|VZRb=7zFq%3O4#pOe;dL>jLa~KncuQ>I?(hJb zVTe7fv^P2?0zV@27gmawev78Hu1$N<81pas(N7BoWt3i3E$H;+@(P?({W^=RT}bz$>x-aa&<@$xL^Ju2^K11e+5uw-hr;xAx3NrCqG6i(?D5%Kz)D>K2j8*=|7LdH=AiTa+zU$r$HGMLQH-r|4Qmnb28*!m`>|^B#lsd0#^NY6_P)L_DhO zvwTtUewEMob`Ov3imcC^(nq^VUQ>GK6+ff$mH6*Y_`K>h^hy&ydZ3sq%-y+P>>n9< zcWBGl#Mo#kMn}D?Lfg%2&F!Hh(}_gicKh1C$$UJUm8xEUq>>N_5yGPja^exB`tZo}9PLA=Gr36+A$H)=1sNW|b2B(~08A80 z_Eb8X7U^0UL`6u1DBw{|0we-AiK3?2UpP@nrsfL$I91y?Wv4Pz{qn$Rj*0$KA>Uuf zC;I2&i4Vl5Q-%IL6Vce-{%JaW_H8o<2hG8L_&_J<(C}CY$Ik(CKxp@TOq!+f?$@LcYw@=s zdk!hzt%$Ts@1wZg@V8n>;40<46n_DaE^NfgmAlM@I0cq_`x4cQA16iaNyM72QD=D- zAd;PE;0Oa9QqdpE82>7{W0E^6S{xUB{<|UY`9F%d)p4@zKS)uQ3@6vAGq%`@e$mC# z$W#9P5=m+*9$nv5{+)(Q)!g_lrJub~N@P2+S?%p8PPG=oDQVrGsQHNJeA?Amw)d29b9CD->(p0Wx)i!w9Wg1Y5hc zHJahy;NR@;p~0ecYnwZJI*iT@os<)7h0(C+=-3SrMvY41C2w((=$Y-p=zMp_^&Qck z8v{Bf`qp=fD9uA$l;2g9sofZGw66?Ad&q*-szKTCRSKaZRN)5f__|=7%4@B<(dIQ- zvDL)ww^d57E!z}Tcce4qzT3yDhj^8^nmQWAUv&Z9)}c*6whA)8(-Nf8!JY@423f6X z(H^R7=VfS}uC>~g0pfx_*<9V#0&fiL3P6LVVqKo5HSG_btI>V(!k2om;ar0$if*?s z*+2zO={!RjOF}b(=I+aO$HGQusC4Z35loVrc7-(XcEoiC1WkK*yD88um@epMtzM00 z02$hKe#d*h=D+D79oG`t!xH$uMnE|oI(Ly+kJNV7bB3$QFQ_rEY3q$UUNgGCXyl$Xy8T5Xx6A0BGI9ru&Rs{o_Qb>b74S;B z+l!d?MrelPL3O7=8FZ&Xfh%*3!&a_En&C)3gok}`77FLSm*hNAlvR1&YmrPYI~^)z z3+d@>DjA}gxiFK<7i&dtc@1%}RElSntXN3Q#PdQLB`sQWM&9cFAfBH-Ci;uBbN#8w zR5qE)7o61<@`SVC7Sea6?4n3zkIlJPSGkh9y3)2uV#o9O_zCC!RS*&ommC({Wylp; zwV~7hV6ZXpJKc)6TV+BPN}}=)HtI)Q9-FaSQT53dJm&$d7+?HK2110;@ef~gNkZmtdge^^iosU;t;{5 z(h^J|>JYh4qNJtx5k>2WSvWCUj86))P|Q1YhEo`5sr;NUv$m2a%c2CF8}oFjF)3R6ahNvS*UOGAGPLF^8dTCLQwr1Ts-(d^Vkc!5oS~WXiIg zawZGVPUL21Q(4Tx*ms);4~FlB-5uJiR%zJ!1b) z=@vLT4@)B0KG~pvjf)Ii&Ova$#TkY*blqM89V>8d^ z>^X%GIdJK>+kYjNyJ%`l#xEL{%vw}1B^VW{nJN{13c*ZgRFCIMK>s+8gjX-sF z2e!{Ld4K`K-&Xlx=vNKm)n}gR*MX(_bN}&o@qogI*^r`)WASMu-1vc3OJ)ryJhiVo zaR0OHX(R}*n%nh10!PE!US9n7J$U-kNnwj>xW9z|A#~mL=`$yVA%*wa^YTB2E>)HN z=X)Xkf5RhGI0ECCvR}VIp4&cuuRhx)VHKRi)tB><2haC%%L*T2prUM_>FXZ6KX_DX z`jvyd3de+T%>Mxil5zb*3cpd|xt^@=#Z%>}{sXL_DC5;Is^qHXlo%5BG{Il44DYNU zorD*^QQh--#Z&+c zc>o?(OfT(UN7UK=&hA1|aw+P5##`CVD`-nNKWS6m{Qr1_wcya#d}<87;F zype~E^l7@#@NXpgqmw((`Aie_MomEqqIYYoQvV~|U`K1=Fx)U2I<0EI^F`F~C1 z=OEYg`uE}j8Pa=2ugCv(YN!Tsxo^?y-^b0+58+PO`+f~Tz6R|XQuX3B$5L|dyhQto zZj{5{aCqMzgFHmOdcu(=@*L!AaGB?w-%3*MDFp1NA@9bI=RLOjWypgK>UE0r_4OjC z#t)bHn3S&-^k+$T_;1Lm9lZCx&&%>(UD@a<=jTd3;E})BME^BazW1K`cT(P9y`cLq z+|?7I>n|h|@l3|`r&y857YoG_{xg}th3m-p!NYbeIyR2~%G&nc8t%Y|D=V3^>Dy{N zW6O&&J6<|2a0xb-NflE`bI0IyI|XIf>Ev;6I|hYw*On~J&Yoa9qqpppX7(K(zIoJU zj?8enYDM14Nh|c}!F`*&KX!1>aLhiqZ{OJHxII3+CpJpHQ;pRfvvZg163Fi*!rpt^ zEyFiQM+DvDsf%duM`0zFpOHO(CAJS7vk#}H(*;~_j%4D60xt8MFUfU(Ew(K8e~UeL v^R``MKUv7xGx2OPLl@$*1Gq8d|CQ-;HPr-}xOil*WerpEK2m=Ac2xcZDyqhn literal 0 HcmV?d00001 diff --git a/elftools/__init__.py b/elftools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/elftools/construct/README b/elftools/construct/README new file mode 100644 index 0000000..88ba6e9 --- /dev/null +++ b/elftools/construct/README @@ -0,0 +1,5 @@ +'construct' is the actual library (version 2, downloaded on 15.09.2010) +Taken from http://construct.wikispaces.com/ +The code for version 2.00 has been released by the author into the public domain + + diff --git a/elftools/construct/__init__.py b/elftools/construct/__init__.py new file mode 100644 index 0000000..0b97feb --- /dev/null +++ b/elftools/construct/__init__.py @@ -0,0 +1,81 @@ +""" +Construct 2.00 -- parsing made even more fun (and faster) + +Homepage: +http://construct.wikispaces.com + +Typical usage: +>>> from construct import * + +Example: +>>> from construct import * +>>> +>>> s = Struct("foo", +... UBInt8("a"), +... UBInt16("b"), +... ) +>>> +>>> s.parse("\x01\x02\x03") +Container(a = 1, b = 515) +>>> print s.parse("\x01\x02\x03") +Container: + a = 1 + b = 515 +>>> s.build(Container(a = 1, b = 0x0203)) +"\x01\x02\x03" +""" +from core import * +from adapters import * +from macros import * +from debug import Probe, Debugger + + +#=============================================================================== +# meta data +#=============================================================================== +__author__ = "tomer filiba (tomerfiliba [at] gmail.com)" +__version__ = "2.00" + +#=============================================================================== +# shorthands +#=============================================================================== +Bits = BitField +Byte = UBInt8 +Bytes = Field +Const = ConstAdapter +Tunnel = TunnelAdapter +Embed = Embedded + +#=============================================================================== +# backward compatibility with RC1 +#=============================================================================== +MetaField = Field +MetaBytes = Field +GreedyRepeater = GreedyRange +OptionalGreedyRepeater = OptionalGreedyRange +Repeater = Array +StrictRepeater = Array +MetaRepeater = Array +OneOfValidator = OneOf +NoneOfValidator = NoneOf + +#=============================================================================== +# don't want to leek these out... +#=============================================================================== +del encode_bin, decode_bin, int_to_bin, bin_to_int, swap_bytes +del Packer, StringIO +del HexString, LazyContainer, AttrDict + + + + + + + + + + + + + + diff --git a/elftools/construct/adapters.py b/elftools/construct/adapters.py new file mode 100644 index 0000000..98fda72 --- /dev/null +++ b/elftools/construct/adapters.py @@ -0,0 +1,482 @@ +from core import Adapter, AdaptationError, Pass +from lib import int_to_bin, bin_to_int, swap_bytes, StringIO +from lib import FlagsContainer, HexString + + +#=============================================================================== +# exceptions +#=============================================================================== +class BitIntegerError(AdaptationError): + __slots__ = [] +class MappingError(AdaptationError): + __slots__ = [] +class ConstError(AdaptationError): + __slots__ = [] +class ValidationError(AdaptationError): + __slots__ = [] +class PaddingError(AdaptationError): + __slots__ = [] + +#=============================================================================== +# adapters +#=============================================================================== +class BitIntegerAdapter(Adapter): + """ + Adapter for bit-integers (converts bitstrings to integers, and vice versa). + See BitField. + + Parameters: + * subcon - the subcon to adapt + * width - the size of the subcon, in bits + * swapped - whether to swap byte order (little endian/big endian). + default is False (big endian) + * signed - whether the value is signed (two's complement). the default + is False (unsigned) + * bytesize - number of bits per byte, used for byte-swapping (if swapped). + default is 8. + """ + __slots__ = ["width", "swapped", "signed", "bytesize"] + def __init__(self, subcon, width, swapped = False, signed = False, + bytesize = 8): + Adapter.__init__(self, subcon) + self.width = width + self.swapped = swapped + self.signed = signed + self.bytesize = bytesize + def _encode(self, obj, context): + if obj < 0 and not self.signed: + raise BitIntegerError("object is negative, but field is not signed", + obj) + obj2 = int_to_bin(obj, width = self.width) + if self.swapped: + obj2 = swap_bytes(obj2, bytesize = self.bytesize) + return obj2 + def _decode(self, obj, context): + if self.swapped: + obj = swap_bytes(obj, bytesize = self.bytesize) + return bin_to_int(obj, signed = self.signed) + +class MappingAdapter(Adapter): + """ + Adapter that maps objects to other objects. + See SymmetricMapping and Enum. + + Parameters: + * subcon - the subcon to map + * decoding - the decoding (parsing) mapping (a dict) + * encoding - the encoding (building) mapping (a dict) + * decdefault - the default return value when the object is not found + in the decoding mapping. if no object is given, an exception is raised. + if `Pass` is used, the unmapped object will be passed as-is + * encdefault - the default return value when the object is not found + in the encoding mapping. if no object is given, an exception is raised. + if `Pass` is used, the unmapped object will be passed as-is + """ + __slots__ = ["encoding", "decoding", "encdefault", "decdefault"] + def __init__(self, subcon, decoding, encoding, + decdefault = NotImplemented, encdefault = NotImplemented): + Adapter.__init__(self, subcon) + self.decoding = decoding + self.encoding = encoding + self.decdefault = decdefault + self.encdefault = encdefault + def _encode(self, obj, context): + try: + return self.encoding[obj] + except (KeyError, TypeError): + if self.encdefault is NotImplemented: + raise MappingError("no encoding mapping for %r" % (obj,)) + if self.encdefault is Pass: + return obj + return self.encdefault + def _decode(self, obj, context): + try: + return self.decoding[obj] + except (KeyError, TypeError): + if self.decdefault is NotImplemented: + raise MappingError("no decoding mapping for %r" % (obj,)) + if self.decdefault is Pass: + return obj + return self.decdefault + +class FlagsAdapter(Adapter): + """ + Adapter for flag fields. Each flag is extracted from the number, resulting + in a FlagsContainer object. Not intended for direct usage. + See FlagsEnum. + + Parameters + * subcon - the subcon to extract + * flags - a dictionary mapping flag-names to their value + """ + __slots__ = ["flags"] + def __init__(self, subcon, flags): + Adapter.__init__(self, subcon) + self.flags = flags + def _encode(self, obj, context): + flags = 0 + for name, value in self.flags.iteritems(): + if getattr(obj, name, False): + flags |= value + return flags + def _decode(self, obj, context): + obj2 = FlagsContainer() + for name, value in self.flags.iteritems(): + setattr(obj2, name, bool(obj & value)) + return obj2 + +class StringAdapter(Adapter): + """ + Adapter for strings. Converts a sequence of characters into a python + string, and optionally handles character encoding. + See String. + + Parameters: + * subcon - the subcon to convert + * encoding - the character encoding name (e.g., "utf8"), or None to + return raw bytes (usually 8-bit ASCII). + """ + __slots__ = ["encoding"] + def __init__(self, subcon, encoding = None): + Adapter.__init__(self, subcon) + self.encoding = encoding + def _encode(self, obj, context): + if self.encoding: + obj = obj.encode(self.encoding) + return obj + def _decode(self, obj, context): + obj = "".join(obj) + if self.encoding: + obj = obj.decode(self.encoding) + return obj + +class PaddedStringAdapter(Adapter): + r""" + Adapter for padded strings. + See String. + + Parameters: + * subcon - the subcon to adapt + * padchar - the padding character. default is "\x00". + * paddir - the direction where padding is placed ("right", "left", or + "center"). the default is "right". + * trimdir - the direction where trimming will take place ("right" or + "left"). the default is "right". trimming is only meaningful for + building, when the given string is too long. + """ + __slots__ = ["padchar", "paddir", "trimdir"] + def __init__(self, subcon, padchar = "\x00", paddir = "right", + trimdir = "right"): + if paddir not in ("right", "left", "center"): + raise ValueError("paddir must be 'right', 'left' or 'center'", + paddir) + if trimdir not in ("right", "left"): + raise ValueError("trimdir must be 'right' or 'left'", trimdir) + Adapter.__init__(self, subcon) + self.padchar = padchar + self.paddir = paddir + self.trimdir = trimdir + def _decode(self, obj, context): + if self.paddir == "right": + obj = obj.rstrip(self.padchar) + elif self.paddir == "left": + obj = obj.lstrip(self.padchar) + else: + obj = obj.strip(self.padchar) + return obj + def _encode(self, obj, context): + size = self._sizeof(context) + if self.paddir == "right": + obj = obj.ljust(size, self.padchar) + elif self.paddir == "left": + obj = obj.rjust(size, self.padchar) + else: + obj = obj.center(size, self.padchar) + if len(obj) > size: + if self.trimdir == "right": + obj = obj[:size] + else: + obj = obj[-size:] + return obj + +class LengthValueAdapter(Adapter): + """ + Adapter for length-value pairs. It extracts only the value from the + pair, and calculates the length based on the value. + See PrefixedArray and PascalString. + + Parameters: + * subcon - the subcon returning a length-value pair + """ + __slots__ = [] + def _encode(self, obj, context): + return (len(obj), obj) + def _decode(self, obj, context): + return obj[1] + +class CStringAdapter(StringAdapter): + r""" + Adapter for C-style strings (strings terminated by a terminator char). + + Parameters: + * subcon - the subcon to convert + * terminators - a sequence of terminator chars. default is "\x00". + * encoding - the character encoding to use (e.g., "utf8"), or None to + return raw-bytes. the terminator characters are not affected by the + encoding. + """ + __slots__ = ["terminators"] + def __init__(self, subcon, terminators = "\x00", encoding = None): + StringAdapter.__init__(self, subcon, encoding = encoding) + self.terminators = terminators + def _encode(self, obj, context): + return StringAdapter._encode(self, obj, context) + self.terminators[0] + def _decode(self, obj, context): + return StringAdapter._decode(self, obj[:-1], context) + +class TunnelAdapter(Adapter): + """ + Adapter for tunneling (as in protocol tunneling). A tunnel is construct + nested upon another (layering). For parsing, the lower layer first parses + the data (note: it must return a string!), then the upper layer is called + to parse that data (bottom-up). For building it works in a top-down manner; + first the upper layer builds the data, then the lower layer takes it and + writes it to the stream. + + Parameters: + * subcon - the lower layer subcon + * inner_subcon - the upper layer (tunneled/nested) subcon + + Example: + # a pascal string containing compressed data (zlib encoding), so first + # the string is read, decompressed, and finally re-parsed as an array + # of UBInt16 + TunnelAdapter( + PascalString("data", encoding = "zlib"), + GreedyRange(UBInt16("elements")) + ) + """ + __slots__ = ["inner_subcon"] + def __init__(self, subcon, inner_subcon): + Adapter.__init__(self, subcon) + self.inner_subcon = inner_subcon + def _decode(self, obj, context): + return self.inner_subcon._parse(StringIO(obj), context) + def _encode(self, obj, context): + stream = StringIO() + self.inner_subcon._build(obj, stream, context) + return stream.getvalue() + +class ExprAdapter(Adapter): + """ + A generic adapter that accepts 'encoder' and 'decoder' as parameters. You + can use ExprAdapter instead of writing a full-blown class when only a + simple expression is needed. + + Parameters: + * subcon - the subcon to adapt + * encoder - a function that takes (obj, context) and returns an encoded + version of obj + * decoder - a function that takes (obj, context) and returns an decoded + version of obj + + Example: + ExprAdapter(UBInt8("foo"), + encoder = lambda obj, ctx: obj / 4, + decoder = lambda obj, ctx: obj * 4, + ) + """ + __slots__ = ["_encode", "_decode"] + def __init__(self, subcon, encoder, decoder): + Adapter.__init__(self, subcon) + self._encode = encoder + self._decode = decoder + +class HexDumpAdapter(Adapter): + """ + Adapter for hex-dumping strings. It returns a HexString, which is a string + """ + __slots__ = ["linesize"] + def __init__(self, subcon, linesize = 16): + Adapter.__init__(self, subcon) + self.linesize = linesize + def _encode(self, obj, context): + return obj + def _decode(self, obj, context): + return HexString(obj, linesize = self.linesize) + +class ConstAdapter(Adapter): + """ + Adapter for enforcing a constant value ("magic numbers"). When decoding, + the return value is checked; when building, the value is substituted in. + + Parameters: + * subcon - the subcon to validate + * value - the expected value + + Example: + Const(Field("signature", 2), "MZ") + """ + __slots__ = ["value"] + def __init__(self, subcon, value): + Adapter.__init__(self, subcon) + self.value = value + def _encode(self, obj, context): + if obj is None or obj == self.value: + return self.value + else: + raise ConstError("expected %r, found %r" % (self.value, obj)) + def _decode(self, obj, context): + if obj != self.value: + raise ConstError("expected %r, found %r" % (self.value, obj)) + return obj + +class SlicingAdapter(Adapter): + """ + Adapter for slicing a list (getting a slice from that list) + + Parameters: + * subcon - the subcon to slice + * start - start index + * stop - stop index (or None for up-to-end) + * step - step (or None for every element) + """ + __slots__ = ["start", "stop", "step"] + def __init__(self, subcon, start, stop = None): + Adapter.__init__(self, subcon) + self.start = start + self.stop = stop + def _encode(self, obj, context): + if self.start is None: + return obj + return [None] * self.start + obj + def _decode(self, obj, context): + return obj[self.start:self.stop] + +class IndexingAdapter(Adapter): + """ + Adapter for indexing a list (getting a single item from that list) + + Parameters: + * subcon - the subcon to index + * index - the index of the list to get + """ + __slots__ = ["index"] + def __init__(self, subcon, index): + Adapter.__init__(self, subcon) + if type(index) is not int: + raise TypeError("index must be an integer", type(index)) + self.index = index + def _encode(self, obj, context): + return [None] * self.index + [obj] + def _decode(self, obj, context): + return obj[self.index] + +class PaddingAdapter(Adapter): + r""" + Adapter for padding. + + Parameters: + * subcon - the subcon to pad + * pattern - the padding pattern (character). default is "\x00") + * strict - whether or not to verify, during parsing, that the given + padding matches the padding pattern. default is False (unstrict) + """ + __slots__ = ["pattern", "strict"] + def __init__(self, subcon, pattern = "\x00", strict = False): + Adapter.__init__(self, subcon) + self.pattern = pattern + self.strict = strict + def _encode(self, obj, context): + return self._sizeof(context) * self.pattern + def _decode(self, obj, context): + if self.strict: + expected = self._sizeof(context) * self.pattern + if obj != expected: + raise PaddingError("expected %r, found %r" % (expected, obj)) + return obj + + +#=============================================================================== +# validators +#=============================================================================== +class Validator(Adapter): + """ + Abstract class: validates a condition on the encoded/decoded object. + Override _validate(obj, context) in deriving classes. + + Parameters: + * subcon - the subcon to validate + """ + __slots__ = [] + def _decode(self, obj, context): + if not self._validate(obj, context): + raise ValidationError("invalid object", obj) + return obj + def _encode(self, obj, context): + return self._decode(obj, context) + def _validate(self, obj, context): + raise NotImplementedError() + +class OneOf(Validator): + """ + Validates that the value is one of the listed values + + Parameters: + * subcon - the subcon to validate + * valids - a set of valid values + """ + __slots__ = ["valids"] + def __init__(self, subcon, valids): + Validator.__init__(self, subcon) + self.valids = valids + def _validate(self, obj, context): + return obj in self.valids + +class NoneOf(Validator): + """ + Validates that the value is none of the listed values + + Parameters: + * subcon - the subcon to validate + * invalids - a set of invalid values + """ + __slots__ = ["invalids"] + def __init__(self, subcon, invalids): + Validator.__init__(self, subcon) + self.invalids = invalids + def _validate(self, obj, context): + return obj not in self.invalids + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/elftools/construct/core.py b/elftools/construct/core.py new file mode 100644 index 0000000..b09382f --- /dev/null +++ b/elftools/construct/core.py @@ -0,0 +1,1217 @@ +from lib import StringIO, Packer +from lib import Container, ListContainer, AttrDict, LazyContainer + + +#=============================================================================== +# exceptions +#=============================================================================== +class ConstructError(Exception): + __slots__ = [] +class FieldError(ConstructError): + __slots__ = [] +class SizeofError(ConstructError): + __slots__ = [] +class AdaptationError(ConstructError): + __slots__ = [] +class ArrayError(ConstructError): + __slots__ = [] +class RangeError(ConstructError): + __slots__ = [] +class SwitchError(ConstructError): + __slots__ = [] +class SelectError(ConstructError): + __slots__ = [] +class TerminatorError(ConstructError): + __slots__ = [] + +#=============================================================================== +# abstract constructs +#=============================================================================== +class Construct(object): + """ + The mother of all constructs! + + User API: + * parse(buf) - parses an in-memory buffer (usually a string) + * parse_stream(stream) - parses a stream (in-memory, file, pipe, ...) + * build(obj) - builds the object into an in-memory buffer (a string) + * build_stream(obj, stream) - builds the object into the given stream + * sizeof(context) - calculates the size of the construct, if possible, + based on the context + + Overriable methods for subclassing: + * _parse(stream, context) - low-level parse from stream + * _build(obj, stream, context) - low-level build to stream + * _sizeof(context) - low-level compute size + + Flags API: + * _set_flag(flag) - sets the given flag/flags + * _clear_flag(flag) - clears the given flag/flags + * _inherit_flags(*subcons) - inherits the flag of subcons + * _is_flag(flag) - is the flag set? (predicate) + + Overridable methods for the copy-API: + * __getstate__() - returns a dict of the attributes of self + * __setstate__(attrs) - sets the attrs to self + + Attributes: + All constructs have a name and flags. The name is used for naming + struct-members and context dicts. Note that the name must be a string or + None (if the name is not needed). A single underscore ("_") is a reserved + name, and so are names starting with a less-than character ("<"). The name + should be descriptive, short, and valid as a python identifier (although + these rules are not enforced). + + The flags specify additional behavioral information about this construct. + The flags are used by enclosing constructs to determine a proper course + of action. Usually, flags are "inherited", i.e., an enclosing construct + inherits the flags of its subconstruct. The enclosing construct may + set new flags or clear existing ones, as necessary. + + For example, if FLAG_COPY_CONTEXT is set, repeaters will pass a copy of + the context for each iteration, which is necessary for OnDemand parsing. + """ + FLAG_COPY_CONTEXT = 0x0001 + FLAG_DYNAMIC = 0x0002 + FLAG_EMBED = 0x0004 + FLAG_NESTING = 0x0008 + + __slots__ = ["name", "conflags"] + def __init__(self, name, flags = 0): + if name is not None: + if type(name) is not str: + raise TypeError("name must be a string or None", name) + if name == "_" or name.startswith("<"): + raise ValueError("reserved name", name) + self.name = name + self.conflags = flags + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self.name) + + def _set_flag(self, flag): + self.conflags |= flag + def _clear_flag(self, flag): + self.conflags &= ~flag + def _inherit_flags(self, *subcons): + for sc in subcons: + self._set_flag(sc.conflags) + def _is_flag(self, flag): + return bool(self.conflags & flag) + + def __getstate__(self): + attrs = {} + if hasattr(self, "__dict__"): + attrs.update(self.__dict__) + slots = [] + c = self.__class__ + while c is not None: + if hasattr(c, "__slots__"): + slots.extend(c.__slots__) + c = c.__base__ + for name in slots: + if hasattr(self, name): + attrs[name] = getattr(self, name) + return attrs + def __setstate__(self, attrs): + for name, value in attrs.iteritems(): + setattr(self, name, value) + def __copy__(self): + """returns a copy of this construct""" + self2 = object.__new__(self.__class__) + self2.__setstate__(self.__getstate__()) + return self2 + + def parse(self, data): + """parses data given as a buffer or a string (in-memory)""" + return self.parse_stream(StringIO(data)) + def parse_stream(self, stream): + """parses data read directly from a stream""" + return self._parse(stream, AttrDict()) + def _parse(self, stream, context): + raise NotImplementedError() + + def build(self, obj): + """builds an object in a string (in memory)""" + stream = StringIO() + self.build_stream(obj, stream) + return stream.getvalue() + def build_stream(self, obj, stream): + """builds an object into a stream""" + self._build(obj, stream, AttrDict()) + def _build(self, obj, stream, context): + raise NotImplementedError() + + def sizeof(self, context = None): + """calculates the size of the construct (if possible) using the + given context""" + if context is None: + context = AttrDict() + return self._sizeof(context) + def _sizeof(self, context): + raise SizeofError("can't calculate size") + +class Subconstruct(Construct): + """ + Abstract subconstruct (wraps an inner construct, inheriting it's + name and flags). + + Parameters: + * subcon - the construct to wrap + """ + __slots__ = ["subcon"] + def __init__(self, subcon): + Construct.__init__(self, subcon.name, subcon.conflags) + self.subcon = subcon + def _parse(self, stream, context): + return self.subcon._parse(stream, context) + def _build(self, obj, stream, context): + self.subcon._build(obj, stream, context) + def _sizeof(self, context): + return self.subcon._sizeof(context) + +class Adapter(Subconstruct): + """ + Abstract adapter: calls _decode for parsing and _encode for building. + + Parameters: + * subcon - the construct to wrap + """ + __slots__ = [] + def _parse(self, stream, context): + return self._decode(self.subcon._parse(stream, context), context) + def _build(self, obj, stream, context): + self.subcon._build(self._encode(obj, context), stream, context) + def _decode(self, obj, context): + raise NotImplementedError() + def _encode(self, obj, context): + raise NotImplementedError() + + +#=============================================================================== +# primitives +#=============================================================================== +def _read_stream(stream, length): + if length < 0: + raise ValueError("length must be >= 0", length) + data = stream.read(length) + if len(data) != length: + raise FieldError("expected %d, found %d" % (length, len(data))) + return data + +def _write_stream(stream, length, data): + if length < 0: + raise ValueError("length must be >= 0", length) + if len(data) != length: + raise FieldError("expected %d, found %d" % (length, len(data))) + stream.write(data) + +class StaticField(Construct): + """ + A field of a fixed size + + Parameters: + * name - the name of the field + * length - the length (an integer) + + Example: + StaticField("foo", 5) + """ + __slots__ = ["length"] + def __init__(self, name, length): + Construct.__init__(self, name) + self.length = length + def _parse(self, stream, context): + return _read_stream(stream, self.length) + def _build(self, obj, stream, context): + _write_stream(stream, self.length, obj) + def _sizeof(self, context): + return self.length + +class FormatField(StaticField): + """ + A field that uses python's built-in struct module to pack/unpack data + according to a format string. + Note: this field has been originally implemented as an Adapter, but it + was made a construct for performance reasons. + + Parameters: + * name - the name + * endianity - "<" for little endian, ">" for big endian, or "=" for native + * format - a single format character + + Example: + FormatField("foo", ">", "L") + """ + __slots__ = ["packer"] + def __init__(self, name, endianity, format): + if endianity not in (">", "<", "="): + raise ValueError("endianity must be be '=', '<', or '>'", + endianity) + if len(format) != 1: + raise ValueError("must specify one and only one format char") + self.packer = Packer(endianity + format) + StaticField.__init__(self, name, self.packer.size) + def __getstate__(self): + attrs = StaticField.__getstate__(self) + attrs["packer"] = attrs["packer"].format + return attrs + def __setstate__(self, attrs): + attrs["packer"] = Packer(attrs["packer"]) + return StaticField.__setstate__(attrs) + def _parse(self, stream, context): + try: + return self.packer.unpack(_read_stream(stream, self.length))[0] + except Exception, ex: + raise FieldError(ex) + def _build(self, obj, stream, context): + try: + _write_stream(stream, self.length, self.packer.pack(obj)) + except Exception, ex: + raise FieldError(ex) + +class MetaField(Construct): + """ + A field of a meta-length. The length is computed at runtime based on + the context. + + Parameters: + * name - the name of the field + * lengthfunc - a function that takes the context as a parameter and return + the length of the field + + Example: + MetaField("foo", lambda ctx: 5) + """ + __slots__ = ["lengthfunc"] + def __init__(self, name, lengthfunc): + Construct.__init__(self, name) + self.lengthfunc = lengthfunc + self._set_flag(self.FLAG_DYNAMIC) + def _parse(self, stream, context): + return _read_stream(stream, self.lengthfunc(context)) + def _build(self, obj, stream, context): + _write_stream(stream, self.lengthfunc(context), obj) + def _sizeof(self, context): + return self.lengthfunc(context) + + +#=============================================================================== +# arrays and repeaters +#=============================================================================== +class MetaArray(Subconstruct): + """ + An array (repeater) of a meta-count. The array will iterate exactly + `countfunc()` times. Will raise ArrayError if less elements are found. + See also Array, Range and RepeatUntil. + + Parameters: + * countfunc - a function that takes the context as a parameter and returns + the number of elements of the array (count) + * subcon - the subcon to repeat `countfunc()` times + + Example: + MetaArray(lambda ctx: 5, UBInt8("foo")) + """ + __slots__ = ["countfunc"] + def __init__(self, countfunc, subcon): + Subconstruct.__init__(self, subcon) + self.countfunc = countfunc + self._clear_flag(self.FLAG_COPY_CONTEXT) + self._set_flag(self.FLAG_DYNAMIC) + def _parse(self, stream, context): + obj = ListContainer() + c = 0 + count = self.countfunc(context) + try: + if self.subcon.conflags & self.FLAG_COPY_CONTEXT: + while c < count: + obj.append(self.subcon._parse(stream, context.__copy__())) + c += 1 + else: + while c < count: + obj.append(self.subcon._parse(stream, context)) + c += 1 + except ConstructError, ex: + raise ArrayError("expected %d, found %d" % (count, c), ex) + return obj + def _build(self, obj, stream, context): + count = self.countfunc(context) + if len(obj) != count: + raise ArrayError("expected %d, found %d" % (count, len(obj))) + if self.subcon.conflags & self.FLAG_COPY_CONTEXT: + for subobj in obj: + self.subcon._build(subobj, stream, context.__copy__()) + else: + for subobj in obj: + self.subcon._build(subobj, stream, context) + def _sizeof(self, context): + return self.subcon._sizeof(context) * self.countfunc(context) + +class Range(Subconstruct): + """ + A range-array. The subcon will iterate between `mincount` to `maxcount` + times. If less than `mincount` elements are found, raises RangeError. + See also GreedyRange and OptionalGreedyRange. + + Notes: + * requires a seekable stream. + + Parameters: + * mincount - the minimal count (an integer) + * maxcount - the maximal count (an integer) + * subcon - the subcon to repeat + + Example: + Range(5, 8, UBInt8("foo")) + """ + __slots__ = ["mincount", "maxcout"] + def __init__(self, mincount, maxcout, subcon): + Subconstruct.__init__(self, subcon) + self.mincount = mincount + self.maxcout = maxcout + self._clear_flag(self.FLAG_COPY_CONTEXT) + self._set_flag(self.FLAG_DYNAMIC) + def _parse(self, stream, context): + obj = ListContainer() + c = 0 + try: + if self.subcon.conflags & self.FLAG_COPY_CONTEXT: + while c < self.maxcout: + pos = stream.tell() + obj.append(self.subcon._parse(stream, context.__copy__())) + c += 1 + else: + while c < self.maxcout: + pos = stream.tell() + obj.append(self.subcon._parse(stream, context)) + c += 1 + except ConstructError: + if c < self.mincount: + raise RangeError("expected %d to %d, found %d" % + (self.mincount, self.maxcout, c)) + stream.seek(pos) + return obj + def _build(self, obj, stream, context): + if len(obj) < self.mincount or len(obj) > self.maxcout: + raise RangeError("expected %d to %d, found %d" % + (self.mincount, self.maxcout, len(obj))) + cnt = 0 + try: + if self.subcon.conflags & self.FLAG_COPY_CONTEXT: + for subobj in obj: + self.subcon._build(subobj, stream, context.__copy__()) + cnt += 1 + else: + for subobj in obj: + self.subcon._build(subobj, stream, context) + cnt += 1 + except ConstructError: + if cnt < self.mincount: + raise RangeError("expected %d to %d, found %d" % + (self.mincount, self.maxcout, len(obj))) + def _sizeof(self, context): + raise SizeofError("can't calculate size") + +class RepeatUntil(Subconstruct): + """ + An array that repeat until the predicate indicates it to stop. Note that + the last element (which caused the repeat to exit) is included in the + return value. + + Parameters: + * predicate - a predicate function that takes (obj, context) and returns + True if the stop-condition is met, or False to continue. + * subcon - the subcon to repeat. + + Example: + # will read chars until \x00 (inclusive) + RepeatUntil(lambda obj, ctx: obj == "\x00", + Field("chars", 1) + ) + """ + __slots__ = ["predicate"] + def __init__(self, predicate, subcon): + Subconstruct.__init__(self, subcon) + self.predicate = predicate + self._clear_flag(self.FLAG_COPY_CONTEXT) + self._set_flag(self.FLAG_DYNAMIC) + def _parse(self, stream, context): + obj = [] + try: + if self.subcon.conflags & self.FLAG_COPY_CONTEXT: + while True: + subobj = self.subcon._parse(stream, context.__copy__()) + obj.append(subobj) + if self.predicate(subobj, context): + break + else: + while True: + subobj = self.subcon._parse(stream, context) + obj.append(subobj) + if self.predicate(subobj, context): + break + except ConstructError, ex: + raise ArrayError("missing terminator", ex) + return obj + def _build(self, obj, stream, context): + terminated = False + if self.subcon.conflags & self.FLAG_COPY_CONTEXT: + for subobj in obj: + self.subcon._build(subobj, stream, context.__copy__()) + if self.predicate(subobj, context): + terminated = True + break + else: + for subobj in obj: + self.subcon._build(subobj, stream, context.__copy__()) + if self.predicate(subobj, context): + terminated = True + break + if not terminated: + raise ArrayError("missing terminator") + def _sizeof(self, context): + raise SizeofError("can't calculate size") + + +#=============================================================================== +# structures and sequences +#=============================================================================== +class Struct(Construct): + """ + A sequence of named constructs, similar to structs in C. The elements are + parsed and built in the order they are defined. + See also Embedded. + + Parameters: + * name - the name of the structure + * subcons - a sequence of subconstructs that make up this structure. + * nested - a keyword-only argument that indicates whether this struct + creates a nested context. The default is True. This parameter is + considered "advanced usage", and may be removed in the future. + + Example: + Struct("foo", + UBInt8("first_element"), + UBInt16("second_element"), + Padding(2), + UBInt8("third_element"), + ) + """ + __slots__ = ["subcons", "nested"] + def __init__(self, name, *subcons, **kw): + self.nested = kw.pop("nested", True) + if kw: + raise TypeError("the only keyword argument accepted is 'nested'", kw) + Construct.__init__(self, name) + self.subcons = subcons + self._inherit_flags(*subcons) + self._clear_flag(self.FLAG_EMBED) + def _parse(self, stream, context): + if "" in context: + obj = context[""] + del context[""] + else: + obj = Container() + if self.nested: + context = AttrDict(_ = context) + for sc in self.subcons: + if sc.conflags & self.FLAG_EMBED: + context[""] = obj + sc._parse(stream, context) + else: + subobj = sc._parse(stream, context) + if sc.name is not None: + obj[sc.name] = subobj + context[sc.name] = subobj + return obj + def _build(self, obj, stream, context): + if "" in context: + del context[""] + elif self.nested: + context = AttrDict(_ = context) + for sc in self.subcons: + if sc.conflags & self.FLAG_EMBED: + context[""] = True + subobj = obj + elif sc.name is None: + subobj = None + else: + subobj = getattr(obj, sc.name) + context[sc.name] = subobj + sc._build(subobj, stream, context) + def _sizeof(self, context): + if self.nested: + context = AttrDict(_ = context) + return sum(sc._sizeof(context) for sc in self.subcons) + +class Sequence(Struct): + """ + A sequence of unnamed constructs. The elements are parsed and built in the + order they are defined. + See also Embedded. + + Parameters: + * name - the name of the structure + * subcons - a sequence of subconstructs that make up this structure. + * nested - a keyword-only argument that indicates whether this struct + creates a nested context. The default is True. This parameter is + considered "advanced usage", and may be removed in the future. + + Example: + Sequence("foo", + UBInt8("first_element"), + UBInt16("second_element"), + Padding(2), + UBInt8("third_element"), + ) + """ + __slots__ = [] + def _parse(self, stream, context): + if "" in context: + obj = context[""] + del context[""] + else: + obj = ListContainer() + if self.nested: + context = AttrDict(_ = context) + for sc in self.subcons: + if sc.conflags & self.FLAG_EMBED: + context[""] = obj + sc._parse(stream, context) + else: + subobj = sc._parse(stream, context) + if sc.name is not None: + obj.append(subobj) + context[sc.name] = subobj + return obj + def _build(self, obj, stream, context): + if "" in context: + del context[""] + elif self.nested: + context = AttrDict(_ = context) + objiter = iter(obj) + for sc in self.subcons: + if sc.conflags & self.FLAG_EMBED: + context[""] = True + subobj = objiter + elif sc.name is None: + subobj = None + else: + subobj = objiter.next() + context[sc.name] = subobj + sc._build(subobj, stream, context) + +class Union(Construct): + """ + a set of overlapping fields (like unions in C). when parsing, + all fields read the same data; when building, only the first subcon + (called "master") is used. + + Parameters: + * name - the name of the union + * master - the master subcon, i.e., the subcon used for building and + calculating the total size + * subcons - additional subcons + + Example: + Union("what_are_four_bytes", + UBInt32("one_dword"), + Struct("two_words", UBInt16("first"), UBInt16("second")), + Struct("four_bytes", + UBInt8("a"), + UBInt8("b"), + UBInt8("c"), + UBInt8("d") + ), + ) + """ + __slots__ = ["parser", "builder"] + def __init__(self, name, master, *subcons, **kw): + Construct.__init__(self, name) + args = [Peek(sc) for sc in subcons] + args.append(MetaField(None, lambda ctx: master._sizeof(ctx))) + self.parser = Struct(name, Peek(master, perform_build = True), *args) + self.builder = Struct(name, master) + def _parse(self, stream, context): + return self.parser._parse(stream, context) + def _build(self, obj, stream, context): + return self.builder._build(obj, stream, context) + def _sizeof(self, context): + return self.builder._sizeof(context) + +#=============================================================================== +# conditional +#=============================================================================== +class Switch(Construct): + """ + A conditional branch. Switch will choose the case to follow based on + the return value of keyfunc. If no case is matched, and no default value + is given, SwitchError will be raised. + See also Pass. + + Parameters: + * name - the name of the construct + * keyfunc - a function that takes the context and returns a key, which + will ne used to choose the relevant case. + * cases - a dictionary mapping keys to constructs. the keys can be any + values that may be returned by keyfunc. + * default - a default value to use when the key is not found in the cases. + if not supplied, an exception will be raised when the key is not found. + You can use the builtin construct Pass for 'do-nothing'. + * include_key - whether or not to include the key in the return value + of parsing. defualt is False. + + Example: + Struct("foo", + UBInt8("type"), + Switch("value", lambda ctx: ctx.type, { + 1 : UBInt8("spam"), + 2 : UBInt16("spam"), + 3 : UBInt32("spam"), + 4 : UBInt64("spam"), + } + ), + ) + """ + + class NoDefault(Construct): + def _parse(self, stream, context): + raise SwitchError("no default case defined") + def _build(self, obj, stream, context): + raise SwitchError("no default case defined") + def _sizeof(self, context): + raise SwitchError("no default case defined") + NoDefault = NoDefault("NoDefault") + + __slots__ = ["subcons", "keyfunc", "cases", "default", "include_key"] + + def __init__(self, name, keyfunc, cases, default = NoDefault, + include_key = False): + Construct.__init__(self, name) + self._inherit_flags(*cases.values()) + self.keyfunc = keyfunc + self.cases = cases + self.default = default + self.include_key = include_key + self._inherit_flags(*cases.values()) + self._set_flag(self.FLAG_DYNAMIC) + def _parse(self, stream, context): + key = self.keyfunc(context) + obj = self.cases.get(key, self.default)._parse(stream, context) + if self.include_key: + return key, obj + else: + return obj + def _build(self, obj, stream, context): + if self.include_key: + key, obj = obj + else: + key = self.keyfunc(context) + case = self.cases.get(key, self.default) + case._build(obj, stream, context) + def _sizeof(self, context): + case = self.cases.get(self.keyfunc(context), self.default) + return case._sizeof(context) + +class Select(Construct): + """ + Selects the first matching subconstruct. It will literally try each of + the subconstructs, until one matches. + + Notes: + * requires a seekable stream. + + Parameters: + * name - the name of the construct + * subcons - the subcons to try (order-sensitive) + * include_name - a keyword only argument, indicating whether to include + the name of the selected subcon in the return value of parsing. default + is false. + + Example: + Select("foo", + UBInt64("large"), + UBInt32("medium"), + UBInt16("small"), + UBInt8("tiny"), + ) + """ + __slots__ = ["subcons", "include_name"] + def __init__(self, name, *subcons, **kw): + include_name = kw.pop("include_name", False) + if kw: + raise TypeError("the only keyword argument accepted " + "is 'include_name'", kw) + Construct.__init__(self, name) + self.subcons = subcons + self.include_name = include_name + self._inherit_flags(*subcons) + self._set_flag(self.FLAG_DYNAMIC) + def _parse(self, stream, context): + for sc in self.subcons: + pos = stream.tell() + context2 = context.__copy__() + try: + obj = sc._parse(stream, context2) + except ConstructError: + stream.seek(pos) + else: + context.__update__(context2) + if self.include_name: + return sc.name, obj + else: + return obj + raise SelectError("no subconstruct matched") + def _build(self, obj, stream, context): + if self.include_name: + name, obj = obj + for sc in self.subcons: + if sc.name == name: + sc._build(obj, stream, context) + return + else: + for sc in self.subcons: + stream2 = StringIO() + context2 = context.__copy__() + try: + sc._build(obj, stream2, context2) + except Exception: + pass + else: + context.__update__(context2) + stream.write(stream2.getvalue()) + return + raise SelectError("no subconstruct matched", obj) + def _sizeof(self, context): + raise SizeofError("can't calculate size") + + +#=============================================================================== +# stream manipulation +#=============================================================================== +class Pointer(Subconstruct): + """ + Changes the stream position to a given offset, where the construction + should take place, and restores the stream position when finished. + See also Anchor, OnDemand and OnDemandPointer. + + Notes: + * requires a seekable stream. + + Parameters: + * offsetfunc: a function that takes the context and returns an absolute + stream position, where the construction would take place + * subcon - the subcon to use at `offsetfunc()` + + Example: + Struct("foo", + UBInt32("spam_pointer"), + Pointer(lambda ctx: ctx.spam_pointer, + Array(5, UBInt8("spam")) + ) + ) + """ + __slots__ = ["offsetfunc"] + def __init__(self, offsetfunc, subcon): + Subconstruct.__init__(self, subcon) + self.offsetfunc = offsetfunc + def _parse(self, stream, context): + newpos = self.offsetfunc(context) + origpos = stream.tell() + stream.seek(newpos) + obj = self.subcon._parse(stream, context) + stream.seek(origpos) + return obj + def _build(self, obj, stream, context): + newpos = self.offsetfunc(context) + origpos = stream.tell() + stream.seek(newpos) + self.subcon._build(obj, stream, context) + stream.seek(origpos) + def _sizeof(self, context): + return 0 + +class Peek(Subconstruct): + """ + Peeks at the stream: parses without changing the stream position. + See also Union. If the end of the stream is reached when peeking, + returns None. + + Notes: + * requires a seekable stream. + + Parameters: + * subcon - the subcon to peek at + * perform_build - whether or not to perform building. by default this + parameter is set to False, meaning building is a no-op. + + Example: + Peek(UBInt8("foo")) + """ + __slots__ = ["perform_build"] + def __init__(self, subcon, perform_build = False): + Subconstruct.__init__(self, subcon) + self.perform_build = perform_build + def _parse(self, stream, context): + pos = stream.tell() + try: + try: + return self.subcon._parse(stream, context) + except FieldError: + pass + finally: + stream.seek(pos) + def _build(self, obj, stream, context): + if self.perform_build: + self.subcon._build(obj, stream, context) + def _sizeof(self, context): + return 0 + +class OnDemand(Subconstruct): + """ + Allows for on-demand (lazy) parsing. When parsing, it will return a + LazyContainer that represents a pointer to the data, but does not actually + parses it from stream until it's "demanded". + By accessing the 'value' property of LazyContainers, you will demand the + data from the stream. The data will be parsed and cached for later use. + You can use the 'has_value' property to know whether the data has already + been demanded. + See also OnDemandPointer. + + Notes: + * requires a seekable stream. + + Parameters: + * subcon - + * advance_stream - whether or not to advance the stream position. by + default this is True, but if subcon is a pointer, this should be False. + * force_build - whether or not to force build. If set to False, and the + LazyContainer has not been demaned, building is a no-op. + + Example: + OnDemand(Array(10000, UBInt8("foo")) + """ + __slots__ = ["advance_stream", "force_build"] + def __init__(self, subcon, advance_stream = True, force_build = True): + Subconstruct.__init__(self, subcon) + self.advance_stream = advance_stream + self.force_build = force_build + def _parse(self, stream, context): + obj = LazyContainer(self.subcon, stream, stream.tell(), context) + if self.advance_stream: + stream.seek(self.subcon._sizeof(context), 1) + return obj + def _build(self, obj, stream, context): + if not isinstance(obj, LazyContainer): + self.subcon._build(obj, stream, context) + elif self.force_build or obj.has_value: + self.subcon._build(obj.value, stream, context) + elif self.advance_stream: + stream.seek(self.subcon._sizeof(context), 1) + +class Buffered(Subconstruct): + """ + Creates an in-memory buffered stream, which can undergo encoding and + decoding prior to being passed on to the subconstruct. + See also Bitwise. + + Note: + * Do not use pointers inside Buffered + + Parameters: + * subcon - the subcon which will operate on the buffer + * encoder - a function that takes a string and returns an encoded + string (used after building) + * decoder - a function that takes a string and returns a decoded + string (used before parsing) + * resizer - a function that takes the size of the subcon and "adjusts" + or "resizes" it according to the encoding/decoding process. + + Example: + Buffered(BitField("foo", 16), + encoder = decode_bin, + decoder = encode_bin, + resizer = lambda size: size / 8, + ) + """ + __slots__ = ["encoder", "decoder", "resizer"] + def __init__(self, subcon, decoder, encoder, resizer): + Subconstruct.__init__(self, subcon) + self.encoder = encoder + self.decoder = decoder + self.resizer = resizer + def _parse(self, stream, context): + data = _read_stream(stream, self._sizeof(context)) + stream2 = StringIO(self.decoder(data)) + return self.subcon._parse(stream2, context) + def _build(self, obj, stream, context): + size = self._sizeof(context) + stream2 = StringIO() + self.subcon._build(obj, stream2, context) + data = self.encoder(stream2.getvalue()) + assert len(data) == size + _write_stream(stream, self._sizeof(context), data) + def _sizeof(self, context): + return self.resizer(self.subcon._sizeof(context)) + +class Restream(Subconstruct): + """ + Wraps the stream with a read-wrapper (for parsing) or a + write-wrapper (for building). The stream wrapper can buffer the data + internally, reading it from- or writing it to the underlying stream + as needed. For example, BitStreamReader reads whole bytes from the + underlying stream, but returns them as individual bits. + See also Bitwise. + + When the parsing or building is done, the stream's close method + will be invoked. It can perform any finalization needed for the stream + wrapper, but it must not close the underlying stream. + + Note: + * Do not use pointers inside Restream + + Parameters: + * subcon - the subcon + * stream_reader - the read-wrapper + * stream_writer - the write wrapper + * resizer - a function that takes the size of the subcon and "adjusts" + or "resizes" it according to the encoding/decoding process. + + Example: + Restream(BitField("foo", 16), + stream_reader = BitStreamReader, + stream_writer = BitStreamWriter, + resizer = lambda size: size / 8, + ) + """ + __slots__ = ["stream_reader", "stream_writer", "resizer"] + def __init__(self, subcon, stream_reader, stream_writer, resizer): + Subconstruct.__init__(self, subcon) + self.stream_reader = stream_reader + self.stream_writer = stream_writer + self.resizer = resizer + def _parse(self, stream, context): + stream2 = self.stream_reader(stream) + obj = self.subcon._parse(stream2, context) + stream2.close() + return obj + def _build(self, obj, stream, context): + stream2 = self.stream_writer(stream) + self.subcon._build(obj, stream2, context) + stream2.close() + def _sizeof(self, context): + return self.resizer(self.subcon._sizeof(context)) + + +#=============================================================================== +# miscellaneous +#=============================================================================== +class Reconfig(Subconstruct): + """ + Reconfigures a subconstruct. Reconfig can be used to change the name and + set and clear flags of the inner subcon. + + Parameters: + * name - the new name + * subcon - the subcon to reconfigure + * setflags - the flags to set (default is 0) + * clearflags - the flags to clear (default is 0) + + Example: + Reconfig("foo", UBInt8("bar")) + """ + __slots__ = [] + def __init__(self, name, subcon, setflags = 0, clearflags = 0): + Construct.__init__(self, name, subcon.conflags) + self.subcon = subcon + self._set_flag(setflags) + self._clear_flag(clearflags) + +class Anchor(Construct): + """ + Returns the "anchor" (stream position) at the point where it's inserted. + Useful for adjusting relative offsets to absolute positions, or to measure + sizes of constructs. + absolute pointer = anchor + relative offset + size = anchor_after - anchor_before + See also Pointer. + + Notes: + * requires a seekable stream. + + Parameters: + * name - the name of the anchor + + Example: + Struct("foo", + Anchor("base"), + UBInt8("relative_offset"), + Pointer(lambda ctx: ctx.relative_offset + ctx.base, + UBInt8("data") + ) + ) + """ + __slots__ = [] + def _parse(self, stream, context): + return stream.tell() + def _build(self, obj, stream, context): + context[self.name] = stream.tell() + def _sizeof(self, context): + return 0 + +class Value(Construct): + """ + A computed value. + + Parameters: + * name - the name of the value + * func - a function that takes the context and return the computed value + + Example: + Struct("foo", + UBInt8("width"), + UBInt8("height"), + Value("total_pixels", lambda ctx: ctx.width * ctx.height), + ) + """ + __slots__ = ["func"] + def __init__(self, name, func): + Construct.__init__(self, name) + self.func = func + self._set_flag(self.FLAG_DYNAMIC) + def _parse(self, stream, context): + return self.func(context) + def _build(self, obj, stream, context): + context[self.name] = self.func(context) + def _sizeof(self, context): + return 0 + +#class Dynamic(Construct): +# """ +# Dynamically creates a construct and uses it for parsing and building. +# This allows you to create change the construction tree on the fly. +# Deprecated. +# +# Parameters: +# * name - the name of the construct +# * factoryfunc - a function that takes the context and returns a new +# construct object which will be used for parsing and building. +# +# Example: +# def factory(ctx): +# if ctx.bar == 8: +# return UBInt8("spam") +# if ctx.bar == 9: +# return String("spam", 9) +# +# Struct("foo", +# UBInt8("bar"), +# Dynamic("spam", factory), +# ) +# """ +# __slots__ = ["factoryfunc"] +# def __init__(self, name, factoryfunc): +# Construct.__init__(self, name, self.FLAG_COPY_CONTEXT) +# self.factoryfunc = factoryfunc +# self._set_flag(self.FLAG_DYNAMIC) +# def _parse(self, stream, context): +# return self.factoryfunc(context)._parse(stream, context) +# def _build(self, obj, stream, context): +# return self.factoryfunc(context)._build(obj, stream, context) +# def _sizeof(self, context): +# return self.factoryfunc(context)._sizeof(context) + +class LazyBound(Construct): + """ + Lazily bound construct, useful for constructs that need to make cyclic + references (linked-lists, expression trees, etc.). + + Parameters: + + + Example: + foo = Struct("foo", + UBInt8("bar"), + LazyBound("next", lambda: foo), + ) + """ + __slots__ = ["bindfunc", "bound"] + def __init__(self, name, bindfunc): + Construct.__init__(self, name) + self.bound = None + self.bindfunc = bindfunc + def _parse(self, stream, context): + if self.bound is None: + self.bound = self.bindfunc() + return self.bound._parse(stream, context) + def _build(self, obj, stream, context): + if self.bound is None: + self.bound = self.bindfunc() + self.bound._build(obj, stream, context) + def _sizeof(self, context): + if self.bound is None: + self.bound = self.bindfunc() + return self.bound._sizeof(context) + +class Pass(Construct): + """ + A do-nothing construct, useful as the default case for Switch, or + to indicate Enums. + See also Switch and Enum. + + Notes: + * this construct is a singleton. do not try to instatiate it, as it + will not work :) + + Example: + Pass + """ + __slots__ = [] + def _parse(self, stream, context): + pass + def _build(self, obj, stream, context): + assert obj is None + def _sizeof(self, context): + return 0 +Pass = Pass(None) + +class Terminator(Construct): + """ + Asserts the end of the stream has been reached at the point it's placed. + You can use this to ensure no more unparsed data follows. + + Notes: + * this construct is a singleton. do not try to instatiate it, as it + will not work :) + + Example: + Terminator + """ + __slots__ = [] + def _parse(self, stream, context): + if stream.read(1): + raise TerminatorError("expected end of stream") + def _build(self, obj, stream, context): + assert obj is None + def _sizeof(self, context): + return 0 +Terminator = Terminator(None) + + + + + + + + + + + + + + + + + + + diff --git a/elftools/construct/debug.py b/elftools/construct/debug.py new file mode 100644 index 0000000..47e47c6 --- /dev/null +++ b/elftools/construct/debug.py @@ -0,0 +1,160 @@ +""" +Debugging utilities for constructs +""" +import sys +import traceback +import pdb +import inspect +from core import Construct, Subconstruct +from lib import HexString, Container, ListContainer, AttrDict + + +class Probe(Construct): + """ + A probe: dumps the context, stack frames, and stream content to the screen + to aid the debugging process. + See also Debugger. + + Parameters: + * name - the display name + * show_stream - whether or not to show stream contents. default is True. + the stream must be seekable. + * show_context - whether or not to show the context. default is True. + * show_stack - whether or not to show the upper stack frames. default + is True. + * stream_lookahead - the number of bytes to dump when show_stack is set. + default is 100. + + Example: + Struct("foo", + UBInt8("a"), + Probe("between a and b"), + UBInt8("b"), + ) + """ + __slots__ = [ + "printname", "show_stream", "show_context", "show_stack", + "stream_lookahead" + ] + counter = 0 + + def __init__(self, name = None, show_stream = True, + show_context = True, show_stack = True, + stream_lookahead = 100): + Construct.__init__(self, None) + if name is None: + Probe.counter += 1 + name = "" % (Probe.counter,) + self.printname = name + self.show_stream = show_stream + self.show_context = show_context + self.show_stack = show_stack + self.stream_lookahead = stream_lookahead + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self.printname) + def _parse(self, stream, context): + self.printout(stream, context) + def _build(self, obj, stream, context): + self.printout(stream, context) + def _sizeof(self, context): + return 0 + + def printout(self, stream, context): + obj = Container() + if self.show_stream: + obj.stream_position = stream.tell() + follows = stream.read(self.stream_lookahead) + if not follows: + obj.following_stream_data = "EOF reached" + else: + stream.seek(-len(follows), 1) + obj.following_stream_data = HexString(follows) + print + + if self.show_context: + obj.context = context + + if self.show_stack: + obj.stack = ListContainer() + frames = [s[0] for s in inspect.stack()][1:-1] + frames.reverse() + for f in frames: + a = AttrDict() + a.__update__(f.f_locals) + obj.stack.append(a) + + print "=" * 80 + print "Probe", self.printname + print obj + print "=" * 80 + +class Debugger(Subconstruct): + """ + A pdb-based debugger. When an exception occurs in the subcon, a debugger + will appear and allow you to debug the error (and even fix on-the-fly). + + Parameters: + * subcon - the subcon to debug + + Example: + Debugger( + Enum(UBInt8("foo"), + a = 1, + b = 2, + c = 3 + ) + ) + """ + __slots__ = ["retval"] + def _parse(self, stream, context): + try: + return self.subcon._parse(stream, context) + except Exception: + self.retval = NotImplemented + self.handle_exc("(you can set the value of 'self.retval', " + "which will be returned)") + if self.retval is NotImplemented: + raise + else: + return self.retval + def _build(self, obj, stream, context): + try: + self.subcon._build(obj, stream, context) + except Exception: + self.handle_exc() + def handle_exc(self, msg = None): + print "=" * 80 + print "Debugging exception of %s:" % (self.subcon,) + print "".join(traceback.format_exception(*sys.exc_info())[1:]) + if msg: + print msg + pdb.post_mortem(sys.exc_info()[2]) + print "=" * 80 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/elftools/construct/lib/__init__.py b/elftools/construct/lib/__init__.py new file mode 100644 index 0000000..23f0820 --- /dev/null +++ b/elftools/construct/lib/__init__.py @@ -0,0 +1,10 @@ +from binary import int_to_bin, bin_to_int, swap_bytes, encode_bin, decode_bin +from bitstream import BitStreamReader, BitStreamWriter +from container import (Container, AttrDict, FlagsContainer, + ListContainer, LazyContainer) +from hex import HexString, hexdump +from utils import Packer, StringIO + + + + diff --git a/elftools/construct/lib/binary.py b/elftools/construct/lib/binary.py new file mode 100644 index 0000000..971882e --- /dev/null +++ b/elftools/construct/lib/binary.py @@ -0,0 +1,61 @@ +def int_to_bin(number, width = 32): + if number < 0: + number += 1 << width + i = width - 1 + bits = ["\x00"] * width + while number and i >= 0: + bits[i] = "\x00\x01"[number & 1] + number >>= 1 + i -= 1 + return "".join(bits) + +_bit_values = {"\x00" : 0, "\x01" : 1, "0" : 0, "1" : 1} +def bin_to_int(bits, signed = False): + number = 0 + bias = 0 + if signed and _bit_values[bits[0]] == 1: + bits = bits[1:] + bias = 1 << len(bits) + for b in bits: + number <<= 1 + number |= _bit_values[b] + return number - bias + +def swap_bytes(bits, bytesize = 8): + i = 0 + l = len(bits) + output = [""] * ((l // bytesize) + 1) + j = len(output) - 1 + while i < l: + output[j] = bits[i : i + bytesize] + i += bytesize + j -= 1 + return "".join(output) + +_char_to_bin = {} +_bin_to_char = {} +for i in range(256): + ch = chr(i) + bin = int_to_bin(i, 8) + _char_to_bin[ch] = bin + _bin_to_char[bin] = ch + _bin_to_char[bin] = ch + +def encode_bin(data): + return "".join(_char_to_bin[ch] for ch in data) + +def decode_bin(data): + assert len(data) & 7 == 0, "data length must be a multiple of 8" + i = 0 + j = 0 + l = len(data) // 8 + chars = [""] * l + while j < l: + chars[j] = _bin_to_char[data[i:i+8]] + i += 8 + j += 1 + return "".join(chars) + + + + diff --git a/elftools/construct/lib/bitstream.py b/elftools/construct/lib/bitstream.py new file mode 100644 index 0000000..ff56be6 --- /dev/null +++ b/elftools/construct/lib/bitstream.py @@ -0,0 +1,80 @@ +from binary import encode_bin, decode_bin + + +class BitStreamReader(object): + __slots__ = ["substream", "buffer", "total_size"] + def __init__(self, substream): + self.substream = substream + self.total_size = 0 + self.buffer = "" + def close(self): + if self.total_size % 8 != 0: + raise ValueError("total size of read data must be a multiple of 8", + self.total_size) + def tell(self): + return self.substream.tell() + def seek(self, pos, whence = 0): + self.buffer = "" + self.total_size = 0 + self.substream.seek(pos, whence) + def read(self, count): + assert count >= 0 + l = len(self.buffer) + if count == 0: + data = "" + elif count <= l: + data = self.buffer[:count] + self.buffer = self.buffer[count:] + else: + data = self.buffer + count -= l + bytes = count // 8 + if count & 7: + bytes += 1 + buf = encode_bin(self.substream.read(bytes)) + data += buf[:count] + self.buffer = buf[count:] + self.total_size += len(data) + return data + + +class BitStreamWriter(object): + __slots__ = ["substream", "buffer", "pos"] + def __init__(self, substream): + self.substream = substream + self.buffer = [] + self.pos = 0 + def close(self): + self.flush() + def flush(self): + bytes = decode_bin("".join(self.buffer)) + self.substream.write(bytes) + self.buffer = [] + self.pos = 0 + def tell(self): + return self.substream.tell() + self.pos // 8 + def seek(self, pos, whence = 0): + self.flush() + self.substream.seek(pos, whence) + def write(self, data): + if not data: + return + if type(data) is not str: + raise TypeError("data must be a string, not %r" % (type(data),)) + self.buffer.append(data) + + + + + + + + + + + + + + + + diff --git a/elftools/construct/lib/container.py b/elftools/construct/lib/container.py new file mode 100644 index 0000000..66f8ab4 --- /dev/null +++ b/elftools/construct/lib/container.py @@ -0,0 +1,258 @@ +def recursion_lock(retval, lock_name = "__recursion_lock__"): + def decorator(func): + def wrapper(self, *args, **kw): + if getattr(self, lock_name, False): + return retval + setattr(self, lock_name, True) + try: + return func(self, *args, **kw) + finally: + setattr(self, lock_name, False) + wrapper.__name__ = func.__name__ + return wrapper + return decorator + +class Container(object): + """ + A generic container of attributes + """ + __slots__ = ["__dict__", "__attrs__"] + def __init__(self, **kw): + self.__dict__.update(kw) + object.__setattr__(self, "__attrs__", kw.keys()) + + def __eq__(self, other): + try: + return self.__dict__ == other.__dict__ + except AttributeError: + return False + def __ne__(self, other): + return not (self == other) + + def __delattr__(self, name): + object.__delattr__(self, name) + self.__attrs__.remove(name) + def __setattr__(self, name, value): + d = self.__dict__ + if name not in d: + self.__attrs__.append(name) + d[name] = value + def __getitem__(self, name): + return self.__dict__[name] + def __delitem__(self, name): + self.__delattr__(name) + def __setitem__(self, name, value): + self.__setattr__(name, value) + def __update__(self, obj): + for name in obj.__attrs__: + self[name] = obj[name] + def __copy__(self): + new = self.__class__() + new.__attrs__ = self.__attrs__[:] + new.__dict__ = self.__dict__.copy() + return new + + @recursion_lock("<...>") + def __repr__(self): + attrs = sorted("%s = %r" % (k, v) + for k, v in self.__dict__.iteritems() + if not k.startswith("_")) + return "%s(%s)" % (self.__class__.__name__, ", ".join(attrs)) + def __str__(self): + return self.__pretty_str__() + @recursion_lock("<...>") + def __pretty_str__(self, nesting = 1, indentation = " "): + attrs = [] + ind = indentation * nesting + for k in self.__attrs__: + v = self.__dict__[k] + if not k.startswith("_"): + text = [ind, k, " = "] + if hasattr(v, "__pretty_str__"): + text.append(v.__pretty_str__(nesting + 1, indentation)) + else: + text.append(repr(v)) + attrs.append("".join(text)) + if not attrs: + return "%s()" % (self.__class__.__name__,) + attrs.insert(0, self.__class__.__name__ + ":") + return "\n".join(attrs) + +class FlagsContainer(Container): + """ + A container providing pretty-printing for flags. Only set flags are + displayed. + """ + def __pretty_str__(self, nesting = 1, indentation = " "): + attrs = [] + ind = indentation * nesting + for k in self.__attrs__: + v = self.__dict__[k] + if not k.startswith("_") and v: + attrs.append(ind + k) + if not attrs: + return "%s()" % (self.__class__.__name__,) + attrs.insert(0, self.__class__.__name__+ ":") + return "\n".join(attrs) + +class ListContainer(list): + """ + A container for lists + """ + __slots__ = ["__recursion_lock__"] + def __str__(self): + return self.__pretty_str__() + @recursion_lock("[...]") + def __pretty_str__(self, nesting = 1, indentation = " "): + if not self: + return "[]" + ind = indentation * nesting + lines = ["["] + for elem in self: + lines.append("\n") + lines.append(ind) + if hasattr(elem, "__pretty_str__"): + lines.append(elem.__pretty_str__(nesting + 1, indentation)) + else: + lines.append(repr(elem)) + lines.append("\n") + lines.append(indentation * (nesting - 1)) + lines.append("]") + return "".join(lines) + +class AttrDict(object): + """ + A dictionary that can be accessed both using indexing and attributes, + i.e., + x = AttrDict() + x.foo = 5 + print x["foo"] + """ + __slots__ = ["__dict__"] + def __init__(self, **kw): + self.__dict__ = kw + def __contains__(self, key): + return key in self.__dict__ + def __nonzero__(self): + return bool(self.__dict__) + def __repr__(self): + return repr(self.__dict__) + def __str__(self): + return self.__pretty_str__() + def __pretty_str__(self, nesting = 1, indentation = " "): + if not self: + return "{}" + text = ["{\n"] + ind = nesting * indentation + for k in sorted(self.__dict__.keys()): + v = self.__dict__[k] + text.append(ind) + text.append(repr(k)) + text.append(" : ") + if hasattr(v, "__pretty_str__"): + try: + text.append(v.__pretty_str__(nesting+1, indentation)) + except Exception: + text.append(repr(v)) + else: + text.append(repr(v)) + text.append("\n") + text.append((nesting-1) * indentation) + text.append("}") + return "".join(text) + def __delitem__(self, key): + del self.__dict__[key] + def __getitem__(self, key): + return self.__dict__[key] + def __setitem__(self, key, value): + self.__dict__[key] = value + def __copy__(self): + new = self.__class__() + new.__dict__ = self.__dict__.copy() + return new + def __update__(self, other): + if isinstance(other, dict): + self.__dict__.update(other) + else: + self.__dict__.update(other.__dict__) + +class LazyContainer(object): + __slots__ = ["subcon", "stream", "pos", "context", "_value"] + def __init__(self, subcon, stream, pos, context): + self.subcon = subcon + self.stream = stream + self.pos = pos + self.context = context + self._value = NotImplemented + def __eq__(self, other): + try: + return self._value == other._value + except AttributeError: + return False + def __ne__(self, other): + return not (self == other) + def __str__(self): + return self.__pretty_str__() + def __pretty_str__(self, nesting = 1, indentation = " "): + if self._value is NotImplemented: + text = "" + elif hasattr(self._value, "__pretty_str__"): + text = self._value.__pretty_str__(nesting, indentation) + else: + text = repr(self._value) + return "%s: %s" % (self.__class__.__name__, text) + def read(self): + self.stream.seek(self.pos) + return self.subcon._parse(self.stream, self.context) + def dispose(self): + self.subcon = None + self.stream = None + self.context = None + self.pos = None + def _get_value(self): + if self._value is NotImplemented: + self._value = self.read() + return self._value + value = property(_get_value) + has_value = property(lambda self: self._value is not NotImplemented) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/elftools/construct/lib/hex.py b/elftools/construct/lib/hex.py new file mode 100644 index 0000000..e392bd3 --- /dev/null +++ b/elftools/construct/lib/hex.py @@ -0,0 +1,34 @@ +_printable = dict((chr(i), ".") for i in range(256)) +_printable.update((chr(i), chr(i)) for i in range(32, 128)) + +def hexdump(data, linesize = 16): + prettylines = [] + if len(data) < 65536: + fmt = "%%04X %%-%ds %%s" + else: + fmt = "%%08X %%-%ds %%s" + fmt = fmt % (3 * linesize - 1,) + for i in xrange(0, len(data), linesize): + line = data[i : i + linesize] + hextext = " ".join(b.encode("hex") for b in line) + rawtext = "".join(_printable[b] for b in line) + prettylines.append(fmt % (i, hextext, rawtext)) + return prettylines + +class HexString(str): + """ + represents a string that will be hex-dumped (only via __pretty_str__). + this class derives of str, and behaves just like a normal string in all + other contexts. + """ + def __init__(self, data, linesize = 16): + str.__init__(self, data) + self.linesize = linesize + def __new__(cls, data, *args, **kwargs): + return str.__new__(cls, data) + def __pretty_str__(self, nesting = 1, indentation = " "): + sep = "\n" + indentation * nesting + return sep + sep.join(hexdump(self)) + + + diff --git a/elftools/construct/lib/utils.py b/elftools/construct/lib/utils.py new file mode 100644 index 0000000..968dc26 --- /dev/null +++ b/elftools/construct/lib/utils.py @@ -0,0 +1,22 @@ +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + + +try: + from struct import Struct as Packer +except ImportError: + from struct import pack, unpack, calcsize + class Packer(object): + __slots__ = ["format", "size"] + def __init__(self, format): + self.format = format + self.size = calcsize(format) + def pack(self, *args): + return pack(self.format, *args) + def unpack(self, data): + return unpack(self.format, data) + + + diff --git a/elftools/construct/macros.py b/elftools/construct/macros.py new file mode 100644 index 0000000..35e689a --- /dev/null +++ b/elftools/construct/macros.py @@ -0,0 +1,514 @@ +from lib import BitStreamReader, BitStreamWriter, encode_bin, decode_bin +from core import * +from adapters import * + + +#=============================================================================== +# fields +#=============================================================================== +def Field(name, length): + """a field + * name - the name of the field + * length - the length of the field. the length can be either an integer + (StaticField), or a function that takes the context as an argument and + returns the length (MetaField) + """ + if callable(length): + return MetaField(name, length) + else: + return StaticField(name, length) + +def BitField(name, length, swapped = False, signed = False, bytesize = 8): + """a bit field; must be enclosed in a BitStruct + * name - the name of the field + * length - the length of the field in bits. the length can be either + an integer, or a function that takes the context as an argument and + returns the length + * swapped - whether the value is byte-swapped (little endian). the + default is False. + * signed - whether the value of the bitfield is a signed integer. the + default is False. + * bytesize - the number of bits in a byte (used for byte-swapping). the + default is 8. + """ + return BitIntegerAdapter(Field(name, length), + length, + swapped = swapped, + signed = signed, + bytesize = bytesize + ) + +def Padding(length, pattern = "\x00", strict = False): + r"""a padding field (value is discarded) + * length - the length of the field. the length can be either an integer, + or a function that takes the context as an argument and returns the + length + * pattern - the padding pattern (character) to use. default is "\x00" + * strict - whether or not to raise an exception is the actual padding + pattern mismatches the desired pattern. default is False. + """ + return PaddingAdapter(Field(None, length), + pattern = pattern, + strict = strict, + ) + +def Flag(name, truth = 1, falsehood = 0, default = False): + """a flag field (True or False) + * name - the name of the field + * truth - the numeric value of truth. the default is 1. + * falsehood - the numeric value of falsehood. the default is 0. + * default - the default value to assume, when the value is neither + `truth` nor `falsehood`. the default is False. + """ + return SymmetricMapping(Field(name, 1), + {True : chr(truth), False : chr(falsehood)}, + default = default, + ) + +#=============================================================================== +# field shortcuts +#=============================================================================== +def Bit(name): + """a 1-bit BitField; must be enclosed in a BitStruct""" + return BitField(name, 1) +def Nibble(name): + """a 4-bit BitField; must be enclosed in a BitStruct""" + return BitField(name, 4) +def Octet(name): + """an 8-bit BitField; must be enclosed in a BitStruct""" + return BitField(name, 8) + +def UBInt8(name): + """unsigned, big endian 8-bit integer""" + return FormatField(name, ">", "B") +def UBInt16(name): + """unsigned, big endian 16-bit integer""" + return FormatField(name, ">", "H") +def UBInt32(name): + """unsigned, big endian 32-bit integer""" + return FormatField(name, ">", "L") +def UBInt64(name): + """unsigned, big endian 64-bit integer""" + return FormatField(name, ">", "Q") + +def SBInt8(name): + """signed, big endian 8-bit integer""" + return FormatField(name, ">", "b") +def SBInt16(name): + """signed, big endian 16-bit integer""" + return FormatField(name, ">", "h") +def SBInt32(name): + """signed, big endian 32-bit integer""" + return FormatField(name, ">", "l") +def SBInt64(name): + """signed, big endian 64-bit integer""" + return FormatField(name, ">", "q") + +def ULInt8(name): + """unsigned, little endian 8-bit integer""" + return FormatField(name, "<", "B") +def ULInt16(name): + """unsigned, little endian 16-bit integer""" + return FormatField(name, "<", "H") +def ULInt32(name): + """unsigned, little endian 32-bit integer""" + return FormatField(name, "<", "L") +def ULInt64(name): + """unsigned, little endian 64-bit integer""" + return FormatField(name, "<", "Q") + +def SLInt8(name): + """signed, little endian 8-bit integer""" + return FormatField(name, "<", "b") +def SLInt16(name): + """signed, little endian 16-bit integer""" + return FormatField(name, "<", "h") +def SLInt32(name): + """signed, little endian 32-bit integer""" + return FormatField(name, "<", "l") +def SLInt64(name): + """signed, little endian 64-bit integer""" + return FormatField(name, "<", "q") + +def UNInt8(name): + """unsigned, native endianity 8-bit integer""" + return FormatField(name, "=", "B") +def UNInt16(name): + """unsigned, native endianity 16-bit integer""" + return FormatField(name, "=", "H") +def UNInt32(name): + """unsigned, native endianity 32-bit integer""" + return FormatField(name, "=", "L") +def UNInt64(name): + """unsigned, native endianity 64-bit integer""" + return FormatField(name, "=", "Q") + +def SNInt8(name): + """signed, native endianity 8-bit integer""" + return FormatField(name, "=", "b") +def SNInt16(name): + """signed, native endianity 16-bit integer""" + return FormatField(name, "=", "h") +def SNInt32(name): + """signed, native endianity 32-bit integer""" + return FormatField(name, "=", "l") +def SNInt64(name): + """signed, native endianity 64-bit integer""" + return FormatField(name, "=", "q") + +def BFloat32(name): + """big endian, 32-bit IEEE floating point number""" + return FormatField(name, ">", "f") +def LFloat32(name): + """little endian, 32-bit IEEE floating point number""" + return FormatField(name, "<", "f") +def NFloat32(name): + """native endianity, 32-bit IEEE floating point number""" + return FormatField(name, "=", "f") + +def BFloat64(name): + """big endian, 64-bit IEEE floating point number""" + return FormatField(name, ">", "d") +def LFloat64(name): + """little endian, 64-bit IEEE floating point number""" + return FormatField(name, "<", "d") +def NFloat64(name): + """native endianity, 64-bit IEEE floating point number""" + return FormatField(name, "=", "d") + + +#=============================================================================== +# arrays +#=============================================================================== +def Array(count, subcon): + """array of subcon repeated count times. + * subcon - the subcon. + * count - an integer, or a function taking the context as an argument, + returning the count + """ + if callable(count): + con = MetaArray(count, subcon) + else: + con = MetaArray(lambda ctx: count, subcon) + con._clear_flag(con.FLAG_DYNAMIC) + return con + +def PrefixedArray(subcon, length_field = UBInt8("length")): + """an array prefixed by a length field. + * subcon - the subcon to be repeated + * length_field - an integer construct + """ + return LengthValueAdapter( + Sequence(subcon.name, + length_field, + Array(lambda ctx: ctx[length_field.name], subcon), + nested = False + ) + ) + +def OpenRange(mincount, subcon): + from sys import maxint + return Range(mincount, maxint, subcon) + +def GreedyRange(subcon): + """an open range (1 or more times) of repeated subcon. + * subcon - the subcon to repeat""" + return OpenRange(1, subcon) + +def OptionalGreedyRange(subcon): + """an open range (0 or more times) of repeated subcon. + * subcon - the subcon to repeat""" + return OpenRange(0, subcon) + + +#=============================================================================== +# subconstructs +#=============================================================================== +def Optional(subcon): + """an optional construct. if parsing fails, returns None. + * subcon - the subcon to optionally parse or build + """ + return Select(subcon.name, subcon, Pass) + +def Bitwise(subcon): + """converts the stream to bits, and passes the bitstream to subcon + * subcon - a bitwise construct (usually BitField) + """ + # subcons larger than MAX_BUFFER will be wrapped by Restream instead + # of Buffered. implementation details, don't stick your nose :) + MAX_BUFFER = 1024 * 8 + def resizer(length): + if length & 7: + raise SizeofError("size must be a multiple of 8", length) + return length >> 3 + if not subcon._is_flag(subcon.FLAG_DYNAMIC) and subcon.sizeof() < MAX_BUFFER: + con = Buffered(subcon, + encoder = decode_bin, + decoder = encode_bin, + resizer = resizer + ) + else: + con = Restream(subcon, + stream_reader = BitStreamReader, + stream_writer = BitStreamWriter, + resizer = resizer) + return con + +def Aligned(subcon, modulus = 4, pattern = "\x00"): + r"""aligns subcon to modulus boundary using padding pattern + * subcon - the subcon to align + * modulus - the modulus boundary (default is 4) + * pattern - the padding pattern (default is \x00) + """ + if modulus < 2: + raise ValueError("modulus must be >= 2", modulus) + if modulus in (2, 4, 8, 16, 32, 64, 128, 256, 512, 1024): + def padlength(ctx): + m1 = modulus - 1 + return (modulus - (subcon._sizeof(ctx) & m1)) & m1 + else: + def padlength(ctx): + return (modulus - (subcon._sizeof(ctx) % modulus)) % modulus + return IndexingAdapter( + Sequence(subcon.name, + subcon, + Padding(padlength, pattern = pattern), + nested = False, + ), + 0 + ) + +def Embedded(subcon): + """embeds a struct into the enclosing struct. + * subcon - the struct to embed + """ + return Reconfig(subcon.name, subcon, subcon.FLAG_EMBED) + +def Rename(newname, subcon): + """renames an existing construct + * newname - the new name + * subcon - the subcon to rename + """ + return Reconfig(newname, subcon) + +def Alias(newname, oldname): + """creates an alias for an existing element in a struct + * newname - the new name + * oldname - the name of an existing element + """ + return Value(newname, lambda ctx: ctx[oldname]) + + +#=============================================================================== +# mapping +#=============================================================================== +def SymmetricMapping(subcon, mapping, default = NotImplemented): + """defines a symmetrical mapping: a->b, b->a. + * subcon - the subcon to map + * mapping - the encoding mapping (a dict); the decoding mapping is + achieved by reversing this mapping + * default - the default value to use when no mapping is found. if no + default value is given, and exception is raised. setting to Pass would + return the value "as is" (unmapped) + """ + reversed_mapping = dict((v, k) for k, v in mapping.iteritems()) + return MappingAdapter(subcon, + encoding = mapping, + decoding = reversed_mapping, + encdefault = default, + decdefault = default, + ) + +def Enum(subcon, **kw): + """a set of named values mapping. + * subcon - the subcon to map + * kw - keyword arguments which serve as the encoding mapping + * _default_ - an optional, keyword-only argument that specifies the + default value to use when the mapping is undefined. if not given, + and exception is raised when the mapping is undefined. use `Pass` to + pass the unmapped value as-is + """ + return SymmetricMapping(subcon, kw, kw.pop("_default_", NotImplemented)) + +def FlagsEnum(subcon, **kw): + """a set of flag values mapping. + * subcon - the subcon to map + * kw - keyword arguments which serve as the encoding mapping + """ + return FlagsAdapter(subcon, kw) + + +#=============================================================================== +# structs +#=============================================================================== +def AlignedStruct(name, *subcons, **kw): + """a struct of aligned fields + * name - the name of the struct + * subcons - the subcons that make up this structure + * kw - keyword arguments to pass to Aligned: 'modulus' and 'pattern' + """ + return Struct(name, *(Aligned(sc, **kw) for sc in subcons)) + +def BitStruct(name, *subcons): + """a struct of bitwise fields + * name - the name of the struct + * subcons - the subcons that make up this structure + """ + return Bitwise(Struct(name, *subcons)) + +def EmbeddedBitStruct(*subcons): + """an embedded BitStruct. no name is necessary. + * subcons - the subcons that make up this structure + """ + return Bitwise(Embedded(Struct(None, *subcons))) + +#=============================================================================== +# strings +#=============================================================================== +def String(name, length, encoding = None, padchar = None, + paddir = "right", trimdir = "right"): + """a fixed-length, optionally padded string of characters + * name - the name of the field + * length - the length (integer) + * encoding - the encoding to use (e.g., "utf8"), or None, for raw bytes. + default is None + * padchar - the padding character (commonly "\x00"), or None to + disable padding. default is None + * paddir - the direction where padding is placed ("right", "left", or + "center"). the default is "right". this argument is meaningless if + padchar is None. + * trimdir - the direction where trimming will take place ("right" or + "left"). the default is "right". trimming is only meaningful for + building, when the given string is too long. this argument is + meaningless if padchar is None. + """ + con = StringAdapter(Field(name, length), encoding = encoding) + if padchar is not None: + con = PaddedStringAdapter(con, + padchar = padchar, + paddir = paddir, + trimdir = trimdir + ) + return con + +def PascalString(name, length_field = UBInt8("length"), encoding = None): + """a string prefixed with a length field. the data must directly follow + the length field. + * name - the name of the + * length_field - a numeric construct (i.e., UBInt8) that holds the + length. default is an unsigned, 8-bit integer field. note that this + argument must pass an instance of a construct, not a class + (`UBInt8("length")` rather than `UBInt8`) + * encoding - the encoding to use (e.g., "utf8"), or None, for raw bytes. + default is None + """ + return StringAdapter( + LengthValueAdapter( + Sequence(name, + length_field, + Field("data", lambda ctx: ctx[length_field.name]), + ) + ), + encoding = encoding, + ) + +def CString(name, terminators = "\x00", encoding = None, + char_field = Field(None, 1)): + r"""a c-style string (string terminated by a terminator char) + * name - the name fo the string + * terminators - a sequence of terminator chars. default is "\x00". + * encoding - the encoding to use (e.g., "utf8"), or None, for raw bytes. + default is None + * char_field - the construct that represents a single character. default + is a one-byte character. note that this argument must be an instance + of a construct, not a construct class (`Field("char", 1)` rather than + `Field`) + """ + return Rename(name, + CStringAdapter( + RepeatUntil(lambda obj, ctx: obj in terminators, + char_field, + ), + terminators = terminators, + encoding = encoding, + ) + ) + + +#=============================================================================== +# conditional +#=============================================================================== +def IfThenElse(name, predicate, then_subcon, else_subcon): + """an if-then-else conditional construct: if the predicate indicates True, + `then_subcon` will be used; otherwise `else_subcon` + * name - the name of the construct + * predicate - a function taking the context as an argument and returning + True or False + * then_subcon - the subcon that will be used if the predicate returns True + * else_subcon - the subcon that will be used if the predicate returns False + """ + return Switch(name, lambda ctx: bool(predicate(ctx)), + { + True : then_subcon, + False : else_subcon, + } + ) + +def If(predicate, subcon, elsevalue = None): + """an if-then conditional construct: if the predicate indicates True, + subcon will be used; otherwise, `elsevalue` will be returned instead. + * predicate - a function taking the context as an argument and returning + True or False + * subcon - the subcon that will be used if the predicate returns True + * elsevalue - the value that will be used should the predicate return False. + by default this value is None. + """ + return IfThenElse(subcon.name, + predicate, + subcon, + Value("elsevalue", lambda ctx: elsevalue) + ) + + +#=============================================================================== +# misc +#=============================================================================== +def OnDemandPointer(offsetfunc, subcon, force_build = True): + """an on-demand pointer. + * offsetfunc - a function taking the context as an argument and returning + the absolute stream position + * subcon - the subcon that will be parsed from the `offsetfunc()` stream + position on demand + * force_build - see OnDemand. by default True. + """ + return OnDemand(Pointer(offsetfunc, subcon), + advance_stream = False, + force_build = force_build + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/elftools/construct/text.py b/elftools/construct/text.py new file mode 100644 index 0000000..dee47a1 --- /dev/null +++ b/elftools/construct/text.py @@ -0,0 +1,286 @@ +from core import * +from adapters import * +from macros import * + + +#=============================================================================== +# exceptions +#=============================================================================== +class QuotedStringError(ConstructError): + __slots__ = [] + + +#=============================================================================== +# constructs +#=============================================================================== +class QuotedString(Construct): + r""" + A quoted string (begins with an opening-quote, terminated by a + closing-quote, which may be escaped by an escape character) + + Parameters: + * name - the name of the field + * start_quote - the opening quote character. default is '"' + * end_quote - the closing quote character. default is '"' + * esc_char - the escape character, or None to disable escaping. defualt + is "\" (backslash) + * encoding - the character encoding (e.g., "utf8"), or None to return + raw bytes. defualt is None. + * allow_eof - whether to allow EOF before the closing quote is matched. + if False, an exception will be raised when EOF is reached by the closing + quote is missing. default is False. + + Example: + QuotedString("foo", start_quote = "{", end_quote = "}", esc_char = None) + """ + __slots__ = [ + "start_quote", "end_quote", "char", "esc_char", "encoding", + "allow_eof" + ] + def __init__(self, name, start_quote = '"', end_quote = None, + esc_char = '\\', encoding = None, allow_eof = False): + Construct.__init__(self, name) + if end_quote is None: + end_quote = start_quote + self.start_quote = Literal(start_quote) + self.char = Char("char") + self.end_quote = end_quote + self.esc_char = esc_char + self.encoding = encoding + self.allow_eof = allow_eof + + def _parse(self, stream, context): + self.start_quote._parse(stream, context) + text = [] + escaped = False + try: + while True: + ch = self.char._parse(stream, context) + if ch == self.esc_char: + if escaped: + text.append(ch) + escaped = False + else: + escaped = True + elif ch == self.end_quote and not escaped: + break + else: + text.append(ch) + escaped = False + except FieldError: + if not self.allow_eof: + raise + text = "".join(text) + if self.encoding is not None: + text = text.decode(self.encoding) + return text + + def _build(self, obj, stream, context): + self.start_quote._build(None, stream, context) + if self.encoding: + obj = obj.encode(self.encoding) + for ch in obj: + if ch == self.esc_char: + self.char._build(self.esc_char, stream, context) + elif ch == self.end_quote: + if self.esc_char is None: + raise QuotedStringError("found ending quote in data, " + "but no escape char defined", ch) + else: + self.char._build(self.esc_char, stream, context) + self.char._build(ch, stream, context) + self.char._build(self.end_quote, stream, context) + + def _sizeof(self, context): + raise SizeofError("can't calculate size") + + +#=============================================================================== +# macros +#=============================================================================== +class WhitespaceAdapter(Adapter): + """ + Adapter for whitespace sequences; do not use directly. + See Whitespace. + + Parameters: + * subcon - the subcon to adapt + * build_char - the character used for encoding (building) + """ + __slots__ = ["build_char"] + def __init__(self, subcon, build_char): + Adapter.__init__(self, subcon) + self.build_char = build_char + def _encode(self, obj, context): + return self.build_char + def _decode(self, obj, context): + return None + +def Whitespace(charset = " \t", optional = True): + """whitespace (space that is ignored between tokens). when building, the + first character of the charset is used. + * charset - the set of characters that are considered whitespace. default + is space and tab. + * optional - whether or not whitespace is optional. default is True. + """ + con = CharOf(None, charset) + if optional: + con = OptionalGreedyRange(con) + else: + con = GreedyRange(con) + return WhitespaceAdapter(con, build_char = charset[0]) + +def Literal(text): + """matches a literal string in the text + * text - the text (string) to match + """ + return ConstAdapter(Field(None, len(text)), text) + +def Char(name): + """a one-byte character""" + return Field(name, 1) + +def CharOf(name, charset): + """matches only characters of a given charset + * name - the name of the field + * charset - the set of valid characters + """ + return OneOf(Char(name), charset) + +def CharNoneOf(name, charset): + """matches only characters that do not belong to a given charset + * name - the name of the field + * charset - the set of invalid characters + """ + return NoneOf(Char(name), charset) + +def Alpha(name): + """a letter character (A-Z, a-z)""" + return CharOf(name, set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')) + +def Digit(name): + """a digit character (0-9)""" + return CharOf(name, set('0123456789')) + +def AlphaDigit(name): + """an alphanumeric character (A-Z, a-z, 0-9)""" + return CharOf(name, set("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")) + +def BinDigit(name): + """a binary digit (0-1)""" + return CharOf(name, set('01')) + +def HexDigit(name): + """a hexadecimal digit (0-9, A-F, a-f)""" + return CharOf(name, set('0123456789abcdefABCDEF')) + +def Word(name): + """a sequence of letters""" + return StringAdapter(GreedyRange(Alpha(name))) + +class TextualIntAdapter(Adapter): + """ + Adapter for textual integers + + Parameters: + * subcon - the subcon to adapt + * radix - the base of the integer (decimal, hexadecimal, binary, ...) + * digits - the sequence of digits of that radix + """ + __slots__ = ["radix", "digits"] + def __init__(self, subcon, radix = 10, digits = "0123456789abcdef"): + Adapter.__init__(self, subcon) + if radix > len(digits): + raise ValueError("not enough digits for radix %d" % (radix,)) + self.radix = radix + self.digits = digits + def _encode(self, obj, context): + chars = [] + if obj < 0: + chars.append("-") + n = -obj + else: + n = obj + r = self.radix + digs = self.digits + while n > 0: + n, d = divmod(n, r) + chars.append(digs[d]) + # obj2 = "".join(reversed(chars)) + # filler = digs[0] * (self._sizeof(context) - len(obj2)) + # return filler + obj2 + return "".join(reversed(chars)) + def _decode(self, obj, context): + return int("".join(obj), self.radix) + +def DecNumber(name): + """decimal number""" + return TextualIntAdapter(GreedyRange(Digit(name))) + +def BinNumber(name): + """binary number""" + return TextualIntAdapter(GreedyRange(Digit(name)), 2) + +def HexNumber(name): + """hexadecimal number""" + return TextualIntAdapter(GreedyRange(Digit(name)), 16) + +def StringUpto(name, charset): + """a string that stretches up to a terminator, or EOF. unlike CString, + StringUpto will no consume the terminator char. + * name - the name of the field + * charset - the set of terminator characters""" + return StringAdapter(OptionalGreedyRange(CharNoneOf(name, charset))) + +def Line(name): + r"""a textual line (up to "\n")""" + return StringUpto(name, "\n") + +class IdentifierAdapter(Adapter): + """ + Adapter for programmatic identifiers + + Parameters: + * subcon - the subcon to adapt + """ + def _encode(self, obj, context): + return obj[0], obj[1:] + def _decode(self, obj, context): + return obj[0] + "".join(obj[1]) + +def Identifier(name, + headset = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"), + tailset = set("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_") + ): + """a programmatic identifier (symbol). must start with a char of headset, + followed by a sequence of tailset characters + * name - the name of the field + * headset - charset for the first character. default is A-Z, a-z, and _ + * tailset - charset for the tail. default is A-Z, a-z, 0-9 and _ + """ + return IdentifierAdapter( + Sequence(name, + CharOf("head", headset), + OptionalGreedyRange(CharOf("tail", tailset)), + ) + ) + + + + + + + + + + + + + + + + + + + + diff --git a/elftools/elf/__init__.py b/elftools/elf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/elftools/elf/enums.py b/elftools/elf/enums.py new file mode 100644 index 0000000..e69de29 diff --git a/elftools/elf/structs.py b/elftools/elf/structs.py new file mode 100644 index 0000000..87725a8 --- /dev/null +++ b/elftools/elf/structs.py @@ -0,0 +1,37 @@ +from ..construct import ( + UBInt8, UBInt16, UBInt32, UBInt64, + ULInt8, ULInt16, ULInt32, ULInt64, + SBInt32, SLInt32, SBInt64, SLInt64, + Struct, Array, + ) + + +class ELFStructs(object): + def __init__(self, little_endian=True, elfclass=32): + assert elfclass == 32 or elfclass == 64 + self.little_endian = little_endian + self.elfclass = elfclass + self._create_structs() + + def _create_structs(self): + if self.little_endian: + self.Elf_byte = ULInt8 + self.Elf_half = ULInt16 + self.Elf_word = ULInt32 + self.Elf_addr = ULInt32 if self.elfclass == 32 else ULInt64 + self.Elf_offset = self.Elf_addr + self.Elf_sword = SLInt32 + self.Elf_xword = ULInt64 + self.Elf_sxword = SLInt64 + else: + self.Elf_byte = UBInt8 + self.Elf_half = UBInt16 + self.Elf_word = UBInt32 + self.Elf_addr = UBInt32 if self.elfclass == 32 else UBInt64 + self.Elf_offset = self.Elf_addr + self.Elf_sword = SBInt32 + self.Elf_xword = UBInt64 + self.Elf_sxword = SBInt64 + + self.Elf_Ehdr = Struct('Elf_Ehdr', + \ No newline at end of file diff --git a/z.py b/z.py new file mode 100644 index 0000000..22d4d03 --- /dev/null +++ b/z.py @@ -0,0 +1,2 @@ +from elftools.elf.structs import ELFStructs + -- 2.30.2