]> sigrok.org Git - libsigrokdecode.git/blame - decoders/arm_etmv3/pd.py
Add ARM TPIU/ITM/ETMv3 decoders
[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
25def 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
35def 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
42def 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
63def 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
116class 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 = []