forked from pycca/pycca
-
Notifications
You must be signed in to change notification settings - Fork 0
/
asm_examples.py
executable file
·362 lines (304 loc) · 9.62 KB
/
asm_examples.py
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
import ctypes, struct, time, math
from pycca.asm import *
print("""
Example 1: Return a value from call
------------------------------------------------------
""")
# To return a value from a function, just put it in eax or rax:
fn = mkfunction([
mov(eax, 0xdeadbeef),
ret()
])
# Tell ctypes how to interpret the return value
fn.restype = ctypes.c_uint32
# Call! Hopefully we get 0xdeadbeef back.
print("Return: 0x%x" % fn())
print("""
Example 2: Jump to label
----------------------------------------------
""")
# Also show that we can give an assembly string rather than
# instruction objects:
fn = mkfunction("""
mov eax, 0x1
jmp start
end:
ret
mov eax, 0x1
mov eax, 0x1
start:
mov eax, 0xdeadbeef
jmp end
mov eax, 0x1
""")
fn.restype = ctypes.c_uint32
# We get 0xdeadbeef back if jumps are followed.
print("Return: 0x%x" % fn())
print("""
Example 3: Access values from an array
------------------------------------------------------
""")
# Show how to access data from both numpy array and python arrays
try:
import numpy as np
HAVE_NUMPY = True
except ImportError:
import array
HAVE_NUMPY = False
if HAVE_NUMPY:
data = np.ones(10, dtype=np.uint32)
addr = data.ctypes.data
stride = data.strides[0]
else:
data = array.array('I', [1]*10)
addr = data.buffer_info()[0]
stride = 4
I = 5 # will access 5th item from asm function
data[I] = 12345
offset = I * stride
if ARCH == 64:
fn = mkfunction([
mov(rcx, addr), # copy memory address to rcx
mov(eax, dword([rcx+offset])), # return value from array[5]
mov(dword([rcx+offset]), 54321), # copy new value to array[5]
ret(),
])
else:
fn = mkfunction([
mov(ecx, addr), # copy memory address to rcx
mov(eax, [ecx+offset]), # return value from array[5]
mov([ecx+offset], 54321), # copy new value to array[5]
ret(),
])
fn.restype = ctypes.c_uint32
print("Read from array: %d" % fn())
print("Modified array: %d" % data[5])
print("""
Example 4: a basic for-loop
------------------------------------------------------
""")
fn = mkfunction([
mov(eax, 0),
label('startfor'),
cmp(eax, 10),
jge('breakfor'),
inc(eax),
jmp('startfor'),
label('breakfor'),
ret()
])
fn.restype = ctypes.c_uint32
print("Iterate to 10: %d" % fn())
print("""
Example 5: Write a string to stdout
------------------------------------------------------
""")
# Printing requires OS calls that are different for every platform.
# (However: you probably want to just call printf() anyway; see
# below for an example of calling an external function)
msg = ctypes.create_string_buffer(b"Howdy.\n")
if sys.platform == 'win32':
print("[ not implemented on win32 ]")
else:
if ARCH == 32:
prnt = [ # write to stdout on 32-bit linux
mov(eax, 4), # sys_write (see unistd_32.h)
mov(ebx, 1), # stdout
mov(ecx, ctypes.addressof(msg)),
mov(edx, len(msg)-1),
int_(0x80),
ret()
]
fn = mkfunction(prnt)
fn.restype = ctypes.c_uint32
else:
if sys.platform == 'darwin':
syscall_cmd = 0x2000004
else:
syscall_cmd = 0x1
prnt = [ # write to stdout on 64-bit linux
mov(rax, syscall_cmd), # write (see unistd_64.h)
mov(rdi, 1), # stdout
mov(rsi, ctypes.addressof(msg)),
mov(rdx, len(msg)-1),
syscall(),
ret()
]
fn = mkfunction(prnt)
fn.restype = ctypes.c_uint64
# print!
fn()
print("""
Example 6: Pass arguments to function
------------------------------------------------------
""")
# This example copies 8 bytes from one char* to another char*.
# Each platform uses a different calling convention:
if ARCH == 32:
if sys.platform == 'win32':
ret_byts = 8
else:
ret_byts = 0
# stdcall convention
fn = mkfunction([
mov(ecx, [esp+8]), # get arg 1 location from stack
mov(edx, [esp+4]), # get arg 0 location from stack
mov(eax, [ecx]), # get 4 bytes from arg 1 string
mov([edx], eax), # copy to arg 0 string
mov(eax, [ecx+4]), # get next 4 bytes
mov([edx+4], eax), # copy to arg 0 string
ret(8), # in stdcall, the callee must clean up the stack
])
else:
if sys.platform == 'win32':
# Microsoft x64 convention
fn = mkfunction([
mov(rax, [rdx]), # copy 8 bytes from second arg
mov([rcx], rax), # copy to first arg
ret(), # caller clean-up
])
else:
# System V AMD64 convention
fn = mkfunction([
mov(rax, [argi[1]]), # copy 8 bytes from second arg
mov([argi[0]], rax), # copy to first arg
ret(), # caller clean-up
])
fn.argtypes = (ctypes.c_char_p, ctypes.c_char_p)
msg1 = ctypes.create_string_buffer(b"original original")
msg2 = ctypes.create_string_buffer(b"modified modified")
fn(msg1, msg2)
print('Modified string: "%s"' % msg1.value)
print("""
Example 7: Call an external function
------------------------------------------------------
""")
# look up math.exp() from C standard lib
if sys.platform == 'darwin':
libm = ctypes.cdll.LoadLibrary('libm.dylib')
elif sys.platform == 'win32':
libm = ctypes.windll.msvcrt
else:
libm = ctypes.cdll.LoadLibrary('libm.so.6')
# Again we need to worry about calling conventions here..
if ARCH == 64:
# dereference the function pointer
fp = (ctypes.c_char*8).from_address(ctypes.addressof(libm.exp))
fp = struct.unpack('Q', fp[:8])[0]
# Most common 64-bit conventions pass the first float arg in xmm0
if sys.platform == 'win32':
exp = mkfunction([
push(rbp),
mov(rbp, rsp),
mov(rax, struct.pack('Q', fp)), # Load address of exp()
sub(rsp, 32), # MS requires 32-byte shadow on the stack.
call(rax), # call exp() - input arg is already in xmm0
add(rsp, 32),
pop(rbp),
ret(), # return; now output arg is in xmm0
])
else:
exp = mkfunction([
mov(rax, fp), # Load address of exp()
call(rax), # call exp() - input arg is already in xmm0
ret(), # return; now output arg is in xmm0
])
else:
# dereference the function pointer
fp = (ctypes.c_char*4).from_address(ctypes.addressof(libm.exp))
fp = struct.unpack('I', fp[:4])[0]
if sys.platform == 'win32':
ret_byts = 8
else:
ret_byts = 0
exp = mkfunction([
push(ebp), # Need to set up a proper frame here.
mov(ebp, esp),
push(dword([ebp+12])), # Copy input value to new location in stack
push(dword([ebp+8])),
mov(eax, fp), # Load address of exp()
call(eax), # call exp() - will clean up stack for us
mov(esp, ebp),
pop(ebp),
ret(ret_byts), # return; clean stack only in windows
])
exp.restype = ctypes.c_double
exp.argtypes = (ctypes.c_double,)
op = 3.1415
out = exp(op)
print("exp(%f) = %f =? %f" % (op, out, math.exp(op)))
print("""
Example 8: a useful function!
------------------------------------------------------
""")
if ARCH == 64:
find_first = mkfunction([
mov(rax, 0),
label('start_for'),
cmp(dword([argi[0]+rax*4]), 0),
jge('break_for'),
inc(rax),
cmp(rax, argi[1]),
jge('break_for'),
jmp('start_for'),
label('break_for'),
ret()
])
find_first.argtypes = [ctypes.c_uint64, ctypes.c_uint64]
find_first.restype = ctypes.c_uint64
else:
if sys.platform == 'win32':
ret_byts = 8
else:
ret_byts = 0
find_first = mkfunction([
mov(eax, 0),
mov(edx, dword([esp+8])), # array length
mov(ecx, dword([esp+4])), # base array pointer
label('start_for'),
cmp(dword([ecx+eax*4]), 0),
jge('break_for'),
inc(eax),
cmp(eax, edx),
jge('break_for'),
jmp('start_for'),
label('break_for'),
ret(ret_byts)
])
find_first.argtypes = [ctypes.c_uint32, ctypes.c_uint32]
find_first.restype = ctypes.c_uint32
find_first.__doc__ = "Return index of first value in an array that is >= 0"
# Create numpy array if possible, otherwise python array
if HAVE_NUMPY:
data = -1 + np.zeros(10000000, dtype=np.int32)
data[-1] = 1
addr = data.ctypes.data
else:
data = array.array('i', [-1]*1000000)
data[-1] = 1
addr = data.buffer_info()[0]
# Find first element >= 0 using asm
start = time.clock()
ind1 = find_first(addr, len(data))
duration1 = time.clock() - start
if HAVE_NUMPY:
# Find the first element >= 0 using numpy
start = time.clock()
ind2 = np.argwhere(data >= 0)[0,0]
duration2 = time.clock() - start
assert ind1 == ind2
print("First >= 0: %d" % ind1)
print("ASM version took %0.2fms" % (duration1*1000))
print("NumPy version took %0.2fms" % (duration2*1000))
else:
# Find the first element >= 0 using python
start = time.clock()
for i in range(len(data)):
if data[i] >= 0:
break
ind2 = i
duration2 = time.clock() - start
assert ind1 == ind2
print("First >= 0: %d" % ind1)
print("ASM version took %0.2fms" % (duration1*1000))
print("Python version took %0.2fms" % (duration2*1000))