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