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')},
180 self.cpu_state = 'arm'
182 self.current_loc = None
183 self.current_func = None
184 self.next_instr_lookup = {}
185 self.file_lookup = {}
186 self.func_lookup = {}
187 self.disasm_lookup = {}
188 self.source_lookup = {}
191 self.out_ann = self.register(srd.OUTPUT_ANN)
194 def load_objdump(self):
195 '''Parse disassembly obtained from objdump into two tables:
196 next_instr_lookup: Find the next PC addr from current PC.
197 disasm_lookup: Find the instruction text from current PC.
198 source_lookup: Find the source code line from current PC.
200 if not (self.options['objdump'] and self.options['elffile']):
203 opts = [self.options['objdump']]
204 opts += self.options['objdump_opts'].split()
205 opts += [self.options['elffile']]
208 disasm = subprocess.check_output(opts)
209 except subprocess.CalledProcessError:
212 disasm = disasm.decode('utf-8', 'replace')
214 instpat = re.compile('\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*')
215 branchpat = re.compile('(b|bl|b..|bl..|cbnz|cbz)(?:\.[wn])?\s+(?:r[0-9]+,\s*)?([0-9a-fA-F]+)')
216 filepat = re.compile('[^\s]+[/\\\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?')
217 funcpat = re.compile('[0-9a-fA-F]+\s*<([^>]+)>:.*')
223 for line in disasm.split('\n'):
224 m = instpat.match(line)
226 addr = int(m.group(1), 16)
228 disas = m.group(3).strip().replace('\t', ' ')
229 self.disasm_lookup[addr] = disas
230 self.source_lookup[addr] = prev_src
231 self.file_lookup[addr] = prev_file
232 self.func_lookup[addr] = prev_func
234 # Next address in direct sequence.
235 ilen = len(raw.replace(' ', '')) // 2
238 # Next address if branch is taken.
239 bm = branchpat.match(disas)
241 next_e = int(bm.group(2), 16)
245 self.next_instr_lookup[addr] = (next_n, next_e)
247 m = funcpat.match(line)
249 prev_func = m.group(1)
252 m = filepat.match(line)
254 prev_file = m.group(1)
257 prev_src = line.strip()
259 def flush_current_loc(self):
260 if self.current_loc is not None:
261 ss, es, loc, src = self.current_loc
263 self.put(ss, es, self.out_ann, [9, [loc]])
265 self.put(ss, es, self.out_ann, [8, [src]])
266 self.current_loc = None
268 def flush_current_func(self):
269 if self.current_func is not None:
270 ss, es, func = self.current_func
272 self.put(ss, es, self.out_ann, [10, [func]])
273 self.current_func = None
275 def instructions_executed(self, exec_status):
276 '''Advance program counter based on executed instructions.
277 Argument is a list of False for not executed and True for executed
281 if len(exec_status) == 0:
284 tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status))
286 for i, exec_status in enumerate(exec_status):
288 default_next = pc + 2 if self.cpu_state == 'thumb' else pc + 4
289 target_n, target_e = self.next_instr_lookup.get(pc, (default_next, default_next))
290 ss = self.startsample + round(tdelta * i)
291 es = self.startsample + round(tdelta * (i+1))
293 self.put(ss, es, self.out_ann,
294 [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]])
296 new_loc = self.file_lookup.get(pc)
297 new_src = self.source_lookup.get(pc)
298 new_dis = self.disasm_lookup.get(pc)
299 new_func = self.func_lookup.get(pc)
301 # Report source line only when it changes.
302 if self.current_loc is not None:
303 if new_loc != self.current_loc[2] or new_src != self.current_loc[3]:
304 self.flush_current_loc()
306 if self.current_loc is None:
307 self.current_loc = [ss, es, new_loc, new_src]
309 self.current_loc[1] = es
311 # Report function name only when it changes.
312 if self.current_func is not None:
313 if new_func != self.current_func[2]:
314 self.flush_current_func()
316 if self.current_func is None:
317 self.current_func = [ss, es, new_func]
319 self.current_func[1] = es
321 # Report instruction every time.
324 a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]]
326 a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]]
327 self.put(ss, es, self.out_ann, a)
330 self.current_pc = target_e
332 self.current_pc = target_n
334 def get_packet_type(self, byte):
335 '''Identify packet type based on its first byte.
336 See ARM IHI0014Q section "ETMv3 Signal Protocol" "Packet Types"
338 if byte & 0x01 == 0x01:
348 elif byte & 0xF3 in (0x20, 0x40, 0x60):
351 return 'store_failed'
354 elif byte & 0xDF in (0x54, 0x58, 0x5C):
358 elif byte & 0xD3 == 0x02:
360 elif byte & 0xFB == 0x42:
363 return 'data_suppressed'
366 elif byte & 0xEF == 0x6A:
367 return 'value_not_traced'
371 return 'exception_exit'
373 return 'exception_entry'
374 elif byte & 0x81 == 0x80:
379 def fallback(self, buf):
380 ptype = self.get_packet_type(buf[0])
381 return [0, ['Unhandled ' + ptype + ': ' + ' '.join(['%02x' % b for b in buf])]]
383 def handle_a_sync(self, buf):
385 return [0, ['Synchronization']]
387 def handle_exception_exit(self, buf):
388 return [2, ['Exception exit']]
390 def handle_exception_entry(self, buf):
391 return [2, ['Exception entry']]
393 def handle_i_sync(self, buf):
394 contextid_bytes = 0 # This is the default ETM config.
397 return None # Packet definitely not full yet.
399 if buf[0] == 0x08: # No cycle count.
401 idx = 1 + contextid_bytes # Index to info byte.
402 elif buf[0] == 0x70: # With cycle count.
403 cyclecount, cyclen = parse_varint(buf[1:6])
404 idx = 1 + cyclen + contextid_bytes
406 if len(buf) <= idx + 4:
409 addr = parse_uint(buf[idx+1:idx+5])
411 reasoncode = (infobyte >> 5) & 3
412 reason = ('Periodic', 'Tracing enabled', 'After overflow', 'Exit from debug')[reasoncode]
413 jazelle = (infobyte >> 4) & 1
414 nonsec = (infobyte >> 3) & 1
415 altisa = (infobyte >> 2) & 1
416 hypervisor = (infobyte >> 1) & 1
420 if reasoncode == 0 and self.current_pc != addr:
421 self.put(self.startsample, self.prevsample, self.out_ann,
422 [0, ['WARN: Unexpected PC change 0x%08x -> 0x%08x' % \
423 (self.current_pc, addr)]])
424 elif reasoncode != 0:
425 # Reset location when the trace has been interrupted.
426 self.flush_current_loc()
427 self.flush_current_func()
429 self.last_branch = addr
430 self.current_pc = addr
433 self.cpu_state = 'jazelle'
435 self.cpu_state = 'thumb'
437 self.cpu_state = 'arm'
440 if cyclecount is not None:
441 cycstr = ', cyclecount %d' % cyclecount
443 if infobyte & 0x80: # LSIP packet
444 self.put(self.startsample, self.prevsample, self.out_ann,
445 [0, ['WARN: LSIP I-Sync packet not implemented']])
447 return [0, ['I-Sync: %s, PC 0x%08x, %s state%s' % \
448 (reason, addr, self.cpu_state, cycstr), \
449 'I-Sync: %s 0x%08x' % (reason, addr)]]
451 def handle_trigger(self, buf):
452 return [0, ['Trigger event', 'Trigger']]
454 def handle_p_header(self, buf):
455 # Only non cycle-accurate mode supported.
456 if buf[0] & 0x83 == 0x80:
457 n = (buf[0] >> 6) & 1
458 e = (buf[0] >> 2) & 15
460 self.instructions_executed([1] * e + [0] * n)
463 return [3, ['%d instructions executed, %d skipped due to ' \
464 'condition codes' % (e, n),
465 '%d ins exec, %d skipped' % (e, n),
468 return [3, ['%d instructions executed' % e,
469 '%d ins exec' % e, '%dE' % e]]
470 elif buf[0] & 0xF3 == 0x82:
471 i1 = (buf[0] >> 3) & 1
472 i2 = (buf[0] >> 2) & 1
473 self.instructions_executed([not i1, not i2])
474 txt1 = ('executed', 'skipped')
476 return [3, ['Instruction 1 %s, instruction 2 %s' % (txt1[i1], txt1[i2]),
477 'I1 %s, I2 %s' % (txt2[i1], txt2[i2]),
478 '%s,%s' % (txt2[i1], txt2[i2])]]
480 return self.fallback(buf)
482 def handle_branch(self, buf):
483 if buf[-1] & 0x80 != 0x00:
484 return None # Not complete yet.
486 brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state,
487 self.options['branch_enc'])
490 return None # Not complete yet.
492 addr, addrlen, cpu_state, exc_info = brinfo
493 self.last_branch = addr
494 self.current_pc = addr
498 if cpu_state != self.cpu_state:
499 txt += ', to %s state' % cpu_state
500 self.cpu_state = cpu_state
506 ns, exc, cancel, altisa, hyp, resume = exc_info
508 txt += ', to non-secure state'
510 if exc < len(exc_names):
511 txt += ', exception %s' % exc_names[exc]
513 txt += ', exception 0x%02x' % exc
515 txt += ', instr cancelled'
519 txt += ', to hypervisor'
521 txt += ', instr resume 0x%02x' % resume
523 return [annidx, ['Branch to 0x%08x%s' % (addr, txt),
524 'B 0x%08x%s' % (addr, txt)]]
526 def decode(self, ss, es, data):
527 ptype, rxtx, pdata = data
532 # Reset packet if there is a long pause between bytes.
533 # This helps getting the initial synchronization.
534 self.byte_len = es - ss
535 if ss - self.prevsample > 16 * self.byte_len:
536 self.flush_current_loc()
537 self.flush_current_func()
541 self.buf.append(pdata[0])
543 # Store the start time of the packet.
544 if len(self.buf) == 1:
545 self.startsample = ss
547 # Keep separate buffer for detection of sync packets.
548 # Sync packets override everything else, so that we can regain sync
549 # even if some packets are corrupted.
550 self.syncbuf = self.syncbuf[-4:] + [pdata[0]]
551 if self.syncbuf == [0x00, 0x00, 0x00, 0x00, 0x80]:
552 self.buf = self.syncbuf
555 # See if it is ready to be decoded.
556 ptype = self.get_packet_type(self.buf[0])
557 if hasattr(self, 'handle_' + ptype):
558 func = getattr(self, 'handle_' + ptype)
559 data = func(self.buf)
561 data = self.fallback(self.buf)
565 self.put(self.startsample, es, self.out_ann, data)