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