-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathXatos.py
executable file
·215 lines (196 loc) · 10.5 KB
/
Xatos.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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
from os import path, linesep, environ
from fileinput import (input as fi_input, close as fi_close)
from re import compile as re_compile
from subprocess import (check_output as sp_co, CalledProcessError)
from sys import (argv as sys_argv, getfilesystemencoding as getfsencoding)
from codecs import open
__author__ = 'Xiao Wang, linhan.wx'
print_ng = lambda *args, **kwargs: print(*[unicode(i).encode(getfsencoding()) for i in args], **kwargs)
class Xatos(object):
def __init__(self, crashlog_path, dsym_or_app_path):
self.crashlog_path = self.get_abs_path(crashlog_path)
self.get_crashlog_info()
self.dsym_or_app_path = self.get_dsym_path(dsym_or_app_path)
arm_uuid_dict = self.get_arm_uuid()
if arm_uuid_dict.get(self.bin_arch) != self.bin_uuid:
raise SystemExit(
"ERROR: UUID in crashlog and binary (dSYM/app) are not consistent:{}Crashlog: ({}) {}{}Binary: ({}) {}".format(
linesep, self.bin_arch, self.bin_uuid, linesep, self.bin_arch, arm_uuid_dict.get(self.bin_arch)))
self.bin_line_head_ptn = re_compile('^\d+\s+{}\s+(0x[0-9a-f]+)\s+'.format(self.bin_name))
self.bin_line_tail_ptn = re_compile(':\d+\)')
self.slide_addr = self.get_slide_addr()
# load addr example: 17 AliTrip 0x0002b56e 0x21000 + 42350
self.load_addr_ptn = re_compile('^\d+\s+{}\s+(0x[0-9a-f]+)\s+(0x[0-9a-f]+)\s+\+\s+\d+'.format(self.bin_name))
# stack addr example: 2 AliTrip 0x00000001003dc6d8 curl_multi_setopt (in AliTrip) + 788
self.stack_decimal_ptn = re_compile('^\d+\s+{}\s+(0x[0-9a-f]+)\s+.+\+\s+(\d+)'.format(self.bin_name))
self.__env = environ.copy()
@staticmethod
def get_abs_path(a_path):
abs_path = path.abspath(a_path.decode(getfsencoding()))
if path.exists(abs_path):
return abs_path
else:
raise SystemExit('ERROR:: File not found: {}'.format(abs_path))
def get_dsym_path(self, a_path):
abs_path = self.get_abs_path(a_path)
if abs_path.lower().endswith('.dsym'):
abs_path = path.join(abs_path, 'Contents', 'Resources', 'DWARF', self.bin_name)
return abs_path
def get_arm_uuid(self):
uuid_arm_ptn = re_compile('([A-F0-9-]+) \((.+)\)')
arm_uuid_dict = {}
try:
output = sp_co(['dwarfdump', '-u', self.dsym_or_app_path]).decode(getfsencoding())
except CalledProcessError as cpe:
raise SystemExit(cpe.output)
for i in output.splitlines():
match = uuid_arm_ptn.search(i)
if match:
uuid, arm = match.groups()
lower_uuid = uuid.replace('-', '').lower()
arm_uuid_dict[arm] = lower_uuid
return arm_uuid_dict
def desymbolicate_file(self):
self.symbolicatecrash()
desym_result = self.desymbolicate()
for line in fi_input(self.crashlog_path, backup='.dbak', inplace=1):
if line in desym_result.keys():
print_ng(desym_result[line])
else:
print_ng(line.rstrip())
fi_close()
print_ng("desymbolicated log saved to '",self.crashlog_path,"', original log saved to '",self.crashlog_path + '.dbak',"'",sep='')
def symbolicatecrash(self):
'''use symbolicatecrash when dSYM file available'''
try:
sp_co(['which', 'xcodebuild'])
self.__dev_dir = sp_co(['xcode-select', '-p']).decode(getfsencoding()).rstrip()
except CalledProcessError as cpe:
print_ng(cpe.output)
print_ng('WARNING:: Xcode not installed, using "atos" instead of "symbolicatecrash"')
else:
self.__symbolicatecrash_path = self.get_symbolicatecrash_path()
if not self.__env.get('DEVELOPER_DIR'):
self.__env['DEVELOPER_DIR'] = self.__dev_dir
if '.dsym' in self.dsym_or_app_path.lower():
try:
output = sp_co([self.__symbolicatecrash_path, self.crashlog_path, self.dsym_or_app_path],
env=self.__env).decode(getfsencoding())
with open(self.crashlog_path, 'w',encoding=getfsencoding()) as f:
f.write(output)
except CalledProcessError:
print_ng('WARNING:: symbolicatecrash failed, will use "atos" only')
def get_symbolicatecrash_path(self):
if self.__dev_dir:
xcode_version_list = sp_co(['xcodebuild', '-version']).decode(getfsencoding()).rstrip().split()
xcode_verion = xcode_version_list[xcode_version_list.index('Xcode') + 1]
if xcode_verion < '4.3':
return '/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKit.framework/Versions/A/Resources/symbolicatecrash'
elif xcode_verion < '5':
return path.join(self.__dev_dir,
'Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKit.framework/Versions/A/Resources/symbolicatecrash')
elif xcode_verion < '6':
return path.join(self.__dev_dir,
'Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash')
else:
return path.join(path.dirname(self.__dev_dir),
'SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash')
def get_crashlog_info(self):
bin_img_line_flag = False
bin_img_line = ''
with open(self.crashlog_path, encoding=getfsencoding()) as crash_fp:
for line in crash_fp:
line = line.strip()
if not line:
continue
if bin_img_line_flag:
bin_img_line = line
break
if 'Binary Images:' in line:
bin_img_line_flag = True
if not bin_img_line:
raise SystemExit('Cannot find crash app binary image info')
bin_img_info = [i for i in bin_img_line.rstrip().split() if i and i != '-']
self.load_addr, end_laddr, bin_name, bin_arch, bin_uuid, bin_path = bin_img_info
self.bin_uuid = bin_uuid.strip('<>')
self.bin_name = bin_name.replace('+', '')
self.bin_arch = bin_arch
def desymbolicate(self):
desym_result = {}
with open(self.crashlog_path, encoding=getfsencoding()) as crash_fp:
for line in crash_fp:
line = line.decode(getfsencoding())
if self.bin_line_head_ptn.search(line) and not self.bin_line_tail_ptn.search(line):
desym_result.setdefault(line)
all_stack_addr = []
all_symbol_addr = []
lines = desym_result.keys()
stack_decimal_ptn = False
for line in lines:
load_addr_result = self.load_addr_ptn.findall(line)
if load_addr_result:
stack_addr, load_addr = load_addr_result[0]
all_stack_addr.append(stack_addr)
assert load_addr == self.load_addr, 'ERROR:: Crashlog is invalid, load address is not the same'
stack_addr_result = self.stack_decimal_ptn.findall(line)
if stack_addr_result:
"""
http://stackoverflow.com/a/13576028/562154
symbol address = slide + stack address - load address
The slide value is the value of vmaddr in LC_SEGMENT cmd (Mostly this is 0x1000). Run the following to get it:
xcrun -sdk iphoneos otool -arch ARCHITECTURE -l "APP_BUNDLE/APP_EXECUTABLE" | grep -A 10 "LC_SEGMENT" | grep -A 8 "__TEXT"|grep vmaddr
Replace ARCHITECTURE with the actual architecture the crash report shows, e.g. armv7. Replace APP_BUNDLE/APP_EXECUTABLE with the path to the actual executable.
The stack address is the hex value from the crash report.
The load address is the first address showing in the Binary Images section at the very front of the line which contains your executable. (Usually the first entry).
"""
stack_decimal_ptn = True
stack_addr, decimal_sum = stack_addr_result[0]
assert self.load_addr is not None, 'ERROR:: Cannot get load address'
symbol_addr = hex(int(self.slide_addr, 16) + int(stack_addr, 16) - int(self.load_addr, 16))
all_symbol_addr.append(symbol_addr)
if stack_decimal_ptn:
atos_cmd = ['xcrun', 'atos', '-o', self.dsym_or_app_path, '-arch', self.bin_arch]
atos_cmd.extend(all_symbol_addr)
else:
atos_cmd = ['xcrun', 'atos', '-o', self.dsym_or_app_path, '-l', self.load_addr, '-arch', self.bin_arch]
atos_cmd.extend(all_stack_addr)
try:
dsymed = [i for i in sp_co(atos_cmd).decode(getfsencoding()).splitlines() if i]
except CalledProcessError as cpe:
raise SystemExit('ERROR:: ' + cpe.output)
if len(lines) != len(dsymed):
raise SystemExit('ERROR:: Crashlog desymbolicate error!')
for idx, line in enumerate(lines):
desym_result[line] = '{}{}'.format(self.bin_line_head_ptn.search(line).group(), dsymed[idx])
return desym_result
def get_slide_addr(self):
otool_cmd = ['xcrun', 'otool', '-arch', self.bin_arch, '-l', self.dsym_or_app_path]
output = sp_co(otool_cmd).decode(getfsencoding())
lines = [i.strip() for i in output.splitlines() if i]
cmd_segment = False
segname_text = False
for line in lines:
if 'cmd LC_SEGMENT' in line:
cmd_segment = True
if cmd_segment and 'segname __' in line and 'segname __TEXT' not in line:
cmd_segment = False
if 'segname __TEXT' in line:
segname_text = True
if cmd_segment and segname_text:
if 'vmaddr' in line:
return line.split()[1]
raise SystemExit('ERROR:: cannot get binary image slide address')
def main():
if len(sys_argv) != 3:
raise SystemExit("""Xatos.py path/to/crashlog.crash path/to/dSYM/or/app/binary
Example:
1) Xatos.py "MyApp_1986-1-1_Sean-teki-iPhone.crash" "Payload/MyApp.app/MyApp"
2) Xatos.py "MyApp_1995-6-1_Noelani-teki-iPad.ips" "MyApp.app.dSYM"
""")
else:
Xatos(sys_argv[1], sys_argv[2]).desymbolicate_file()
if __name__ == '__main__':
main()