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