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, see <http://www.gnu.org/licenses/>.
20 import sigrokdecode as srd
24 # See ETMv3 Signal Protocol table 7-11: 'Encoding of Exception[8:0]'.
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'
31 for i in range(8, 496):
32 exc_names.append('IRQ%d' % i)
34 def parse_varint(bytes_):
35 '''Parse an integer where the top bit is the continuation bit.
36 Returns value and number of parsed bytes.'''
38 for i, b in enumerate(bytes_):
39 v |= (b & 0x7F) << (i * 7)
44 def parse_uint(bytes_):
45 '''Parse little-endian integer.'''
47 for i, b in enumerate(bytes_):
51 def parse_exc_info(bytes_):
52 '''Parse exception information bytes from a branch packet.'''
56 excv, exclen = parse_varint(bytes_)
57 if bytes_[exclen - 1] & 0x80 != 0x00:
58 return None # Exception info not complete.
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)
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)
72 def 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'''
77 addr, addrlen = parse_varint(bytes_)
79 if bytes_[addrlen - 1] & 0x80 != 0x00:
80 return None # Branch address not complete.
82 addr_bits = 7 * addrlen
85 if branch_enc == 'original':
86 if addrlen == 5 and bytes_[4] & 0x40:
88 elif branch_enc == 'alternative':
89 addr_bits -= 1 # Top bit of address indicates exc_info.
90 if addrlen >= 2 and addr & (1 << addr_bits):
92 addr &= ~(1 << addr_bits)
96 exc_info = parse_exc_info(bytes_[addrlen:])
98 return None # Exception info not complete.
101 # Possible change in CPU state.
102 if bytes_[4] & 0xB8 == 0x08:
104 elif bytes_[4] & 0xB0 == 0x10:
106 elif bytes_[4] & 0xA0 == 0x20:
107 cpu_state = 'jazelle'
109 raise NotImplementedError('Unhandled branch byte 4: 0x%02x' % bytes_[4])
111 # Shift the address according to current CPU state.
112 if cpu_state == 'arm':
113 addr = (addr & 0xFFFFFFFE) << 1
115 elif cpu_state == 'thumb':
116 addr = addr & 0xFFFFFFFE
117 elif cpu_state == 'jazelle':
118 addr = (addr & 0xFFFFFFFFE) >> 1
121 raise NotImplementedError('Unhandled state: ' + cpu_state)
123 # If the address wasn't full, fill in with the previous address.
125 addr |= ref_addr & (0xFFFFFFFF << addr_bits)
127 return addr, addrlen, cpu_state, exc_info
129 class Decoder(srd.Decoder):
133 longname = 'ARM Embedded Trace Macroblock v3'
134 desc = 'ARM ETM v3 instruction trace protocol.'
138 tags = ['Debug/trace']
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')},
181 self.cpu_state = 'arm'
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 = {}
192 self.out_ann = self.register(srd.OUTPUT_ANN)
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.
201 if not (self.options['objdump'] and self.options['elffile']):
204 opts = [self.options['objdump']]
205 opts += self.options['objdump_opts'].split()
206 opts += [self.options['elffile']]
209 disasm = subprocess.check_output(opts)
210 except subprocess.CalledProcessError:
213 disasm = disasm.decode('utf-8', 'replace')
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.*)?')
218 funcpat = re.compile('[0-9a-fA-F]+\s*<([^>]+)>:.*')
224 for line in disasm.split('\n'):
225 m = instpat.match(line)
227 addr = int(m.group(1), 16)
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
235 # Next address in direct sequence.
236 ilen = len(raw.replace(' ', '')) // 2
239 # Next address if branch is taken.
240 bm = branchpat.match(disas)
242 next_e = int(bm.group(2), 16)
246 self.next_instr_lookup[addr] = (next_n, next_e)
248 m = funcpat.match(line)
250 prev_func = m.group(1)
253 m = filepat.match(line)
255 prev_file = m.group(1)
258 prev_src = line.strip()
260 def flush_current_loc(self):
261 if self.current_loc is not None:
262 ss, es, loc, src = self.current_loc
264 self.put(ss, es, self.out_ann, [9, [loc]])
266 self.put(ss, es, self.out_ann, [8, [src]])
267 self.current_loc = None
269 def flush_current_func(self):
270 if self.current_func is not None:
271 ss, es, func = self.current_func
273 self.put(ss, es, self.out_ann, [10, [func]])
274 self.current_func = None
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
282 if len(exec_status) == 0:
285 tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status))
287 for i, exec_status in enumerate(exec_status):
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))
294 self.put(ss, es, self.out_ann,
295 [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]])
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)
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()
307 if self.current_loc is None:
308 self.current_loc = [ss, es, new_loc, new_src]
310 self.current_loc[1] = es
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()
317 if self.current_func is None:
318 self.current_func = [ss, es, new_func]
320 self.current_func[1] = es
322 # Report instruction every time.
325 a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]]
327 a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]]
328 self.put(ss, es, self.out_ann, a)
331 self.current_pc = target_e
333 self.current_pc = target_n
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"
339 if byte & 0x01 == 0x01:
349 elif byte & 0xF3 in (0x20, 0x40, 0x60):
352 return 'store_failed'
355 elif byte & 0xDF in (0x54, 0x58, 0x5C):
359 elif byte & 0xD3 == 0x02:
361 elif byte & 0xFB == 0x42:
364 return 'data_suppressed'
367 elif byte & 0xEF == 0x6A:
368 return 'value_not_traced'
372 return 'exception_exit'
374 return 'exception_entry'
375 elif byte & 0x81 == 0x80:
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])]]
384 def handle_a_sync(self, buf):
386 return [0, ['Synchronization']]
388 def handle_exception_exit(self, buf):
389 return [2, ['Exception exit']]
391 def handle_exception_entry(self, buf):
392 return [2, ['Exception entry']]
394 def handle_i_sync(self, buf):
395 contextid_bytes = 0 # This is the default ETM config.
398 return None # Packet definitely not full yet.
400 if buf[0] == 0x08: # No cycle count.
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
407 if len(buf) <= idx + 4:
410 addr = parse_uint(buf[idx+1:idx+5])
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
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()
430 self.last_branch = addr
431 self.current_pc = addr
434 self.cpu_state = 'jazelle'
436 self.cpu_state = 'thumb'
438 self.cpu_state = 'arm'
441 if cyclecount is not None:
442 cycstr = ', cyclecount %d' % cyclecount
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']])
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)]]
452 def handle_trigger(self, buf):
453 return [0, ['Trigger event', 'Trigger']]
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
461 self.instructions_executed([1] * e + [0] * n)
464 return [3, ['%d instructions executed, %d skipped due to ' \
465 'condition codes' % (e, n),
466 '%d ins exec, %d skipped' % (e, n),
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')
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])]]
481 return self.fallback(buf)
483 def handle_branch(self, buf):
484 if buf[-1] & 0x80 != 0x00:
485 return None # Not complete yet.
487 brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state,
488 self.options['branch_enc'])
491 return None # Not complete yet.
493 addr, addrlen, cpu_state, exc_info = brinfo
494 self.last_branch = addr
495 self.current_pc = addr
499 if cpu_state != self.cpu_state:
500 txt += ', to %s state' % cpu_state
501 self.cpu_state = cpu_state
507 ns, exc, cancel, altisa, hyp, resume = exc_info
509 txt += ', to non-secure state'
511 if exc < len(exc_names):
512 txt += ', exception %s' % exc_names[exc]
514 txt += ', exception 0x%02x' % exc
516 txt += ', instr cancelled'
520 txt += ', to hypervisor'
522 txt += ', instr resume 0x%02x' % resume
524 return [annidx, ['Branch to 0x%08x%s' % (addr, txt),
525 'B 0x%08x%s' % (addr, txt)]]
527 def decode(self, ss, es, data):
528 ptype, rxtx, pdata = data
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()
542 self.buf.append(pdata[0])
544 # Store the start time of the packet.
545 if len(self.buf) == 1:
546 self.startsample = ss
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
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)
562 data = self.fallback(self.buf)
566 self.put(self.startsample, es, self.out_ann, data)