]>
Commit | Line | Data |
---|---|---|
686f0c36 PA |
1 | ## |
2 | ## This file is part of the libsigrokdecode project. | |
3 | ## | |
4 | ## Copyright (C) 2015 Petteri Aimonen <jpa@sigrok.mail.kapsi.fi> | |
5 | ## | |
6 | ## This program is free software; you can redistribute it and/or modify | |
7 | ## it under the terms of the GNU General Public License as published by | |
8 | ## the Free Software Foundation; either version 2 of the License, or | |
9 | ## (at your option) any later version. | |
10 | ## | |
11 | ## This program is distributed in the hope that it will be useful, | |
12 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | ## GNU General Public License for more details. | |
15 | ## | |
16 | ## You should have received a copy of the GNU General Public License | |
17 | ## along with this program; if not, write to the Free Software | |
18 | ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
19 | ## | |
20 | ||
21 | import sigrokdecode as srd | |
22 | import subprocess | |
23 | import re | |
24 | ||
25 | def parse_varint(bytes): | |
26 | '''Parse an integer where the top bit is the continuation bit. | |
27 | Returns value and number of parsed bytes.''' | |
28 | v = 0 | |
29 | for i, b in enumerate(bytes): | |
30 | v |= (b & 0x7F) << (i * 7) | |
31 | if b & 0x80 == 0: | |
32 | return v, i+1 | |
33 | return v, len(bytes) | |
34 | ||
35 | def parse_uint(bytes): | |
36 | '''Parse little-endian integer.''' | |
37 | v = 0 | |
38 | for i, b in enumerate(bytes): | |
39 | v |= b << (i * 8) | |
40 | return v | |
41 | ||
42 | def parse_exc_info(bytes): | |
43 | '''Parse exception information bytes from a branch packet.''' | |
44 | if len(bytes) < 1: | |
45 | return None | |
46 | ||
47 | excv, exclen = parse_varint(bytes) | |
48 | if bytes[exclen - 1] & 0x80 != 0x00: | |
49 | return None # Exception info not complete. | |
50 | ||
51 | if exclen == 2 and excv & (1 << 13): | |
52 | # Exception byte 1 was skipped, fix up the decoding. | |
53 | excv = (excv & 0x7F) | ((excv & 0x3F80) << 7) | |
54 | ||
55 | ns = excv & 1 | |
56 | exc = ((excv >> 1) & 0x0F) | ((excv >> 7) & 0x1F0) | |
57 | cancel = (excv >> 5) & 1 | |
58 | altisa = (excv >> 6) & 1 | |
59 | hyp = (excv >> 12) & 1 | |
60 | resume = (excv >> 14) & 0x0F | |
61 | return (ns, exc, cancel, altisa, hyp, resume) | |
62 | ||
63 | def parse_branch_addr(bytes, ref_addr, cpu_state, branch_enc): | |
64 | '''Parse encoded branch address. | |
65 | Returns addr, addrlen, cpu_state, exc_info. | |
66 | Returns None if packet is not yet complete''' | |
67 | ||
68 | addr, addrlen = parse_varint(bytes) | |
69 | ||
70 | if bytes[addrlen-1] & 0x80 != 0x00: | |
71 | return None # Branch address not complete. | |
72 | ||
73 | have_exc_info = False | |
74 | if branch_enc == 'original': | |
75 | if addrlen == 5 and bytes[4] & 0x40: | |
76 | have_exc_info = True | |
77 | elif branch_enc == 'alternative': | |
78 | if addrlen >= 2 and bytes[addrlen - 1] & 0x40: | |
79 | have_exc_info = True | |
80 | addr &= ~(1 << (addrlen * 7 - 1)) | |
81 | ||
82 | exc_info = None | |
83 | if have_exc_info: | |
84 | exc_info = parse_exc_info(bytes[addrlen:]) | |
85 | if exc_info is None: | |
86 | return None # Exception info not complete. | |
87 | ||
88 | if addrlen == 5: | |
89 | # Possible change in CPU state. | |
90 | if bytes[4] & 0xB8 == 0x08: | |
91 | cpu_state = 'arm' | |
92 | elif bytes[4] & 0xB0 == 0x10: | |
93 | cpu_state = 'thumb' | |
94 | elif bytes[4] & 0xA0 == 0x20: | |
95 | cpu_state = 'jazelle' | |
96 | else: | |
97 | raise NotImplementedError('Unhandled branch byte 4: 0x%02x' % bytes[4]) | |
98 | ||
99 | # Shift the address according to current CPU state. | |
100 | if cpu_state == 'arm': | |
101 | addr = (addr & 0xFFFFFFFE) << 1 | |
102 | elif cpu_state == 'thumb': | |
103 | addr = addr & 0xFFFFFFFE | |
104 | elif cpu_state == 'jazelle': | |
105 | addr = (addr & 0xFFFFFFFFE) >> 1 | |
106 | else: | |
107 | raise NotImplementedError('Unhandled state: ' + cpu_state) | |
108 | ||
109 | # If the address wasn't full, fill in with the previous address. | |
110 | if addrlen < 5: | |
111 | bits = 7 * addrlen | |
112 | addr |= ref_addr & (0xFFFFFFFF << bits) | |
113 | ||
114 | return addr, addrlen, cpu_state, exc_info | |
115 | ||
116 | class Decoder(srd.Decoder): | |
117 | api_version = 2 | |
118 | id = 'arm_etmv3' | |
119 | name = 'ARM ETMv3' | |
120 | longname = 'ARM Embedded Trace Macroblock' | |
121 | desc = 'Decode ETM instruction trace packets.' | |
122 | license = 'gplv2+' | |
123 | inputs = ['uart'] | |
124 | outputs = ['arm_etmv3'] | |
125 | annotations = ( | |
126 | ('trace', 'Trace info'), | |
127 | ('branch', 'Branches'), | |
128 | ('exception', 'Exceptions'), | |
129 | ('execution', 'Instruction execution'), | |
130 | ('data', 'Data access'), | |
131 | ('pc', 'Program counter'), | |
132 | ('instr_e', 'Executed instructions'), | |
133 | ('instr_n', 'Not executed instructions'), | |
134 | ('source', 'Source code'), | |
135 | ('location', 'Current location'), | |
136 | ('function', 'Current function'), | |
137 | ) | |
138 | annotation_rows = ( | |
139 | ('trace', 'Trace info', (0,)), | |
140 | ('flow', 'Code flow', (1, 2, 3,)), | |
141 | ('data', 'Data access', (4,)), | |
142 | ('pc', 'Program counter', (5,)), | |
143 | ('instruction', 'Instructions', (6, 7,)), | |
144 | ('source', 'Source code', (8,)), | |
145 | ('location', 'Current location', (9,)), | |
146 | ('function', 'Current function', (10,)), | |
147 | ) | |
148 | options = ( | |
149 | {'id': 'objdump', 'desc': 'objdump path', | |
150 | 'default': 'arm-none-eabi-objdump'}, | |
151 | {'id': 'objdump_opts', 'desc': 'objdump options', | |
152 | 'default': '-lS'}, | |
153 | {'id': 'elffile', 'desc': '.elf path', | |
154 | 'default': ''}, | |
155 | {'id': 'branch_enc', 'desc': 'Branch encoding', | |
156 | 'default': 'alternative', 'values': ('alternative', 'original')}, | |
157 | ) | |
158 | ||
159 | def __init__(self, **kwargs): | |
160 | self.buf = [] | |
161 | self.syncbuf = [] | |
162 | self.prevsample = 0 | |
163 | self.last_branch = 0 | |
164 | self.cpu_state = 'arm' | |
165 | self.current_pc = 0 | |
166 | self.current_loc = None | |
167 | self.current_func = None | |
168 | self.next_instr_lookup = {} | |
169 | self.file_lookup = {} | |
170 | self.func_lookup = {} | |
171 | self.disasm_lookup = {} | |
172 | self.source_lookup = {} | |
173 | ||
174 | def start(self): | |
175 | self.out_ann = self.register(srd.OUTPUT_ANN) | |
176 | self.load_objdump() | |
177 | ||
178 | def load_objdump(self): | |
179 | '''Parse disassembly obtained from objdump into two tables: | |
180 | next_instr_lookup: Find the next PC addr from current PC. | |
181 | disasm_lookup: Find the instruction text from current PC. | |
182 | source_lookup: Find the source code line from current PC. | |
183 | ''' | |
184 | if not (self.options['objdump'] and self.options['elffile']): | |
185 | return | |
186 | ||
187 | opts = [self.options['objdump']] | |
188 | opts += self.options['objdump_opts'].split() | |
189 | opts += [self.options['elffile']] | |
190 | ||
191 | try: | |
192 | disasm = subprocess.check_output(opts) | |
193 | except subprocess.CalledProcessError: | |
194 | return | |
195 | ||
196 | disasm = disasm.decode('utf-8', 'replace') | |
197 | ||
198 | instpat = re.compile('\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*') | |
199 | branchpat = re.compile('(b|bl|b..|bl..|cbnz|cbz)(?:\.[wn])?\s+(?:r[0-9]+,\s*)?([0-9a-fA-F]+)') | |
200 | filepat = re.compile('[^\s]+[/\\\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?') | |
201 | funcpat = re.compile('[0-9a-fA-F]+\s*<([a-zA-Z0-9_-]+)>:.*') | |
202 | ||
203 | prev_src = '' | |
204 | prev_file = '' | |
205 | prev_func = '' | |
206 | ||
207 | for line in disasm.split('\n'): | |
208 | m = instpat.match(line) | |
209 | if m: | |
210 | addr = int(m.group(1), 16) | |
211 | raw = m.group(2) | |
212 | disas = m.group(3).strip().replace('\t', ' ') | |
213 | self.disasm_lookup[addr] = disas | |
214 | self.source_lookup[addr] = prev_src | |
215 | self.file_lookup[addr] = prev_file | |
216 | self.func_lookup[addr] = prev_func | |
217 | ||
218 | # Next address in direct sequence. | |
219 | ilen = len(raw.replace(' ', '')) // 2 | |
220 | next_n = addr + ilen | |
221 | ||
222 | # Next address if branch is taken. | |
223 | bm = branchpat.match(disas) | |
224 | if bm: | |
225 | next_e = int(bm.group(2), 16) | |
226 | else: | |
227 | next_e = next_n | |
228 | ||
229 | self.next_instr_lookup[addr] = (next_n, next_e) | |
230 | else: | |
231 | m = funcpat.match(line) | |
232 | if m: | |
233 | prev_func = m.group(1) | |
234 | else: | |
235 | m = filepat.match(line) | |
236 | if m: | |
237 | prev_file = m.group(1) | |
238 | else: | |
239 | prev_src = line.strip() | |
240 | ||
241 | def flush_current_loc(self): | |
242 | if self.current_loc is not None: | |
243 | ss, es, loc, src = self.current_loc | |
244 | if loc: | |
245 | self.put(ss, es, self.out_ann, [9, [loc]]) | |
246 | if src: | |
247 | self.put(ss, es, self.out_ann, [8, [src]]) | |
248 | self.current_loc = None | |
249 | ||
250 | def flush_current_func(self): | |
251 | if self.current_func is not None: | |
252 | ss, es, func = self.current_func | |
253 | if func: | |
254 | self.put(ss, es, self.out_ann, [10, [func]]) | |
255 | self.current_func = None | |
256 | ||
257 | def instructions_executed(self, exec_status): | |
258 | '''Advance program counter based on executed instructions. | |
259 | Argument is a list of False for not executed and True for executed | |
260 | instructions. | |
261 | ''' | |
262 | ||
263 | tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status)) | |
264 | ||
265 | for i, exec_status in enumerate(exec_status): | |
266 | pc = self.current_pc | |
267 | default_next = pc + 2 if self.cpu_state == 'thumb' else pc + 4 | |
268 | target_n, target_e = self.next_instr_lookup.get(pc, (default_next, default_next)) | |
269 | ss = self.startsample + round(tdelta * i) | |
270 | es = self.startsample + round(tdelta * (i+1)) | |
271 | ||
272 | self.put(ss, es, self.out_ann, | |
273 | [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]]) | |
274 | ||
275 | new_loc = self.file_lookup.get(pc) | |
276 | new_src = self.source_lookup.get(pc) | |
277 | new_dis = self.disasm_lookup.get(pc) | |
278 | new_func = self.func_lookup.get(pc) | |
279 | ||
280 | # Report source line only when it changes. | |
281 | if self.current_loc is not None: | |
282 | if new_loc != self.current_loc[2] or new_src != self.current_loc[3]: | |
283 | self.flush_current_loc() | |
284 | ||
285 | if self.current_loc is None: | |
286 | self.current_loc = [ss, es, new_loc, new_src] | |
287 | else: | |
288 | self.current_loc[1] = es | |
289 | ||
290 | # Report function name only when it changes. | |
291 | if self.current_func is not None: | |
292 | if new_func != self.current_func[2]: | |
293 | self.flush_current_func() | |
294 | ||
295 | if self.current_func is None: | |
296 | self.current_func = [ss, es, new_func] | |
297 | else: | |
298 | self.current_func[1] = es | |
299 | ||
300 | # Report instruction every time. | |
301 | if new_dis: | |
302 | if exec_status: | |
303 | a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]] | |
304 | else: | |
305 | a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]] | |
306 | self.put(ss, es, self.out_ann, a) | |
307 | ||
308 | if exec_status: | |
309 | self.current_pc = target_e | |
310 | else: | |
311 | self.current_pc = target_n | |
312 | ||
313 | def get_packet_type(self, byte): | |
314 | '''Identify packet type based on its first byte. | |
315 | See ARM IHI0014Q section "ETMv3 Signal Protocol" "Packet Types" | |
316 | ''' | |
317 | if byte & 0x01 == 0x01: | |
318 | return 'branch' | |
319 | elif byte == 0x00: | |
320 | return 'a_sync' | |
321 | elif byte == 0x04: | |
322 | return 'cyclecount' | |
323 | elif byte == 0x08: | |
324 | return 'i_sync' | |
325 | elif byte == 0x0C: | |
326 | return 'trigger' | |
327 | elif byte & 0xF3 in (0x20, 0x40, 0x60): | |
328 | return 'ooo_data' | |
329 | elif byte == 0x50: | |
330 | return 'store_failed' | |
331 | elif byte == 0x70: | |
332 | return 'i_sync' | |
333 | elif byte & 0xDF in (0x54, 0x58, 0x5C): | |
334 | return 'ooo_place' | |
335 | elif byte == 0x3C: | |
336 | return 'vmid' | |
337 | elif byte & 0xD3 == 0x02: | |
338 | return 'data' | |
339 | elif byte & 0xFB == 0x42: | |
340 | return 'timestamp' | |
341 | elif byte == 0x62: | |
342 | return 'data_suppressed' | |
343 | elif byte == 0x66: | |
344 | return 'ignore' | |
345 | elif byte & 0xEF == 0x6A: | |
346 | return 'value_not_traced' | |
347 | elif byte == 0x6E: | |
348 | return 'context_id' | |
349 | elif byte == 0x76: | |
350 | return 'exception_exit' | |
351 | elif byte == 0x7E: | |
352 | return 'exception_entry' | |
353 | elif byte & 0x81 == 0x80: | |
354 | return 'p_header' | |
355 | else: | |
356 | return 'unknown' | |
357 | ||
358 | def fallback(self, buf): | |
359 | ptype = self.get_packet_type(buf[0]) | |
360 | return [0, ['Unhandled ' + ptype + ': ' + ' '.join(['%02x' % b for b in buf])]] | |
361 | ||
362 | def handle_a_sync(self, buf): | |
363 | if buf[-1] == 0x80: | |
364 | return [0, ['Synchronization']] | |
365 | ||
366 | def handle_exception_exit(self, buf): | |
367 | return [2, 'Exception exit'] | |
368 | ||
369 | def handle_exception_entry(self, buf): | |
370 | return [1, 'Exception entry'] | |
371 | ||
372 | def handle_i_sync(self, buf): | |
373 | contextid_bytes = 0 # This is the default ETM config. | |
374 | ||
375 | if len(buf) < 6: | |
376 | return None # Packet definitely not full yet. | |
377 | ||
378 | if buf[0] == 0x08: # No cycle count. | |
379 | cyclecount = None | |
380 | idx = 1 + contextid_bytes # Index to info byte. | |
381 | elif buf[0] == 0x70: # With cycle count. | |
382 | cyclecount, cyclen = parse_varint(buf[1:6]) | |
383 | idx = 1 + cyclen + contextid_bytes | |
384 | ||
385 | if len(buf) <= idx + 4: | |
386 | return None | |
387 | infobyte = buf[idx] | |
388 | addr = parse_uint(buf[idx+1:idx+5]) | |
389 | ||
390 | reasoncode = (infobyte >> 5) & 3 | |
391 | reason = ('Periodic', 'Tracing enabled', 'After overflow', 'Exit from debug')[reasoncode] | |
392 | jazelle = (infobyte >> 4) & 1 | |
393 | nonsec = (infobyte >> 3) & 1 | |
394 | altisa = (infobyte >> 2) & 1 | |
395 | hypervisor = (infobyte >> 1) & 1 | |
396 | thumb = addr & 1 | |
397 | addr &= 0xFFFFFFFE | |
398 | ||
399 | if reasoncode == 0 and self.current_pc != addr: | |
400 | self.put(self.startsample, self.prevsample, self.out_ann, | |
401 | [0, ['WARN: Unexpected PC change 0x%08x -> 0x%08x' % \ | |
402 | (self.current_pc, addr)]]) | |
403 | elif reasoncode != 0: | |
404 | # Reset location when the trace has been interrupted. | |
405 | self.flush_current_loc() | |
406 | self.flush_current_func() | |
407 | ||
408 | self.last_branch = addr | |
409 | self.current_pc = addr | |
410 | ||
411 | if jazelle: | |
412 | self.cpu_state = 'jazelle' | |
413 | elif thumb: | |
414 | self.cpu_state = 'thumb' | |
415 | else: | |
416 | self.cpu_state = 'arm' | |
417 | ||
418 | cycstr = '' | |
419 | if cyclecount is not None: | |
420 | cycstr = ', cyclecount %d' % cyclecount | |
421 | ||
422 | if infobyte & 0x80: # LSIP packet | |
423 | self.put(self.startsample, self.prevsample, self.out_ann, | |
424 | [0, ['WARN: LSIP I-Sync packet not implemented']]) | |
425 | ||
426 | return [0, ['I-Sync: %s, PC 0x%08x, %s state%s' % \ | |
427 | (reason, addr, self.cpu_state, cycstr), \ | |
428 | 'I-Sync: %s 0x%08x' % (reason, addr)]] | |
429 | ||
430 | def handle_trigger(self, buf): | |
431 | return [0, ['Trigger event', 'Trigger']] | |
432 | ||
433 | def handle_p_header(self, buf): | |
434 | # Only non cycle-accurate mode supported. | |
435 | if buf[0] & 0x83 == 0x80: | |
436 | n = (buf[0] >> 6) & 1 | |
437 | e = (buf[0] >> 2) & 15 | |
438 | ||
439 | self.instructions_executed([1] * e + [0] * n) | |
440 | ||
441 | if n: | |
442 | return [3, ['%d instructions executed, %d skipped due to ' \ | |
443 | 'condition codes' % (e, n), | |
444 | '%d ins exec, %d skipped' % (e, n), | |
445 | '%dE,%dN' % (e, n)]] | |
446 | else: | |
447 | return [3, ['%d instructions executed' % e, | |
448 | '%d ins exec' % e, '%dE' % e]] | |
449 | elif buf[0] & 0xF3 == 0x82: | |
450 | i1 = (buf[0] >> 3) & 1 | |
451 | i2 = (buf[0] >> 2) & 1 | |
452 | self.instructions_executed([not i1, not i2]) | |
453 | txt1 = ('executed', 'skipped') | |
454 | txt2 = ('E', 'S') | |
455 | return [3, ['Instruction 1 %s, instruction 2 %s' % (txt1[i1], txt1[i2]), | |
456 | 'I1 %s, I2 %s' % (txt2[i1], txt2[i2]), | |
457 | '%s,%s' % (txt2[i1], txt2[i2])]] | |
458 | else: | |
459 | return self.fallback(buf) | |
460 | ||
461 | def handle_branch(self, buf): | |
462 | if buf[-1] & 0x80 != 0x00: | |
463 | return None # Not complete yet. | |
464 | ||
465 | brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state, | |
466 | self.options['branch_enc']) | |
467 | ||
468 | if brinfo is None: | |
469 | return None # Not complete yet. | |
470 | ||
471 | addr, addrlen, cpu_state, exc_info = brinfo | |
472 | self.last_branch = addr | |
473 | self.current_pc = addr | |
474 | ||
475 | txt = '' | |
476 | ||
477 | if cpu_state != self.cpu_state: | |
478 | txt += ', to %s state' % cpu_state | |
479 | self.cpu_state = cpu_state | |
480 | ||
481 | if exc_info: | |
482 | ns, exc, cancel, altisa, hyp, resume = exc_info | |
483 | if ns: | |
484 | txt += ', to non-secure state' | |
485 | if exc: | |
486 | # TODO: Parse the exception value to text. | |
487 | txt += ', exception 0x%02x' % exc | |
488 | if cancel: | |
489 | txt += ', instr cancelled' | |
490 | if altisa: | |
491 | txt += ', to AltISA' | |
492 | if hyp: | |
493 | txt += ', to hypervisor' | |
494 | if resume: | |
495 | txt += ', instr resume 0x%02x' % resume | |
496 | ||
497 | return [1, ['Branch to 0x%08x%s' % (addr, txt), | |
498 | 'B 0x%08x%s' % (addr, txt)]] | |
499 | ||
500 | def decode(self, ss, es, data): | |
501 | ptype, rxtx, pdata = data | |
502 | ||
503 | if ptype != 'DATA': | |
504 | return | |
505 | ||
506 | # Reset packet if there is a long pause between bytes. | |
507 | # This helps getting the initial synchronization. | |
508 | self.byte_len = es - ss | |
509 | if ss - self.prevsample > 16 * self.byte_len: | |
510 | self.flush_current_loc() | |
511 | self.flush_current_func() | |
512 | self.buf = [] | |
513 | self.prevsample = es | |
514 | ||
515 | self.buf.append(pdata[0]) | |
516 | ||
517 | # Store the start time of the packet. | |
518 | if len(self.buf) == 1: | |
519 | self.startsample = ss | |
520 | ||
521 | # Keep separate buffer for detection of sync packets. | |
522 | # Sync packets override everything else, so that we can regain sync | |
523 | # even if some packets are corrupted. | |
524 | self.syncbuf = self.syncbuf[-4:] + [pdata[0]] | |
525 | if self.syncbuf == [0x00, 0x00, 0x00, 0x00, 0x80]: | |
526 | self.buf = self.syncbuf | |
527 | self.syncbuf = [] | |
528 | ||
529 | # See if it is ready to be decoded. | |
530 | ptype = self.get_packet_type(self.buf[0]) | |
531 | if hasattr(self, 'handle_' + ptype): | |
532 | func = getattr(self, 'handle_' + ptype) | |
533 | data = func(self.buf) | |
534 | else: | |
535 | data = self.fallback(self.buf) | |
536 | ||
537 | if data is not None: | |
538 | if data: | |
539 | self.put(self.startsample, es, self.out_ann, data) | |
540 | self.buf = [] |