-
Notifications
You must be signed in to change notification settings - Fork 4
/
patch.py
180 lines (154 loc) · 7.89 KB
/
patch.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
import sys
from patch_tools import Patcher, CallsiteValue
if len(sys.argv) < 5:
print("patch.py platform tintin_fw.bin libpebble.a tintin_fw.out.bin")
platform = sys.argv[1]
PLATFORM_UNSHAPE_MAP = {
"aplite": False
}
TEXT_UNSHAPE = PLATFORM_UNSHAPE_MAP.get(platform, True)
p = Patcher(
platform=platform,
target_bin_path=sys.argv[2],
libpebble_a_path=sys.argv[3],
patch_c_path="runtime/patch.c",
other_c_paths=[
"runtime/patch.S",
"runtime/text_shaper.c",
"runtime/text_shaper_lut.c",
"runtime/utf8.c",
"runtime/rtl.c",
"runtime/rtl_ranges.c",
"runtime/font_ranges.c"
],
cflags=["-DTEXT_UNSHAPE"] if TEXT_UNSHAPE else []
)
gdt_match = p.match_symbol("graphics_draw_text")
gdt_end_match = p.match(r"""JUMP
add sp, .+
ldmia.w sp!.+
.+
bx\s+lr""", start=gdt_match.start, n=0)
print("GDT %x - %x" % (gdt_match.start, gdt_end_match.start))
if TEXT_UNSHAPE:
# As it turns out, the Pebble text renderer runs with <16 bytes of stack free in some situations
# So we need to make sure the calls through to the OS use 0 more bytes stack than the original.
# For unshaping, we need to preserve the *text arg for the unshape call.
# We find that in graphics_draw_text's stack frame.
# However, we also need to intercept the return of the wrapped graphics_draw_text for this reshaping.
# So we can't just branch back to the old graphics_draw_text (we won't get execution back)
# Nor can we BL - since then we lose the caller's return site.
# So, need to patch the end of graphics_draw_text to return to our code without the use of LR.
gdt_return_overwrote_mcode = p.target_bin[gdt_end_match.start:p.addr_step(gdt_end_match.end, 2)]
# We need to trace the path of r1, aka *text.
r1_stash_reg = p.match(r"mov (?P<reg>r\d+), r1", start=gdt_match.start, n=0).groups["reg"]
# Does it go into the stack right away?
try:
text_struct_sp_off = int(p.match(r"str %s, \[sp, #(?P<off>\d+)\]" % r1_stash_reg, start=gdt_match.start, end=gdt_end_match.end, n=0).groups["off"])
print("Detected *text SP offset directly")
except AssertionError:
# I guess not.
# The text is stored in a struct or something on the stack, passed to the first call of graphics_draw_text.
first_call_match = p.match("bl .+", start=gdt_match.start, end=gdt_end_match.start, n=0)
first_call_r0_match = p.match(r"mov r0, (?P<reg>r\d+)", n=-1, start=gdt_match.start, end=first_call_match.start)
text_struct_sp_off = int(p.match(r"add %s, sp, #(?P<off>\d+)" % first_call_r0_match.groups["reg"], n=-1, start=gdt_match.start, end=first_call_r0_match.start).groups["off"])
print("Detected *text SP offset INdirectly")
# The end of graphics_draw_text is a stack pointer op, wide pop, another stack ptr op, then bx lr
# We need to grab a value of a register before popping - the one that points to *text.
text_ptr_match = p.match(r"mov r1, (?P<reg>r\d+)", end=gdt_end_match.start, n=-1)
unshape_asm = """
LDR r0, [sp, #""" + str(text_struct_sp_off) + """]
""" + \
"\n".join((".byte 0x%x" % ord(mc) for mc in gdt_return_overwrote_mcode[:8])) + """
@ At this point, we're back to immediately after the BL...
@ Run the un-shaper...
PUSH {ip, lr}
BL unshape_text
@ Done!
POP {ip, pc}
"""
p.inject("graphics_draw_text_unshape", gdt_end_match, asm=unshape_asm)
p.wrap("graphics_draw_text_patch", gdt_match)
p.wrap("graphics_text_layout_get_content_size_patch",
p.match_symbol("graphics_text_layout_get_content_size"),
"GSize", passthru=False)
p.wrap("graphics_text_layout_get_content_size_with_attributes_patch",
p.match_symbol("graphics_text_layout_get_content_size_with_attributes"),
"GSize")
# Find the layout driver function - it's the last call graphics_draw_text makes.
layout_driver_match = p.match(r"bl\s+0x(?P<fnc>[0-9a-f]+)$", start=gdt_match.start, end=gdt_end_match.end, n=-1)
layout_driver_addr = int(layout_driver_match.groups["fnc"], 16)
print("Layout driver start %x" % layout_driver_addr)
layout_driver_end_match = p.match(r"""
add sp, #(?P<sz1>\d+).*
ldm.+\{(?P<popregs>.+)\}
""", start=layout_driver_addr, n=0)
layout_driver_frame_size = int(layout_driver_end_match.groups["sz1"]) + (layout_driver_end_match.groups["popregs"].count(",") + 1) * 4
if platform == "aplite":
# Dig out a pointer to the structure that holds the input iteration state.
# The layout function has two fast-exit checks, then enters a setup block.
# The last call in this setup block is to the thing that sets up the desired structure.
# Its r1 is what we want.
layour_driver_setup_end = p.match(r"b(?:ne|eq).+", start=layout_driver_addr, n=2).start
layout_driver_last_call = p.match("bl.+", start=layout_driver_addr, end=layour_driver_setup_end, n=-1).start
lineend_sp_off = int(p.match("add r1, sp, #(?P<off>\d+).*", start=layout_driver_addr, end=layout_driver_last_call, n=-1).groups["off"])
print("Line-end stack pointer offset %x" % lineend_sp_off)
p.define_macro("LINEEND_SP_OFF", lineend_sp_off)
elif platform == "diorite":
# Empirically determined - probably only works on >=4.1.
p.define_macro("LINEEND_INDIRECT_SP_OFF", 56)
else:
# Very empirically determined, thanks gdb.
p.define_macro("LINEEND_SP_OFF", 124)
# This is the part that actually calls the render callback - which we intend to wrap.
render_handler_call_match = p.match(r"""
mov r0, (?P<gctx_reg>r\d+)
mov r1, (?P<layout_reg>r\d+)
JUMP
ldr r2, \[sp, #(?P<arg3_sp_off>\d+)\].*
blx (?P<hdlr_reg>r\d+)
b.+
""", start=layout_driver_addr, n=0)
more_text_reg_match = p.match(r"c(mp|bnz).+(?P<reg>r\d+).*", start=layout_driver_addr, end=render_handler_call_match.start, n=-1)
more_text_reg_value = CallsiteValue(register=more_text_reg_match.groups["reg"])
print("More-text register %s" % more_text_reg_match.groups["reg"])
print("Render handler call %x" % render_handler_call_match.start)
layout_reg_match = CallsiteValue(register=render_handler_call_match.groups["layout_reg"])
hdlr_reg_value = CallsiteValue(register=render_handler_call_match.groups["hdlr_reg"])
print("Handler function pointer register %s" % hdlr_reg_value.register)
if int(more_text_reg_value.register.strip("r")) < 4:
# We need more_text to survive the call through to the handler.
# So, it can't be in r0-r3
more_text_reg_match = p.match(r"c(mp|bnz).+(?P<reg>r\d+).*", start=layout_driver_addr, end=layout_driver_end_match.start, n=-3)
more_text_reg_value = CallsiteValue(register=more_text_reg_match.groups["reg"])
print("More-text register was in r0-r3!")
print("More-text register re-matched to %s" % more_text_reg_match.groups["reg"])
assert int(more_text_reg_value.register.strip("r")) > 2
p.define_macro("RENDERHDLR_ARG3_SP_OFF", render_handler_call_match.groups["arg3_sp_off"])
render_wrap_asm = """
@ At this point, we have the render handler args 1 (gcontext) and 2 (layout) in r0/r1, and were about to load the 3rd (??) from wherever.
@ The handler itself is in r3, probably
@ render_rtl_step wants *text, more_text, and callsite SP
@ So, first back up the render handler args
PUSH {r0-r3}
LDR r0, [""" + layout_reg_match.register + """]
MOV r1, """ + more_text_reg_value.register + """
ADD r2, sp, #16
@ We don't need to preserve LR!
BL render_rtl_step
POP {r0-r3}
@ Load the final handler argument
LDR r2, [sp, #""" + render_handler_call_match.groups["arg3_sp_off"] + """]
@ Run render handler
BLX """ + hdlr_reg_value.register + """
@ Run the RTL routine again
@ This time we need not preserve r0-r3
LDR r0, [""" + layout_reg_match.register + """]
MOV r1, """ + more_text_reg_value.register + """
MOV r2, sp
BL render_rtl_step
# Return to original site
B render_wrap__return
"""
p.inject("render_wrap", render_handler_call_match, asm=render_wrap_asm)
p.finalize(sys.argv[4])