├── .gitignore ├── README ├── balanced_substring.py ├── bitpattern.py ├── cpu ├── __init__.py ├── arm │ ├── __init__.py │ ├── _test.py │ ├── decoder.py │ ├── functions.py │ ├── instruction.py │ ├── instructions │ │ ├── __init__.py │ │ ├── core.py │ │ └── index.txt │ ├── operand.py │ ├── status.py │ └── thread.py ├── memory.py └── pointers.py ├── data_table.py ├── doc ├── .gitignore ├── Makefile ├── _static │ └── flushRight.css ├── balanced_substring.rst ├── bitpattern.rst ├── conf.py ├── cpu.rst ├── cpu │ ├── arm.rst │ ├── arm │ │ ├── decoder.rst │ │ ├── functions.rst │ │ ├── instruction.rst │ │ ├── instructions.rst │ │ ├── instructions │ │ │ └── core.rst │ │ ├── operand.rst │ │ └── status.rst │ ├── memory.rst │ └── pointers.rst ├── data_table.rst ├── factory.rst ├── hexdump.rst ├── index.rst ├── list_diff.rst ├── macho.rst ├── macho │ ├── arch.rst │ ├── features.rst │ ├── loadcommands.rst │ ├── loadcommands │ │ ├── dyld_info.rst │ │ ├── dylib.rst │ │ ├── dysymtab.rst │ │ ├── encryption_info.rst │ │ ├── loadcommand.rst │ │ ├── segment.rst │ │ └── symtab.rst │ ├── macho.rst │ ├── sections.rst │ ├── sections │ │ ├── cfstring.rst │ │ ├── cstring.rst │ │ ├── objc.rst │ │ ├── objc │ │ │ ├── _abi1reader.rst │ │ │ ├── _abi2reader.rst │ │ │ ├── catlist.rst │ │ │ ├── classlist.rst │ │ │ └── protolist.rst │ │ ├── section.rst │ │ └── symbol_ptr.rst │ ├── sharedcache.rst │ ├── symbols.rst │ ├── utilities.rst │ └── vmaddr.rst ├── monkey_patching.rst ├── objc.rst ├── objc │ ├── category.rst │ ├── class_.rst │ ├── classlike.rst │ ├── ivar.rst │ ├── method.rst │ ├── property.rst │ └── protocol.rst ├── objctype2.rst ├── objctype2 │ ├── parser.rst │ └── types.rst ├── sorted_list.rst ├── sym.rst ├── symbolic.rst └── symbolic │ ├── expression.rst │ ├── simplify.rst │ └── simplify │ ├── compare.rst │ ├── distributive.rst │ ├── fold_constant.rst │ ├── recursive.rst │ ├── semigroup.rst │ └── utility.rst ├── factory.py ├── hexdump.py ├── list_diff.py ├── macho ├── __init__.py ├── arch.py ├── features.py ├── loadcommands │ ├── __init__.py │ ├── dyld_info.py │ ├── dylib.py │ ├── dysymtab.py │ ├── encryption_info.py │ ├── loadcommand.py │ ├── segment.py │ └── symtab.py ├── loader.py ├── macho.py ├── sections │ ├── __init__.py │ ├── cfstring.py │ ├── cstring.py │ ├── objc │ │ ├── __init__.py │ │ ├── _abi1reader.py │ │ ├── _abi2reader.py │ │ ├── catlist.py │ │ ├── classlist.py │ │ └── protolist.py │ ├── section.py │ └── symbol_ptr.py ├── sharedcache.py ├── symbol.py ├── utilities.py └── vmaddr.py ├── monkey_patching.py ├── objc ├── __init__.py ├── category.py ├── class_.py ├── classlike.py ├── ivar.py ├── method.py ├── property.py └── protocol.py ├── objctype2 ├── __init__.py ├── parser.py └── types.py ├── sorted_list.py ├── sphinx_ext_superclass.py ├── sym.py └── symbolic ├── __init__.py ├── expression.py └── simplify ├── __init__.py ├── compare.py ├── distributive.py ├── fold_constant.py ├── recursive.py ├── semigroup.py └── utilities.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.pyc 3 | *.pyo 4 | .DS_Store 5 | test.py 6 | XcodeProject 7 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kennytm/EcaFretni/7d44a7ed9fe4920d4c443ea02b0e1f6be5aacd64/README -------------------------------------------------------------------------------- /balanced_substring.py: -------------------------------------------------------------------------------- 1 | # 2 | # balanced_substring.py ... Parse a balanced substring 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import re 19 | 20 | 21 | 22 | def balancedSubstring(string, index=0): 23 | ''' 24 | Skip a balanced substring from specified index, and return the next string 25 | index. 26 | 27 | This function implements a simplistic stack-based parenthesis parser. It can 28 | consume a substring forming one balanced group of parenthesis, or a quoted 29 | string 30 | 31 | >>> '(foo)bar'[:balancedSubstring('(foo)bar')] 32 | '(foo)' 33 | >>> 'foo(bar)'[:balancedSubstring('foo(bar)')] 34 | 'f' 35 | >>> '"foo"bar'[:balancedSubstring('"foo"bar')] 36 | '"foo"' 37 | 38 | A balanced substring means one of these: 39 | 40 | * a character 41 | * a string enclosed between a matching pair of parenthesis: ``(...)``, 42 | ``[...]`` and ``{...}`` 43 | * a quoted string: ``"..."``, ``'...'``, which can recognize the C-style 44 | escape character e.g. ``'o\\'clock'``. 45 | 46 | .. note:: 47 | 48 | This module is not designed for validation. The 3 different kinds of 49 | parenthesis are not distinguished. That means ``"[foo)"`` will be 50 | considered as balanced. 51 | 52 | The optional parameter *index* can be used to tokenize the string:: 53 | 54 | >>> balancedSubstring('(a)(bbb)c') 55 | 3 56 | >>> balancedSubstring('(a)(bbb)c', index=3) 57 | 8 58 | 59 | A number larger than the length of *string* will be returned on unbalanced 60 | paranthesis. 61 | 62 | ''' 63 | 64 | level = 0 65 | quote_mode = '' 66 | strlen = len(string) 67 | while strlen > index: 68 | c = string[index] 69 | if c == '\\' and quote_mode: 70 | index += 1 71 | elif c == quote_mode: 72 | quote_mode = '' 73 | if level <= 0: 74 | break 75 | 76 | elif not quote_mode: 77 | if c in "([{": 78 | level += 1 79 | elif c in ")]}": 80 | level -= 1 81 | elif c in "\"'": 82 | quote_mode = c 83 | 84 | if level <= 0 and not quote_mode: 85 | break 86 | 87 | index += 1 88 | 89 | return index+1 90 | 91 | 92 | _numberRe = re.compile('\d+') 93 | 94 | def numericSubstring(string, index=0): 95 | ''' 96 | Skip a numeric substring from specified index, return that number and the 97 | next string index. 98 | 99 | >>> numericSubstring('127foo') 100 | (127, 3) 101 | >>> numericSubstring('abc765def490', index=3) 102 | (765, 6) 103 | 104 | It is expected that ``string[index]`` is a digit. If not, an exception may 105 | be raised. 106 | ''' 107 | 108 | m = _numberRe.match(string, index) 109 | return (int(m.group()), m.end()) 110 | 111 | 112 | 113 | if __name__ == '__main__': 114 | s = '(foo)bar[baz[bar]{bar}]' 115 | assert 5 == balancedSubstring(s) # (foo) 116 | assert 6 == balancedSubstring(s, 5) # b 117 | assert 7 == balancedSubstring(s, 6) # a 118 | assert 8 == balancedSubstring(s, 7) # r 119 | assert 23 == balancedSubstring(s, 8) # [baz[bar]{bar}] 120 | 121 | s = '"foo\\"bar("\')b"az\'' 122 | assert 11 == balancedSubstring(s) # "foo\"bar(" 123 | assert 18 == balancedSubstring(s,11) # ')b"az' 124 | 125 | s = '(((foo' 126 | assert balancedSubstring(s) > 6 127 | 128 | assert numericSubstring('127foo') == (127, 3) 129 | assert numericSubstring('abc765def490', index=3) == (765, 6) 130 | -------------------------------------------------------------------------------- /cpu/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py ... Initialization 3 | # Copyright (C) 2011 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | __all__ = ['arm', 'memory', 'pointers'] 19 | -------------------------------------------------------------------------------- /cpu/arm/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py ... Initialization 3 | # Copyright (C) 2011 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | __all__ = ['status', 'instruction', 'thread', 'instructions', 'functions'] 19 | -------------------------------------------------------------------------------- /cpu/arm/decoder.py: -------------------------------------------------------------------------------- 1 | # 2 | # decoder.py ... ARM instruction decoders 3 | # Copyright (C) 2011 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from bitpattern import BitPattern 20 | from functools import wraps 21 | from itertools import chain 22 | from cpu.arm.instruction import Instruction, Condition 23 | from cpu.arm.functions import COND_NONE 24 | 25 | _decoders = { 26 | (4,0):[], 27 | (2,1):[], 28 | (4,1):[], 29 | (2,3):[], 30 | (4,3):[], 31 | } 32 | 33 | class InstructionDecoderNotFoundError(Exception): 34 | '''This exception is raised when :class:`InstructionDecoder` fails to find a 35 | suitable decoder to decode a byte sequence.''' 36 | def __init__(self, encoding, length, instructionSet): 37 | fmt = "Cannot decode {2} instruction {0:#0{1}x} [{0:0{3}b}]." 38 | isetName = ('ARM', 'Thumb', 'Jazelle', 'Thumb-EE')[instructionSet] 39 | bitLength = length*8 if instructionSet else 28 40 | super().__init__(fmt.format(encoding, bitLength>>2, isetName, bitLength)) 41 | 42 | 43 | class InstructionDecoder(object): 44 | '''A decorator to convert a function into an instruction decoder. You should 45 | supply the instruction length, instruction set and bit pattern which the 46 | collection of instructions the decorated function can decode. The decorated 47 | function's signature must be:: 48 | 49 | def f(res: "Decoded struct", encoding, condition) -> Instruction: 50 | ... 51 | 52 | If the function cannot handle *res*, it should return ``None``. 53 | 54 | When this method is called with an ARM instruction, the condition code will 55 | *not* be included along the encoding, i.e. the *pattern* should only be 56 | 28-bit in size. If the decoder is handling an unconditional instruction 57 | (i.e. the condition code must always be 58 | :const:`~cpu.arm.functions.COND_NONE`), the user should supply ``True`` to 59 | the optional parameter ``unconditional``. 60 | 61 | .. attribute:: unconditional 62 | 63 | Whether this instruction does not have the condition field. This is 64 | applicable to ARM instruction decoders only. 65 | 66 | ''' 67 | 68 | @staticmethod 69 | def decoders(length, instructionSet): 70 | 'Get an iterable of decoders for a given instruction set and encoding length.' 71 | if instructionSet == 3: 72 | return chain(_decoders[(length, 3)], _decoders[(length, 1)]) 73 | else: 74 | return _decoders[(length, instructionSet)] 75 | 76 | 77 | @staticmethod 78 | def create(encoding, length, instructionSet, forceCondition=COND_NONE): 79 | '''Create an instruction using *encoding* with length *length* in 80 | *instructionSet*. 81 | 82 | If the instruction ought to carry a condition (e.g. due to the IT block) 83 | you could supply a valid condition code in *forceCondition*.''' 84 | 85 | isARM = instructionSet == 0 86 | cond = forceCondition 87 | if isARM and cond == COND_NONE: 88 | cond = encoding >> 28 89 | encoding &= 0xfffffff 90 | 91 | for decoder in InstructionDecoder.decoders(length, instructionSet): 92 | if isARM and decoder.unconditional != (cond == COND_NONE): 93 | continue 94 | retval = decoder(encoding, cond) 95 | if retval: 96 | retval.decoder = decoder 97 | break 98 | else: # pragma: no cover 99 | raise InstructionDecoderNotFoundError(encoding, length, instructionSet) 100 | retval = Instruction(encoding, length, instructionSet) 101 | 102 | if cond != COND_NONE: 103 | retval.condition = Condition(cond) 104 | return retval 105 | 106 | 107 | def __init__(self, length, instructionSet, pattern, unconditional=False): 108 | # safe check 109 | assert (instructionSet == 0 and length == 4 and len(pattern) == 28) \ 110 | or (instructionSet & 1 and length == 2 and len(pattern) == 16) \ 111 | or (instructionSet & 1 and length == 4 and len(pattern.replace(' ','')) == 32) 112 | 113 | self.pattern = pattern 114 | self.unconditional = unconditional 115 | self.length = length 116 | self.instrSet = instructionSet 117 | 118 | 119 | def __call__(self, f): 120 | unpacker = BitPattern(self.pattern).unpack 121 | 122 | @wraps(f) 123 | def decoder(encoding, condition): 124 | res = unpacker(encoding) 125 | return res and f(res, encoding, condition) 126 | 127 | decoder.unconditional = self.unconditional 128 | _decoders[(self.length, self.instrSet)].append(decoder) 129 | 130 | return decoder 131 | 132 | 133 | -------------------------------------------------------------------------------- /cpu/arm/functions.py: -------------------------------------------------------------------------------- 1 | # 2 | # functions.py ... ARM functions 3 | # Copyright (C) 2011 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | (SRTYPE_LSL, SRTYPE_LSR, SRTYPE_ASR, SRTYPE_ROR, SRTYPE_RRX) = range(5) 20 | (REG_SP, REG_LR, REG_PC) = (13, 14, 15) 21 | COND_NONE = 15 22 | 23 | def signed(notmask, x): 24 | '''Make *x* a signed number. *notmask* should be set to ``-1<>1: 29 | x += notmask 30 | return x 31 | 32 | 33 | 34 | def fixPCAddrBX(pcAddr): 35 | '''Fix *pcAddr* into a valid instruction address, with possibility of 36 | changing instruction set. Return a tuple of the fixed address and whether 37 | it will run in Thumb mode or not.''' 38 | 39 | if pcAddr & 1: 40 | return (pcAddr-1, True) 41 | else: 42 | return (pcAddr&~3, False) 43 | 44 | def fixPCAddrB(pcAddr, thumbMode): 45 | 'Fix *pcAddr* into a valid instruction address.' 46 | notmask = ~1 if thumbMode else ~3 47 | return pcAddr & notmask 48 | 49 | def fixPCAddrLoad(pcAddr, thumbMode): 50 | '''Fix *pcAddr* using ARM ARM's ``LoadWritePC`` algorithm. Return a tuple of 51 | the fixed address and whether it will run in Thumb mode or not.''' 52 | return fixPCAddrBX(pcAddr) 53 | 54 | def fixPCAddrALU(pcAddr, thumbMode): 55 | '''Fix *pcAddr* using ARM ARM's ``ALUWritePC`` algorithm. Return a tuple of 56 | the fixed address and whether it will run in Thumb mode or not.''' 57 | if thumbMode: 58 | return (pcAddr & ~1, True) 59 | else: 60 | return fixPCAddrBX(pcAddr) 61 | 62 | 63 | 64 | def LSL_C(mask, x, shift): 65 | "ARM ARM's ``LSL_C`` function. Here, *mask* should be set to ``(1<>= shift-1 77 | return (x >> 1, x & 1) 78 | 79 | def LSR(mask, x, shift): 80 | "ARM ARM's ``LSR`` function. Here, *mask* should be set to ``(1<> shift 82 | 83 | def ASR_C(mask, x, shift): 84 | """ARM ARM's ``ASR_C`` function. Here, *mask* should be set to ``(1<> (shift-1) 87 | return ((x >> 1) & mask, x & 1) 88 | 89 | def ASR(mask, x, shift): 90 | "ARM ARM's ``ASR`` function. Here, *mask* should be set to ``(1<> shift) & mask 92 | 93 | def ROR_C(mask, x, shift): 94 | """ARM ARM's ``ROR_C`` function. Here, *mask* should be set to ``(1<> shift 97 | return (res & mask, not not(res & (mask+1)>>1)) 98 | 99 | def ROR(mask, x, shift): 100 | """ARM ARM's ``ROR`` function. Here, *mask* should be set to ``(1<> shift) & mask 103 | 104 | def RRX_C(mask, x, carry): 105 | "ARM ARM's ``RRX_C`` function. Here, *mask* should be set to ``(1<> 1, x & 1) 108 | 109 | def RRX(mask, x, carry): 110 | "ARM ARM's ``RRX`` function. Here, *mask* should be set to ``(1<> 1 112 | 113 | def DecodeImmShift(rawShiftType, imm): 114 | "ARM ARM's ``DecodeImmShift`` function." 115 | if rawShiftType != SRTYPE_LSL: 116 | if not imm: 117 | if rawShiftType == SRTYPE_ROR: 118 | rawShiftType = SRTYPE_RRX 119 | imm = 1 120 | else: 121 | imm = 32 122 | return (rawShiftType, imm) 123 | 124 | def Shift_C(mask, value, shiftType, shiftAmount, carry): 125 | "ARM ARM's ``Shift_C`` function. Here, *mask* should be set to ``(1<> 10 145 | if not top2: 146 | middle2 = imm >> 8 147 | if middle2 == 0: 148 | retval = imm 149 | else: 150 | bottom8 = imm & 0xff 151 | retval = 0 152 | lopart = bottom8 | bottom8 << 16 153 | if middle2 & 1: 154 | retval = lopart 155 | if middle2 & 2: 156 | retval |= lopart << 8 157 | return (retval, carry) 158 | else: 159 | lshift = 32 - (imm >> 7) 160 | res = (0x80 + (imm & 0x7f)) << lshift 161 | return (res, lshift == 24) 162 | 163 | def ThumbExpandImm(imm): 164 | "ARM ARM's ``ThumbExpandImm`` function." 165 | return ThumbExpandImm_C(imm, 0)[0] 166 | 167 | def ARMExpandImm_C(imm, carry): 168 | "ARM ARM's ``ARMExpandImm_C`` function." 169 | amount = (imm >> 8) * 2 170 | if not amount: 171 | return (imm, carry) 172 | else: 173 | imm &= 0xff 174 | if amount < 8: 175 | imm = ((imm * 0x100000001) >> amount) & 0xffffffff 176 | return (imm, imm >> 31) 177 | else: 178 | imm <<= 32-amount 179 | return (imm, imm >> 31) 180 | 181 | def ARMExpandImm(imm): 182 | "ARM ARM's ``ARMExpandImm`` function." 183 | return ROR(0xffffffff, imm & 0xff, (imm >> 8) * 2) 184 | 185 | def AddWithCarry(mask, x, y, carry): 186 | "ARM ARM's ``AddWithCarry`` function." 187 | notmask = ~mask 188 | usum = x + y + carry 189 | ssum = signed(notmask, x) + signed(notmask, y) + carry 190 | carry = not not (usum & notmask) 191 | #^ 'usum > mask' would be more efficient, but SpecialPointer doesn't support 192 | # such comparison. 193 | usum &= mask 194 | overflow = ssum != signed(notmask, usum) 195 | return (usum, carry, overflow) 196 | 197 | 198 | 199 | def ITAdvance(itstate): 200 | "ARM ARM's ``ITAdvance`` function." 201 | return (itstate & 0b111) and ((itstate & 0b11100000) + ((itstate&0b1111)*2)) 202 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /cpu/arm/instructions/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py ... Initialization 3 | # Copyright (C) 2011 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | __all__ = ['core'] 19 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = -P 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " pickle to make pickle files" 22 | @echo " json to make JSON files" 23 | @echo " htmlhelp to make HTML files and a HTML help project" 24 | @echo " qthelp to make HTML files and a qthelp project" 25 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 26 | @echo " changes to make an overview of all changed/added/deprecated items" 27 | @echo " linkcheck to check all external links for integrity" 28 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 29 | 30 | clean: 31 | -rm -rf $(BUILDDIR)/* 32 | 33 | html: 34 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 35 | @echo 36 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 37 | 38 | dirhtml: 39 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 40 | @echo 41 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 42 | 43 | pickle: 44 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 45 | @echo 46 | @echo "Build finished; now you can process the pickle files." 47 | 48 | json: 49 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 50 | @echo 51 | @echo "Build finished; now you can process the JSON files." 52 | 53 | htmlhelp: 54 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 55 | @echo 56 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 57 | ".hhp project file in $(BUILDDIR)/htmlhelp." 58 | 59 | qthelp: 60 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 61 | @echo 62 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 63 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 64 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/EcaFretni.qhcp" 65 | @echo "To view the help file:" 66 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/EcaFretni.qhc" 67 | 68 | latex: 69 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 70 | @echo 71 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 72 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 73 | "run these through (pdf)latex." 74 | 75 | changes: 76 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 77 | @echo 78 | @echo "The overview file is in $(BUILDDIR)/changes." 79 | 80 | linkcheck: 81 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 82 | @echo 83 | @echo "Link check complete; look for any errors in the above output " \ 84 | "or in $(BUILDDIR)/linkcheck/output.txt." 85 | 86 | doctest: 87 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 88 | @echo "Testing of doctests in the sources finished, look at the " \ 89 | "results in $(BUILDDIR)/doctest/output.txt." 90 | -------------------------------------------------------------------------------- /doc/_static/flushRight.css: -------------------------------------------------------------------------------- 1 | @import url("default.css"); 2 | 3 | div.body p.flushRight { 4 | padding-left: 1em; 5 | font-size: 0.66em; 6 | font-style: oblique; 7 | color: gray; 8 | } 9 | 10 | .flushRight a { 11 | color: #688faf; 12 | } 13 | 14 | .sphinxsidebar { 15 | overflow: hidden; 16 | } 17 | 18 | col { 19 | width: inherit; 20 | } 21 | -------------------------------------------------------------------------------- /doc/balanced_substring.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`balanced_substring` --- Parse a balanced substring 4 | ======================================================== 5 | 6 | .. automodule:: balanced_substring 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/bitpattern.rst: -------------------------------------------------------------------------------- 1 | :mod:`bitpattern` --- Parse bits 2 | ================================ 3 | 4 | .. automodule:: bitpattern 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # EcaFretni documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Jun 2 05:49:10 2010. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.append(os.path.abspath('.')) 20 | 21 | sys.path.insert(0, os.path.abspath('..')) 22 | 23 | # -- General configuration ----------------------------------------------------- 24 | 25 | # Add any Sphinx extension module names here, as strings. They can be extensions 26 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 27 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', 'sphinx_ext_superclass'] 28 | 29 | # Add any paths that contain templates here, relative to this directory. 30 | templates_path = ['_templates'] 31 | 32 | # The suffix of source filenames. 33 | source_suffix = '.rst' 34 | 35 | # The encoding of source files. 36 | #source_encoding = 'utf-8' 37 | 38 | # The master toctree document. 39 | master_doc = 'index' 40 | 41 | # General information about the project. 42 | project = u'EcaFretni' 43 | copyright = u'2010, KennyTM~' 44 | 45 | # The version info for the project you're documenting, acts as replacement for 46 | # |version| and |release|, also used in various other places throughout the 47 | # built documents. 48 | # 49 | # The short X.Y version. 50 | version = '0.0' 51 | # The full version, including alpha/beta/rc tags. 52 | release = '0.0' 53 | 54 | # The language for content autogenerated by Sphinx. Refer to documentation 55 | # for a list of supported languages. 56 | #language = None 57 | 58 | # There are two options for replacing |today|: either, you set today to some 59 | # non-false value, then it is used: 60 | #today = '' 61 | # Else, today_fmt is used as the format for a strftime call. 62 | #today_fmt = '%B %d, %Y' 63 | 64 | # List of documents that shouldn't be included in the build. 65 | #unused_docs = [] 66 | 67 | # List of directories, relative to source directory, that shouldn't be searched 68 | # for source files. 69 | exclude_trees = ['_build'] 70 | 71 | # The reST default role (used for this markup: `text`) to use for all documents. 72 | #default_role = None 73 | 74 | # If true, '()' will be appended to :func: etc. cross-reference text. 75 | #add_function_parentheses = True 76 | 77 | # If true, the current module name will be prepended to all description 78 | # unit titles (such as .. function::). 79 | #add_module_names = True 80 | 81 | # If true, sectionauthor and moduleauthor directives will be shown in the 82 | # output. They are ignored by default. 83 | #show_authors = False 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | pygments_style = 'default' 87 | 88 | # A list of ignored prefixes for module index sorting. 89 | #modindex_common_prefix = [] 90 | 91 | 92 | # -- Options for HTML output --------------------------------------------------- 93 | 94 | # The theme to use for HTML and HTML Help pages. Major themes that come with 95 | # Sphinx are currently 'default' and 'sphinxdoc'. 96 | html_theme = 'default' 97 | 98 | # Theme options are theme-specific and customize the look and feel of a theme 99 | # further. For a list of options available for each theme, see the 100 | # documentation. 101 | #html_theme_options = {} 102 | 103 | # Add any paths that contain custom themes here, relative to this directory. 104 | #html_theme_path = [] 105 | 106 | # The name for this set of Sphinx documents. If None, it defaults to 107 | # " v documentation". 108 | #html_title = None 109 | 110 | # A shorter title for the navigation bar. Default is the same as html_title. 111 | #html_short_title = None 112 | 113 | # The name of an image file (relative to this directory) to place at the top 114 | # of the sidebar. 115 | #html_logo = None 116 | 117 | # The name of an image file (within the static path) to use as favicon of the 118 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 119 | # pixels large. 120 | #html_favicon = None 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = ['_static'] 126 | 127 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 128 | # using the given strftime format. 129 | #html_last_updated_fmt = '%b %d, %Y' 130 | 131 | # If true, SmartyPants will be used to convert quotes and dashes to 132 | # typographically correct entities. 133 | #html_use_smartypants = True 134 | 135 | # Custom sidebar templates, maps document names to template names. 136 | #html_sidebars = {} 137 | 138 | # Additional templates that should be rendered to pages, maps page names to 139 | # template names. 140 | #html_additional_pages = {} 141 | 142 | # If false, no module index is generated. 143 | #html_use_modindex = True 144 | 145 | # If false, no index is generated. 146 | #html_use_index = True 147 | 148 | # If true, the index is split into individual pages for each letter. 149 | #html_split_index = False 150 | 151 | # If true, links to the reST sources are added to the pages. 152 | #html_show_sourcelink = True 153 | 154 | # If true, an OpenSearch description file will be output, and all pages will 155 | # contain a tag referring to it. The value of this option must be the 156 | # base URL from which the finished HTML is served. 157 | #html_use_opensearch = '' 158 | 159 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 160 | #html_file_suffix = '' 161 | 162 | # Output file base name for HTML help builder. 163 | htmlhelp_basename = 'EcaFretnidoc' 164 | 165 | 166 | # -- Options for LaTeX output -------------------------------------------------- 167 | 168 | # The paper size ('letter' or 'a4'). 169 | #latex_paper_size = 'letter' 170 | 171 | # The font size ('10pt', '11pt' or '12pt'). 172 | #latex_font_size = '10pt' 173 | 174 | # Grouping the document tree into LaTeX files. List of tuples 175 | # (source start file, target name, title, author, documentclass [howto/manual]). 176 | latex_documents = [ 177 | ('index', 'EcaFretni.tex', u'EcaFretni Documentation', 178 | u'KennyTM\\textasciitilde{}', 'manual'), 179 | ] 180 | 181 | # The name of an image file (relative to this directory) to place at the top of 182 | # the title page. 183 | #latex_logo = None 184 | 185 | # For "manual" documents, if this is true, then toplevel headings are parts, 186 | # not chapters. 187 | #latex_use_parts = False 188 | 189 | # Additional stuff for the LaTeX preamble. 190 | #latex_preamble = '' 191 | 192 | # Documents to append as an appendix to all manuals. 193 | #latex_appendices = [] 194 | 195 | # If false, no module index is generated. 196 | #latex_use_modindex = True 197 | 198 | 199 | # Example configuration for intersphinx: refer to the Python standard library. 200 | intersphinx_mapping = {'http://docs.python.org/py3k/': None} 201 | 202 | 203 | autodoc_member_order = 'bysource' 204 | autodoc_default_flags = ['members'] 205 | highlight_language = 'python' 206 | 207 | html_style = 'flushRight.css' 208 | -------------------------------------------------------------------------------- /doc/cpu.rst: -------------------------------------------------------------------------------- 1 | :mod:`cpu` --- CPU emulator 2 | =========================== 3 | 4 | This package contains modules for emulating the CPU and run some instructions. 5 | Note that the purpose of this package is to allow some simple dynamic analysis 6 | without hooking to a device, but *not* act as a full-featured emulator that can 7 | run and debug programs smoothly. Please consider using dedicated emulator 8 | library such as `PyQemu `_ for the latter 9 | purpose. 10 | 11 | .. toctree:: 12 | :glob: 13 | 14 | cpu/* 15 | -------------------------------------------------------------------------------- /doc/cpu/arm.rst: -------------------------------------------------------------------------------- 1 | :mod:`~cpu.arm` --- ARM architecture 2 | ==================================== 3 | 4 | CPU operations specific to the ARM architecture. 5 | 6 | .. toctree:: 7 | :glob: 8 | 9 | arm/* 10 | -------------------------------------------------------------------------------- /doc/cpu/arm/decoder.rst: -------------------------------------------------------------------------------- 1 | :mod:`~cpu.arm.decoder` --- ARM instruction decoder 2 | =================================================== 3 | 4 | .. automodule:: cpu.arm.decoder 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/cpu/arm/functions.rst: -------------------------------------------------------------------------------- 1 | :mod:`~cpu.arm.functions` --- ARM pseudocode functions 2 | ====================================================== 3 | 4 | The ARM reference defines a lot of pseudocode functions in explaining how the 5 | instructions work. This module implements most of them that are relevant. 6 | 7 | Auxiliary functions 8 | ------------------- 9 | .. autofunction:: cpu.arm.functions.signed 10 | 11 | .. data:: cpu.arm.functions.REG_SP (13) 12 | cpu.arm.functions.REG_LR (14) 13 | cpu.arm.functions.REG_PC (15) 14 | 15 | Special register names. 16 | 17 | .. data:: cpu.arm.functions.COND_NONE (15) 18 | 19 | The condition code representing "no condition". Equivalent to 20 | :const:`cpu.arm.instruction.Condition.NV`. 21 | 22 | 23 | .. (sect A2.2.1) 24 | 25 | Integer arithmetic 26 | ------------------ 27 | 28 | .. autofunction:: cpu.arm.functions.LSL_C 29 | .. autofunction:: cpu.arm.functions.LSL 30 | .. autofunction:: cpu.arm.functions.LSR_C 31 | .. autofunction:: cpu.arm.functions.LSR 32 | .. autofunction:: cpu.arm.functions.ASR_C 33 | .. autofunction:: cpu.arm.functions.ASR 34 | .. autofunction:: cpu.arm.functions.ROR_C 35 | .. autofunction:: cpu.arm.functions.ROR 36 | .. autofunction:: cpu.arm.functions.RRX_C 37 | .. autofunction:: cpu.arm.functions.RRX 38 | 39 | .. autofunction:: cpu.arm.functions.AddWithCarry 40 | 41 | 42 | .. (sect 2.3.1) 43 | 44 | Fixing PC address 45 | ----------------- 46 | 47 | .. autofunction:: cpu.arm.functions.fixPCAddrBX 48 | .. autofunction:: cpu.arm.functions.fixPCAddrB 49 | .. autofunction:: cpu.arm.functions.fixPCAddrLoad 50 | .. autofunction:: cpu.arm.functions.fixPCAddrALU 51 | 52 | 53 | 54 | .. (sect A2.5.2) 55 | 56 | ITSTATE operations 57 | ------------------ 58 | 59 | .. autofunction:: cpu.arm.functions.ITAdvance 60 | 61 | 62 | 63 | .. (sect A5.2.4 & A6.3.2) 64 | 65 | Expansion of immediates 66 | ----------------------- 67 | 68 | .. autofunction:: cpu.arm.functions.ThumbExpandImm_C 69 | .. autofunction:: cpu.arm.functions.ThumbExpandImm 70 | .. autofunction:: cpu.arm.functions.ARMExpandImm_C 71 | .. autofunction:: cpu.arm.functions.ARMExpandImm 72 | 73 | 74 | 75 | .. (sect A8.4.3) 76 | 77 | Instruction-specified shifts and rotates 78 | ---------------------------------------- 79 | 80 | .. autofunction:: cpu.arm.functions.Shift_C 81 | .. autofunction:: cpu.arm.functions.Shift 82 | .. autofunction:: cpu.arm.functions.DecodeImmShift 83 | 84 | .. data:: cpu.arm.functions.SRTYPE_LSL (0) 85 | cpu.arm.functions.SRTYPE_LSR (1) 86 | cpu.arm.functions.SRTYPE_ASR (2) 87 | cpu.arm.functions.SRTYPE_ROR (3) 88 | cpu.arm.functions.SRTYPE_RRX (4) 89 | 90 | The shift types. 91 | 92 | -------------------------------------------------------------------------------- /doc/cpu/arm/instruction.rst: -------------------------------------------------------------------------------- 1 | :mod:`~cpu.arm.instruction` --- ARM instruction 2 | =============================================== 3 | 4 | .. automodule:: cpu.arm.instruction 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/cpu/arm/instructions.rst: -------------------------------------------------------------------------------- 1 | :mod:`~cpu.arm.instructions` --- Collection of ARM instruction parsers 2 | ====================================================================== 3 | 4 | This subpackage contains parsers for the ARM instruction set and its extensions. 5 | You need to import them to enable parsing of those instructions. 6 | 7 | .. toctree:: 8 | :glob: 9 | 10 | ./instructions/* 11 | 12 | -------------------------------------------------------------------------------- /doc/cpu/arm/instructions/core.rst: -------------------------------------------------------------------------------- 1 | :mod:`~cpu.arm.instructions.core` --- Core ARM instructions 2 | =========================================================== 3 | 4 | .. automodule:: cpu.arm.instructions.core 5 | :members: -------------------------------------------------------------------------------- /doc/cpu/arm/operand.rst: -------------------------------------------------------------------------------- 1 | :mod:`~cpu.arm.operand` --- ARM instruction operands 2 | ==================================================== 3 | 4 | .. automodule:: cpu.arm.operand 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/cpu/arm/status.rst: -------------------------------------------------------------------------------- 1 | :mod:`~cpu.arm.status` --- ARM status register 2 | ============================================== 3 | 4 | .. automodule:: cpu.arm.status 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/cpu/memory.rst: -------------------------------------------------------------------------------- 1 | :mod:`~cpu.memory` --- Emulator memory management 2 | ================================================= 3 | 4 | .. automodule:: cpu.memory 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/cpu/pointers.rst: -------------------------------------------------------------------------------- 1 | :mod:`~cpu.pointers` --- Special pointers 2 | ========================================= 3 | 4 | .. automodule:: cpu.pointers 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/data_table.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`data_table` --- Table-like container indiced by multiple keys 4 | =================================================================== 5 | 6 | .. automodule:: data_table 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/factory.rst: -------------------------------------------------------------------------------- 1 | :mod:`factory` --- Factory pattern generator 2 | ============================================ 3 | 4 | .. automodule:: factory 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/hexdump.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`hexdump` --- Dump bytes 4 | ============================= 5 | 6 | .. automodule:: hexdump 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | EcaFretni Development Documentation 2 | *********************************** 3 | 4 | **EcaFretni** is a collection of tools for analyzing executables in Mach-O 5 | format. It currently consists of: 6 | 7 | * An Objective-C interface reverser, as a replacement of `class-dump-z`_. 8 | * An ARM Thumb-2 disassembler, as a replacement of `thumb-ddis`_. 9 | 10 | EcaFretni is highly modularized and designed to be easily extensible, and is 11 | therefore written in Python. 12 | 13 | .. _`class-dump-z`: http://code.google.com/p/networkpx/wiki/class_dump_z 14 | .. _`thumb-ddis`: http://code.google.com/p/networkpx/downloads/detail?name=thumb-ddis(v2).zip 15 | 16 | This documentation include all public functions used in EcaFretni. 17 | 18 | 19 | Contents 20 | ======== 21 | 22 | .. toctree:: 23 | :glob: 24 | 25 | * 26 | 27 | 28 | Indices and tables 29 | ================== 30 | 31 | * :ref:`genindex` 32 | * :ref:`modindex` 33 | * :ref:`search` 34 | 35 | -------------------------------------------------------------------------------- /doc/list_diff.rst: -------------------------------------------------------------------------------- 1 | :mod:`list_diff` --- Compute lifetime from snapshots 2 | ==================================================== 3 | 4 | .. automodule:: list_diff 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/macho.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho` --- Mach-O format processing 2 | ========================================= 3 | 4 | This package contains analysis modules directly related to the Mach-O format. 5 | 6 | .. toctree:: 7 | :glob: 8 | 9 | macho/* 10 | -------------------------------------------------------------------------------- /doc/macho/arch.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`macho.arch` --- CPU architecture 4 | ====================================== 5 | 6 | .. automodule:: macho.arch 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/macho/features.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.features` --- Enabling features for :class:`~macho.macho.MachO` 2 | =========================================================================== 3 | 4 | .. automodule:: macho.features 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/macho/loadcommands.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.loadcommands` --- Load command analysis 2 | =================================================== 3 | 4 | This package contains modules that when imported, can define how a load command 5 | be analyzed. 6 | 7 | .. toctree:: 8 | :glob: 9 | :maxdepth: 2 10 | 11 | loadcommands/* 12 | -------------------------------------------------------------------------------- /doc/macho/loadcommands/dyld_info.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.loadcommands.dyld_info` --- Dyld info command 2 | ========================================================= 3 | 4 | .. automodule:: macho.loadcommands.dyld_info 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/macho/loadcommands/dylib.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.loadcommands.dylib` --- Load commands related to dynamic libraries 2 | ============================================================================== 3 | 4 | .. automodule:: macho.loadcommands.dylib 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/macho/loadcommands/dysymtab.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.loadcommands.dysymtab` --- Dynamic symbol table load command 2 | ======================================================================== 3 | 4 | .. automodule:: macho.loadcommands.dysymtab 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/macho/loadcommands/encryption_info.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.loadcommands.encryption_info` --- Encryption info 2 | ============================================================= 3 | 4 | .. automodule:: macho.loadcommands.encryption_info 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/macho/loadcommands/loadcommand.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.loadcommands.loadcommand` --- Base class for all load commands 2 | ========================================================================== 3 | 4 | .. automodule:: macho.loadcommands.loadcommand 5 | :members: 6 | 7 | Constants 8 | --------- 9 | 10 | .. data:: LC_SEGMENT(0x01) 11 | LC_SYMTAB(0x02) 12 | LC_SYMSEG(0x03) 13 | LC_THREAD(0x04) 14 | LC_UNIXTHREAD(0x05) 15 | LC_LOADFVMLIB(0x06) 16 | LC_IDFVMLIB(0x07) 17 | LC_IDENT(0x08) 18 | LC_FVMFILE(0x09) 19 | LC_PREPAGE(0x0a) 20 | LC_DYSYMTAB(0x0b) 21 | LC_LOAD_DYLIB(0x0c) 22 | LC_ID_DYLIB(0x0d) 23 | LC_LOAD_DYLINKER(0x0e) 24 | LC_ID_DYLINKER(0x0f) 25 | LC_PREBOUND_DYLIB(0x10) 26 | LC_ROUTINES(0x11) 27 | LC_SUB_FRAMEWORK(0x12) 28 | LC_SUB_UMBRELLA(0x13) 29 | LC_SUB_CLIENT(0x14) 30 | LC_SUB_LIBRARY(0x15) 31 | LC_TWOLEVEL_HINTS(0x16) 32 | LC_PREBIND_CKSUM(0x17) 33 | LC_LOAD_WEAK_DYLIB(0x18) 34 | LC_SEGMENT_64(0x19) 35 | LC_ROUTINES_64(0x1a) 36 | LC_UUID(0x1b) 37 | LC_RPATH(0x1c) 38 | LC_CODE_SIGNATURE(0x1d) 39 | LC_SEGMENT_SPLIT_INFO(0x1e) 40 | LC_REEXPORT_DYLIB(0x1f) 41 | LC_LAZY_LOAD_DYLIB(0x20) 42 | LC_ENCRYPTION_INFO(0x21) 43 | LC_DYLD_INFO(0x22) 44 | LC_LOAD_UPWARD_DYLIB(0x23) 45 | 46 | The numerical value of each load command type, ignoring the ``LC_REQ_DYLD`` 47 | bit. See the `Mac OS X ABI Mach-O File Format Reference 48 | `_ 49 | for the definition of these. 50 | -------------------------------------------------------------------------------- /doc/macho/loadcommands/segment.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`macho.loadcommands.segment` --- Segment command and VM addressing 4 | ======================================================================= 5 | 6 | .. automodule:: macho.loadcommands.segment 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/macho/loadcommands/symtab.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`macho.loadcommands.symtab` --- Symbol table load command 4 | ============================================================== 5 | 6 | .. automodule:: macho.loadcommands.symtab 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/macho/macho.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`macho.macho` --- Mach-O file 4 | ================================== 5 | 6 | .. automodule:: macho.macho 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/macho/sections.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.sections` --- Section analysis 2 | ========================================== 3 | 4 | This package contains modules that when imported, can define how a section be 5 | analyzed. 6 | 7 | .. toctree:: 8 | :glob: 9 | :maxdepth: 2 10 | 11 | sections/* 12 | -------------------------------------------------------------------------------- /doc/macho/sections/cfstring.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`macho.sections.cfstring` --- CoreFoundation string section 4 | ================================================================ 5 | 6 | .. automodule:: macho.sections.cfstring 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/macho/sections/cstring.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`macho.sections.cstring` --- C string section 4 | ================================================== 5 | 6 | .. automodule:: macho.sections.cstring 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/macho/sections/objc.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.sections.objc` --- Objective-C sections 2 | =================================================== 3 | 4 | This subpackage contains modules that define how Objective-C related sections 5 | are analyzed. 6 | 7 | .. toctree:: 8 | :glob: 9 | :maxdepth: 2 10 | 11 | objc/* 12 | -------------------------------------------------------------------------------- /doc/macho/sections/objc/_abi1reader.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.sections.objc._abi1reader` --- Objective-C ABI 1.0 reader 2 | ===================================================================== 3 | 4 | .. automodule:: macho.sections.objc._abi1reader 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/macho/sections/objc/_abi2reader.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.sections.objc._abi2reader` --- Objective-C ABI 2.0 reader 2 | ===================================================================== 3 | 4 | .. automodule:: macho.sections.objc._abi2reader 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/macho/sections/objc/catlist.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.sections.objc.catlist` --- Objective-C category list section 2 | ======================================================================== 3 | 4 | .. automodule:: macho.sections.objc.catlist 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/macho/sections/objc/classlist.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.sections.objc.classlist` --- Objective-C class list section 2 | ======================================================================= 3 | 4 | .. automodule:: macho.sections.objc.classlist 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/macho/sections/objc/protolist.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.sections.objc.protolist` --- Objective-C protocol list section 2 | ========================================================================== 3 | 4 | .. automodule:: macho.sections.objc.protolist 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/macho/sections/section.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.sections.section` --- Base class for all sections 2 | ============================================================= 3 | 4 | .. automodule:: macho.sections.section 5 | :members: 6 | 7 | Constants 8 | --------- 9 | 10 | .. data:: S_REGULAR(0) 11 | S_ZEROFILL(1) 12 | S_CSTRING_LITERALS(2) 13 | S_4BYTE_LITERALS(3) 14 | S_8BYTE_LITERALS(4) 15 | S_LITERAL_POINTERS(5) 16 | S_NON_LAZY_SYMBOL_POINTERS(6) 17 | S_LAZY_SYMBOL_POINTERS(7) 18 | S_SYMBOL_STUBS(8) 19 | S_MOD_INIT_FUNC_POINTERS(9) 20 | S_MOD_TERM_FUNC_POINTERS(10) 21 | S_COALESCED(11) 22 | S_GB_ZEROFILL(12) 23 | S_INTERPOSING(13) 24 | S_16BYTE_LITERALS(14) 25 | S_DTRACE_DOF(15) 26 | S_LAZY_DYLIB_SYMBOL_POINTERS(16) 27 | S_THREAD_LOCAL_REGULAR(17) 28 | S_THREAD_LOCAL_ZEROFILL(18) 29 | S_THREAD_LOCAL_VARIABLES(19) 30 | S_THREAD_LOCAL_VARIABLE_POINTERS(20) 31 | S_THREAD_LOCAL_INIT_FUNCTION_POINTERS(21) 32 | 33 | The numerical value of each section type. See the `Mac OS X ABI Mach-O File 34 | Format Reference `_ 35 | for the definition of these. 36 | -------------------------------------------------------------------------------- /doc/macho/sections/symbol_ptr.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.sections.symbol_ptr` --- Symbol pointer sections 2 | ============================================================ 3 | 4 | .. automodule:: macho.sections.symbol_ptr 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/macho/sharedcache.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.sharedcache` --- Shared cache support 2 | ================================================= 3 | 4 | .. automodule:: macho.sharedcache 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/macho/symbols.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`macho.symbol` --- Symbols 4 | =============================== 5 | 6 | .. automodule:: macho.symbol 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/macho/utilities.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`macho.utilities` --- Utility functions for Mach-O parsing 4 | =============================================================== 5 | 6 | .. automodule:: macho.utilities 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/macho/vmaddr.rst: -------------------------------------------------------------------------------- 1 | :mod:`macho.vmaddr` --- Convert between VM address and file offset 2 | ================================================================== 3 | 4 | .. automodule:: macho.vmaddr 5 | :members: 6 | 7 | -------------------------------------------------------------------------------- /doc/monkey_patching.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`monkey_patching` --- Monkey patching idiom 4 | ================================================ 5 | 6 | .. automodule:: monkey_patching 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/objc.rst: -------------------------------------------------------------------------------- 1 | :mod:`objc` --- Objective-C structures 2 | ====================================== 3 | 4 | This package is a collection of classes to represent elements in the Objective-C 5 | run time system, e.g. classes, methods, protocols etc. 6 | 7 | .. toctree:: 8 | :glob: 9 | 10 | objc/* 11 | -------------------------------------------------------------------------------- /doc/objc/category.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`objc.category` --- Categories 4 | =================================== 5 | 6 | .. automodule:: objc.category 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/objc/class_.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`objc.class_` --- Classes 4 | ============================== 5 | 6 | .. automodule:: objc.class_ 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/objc/classlike.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`objc.classlike` --- Class-like objects 4 | ============================================ 5 | 6 | .. automodule:: objc.classlike 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/objc/ivar.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`objc.ivar` --- Instance variables (ivars) 4 | =============================================== 5 | 6 | .. automodule:: objc.ivar 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/objc/method.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`objc.method` --- Methods 4 | ============================== 5 | 6 | .. automodule:: objc.method 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/objc/property.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`objc.property` --- Declared properties 4 | ============================================ 5 | 6 | .. automodule:: objc.property 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/objc/protocol.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`objc.protocol` --- Protocols 4 | ================================== 5 | 6 | .. automodule:: objc.protocol 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/objctype2.rst: -------------------------------------------------------------------------------- 1 | :mod:`objctype2` --- Objective-C type managements 2 | ================================================= 3 | 4 | This package comprises of a type management system based on Objective-C encoding. 5 | 6 | 7 | .. toctree:: 8 | :glob: 9 | 10 | objctype2/* 11 | -------------------------------------------------------------------------------- /doc/objctype2/parser.rst: -------------------------------------------------------------------------------- 1 | :mod:`objctype2.parser` --- Type encoding parser 2 | ================================================ 3 | 4 | .. automodule:: objctype2.parser 5 | :members: 6 | 7 | -------------------------------------------------------------------------------- /doc/objctype2/types.rst: -------------------------------------------------------------------------------- 1 | :mod:`objctype2.types` --- Types 2 | ================================ 3 | 4 | .. automodule:: objctype2.types 5 | :members: 6 | 7 | Constants 8 | --------- 9 | 10 | .. data:: BOOL('c') 11 | CHAR('c') 12 | SHORT('s') 13 | INT('i') 14 | LONG('l') 15 | LONG_LONG('q') 16 | UNSIGNED_CHAR('C') 17 | UNSIGNED_SHORT('S') 18 | UNSIGNED_INT('I') 19 | UNSIGNED_LONG('L') 20 | UNSIGNED_LONG_LONG('Q') 21 | FLOAT('f') 22 | DOUBLE('d') 23 | VOID('v') 24 | BOOL_C99('B') 25 | CLASS('#') 26 | SEL(':') 27 | FUNCTION_POINTER('^?') 28 | BLOCK('@?') 29 | NXATOM('%') 30 | 31 | Primitive type encodings. 32 | 33 | .. data:: POINTER('^') 34 | COMPLEX('j') 35 | ONEWAY('V') 36 | CONST('r') 37 | BYCOPY('O') 38 | BYREF('R') 39 | IN('n') 40 | OUT('o') 41 | INOUT('N') 42 | GCINVISIBLE('!') 43 | 44 | Modifier type encodings. 45 | 46 | -------------------------------------------------------------------------------- /doc/sorted_list.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`sorted_list` --- List automatically sorted by usage frequency 4 | =================================================================== 5 | 6 | .. automodule:: sorted_list 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/sym.rst: -------------------------------------------------------------------------------- 1 | :mod:`sym` --- Symbols 2 | ====================== 3 | 4 | .. automodule:: sym 5 | :members: 6 | 7 | Constants 8 | --------- 9 | 10 | .. data:: SYMTYPE_UNDEFINED(-1) 11 | SYMTYPE_GENERIC(0) 12 | SYMTYPE_CSTRING(3) 13 | SYMTYPE_CFSTRING(4) 14 | SYMTYPE_OBJC_SEL(5) 15 | 16 | Type of symbols. 17 | 18 | -------------------------------------------------------------------------------- /doc/symbolic.rst: -------------------------------------------------------------------------------- 1 | :mod:`symbolic` --- Symbolic arithmetic 2 | ======================================= 3 | 4 | This is the symbolic arithmetic package for the EcaFretni project. Unlike other 5 | symbolic math libraries (e.g. `SymPy `_), this 6 | package is aimed at extensible simplification routines, which is useful for code 7 | analysis and decompiling. Unnecessary features e.g. string parsing, calculus 8 | etc. will not be present. 9 | 10 | 11 | .. toctree:: 12 | :glob: 13 | 14 | symbolic/* 15 | -------------------------------------------------------------------------------- /doc/symbolic/expression.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`symbolic.expression` --- Symbolic expressions 4 | =================================================== 5 | 6 | .. automodule:: symbolic.expression 7 | :members: 8 | 9 | Concepts 10 | -------- 11 | 12 | A generic expression is represented by a tree. For example, the expression 13 | ``2 * (x + y/w + z/w)`` looks like:: 14 | 15 | (*) -> 2 16 | (+) -> x 17 | (/) -> y 18 | w 19 | (/) -> z 20 | w 21 | 22 | There are 2 kinds of generic expressions: 23 | 24 | * **Atomic** -- expressions which cannot be further simplified, which include: 25 | 26 | * **Constants** -- an atomic expression having a definite value that can be 27 | evaluated, e.g. ``89`` and ``-6.4``. 28 | 29 | * **Symbols** -- an unknown variable, e.g. ``x`` and ``y`` above. 30 | 31 | * **Expression** -- any expressions that is not atomic, e.g. ``x + y`` and 32 | ``6 / 2``. 33 | 34 | 35 | 36 | The :class:`Expression` class understands the following operators: 37 | 38 | * **Commutative semigroup operators**. These operators are associative and 39 | commutative, thus form a `commutative semigroup 40 | `_. These include: 41 | 42 | * ``+`` (addition) 43 | * ``*`` (multiplication) 44 | * ``&`` (bitwise AND) 45 | * ``|`` (bitwise OR) 46 | * ``^`` (bitwise XOR) 47 | * ``&&`` (logical AND) 48 | 49 | * ``||`` (logical OR) 50 | 51 | Their children are stored as a multiset (:class:`collections.Counter`). 52 | 53 | * **Unary operators**. These contain exactly 1 child. These include: 54 | 55 | * ``~`` (bitwise NOT) 56 | 57 | * ``!`` (logical NOT) 58 | 59 | There is no negation operator. ``-x`` is represented by ``x * -1``. 60 | 61 | * **Binary operators**. These contain exactly 2 children, and their order 62 | cannot be swapped (i.e. non-commutative). These include: 63 | 64 | * ``//`` (integer division) 65 | * ``/`` (floating-point division) 66 | * ``%`` (modulus) 67 | * ``>>`` (right shift) 68 | * ``**`` (exponentiation) 69 | * ``rol`` (rotate left) and ``ror`` (rotate right) 70 | * ``==`` (equality) and ``!=`` (inequality) 71 | * ``<`` (less than) and ``<=`` (less than or equals to) 72 | 73 | The ``<<`` (left shift) operator should be written as ``x * 2**y``. The 74 | ``>`` (greater than) and ``>=`` (greater than or equals to) operators 75 | should be rewritten into ``<`` and ``<=`` with the arguments swapped. 76 | 77 | * ``?:`` (conditional operator). This operator has exactly 3 children. 78 | 79 | * ``fn`` (function application). This operator has at least 1 child, with 80 | the first child being the function to be applied on the rest of the 81 | children. The exact number of children depends on the arity of the 82 | function. 83 | -------------------------------------------------------------------------------- /doc/symbolic/simplify.rst: -------------------------------------------------------------------------------- 1 | :mod:`symbolic.simplify` --- Simplification routines 2 | ==================================================== 3 | 4 | This package contains modules that when imported, can create new simplification 5 | rules for :class:`symbolic.expression.Expression`. 6 | 7 | .. toctree:: 8 | :glob: 9 | :maxdepth: 2 10 | 11 | simplify/* 12 | -------------------------------------------------------------------------------- /doc/symbolic/simplify/compare.rst: -------------------------------------------------------------------------------- 1 | :mod:`symbolic.simplify.compare` --- Simplification for comparison 2 | ================================================================== 3 | 4 | Importing this module will perform simplification involving comparison 5 | operators. 6 | 7 | **Self comparison** 8 | 9 | When an expression is compared with itself, it can be reduced to a constant 10 | boolean value:: 11 | 12 | (a == a) == True 13 | 14 | **Negated comparison** 15 | 16 | Every comparison operator has a corresponding dual when the whole expression 17 | is negated:: 18 | 19 | !(a < b) == (b <= a) 20 | 21 | **Subtract and compare** 22 | 23 | Simple comparison like ``a < b`` is often executed as ``a - b < 0`` in the 24 | ALU. This rule reverts such transformation:: 25 | 26 | (a - b < 0) == (a < b) 27 | 28 | **Equality with zero** 29 | 30 | To check the truthness of an expression, it is often checked equality with 31 | zero. This rule converts this back to a boolean expression:: 32 | 33 | (a == 0) == !a 34 | -------------------------------------------------------------------------------- /doc/symbolic/simplify/distributive.rst: -------------------------------------------------------------------------------- 1 | :mod:`symbolic.simplify.distributive` --- Simplification by applying distribution law 2 | ===================================================================================== 3 | 4 | Importing this module will insert simplification rules involving distribution 5 | law. These rules are: 6 | 7 | **Repetition** 8 | 9 | Multiple copies of itself can be rewritten using a higher-rank operator:: 10 | 11 | a + a + a == 3 * a 12 | 13 | This rule is applied to (``+``, ``*``) and (``*``, ``**``). 14 | 15 | **Distributive** 16 | 17 | Also known as *factorization*, if the same subexpression appear in different 18 | terms, it can be taken out, like:: 19 | 20 | a*b + a*c == a*(b + c) 21 | 22 | This is useful when the uncommon subexpressions are constants. Using 23 | together with the rules in :mod:`symbolic.simplify.fold_constant`, one could 24 | assert:: 25 | 26 | 3*a + 4*a == (3 + 4)*a # distributive 27 | == 7*a # fold constant (binary) 28 | 29 | This rule is applied to (``+``, ``*``), (``|``, ``&``), (``&``, ``|``), 30 | (``&&``, ``||``) and (``||``, ``&&``). 31 | 32 | -------------------------------------------------------------------------------- /doc/symbolic/simplify/fold_constant.rst: -------------------------------------------------------------------------------- 1 | :mod:`symbolic.simplify.fold_constant` --- Simplification by evaluating constant expressions 2 | ============================================================================================ 3 | 4 | Importing this module will evaluate constant expressions. 5 | 6 | **Fold constant** 7 | 8 | Whenever an expression's children are all constants, it can be evaluated to 9 | give a simple constant, e.g.:: 10 | 11 | 7 + 6 == 13 12 | ~ 4 == -5 13 | ... 14 | 15 | **Fold constant (N-ary)** 16 | 17 | Commutative semigroup operators are all N-ary, hence it may only be partly 18 | composed of constants. This rule will evaluate the constant part, and leave 19 | the non-constant part untouched:: 20 | 21 | a + 3 + 4 == a + 7 22 | 23 | **Base condition** 24 | 25 | Commutative semigroup operators having exactly 0 children are equivalent to 26 | their identity value, and those having exactly 1 child can be replaced by 27 | the child:: 28 | 29 | +(a) == a 30 | +() == 0 31 | 32 | **Short circuit** 33 | 34 | Some operators, when having a special constant as a child, the whole 35 | expression must have the same value as it:: 36 | 37 | 0 * x == 0 38 | 39 | This rule is applied to ``*``, ``&``, ``|``, ``&&`` and ``||``. 40 | 41 | **Constant condition** 42 | 43 | If the condition of the ``?:`` (conditional) operator is a constant, the 44 | expression of the opposite truthness can be ignored:: 45 | 46 | (1 ? t : f) == t 47 | (0 ? t : f) == f -------------------------------------------------------------------------------- /doc/symbolic/simplify/recursive.rst: -------------------------------------------------------------------------------- 1 | :mod:`symbolic.simplify.recursive` --- Recursive simplifying 2 | ============================================================ 3 | 4 | Importing this module allows simplification to be applied to child nodes 5 | recursively. 6 | 7 | -------------------------------------------------------------------------------- /doc/symbolic/simplify/semigroup.rst: -------------------------------------------------------------------------------- 1 | :mod:`symbolic.simplify.semigroup` --- Simplification involving one commutative semigroup operator 2 | ================================================================================================== 3 | 4 | Importing this module will insert simplification rules involving one commutative 5 | semigroup operator. These rules are: 6 | 7 | **Commutative semigroup** 8 | 9 | Using commutativity and associativity, a hierachical tree can be flattened:: 10 | 11 | a + (c + b) == a + b + c 12 | 13 | This rule is applied to all commutative semigroup operators (``+``, ``*``, 14 | ``&``, ``|``, ``^``, ``&&``, ``||``). 15 | 16 | **Idempotent** 17 | 18 | Some operators are *idempotent*, i.e.:: 19 | 20 | (a & a) == a 21 | 22 | With this, extra copies of children can be reduced to one. This rule is 23 | applied to ``&``, ``|``, ``&&`` and ``||``. 24 | 25 | **Involution** 26 | 27 | Some operators form *involution*, i.e.:: 28 | 29 | (a ^ a) == 0 30 | # identity element 31 | 32 | With this, extra copies of children can be reduced to one or zero. This rule 33 | is applied to ``^``. 34 | 35 | -------------------------------------------------------------------------------- /doc/symbolic/simplify/utility.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 1 2 | 3 | :mod:`symbolic.simplify.utilities` --- Utilities for simplication 4 | ================================================================= 5 | 6 | .. automodule:: symbolic.simplify.utilities 7 | :members: 8 | 9 | -------------------------------------------------------------------------------- /factory.py: -------------------------------------------------------------------------------- 1 | # 2 | # factory.py ... Factory pattern generator. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | import sys 20 | 21 | class factorySuffix(object): 22 | """A class decorator that make a class adopts the `factory pattern 23 | `_ distinguished by a 24 | keyword. Factories are registered at runtime, allowing plug-ins to provide 25 | extra capabilities without modifying the base source code:: 26 | 27 | @factory 28 | class Image(object): 29 | def __init__(self, typ, width, height): 30 | ... 31 | 32 | class PNGImage(Image): 33 | ... 34 | 35 | class JPGImage(Image): 36 | ... 37 | 38 | Image.registerFactory("png", PNGImage) 39 | Image.registerFactory("jpg", JPGImage) 40 | 41 | pngImg = Image.create("png", 100, 100) 42 | jpgImg = Image.create("jpg", 800, 600) 43 | genericImg = Image.create("gif", 20, 20) 44 | 45 | assert type(pngImg) is PNGImage 46 | assert type(jpgImg) is JPGImage 47 | assert type(genericImg) is Image 48 | 49 | User may provide a suffix so that the class may be generated by different 50 | factories:: 51 | 52 | @factory 53 | class Foo(object): 54 | ... 55 | 56 | @factorySuffix(suffix='More') 57 | class Bar1(Foo): 58 | @classmethod 59 | def _createBar(cls, keywordA, keywordB): 60 | return cls.createMore(keywordB, keywordA) 61 | 62 | def __init__(self, keywordB, keywordA): 63 | ... 64 | 65 | class Baz(Bar1): 66 | ... 67 | 68 | Foo.registerFactory(1, Bar1._createBar) 69 | Bar1.registerFactoryMore('baz', Baz) 70 | 71 | obj = Foo.create(1, 'baz') 72 | # calls Bar1._createBar(1, 'baz') 73 | # calls Bar1.createMore('baz', 1) 74 | # calls Baz('baz', 1) 75 | 76 | The following class methods are added to a class adopting the this 77 | decorator. If *suffix* is provided, the methods added will be named as 78 | ``cls.registerFactorySuffix``, etc. 79 | """ 80 | 81 | @classmethod 82 | def registerFactory(cls, keyword, cons): 83 | '''Register a keyword with a subclass *cons* of the class *cls*. The 84 | subclass's constructor's signature should be:: 85 | 86 | @classmethod 87 | def constructor(cls, keyword, ...): 88 | ... 89 | ''' 90 | 91 | @classmethod 92 | def getFactory(cls, keyword): 93 | '''Returns the subclass registered with *keyword*. The base class 94 | *cls* will be returned if that keyword is unregistered.''' 95 | 96 | @classmethod 97 | def create(cls, keyword, *args, **kargs): 98 | '''Create an instance, specialize to a subclass based on the 99 | *keyword*.''' 100 | 101 | def __init__(self, suffix='', defaultConstructor='__call__'): 102 | self.suffix = suffix 103 | self.defcon = defaultConstructor 104 | 105 | def __call__(self, clsx): 106 | suffix = self.suffix 107 | factoryName = '_factories' + suffix 108 | getFactoryName = 'getFactory' + suffix 109 | defcon = self.defcon 110 | 111 | if 'sphinx-build' in sys.argv[0]: 112 | @classmethod 113 | def nop(k, *args, **kwargs): 114 | pass 115 | rf = gf = cf = nop 116 | 117 | else: 118 | @classmethod 119 | def rf(cls, keyword, cons): 120 | getattr(cls, factoryName)[keyword] = cons 121 | 122 | @classmethod 123 | def gf(cls, keyword): 124 | return getattr(cls, factoryName).get(keyword, getattr(cls, defcon)) 125 | 126 | @classmethod 127 | def cf(cls, keyword, *args, **kargs): 128 | return getattr(cls, getFactoryName)(keyword)(keyword, *args, **kargs) 129 | 130 | setattr(clsx, 'registerFactory' + suffix, rf) 131 | setattr(clsx, getFactoryName, gf) 132 | setattr(clsx, 'create' + suffix, cf) 133 | setattr(clsx, factoryName, {}) 134 | 135 | return clsx 136 | 137 | 138 | def factory(cls): 139 | '''Equivalent to :class:`factorySuffix` with no suffix.''' 140 | return factorySuffix()(cls) 141 | 142 | 143 | if __name__ == '__main__': 144 | @factory 145 | class A(object): 146 | def __init__(self, index, value): 147 | self.index = index 148 | self.value = value 149 | 150 | def __str__(self): 151 | return "{}: {}={}".format(type(self), self.index, self.value) 152 | 153 | class B(A): 154 | def __init__(self, index, value): 155 | super().__init__(index, value) 156 | print("constructing B") 157 | 158 | class C(A): 159 | def __init__(self, index, value): 160 | super().__init__(index, value) 161 | print("constructing C") 162 | 163 | A.registerFactory(4, B) 164 | A.registerFactory(7, B) 165 | A.registerFactory(6, C) 166 | 167 | print (A.create(3, 6)) 168 | print (A.create(4, 7)) 169 | print (A.create(5, 8)) 170 | print (A.create(6, 9)) 171 | print (A.create(7, 10)) 172 | print (A.create(8, 11)) 173 | 174 | -------------------------------------------------------------------------------- /hexdump.py: -------------------------------------------------------------------------------- 1 | # 2 | # hexdump.py ... Dump bytes. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | 20 | ''' 21 | 22 | This module can print a byte array as hex dump to the standard output. This is 23 | useful for debugging a binary blob. 24 | 25 | >>> hexdump(b"\\x12\\x34\\x56foobar\\xab\\xcd\\xef" * 3) 26 | 0 12 34 56 66 6f 6f 62 61 72 ab cd ef 12 34 56 66 `4Vfoobar````4Vf 27 | 10 6f 6f 62 61 72 ab cd ef 12 34 56 66 6f 6f 62 61 oobar````4Vfooba 28 | 20 72 ab cd ef r``` 29 | 30 | Members 31 | ------- 32 | 33 | ''' 34 | 35 | def _dumpLine(lineList, index, maxIndexWidth, width, visualizer, skip=0): 36 | pl = ['{0:{1}x}'.format(index, maxIndexWidth), ' '*skip + ' '.join(map('{:02x}'.format, lineList)) + ' ' * (width - skip - len(lineList))] 37 | if visualizer is not None: 38 | pl.append(visualizer(lineList, skip)) 39 | print (' '.join(pl)) 40 | 41 | 42 | 43 | def hexdump(arr, width=16, location=0, visualizer='ascii'): 44 | ''' 45 | 46 | Dump the byte array on screen. 47 | 48 | The *location* argument changes the starting address to print, for example:: 49 | 50 | >>> hexdump(b'123456' * 3) 51 | 0 31 32 33 34 35 36 31 32 33 34 35 36 31 32 33 34 1234561234561234 52 | 10 35 36 56 53 | >>> hexdump(b'123456' * 3, location=0x20fc) 54 | 20f0 31 32 33 34 1234 55 | 2100 35 36 31 32 33 34 35 36 31 32 33 34 35 36 56123456123456 56 | 57 | The *visualizer* argument defines which function should be applied to the 58 | bytes to generate rightmost column. Only the visualizer ``'ascii'`` is 59 | defined in this module. 60 | 61 | ''' 62 | 63 | arrLen = len(arr) 64 | 65 | maxIndex = location + arrLen 66 | maxIndex -= maxIndex % width 67 | maxIndexWidth = len(hex(maxIndex)) - 2 68 | 69 | visualizerFunc = __visualizers.get(visualizer, None) 70 | 71 | index = location % width 72 | if index: 73 | location -= index 74 | index = width-index 75 | _dumpLine(arr[:index], location, maxIndexWidth, width, visualizerFunc, skip=width-index) 76 | location += width 77 | 78 | while index < arrLen: 79 | _dumpLine(arr[index:index+width], location, maxIndexWidth, width, visualizerFunc) 80 | index += width 81 | location += width 82 | 83 | def listVisualizers(): 84 | """Return an iterator of strings listing all available visualizers.""" 85 | 86 | return __visualizers.keys() 87 | 88 | def registerVisualizer(key, func): 89 | """Register a visualizer for use in hexdump. 90 | 91 | A visualizer should accept 2 parameters 92 | 93 | 1. a ``bytes`` object to be dumped. 94 | 95 | 2. an integer describing how many empty bytes should be padded. 96 | 97 | and returns a string. For instance, an ASCII-based visualizer may be 98 | implemented as:: 99 | 100 | def myAsciiVisualizer(theBytes, skip): 101 | return ' '*skip + theBytes.decode(encoding='ascii', errors='replace') 102 | 103 | """ 104 | __visualizers[key] = func 105 | 106 | 107 | if hasattr(bytes, 'maketrans'): # hack to make Sphinx work. 108 | __asciiTranslator = bytes.maketrans(bytes(range(0,0x20)) + bytes(range(0x7f,0x100)), b'`' * (0x20 + 0x100 - 0x7f)) 109 | 110 | def _asciiVisualizer(lineList, skip): 111 | return ' '*skip + lineList.translate(__asciiTranslator).decode() 112 | 113 | __visualizers = {'ascii': _asciiVisualizer} 114 | 115 | 116 | if __name__ == '__main__': 117 | hexdump(b'1234567\x8901234\x567890ashkfshfj\xaawroq23irme0 \x038mr0werm09asdsf', location=100) 118 | 119 | 120 | -------------------------------------------------------------------------------- /list_diff.py: -------------------------------------------------------------------------------- 1 | # 2 | # list_diff.py ... Compute lifetime of each element from snapshot of lists. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | """ 20 | This module provides a function :func:`versioning` to compute the diffs from 21 | snapshots, and produce the lifetime of each element. Usage: 22 | 23 | >>> import list_diff 24 | >>> lists = [ 25 | ... [3, 1, 4, 1, 5, 9], 26 | ... [3, 4, 1, 6, 9], 27 | ... [1, 3, 4, 9, 9, 9], 28 | ... [2, 2, 4, 5, 6, 8], 29 | ... [2, 7, 2, 4, 5, 6, 8], 30 | ... [2, 7, 1, 8, 2, 8] 31 | ... ] 32 | >>> revs = list(list_diff.versioning(lists)) 33 | >>> for r in revs: 34 | ... print("Element {0} exists from revisions {1} to {2}".format(r.content, r.low, r.high)) 35 | ... 36 | Element 1 exists from revisions 2 to 2 37 | Element 3 exists from revisions 0 to 2 38 | Element 1 exists from revisions 0 to 0 39 | Element 2 exists from revisions 3 to 5 40 | Element 7 exists from revisions 4 to 5 41 | Element 1 exists from revisions 5 to 5 42 | Element 8 exists from revisions 5 to 5 43 | Element 2 exists from revisions 3 to 5 44 | Element 4 exists from revisions 0 to 4 45 | Element 1 exists from revisions 0 to 1 46 | Element 5 exists from revisions 0 to 0 47 | Element 6 exists from revisions 1 to 1 48 | Element 9 exists from revisions 0 to 2 49 | Element 9 exists from revisions 2 to 2 50 | Element 9 exists from revisions 2 to 2 51 | Element 5 exists from revisions 3 to 4 52 | Element 6 exists from revisions 3 to 4 53 | Element 8 exists from revisions 3 to 5 54 | >>> for i, l in enumerate(lists): 55 | ... assert list(list_diff.snapshot(revs, i)) == l 56 | ... 57 | >>> 58 | 59 | """ 60 | 61 | from itertools import chain 62 | from difflib import SequenceMatcher 63 | 64 | 65 | 66 | def _pairwise(lists, zerothVersion): 67 | old = zerothVersion 68 | for l in lists: 69 | yield (old, l) 70 | old = l 71 | 72 | class Versioned(object): 73 | """ 74 | This class encapsulates an object and adds information about its 75 | (continuous) lifetime. 76 | 77 | .. data:: content 78 | 79 | The object 80 | 81 | .. data:: low 82 | 83 | The first version where the object starts to appear. 84 | 85 | .. data:: high 86 | 87 | The last version where the object still appears. 88 | """ 89 | 90 | def __init__(self, content, low, high): 91 | self.content = content 92 | self.low = low 93 | self.high = high 94 | 95 | def __repr__(self): 96 | return "Versioned({0!r}, {1!r}, {2!r})".format(self.content, self.low, self.high) 97 | 98 | def versioning(lists): 99 | """ 100 | Compute the lifetime of every element from an iterable of sequences. It 101 | returns an iterable of :class:`Versioned` classes to indicate the lifetimes. 102 | 103 | The computation is backed by the built-in :mod:`difflib` module. As such, 104 | every element must be hashable. 105 | """ 106 | 107 | ci = chain.from_iterable 108 | 109 | sm = SequenceMatcher() 110 | oldVersions = [ [] ] 111 | 112 | for newName, (oldList, newList) in enumerate(_pairwise(lists, [])): 113 | sm.set_seqs(oldList, newList) 114 | 115 | newVersions = [ oldVersions[0] ] 116 | 117 | for op, oldStart, oldEnd, newStart, newEnd in sm.get_opcodes(): 118 | if op == 'equal': 119 | for i in range(oldStart+1, oldEnd+1): 120 | oldVersions[i][0].high = newName 121 | newVersions.extend(oldVersions[oldStart+1:oldEnd+1]) 122 | 123 | if op == 'delete' or op == 'replace': 124 | newVersions[-1].extend(ci(oldVersions[oldStart+1:oldEnd+1])) 125 | 126 | if op == 'insert' or op == 'replace': 127 | newVersions.extend([Versioned(x, newName, newName)] for x in newList[newStart:newEnd]) 128 | 129 | oldVersions = newVersions 130 | 131 | return ci(oldVersions) 132 | 133 | 134 | def versioningUnordered(sets): 135 | """ 136 | Like :func:`versioning`, but takes an iterable of :class:`set`\\s instead. 137 | This is more efficient than :func:`versioning` if you don't care about the 138 | relative order between snapshots. 139 | """ 140 | 141 | lowVersions = {} 142 | 143 | for newName, (oldSet, newSet) in enumerate(_pairwise(sets, set())): 144 | insertedElements = newSet - oldSet 145 | deletedElements = oldSet - newSet 146 | 147 | for v in deletedElements: 148 | yield Versioned(v, lowVersions[v], newName - 1) 149 | del lowVersions[v] 150 | 151 | for v in insertedElements: 152 | lowVersions[v] = newName 153 | 154 | for v, low in lowVersions.items(): 155 | yield Versioned(v, low, newName) 156 | 157 | 158 | def snapshot(versionedIterable, versionNumber): 159 | """ 160 | Recover an iterable at a particular version from an iterable of 161 | :class:`Versioned`\\s. 162 | """ 163 | return (v.content for v in versionedIterable if v.low <= versionNumber <= v.high) 164 | 165 | 166 | if __name__ == '__main__': 167 | lists = [ 168 | [3, 1, 4, 1, 5, 9], 169 | [3, 4, 1, 6, 9], 170 | [1, 3, 4, 9, 9, 9], 171 | [2, 2, 4, 5, 6, 8], 172 | [2, 7, 2, 4, 5, 6, 8], 173 | [2, 7, 1, 8, 2, 8] 174 | ] 175 | revs = list(versioning(lists)) 176 | for i, l in enumerate(lists): 177 | assert list(snapshot(revs, i)) == l 178 | 179 | sets = list(map(set, lists)) 180 | revso = list(versioningUnordered(sets)) 181 | for i, l in enumerate(sets): 182 | assert set(snapshot(revso, i)) == l 183 | 184 | 185 | -------------------------------------------------------------------------------- /macho/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py ... Initialization 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | __all__ = ['arch', 'macho', 'symbol', 'utilities', 'sharedcache', 'features'] 19 | -------------------------------------------------------------------------------- /macho/features.py: -------------------------------------------------------------------------------- 1 | # 2 | # features.py ... Enable features in Mach-O 3 | # Copyright (C) 2011 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | 20 | """ 21 | By default, the :mod:`macho.macho` module does have many features, and the 22 | interesting ones are delegated to the load command parsers, which the developers 23 | must import them to enable. This module defines the convenient function 24 | :meth:`enable` to formalize the process. 25 | """ 26 | 27 | 28 | def _enable_libord(): 29 | import macho.loadcommands.dylib 30 | 31 | def _enable_vmaddr(): 32 | import macho.vmaddr 33 | import macho.loadcommands.segment 34 | 35 | def _enable_symbol(): 36 | import macho.symbol 37 | import macho.loadcommands.symtab 38 | import macho.loadcommands.dysymtab 39 | import macho.loadcommands.dyld_info 40 | import macho.sections.symbol_ptr 41 | 42 | def _enable_encryption(): 43 | import macho.loadcommands.encryption_info 44 | 45 | def _enable_strings(): 46 | import macho.sections.cstring 47 | import macho.sections.cfstring 48 | 49 | def _enable_objc(): 50 | _enable_symbol() 51 | import macho.sections.objc.classlist 52 | import macho.sections.objc.protolist 53 | import macho.sections.objc.catlist 54 | 55 | def _enable_all(): 56 | _enable_symbol() 57 | _enable_vmaddr() 58 | _enable_encryption() 59 | _enable_libord() 60 | _enable_strings() 61 | _enable_objc() 62 | 63 | __features = { 64 | 'libord': _enable_libord, 65 | 'symbol': _enable_symbol, 66 | 'vmaddr': _enable_vmaddr, 67 | 'encryption': _enable_encryption, 68 | 'objc': _enable_objc, 69 | 'all': _enable_all, 70 | 'strings': _enable_strings, 71 | } 72 | 73 | 74 | def enable(*features): 75 | '''Enable features. 76 | 77 | Currently, the following features are supported: 78 | 79 | +------------------+-------------------------------------------------+--------------------------------------------+ 80 | | Feature | Purpose | Modules imported | 81 | +==================+=================================================+============================================+ 82 | | ``'libord'`` | Finding a | :mod:`macho.loadcommands.dylib` | 83 | | | :class:`~macho.loadcommands.dylib.DylibCommand` | | 84 | | | from the library ordinal. | | 85 | +------------------+-------------------------------------------------+--------------------------------------------+ 86 | | ``'vmaddr'`` | Convert VM addresses to and from file offsets. | :mod:`macho.vmaddr`, | 87 | | | | :mod:`macho.loadcommands.segment` | 88 | +------------------+-------------------------------------------------+--------------------------------------------+ 89 | | ``'symbol'`` | Retrieve :class:`~sym.Symbol`\s of the file. | :mod:`macho.symbol`, | 90 | | | | :mod:`macho.loadcommands.symtab`, | 91 | | | | :mod:`macho.loadcommands.dysymtab`, | 92 | | | | :mod:`macho.loadcommands.dyld_info`, | 93 | | | | :mod:`macho.sections.symbol_ptr` | 94 | +------------------+-------------------------------------------------+--------------------------------------------+ 95 | | ``'encryption'`` | Checking if a location is encrypted. | :mod:`macho.loadcommands.encryption_info` | 96 | +------------------+-------------------------------------------------+--------------------------------------------+ 97 | | ``'strings'`` | Retrieve string constants (as | :mod:`macho.sections.cstring`, | 98 | | | :class:`~sym.Symbol`\s) of the file. | :mod:`macho.sections.cfstring` | 99 | +------------------+-------------------------------------------------+--------------------------------------------+ 100 | | ``'objc'`` | Parse Objective-C structures. | :mod:`macho.sections.objc.classlist`, | 101 | | | | :mod:`macho.sections.objc.protolist`, | 102 | | | | :mod:`macho.sections.objc.catlist` | 103 | +------------------+-------------------------------------------------+--------------------------------------------+ 104 | | ``'all'`` | Turn on all the above features | 105 | +------------------+----------------------------------------------------------------------------------------------+ 106 | 107 | Once a feature is enabled, it cannot be disabled later. Note that some 108 | features depends on others to work, so turn it on will implicitly import 109 | their dependency as well. For instance, using the ``'symbol'`` feature will 110 | make ``'vmaddr'`` and ``'libord'`` also available. 111 | 112 | If an unrecognized feature is provided, it will be ignored. 113 | 114 | ''' 115 | for feature in features: 116 | if feature in __features: 117 | __features[feature]() 118 | 119 | -------------------------------------------------------------------------------- /macho/loadcommands/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py ... Initialization 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | __all__ = ['loadcommand', 'segment', 'dylib', 'encryption_info', 'symtab', 'dyld_info', 'dysymtab'] 19 | -------------------------------------------------------------------------------- /macho/loadcommands/dyld_info.py: -------------------------------------------------------------------------------- 1 | # 2 | # dyld_info.py ... LC_DYLD_INFO[_ONLY] load command. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from .loadcommand import LoadCommand, LC_DYLD_INFO 20 | from macho.symbol import Symbol, SYMTYPE_UNDEFINED, SYMTYPE_GENERIC 21 | from macho.utilities import peekStruct, readULeb128, readSLeb128, readString 22 | import macho.loadcommands.segment 23 | 24 | def _bind(machO, size, symbols): 25 | libord = 0 26 | sym = None 27 | addr = 0 28 | 29 | f = machO.file 30 | 31 | end = f.tell() + size 32 | 33 | lcs_all = machO.loadCommands.all 34 | ptrwidth = machO.pointerWidth 35 | 36 | allSegs = lcs_all('className', 'SegmentCommand') 37 | 38 | while f.tell() < end: 39 | c = f.read_byte() 40 | imm = c & 0xf # BIND_IMMEDIATE_MASK 41 | opcode = c & 0xf0 # BIND_OPCODE_MASK 42 | 43 | if opcode == 0: # BIND_OPCODE_DONE 44 | pass 45 | 46 | elif opcode == 0x10: # BIND_OPCODE_SET_DYLIB_ORDINAL_IMM 47 | libord = imm 48 | 49 | elif opcode == 0x20: # BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB 50 | libord = readULeb128(f) 51 | 52 | elif opcode == 0x30: # BIND_OPCODE_SET_DYLIB_SPECIAL_IMM 53 | libord = (imm | 0xf0) if imm else 0 54 | 55 | elif opcode == 0x40: # BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM 56 | sym = readString(f) 57 | 58 | elif opcode == 0x50: # BIND_OPCODE_SET_TYPE_IMM 59 | pass 60 | 61 | elif opcode == 0x60: # BIND_OPCODE_SET_ADDEND_SLEB 62 | readSLeb128(f) 63 | 64 | elif opcode == 0x70: # BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB 65 | addr = allSegs[imm].vmaddr + readULeb128(f) 66 | 67 | elif opcode == 0x80: # BIND_OPCODE_ADD_ADDR_ULEB 68 | addr += readULeb128(f) 69 | 70 | elif opcode == 0x90: # BIND_OPCODE_DO_BIND 71 | symbols.append(Symbol(sym, addr, SYMTYPE_UNDEFINED, libord=libord)) 72 | addr += ptrwidth 73 | 74 | elif opcode == 0xa0: # BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB 75 | symbols.append(Symbol(sym, addr, SYMTYPE_UNDEFINED, libord=libord)) 76 | addr += ptrwidth + readULeb128(f) 77 | 78 | elif opcode == 0xb0: # BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED 79 | symbols.append(Symbol(sym, addr, SYMTYPE_UNDEFINED, libord=libord)) 80 | addr += (imm+1) * ptrwidth 81 | 82 | elif opcode == 0xc0: # BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB 83 | count = readULeb128(f) 84 | skip = readULeb128(f) 85 | for i in range(count): 86 | symbols.append(Symbol(sym, addr, SYMTYPE_UNDEFINED, libord=libord)) 87 | addr += skip + ptrwidth 88 | 89 | 90 | def _recursiveProcessExportTrieNode(f, start, cur, end, prefix, symbols): 91 | if cur < end: 92 | f.seek(cur) 93 | termSize = f.read_byte() 94 | if termSize: 95 | sym = prefix 96 | readULeb128(f) 97 | addr = readULeb128(f) 98 | symbols.append(Symbol(sym, addr, SYMTYPE_GENERIC, extern=True)) 99 | f.seek(cur + termSize + 1) 100 | childCount = f.read_byte() 101 | for i in range(childCount): 102 | suffix = readString(f) 103 | offset = readULeb128(f) 104 | lastPos = f.tell() 105 | _recursiveProcessExportTrieNode(f, start, start + offset, end, prefix + suffix, symbols) 106 | f.seek(lastPos) 107 | 108 | 109 | class DyldInfoCommand(LoadCommand): 110 | ''' 111 | The dyld info (only) load command. 112 | 113 | This class performs decoding of the 114 | :const:`~macho.loadcommands.loadcommand.LC_DYLD_INFO` and 115 | ``LC_DYLD_INFO_ONLY`` load commands. These two commands are introduced in 116 | Mac OS X 10.6 and iPhone OS 3.1 to supersede the 117 | :const:`~macho.loadcommands.loadcommand.LC_DYSYMTAB` command. 118 | These, known as *compressed dyld info* to Apple, includes a domain-specific 119 | assembly language to encode address binding, and a trie to store the export 120 | symbols. 121 | 122 | When analyzed, the symbols will be added back to the Mach-O object. See the 123 | :mod:`macho.symbol` module for how to access these symbols. 124 | ''' 125 | 126 | def analyze(self, machO): 127 | (rebaseOff, rebaseSize, bindOff, bindSize, weakBindOff, weakBindSize, 128 | lazyBindOff, lazyBindSize, exportOff, exportSize) = peekStruct(machO.file, machO.makeStruct('10L')) 129 | symbols = [] 130 | 131 | if bindSize: 132 | machO.seek(bindOff) 133 | _bind(machO, bindSize, symbols) 134 | 135 | if weakBindSize: 136 | machO.seek(weakBindOff) 137 | _bind(machO, weakBindSize, symbols) 138 | 139 | if lazyBindSize: 140 | machO.seek(lazyBindOff) 141 | _bind(machO, lazyBindSize, symbols) 142 | 143 | if exportSize: 144 | exportOff += machO.origin 145 | _recursiveProcessExportTrieNode(machO.file, exportOff, exportOff, exportOff + exportSize, "", symbols) 146 | 147 | machO.addSymbols(symbols) 148 | 149 | 150 | LoadCommand.registerFactory(LC_DYLD_INFO, DyldInfoCommand) 151 | 152 | -------------------------------------------------------------------------------- /macho/loadcommands/dylib.py: -------------------------------------------------------------------------------- 1 | # 2 | # dylib.py ... LC_LOAD_DYLIB load command. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from macho.loadcommands.loadcommand import LoadCommand, LC_LOAD_DYLIB, LC_ID_DYLIB, LC_LOAD_WEAK_DYLIB, LC_REEXPORT_DYLIB, LC_LAZY_LOAD_DYLIB, LC_LOAD_UPWARD_DYLIB 20 | from macho.utilities import peekString, peekStruct 21 | from monkey_patching import patch 22 | from macho.macho import MachO 23 | 24 | class DylibCommand(LoadCommand): 25 | """A dylib load command. This can represent any of these commands: 26 | 27 | .. hlist:: 28 | 29 | * :const:`~macho.loadcommands.loadcommand.LC_LOAD_DYLIB` (``0x0c``) 30 | * :const:`~macho.loadcommands.loadcommand.LC_ID_DYLIB` (``0x0d``) 31 | * :const:`~macho.loadcommands.loadcommand.LC_LOAD_WEAK_DYLIB` (``0x18``) 32 | * :const:`~macho.loadcommands.loadcommand.LC_REEXPORT_DYLIB` (``0x1f``) 33 | * :const:`~macho.loadcommands.loadcommand.LC_LAZY_LOAD_DYLIB` (``0x20``) 34 | * :const:`~macho.loadcommands.loadcommand.LC_LOAD_UPWARD_DYLIB` (``0x23``) 35 | 36 | .. attribute:: name 37 | 38 | The name of the dynamic library. 39 | 40 | .. attribute:: timestamp 41 | 42 | The timestamp of the dynamic library. 43 | 44 | .. attribute:: version 45 | 46 | The version of the dynamic library. 47 | 48 | .. attribute:: minVersion 49 | 50 | The compatibility version of the dynamic library. 51 | 52 | """ 53 | 54 | def analyze(self, machO): 55 | (offset, self.timestamp, self.version, self.minVersion) = peekStruct(machO.file, machO.makeStruct('4L')) 56 | self.name = peekString(machO.file, position=offset + machO.origin + self.offset - 8) 57 | 58 | def __str__(self): 59 | return "".format(self.name) 60 | 61 | for i in (LC_LOAD_DYLIB, LC_ID_DYLIB, LC_LOAD_WEAK_DYLIB, LC_REEXPORT_DYLIB, LC_LAZY_LOAD_DYLIB, LC_LOAD_UPWARD_DYLIB): 62 | LoadCommand.registerFactory(i, DylibCommand) 63 | 64 | @patch 65 | class MachO_FromLibord(MachO): 66 | """This patch defines a single convenient function :meth:`dylibFromLibord` 67 | which can convert a library ordinal to a :class:`DylibCommand` object.""" 68 | 69 | def dylibFromLibord(self, libord): 70 | """Converts library ordinal to a :class:`DylibCommand` object. Returns 71 | ``None`` if the input is invalid.""" 72 | 73 | if libord < 0: 74 | return None 75 | 76 | lcs = self.loadCommands 77 | if not libord: 78 | return lcs.any1('cmd', LC_ID_DYLIB) 79 | 80 | else: 81 | for lc in lcs: 82 | if lc.cmd in (LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, LC_LOAD_UPWARD_DYLIB): 83 | libord -= 1 84 | if not libord: 85 | return lc 86 | return None 87 | 88 | 89 | -------------------------------------------------------------------------------- /macho/loadcommands/dysymtab.py: -------------------------------------------------------------------------------- 1 | # 2 | # dysymtab.py ... LC_DYSYMTAB load command. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from macho.loadcommands.loadcommand import LoadCommand, LC_DYSYMTAB 20 | from macho.macho import MachOError 21 | from macho.symbol import Symbol 22 | from macho.utilities import peekPrimitives, peekStruct, peekStructs 23 | from struct import Struct 24 | 25 | import macho.loadcommands.symtab 26 | 27 | class DySymtabCommand(LoadCommand): 28 | """ 29 | The :const:`~macho.loadcommands.loadcommand.LC_DYSYMTAB` (dynamic symbol 30 | table) load command. When analyzed, the external symbols will be added back 31 | to the Mach-O object. See the :mod:`macho.symbol` module for how to access 32 | these symbols. 33 | 34 | This command also provides access to the indirect symbol table, which is 35 | needed when the :class:`~macho.sections.symbol_ptr.SymbolPtrSection` 36 | section is analyzed. 37 | """ 38 | 39 | def _exrelIter(self, machO, extreloff, count): 40 | reloc_res = peekStructs(machO.file, machO.makeStruct('LL'), count, position=extreloff+machO.origin) 41 | isBigEndian = machO.endian == '>' 42 | 43 | for r_address, r_extra in reloc_res: 44 | if r_address & 0x80000000: 45 | # it's a scattered_relocation_info 46 | raise MachOError('Analyzing scattered_relocation_info not implemented yet.') 47 | else: 48 | if isBigEndian: 49 | r_symbolnum = r_extra >> 8 50 | r_extern = r_extra & 0x10 51 | else: 52 | r_symbolnum = r_extra & 0xFFFFFF 53 | r_extern = r_extra & 0x8000000 54 | 55 | if not r_extern: 56 | raise MachOError('Analyzing non-extern relocation not implemented yet.') 57 | 58 | yield (r_symbolnum, r_address) 59 | 60 | 61 | 62 | 63 | def analyze(self, machO): 64 | # Make sure the SYMTAB command is ready. 65 | if not all(lc.isAnalyzed for lc in machO.loadCommands.all('className', 'SymtabCommand')): 66 | return True 67 | 68 | ( ilocalsym, nlocalsym, 69 | iextdefsym, nextdefsym, 70 | iundefsym, nundefsym, 71 | tocoff, ntoc, 72 | modtaboff, nmodtab, 73 | extrefsymoff, nextrefsyms, 74 | self.indirectsymoff, nindirectsyms, 75 | extreloff, nextrel, 76 | locreloff, nlocrel) = peekStruct(machO.file, machO.makeStruct('18L')) 77 | 78 | if nextrel: 79 | machO.provideAddresses(self._exrelIter(machO, extreloff, nextrel)) 80 | 81 | 82 | def indirectSymbols(self, start, count, machO): 83 | '''Get symbol indices from the indirect symbol table, given the *start* 84 | index and the *count* of indices to retrieve.''' 85 | 86 | offset = self.indirectsymoff + start * 4 + machO.origin 87 | return peekPrimitives(machO.file, 'L', count, machO.endian, machO.is64bit, position=offset) 88 | 89 | 90 | LoadCommand.registerFactory(LC_DYSYMTAB, DySymtabCommand) 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /macho/loadcommands/encryption_info.py: -------------------------------------------------------------------------------- 1 | # 2 | # encryption_info.py ... LC_ENCRYPTION_INFO load command. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from macho.loadcommands.loadcommand import LoadCommand, LC_ENCRYPTION_INFO 20 | from macho.macho import MachO 21 | from monkey_patching import patch 22 | 23 | class EncryptionInfoCommand(LoadCommand): 24 | """The encryption info load command. This load command marks a range of file 25 | offset as encrypted. 26 | 27 | An encrypted region cannot be used (the decryption procedure is in the 28 | kernel). Users may first check if a file offset is encrypted. 29 | 30 | .. attribute:: cryptoff 31 | 32 | The starting file offset of this encryption region. 33 | 34 | .. attribute:: cryptsize 35 | 36 | The size of this encryption region. 37 | 38 | .. attribute:: cryptid 39 | 40 | The method of encryption. 41 | 42 | """ 43 | 44 | def analyze(self, machO): 45 | (self.cryptoff, self.cryptsize, self.cryptid) = machO.readFormatStruct('3L') 46 | 47 | def __str__(self): 48 | return "".format(self.cryptid, self.cryptoff) 49 | 50 | def encrypted(self, offset): 51 | """Checks if the offset is encrypted.""" 52 | return self.cryptid != 0 and self.cryptoff <= offset < self.cryptoff + self.cryptsize 53 | 54 | 55 | LoadCommand.registerFactory(LC_ENCRYPTION_INFO, EncryptionInfoCommand) 56 | 57 | @patch 58 | class MachO_EncryptionPatches(MachO): 59 | """This patch defines a single convenient function :meth:`encrypted` which 60 | can check if a file offset is encrypted.""" 61 | 62 | def encrypted(self, fileoff): 63 | """Checks if the file offset is in any encrypted region.""" 64 | encCmds = self.loadCommands.all('className', 'EncryptionInfoCommand') 65 | return any(lc.encrypted(fileoff) for lc in encCmds) 66 | 67 | -------------------------------------------------------------------------------- /macho/loadcommands/loadcommand.py: -------------------------------------------------------------------------------- 1 | # 2 | # loadcommand.py ... Base class for all load commands 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from factory import factory 20 | 21 | ( 22 | LC_SEGMENT, # 0x1, segment of this file to be mapped 23 | LC_SYMTAB, # 0x2, link-edit stab symbol table info 24 | LC_SYMSEG, # 0x3, link-edit gdb symbol table info (obsolete) 25 | LC_THREAD, # 0x4, thread 26 | LC_UNIXTHREAD, # 0x5, unix thread (includes a stack) 27 | LC_LOADFVMLIB, # 0x6, load a specified fixed VM shared library 28 | LC_IDFVMLIB, # 0x7, fixed VM shared library identification 29 | LC_IDENT, # 0x8, object identification info (obsolete) 30 | LC_FVMFILE, # 0x9, fixed VM file inclusion (internal use) 31 | LC_PREPAGE, # 0xa, prepage command (internal use) 32 | LC_DYSYMTAB, # 0xb, dynamic link-edit symbol table info 33 | LC_LOAD_DYLIB, # 0xc, load a dynamically linked shared library 34 | LC_ID_DYLIB, # 0xd, dynamically linked shared lib ident 35 | LC_LOAD_DYLINKER, # 0xe, load a dynamic linker 36 | LC_ID_DYLINKER, # 0xf, dynamic linker identification 37 | LC_PREBOUND_DYLIB, # 0x10, modules prebound for a dynamically 38 | 39 | LC_ROUTINES, # 0x11, image routines 40 | LC_SUB_FRAMEWORK, # 0x12, sub framework 41 | LC_SUB_UMBRELLA, # 0x13, sub umbrella 42 | LC_SUB_CLIENT, # 0x14, sub client 43 | LC_SUB_LIBRARY, # 0x15, sub library 44 | LC_TWOLEVEL_HINTS, # 0x16, two-level namespace lookup hints 45 | LC_PREBIND_CKSUM, # 0x17, prebind checksum 46 | 47 | LC_LOAD_WEAK_DYLIB, # 0x18, load a dynamically linked shared library that is allowed to be missing (all symbols are weak imported) 48 | 49 | LC_SEGMENT_64, # 0x19, 64-bit segment of this file to be mapped 50 | LC_ROUTINES_64, # 0x1a, 64-bit image routines 51 | LC_UUID, # 0x1b, the uuid 52 | LC_RPATH, # 0x1c, runpath additions 53 | LC_CODE_SIGNATURE, # 0x1d, local of code signature 54 | LC_SEGMENT_SPLIT_INFO,# 0x1e, local of info to split segments 55 | LC_REEXPORT_DYLIB, # 0x1f, load and re-export dylib 56 | LC_LAZY_LOAD_DYLIB, # 0x20, delay load of dylib until first use 57 | LC_ENCRYPTION_INFO, # 0x21, encrypted segment information 58 | LC_DYLD_INFO, # 0x22, compressed dyld information 59 | LC_LOAD_UPWARD_DYLIB, # 0x23, load upward dylib 60 | 61 | LC_VERSION_MIN_MACOSX, # 0x24, build for MacOSX min OS version 62 | LC_VERSION_MIN_IPHONEOS, # 0x25, build for iPhoneOS min OS version 63 | LC_FUNCTION_STARTS, # 0x26, compressed table of function start addresses 64 | LC_DYLD_ENVIRONMENT, # 0x27, string for dyld to treat like environment variable 65 | ) = range(1, 0x28) 66 | 67 | @factory 68 | class LoadCommand(object): 69 | """An abstract load command. 70 | 71 | This class adopts the :func:`factory.factory` decorator. Subclasses should 72 | override the :meth:`analyze` method to collect data from the Mach-O file. 73 | 74 | .. attribute:: cmd 75 | 76 | Get the numerical value of this load command. 77 | 78 | .. attribute:: offset 79 | 80 | Get the file offset of this load command, after the 8-byte common 81 | header. 82 | 83 | .. attribute:: isAnalyzed 84 | 85 | Returns whether this load command has been completely analyzed. 86 | 87 | """ 88 | 89 | def analyze(self, machO): 90 | """Analyze the load command. 91 | 92 | The file pointer is guaranteed to be at the desired offset when this 93 | method is called from :meth:`macho.macho.MachO.open`. 94 | 95 | Return a true value to require further analysis.""" 96 | 97 | return None 98 | 99 | def __init__(self, cmd, size, offset): 100 | self.cmd = cmd 101 | self.size = size 102 | self.offset = offset 103 | self.isAnalyzed = False 104 | 105 | __names = [ 106 | 'SEGMENT', # 0x1, segment of this file to be mapped 107 | 'SYMTAB', # 0x2, link-edit stab symbol table info 108 | 'SYMSEG', # 0x3, link-edit gdb symbol table info (obsolete) 109 | 'THREAD', # 0x4, thread 110 | 'UNIXTHREAD', # 0x5, unix thread (includes a stack) 111 | 'LOADFVMLIB', # 0x6, load a specified fixed VM shared library 112 | 'IDFVMLIB', # 0x7, fixed VM shared library identification 113 | 'IDENT', # 0x8, object identification info (obsolete) 114 | 'FVMFILE', # 0x9, fixed VM file inclusion (internal use) 115 | 'PREPAGE', # 0xa, prepage command (internal use) 116 | 'DYSYMTAB', # 0xb, dynamic link-edit symbol table info 117 | 'LOAD_DYLIB', # 0xc, load a dynamically linked shared library 118 | 'ID_DYLIB', # 0xd, dynamically linked shared lib ident 119 | 'LOAD_DYLINKER', # 0xe, load a dynamic linker 120 | 'ID_DYLINKER', # 0xf, dynamic linker identification 121 | 'PREBOUND_DYLIB', # 0x10, modules prebound for a dynamically 122 | 123 | 'ROUTINES', # 0x11, image routines 124 | 'SUB_FRAMEWORK', # 0x12, sub framework 125 | 'SUB_UMBRELLA', # 0x13, sub umbrella 126 | 'SUB_CLIENT', # 0x14, sub client 127 | 'SUB_LIBRARY', # 0x15, sub library 128 | 'TWOLEVEL_HINTS', # 0x16, two-level namespace lookup hints 129 | 'PREBIND_CKSUM', # 0x17, prebind checksum 130 | 131 | 'LOAD_WEAK_DYLIB', # 0x18, load a dynamically linked shared library that is allowed to be missing (all symbols are weak imported) 132 | 133 | 'SEGMENT_64', # 0x19, 64-bit segment of this file to be mapped 134 | 'ROUTINES_64', # 0x1a, 64-bit image routines 135 | 'UUID', # 0x1b, the uuid 136 | 'RPATH', # 0x1c, runpath additions 137 | 'CODE_SIGNATURE', # 0x1d, local of code signature 138 | 'SEGMENT_SPLIT_INFO',# 0x1e, local of info to split segments 139 | 'REEXPORT_DYLIB', # 0x1f, load and re-export dylib 140 | 'LAZY_LOAD_DYLIB', # 0x20, delay load of dylib until first use 141 | 'ENCRYPTION_INFO', # 0x21, encrypted segment information 142 | 'DYLD_INFO', # 0x22, compressed dyld information 143 | 'LOAD_UPWARD_DYLIB', # 0x23, load upward dylib 144 | 145 | 'VERSION_MIN_MACOSX', # 0x24, build for MacOSX min OS version 146 | 'VERSION_MIN_IPHONEOS', # 0x25, build for iPhoneOS min OS version 147 | 'FUNCTION_STARTS', # 0x26, compressed table of function start addresses 148 | 'DYLD_ENVIRONMENT', # 0x27, string for dyld to treat like environment variable 149 | ] 150 | __names_map = dict((j, i) for i, j in enumerate(__names)) 151 | 152 | @classmethod 153 | def cmdname(cls, cmd): 154 | """Get the name of a load command given the numeric value. Returns 155 | ``hex(cmd)`` if not found.""" 156 | cmd &= ~0x80000000 157 | if 1 <= cmd <= len(cls.__names): 158 | return cls.__names[cmd-1] 159 | else: 160 | return hex(cmd) 161 | 162 | @classmethod 163 | def cmdindex(cls, name): 164 | """Get the numeric value from the name of the load command.""" 165 | return cls.__names_map.get(name, -1) + 1 166 | 167 | def __str__(self): 168 | return "".format(self.cmdname(self.cmd), self.offset) 169 | -------------------------------------------------------------------------------- /macho/loadcommands/segment.py: -------------------------------------------------------------------------------- 1 | # 2 | # segment.py ... LC_SEGMENT load command. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from macho.loadcommands.loadcommand import LoadCommand, LC_SEGMENT, LC_SEGMENT_64 20 | from macho.utilities import fromStringz, peekStructs, peekString, readStruct, peekStruct 21 | from macho.macho import MachO 22 | from factory import factory 23 | from macho.sections.section import Section 24 | from data_table import DataTable 25 | from monkey_patching import patch 26 | from macho.vmaddr import Mapping 27 | import struct 28 | 29 | class SegmentCommand(LoadCommand): 30 | '''The segment load command. This can represent the 32-bit 31 | :const:`~macho.loadcommands.loadcommand.LC_SEGMENT` command (``0x01``) or 32 | the 64-bit :const:`~macho.loadcommands.loadcommand.LC_SEGMENT_64` command 33 | (``0x19``). 34 | 35 | A segment consists of many sections, which contain actual code and data. 36 | 37 | .. note:: 38 | 39 | Sections falling in the encrypted region will not be analyzed if the 40 | :mod:`macho.loadcommands.encryption_info` module is imported. 41 | 42 | 43 | .. attribute:: segname 44 | 45 | Get the segment name. 46 | 47 | .. attribute:: vmaddr 48 | 49 | Get the base VM address of this segment. 50 | 51 | .. attribute:: maxprot 52 | 53 | Maximum VM protection of this segment when mapped to memory. 54 | 55 | .. attribute:: initprot 56 | 57 | Initial VM protection of this segment when mapped to memory. 58 | 59 | .. attribute:: sections 60 | 61 | A :class:`~data_table.DataTable` of all 62 | :class:`~macho.sections.section.Section`\\s within this segment. This 63 | table contains two columns: ``'className'`` and ``'sectname'``. 64 | 65 | ''' 66 | 67 | def _loadSections(self, machO): 68 | segStruct = machO.makeStruct('16s4^2i2L') 69 | sectStruct = machO.makeStruct(Section.STRUCT_FORMAT) 70 | (segname, self.vmaddr, self._vmsize, self._fileoff, self._filesize, self.maxprot, self.initprot, nsects, _) = readStruct(machO.file, segStruct) 71 | 72 | self.segname = fromStringz(segname) 73 | 74 | machO_fileOrigin = machO._fileOrigin 75 | 76 | sectVals = peekStructs(machO.file, sectStruct, count=nsects) # get all section headers 77 | sectionsList = (Section.createSection(i) for i in sectVals) # convert all headers into Section objects 78 | sections = DataTable('className', 'sectname', 'ftype') 79 | for s in sectionsList: 80 | if s.offset < machO_fileOrigin: 81 | s.offset += machO_fileOrigin 82 | sections.append(s, className=type(s).__name__, sectname=s.sectname, ftype=s.ftype) 83 | self.sections = sections 84 | self._hasAnalyzedSections = False 85 | self._shouldImportMappings = machO.mappings.mutable 86 | 87 | 88 | def _analyzeSections(self, machO): 89 | # we need to make sure the section is not encrypted. 90 | self_sections = self.sections 91 | machO_encrypted = getattr(machO, 'encrypted', lambda x: False) 92 | machO_seek = machO.seek 93 | 94 | while not all(s.isAnalyzed for s in self_sections): 95 | for s in self_sections: 96 | if not s.isAnalyzed: 97 | offset = s.offset 98 | if s.isZeroFill or machO_encrypted(offset): 99 | s.isAnalyzed = True 100 | else: 101 | machO_seek(offset) 102 | s.isAnalyzed = not s.analyze(self, machO) 103 | 104 | self._hasAnalyzedSections = True 105 | 106 | 107 | def analyze(self, machO): 108 | # make sure all encryption_info commands are ready if they exist. 109 | if not all(lc.isAnalyzed for lc in machO.loadCommands.all('className', 'EncryptionInfoCommand')): 110 | return True 111 | 112 | # load sections if they aren't loaded yet. 113 | if not hasattr(self, 'sections'): 114 | self._loadSections(machO) 115 | 116 | # import mappings from sections if not closed yet 117 | if self._shouldImportMappings: 118 | addMapping = machO.mappings.add 119 | for s in self.sections: 120 | if not s.isZeroFill: 121 | addMapping(Mapping(s.addr, s.size, s.offset, self.maxprot, self.initprot)) 122 | machO.mappings.optimize() 123 | self._shouldImportMappings = False 124 | 125 | # make sure all segments are ready 126 | allSegments = machO.loadCommands.all('className', type(self).__name__) 127 | if not all(hasattr(seg, 'vmaddr') for seg in allSegments): 128 | return True 129 | 130 | # now analyze the sections. 131 | if not self._hasAnalyzedSections: 132 | self._analyzeSections(machO) 133 | 134 | 135 | def __str__(self): 136 | return "".format(self.segname, ', '.join(map(str, self._sections.values()))) 137 | 138 | 139 | 140 | 141 | LoadCommand.registerFactory(LC_SEGMENT, SegmentCommand) 142 | LoadCommand.registerFactory(LC_SEGMENT_64, SegmentCommand) 143 | 144 | 145 | @patch 146 | class MachO_SegmentCommandPatches(MachO): 147 | """This patch to the :class:`~macho.macho.MachO` class defines several 148 | methods that operate over all segments.""" 149 | 150 | def segment(self, segname): 151 | """Find a :class:`SegmentCommand` with the specified *segname*.""" 152 | for segment in self.loadCommands.all('className', 'SegmentCommand'): 153 | if segment.segname == segname: 154 | return segment 155 | return None 156 | 157 | 158 | def allSections(self, idtype, sectid): 159 | '''Returns an iterable of all :class:`~macho.sections.section.Section`\\s 160 | having the specified section identifier. One of the following 161 | combinations of *idtype* and *sectid* may be used: 162 | 163 | +-----------------+------------------------------------------------------+ 164 | | *idtype* | *sectid* | 165 | +=================+======================================================+ 166 | | ``'sectname'`` | Section name, e.g. ``'__cstring'``. | 167 | +-----------------+------------------------------------------------------+ 168 | | ``'className'`` | Class name of the section, e.g. | 169 | | | ``'CStringSection'``. | 170 | +-----------------+------------------------------------------------------+ 171 | | ``'ftype'`` | Numerical value for the section type, e.g. | 172 | | | :const:`~macho.sections.section.S_CSTRING_LITERALS`. | 173 | +-----------------+------------------------------------------------------+ 174 | ''' 175 | 176 | for seg in self.loadCommands.all('className', 'SegmentCommand'): 177 | for sect in seg.sections.all(idtype, sectid): 178 | yield sect 179 | 180 | def anySection(self, idtype, sectid): 181 | '''Get any :class:`~macho.sections.section.Section` having the specified 182 | section identifier. Returns ``None`` if no such section exists.''' 183 | for sect in self.allSections(idtype, sectid): 184 | return sect 185 | return None 186 | 187 | def anySectionProperty(self, idtype, sectid, prop, default=None): 188 | """Retrieve a the section, and returns its property *prop* if exists. 189 | 190 | Returns an *default* if the section does not exist. Returns ``None`` if 191 | the section is not analyzed. 192 | """ 193 | s = self.anySection(idtype, sectid) 194 | if not s: 195 | return default 196 | elif not s.isAnalyzed: 197 | return None 198 | else: 199 | return getattr(s, prop) 200 | -------------------------------------------------------------------------------- /macho/loadcommands/symtab.py: -------------------------------------------------------------------------------- 1 | # 2 | # symtab.py ... LC_SYMTAB load command. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from macho.loadcommands.loadcommand import LoadCommand, LC_SYMTAB 20 | from macho.symbol import Symbol, SYMTYPE_UNDEFINED, SYMTYPE_GENERIC 21 | from macho.utilities import peekStruct, peekStructs, peekString 22 | 23 | 24 | class SymtabCommand(LoadCommand): 25 | """ 26 | The :const:`~macho.loadcommands.loadcommand.LC_SYMTAB` load command. When 27 | analyzed, the symbols will be added back to the Mach-O object. See the 28 | :mod:`macho.symbol` module for how to access these symbols. 29 | """ 30 | 31 | def analyze(self, machO): 32 | symtabStruct = machO.makeStruct('4L') 33 | nlistStruct = machO.makeStruct('LBBH^') 34 | 35 | (symoff, nsyms, stroff, _) = peekStruct(machO.file, symtabStruct) 36 | 37 | # Get all nlist structs 38 | origin = machO.origin 39 | nlists = peekStructs(machO.file, nlistStruct, count=nsyms, position=symoff+origin) 40 | 41 | # Now analyze the nlist structs 42 | symbols = [] 43 | for (ordinal, (idx, typ, sect, desc, value)) in enumerate(nlists): 44 | string = peekString(machO.file, position=stroff+idx+origin) 45 | libord = (desc >> 8) & 0xff # GET_LIBRARY_ORDINAL 46 | extern = bool(typ & 1) # N_EXT 47 | symtype = SYMTYPE_GENERIC if (typ & 0xe) else SYMTYPE_UNDEFINED 48 | isThumb = bool(desc & 8) # N_ARM_THUMB_DEF 49 | if isThumb: 50 | value &= ~1 51 | symbol = Symbol(string, value, symtype, ordinal, libord, extern, isThumb) 52 | symbols.append(symbol) 53 | 54 | # add those symbols back into the Mach-O. 55 | machO.addSymbols(symbols) 56 | 57 | LoadCommand.registerFactory(LC_SYMTAB, SymtabCommand) 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /macho/loader.py: -------------------------------------------------------------------------------- 1 | # 2 | # loader.py ... Load Mach-O file with specified path 3 | # Copyright (C) 2011 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from .macho import MachO 20 | from .arch import Arch 21 | from .sharedcache import DyldSharedCache 22 | from contextlib import contextmanager 23 | from os.path import join, isfile, sep, altsep 24 | 25 | __fnmethods = [ 26 | join, 27 | lambda sdk, fn: join(sdk, 'System', 'Library', 'Frameworks', fn+'.framework', fn), 28 | lambda sdk, fn: join(sdk, 'System', 'Library', 'PrivateFrameworks', fn+'.framework', fn), 29 | lambda sdk, fn: join(sdk, 'usr', 'lib', fn+'.dylib'), 30 | lambda sdk, fn: join(sdk, 'Applications', fn+'.app', fn), 31 | lambda sdk, fn: join(sdk, 'System', 'Library', 'CoreServices', fn+'.app', fn), 32 | ] 33 | 34 | def __stripLeadingSep(fn): 35 | seps = sep 36 | if altsep: 37 | seps += altsep 38 | return fn.lstrip(seps) 39 | 40 | 41 | def _loadFile(filename, sdk, cache_images_any, arch, lenientArchMatching): 42 | if cache_images_any: 43 | image = cache_images_any('path', filename) 44 | if image: 45 | return image.machO 46 | image = cache_images_any('name', filename) 47 | if image: 48 | return image.machO 49 | 50 | strippedFilename = __stripLeadingSep(filename) 51 | for f in __fnmethods: 52 | fn = f(sdk, filename) 53 | if isfile(fn): 54 | break 55 | else: 56 | fn = filename 57 | 58 | return MachO(fn, arch, lenientArchMatching).__enter__() 59 | 60 | 61 | 62 | class MachOLoader(object): 63 | '''Loads multiple :class:`~macho.macho.MachO` object from Mach-O files or 64 | images in a shared cache by filename. 65 | 66 | Example:: 67 | 68 | with MachOLoader('AudioToolbox', 'CoreMedia', cache='', sdk='/Volumes/Jasper8C148.N90OS/') \\ 69 | as (audioToolbox, coreMedia): 70 | ... 71 | 72 | This class supports the following keywoard arguments in the initializer: 73 | 74 | :param cache: The path, or a :class:`~macho.sharedcache.DyldSharedCache` 75 | object which the loader may load from. If you pass a 76 | :class:`~macho.sharedcache.DyldSharedCache`, it should be already 77 | :meth:`~macho.sharedcache.DyldSharedCache.open`\ ed. 78 | :param sdk: The root folder which the files are searched. Default to ``'/'``. 79 | :param arch: The :class:`~macho.arch.Arch` the images should be in. Default 80 | to ``'armv7'``. 81 | :param lenientArchMatching: Whether arch-matching should be done leniently 82 | (will not affect images loaded from *cache*) 83 | :param endian: Specify endianness of the *cache*. 84 | 85 | The cache file, if not ``None``, is loaded by the following means in order: 86 | 87 | 1. If *cache* is a :attr:`~macho.sharedcache.DyldSharedCache`, use it 88 | directly. 89 | 2. If *cache* is an empty string, load from 90 | ``/System/Library/Caches/com.apple.dyld/dyld_shared_cache_`` 91 | 3. Otherwise, load from ``/`` if exists. 92 | 93 | 4. Load from ````. 94 | 95 | The files are loaded from the following means in order: 96 | 97 | 1. An image matched by ``'path'`` in the *cache*'s 98 | :attr:`~macho.sharedcache.DyldSharedCache.images`. 99 | 2. An image matched by ``'name'`` in the *cache*'s 100 | :attr:`~macho.sharedcache.DyldSharedCache.images`. 101 | 3. The file ``/`` if exists. 102 | 4. The file ``/System/Library/Frameworks/.framework/`` 103 | if exists. 104 | 5. The file ``/System/Library/PrivateFrameworks/.framework/`` 105 | if exists. 106 | 6. The file ``/usr/lib/.dylib`` if exists. 107 | 7. The file ``/Applications/.app/`` if exists. 108 | 8. The file ``/System/Library/CoreServices/.app/`` 109 | if exists. 110 | 111 | 9. The file ````. 112 | 113 | ''' 114 | 115 | def __init__(self, *filenames, **kwargs): 116 | kg = kwargs.get 117 | arch = Arch(kg('arch', 'armv7')) 118 | sdk = kg('sdk', '/') 119 | self._sdk = sdk 120 | self._arch = arch 121 | self._filenames = filenames 122 | 123 | cache = kg('cache') 124 | if cache is not None: 125 | if isinstance(cache, DyldSharedCache): 126 | cachePath = None 127 | elif cache == '': 128 | cachePath = join(sdk, 'System/Library/Caches/com.apple.dyld/dyld_shared_cache_' + str(arch)) 129 | else: 130 | cachePath = join(sdk, cache.lstrip('/')) 131 | if not isfile(cachePath): 132 | cachePath = cache 133 | if cachePath: 134 | cache = DyldSharedCache(cachePath, endian=kg('endian')).__enter__() 135 | 136 | self._cache = cache 137 | self._lenientArchMatching = kg('lenientArchMatching', False) 138 | self._openedMachOs = [None] * len(filenames) 139 | 140 | def __enter__(self): 141 | cache = self._cache 142 | sdk = self._sdk 143 | cache_images_any = cache.images.any if cache else None 144 | arch = self._arch 145 | lenientArchMatching = self._lenientArchMatching 146 | 147 | machOs = self._openedMachOs 148 | try: 149 | for i, fn in enumerate(self._filenames): 150 | machOs[i] = _loadFile(fn, sdk, cache_images_any, arch, lenientArchMatching) 151 | finally: 152 | return machOs 153 | 154 | def __exit__(self, exc_type, exc_value, traceback): 155 | for mo in self._openedMachOs: 156 | if mo: 157 | mo.__exit__(exc_type, exc_value, traceback) 158 | 159 | 160 | -------------------------------------------------------------------------------- /macho/sections/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py ... Initialization 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | __all__ = ['section', 'cstring', 'cfstring', 'symbol_ptr'] 19 | -------------------------------------------------------------------------------- /macho/sections/cfstring.py: -------------------------------------------------------------------------------- 1 | # 2 | # cfstring.py ... __DATA,__cfstring section 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from macho.sections.section import Section 20 | from macho.utilities import peekFixedLengthString 21 | from macho.symbol import Symbol, SYMTYPE_CFSTRING 22 | import macho.loadcommands.segment # to ensure macho.macho.fromVM is defined. 23 | 24 | def _stringReader(machO, addressesAndLengths): 25 | origin = machO.origin 26 | machO_fromVM = machO.fromVM 27 | machO_file = machO.file 28 | for addr, (_, _, strAddr, strLen) in addressesAndLengths: 29 | fileoff = machO_fromVM(strAddr) 30 | string = peekFixedLengthString(machO_file, strLen, position=fileoff+origin) 31 | yield Symbol(string, addr, SYMTYPE_CFSTRING) 32 | 33 | 34 | class CFStringSection(Section): 35 | """The CoreFoundation string (``__DATA,__cfstring``) section.""" 36 | 37 | def analyze(self, segment, machO): 38 | cfstrStruct = machO.makeStruct('4^') 39 | addressesAndLengths = self.asStructs(cfstrStruct, machO, includeAddresses=True) 40 | machO.addSymbols(_stringReader(machO, addressesAndLengths)) 41 | 42 | 43 | Section.registerFactory('__cfstring', CFStringSection) 44 | 45 | -------------------------------------------------------------------------------- /macho/sections/cstring.py: -------------------------------------------------------------------------------- 1 | # 2 | # cstring.py ... __TEXT,__cstring section 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from macho.sections.section import Section, S_CSTRING_LITERALS 20 | from macho.utilities import readString 21 | from macho.symbol import SYMTYPE_CSTRING, Symbol 22 | 23 | def _stringReader(file, curAddr, final): 24 | while curAddr < final: 25 | (string, length) = readString(file, returnLength=True) 26 | if length: 27 | yield Symbol(string, curAddr, SYMTYPE_CSTRING) 28 | curAddr += length+1 29 | 30 | class CStringSection(Section): 31 | """The C string (``__TEXT,__cstring``) section.""" 32 | 33 | def analyze(self, segment, machO): 34 | machO.addSymbols(_stringReader(machO.file, self.addr, self.addr + self.size)) 35 | 36 | 37 | Section.registerFactoryFType(S_CSTRING_LITERALS, CStringSection.byFType) 38 | 39 | 40 | -------------------------------------------------------------------------------- /macho/sections/objc/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py ... Initialization 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | __all__ = ['classlist', 'protolist', 'catlist'] 19 | -------------------------------------------------------------------------------- /macho/sections/objc/catlist.py: -------------------------------------------------------------------------------- 1 | # 2 | # classlist.py ... __DATA,__objc_classlist section. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from macho.sections.section import Section 19 | from ._abi2reader import readCategoryList 20 | from ._abi1reader import analyzeCategoryList 21 | 22 | class ObjCCategoryListSection(Section): 23 | """The Objective-C category list section (``__DATA,__objc_catlist``, etc). 24 | 25 | .. attribute:: categories 26 | 27 | A :class:`~data_table.DataTable` of :class:`~objc.category.Category`\\s, 28 | with the following columns: 29 | 30 | * ``'name'`` (string, the name of the category) 31 | 32 | * ``'base'`` (string, the name of the class the category is patching) 33 | 34 | """ 35 | 36 | def _analyze1(self, machO, classes, protoRefsMap): 37 | cats = self.asStructs(machO.makeStruct('5^L~^'), machO) 38 | # assert False, "Analyzing ABI 1.0 for the __OBJC,__category section is not implemented yet." 39 | self.categories = analyzeCategoryList(machO, cats, classes, protoRefsMap) 40 | 41 | def _analyze2(self, machO, classes, protoRefsMap): 42 | addresses = self.asPrimitives('^', machO) 43 | self.categories = readCategoryList(machO, addresses, classes, protoRefsMap) 44 | 45 | 46 | def analyze(self, segment, machO): 47 | # Make sure the classlist section is ready if exists. 48 | protoRefsMap = machO.anySectionProperty('className', 'ObjCProtoListSection', 'protocols', default={}) 49 | classes = machO.anySectionProperty('className', 'ObjCClassListSection', 'classes', default={}) 50 | 51 | if protoRefsMap is None or classes is None: 52 | return True 53 | if self.segname == '__OBJC': 54 | self._analyze1(machO, classes, protoRefsMap) 55 | else: 56 | self._analyze2(machO, classes, protoRefsMap) 57 | 58 | 59 | Section.registerFactory('__objc_catlist', ObjCCategoryListSection) # __DATA,__objc_catlist 60 | Section.registerFactory('__category_list', ObjCCategoryListSection) # __OBJC2,__category_list 61 | Section.registerFactory('__category', ObjCCategoryListSection) # __OBJC,__category (ABI 1.0) 62 | 63 | -------------------------------------------------------------------------------- /macho/sections/objc/classlist.py: -------------------------------------------------------------------------------- 1 | # 2 | # classlist.py ... __DATA,__objc_classlist section. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from macho.sections.section import Section 19 | from ._abi2reader import readClassList 20 | from ._abi1reader import analyzeClassList 21 | 22 | class ObjCClassListSection(Section): 23 | """The Objective-C class list section (``__DATA,__objc_classlist``, etc). 24 | 25 | .. attribute:: classes 26 | 27 | A :class:`~data_table.DataTable` of :class:`~objc.class_.Class`\\es, 28 | with the following columns: 29 | 30 | * ``'name'`` (unique, string, the name of the class) 31 | * ``'addr'`` (unique, integer, the VM address to the class) 32 | 33 | """ 34 | 35 | def _analyze1(self, machO, protoRefsMap): 36 | addressesAndClassTuples = self.asStructs(machO.makeStruct('12^'), machO, includeAddresses=True) 37 | self.classes = analyzeClassList(machO, addressesAndClassTuples, protoRefsMap) 38 | 39 | 40 | def _analyze2(self, machO, protoRefsMap): 41 | addresses = self.asPrimitives('^', machO) 42 | self.classes = readClassList(machO, addresses, protoRefsMap) 43 | 44 | 45 | def analyze(self, segment, machO): 46 | # Make sure the protocol sections is ready if exists. 47 | 48 | protoRefsMap = machO.anySectionProperty('className', 'ObjCProtoListSection', 'protocols', default={}) 49 | if protoRefsMap is None: 50 | return True 51 | 52 | if self.segname == '__OBJC': 53 | self._analyze1(machO, protoRefsMap) 54 | else: 55 | self._analyze2(machO, protoRefsMap) 56 | 57 | 58 | Section.registerFactory('__objc_classlist', ObjCClassListSection) # __DATA,__objc_classlist 59 | Section.registerFactory('__class_list', ObjCClassListSection) # __OBJC2,__class_list 60 | Section.registerFactory('__class', ObjCClassListSection) # __OBJC,__class 61 | # how about __DATA,__objc_nlclslist? what does it do? 62 | -------------------------------------------------------------------------------- /macho/sections/objc/protolist.py: -------------------------------------------------------------------------------- 1 | # 2 | # protolist.py ... __DATA,__objc_protolist section. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from macho.sections.section import Section 19 | from ._abi2reader import readProtocolList 20 | from ._abi1reader import analyzeProtocolList 21 | 22 | class ObjCProtoListSection(Section): 23 | """The Objective-C protocol list section (``__DATA,__objc_protolist``, etc). 24 | 25 | .. attribute:: protocols 26 | 27 | A :class:`~data_table.DataTable` of :class:`~objc.protocol.Protocol`\\s, 28 | with the following columns: 29 | 30 | * ``'name'`` (string, the name of the protocol) 31 | * ``'addr'`` (unique, integer, the VM address to the protocol) 32 | 33 | """ 34 | 35 | def _analyze1(self, machO): 36 | protos = self.asStructs(machO.makeStruct('5^'), machO, includeAddresses=True) 37 | self.protocols = analyzeProtocolList(machO, protos) 38 | 39 | def _analyze2(self, machO): 40 | # In ABI 2.0, the __DATA,__objc_protolist contains a list of file offsets 41 | # in native width and endian. These offsets will point to a protocol_t 42 | # structure as described in objc-runtime-new.h. 43 | addresses = self.asPrimitives('^', machO) 44 | self.protocols = readProtocolList(machO, addresses) 45 | 46 | def analyze(self, segment, machO): 47 | if self.segname == '__OBJC': 48 | return self._analyze1(machO) 49 | else: 50 | return self._analyze2(machO) 51 | 52 | 53 | Section.registerFactory('__objc_protolist', ObjCProtoListSection) # __DATA,__objc_protolist 54 | Section.registerFactory('__protocol_list', ObjCProtoListSection) # __OBJC2,__protocol_list 55 | Section.registerFactory('__protocol', ObjCProtoListSection) # __OBJC,__protocols (ABI 1.0) 56 | 57 | -------------------------------------------------------------------------------- /macho/sections/section.py: -------------------------------------------------------------------------------- 1 | # 2 | # segment.py ... A section. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from factory import factory, factorySuffix 20 | from macho.utilities import fromStringz, peekStructs, peekPrimitives, decodeStructFormat 21 | from struct import calcsize 22 | from hexdump import hexdump 23 | 24 | ( 25 | S_REGULAR, # 0, regular section 26 | S_ZEROFILL, # 1, zero fill on demand section 27 | S_CSTRING_LITERALS, # 2, section with only literal C strings 28 | S_4BYTE_LITERALS, # 3, section with only 4 byte literals 29 | S_8BYTE_LITERALS, # 4, section with only 8 byte literals 30 | S_LITERAL_POINTERS, # 5, section with only pointers to literals 31 | 32 | S_NON_LAZY_SYMBOL_POINTERS, # 6, section with only non-lazy symbol pointers 33 | S_LAZY_SYMBOL_POINTERS, # 7, section with only lazy symbol pointers 34 | S_SYMBOL_STUBS, # 8, section with only symbol stubs, byte size of stub in the reserved2 field 35 | S_MOD_INIT_FUNC_POINTERS, # 9, section with only function pointers for initialization 36 | S_MOD_TERM_FUNC_POINTERS, # 10, section with only function pointers for termination 37 | S_COALESCED, # 11, section contains symbols that are to be coalesced 38 | S_GB_ZEROFILL, # 12, zero fill on demand section (that can be larger than 4 gigabytes) 39 | S_INTERPOSING, # 13, section with only pairs of function pointers for interposing 40 | S_16BYTE_LITERALS, # 14, section with only 16 byte literals 41 | S_DTRACE_DOF, # 15, section contains DTrace Object Format 42 | S_LAZY_DYLIB_SYMBOL_POINTERS, # 16, section with only lazy symbol pointers to lazy loaded dylibs 43 | 44 | S_THREAD_LOCAL_REGULAR, # 17, template of initial values for TLVs 45 | S_THREAD_LOCAL_ZEROFILL, # 18, template of initial values for TLVs 46 | S_THREAD_LOCAL_VARIABLES, # 19, TLV descriptors 47 | S_THREAD_LOCAL_VARIABLE_POINTERS, # 20, pointers to TLV descriptors 48 | S_THREAD_LOCAL_INIT_FUNCTION_POINTERS, # 21, functions to call to initialize TLV values 49 | 50 | ) = range(22) 51 | 52 | _zeroFillFTypes = frozenset([S_ZEROFILL, S_GB_ZEROFILL, S_THREAD_LOCAL_ZEROFILL]) 53 | 54 | @factorySuffix(suffix='FType', defaultConstructor='byFType') 55 | @factory 56 | class Section(object): 57 | """An abstract section. 58 | 59 | This class adopts the :func:`~factory.factory` class decorator. Subclasses 60 | should override the :meth:`analyze` method to collect data from the Mach-O 61 | file. 62 | 63 | .. attribute:: sectname 64 | 65 | Section name (e.g. ``'__cstring'``) 66 | 67 | .. attribute:: segname 68 | 69 | Segment name (e.g. ``'__TEXT'``) 70 | 71 | .. attribute:: addr 72 | 73 | The base VM address of this section. 74 | 75 | .. attribute:: size 76 | 77 | Size of this section. 78 | 79 | .. attribute:: offset 80 | 81 | File offset of this section. 82 | 83 | .. attribute:: align 84 | 85 | Alignment of this section. 86 | 87 | .. attribute:: reloff 88 | nreloc 89 | 90 | Relocation information. 91 | 92 | .. attribute:: ftype 93 | 94 | Section type, e.g. :const:`S_CSTRING_LITERALS`. 95 | 96 | .. attribute:: attrib 97 | 98 | Attributes of this section. 99 | 100 | .. attribute:: reserved 101 | 102 | A tuple of reserved information. 103 | 104 | .. attribute:: isAnalyzed 105 | 106 | Whether this section has been completely analyzed. 107 | 108 | 109 | """ 110 | 111 | STRUCT_FORMAT = '16s16s2^7L~' 112 | 113 | @property 114 | def isZeroFill(self): 115 | '''Whether this section is a zero-filled section, which occupies no file 116 | space.''' 117 | return self.ftype in _zeroFillFTypes 118 | 119 | @classmethod 120 | def createSection(cls, val): 121 | '''Creates a section given be section header. *val* should be a tuple 122 | ordered as the C ``section`` structure.''' 123 | 124 | (sectname, segname, addr, size, 125 | offset, align, reloff, nreloc, flags, rs1, rs2) = val 126 | 127 | sectname = fromStringz(sectname) 128 | segname = fromStringz(segname) 129 | reserved = (rs1, rs2) 130 | 131 | ftype = flags & 0xff 132 | attrib = flags >> 8 133 | 134 | # we pass ftype twice to ensure __init__ and createSectionType won't be 135 | # mixed (as they have different number of arguments.) 136 | return cls.createFType(ftype, sectname, segname, addr, size, offset, align, reloff, nreloc, ftype, attrib, reserved) 137 | 138 | @classmethod 139 | def byFType(cls, ftype_kw, sectname, segname, addr, size, offset, align, reloff, nreloc, ftype, attrib, reserved): 140 | '''Create a section based on the section type. If a section is 141 | differentiated not just by sectname, the subclass should register the 142 | factory using:: 143 | 144 | Section.registerFactoryFType(S_FOO, FooSection.byFType) 145 | ''' 146 | cons = cls.create if cls is Section else cls 147 | return cons(sectname, segname, addr, size, offset, align, reloff, nreloc, ftype, attrib, reserved) 148 | 149 | def __init__(self, sectname, segname, addr, size, offset, align, reloff, nreloc, ftype, attrib, reserved): 150 | self.ftype = ftype 151 | self.sectname = sectname 152 | self.segname = segname 153 | self.addr = addr 154 | self.size = size 155 | self.offset = offset 156 | self.align = align 157 | self.reloff = reloff 158 | self.nreloc = nreloc 159 | self.attrib = attrib 160 | self.reserved = reserved 161 | self.isAnalyzed = False 162 | 163 | def analyze(self, segment, machO): 164 | """Analyze the section. 165 | 166 | The file pointer is guaranteed to be at the desired offset and all 167 | segments are loaded when this method is called from 168 | :meth:`macho.loadcommands.segment.SegmentCommand.analyze`. 169 | 170 | Return a true value to require further analysis. 171 | """ 172 | 173 | return False 174 | 175 | def __str__(self): 176 | return "<{}: {},{}. 0x{:x}/{:x}>".format(type(self).__name__, self.segname, self.sectname, self.addr, self.offset) 177 | 178 | # def _read(self, o, length=None): 179 | # """Read the whole section. For debugging only.""" 180 | # o.seek(self.offset) 181 | # if length is None: 182 | # length = self.size 183 | # length = min(length, self.size) 184 | # return o._file.read(length) 185 | # 186 | # def _hexdump(self, o, length=None, visualizer='ascii'): 187 | # """Hexdump the whole section. For debugging only.""" 188 | # from hexdump import hexdump 189 | # hexdump(self._read(o, length), location=self.addr, visualizer=visualizer) 190 | # 191 | def asStructs(self, stru, machO, includeAddresses=False): 192 | """Read the whole section as structs, and return an iterable of these. 193 | 194 | If *includeAddresses* is set to ``True``, return an iterable of 195 | (address, struct_content) tuples. 196 | """ 197 | 198 | count = self.size // stru.size 199 | structs = peekStructs(machO.file, stru, count=count, position=self.offset+machO.origin) 200 | 201 | if includeAddresses: 202 | addrs = range(self.addr, self.addr + self.size, stru.size) 203 | return zip(addrs, structs) 204 | else: 205 | return structs 206 | 207 | def asPrimitives(self, fmt, machO, includeAddresses=False): 208 | """Read the whole section as primitives, and return an iterable of these. 209 | 210 | If *includeAddresses* is set to ``True``, return an iterable of 211 | (address, primitive) tuples. 212 | """ 213 | 214 | endian = machO.endian 215 | is64bit = machO.is64bit 216 | ssize = calcsize(decodeStructFormat(fmt, endian, is64bit)) 217 | count = self.size // ssize 218 | prims = peekPrimitives(machO.file, fmt, count, endian, is64bit, position=self.offset+machO.origin) 219 | 220 | if includeAddresses: 221 | addrs = range(self.addr, self.addr + self.size, ssize) 222 | return zip(addrs, prims) 223 | else: 224 | return prims 225 | 226 | 227 | -------------------------------------------------------------------------------- /macho/sections/symbol_ptr.py: -------------------------------------------------------------------------------- 1 | # 2 | # symbol_ptr.py ... symbol pointer sections 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from macho.sections.section import Section, S_NON_LAZY_SYMBOL_POINTERS, S_LAZY_SYMBOL_POINTERS, S_SYMBOL_STUBS, S_LAZY_DYLIB_SYMBOL_POINTERS 20 | import macho.loadcommands.dysymtab 21 | import macho.loadcommands.symtab 22 | 23 | class SymbolPtrSection(Section): 24 | """The symbol pointer sections (for example ``__DATA,__nl_symbol_ptr``) and 25 | symbol stub sections. 26 | 27 | Analyzing this section will resolve the indirect symbols.""" 28 | 29 | 30 | def analyze(self, segment, machO): 31 | dysymtab = machO.loadCommands.any('className', 'DySymtabCommand') 32 | if dysymtab is None: # Make sure the DYSYMTAB command exists. 33 | return False 34 | elif not dysymtab.isAnalyzed: # and loaded 35 | return True 36 | elif not dysymtab.indirectsymoff: # and has the indirect symbol table. 37 | return False 38 | 39 | symtab = machO.loadCommands.any('className', 'SymtabCommand') 40 | if symtab is None: 41 | return False 42 | elif not symtab.isAnalyzed: 43 | return True 44 | 45 | stride = self.reserved[1] or machO.pointerWidth 46 | count = self.size // stride 47 | symbols = machO.symbols 48 | symbols_append = symbols.append 49 | 50 | indirectSyms = dysymtab.indirectSymbols(self.reserved[0], self.size // stride, machO) 51 | addresses = range(self.addr, self.addr + count*stride, stride) 52 | 53 | machO.provideAddresses(zip(indirectSyms, addresses)) 54 | 55 | 56 | 57 | Section.registerFactoryFType(S_NON_LAZY_SYMBOL_POINTERS, SymbolPtrSection.byFType) 58 | # Section.registerFactoryFType(S_LAZY_SYMBOL_POINTERS, SymbolPtrSection.byFType) 59 | # # - The __la_symbol_ptr adds nothing of value to the symbol table. 60 | # Section.registerFactoryFType(S_LAZY_DYLIB_SYMBOL_POINTERS, SymbolPtrSection.byFType) 61 | Section.registerFactoryFType(S_SYMBOL_STUBS, SymbolPtrSection.byFType) 62 | -------------------------------------------------------------------------------- /macho/symbol.py: -------------------------------------------------------------------------------- 1 | # 2 | # symbol.py ... Symbols 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from data_table import DataTable 20 | from .macho import MachO 21 | from monkey_patching import patch 22 | from sym import * 23 | 24 | @patch 25 | class MachO_SymbolPatches(MachO): 26 | ''' 27 | This patch adds method to the :class:`~macho.macho.MachO` class for symbol 28 | processing. 29 | 30 | .. attribute:: symbols 31 | 32 | Returns a :class:`~data_table.DataTable` of :class:`~sym.Symbol`\\s 33 | ordered by insertion order, with the following column names: ``'name'``, 34 | ``'addr'`` and ``'ordinal'``. 35 | 36 | ''' 37 | 38 | def addSymbols(self, symbols): 39 | '''Add an iterable of :class:`~sym.Symbol`\\s to this Mach-O object.''' 40 | 41 | if not hasattr(self, 'symbols'): 42 | self.symbols = DataTable('name', 'addr', '!ordinal') 43 | 44 | self_symbols_append = self.symbols.append 45 | for sym in symbols: 46 | self_symbols_append(sym, name=sym.name, addr=sym.addr, ordinal=sym.ordinal) 47 | 48 | def provideAddresses(self, ordinalsAndAddresses, columnName='ordinal'): 49 | ''' 50 | Provide extra addresses to the symbols. The *ordinalsAndAddresses* 51 | parameter should be an iterable of (ordinal, address) tuple, e.g.:: 52 | 53 | machO.provideAddresses([ 54 | (3000, 0x8e004), 55 | (3001, 0x8e550), 56 | (3002, 0x8e218), 57 | ... 58 | ]) 59 | 60 | ''' 61 | 62 | self_symbols = self.symbols 63 | self_symbols_any = self_symbols.any 64 | self_symbols_associate = self_symbols.associate 65 | 66 | for i, addr in ordinalsAndAddresses: 67 | theSymbol = self_symbols_any(columnName, i) 68 | if theSymbol: 69 | self_symbols_associate(theSymbol, 'addr', [addr]) 70 | 71 | 72 | -------------------------------------------------------------------------------- /monkey_patching.py: -------------------------------------------------------------------------------- 1 | # 2 | # monkey_patching.py ... Monkey patching a class. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | import sys 20 | 21 | class MonkeyPatchingError(Exception): 22 | def __init__(self, message): self._msg = message 23 | def __str__(self): return self._msg 24 | 25 | 26 | class DuplicatedMethodError(MonkeyPatchingError): 27 | '''This exception is raised when a method attempted to define conflicts with 28 | an existing one.''' 29 | def __init__(self, baseClassName, subclassName, methodName): 30 | super().__init__("Cannot monkey patch base class '{}' by '{}'," 31 | "because the method '{}' already exists".format( 32 | baseClassName, subclassName, methodName)) 33 | 34 | 35 | class MultipleInheritanceError(MonkeyPatchingError): 36 | '''This exception is raised when the class to monkey patch has multiple 37 | superclasses.''' 38 | def __init__(self, subclassName): 39 | super().__init__("The subclass '{}' has multiple superclasses.".format(subclassName)) 40 | 41 | 42 | if 'sphinx-build' in sys.argv[0]: 43 | def patch(cls): 44 | '''This class decorator turns a subclass into a 45 | `monkey-patching `_ group:: 46 | 47 | class Foo(object): 48 | pass 49 | 50 | @patch 51 | class FooPrinter(Foo): 52 | def show(self): 53 | print('{}.show'.format(type(self))) 54 | 55 | f = Foo() 56 | f.show() 57 | # prints ".show" 58 | 59 | To monkey-patch a class X, the subclass should inherit X. All public 60 | methods will be dynamically added to class X. The method should not 61 | already exists, otherwise a :exc:`DuplicatedMethodError` will be raised. 62 | 63 | .. note:: 64 | 65 | Private methods (``_foo``) and special methods (``__foo__``) are 66 | *not* added to the base class. 67 | 68 | Applying this decorator will make the it a synonym of the patched class. 69 | ''' 70 | 71 | cls.sphinx_monkeyPatched = True 72 | return cls 73 | 74 | else: 75 | def patch(cls): 76 | '''This class decorator turns a subclass into a monkey-patching group.''' 77 | if len(cls.__bases__) != 1: 78 | raise MultipleInheritanceError(cls.__name__) 79 | 80 | the_base = cls.__bases__[0] 81 | for name, method in cls.__dict__.items(): 82 | # Do not import private methods. 83 | if name[0] != '_': 84 | if hasattr(the_base, name): 85 | raise DuplicatedMethodError(the_base.__name__, cls.__name__, name) 86 | setattr(the_base, name, method) 87 | 88 | return the_base 89 | 90 | 91 | if __name__ == '__main__': 92 | class A(object): 93 | pass 94 | 95 | class B(A): 96 | def foo(self): 97 | pass 98 | 99 | @patch 100 | class C(B): 101 | def bar(self): 102 | pass 103 | 104 | assert C == B 105 | assert hasattr(B, 'bar') 106 | assert not hasattr(A, 'bar') 107 | 108 | @patch 109 | class D(B): 110 | def baz(self): 111 | pass 112 | 113 | assert D == C 114 | assert hasattr(B, 'baz') 115 | assert not hasattr(A, 'baz') 116 | 117 | @patch 118 | class E(A): 119 | def qux(self): 120 | pass 121 | 122 | assert E == A 123 | assert E != D 124 | assert hasattr(A, 'qux') 125 | assert hasattr(B, 'qux') 126 | 127 | exceptionRaised = False 128 | try: 129 | class I(object): 130 | pass 131 | 132 | @patch 133 | class F(A, I): 134 | pass 135 | except MultipleInheritanceError: 136 | exceptionRaised = True 137 | assert exceptionRaised 138 | 139 | exceptionRaised = False 140 | try: 141 | @patch 142 | class G(B): 143 | def bar(self): 144 | pass 145 | except DuplicatedMethodError: 146 | exceptionRaised = True 147 | assert exceptionRaised 148 | 149 | exceptionRaised = False 150 | try: 151 | @patch 152 | class H(B): 153 | def foo(self): 154 | pass 155 | except DuplicatedMethodError: 156 | exceptionRaised = True 157 | assert exceptionRaised 158 | 159 | -------------------------------------------------------------------------------- /objc/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py ... Initialization 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | __all__ = ['class_', 'method', 'ivar', 'property', 'protocol', 'category', 'classlike'] 19 | -------------------------------------------------------------------------------- /objc/category.py: -------------------------------------------------------------------------------- 1 | # 2 | # category.py ... Describes an ObjC category 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from .classlike import ClassLike 19 | 20 | class Category(ClassLike): 21 | """A structure representing an Objective-C class. 22 | 23 | .. attribute:: class_ 24 | 25 | The :class:`~objc.class_.Class` this category is overloading. 26 | 27 | """ 28 | 29 | def __init__(self, name, class_): 30 | super().__init__(name) 31 | self.class_ = class_ 32 | 33 | def __str__(self): 34 | return self.stringify('@interface {} ('.format(self.class_.name), ')', '') 35 | -------------------------------------------------------------------------------- /objc/class_.py: -------------------------------------------------------------------------------- 1 | # 2 | # class_.py ... Describes an ObjC class 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from .classlike import ClassLike 19 | from collections import OrderedDict 20 | from sym import SYMTYPE_UNDEFINED 21 | 22 | class Class(ClassLike): 23 | """A structure representing an Objective-C class. 24 | 25 | .. attribute:: superClass 26 | 27 | The :class:`~objc.classlike.ClassLike` object for this class's super 28 | class. If this class is the root class, its value is ``None``. 29 | 30 | .. attribute:: ivars 31 | 32 | An :class:`~collections.OrderedDict` of :class:`~objc.ivar.Ivar`\ s, 33 | keyed by the ivar name. 34 | 35 | .. attribute:: isRoot 36 | 37 | Whether this class is a root class. 38 | 39 | .. attribute:: hasStructors 40 | 41 | Whether this class has C++ constructors or destructors. 42 | 43 | .. attribute:: hidden 44 | 45 | Whether this class has been declared ``__attribute__((hidden))``. 46 | 47 | .. attribute:: exception 48 | 49 | Whether this class is an exception. 50 | 51 | """ 52 | 53 | def __init__(self, name, flags=0): 54 | super().__init__(name) 55 | self.superClass = None 56 | self.ivars = OrderedDict() 57 | self.parseFlags(flags) 58 | 59 | def parseFlags(self, flag): 60 | """Parse a flag from ObjC 2 ABI.""" 61 | self.isMeta = flag & 1 62 | self.isRoot = flag & 2 63 | self.hasStructors = flag & 4 64 | self.hidden = flag & 16 65 | self.exception = flag & 32 66 | 67 | def addIvars(self, ivars): 68 | """Add an iterable of *ivars* into this class.""" 69 | self.ivars.update((v.name, v) for v in ivars) 70 | 71 | def __str__(self): 72 | if self.superClass: 73 | middle = " : " + self.superClass.name 74 | else: 75 | middle = "" 76 | suffix = [" {\n"] 77 | suffix.extend("\t" + str(iv) + "\n" for iv in self.ivars.values()) 78 | suffix.append('}') 79 | return self.stringify('@interface ', middle, ''.join(suffix)) 80 | 81 | 82 | class RemoteClass(ClassLike): 83 | """A structure representing an external class.""" 84 | def __init__(self, symbol): 85 | name = symbol.name 86 | if symbol.symtype == SYMTYPE_UNDEFINED: 87 | name = name[14:] 88 | 89 | super().__init__(name) 90 | self.symbol = symbol 91 | -------------------------------------------------------------------------------- /objc/classlike.py: -------------------------------------------------------------------------------- 1 | # 2 | # classlike.py ... Base class for all class-like objects. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from collections import OrderedDict 19 | 20 | 21 | class ClassLike(object): 22 | """This structure represents all class-like objects. A class-like object can 23 | have methods and adopt other protocols. There are 3 kinds of class-like 24 | objects in Objective-C: 25 | 26 | * :class:`objc.class_.Class` 27 | * :class:`objc.category.Category` 28 | * :class:`objc.protocol.Protocol` 29 | 30 | .. attribute:: name 31 | 32 | The name of this class-like object. 33 | 34 | .. attribute:: methods 35 | 36 | An ordered dictionary of :class:`~objc.method.Method`\ s as instance 37 | methods, keyed by the method name. 38 | 39 | .. attribute:: classMethods 40 | 41 | An ordered dictionary of :class:`~objc.method.Method`\ s as class 42 | methods, keyed by the method name. 43 | 44 | .. attribute:: protocols 45 | 46 | A set of :class:`~objc.protocol.Protocol`\ s adopted by this object. 47 | 48 | .. attribute:: properties 49 | 50 | A list of :class:`~objc.property.Property`\ s. 51 | 52 | """ 53 | 54 | def __init__(self, name): 55 | self.name = name 56 | self.methods = OrderedDict() 57 | self.classMethods = OrderedDict() 58 | self.protocols = set() 59 | self.properties = [] 60 | 61 | def addMethods(self, methods): 62 | """Add an iterable of *methods* to this class-like object.""" 63 | self.methods.update((m.name, m) for m in methods) 64 | 65 | def addClassMethods(self, classMethods): 66 | """Add an iterable of *classMethods* to this class-like object.""" 67 | self.classMethods.update((m.name, m) for m in classMethods) 68 | 69 | def addProperties(self, properties): 70 | """Add a sequence of *properties* to this class-like object. 71 | 72 | .. note:: 73 | 74 | Make sure the corresponding getters and setters of the properties 75 | exist in this class-like object before calling this method. 76 | 77 | """ 78 | 79 | # By default properties do not have the sense of optionality. However, 80 | # when a property is declared optional, its getter and setter will be 81 | # optional as well. Thus, by checking the optionality of the getter and 82 | # setter, the optionality of a property can be fixed. 83 | self_methods = self.methods 84 | for prop in properties: 85 | getter = prop.getter 86 | if getter in self_methods: 87 | prop.optional = self_methods[getter].optional 88 | self.properties.extend(properties) 89 | 90 | def stringify(self, prefix, middle, suffix): 91 | """Stringify this class-like object. This should be used from the 92 | ``__str__`` method of its subclasses. These ``__str__`` methods are for 93 | debugging only. The result will look like:: 94 | 95 | (prefix) name (middle) (suffix) 96 | +classMethods 97 | -instMethods 98 | @end 99 | 100 | """ 101 | 102 | res = [prefix, self.name, middle] 103 | if self.protocols: 104 | res.append(' <') 105 | res.append(', '.join(p.name for p in self.protocols)) 106 | res.append('> ') 107 | res.append(suffix) 108 | res.extend("\n" + str(m) for m in self.properties) 109 | res.extend("\n+" + str(m) for m in self.classMethods.values()) 110 | res.extend("\n-" + str(m) for m in self.methods.values()) 111 | res.append("\n@end\n") 112 | return ''.join(res) 113 | -------------------------------------------------------------------------------- /objc/ivar.py: -------------------------------------------------------------------------------- 1 | # 2 | # method.py ... Describes an ObjC ivar 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | class Ivar(object): 19 | """A structure representing an Objective-C ivar. 20 | 21 | .. attribute:: name 22 | 23 | The name of this ivar. 24 | 25 | .. attribute:: offset 26 | 27 | Offset of this ivar in the instance. 28 | 29 | .. attribute:: encoding 30 | 31 | Type encoding of this ivar. 32 | 33 | .. attribute:: isPrivate 34 | 35 | Whether this ivar is a private one. 36 | 37 | If an ivar is private, its symbol will not be exported. 38 | 39 | """ 40 | 41 | def __init__(self, name, encoding, offset, isPrivate=False): 42 | self.name = name 43 | self.offset = offset 44 | self.encoding = encoding 45 | self.isPrivate = isPrivate 46 | 47 | def __str__(self): 48 | return '{} [{}] / 0x{:x}'.format(self.name, self.encoding, self.offset) 49 | -------------------------------------------------------------------------------- /objc/method.py: -------------------------------------------------------------------------------- 1 | # 2 | # method.py ... Describes an ObjC method 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | METHOD_ASSOC_NONE = 0 19 | METHOD_ASSOC_DECLARED = 1 20 | METHOD_ASSOC_NOUN = 2 21 | METHOD_ASSOC_ADJECTIVE = 3 22 | METHOD_ASSOC_IVAR = 4 23 | 24 | class Method(object): 25 | """A structure representing an Objective-C method. 26 | 27 | .. attribute:: name 28 | 29 | Name of this method, e.g. "initWithFoo:bar:". 30 | 31 | .. attribute:: imp 32 | 33 | VM address to implementation of this method. If there is no 34 | implementation, this value is 0. 35 | 36 | .. attribute:: encoding 37 | 38 | Raw type encoding of this method. 39 | 40 | .. attribute:: optional 41 | 42 | Whether this method is optional. Only useful for 43 | :class:`objc.protocol.Protocol`. 44 | 45 | .. attribute:: property 46 | 47 | The associated property of this method. 48 | 49 | .. attribute:: propertyType 50 | 51 | How the property is associated to this method. It is an integer and can 52 | take one of these values: 53 | 54 | +--------------------------------+-------------------------------------+ 55 | | Value | Meaning | 56 | +================================+=====================================+ 57 | | ``METHOD_ASSOC_NONE`` (0) | No associated properties. | 58 | +--------------------------------+-------------------------------------+ 59 | | ``METHOD_ASSOC_DECLARED`` (1) | This method is a getter or setter | 60 | | | of a declared property. | 61 | +--------------------------------+-------------------------------------+ 62 | | ``METHOD_ASSOC_NOUN`` (2) | This method is associated to a | 63 | | | property because both ``-noun`` and | 64 | | | ``-setNoun:`` exist. | 65 | +--------------------------------+-------------------------------------+ 66 | | ``METHOD_ASSOC_ADJECTIVE`` (3) | This method is associated to a | 67 | | | property because both ``-isAdj`` | 68 | | | and ``-setAdj:`` exist, and it is a | 69 | | | ``BOOL``. | 70 | +--------------------------------+-------------------------------------+ 71 | | ``METHOD_ASSOC_IVAR`` (4) | This method is associated to a | 72 | | | property because an ivar with the | 73 | | | same name exists. | 74 | +--------------------------------+-------------------------------------+ 75 | 76 | """ 77 | 78 | def __init__(self, name, encoding, imp, optional): 79 | self.name = name 80 | self.imp = imp 81 | self.encoding = encoding 82 | self.optional = optional 83 | self.property = None 84 | self.propertyType = METHOD_ASSOC_NONE 85 | 86 | def __str__(self): 87 | res = '{} [{}]'.format(self.name, self.encoding) 88 | if self.optional: 89 | res += ' @optional' 90 | if self.imp: 91 | res += ' / 0x{:x}'.format(self.imp) 92 | return res 93 | 94 | -------------------------------------------------------------------------------- /objc/property.py: -------------------------------------------------------------------------------- 1 | # 2 | # method.py ... Describes an ObjC property 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from balanced_substring import balancedSubstring 19 | 20 | class Property(object): 21 | """A structure representing an Objective-C property. 22 | 23 | .. attribute:: name 24 | 25 | The name of this property. 26 | 27 | .. attribute:: encoding 28 | 29 | The type encoding of this property. 30 | 31 | .. attribute:: optional 32 | 33 | Whether this property is optional. 34 | 35 | There was no notion of optional properties in the Objective-C runtime. 36 | However, the compiler will mark the getter and setter of a property in 37 | the ``@optional`` region as optional. From this information, we can 38 | deduce whether a property is optional or not. 39 | 40 | .. attribute:: attributes 41 | 42 | The raw attributes of this property. The format can be found in the 43 | `Objective-C Runtime Programming Guide `_ 44 | from Apple. 45 | 46 | .. attribute:: getter 47 | 48 | The name of the getter. By default it is ``name``. 49 | 50 | .. attribute:: setter 51 | 52 | The name of the setter. By default it is ``setName:``. 53 | 54 | .. attribute:: hasGetter 55 | 56 | Whether this property has a custom getter. 57 | 58 | .. attribute:: hasSetter 59 | 60 | Whether this property has a custom setter. 61 | 62 | .. attribute:: atomic 63 | 64 | Whether this property is atomic or not. By default it is ``True``. 65 | 66 | .. attribute:: accessMethod 67 | 68 | How this property is accessed. It can be one of ``'assign'``, 69 | ``'retain'`` or ``'copy'``. By default it is ``'assign'``. 70 | 71 | .. attribute:: readOnly 72 | 73 | Whether this property is read-only. By default it is ``False``. 74 | 75 | .. attribute:: synthesizedIvar 76 | 77 | If this attribute is an empty string, this property is dynamic. 78 | Otherwise, the name of the ivar it is synthesized from is recorded here. 79 | 80 | .. attribute:: gcStrength 81 | 82 | The garbage collection "strength". It can be one of ``'__strong'`` 83 | (eligible for GC), ``'__weak'`` (weak reference) or an empty string 84 | (default). 85 | 86 | """ 87 | 88 | def _parseAttributes(self): 89 | index = 0 90 | exploded = self.attributes.split(',') 91 | 92 | # attributes defined by a single letter. 93 | __singleLetters = { 94 | 'C': ('accessMethod', 'copy'), 95 | '&': ('accessMethod', 'retain'), 96 | 'N': ('atomic', False), 97 | 'D': ('synthesizedIvar', ''), 98 | 'P': ('gcStrength', '__strong'), 99 | 'W': ('gcStrength', '__weak'), 100 | 'G': ('hasGetter', True), 101 | 'S': ('hasSetter', True), 102 | 'R': ('readOnly', True) 103 | } 104 | 105 | # attributes defined by a letter followed by some strings. 106 | __multiLetters = { 107 | 'G': 'getter', 108 | 'S': 'setter', 109 | 'V': 'synthesizedIvar' 110 | } 111 | 112 | buffer = [] 113 | for string in exploded: 114 | if buffer: 115 | buffer.append(string) 116 | else: 117 | typ = string[0] 118 | if typ in __multiLetters: 119 | setattr(self, __multiLetters[typ], string[1:]) 120 | if typ in __singleLetters: 121 | (attr, val) = __singleLetters[typ] 122 | setattr(self, attr, val) 123 | if typ == 'T': 124 | buffer = [string[1:]] 125 | 126 | if buffer: 127 | joinedBuffer = ','.join(buffer) 128 | joinedBufferLength = len(joinedBuffer) 129 | if balancedSubstring(joinedBuffer) <= joinedBufferLength: 130 | self.encoding = joinedBuffer 131 | buffer = [] 132 | 133 | 134 | def __init__(self, name, attributes): 135 | self.name = name 136 | self.attributes = attributes 137 | self.getter = name 138 | self.setter = 'set{}:'.format(name.capitalize()) 139 | self.hasGetter = False 140 | self.hasSetter = False 141 | self.atomic = True 142 | self.accessMethod = 'assign' 143 | self.readOnly = False 144 | self.synthesizedIvar = '' 145 | self.gcStrength = '' 146 | self.encoding = '' 147 | self.optional = False 148 | self._parseAttributes() 149 | 150 | @property 151 | def attributeList(self): 152 | """Return a list of attributes.""" 153 | attribList = [self.accessMethod] 154 | if not self.atomic: 155 | attribList.append('nonatomic') 156 | if self.readOnly: 157 | attribList.append('readonly') 158 | if self.hasGetter: 159 | attribList.append('getter=' + self.getter) 160 | if self.hasSetter: 161 | attribList.append('setter=' + self.setter) 162 | return ', '.join(attribList) 163 | 164 | def __str__(self): 165 | res = '@property({}) {} {}[{}]'.format(self.attributeList, self.name, self.gcStrength, self.encoding) 166 | if self.optional: 167 | res += ' @optional' 168 | if self.synthesizedIvar: 169 | res += ' = ' + self.synthesizedIvar 170 | return res + ';' 171 | 172 | -------------------------------------------------------------------------------- /objc/protocol.py: -------------------------------------------------------------------------------- 1 | # 2 | # class_.py ... Describes an ObjC protocol 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from .classlike import ClassLike 19 | 20 | class Protocol(ClassLike): 21 | """A structure representing an Objective-C protocol.""" 22 | 23 | def __str__(self): 24 | return self.stringify('@protocol ', '', '') 25 | 26 | 27 | -------------------------------------------------------------------------------- /objctype2/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py ... Initialization 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | __all__ = ['types'] 19 | -------------------------------------------------------------------------------- /sorted_list.py: -------------------------------------------------------------------------------- 1 | # 2 | # sorted_list.py ... Sorted list 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from bisect import bisect_right 20 | from collections import Sequence, Sized 21 | 22 | class SortedList(Sequence, Sized): 23 | """A simplistic sequence container, which is sorted automatically based on 24 | usage frequency:: 25 | 26 | sl = SortedList() 27 | sl.append('foo') 28 | sl.append('bar') 29 | 30 | print (list(sl)) 31 | # ['foo', 'bar'] 32 | 33 | sl.use('bar') 34 | print (list(sl)) 35 | # ['bar', 'foo'] 36 | """ 37 | 38 | def __init__(self): 39 | self._items = [] 40 | self._useCount = [] 41 | 42 | def append(self, obj): 43 | """Append an object with usage frequency of 0.""" 44 | self._items.append(obj) 45 | self._useCount.append(0) 46 | 47 | def __getitem__(self, i): 48 | """ 49 | Get the *i*-th object ranked by usage frequency. The 0th object will be 50 | the most frequently used one. 51 | """ 52 | return self._items[i] 53 | 54 | def __len__(self): 55 | "Return the length of the list." 56 | return len(self._items) 57 | 58 | def use(self, obj, hint=0): 59 | ''' 60 | Increase the usage frequency of *obj* by 1. 61 | 62 | If *hint* is given, it will be assumed the *obj* is at that rank. 63 | ''' 64 | # Two cases where there's no need to change place. 65 | # 1. Already the most popular item. 66 | # 2. Even after increasing the use count, the next item is still more 67 | # popular. 68 | # 69 | # Note that we store the use count as negative numbers for compatibility 70 | # with bisect. 71 | # 72 | 73 | self_items = self._items 74 | self_useCount = self._useCount 75 | 76 | index = hint 77 | if self_items[index] != obj: 78 | # The hint isn't right! Find again. 79 | index = self_items.index(obj) 80 | 81 | if index == 0 or self_useCount[index-1] < self_useCount[index]: 82 | self_useCount[index] -= 1 83 | 84 | else: 85 | # This case happens only when a[i-1] == a[i] 86 | target = self_useCount[index] - 1 87 | j = bisect_right(self_useCount, target, 0, index) 88 | 89 | # rotate everything in [j, index] to the right by 1 step. 90 | # e.g. [a:-4, b:-3, c:-3, d:-3, e:-2] 91 | # use d -> [a:-4, b:-3, c:-3, d:-4, e:-2] 92 | # rotate -> [a:-4, d:-4, b:-3, c:-3, e:-2] 93 | # ^-- j ^-- index 94 | self_useCount[j+1:index+1] = self_useCount[j:index] 95 | self_useCount[j] = target 96 | 97 | self_items[j+1:index+1] = self_items[j:index] 98 | self_items[j] = obj 99 | 100 | 101 | 102 | if __name__ == '__main__': 103 | p = SortedList() 104 | 105 | p.append('foo') 106 | p.append('bar') 107 | p.append('baz') 108 | assert p._items == ['foo', 'bar', 'baz'] 109 | assert p._useCount == [0, 0, 0] 110 | 111 | p.use('foo') 112 | p.use('foo') 113 | assert p._items == ['foo', 'bar', 'baz'] 114 | assert p._useCount == [-2, 0, 0] 115 | 116 | p.use('bar') 117 | assert p._items == ['foo', 'bar', 'baz'] 118 | assert p._useCount == [-2, -1, 0] 119 | 120 | p.use('bar') 121 | assert p._items == ['foo', 'bar', 'baz'] 122 | assert p._useCount == [-2, -2, 0] 123 | 124 | p.use('bar') 125 | assert p._items == ['bar', 'foo', 'baz'] 126 | assert p._useCount == [-3, -2, 0] 127 | 128 | p.use('baz') 129 | p.use('baz') 130 | assert p._items == ['bar', 'foo', 'baz'] 131 | assert p._useCount == [-3, -2, -2] 132 | 133 | p.use('baz') 134 | assert p._items == ['bar', 'baz', 'foo'] 135 | assert p._useCount == [-3, -3, -2] 136 | 137 | p.use('foo') 138 | assert p._items == ['bar', 'baz', 'foo'] 139 | assert p._useCount == [-3, -3, -3] 140 | 141 | p.use('foo') 142 | assert p._items == ['foo', 'bar', 'baz'] 143 | assert p._useCount == [-4, -3, -3] 144 | 145 | assert p[0] == 'foo' 146 | assert len(p) == 3 147 | assert p.index('bar') == 1 148 | assert 'baz' in p 149 | assert list(p) == ['foo', 'bar', 'baz'] 150 | 151 | -------------------------------------------------------------------------------- /sphinx_ext_superclass.py: -------------------------------------------------------------------------------- 1 | # 2 | # sphinx_ext_superclass ... Sphinx extension to fix superclass and monkey-patching signatures. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from docutils import nodes 20 | from docutils.parsers.rst import Directive, directives 21 | 22 | class flushRightNode(nodes.TextElement, nodes.Element): 23 | pass 24 | 25 | def visit_flushRightNode(self, node): 26 | self.body.append(self.starttag(node, 'p', CLASS="flushRight")) 27 | 28 | def depart_flushRightNode(self, node): 29 | self.body.append('

