2 ## This file is part of the libsigrokdecode project.
4 ## Copyright (C) 2015 Petteri Aimonen <jpa@sigrok.mail.kapsi.fi>
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.
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.
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
21 import sigrokdecode as srd
25 # See ETMv3 Signal Protocol table 7-11: 'Encoding of Exception[8:0]'.
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'
32 for i in range(8, 496):
33 exc_names.append('IRQ%d' % i)
35 def parse_varint(bytes):
36 '''Parse an integer where the top bit is the continuation bit.
37 Returns value and number of parsed bytes.'''
39 for i, b in enumerate(bytes):
40 v |= (b & 0x7F) << (i * 7)
45 def parse_uint(bytes):
46 '''Parse little-endian integer.'''
48 for i, b in enumerate(bytes):
52 def parse_exc_info(bytes):
53 '''Parse exception information bytes from a branch packet.'''
57 excv, exclen = parse_varint(bytes)
58 if bytes[exclen - 1] & 0x80 != 0x00:
59 return None # Exception info not complete.
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)
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)
73 def 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'''
78 addr, addrlen = parse_varint(bytes)
80 if bytes[addrlen-1] & 0x80 != 0x00:
81 return None # Branch address not complete.
83 addr_bits = 7 * addrlen
86 if branch_enc == 'original':
87 if addrlen == 5 and bytes[4] & 0x40:
89 elif branch_enc == 'alternative':
90 addr_bits -= 1 # Top bit of address indicates exc_info.
91 if addrlen >= 2 and addr & (1 << addr_bits):
93 addr &= ~(1 << addr_bits)
97 exc_info = parse_exc_info(bytes[addrlen:])
99 return None # Exception info not complete.
102 # Possible change in CPU state.
103 if bytes[4] & 0xB8 == 0x08:
105 elif bytes[4] & 0xB0 == 0x10:
107 elif bytes[4] & 0xA0 == 0x20:
108 cpu_state = 'jazelle'
110 raise NotImplementedError('Unhandled branch byte 4: 0x%02x' % bytes[4])
112 # Shift the address according to current CPU state.
113 if cpu_state == 'arm':
114 addr = (addr & 0xFFFFFFFE) << 1
116 elif cpu_state == 'thumb':
117 addr = addr & 0xFFFFFFFE
118 elif cpu_state == 'jazelle':
119 addr = (addr & 0xFFFFFFFFE) >> 1
122 raise NotImplementedError('Unhandled state: ' + cpu_state)
124 # If the address wasn't full, fill in with the previous address.
126 addr |= ref_addr & (0xFFFFFFFF << addr_bits)
128 return addr, addrlen, cpu_state, exc_info
130 class Decoder(srd.Decoder):
134 longname = 'ARM Embedded Trace Macroblock'
135 desc = 'Decode ETM instruction trace packets.'
138 outputs = ['arm_etmv3']
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'),
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,)),
163 {'id': 'objdump', 'desc': 'objdump path',
164 'default': 'arm-none-eabi-objdump'},
165 {'id': 'objdump_opts', 'desc': 'objdump options',
167 {'id': 'elffile', 'desc': '.elf path',
169 {'id': 'branch_enc', 'desc': 'Branch encoding',
170 'default': 'alternative', 'values': ('alternative', 'original')},
173 def __init__(self, **kwargs):
178 self.cpu_state = 'arm'
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 = {}
189 self.out_ann = self.register(srd.OUTPUT_ANN)
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.
198 if not (self.options['objdump'] and self.options['elffile']):
201 opts = [self.options['objdump']]
202 opts += self.options['objdump_opts'].split()
203 opts += [self.options['elffile']]
206 disasm = subprocess.check_output(opts)
207 except subprocess.CalledProcessError:
210 disasm = disasm.decode('utf-8', 'replace')
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.*)?')
215 funcpat = re.compile('[0-9a-fA-F]+\s*<([^>]+)>:.*')
221 for line in disasm.split('\n'):
222 m = instpat.match(line)
224 addr = int(m.group(1), 16)
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
232 # Next address in direct sequence.
233 ilen = len(raw.replace(' ', '')) // 2
236 # Next address if branch is taken.
237 bm = branchpat.match(disas)
239 next_e = int(bm.group(2), 16)
243 self.next_instr_lookup[addr] = (next_n, next_e)
245 m = funcpat.match(line)
247 prev_func = m.group(1)
250 m = filepat.match(line)
252 prev_file = m.group(1)
255 prev_src = line.strip()
257 def flush_current_loc(self):
258 if self.current_loc is not None:
259 ss, es, loc, src = self.current_loc
261 self.put(ss, es, self.out_ann, [9, [loc]])
263 self.put(ss, es, self.out_ann, [8, [src]])
264 self.current_loc = None
266 def flush_current_func(self):
267 if self.current_func is not None:
268 ss, es, func = self.current_func
270 self.put(ss, es, self.out_ann, [10, [func]])
271 self.current_func = None
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
279 if len(exec_status) == 0:
282 tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status))
284 for i, exec_status in enumerate(exec_status):
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))
291 self.put(ss, es, self.out_ann,
292 [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]])
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)
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()
304 if self.current_loc is None:
305 self.current_loc = [ss, es, new_loc, new_src]
307 self.current_loc[1] = es
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()
314 if self.current_func is None:
315 self.current_func = [ss, es, new_func]
317 self.current_func[1] = es
319 # Report instruction every time.
322 a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]]
324 a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]]
325 self.put(ss, es, self.out_ann, a)
328 self.current_pc = target_e
330 self.current_pc = target_n
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"
336 if byte & 0x01 == 0x01:
346 elif byte & 0xF3 in (0x20, 0x40, 0x60):
349 return 'store_failed'
352 elif byte & 0xDF in (0x54, 0x58, 0x5C):
356 elif byte & 0xD3 == 0x02:
358 elif byte & 0xFB == 0x42:
361 return 'data_suppressed'
364 elif byte & 0xEF == 0x6A:
365 return 'value_not_traced'
369 return 'exception_exit'
371 return 'exception_entry'
372 elif byte & 0x81 == 0x80:
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])]]
381 def handle_a_sync(self, buf):
383 return [0, ['Synchronization']]
385 def handle_exception_exit(self, buf):
386 return [2, ['Exception exit']]
388 def handle_exception_entry(self, buf):
389 return [2, ['Exception entry']]
391 def handle_i_sync(self, buf):
392 contextid_bytes = 0 # This is the default ETM config.
395 return None # Packet definitely not full yet.
397 if buf[0] == 0x08: # No cycle count.
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
404 if len(buf) <= idx + 4:
407 addr = parse_uint(buf[idx+1:idx+5])
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
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()
427 self.last_branch = addr
428 self.current_pc = addr
431 self.cpu_state = 'jazelle'
433 self.cpu_state = 'thumb'
435 self.cpu_state = 'arm'
438 if cyclecount is not None:
439 cycstr = ', cyclecount %d' % cyclecount
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']])
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)]]
449 def handle_trigger(self, buf):
450 return [0, ['Trigger event', 'Trigger']]
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
458 self.instructions_executed([1] * e + [0] * n)
461 return [3, ['%d instructions executed, %d skipped due to ' \
462 'condition codes' % (e, n),
463 '%d ins exec, %d skipped' % (e, n),
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')
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])]]
478 return self.fallback(buf)
480 def handle_branch(self, buf):
481 if buf[-1] & 0x80 != 0x00:
482 return None # Not complete yet.
484 brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state,
485 self.options['branch_enc'])
488 return None # Not complete yet.
490 addr, addrlen, cpu_state, exc_info = brinfo
491 self.last_branch = addr
492 self.current_pc = addr
496 if cpu_state != self.cpu_state:
497 txt += ', to %s state' % cpu_state
498 self.cpu_state = cpu_state
504 ns, exc, cancel, altisa, hyp, resume = exc_info
506 txt += ', to non-secure state'
508 if exc < len(exc_names):
509 txt += ', exception %s' % exc_names[exc]
511 txt += ', exception 0x%02x' % exc
513 txt += ', instr cancelled'
517 txt += ', to hypervisor'
519 txt += ', instr resume 0x%02x' % resume
521 return [annidx, ['Branch to 0x%08x%s' % (addr, txt),
522 'B 0x%08x%s' % (addr, txt)]]
524 def decode(self, ss, es, data):
525 ptype, rxtx, pdata = data
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()
539 self.buf.append(pdata[0])
541 # Store the start time of the packet.
542 if len(self.buf) == 1:
543 self.startsample = ss
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
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)
559 data = self.fallback(self.buf)
563 self.put(self.startsample, es, self.out_ann, data)