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 def parse_varint(bytes):
26 '''Parse an integer where the top bit is the continuation bit.
27 Returns value and number of parsed bytes.'''
29 for i, b in enumerate(bytes):
30 v |= (b & 0x7F) << (i * 7)
35 def parse_uint(bytes):
36 '''Parse little-endian integer.'''
38 for i, b in enumerate(bytes):
42 def parse_exc_info(bytes):
43 '''Parse exception information bytes from a branch packet.'''
47 excv, exclen = parse_varint(bytes)
48 if bytes[exclen - 1] & 0x80 != 0x00:
49 return None # Exception info not complete.
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)
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)
63 def 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'''
68 addr, addrlen = parse_varint(bytes)
70 if bytes[addrlen-1] & 0x80 != 0x00:
71 return None # Branch address not complete.
74 if branch_enc == 'original':
75 if addrlen == 5 and bytes[4] & 0x40:
77 elif branch_enc == 'alternative':
78 if addrlen >= 2 and bytes[addrlen - 1] & 0x40:
80 addr &= ~(1 << (addrlen * 7 - 1))
84 exc_info = parse_exc_info(bytes[addrlen:])
86 return None # Exception info not complete.
89 # Possible change in CPU state.
90 if bytes[4] & 0xB8 == 0x08:
92 elif bytes[4] & 0xB0 == 0x10:
94 elif bytes[4] & 0xA0 == 0x20:
97 raise NotImplementedError('Unhandled branch byte 4: 0x%02x' % bytes[4])
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
107 raise NotImplementedError('Unhandled state: ' + cpu_state)
109 # If the address wasn't full, fill in with the previous address.
112 addr |= ref_addr & (0xFFFFFFFF << bits)
114 return addr, addrlen, cpu_state, exc_info
116 class Decoder(srd.Decoder):
120 longname = 'ARM Embedded Trace Macroblock'
121 desc = 'Decode ETM instruction trace packets.'
124 outputs = ['arm_etmv3']
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'),
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,)),
149 {'id': 'objdump', 'desc': 'objdump path',
150 'default': 'arm-none-eabi-objdump'},
151 {'id': 'objdump_opts', 'desc': 'objdump options',
153 {'id': 'elffile', 'desc': '.elf path',
155 {'id': 'branch_enc', 'desc': 'Branch encoding',
156 'default': 'alternative', 'values': ('alternative', 'original')},
159 def __init__(self, **kwargs):
164 self.cpu_state = 'arm'
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 = {}
175 self.out_ann = self.register(srd.OUTPUT_ANN)
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.
184 if not (self.options['objdump'] and self.options['elffile']):
187 opts = [self.options['objdump']]
188 opts += self.options['objdump_opts'].split()
189 opts += [self.options['elffile']]
192 disasm = subprocess.check_output(opts)
193 except subprocess.CalledProcessError:
196 disasm = disasm.decode('utf-8', 'replace')
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_-]+)>:.*')
207 for line in disasm.split('\n'):
208 m = instpat.match(line)
210 addr = int(m.group(1), 16)
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
218 # Next address in direct sequence.
219 ilen = len(raw.replace(' ', '')) // 2
222 # Next address if branch is taken.
223 bm = branchpat.match(disas)
225 next_e = int(bm.group(2), 16)
229 self.next_instr_lookup[addr] = (next_n, next_e)
231 m = funcpat.match(line)
233 prev_func = m.group(1)
235 m = filepat.match(line)
237 prev_file = m.group(1)
239 prev_src = line.strip()
241 def flush_current_loc(self):
242 if self.current_loc is not None:
243 ss, es, loc, src = self.current_loc
245 self.put(ss, es, self.out_ann, [9, [loc]])
247 self.put(ss, es, self.out_ann, [8, [src]])
248 self.current_loc = None
250 def flush_current_func(self):
251 if self.current_func is not None:
252 ss, es, func = self.current_func
254 self.put(ss, es, self.out_ann, [10, [func]])
255 self.current_func = None
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
263 tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status))
265 for i, exec_status in enumerate(exec_status):
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))
272 self.put(ss, es, self.out_ann,
273 [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]])
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)
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()
285 if self.current_loc is None:
286 self.current_loc = [ss, es, new_loc, new_src]
288 self.current_loc[1] = es
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()
295 if self.current_func is None:
296 self.current_func = [ss, es, new_func]
298 self.current_func[1] = es
300 # Report instruction every time.
303 a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]]
305 a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]]
306 self.put(ss, es, self.out_ann, a)
309 self.current_pc = target_e
311 self.current_pc = target_n
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"
317 if byte & 0x01 == 0x01:
327 elif byte & 0xF3 in (0x20, 0x40, 0x60):
330 return 'store_failed'
333 elif byte & 0xDF in (0x54, 0x58, 0x5C):
337 elif byte & 0xD3 == 0x02:
339 elif byte & 0xFB == 0x42:
342 return 'data_suppressed'
345 elif byte & 0xEF == 0x6A:
346 return 'value_not_traced'
350 return 'exception_exit'
352 return 'exception_entry'
353 elif byte & 0x81 == 0x80:
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])]]
362 def handle_a_sync(self, buf):
364 return [0, ['Synchronization']]
366 def handle_exception_exit(self, buf):
367 return [2, 'Exception exit']
369 def handle_exception_entry(self, buf):
370 return [1, 'Exception entry']
372 def handle_i_sync(self, buf):
373 contextid_bytes = 0 # This is the default ETM config.
376 return None # Packet definitely not full yet.
378 if buf[0] == 0x08: # No cycle count.
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
385 if len(buf) <= idx + 4:
388 addr = parse_uint(buf[idx+1:idx+5])
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
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()
408 self.last_branch = addr
409 self.current_pc = addr
412 self.cpu_state = 'jazelle'
414 self.cpu_state = 'thumb'
416 self.cpu_state = 'arm'
419 if cyclecount is not None:
420 cycstr = ', cyclecount %d' % cyclecount
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']])
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)]]
430 def handle_trigger(self, buf):
431 return [0, ['Trigger event', 'Trigger']]
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
439 self.instructions_executed([1] * e + [0] * n)
442 return [3, ['%d instructions executed, %d skipped due to ' \
443 'condition codes' % (e, n),
444 '%d ins exec, %d skipped' % (e, n),
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')
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])]]
459 return self.fallback(buf)
461 def handle_branch(self, buf):
462 if buf[-1] & 0x80 != 0x00:
463 return None # Not complete yet.
465 brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state,
466 self.options['branch_enc'])
469 return None # Not complete yet.
471 addr, addrlen, cpu_state, exc_info = brinfo
472 self.last_branch = addr
473 self.current_pc = addr
477 if cpu_state != self.cpu_state:
478 txt += ', to %s state' % cpu_state
479 self.cpu_state = cpu_state
482 ns, exc, cancel, altisa, hyp, resume = exc_info
484 txt += ', to non-secure state'
486 # TODO: Parse the exception value to text.
487 txt += ', exception 0x%02x' % exc
489 txt += ', instr cancelled'
493 txt += ', to hypervisor'
495 txt += ', instr resume 0x%02x' % resume
497 return [1, ['Branch to 0x%08x%s' % (addr, txt),
498 'B 0x%08x%s' % (addr, txt)]]
500 def decode(self, ss, es, data):
501 ptype, rxtx, pdata = data
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()
515 self.buf.append(pdata[0])
517 # Store the start time of the packet.
518 if len(self.buf) == 1:
519 self.startsample = ss
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
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)
535 data = self.fallback(self.buf)
539 self.put(self.startsample, es, self.out_ann, data)