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'
134 desc = 'Decode ETM instruction trace packets.'
137 outputs = ['arm_etmv3']
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'),
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,)),
162 {'id': 'objdump', 'desc': 'objdump path',
163 'default': 'arm-none-eabi-objdump'},
164 {'id': 'objdump_opts', 'desc': 'objdump options',
166 {'id': 'elffile', 'desc': '.elf path',
168 {'id': 'branch_enc', 'desc': 'Branch encoding',
169 'default': 'alternative', 'values': ('alternative', 'original')},
177 self.cpu_state = 'arm'
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 = {}
188 self.out_ann = self.register(srd.OUTPUT_ANN)
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.
197 if not (self.options['objdump'] and self.options['elffile']):
200 opts = [self.options['objdump']]
201 opts += self.options['objdump_opts'].split()
202 opts += [self.options['elffile']]
205 disasm = subprocess.check_output(opts)
206 except subprocess.CalledProcessError:
209 disasm = disasm.decode('utf-8', 'replace')
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.*)?')
214 funcpat = re.compile('[0-9a-fA-F]+\s*<([^>]+)>:.*')
220 for line in disasm.split('\n'):
221 m = instpat.match(line)
223 addr = int(m.group(1), 16)
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
231 # Next address in direct sequence.
232 ilen = len(raw.replace(' ', '')) // 2
235 # Next address if branch is taken.
236 bm = branchpat.match(disas)
238 next_e = int(bm.group(2), 16)
242 self.next_instr_lookup[addr] = (next_n, next_e)
244 m = funcpat.match(line)
246 prev_func = m.group(1)
249 m = filepat.match(line)
251 prev_file = m.group(1)
254 prev_src = line.strip()
256 def flush_current_loc(self):
257 if self.current_loc is not None:
258 ss, es, loc, src = self.current_loc
260 self.put(ss, es, self.out_ann, [9, [loc]])
262 self.put(ss, es, self.out_ann, [8, [src]])
263 self.current_loc = None
265 def flush_current_func(self):
266 if self.current_func is not None:
267 ss, es, func = self.current_func
269 self.put(ss, es, self.out_ann, [10, [func]])
270 self.current_func = None
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
278 if len(exec_status) == 0:
281 tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status))
283 for i, exec_status in enumerate(exec_status):
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))
290 self.put(ss, es, self.out_ann,
291 [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]])
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)
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()
303 if self.current_loc is None:
304 self.current_loc = [ss, es, new_loc, new_src]
306 self.current_loc[1] = es
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()
313 if self.current_func is None:
314 self.current_func = [ss, es, new_func]
316 self.current_func[1] = es
318 # Report instruction every time.
321 a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]]
323 a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]]
324 self.put(ss, es, self.out_ann, a)
327 self.current_pc = target_e
329 self.current_pc = target_n
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"
335 if byte & 0x01 == 0x01:
345 elif byte & 0xF3 in (0x20, 0x40, 0x60):
348 return 'store_failed'
351 elif byte & 0xDF in (0x54, 0x58, 0x5C):
355 elif byte & 0xD3 == 0x02:
357 elif byte & 0xFB == 0x42:
360 return 'data_suppressed'
363 elif byte & 0xEF == 0x6A:
364 return 'value_not_traced'
368 return 'exception_exit'
370 return 'exception_entry'
371 elif byte & 0x81 == 0x80:
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])]]
380 def handle_a_sync(self, buf):
382 return [0, ['Synchronization']]
384 def handle_exception_exit(self, buf):
385 return [2, ['Exception exit']]
387 def handle_exception_entry(self, buf):
388 return [2, ['Exception entry']]
390 def handle_i_sync(self, buf):
391 contextid_bytes = 0 # This is the default ETM config.
394 return None # Packet definitely not full yet.
396 if buf[0] == 0x08: # No cycle count.
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
403 if len(buf) <= idx + 4:
406 addr = parse_uint(buf[idx+1:idx+5])
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
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()
426 self.last_branch = addr
427 self.current_pc = addr
430 self.cpu_state = 'jazelle'
432 self.cpu_state = 'thumb'
434 self.cpu_state = 'arm'
437 if cyclecount is not None:
438 cycstr = ', cyclecount %d' % cyclecount
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']])
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)]]
448 def handle_trigger(self, buf):
449 return [0, ['Trigger event', 'Trigger']]
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
457 self.instructions_executed([1] * e + [0] * n)
460 return [3, ['%d instructions executed, %d skipped due to ' \
461 'condition codes' % (e, n),
462 '%d ins exec, %d skipped' % (e, n),
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')
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])]]
477 return self.fallback(buf)
479 def handle_branch(self, buf):
480 if buf[-1] & 0x80 != 0x00:
481 return None # Not complete yet.
483 brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state,
484 self.options['branch_enc'])
487 return None # Not complete yet.
489 addr, addrlen, cpu_state, exc_info = brinfo
490 self.last_branch = addr
491 self.current_pc = addr
495 if cpu_state != self.cpu_state:
496 txt += ', to %s state' % cpu_state
497 self.cpu_state = cpu_state
503 ns, exc, cancel, altisa, hyp, resume = exc_info
505 txt += ', to non-secure state'
507 if exc < len(exc_names):
508 txt += ', exception %s' % exc_names[exc]
510 txt += ', exception 0x%02x' % exc
512 txt += ', instr cancelled'
516 txt += ', to hypervisor'
518 txt += ', instr resume 0x%02x' % resume
520 return [annidx, ['Branch to 0x%08x%s' % (addr, txt),
521 'B 0x%08x%s' % (addr, txt)]]
523 def decode(self, ss, es, data):
524 ptype, rxtx, pdata = data
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()
538 self.buf.append(pdata[0])
540 # Store the start time of the packet.
541 if len(self.buf) == 1:
542 self.startsample = ss
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
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)
558 data = self.fallback(self.buf)
562 self.put(self.startsample, es, self.out_ann, data)