├── .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 |
--------------------------------------------------------------------------------