') 30 | 31 | 32 | class FlushRightDirective(Directive): 33 | has_content = False 34 | required_arguments = 1 35 | optional_arguments = 0 36 | final_argument_whitespace = True 37 | option_spec = {} 38 | 39 | def run(self): 40 | if not self.arguments: 41 | return [] 42 | (inodes, messages) = self.state.inline_text(self.arguments[0], self.lineno) 43 | subnode = flushRightNode() 44 | subnode.extend(inodes) 45 | return [subnode] + messages 46 | 47 | 48 | 49 | def process_sig_for_monkey_patches(app, what, name, obj, options, signature, return_annotation): 50 | if hasattr(obj, 'sphinx_monkeyPatched'): 51 | return (None, None) 52 | 53 | def process_base_for_classes(app, what, name, obj, options, docstring): 54 | if what == 'class': 55 | bases = [(':class:`{0}.{1}`'.format(x.__module__, x.__name__) if x.__module__ != '__builtin__' else x.__name__) 56 | for x in obj.__bases__ if x is not object] 57 | if bases: 58 | if hasattr(obj, 'sphinx_monkeyPatched'): 59 | prefix = 'Patching: ' 60 | elif len(bases) > 1: 61 | prefix = 'Superclasses: ' 62 | else: 63 | prefix = 'Superclass: ' 64 | docstring.insert(0, '') 65 | docstring.insert(0, '.. efflushright:: %s%s' % (prefix, ', '.join(bases))) 66 | 67 | def check_skip_member(app, what, name, obj, skip, options): 68 | if skip: 69 | try: 70 | if name in obj.im_class.__dict__ and obj.__doc__: 71 | return False 72 | except AttributeError: 73 | return True 74 | else: 75 | return False 76 | 77 | 78 | def setup(app): 79 | app.add_node(flushRightNode, html=(visit_flushRightNode, depart_flushRightNode)) 80 | app.add_directive('efflushright', FlushRightDirective) 81 | app.connect('autodoc-process-signature', process_sig_for_monkey_patches) 82 | app.connect('autodoc-process-docstring', process_base_for_classes) 83 | app.connect('autodoc-skip-member', check_skip_member) 84 | 85 | -------------------------------------------------------------------------------- /sym.py: -------------------------------------------------------------------------------- 1 | # 2 | # sym.py ... Symbols. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | SYMTYPE_UNDEFINED = -1 20 | SYMTYPE_GENERIC = 0 21 | SYMTYPE_CSTRING = 3 22 | SYMTYPE_CFSTRING = 4 23 | SYMTYPE_OBJC_SEL = 5 24 | 25 | _symtypeNames = dict((v, k) for k, v in globals().items() if k.startswith('SYMTYPE_')) 26 | 27 | class Symbol(object): 28 | """A symbol consists minimally of a VM address and a name. It represents a 29 | mapping from one to the other. It is useful to get human-recognizable info 30 | from an address. 31 | 32 | .. attribute:: name 33 | 34 | The name of the symbol. 35 | 36 | .. attribute:: addr 37 | 38 | The address of the symbol 39 | 40 | .. attribute:: ordinal 41 | 42 | Index of this symbol in the local symbol table. If it does not live in 43 | the symbol table, the ordinal is -1. 44 | 45 | .. attribute:: libord 46 | 47 | The "library ordinal" of this symbol. If this symbol is not undefined, 48 | this value should be 0. 49 | 50 | For Mach-O objects, the library can be recovered with the method 51 | :meth:`~macho.loadcommands.dylib.MachO_FromLibord.dylibFromLibord` 52 | patched in :mod:`macho.loadcommands.dylib`. 53 | 54 | .. attribute:: extern 55 | 56 | A boolean indicating whether this is an exported symbol. 57 | 58 | .. attribute:: symtype 59 | 60 | The type of symbol. Can be one of: 61 | 62 | +----------------------------+-----------------------------------+ 63 | | Value | Meaning | 64 | +============================+===================================+ 65 | | :const:`SYMTYPE_UNDEFINED` | Undefined (i.e. external) symbol. | 66 | +----------------------------+-----------------------------------+ 67 | | :const:`SYMTYPE_GENERIC` | Generic local symbol. | 68 | +----------------------------+-----------------------------------+ 69 | | :const:`SYMTYPE_CSTRING` | C strings. | 70 | +----------------------------+-----------------------------------+ 71 | | :const:`SYMTYPE_CFSTRING` | CoreFoundation strings. | 72 | +----------------------------+-----------------------------------+ 73 | | :const:`SYMTYPE_OBJC_SEL` | Objective-C selector. | 74 | +----------------------------+-----------------------------------+ 75 | 76 | .. attribute:: isThumb 77 | 78 | Whether this symbol is in Thumb. This is meaningful for ARM architecture 79 | only. 80 | 81 | """ 82 | 83 | def __init__(self, name, addr, symtype, ordinal=-1, libord=0, extern=False, isThumb=False): 84 | self.name = name 85 | self.addr = addr 86 | self.symtype = symtype 87 | self.ordinal = ordinal 88 | self.libord = libord 89 | self.extern = extern 90 | self.isThumb = isThumb 91 | 92 | def __copy__(self): 93 | '''Create a copy of this symbol.''' 94 | return type(self)(*self._toTuple()) 95 | copy = __copy__ 96 | 97 | def _toTuple(self): 98 | return (self.name, self.addr, self.symtype, self.ordinal, self.libord, self.extern, self.isThumb) 99 | 100 | def __eq__(self, other): 101 | "Check whether two symbols are equivalent." 102 | return self._toTuple() == other._toTuple() 103 | 104 | def __hash__(self): 105 | "Compute the hash of the symbol." 106 | return hash(self._toTuple()) 107 | 108 | 109 | def __str__(self): 110 | return "".format(_symtypeNames[self.symtype], self.name, self.addr) 111 | 112 | def __repr__(self): 113 | args = [repr(self.name), '0x{:x}'.format(self.addr), _symtypeNames[self.symtype]] 114 | args_app = args.append 115 | if self.ordinal >= 0: 116 | args_app('ordinal={!r}'.format(self.ordinal)) 117 | if self.libord: 118 | args_app('libord={!r}'.format(self.libord)) 119 | if self.extern: 120 | args_app('extern=True') 121 | if self.isThumb: 122 | args_app('isThumb=True') 123 | return 'Symbol({})'.format(', '.join(args)) 124 | 125 | -------------------------------------------------------------------------------- /symbolic/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py ... Initialization 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | """ 19 | 20 | This is the symbolic arithmetic package for the EcaFretni project. Unlike other 21 | symbolic math libraries (e.g. SymPy), this package is aimed for simplification 22 | and integer arithmetic, which is useful for code analysis and decompiling. 23 | Unnecessary features e.g. string parsing, calculus etc. will not be present. 24 | 25 | The simplification routine is extensible. To create a new simplification rule, 26 | just add a module in the simplification/ directory, then call 27 | 28 | Expression.addSimplificationRule(rule, "name of rule") 29 | 30 | to enable it. See the documentation of Expression.addSimplificationRule for 31 | detail. 32 | 33 | """ 34 | 35 | __all__ = ['expression'] 36 | -------------------------------------------------------------------------------- /symbolic/simplify/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # __init__.py ... Initialization 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | __all__ = ['recursive', 'fold_constant', 'semigroup', 'distributive'] 19 | -------------------------------------------------------------------------------- /symbolic/simplify/compare.py: -------------------------------------------------------------------------------- 1 | # 2 | # compare.py ... Simplification involving comparison. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from symbolic.expression import Expression, Constant, Symbol 20 | from symbolic.simplify.utilities import performIf 21 | from collections import Counter 22 | 23 | def _selfCompare(self): 24 | if self.type in ('==', '<='): 25 | if self.children[0] == self.children[1]: 26 | return Constant(True) 27 | elif self.type in ('!=', '<'): 28 | if self.children[0] == self.children[1]: 29 | return Constant(False) 30 | 31 | def __findNonZeroSide(self): 32 | # Check if one side is zero. 33 | for i, c in enumerate(self.children): 34 | if Expression.isConstant(c) and c.value == 0: 35 | return (1 - i) 36 | return -1 37 | 38 | 39 | def __isNegativeFactor(self): 40 | if self.type == '*': 41 | for c in self.children: 42 | if Expression.isConstant(c) and c.value < 0: 43 | return True 44 | return False 45 | 46 | 47 | def _subtractionAndCompareWithZero(self): 48 | # a-b < 0 <=> a < b 49 | if self.type in ('==', '<=', '!=', '<'): 50 | otherChildIndex = __findNonZeroSide(self) 51 | 52 | if otherChildIndex >= 0: 53 | otherChild = self.children[otherChildIndex] 54 | if otherChild.type == '+': 55 | negatedFactors = Counter() 56 | def addNegatedFactor(child, count): 57 | nonlocal negatedFactors 58 | negatedFactors[-child] += count 59 | 60 | (positiveFactors, anyNeg) = performIf(otherChild.children, __isNegativeFactor, addNegatedFactor) 61 | 62 | if anyNeg: 63 | lhs = otherChild.replaceChildren(positiveFactors) 64 | rhs = otherChild.replaceChildren(negatedFactors) 65 | return self.replaceChildren([lhs, rhs]) 66 | 67 | 68 | def _equalityWithZero(self): 69 | # a == 0 <=> !a 70 | if self.type in ('==', '!='): 71 | otherChildIndex = __findNonZeroSide(self) 72 | 73 | if otherChildIndex >= 0: 74 | rv = self.children[otherChildIndex] 75 | if self.type == '==': 76 | rv = Expression.not_(rv) 77 | return rv 78 | 79 | __negatedComparisonMap = { 80 | '==': '!=', 81 | '!=': '==', 82 | '<': '<=', 83 | '<=': '<'} 84 | 85 | def _negatedComparison(self): 86 | # !(a < b) <=> b <= a 87 | 88 | if self.type == '!': 89 | child = self.children[0] 90 | if child.type in __negatedComparisonMap: 91 | return Expression(__negatedComparisonMap[child.type], child.children[1], child.children[0]) 92 | 93 | 94 | 95 | Expression.addSimplificationRule(_selfCompare, 'self comparison (a==a <=> True)') 96 | Expression.addSimplificationRule(_negatedComparison, 'negated comparison (!(a b<=a)') 97 | Expression.addSimplificationRule(_subtractionAndCompareWithZero, 'subtract and compare (a-b<0 <=> a !a)') 99 | 100 | 101 | if __name__ == '__main__': 102 | import symbolic.simplify.recursive 103 | import symbolic.simplify.fold_constant 104 | import symbolic.simplify.distributive 105 | from symbolic.expression import Symbol 106 | 107 | Expression.setDebugSimplify(True) 108 | 109 | a = Expression.lt(Symbol('aaa'), Symbol('aaa')) 110 | assert Constant(False) == a.simplify() 111 | 112 | a = Expression.le(Symbol('aaa'), Symbol('aaa')) 113 | assert Constant(True) == a.simplify() 114 | 115 | a = Expression.eq(Constant(0), Symbol('aaa') - Symbol('bbb')) 116 | assert Expression.eq(Symbol('aaa'), Symbol('bbb')) == a.simplify() 117 | 118 | a = Expression.eq(Constant(0), Symbol('aaa') + Symbol('bbb')) 119 | assert Expression.not_(Symbol('aaa') + Symbol('bbb')) == a.simplify() 120 | 121 | a = Expression.not_(Expression.ge(Symbol('aaa'), Symbol('bbb'))) 122 | assert Expression.lt(Symbol('aaa'), Symbol('bbb')) == a.simplify() 123 | 124 | 125 | -------------------------------------------------------------------------------- /symbolic/simplify/distributive.py: -------------------------------------------------------------------------------- 1 | # 2 | # distributive.py ... Simplification using distributivity and existence of inverse. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from symbolic.expression import Expression, Constant 20 | from collections import Counter 21 | from symbolic.simplify.utilities import performIf2, performIf, keysExcept 22 | 23 | import symbolic.simplify.semigroup # for the idempotent rule. 24 | 25 | # The generalized distributive law for 3 operators +, #, * is that: 26 | # 27 | # forall a,b,c. a * b # a * c == a * (b+c) 28 | # b * a # c * a == (b#c) * a 29 | # 30 | # Satisfying examples: 31 | # 32 | # (+, #, *) -> (+, *, ^) 33 | # 34 | # This reduces to normal distributive law if (+) == (#), e.g. 35 | # 36 | # (+, *) 37 | # (&, |) 38 | # (|, &) 39 | # (&&, ||) 40 | # (||, &&) 41 | 42 | __distributions = { 43 | '+': '*', 44 | '|': '&', 45 | '&': '|', 46 | '||': '&&', 47 | '&&': '||' 48 | } 49 | 50 | def _repetition(self): 51 | # a + a + a = 3*a 52 | if self.type in ('+', '*'): 53 | star = '*' if self.type == '+' else '**' 54 | 55 | grouped = [] 56 | def _evaluteRepetition(child, count): 57 | grouped.append(Expression(star, child, Constant(count))) 58 | 59 | (rest, hasRep) = performIf2(self.children, (lambda k, c: c != 1), _evaluteRepetition) 60 | 61 | if hasRep: 62 | rest.update(grouped) 63 | return self.replaceChildren(rest) 64 | 65 | 66 | def _distributive(self): 67 | if self.type in __distributions: 68 | star = __distributions[self.type] 69 | 70 | extracted = [] 71 | factorList = Counter() 72 | def _pushToExtracted(child, count): 73 | assert count == 1 74 | extracted.append(child) 75 | factorList.update(keysExcept(child.children, Expression.isConstant)) 76 | 77 | (rest, hasStar) = performIf(self.children, Expression.isType(star), _pushToExtracted) 78 | 79 | if hasStar: 80 | # Algorithm for factorization: 81 | # 82 | # 1. find the most common factor 83 | # 2. check if that factor has appeared >1 times. If no, quit. 84 | # 3. otherwise, scan for all children which contain that factor. 85 | # 4. remove that factor from those children, and create a new 86 | # a*(b+c+d) style expression. 87 | 88 | factorizedOnce = False 89 | while factorList: 90 | (commonest, count) = factorList.most_common(1)[0] 91 | if count == 1: 92 | if factorizedOnce: 93 | rest.update(extracted) 94 | return self.replaceChildren(rest) 95 | else: 96 | return None 97 | else: 98 | factorizedOnce = True 99 | oldExtracted = extracted 100 | extracted = [] 101 | newChildrenList = [] 102 | for child in oldExtracted: 103 | if commonest in child.children: 104 | factorList.subtract(keysExcept(child.children, Expression.isConstant)) 105 | newChildChildren = Counter(child.children) 106 | newChildChildren[commonest] -= 1 107 | newChild = child.replaceChildren(newChildChildren) 108 | newChildrenList.append(newChild) 109 | else: 110 | extracted.append(child) 111 | newExpression = Expression(star, commonest, Expression(self.type, *newChildrenList)) 112 | extracted.append(newExpression) 113 | factorList.update(keysExcept(child.children, Expression.isConstant)) 114 | 115 | 116 | 117 | Expression.addSimplificationRule(_repetition, 'repetition (a+a+a=3*a)') 118 | Expression.addSimplificationRule(_distributive, 'distributive (a*b+a*c=a*(b+c))') 119 | 120 | if __name__ == '__main__': 121 | import symbolic.simplify.recursive 122 | from symbolic.expression import Symbol 123 | 124 | Expression.setDebugSimplify(True) 125 | 126 | a = Expression('+', Symbol('a'), Symbol('a'), Symbol('b'), Symbol('a'), Symbol('b'), Symbol('b'), Symbol('a'), Symbol('c')) + \ 127 | Expression('+', Symbol('c'), Symbol('a'), Symbol('c'), Symbol('c'), Symbol('b'), Symbol('a'), Symbol('a'), Symbol('d')) 128 | assert a.simplify() == Expression('+', Expression('*', Symbol('a'), Constant(7)), 129 | Expression('*', Symbol('b'), Constant(4)), 130 | Expression('*', Symbol('c'), Constant(4)), Symbol('d')) 131 | 132 | 133 | -------------------------------------------------------------------------------- /symbolic/simplify/fold_constant.py: -------------------------------------------------------------------------------- 1 | # 2 | # fold_constant.py ... Simplification rule (Constant folding) 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from symbolic.expression import Expression, Constant 20 | from symbolic.simplify.utilities import performIf 21 | from collections import Counter 22 | import operator 23 | import functools 24 | import bits 25 | 26 | __unaryFuncs = { 27 | '-': operator.neg, 28 | '~': operator.invert, 29 | '!': operator.not_ 30 | } 31 | __binaryFuncs = { 32 | '/': operator.truediv, 33 | '//': operator.floordiv, 34 | '==': operator.eq, 35 | '!=': operator.ne, 36 | '>=': operator.ge, 37 | '<=': operator.le, 38 | '>': operator.gt, 39 | '<': operator.lt, 40 | '%': operator.mod, 41 | '**': operator.pow, 42 | '<<': bits.lshift, 43 | '>>': bits.rshift, 44 | '>>>': bits.urshift, 45 | 'rol': bits.rol, 46 | 'ror': bits.ror 47 | } 48 | __naryFuncs = { 49 | '+': (operator.add, 0), 50 | '*': (operator.mul, 1), 51 | '&': (operator.and_, -1), 52 | '|': (operator.or_, 0), 53 | '^': (operator.xor, 0) 54 | } 55 | 56 | def _unary(self): 57 | if self.type in __unaryFuncs and Expression.isConstant(self.children[0]): 58 | return Constant(__unaryFuncs[self.type](self.children[0].value)) 59 | 60 | def _binary(self): 61 | if self.type in __binaryFuncs and Expression.isConstant(self.children[0]) and Expression.isConstant(self.children[1]): 62 | return Constant(__binaryFuncs[self.type](self.children[0].value, self.children[1].value)) 63 | 64 | def _applyNtimes(oper, value, count, prev): 65 | # compute value `oper` value `oper` value `oper` ... with 'count' values. 66 | if oper == '+': 67 | return prev + value * count 68 | elif oper == '*': 69 | return prev * (value ** count) 70 | elif oper == '&': 71 | return prev & value 72 | elif oper == '|': 73 | return prev | value 74 | elif oper == '^': 75 | if count % 2 == 0: 76 | return prev 77 | else: 78 | return prev ^ value 79 | 80 | __naryDefaultValue = {'+': 0, '*': 1, '&': -1, '|': 0, '^': 0, '&&': True, '||': False} 81 | 82 | def _nary(self): 83 | if self.type in __naryDefaultValue: 84 | default = __naryDefaultValue[self.type] 85 | val = default 86 | rests = Counter() 87 | totalValCount = 0 88 | 89 | def _updateVal(child, count): 90 | nonlocal val, totalValCount 91 | if child.value != default: 92 | val = _applyNtimes(self.type, child.value, count, val) 93 | totalValCount += count 94 | 95 | (rests, _) = performIf(self.children, Expression.isConstant, _updateVal) 96 | 97 | if val != default: 98 | rests[Constant(val)] += 1 99 | 100 | if totalValCount > 1 or (totalValCount == 1 and val == default): 101 | return self.replaceChildren(rests) 102 | 103 | 104 | __shortCircuitTarget = {'&&': False, '||': True, '&': 0, '|': -1, '*': 0} 105 | 106 | def _shortCircuit(self): 107 | if self.type in __shortCircuitTarget: 108 | target = __shortCircuitTarget[self.type] 109 | targetType = type(target) 110 | 111 | for v in self.children: 112 | if Expression.isConstant(v): 113 | if targetType(v.value) == target: 114 | return Constant(target) 115 | 116 | def _naryBaseCondition(self): 117 | if self.type in __naryDefaultValue: 118 | uniqueChildrenCount = len(self.children) 119 | if uniqueChildrenCount == 0: 120 | return Constant(__naryDefaultValue[self.type]) 121 | elif uniqueChildrenCount == 1: 122 | (child, value) = list(self.children.items())[0] 123 | if value == 1: 124 | return child 125 | 126 | def _evaluateIfThenElse(self): 127 | if self.type == '?:' and Expression.isConstant(self.children[0]): 128 | if self.children[0].value: 129 | return self.children[1] 130 | else: 131 | return self.children[2] 132 | else: 133 | return None 134 | 135 | Expression.addSimplificationRule(_unary, 'fold constant (unary)') 136 | Expression.addSimplificationRule(_binary, 'fold constant (binary)') 137 | Expression.addSimplificationRule(_shortCircuit, 'short circuit') 138 | Expression.addSimplificationRule(_nary, 'fold constant (N-ary)') 139 | Expression.addSimplificationRule(_naryBaseCondition, 'base condition (N-ary)') 140 | Expression.addSimplificationRule(_evaluateIfThenElse, 'constant condition (?:)') 141 | 142 | if __name__ == '__main__': 143 | from symbolic.simplify.recursive import * 144 | from symbolic.expression import Symbol 145 | 146 | Expression.setDebugSimplify(True) 147 | 148 | a = Constant(3) / Constant(2) 149 | assert Constant(1.5) == a.simplify() 150 | 151 | a = Expression('+', Constant(1), Constant(5), Constant(12), Constant(44)) 152 | assert Constant(62) == a.simplify() 153 | 154 | a = Expression.if_(Expression.ge(Constant(7), Constant(2)), Constant(11), Constant(55)) 155 | assert Constant(11) == a.simplify() 156 | 157 | a = (Constant(1) ^ Constant(5) - Constant(122) // Constant(4)) ** Constant(2) 158 | assert Constant(676) == a.simplify() 159 | 160 | a = Expression('*', Symbol("foo"), Constant(0), Symbol("boo")) 161 | assert Constant(0) == a.simplify() 162 | -------------------------------------------------------------------------------- /symbolic/simplify/recursive.py: -------------------------------------------------------------------------------- 1 | # 2 | # recursive.py ... Recursively apply simplification to children. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from symbolic.expression import Expression 20 | from collections import Counter 21 | 22 | class _ChildSimplifier(object): 23 | def __init__(self): 24 | self.simplified = False 25 | 26 | def __call__(self, child): 27 | if not Expression.isAtomic(child): 28 | (child, hasSimplified) = child.simplify(getSimplifyState=True) 29 | self.simplified = self.simplified or hasSimplified 30 | return child 31 | 32 | 33 | def _recursiveRule(self): 34 | if Expression.isAtomic(self): 35 | return None 36 | 37 | simplifier = _ChildSimplifier() 38 | 39 | if isinstance(self.children, Counter): 40 | retval = Counter({simplifier(child): count for child, count in self.children.items()}) 41 | else: 42 | retval = [simplifier(child) for child in self.children] 43 | 44 | if simplifier.simplified: 45 | return self.replaceChildren(retval) 46 | 47 | 48 | Expression.addSimplificationRule(_recursiveRule, 'recursive simplify') 49 | -------------------------------------------------------------------------------- /symbolic/simplify/semigroup.py: -------------------------------------------------------------------------------- 1 | # 2 | # semigroup.py ... Perform simplification for the commutative semigroup operators. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from symbolic.expression import Expression 20 | from collections import Counter 21 | from symbolic.simplify.utilities import performIf 22 | 23 | # Commutative semigroup are mathematical structures for an operator + and set S, 24 | # such that: 25 | # 26 | # forall a,b,c. (a + b) + c == a + (b + c) (associative) 27 | # forall a,b. a + b == b + a (commutative) 28 | # 29 | 30 | def _flatten(self): 31 | # flatten an expression tree of the same type by applying associativity. 32 | # a + (b + c) == a + b + c. 33 | 34 | if self.type in ('+', '*', '&', '|', '^', '&&', '||'): 35 | flatPart = Counter() 36 | def _flattenAction(child, count): 37 | nonlocal flatPart 38 | flatPart += Counter({k: v*count for k, v in child.children.items()}) 39 | 40 | (rest, hasFlatten) = performIf(self.children, Expression.isType(self.type), _flattenAction) 41 | 42 | if hasFlatten: 43 | rest += flatPart 44 | return self.replaceChildren(rest) 45 | 46 | def _idempotent(self): 47 | # (a & a) == a 48 | if self.type in ('&', '|', '&&', '||'): 49 | if any(count > 1 for count in self.children.values()): 50 | return Expression(self.type, *self.children.keys()) 51 | 52 | # 1-ary and 0-ary cases are handled in fold_constant.py already. 53 | 54 | def _involution(self): 55 | # (a ^ a) == 0 56 | if self.type == '^': 57 | if any(count > 1 for count in self.children.values()): 58 | return Expression(self.type, *(k for k, c in self.children.items() if c % 2 != 0)) 59 | 60 | Expression.addSimplificationRule(_flatten, 'commutative semigroup (a*(b*c) == a*b*c)') 61 | Expression.addSimplificationRule(_idempotent, 'idempotent ((a&a) == a)') 62 | Expression.addSimplificationRule(_involution, 'involution ((a^a) == 0)') 63 | 64 | if __name__ == '__main__': 65 | import symbolic.simplify.recursive 66 | from symbolic.expression import Symbol 67 | 68 | Expression.setDebugSimplify(True) 69 | 70 | a = Symbol('foo') + Symbol('bar') + Symbol('baz') + Symbol('ma') * Symbol('maz') - Symbol('y') 71 | assert a.simplify() == Expression('+', Symbol('foo'), Symbol('bar'), Symbol('baz'), Expression('*', Symbol('ma'), Symbol('maz')), -Symbol('y')) 72 | 73 | a = (Expression('&', Symbol('foo'), Symbol('bar'), Symbol('foo'), Symbol('baz'), Symbol('bar'), Symbol('bar')) & \ 74 | Expression('^', Symbol('foo'), Symbol('bar'), Symbol('foo'), Symbol('baz'), Symbol('bar'), Symbol('bar'))) 75 | assert a.simplify() == Expression('&', Symbol('foo'), Symbol('bar'), Symbol('baz'), Expression('^', Symbol('bar'), Symbol('baz'))) 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /symbolic/simplify/utilities.py: -------------------------------------------------------------------------------- 1 | # 2 | # utitlities.py ... Utilities for simplification. 3 | # Copyright (C) 2010 KennyTM~ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | from collections import Counter 20 | 21 | def performIf(counter, predicate, action): 22 | """Filter the *counter* collection by a *predicate* and perform some 23 | *action* on them. Returns a tuple of whether anything satisfies the 24 | predicate, and the counter collection of unsatisfying items. 25 | 26 | The *predicate* should take a key of the counter collection, and returns a 27 | ``bool``. The *action* should take a key and its count in the counter 28 | collection. 29 | 30 | Example: 31 | 32 | >>> pred = (lambda key: key % 2 == 0) 33 | >>> action = (lambda key, count: print('Key =', key, ' Count =', count)) 34 | >>> performIf(Counter([1,1,2,3,6,6,6,6]), pred, action) 35 | Key = 2 Count = 1 36 | Key = 6 Count = 4 37 | (Counter({1: 2, 3: 1}), True) 38 | >>> performIf(Counter([1,1,3]), pred, action) 39 | (Counter({1: 2, 3: 1}), False) 40 | >>> performIf(Counter([2,6,6,6,6]), pred, action) 41 | Key = 2 Count = 1 42 | Key = 6 Count = 4 43 | (Counter(), True) 44 | 45 | """ 46 | 47 | return performIf2(counter, (lambda k, c: predicate(k)), action) 48 | 49 | def performIf2(counter, predicate, action): 50 | """Similar to :func:`performIf`, but the *predicate* accepts the count as 51 | the second argument.""" 52 | 53 | normal = Counter() 54 | anyTrue = False 55 | for child, count in counter.items(): 56 | if predicate(child, count): 57 | anyTrue = True 58 | action(child, count) 59 | else: 60 | normal[child] += count 61 | return (normal, anyTrue) 62 | 63 | def keysExcept(counter, predicate): 64 | """Returns an iterator of keys of the *counter* collection that does not 65 | satisfy the predicate. 66 | 67 | >>> pred = (lambda key: key % 2 == 0) 68 | >>> list(keysExcept(Counter([1,1,2,3,6,6,6,6]), pred)) 69 | [1, 3] 70 | """ 71 | return (k for k in counter.keys() if not predicate(k)) 72 | --------------------------------------------------------------------------------