forked from tzickel/bytehook
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbytehook.py
More file actions
194 lines (162 loc) · 6.36 KB
/
bytehook.py
File metadata and controls
194 lines (162 loc) · 6.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
from __future__ import print_function
import new
import dis
import struct
__version__ = "0.0.1"
# TODO add docstring explanations
# TODO add support for python 3 and pypy
# TODO how to handle flags such as CO_FUTURE_UNICODE_LITERALS (check code.h)
# TODO reloading this clears all the vars... maybe make this object oriented ?
# TODO currently cannot remove the hookpoint completely
def s(short):
# opcode arguments are always unsigned short
return struct.pack('H', short)
def o(s):
return chr(dis.opmap[s])
def createbytecode(*inp):
ret = ''
for item in inp:
if isinstance(item, str):
ret += o(item)
else:
ret += s(item)
return ret
def getoraddtotuple(t, *names):
indexs = []
tmp = list(t)
added = False
for name in names:
try:
ind = tmp.index(name)
indexs.append(ind)
except ValueError:
added = True
tmp.append(name)
indexs.append(len(tmp) - 1)
if added:
t = tuple(tmp)
indexs.insert(0, t)
return indexs
def line2addr(func, line):
code = func.func_code
if line < 0:
lineno = code.co_firstlineno
line = -line
else:
lineno = 0
co_lnotab = code.co_lnotab
addr = 0
for addr_incr, line_incr in zip(co_lnotab[::2], co_lnotab[1::2]):
addr += ord(addr_incr)
lineno += ord(line_incr)
if lineno == line:
return addr
return None
# TODO support scanning the insert_code as well.. currently does not (fix this if you want to make this a generic insert code)
def insertbytecode(co_code, addr, insert_code):
newcode = co_code[:addr] + insert_code + co_code[addr:]
insert_len = len(insert_code)
n = len(newcode)
i = 0
fixedcode = ''
while i < n:
c = newcode[i]
fixedcode += c
op = ord(c)
i += 1
if op >= dis.HAVE_ARGUMENT:
oparg = ord(newcode[i]) + ord(newcode[i + 1]) * 256
i += 2
if op in dis.hasjrel:
target = i + oparg
if i < addr and target >= addr:
oparg += insert_len
elif op in dis.hasjabs:
target = oparg
if i < addr and target >= addr:
oparg += insert_len
elif i > addr + insert_len and target >= addr:
oparg += insert_len
fixedcode += s(oparg)
return fixedcode
# TODO insert_len limited to 256 for now.. (fix this if you want to make this a generic insert code)
def fixlines(co_lnotab, insert_addr, insert_len):
byte_increments = [ord(c) for c in co_lnotab[0::2]]
line_increments = [ord(c) for c in co_lnotab[1::2]]
new_lnotab = ''
lineno = 0
addr = 0
for byte_incr, line_incr in zip(byte_increments, line_increments):
addr += byte_incr
lineno += line_incr
if addr > insert_addr:
byte_incr += insert_len
new_lnotab += chr(byte_incr) + chr(line_incr)
return new_lnotab
hookpoints = {}
disabledhookpoints = {}
hookpointcounter = 0
mapping = {}
origin = {}
def run_hookpoint(num, _locals=None, _globals=None):
if num in hookpoints:
hookpoints[num](_locals, _globals)
def disable_hookpoint(num):
if num not in hookpoints:
raise Exception('Breakpoint not enabled')
disabledhookpoints[num] = hookpoints[num]
del hookpoints[num]
def enable_hookpoint(num):
if num not in disabledhookpoints:
raise Exception('Breakpoint not disabled')
hookpoints[num] = disabledhookpoints[num]
del disabledhookpoints[num]
def change_hookpoint(num, func):
if num in hookpoints:
hookpoints[num] = func
elif num in disabledhookpoints:
disabledhookpoints[num] = func
else:
raise Exception('Breakpoint not found')
def list_hookpoints():
for k, v in origin.iteritems():
if k in hookpoints:
print('E', k, v)
else:
print('D', k, v)
def runpdb(_locals=None, _globals=None):
import pdb
pdb.set_trace()
# TODO check that closures and nested functions work here as well
def hook(func, lineno=None, insert_func=runpdb, with_state=False):
global hookpointcounter
hookpoints[hookpointcounter] = insert_func
code = func.func_code
newconsts, noneindex, minusoneindex, hookpointindex = getoraddtotuple(code.co_consts, None, -1, hookpointcounter)
newnames, replaceindex, runhookpointindex = getoraddtotuple(code.co_names, __name__, 'run_hookpoint')
if with_state:
newnames, localsindex, globalsindex = getoraddtotuple(newnames, 'locals', 'globals')
pdbtracecode = createbytecode('LOAD_CONST', minusoneindex, 'LOAD_CONST', noneindex, 'IMPORT_NAME', replaceindex, 'LOAD_ATTR', runhookpointindex, 'LOAD_CONST', hookpointindex, 'LOAD_GLOBAL', localsindex, 'CALL_FUNCTION', 0, 'LOAD_GLOBAL', globalsindex, 'CALL_FUNCTION', 0, 'CALL_FUNCTION', 3, 'POP_TOP')
else:
pdbtracecode = createbytecode('LOAD_CONST', minusoneindex, 'LOAD_CONST', noneindex, 'IMPORT_NAME', replaceindex, 'LOAD_ATTR', runhookpointindex, 'LOAD_CONST', hookpointindex, 'CALL_FUNCTION', 1, 'POP_TOP')
if lineno is None:
newcode = insertbytecode(code.co_code, 0, pdbtracecode)
newlnotab = fixlines(code.co_lnotab, 0, len(pdbtracecode))
else:
addr = line2addr(func, lineno)
if addr is None:
raise Exception('Line not found')
newcode = insertbytecode(code.co_code, addr, pdbtracecode)
newlnotab = fixlines(code.co_lnotab, addr, len(pdbtracecode))
# TODO is this correct ?
newstacksize = code.co_stacksize + 4 if with_state else 2
newfunc = new.code(code.co_argcount, code.co_nlocals, newstacksize, code.co_flags, newcode, newconsts, newnames, code.co_varnames, code.co_filename, code.co_name, code.co_firstlineno, newlnotab, code.co_freevars, code.co_cellvars)
# TODO make this thread safe (index returning number)
hookpointcounter += 1
if func.func_code in mapping:
mapping[newfunc] = mapping[func.func_code]
else:
mapping[newfunc] = func.func_code
origin[hookpointcounter - 1] = mapping[newfunc]
func.func_code = newfunc
return hookpointcounter - 1