forked from Flipper-XFW/Xtreme-Firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathflipperapps.py
204 lines (169 loc) · 7.15 KB
/
flipperapps.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
from dataclasses import dataclass
from typing import Optional, Tuple, Dict, ClassVar
import struct
import posixpath
import zlib
import gdb
def get_file_crc32(filename):
with open(filename, "rb") as f:
return zlib.crc32(f.read())
@dataclass
class AppState:
name: str
text_address: int = 0
entry_address: int = 0
other_sections: Dict[str, int] = None
debug_link_elf: str = ""
debug_link_crc: int = 0
DEBUG_ELF_ROOT: ClassVar[Optional[str]] = None
def __post_init__(self):
if self.other_sections is None:
self.other_sections = {}
def get_original_elf_path(self) -> str:
if self.DEBUG_ELF_ROOT is None:
raise ValueError("DEBUG_ELF_ROOT not set; call fap-set-debug-elf-root")
return (
posixpath.join(self.DEBUG_ELF_ROOT, self.debug_link_elf)
if self.DEBUG_ELF_ROOT
else self.debug_link_elf
)
def is_debug_available(self) -> bool:
have_debug_info = bool(self.debug_link_elf and self.debug_link_crc)
if not have_debug_info:
print("No debug info available for this app")
return False
debug_elf_path = self.get_original_elf_path()
debug_elf_crc32 = get_file_crc32(debug_elf_path)
if self.debug_link_crc != debug_elf_crc32:
print(
f"Debug info ({debug_elf_path}) CRC mismatch: {self.debug_link_crc:08x} != {debug_elf_crc32:08x}, rebuild app"
)
return False
return True
def get_gdb_load_command(self) -> str:
load_path = self.get_original_elf_path()
print(f"Loading debug information from {load_path}")
load_command = (
f"add-symbol-file -readnow {load_path} 0x{self.text_address:08x} "
)
load_command += " ".join(
f"-s {name} 0x{address:08x}"
for name, address in self.other_sections.items()
)
return load_command
def get_gdb_unload_command(self) -> str:
return f"remove-symbol-file -a 0x{self.text_address:08x}"
@staticmethod
def get_gdb_app_ep(app) -> int:
return int(app["state"]["entry"])
@staticmethod
def parse_debug_link_data(section_data: bytes) -> Tuple[str, int]:
# Debug link format: a null-terminated string with debuggable file name
# Padded with 0's to multiple of 4 bytes
# Followed by 4 bytes of CRC32 checksum of that file
elf_name = section_data[:-4].decode("utf-8").split("\x00")[0]
crc32 = struct.unpack("<I", section_data[-4:])[0]
return (elf_name, crc32)
@classmethod
def from_gdb(cls, gdb_app: "AppState") -> "AppState":
state = AppState(str(gdb_app["manifest"]["name"].string()))
state.entry_address = cls.get_gdb_app_ep(gdb_app)
app_state = gdb_app["state"]
if debug_link_size := int(app_state["debug_link_info"]["debug_link_size"]):
debug_link_data = (
gdb.selected_inferior()
.read_memory(
int(app_state["debug_link_info"]["debug_link"]), debug_link_size
)
.tobytes()
)
state.debug_link_elf, state.debug_link_crc = AppState.parse_debug_link_data(
debug_link_data
)
for idx in range(app_state["mmap_entry_count"]):
mmap_entry = app_state["mmap_entries"][idx]
section_name = mmap_entry["name"].string()
section_addr = int(mmap_entry["address"])
if section_name == ".text":
state.text_address = section_addr
else:
state.other_sections[section_name] = section_addr
return state
class SetFapDebugElfRoot(gdb.Command):
"""Set path to original ELF files for debug info"""
def __init__(self):
super().__init__(
"fap-set-debug-elf-root", gdb.COMMAND_FILES, gdb.COMPLETE_FILENAME
)
self.dont_repeat()
def invoke(self, arg, from_tty):
AppState.DEBUG_ELF_ROOT = arg
try:
global helper
print(f"Set '{arg}' as debug info lookup path for Flipper external apps")
helper.attach_to_fw()
gdb.events.stop.connect(helper.handle_stop)
gdb.events.exited.connect(helper.handle_exit)
except gdb.error as e:
print(f"Support for Flipper external apps debug is not available: {e}")
class FlipperAppStateHelper:
def __init__(self):
self.app_type_ptr = None
self.app_list_ptr = None
self.app_list_entry_type = None
self._current_apps: list[AppState] = []
def _walk_app_list(self, list_head):
while list_head:
if app := list_head["data"]:
yield app.dereference()
list_head = list_head["next"]
def _exec_gdb_command(self, command: str) -> bool:
try:
gdb.execute(command)
return True
except gdb.error as e:
print(f"Failed to execute GDB command '{command}': {e}")
return False
def _sync_apps(self) -> None:
self.set_debug_mode(True)
if not (app_list := self.app_list_ptr.value()):
print("Reset app loader state")
for app in self._current_apps:
self._exec_gdb_command(app.get_gdb_unload_command())
self._current_apps = []
return
loaded_apps: dict[int, gdb.Value] = dict(
(AppState.get_gdb_app_ep(app), app)
for app in self._walk_app_list(app_list[0])
)
for app in self._current_apps.copy():
if app.entry_address not in loaded_apps:
print(f"Application {app.name} is no longer loaded")
if not self._exec_gdb_command(app.get_gdb_unload_command()):
print(f"Failed to unload debug info for {app.name}")
self._current_apps.remove(app)
for entry_point, app in loaded_apps.items():
if entry_point not in set(app.entry_address for app in self._current_apps):
new_app_state = AppState.from_gdb(app)
print(f"New application loaded. Adding debug info")
if self._exec_gdb_command(new_app_state.get_gdb_load_command()):
self._current_apps.append(new_app_state)
else:
print(f"Failed to load debug info for {new_app_state}")
def attach_to_fw(self) -> None:
print("Attaching to Flipper firmware")
self.app_list_ptr = gdb.lookup_global_symbol(
"flipper_application_loaded_app_list"
)
self.app_type_ptr = gdb.lookup_type("FlipperApplication").pointer()
self.app_list_entry_type = gdb.lookup_type("struct FlipperApplicationList_s")
def handle_stop(self, event) -> None:
self._sync_apps()
def handle_exit(self, event) -> None:
self.set_debug_mode(False)
def set_debug_mode(self, mode: bool) -> None:
gdb.execute(f"set variable fap_loader_debug_active = {int(mode)}")
# Init additional 'fap-set-debug-elf-root' command and set up hooks
SetFapDebugElfRoot()
helper = FlipperAppStateHelper()
print("Support for Flipper external apps debug is loaded")