Skip to content

Commit 4a4f32e

Browse files
committed
Add pygame/hilbert/mame code coverage example
1 parent e5b18f4 commit 4a4f32e

File tree

1 file changed

+241
-0
lines changed

1 file changed

+241
-0
lines changed

examples/dacman_live_hilbert.py

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
#!/usr/bin/env python
2+
#
3+
# display live code coverage of DACMAN Colecovision game running in MAME
4+
# drawn as Hilbert curve
5+
#
6+
# $ mame -v coleco -video soft -cart /path/to/DACMAN.ROM -window -nomax -resolution 560x432 -debugger gdbstub -debug
7+
# $ ./dacman_live_hilbert /path/to/DACMAN.ROM
8+
#
9+
10+
import os
11+
import sys
12+
import math
13+
import time
14+
import struct
15+
import platform
16+
from collections import defaultdict
17+
18+
import binaryninja
19+
from binaryninja.binaryview import BinaryViewType
20+
21+
from PIL import Image, ImageDraw
22+
23+
# globals
24+
n = None
25+
draw = None
26+
27+
#------------------------------------------------------------------------------
28+
# Hilbert curve mapping algorithms from:
29+
# https://en.wikipedia.org/wiki/Hilbert_curve
30+
#------------------------------------------------------------------------------
31+
32+
def rot(n, x, y, rx, ry):
33+
if ry == 0:
34+
if rx == 1:
35+
x = n-1 - x;
36+
y = n-1 - y;
37+
38+
(y,x) = (x,y)
39+
40+
return (x,y)
41+
42+
def d2xy(n, d):
43+
(x,y,t) = (0,0,d)
44+
45+
level = 1
46+
while level<n:
47+
rx = 1 & (t//2)
48+
ry = 1 & (t ^ rx)
49+
(x, y) = rot(level, x, y, rx, ry)
50+
x += level * rx
51+
y += level * ry
52+
t //= 4
53+
level *= 2
54+
55+
return (x,y)
56+
57+
def xy2d(n, x, y):
58+
(rx,ry,s,d)=(0,0,0,0)
59+
60+
s = n//2
61+
while s > 0:
62+
rx = int((x & s) > 0)
63+
ry = int((y & s) > 0)
64+
d += s * s * ((3 * rx) ^ ry)
65+
(x, y) = rot(n, x, y, rx, ry)
66+
s //= 2
67+
68+
return d
69+
70+
#------------------------------------------------------------------------------
71+
# Hilbert curve drawing helpers
72+
#------------------------------------------------------------------------------
73+
74+
# trace a Hilbert region by "wall following"
75+
def wall_follower(d0, d1):
76+
global n
77+
78+
def ok(x, y):
79+
if x<0 or y<0: return False
80+
d = xy2d(n**2, x, y)
81+
#print('is %d within %d,%d' % (d, d0, d1))
82+
return d>=0 and d>=d0 and d<d1
83+
84+
# move left until stop
85+
(x,y) = d2xy(n**2, d0)
86+
while 1:
87+
if x == 0: break
88+
if not ok(x-1,y): break
89+
x = x-1
90+
91+
start = (x,y)
92+
trace = [start]
93+
direction = 'down'
94+
95+
tendencies = ['right', 'down', 'left', 'up']
96+
97+
while 1:
98+
#print('at (%d,%d) heading %s' % (x,y,direction))
99+
100+
tendency = tendencies[(tendencies.index(direction)+1) % 4]
101+
102+
xmod = {'right':1, 'down':0, 'left':-1, 'up':0}
103+
ymod = {'right':0, 'down':-1, 'left':0, 'up':1}
104+
105+
moved = False
106+
107+
# case A: we can turn right
108+
x_try = x+xmod[tendency]
109+
y_try = y+ymod[tendency]
110+
if ok(x_try, y_try):
111+
direction = tendency
112+
(x,y) = (x_try, y_try)
113+
moved = True
114+
else:
115+
# case B: we can continue in current direction
116+
x_try = x+xmod[direction]
117+
y_try = y+ymod[direction]
118+
if ok(x_try, y_try):
119+
(x,y) = (x_try, y_try)
120+
moved = True
121+
else:
122+
# case C: we can't continue! ah!
123+
direction = tendencies[(tendencies.index(direction)-1)%4]
124+
125+
if moved:
126+
trace.append((x,y))
127+
128+
if (x,y) == start:
129+
break
130+
131+
return trace
132+
133+
# [start, stop)
134+
def draw_hilbert(start, stop, color='#ffffff'):
135+
global n
136+
global draw
137+
138+
pts = [d2xy(n, x) for x in range(start, stop)]
139+
lines = zip(pts[:-1], pts[1:])
140+
for line in lines:
141+
((x1,y1),(x2,y2)) = line
142+
#print('drawing line (%d,%d) -> (%d,%d)' % (x1,y1,x2,y2))
143+
draw.line((x1,y1,x2,y2), width=1, fill=color)
144+
145+
def draw_region(start, stop, color1='#00ff00', color2=None):
146+
global draw
147+
trace = wall_follower(start, stop)
148+
draw.polygon(trace, outline=color1, fill=color2)
149+
150+
#------------------------------------------------------------------------------
151+
# main()
152+
#------------------------------------------------------------------------------
153+
154+
if __name__ == '__main__':
155+
# analyze functions
156+
fpath = sys.argv[1]
157+
bv = BinaryViewType.get_view_of_file(fpath)
158+
bv.update_analysis_and_wait()
159+
lowest = None
160+
highest = None
161+
addr2func = {}
162+
for f in bv.functions:
163+
addr_start = f.start
164+
addr_end = f.start + f.total_bytes
165+
166+
if lowest==None or addr_start < lowest:
167+
lowest = addr_start
168+
if highest==None or addr_end >= highest:
169+
highest = addr_end
170+
171+
addr2func[addr_start] = f
172+
173+
print('lowest address: 0x%04X' % lowest)
174+
print('highest address: 0x%04X' % highest)
175+
176+
# launch debugger, set breakpoints
177+
from debugger import DebugAdapter, gdblike
178+
adapter = gdblike.connect_sense('localhost', 23946)
179+
for addr in addr2func:
180+
print('setting breakpoint at %04X: %s' % (addr, addr2func[addr].symbol.full_name))
181+
adapter.breakpoint_set(addr)
182+
183+
# calculate image size
184+
pixels = 1
185+
while pixels < (highest-lowest):
186+
pixels *= 4
187+
n = int(math.sqrt(pixels))
188+
print('n:', n)
189+
img = Image.new('RGB', (n,n))
190+
draw = ImageDraw.Draw(img)
191+
192+
# intialize pygame
193+
import pygame
194+
from pygame.locals import *
195+
pygame.init()
196+
surface = pygame.display.set_mode((4*n, 4*n), RESIZABLE)
197+
pygame.display.set_caption('DACMAN code coverage')
198+
199+
# palette is "tab20" from matplotlib
200+
palette_i = 0
201+
palette = [
202+
'#1F77B4', '#AEC7E8', '#FF7F0E', '#FFBB78', '#2CA02C', '#98DF8A', '#D62728', '#FF9896',
203+
'#9467BD', '#C5B0D5', '#8C564B', '#C49C94', '#E377C2', '#F7B6D2', '#7F7F7F', '#C7C7C7',
204+
'#BCBD22', '#DBDB8D', '#17BECF', '#9EDAE5'
205+
]
206+
207+
print('reading to rock, press any key!')
208+
input()
209+
210+
while 1:
211+
# process pygame events
212+
for event in pygame.event.get():
213+
if event.type == QUIT:
214+
pygame.quit()
215+
sys.exit()
216+
217+
# wait for breakpoint, clear it
218+
(reason, data) = adapter.go()
219+
assert reason in [DebugAdapter.STOP_REASON.BREAKPOINT, DebugAdapter.STOP_REASON.SINGLE_STEP]
220+
pc = adapter.reg_read('pc')
221+
f = addr2func[pc]
222+
print('%s()' % f.symbol.full_name)
223+
adapter.breakpoint_clear(pc)
224+
225+
# draw function
226+
addr_start = f.start
227+
addr_end = f.start + f.total_bytes
228+
if addr_end - addr_start < 4:
229+
continue
230+
print('drawing %s [0x%04X, 0x%04X)' % (f.symbol.full_name, addr_start, addr_end))
231+
draw_region(addr_start - lowest, addr_end - lowest, None, palette[palette_i])
232+
palette_i = (palette_i+1) % len(palette)
233+
234+
# drawing to pygame
235+
raw_str = img.tobytes('raw', 'RGB')
236+
img_surface = pygame.image.fromstring(raw_str, (n, n), 'RGB')
237+
img_surface = pygame.transform.scale(img_surface, (4*n, 4*n))
238+
surface.blit(img_surface, (0,0))
239+
pygame.display.update()
240+
241+
#time.sleep(.1)

0 commit comments

Comments
 (0)