summaryrefslogtreecommitdiff
path: root/decoders/arm_etmv3
diff options
context:
space:
mode:
authorPetteri Aimonen <jpa@git.mail.kapsi.fi>2015-02-23 19:49:06 +0200
committerUwe Hermann <uwe@hermann-uwe.de>2015-03-02 10:59:43 +0100
commit686f0c3621cd8ef7264f3e45e0218f2ebdfb612a (patch)
tree743d5d8f624ad9ff9c8b4b4a9c1891e90e4f5fe2 /decoders/arm_etmv3
parent5dc48752efc84171dece2dcef93577e5483b9775 (diff)
downloadlibsigrokdecode-686f0c3621cd8ef7264f3e45e0218f2ebdfb612a.tar.gz
libsigrokdecode-686f0c3621cd8ef7264f3e45e0218f2ebdfb612a.zip
Add ARM TPIU/ITM/ETMv3 decoders
Diffstat (limited to 'decoders/arm_etmv3')
-rw-r--r--decoders/arm_etmv3/__init__.py26
-rw-r--r--decoders/arm_etmv3/pd.py540
2 files changed, 566 insertions, 0 deletions
diff --git a/decoders/arm_etmv3/__init__.py b/decoders/arm_etmv3/__init__.py
new file mode 100644
index 0000000..7955139
--- /dev/null
+++ b/decoders/arm_etmv3/__init__.py
@@ -0,0 +1,26 @@
+##
+## This file is part of the libsigrokdecode project.
+##
+## Copyright (C) 2015 Petteri Aimonen <jpa@sigrok.mail.kapsi.fi>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+##
+
+'''
+This decoder stacks on top of the 'uart' decoder and decodes packets of
+the ARMv7m Embedded Trace Macroblock v3.x.
+'''
+
+from .pd import Decoder
diff --git a/decoders/arm_etmv3/pd.py b/decoders/arm_etmv3/pd.py
new file mode 100644
index 0000000..c637142
--- /dev/null
+++ b/decoders/arm_etmv3/pd.py
@@ -0,0 +1,540 @@
+##
+## This file is part of the libsigrokdecode project.
+##
+## Copyright (C) 2015 Petteri Aimonen <jpa@sigrok.mail.kapsi.fi>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+##
+
+import sigrokdecode as srd
+import subprocess
+import re
+
+def parse_varint(bytes):
+ '''Parse an integer where the top bit is the continuation bit.
+ Returns value and number of parsed bytes.'''
+ v = 0
+ for i, b in enumerate(bytes):
+ v |= (b & 0x7F) << (i * 7)
+ if b & 0x80 == 0:
+ return v, i+1
+ return v, len(bytes)
+
+def parse_uint(bytes):
+ '''Parse little-endian integer.'''
+ v = 0
+ for i, b in enumerate(bytes):
+ v |= b << (i * 8)
+ return v
+
+def parse_exc_info(bytes):
+ '''Parse exception information bytes from a branch packet.'''
+ if len(bytes) < 1:
+ return None
+
+ excv, exclen = parse_varint(bytes)
+ if bytes[exclen - 1] & 0x80 != 0x00:
+ return None # Exception info not complete.
+
+ if exclen == 2 and excv & (1 << 13):
+ # Exception byte 1 was skipped, fix up the decoding.
+ excv = (excv & 0x7F) | ((excv & 0x3F80) << 7)
+
+ ns = excv & 1
+ exc = ((excv >> 1) & 0x0F) | ((excv >> 7) & 0x1F0)
+ cancel = (excv >> 5) & 1
+ altisa = (excv >> 6) & 1
+ hyp = (excv >> 12) & 1
+ resume = (excv >> 14) & 0x0F
+ return (ns, exc, cancel, altisa, hyp, resume)
+
+def parse_branch_addr(bytes, ref_addr, cpu_state, branch_enc):
+ '''Parse encoded branch address.
+ Returns addr, addrlen, cpu_state, exc_info.
+ Returns None if packet is not yet complete'''
+
+ addr, addrlen = parse_varint(bytes)
+
+ if bytes[addrlen-1] & 0x80 != 0x00:
+ return None # Branch address not complete.
+
+ have_exc_info = False
+ if branch_enc == 'original':
+ if addrlen == 5 and bytes[4] & 0x40:
+ have_exc_info = True
+ elif branch_enc == 'alternative':
+ if addrlen >= 2 and bytes[addrlen - 1] & 0x40:
+ have_exc_info = True
+ addr &= ~(1 << (addrlen * 7 - 1))
+
+ exc_info = None
+ if have_exc_info:
+ exc_info = parse_exc_info(bytes[addrlen:])
+ if exc_info is None:
+ return None # Exception info not complete.
+
+ if addrlen == 5:
+ # Possible change in CPU state.
+ if bytes[4] & 0xB8 == 0x08:
+ cpu_state = 'arm'
+ elif bytes[4] & 0xB0 == 0x10:
+ cpu_state = 'thumb'
+ elif bytes[4] & 0xA0 == 0x20:
+ cpu_state = 'jazelle'
+ else:
+ raise NotImplementedError('Unhandled branch byte 4: 0x%02x' % bytes[4])
+
+ # Shift the address according to current CPU state.
+ if cpu_state == 'arm':
+ addr = (addr & 0xFFFFFFFE) << 1
+ elif cpu_state == 'thumb':
+ addr = addr & 0xFFFFFFFE
+ elif cpu_state == 'jazelle':
+ addr = (addr & 0xFFFFFFFFE) >> 1
+ else:
+ raise NotImplementedError('Unhandled state: ' + cpu_state)
+
+ # If the address wasn't full, fill in with the previous address.
+ if addrlen < 5:
+ bits = 7 * addrlen
+ addr |= ref_addr & (0xFFFFFFFF << bits)
+
+ return addr, addrlen, cpu_state, exc_info
+
+class Decoder(srd.Decoder):
+ api_version = 2
+ id = 'arm_etmv3'
+ name = 'ARM ETMv3'
+ longname = 'ARM Embedded Trace Macroblock'
+ desc = 'Decode ETM instruction trace packets.'
+ license = 'gplv2+'
+ inputs = ['uart']
+ outputs = ['arm_etmv3']
+ annotations = (
+ ('trace', 'Trace info'),
+ ('branch', 'Branches'),
+ ('exception', 'Exceptions'),
+ ('execution', 'Instruction execution'),
+ ('data', 'Data access'),
+ ('pc', 'Program counter'),
+ ('instr_e', 'Executed instructions'),
+ ('instr_n', 'Not executed instructions'),
+ ('source', 'Source code'),
+ ('location', 'Current location'),
+ ('function', 'Current function'),
+ )
+ annotation_rows = (
+ ('trace', 'Trace info', (0,)),
+ ('flow', 'Code flow', (1, 2, 3,)),
+ ('data', 'Data access', (4,)),
+ ('pc', 'Program counter', (5,)),
+ ('instruction', 'Instructions', (6, 7,)),
+ ('source', 'Source code', (8,)),
+ ('location', 'Current location', (9,)),
+ ('function', 'Current function', (10,)),
+ )
+ options = (
+ {'id': 'objdump', 'desc': 'objdump path',
+ 'default': 'arm-none-eabi-objdump'},
+ {'id': 'objdump_opts', 'desc': 'objdump options',
+ 'default': '-lS'},
+ {'id': 'elffile', 'desc': '.elf path',
+ 'default': ''},
+ {'id': 'branch_enc', 'desc': 'Branch encoding',
+ 'default': 'alternative', 'values': ('alternative', 'original')},
+ )
+
+ def __init__(self, **kwargs):
+ self.buf = []
+ self.syncbuf = []
+ self.prevsample = 0
+ self.last_branch = 0
+ self.cpu_state = 'arm'
+ self.current_pc = 0
+ self.current_loc = None
+ self.current_func = None
+ self.next_instr_lookup = {}
+ self.file_lookup = {}
+ self.func_lookup = {}
+ self.disasm_lookup = {}
+ self.source_lookup = {}
+
+ def start(self):
+ self.out_ann = self.register(srd.OUTPUT_ANN)
+ self.load_objdump()
+
+ def load_objdump(self):
+ '''Parse disassembly obtained from objdump into two tables:
+ next_instr_lookup: Find the next PC addr from current PC.
+ disasm_lookup: Find the instruction text from current PC.
+ source_lookup: Find the source code line from current PC.
+ '''
+ if not (self.options['objdump'] and self.options['elffile']):
+ return
+
+ opts = [self.options['objdump']]
+ opts += self.options['objdump_opts'].split()
+ opts += [self.options['elffile']]
+
+ try:
+ disasm = subprocess.check_output(opts)
+ except subprocess.CalledProcessError:
+ return
+
+ disasm = disasm.decode('utf-8', 'replace')
+
+ instpat = re.compile('\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*')
+ branchpat = re.compile('(b|bl|b..|bl..|cbnz|cbz)(?:\.[wn])?\s+(?:r[0-9]+,\s*)?([0-9a-fA-F]+)')
+ filepat = re.compile('[^\s]+[/\\\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?')
+ funcpat = re.compile('[0-9a-fA-F]+\s*<([a-zA-Z0-9_-]+)>:.*')
+
+ prev_src = ''
+ prev_file = ''
+ prev_func = ''
+
+ for line in disasm.split('\n'):
+ m = instpat.match(line)
+ if m:
+ addr = int(m.group(1), 16)
+ raw = m.group(2)
+ disas = m.group(3).strip().replace('\t', ' ')
+ self.disasm_lookup[addr] = disas
+ self.source_lookup[addr] = prev_src
+ self.file_lookup[addr] = prev_file
+ self.func_lookup[addr] = prev_func
+
+ # Next address in direct sequence.
+ ilen = len(raw.replace(' ', '')) // 2
+ next_n = addr + ilen
+
+ # Next address if branch is taken.
+ bm = branchpat.match(disas)
+ if bm:
+ next_e = int(bm.group(2), 16)
+ else:
+ next_e = next_n
+
+ self.next_instr_lookup[addr] = (next_n, next_e)
+ else:
+ m = funcpat.match(line)
+ if m:
+ prev_func = m.group(1)
+ else:
+ m = filepat.match(line)
+ if m:
+ prev_file = m.group(1)
+ else:
+ prev_src = line.strip()
+
+ def flush_current_loc(self):
+ if self.current_loc is not None:
+ ss, es, loc, src = self.current_loc
+ if loc:
+ self.put(ss, es, self.out_ann, [9, [loc]])
+ if src:
+ self.put(ss, es, self.out_ann, [8, [src]])
+ self.current_loc = None
+
+ def flush_current_func(self):
+ if self.current_func is not None:
+ ss, es, func = self.current_func
+ if func:
+ self.put(ss, es, self.out_ann, [10, [func]])
+ self.current_func = None
+
+ def instructions_executed(self, exec_status):
+ '''Advance program counter based on executed instructions.
+ Argument is a list of False for not executed and True for executed
+ instructions.
+ '''
+
+ tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status))
+
+ for i, exec_status in enumerate(exec_status):
+ pc = self.current_pc
+ default_next = pc + 2 if self.cpu_state == 'thumb' else pc + 4
+ target_n, target_e = self.next_instr_lookup.get(pc, (default_next, default_next))
+ ss = self.startsample + round(tdelta * i)
+ es = self.startsample + round(tdelta * (i+1))
+
+ self.put(ss, es, self.out_ann,
+ [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]])
+
+ new_loc = self.file_lookup.get(pc)
+ new_src = self.source_lookup.get(pc)
+ new_dis = self.disasm_lookup.get(pc)
+ new_func = self.func_lookup.get(pc)
+
+ # Report source line only when it changes.
+ if self.current_loc is not None:
+ if new_loc != self.current_loc[2] or new_src != self.current_loc[3]:
+ self.flush_current_loc()
+
+ if self.current_loc is None:
+ self.current_loc = [ss, es, new_loc, new_src]
+ else:
+ self.current_loc[1] = es
+
+ # Report function name only when it changes.
+ if self.current_func is not None:
+ if new_func != self.current_func[2]:
+ self.flush_current_func()
+
+ if self.current_func is None:
+ self.current_func = [ss, es, new_func]
+ else:
+ self.current_func[1] = es
+
+ # Report instruction every time.
+ if new_dis:
+ if exec_status:
+ a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]]
+ else:
+ a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]]
+ self.put(ss, es, self.out_ann, a)
+
+ if exec_status:
+ self.current_pc = target_e
+ else:
+ self.current_pc = target_n
+
+ def get_packet_type(self, byte):
+ '''Identify packet type based on its first byte.
+ See ARM IHI0014Q section "ETMv3 Signal Protocol" "Packet Types"
+ '''
+ if byte & 0x01 == 0x01:
+ return 'branch'
+ elif byte == 0x00:
+ return 'a_sync'
+ elif byte == 0x04:
+ return 'cyclecount'
+ elif byte == 0x08:
+ return 'i_sync'
+ elif byte == 0x0C:
+ return 'trigger'
+ elif byte & 0xF3 in (0x20, 0x40, 0x60):
+ return 'ooo_data'
+ elif byte == 0x50:
+ return 'store_failed'
+ elif byte == 0x70:
+ return 'i_sync'
+ elif byte & 0xDF in (0x54, 0x58, 0x5C):
+ return 'ooo_place'
+ elif byte == 0x3C:
+ return 'vmid'
+ elif byte & 0xD3 == 0x02:
+ return 'data'
+ elif byte & 0xFB == 0x42:
+ return 'timestamp'
+ elif byte == 0x62:
+ return 'data_suppressed'
+ elif byte == 0x66:
+ return 'ignore'
+ elif byte & 0xEF == 0x6A:
+ return 'value_not_traced'
+ elif byte == 0x6E:
+ return 'context_id'
+ elif byte == 0x76:
+ return 'exception_exit'
+ elif byte == 0x7E:
+ return 'exception_entry'
+ elif byte & 0x81 == 0x80:
+ return 'p_header'
+ else:
+ return 'unknown'
+
+ def fallback(self, buf):
+ ptype = self.get_packet_type(buf[0])
+ return [0, ['Unhandled ' + ptype + ': ' + ' '.join(['%02x' % b for b in buf])]]
+
+ def handle_a_sync(self, buf):
+ if buf[-1] == 0x80:
+ return [0, ['Synchronization']]
+
+ def handle_exception_exit(self, buf):
+ return [2, 'Exception exit']
+
+ def handle_exception_entry(self, buf):
+ return [1, 'Exception entry']
+
+ def handle_i_sync(self, buf):
+ contextid_bytes = 0 # This is the default ETM config.
+
+ if len(buf) < 6:
+ return None # Packet definitely not full yet.
+
+ if buf[0] == 0x08: # No cycle count.
+ cyclecount = None
+ idx = 1 + contextid_bytes # Index to info byte.
+ elif buf[0] == 0x70: # With cycle count.
+ cyclecount, cyclen = parse_varint(buf[1:6])
+ idx = 1 + cyclen + contextid_bytes
+
+ if len(buf) <= idx + 4:
+ return None
+ infobyte = buf[idx]
+ addr = parse_uint(buf[idx+1:idx+5])
+
+ reasoncode = (infobyte >> 5) & 3
+ reason = ('Periodic', 'Tracing enabled', 'After overflow', 'Exit from debug')[reasoncode]
+ jazelle = (infobyte >> 4) & 1
+ nonsec = (infobyte >> 3) & 1
+ altisa = (infobyte >> 2) & 1
+ hypervisor = (infobyte >> 1) & 1
+ thumb = addr & 1
+ addr &= 0xFFFFFFFE
+
+ if reasoncode == 0 and self.current_pc != addr:
+ self.put(self.startsample, self.prevsample, self.out_ann,
+ [0, ['WARN: Unexpected PC change 0x%08x -> 0x%08x' % \
+ (self.current_pc, addr)]])
+ elif reasoncode != 0:
+ # Reset location when the trace has been interrupted.
+ self.flush_current_loc()
+ self.flush_current_func()
+
+ self.last_branch = addr
+ self.current_pc = addr
+
+ if jazelle:
+ self.cpu_state = 'jazelle'
+ elif thumb:
+ self.cpu_state = 'thumb'
+ else:
+ self.cpu_state = 'arm'
+
+ cycstr = ''
+ if cyclecount is not None:
+ cycstr = ', cyclecount %d' % cyclecount
+
+ if infobyte & 0x80: # LSIP packet
+ self.put(self.startsample, self.prevsample, self.out_ann,
+ [0, ['WARN: LSIP I-Sync packet not implemented']])
+
+ return [0, ['I-Sync: %s, PC 0x%08x, %s state%s' % \
+ (reason, addr, self.cpu_state, cycstr), \
+ 'I-Sync: %s 0x%08x' % (reason, addr)]]
+
+ def handle_trigger(self, buf):
+ return [0, ['Trigger event', 'Trigger']]
+
+ def handle_p_header(self, buf):
+ # Only non cycle-accurate mode supported.
+ if buf[0] & 0x83 == 0x80:
+ n = (buf[0] >> 6) & 1
+ e = (buf[0] >> 2) & 15
+
+ self.instructions_executed([1] * e + [0] * n)
+
+ if n:
+ return [3, ['%d instructions executed, %d skipped due to ' \
+ 'condition codes' % (e, n),
+ '%d ins exec, %d skipped' % (e, n),
+ '%dE,%dN' % (e, n)]]
+ else:
+ return [3, ['%d instructions executed' % e,
+ '%d ins exec' % e, '%dE' % e]]
+ elif buf[0] & 0xF3 == 0x82:
+ i1 = (buf[0] >> 3) & 1
+ i2 = (buf[0] >> 2) & 1
+ self.instructions_executed([not i1, not i2])
+ txt1 = ('executed', 'skipped')
+ txt2 = ('E', 'S')
+ return [3, ['Instruction 1 %s, instruction 2 %s' % (txt1[i1], txt1[i2]),
+ 'I1 %s, I2 %s' % (txt2[i1], txt2[i2]),
+ '%s,%s' % (txt2[i1], txt2[i2])]]
+ else:
+ return self.fallback(buf)
+
+ def handle_branch(self, buf):
+ if buf[-1] & 0x80 != 0x00:
+ return None # Not complete yet.
+
+ brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state,
+ self.options['branch_enc'])
+
+ if brinfo is None:
+ return None # Not complete yet.
+
+ addr, addrlen, cpu_state, exc_info = brinfo
+ self.last_branch = addr
+ self.current_pc = addr
+
+ txt = ''
+
+ if cpu_state != self.cpu_state:
+ txt += ', to %s state' % cpu_state
+ self.cpu_state = cpu_state
+
+ if exc_info:
+ ns, exc, cancel, altisa, hyp, resume = exc_info
+ if ns:
+ txt += ', to non-secure state'
+ if exc:
+ # TODO: Parse the exception value to text.
+ txt += ', exception 0x%02x' % exc
+ if cancel:
+ txt += ', instr cancelled'
+ if altisa:
+ txt += ', to AltISA'
+ if hyp:
+ txt += ', to hypervisor'
+ if resume:
+ txt += ', instr resume 0x%02x' % resume
+
+ return [1, ['Branch to 0x%08x%s' % (addr, txt),
+ 'B 0x%08x%s' % (addr, txt)]]
+
+ def decode(self, ss, es, data):
+ ptype, rxtx, pdata = data
+
+ if ptype != 'DATA':
+ return
+
+ # Reset packet if there is a long pause between bytes.
+ # This helps getting the initial synchronization.
+ self.byte_len = es - ss
+ if ss - self.prevsample > 16 * self.byte_len:
+ self.flush_current_loc()
+ self.flush_current_func()
+ self.buf = []
+ self.prevsample = es
+
+ self.buf.append(pdata[0])
+
+ # Store the start time of the packet.
+ if len(self.buf) == 1:
+ self.startsample = ss
+
+ # Keep separate buffer for detection of sync packets.
+ # Sync packets override everything else, so that we can regain sync
+ # even if some packets are corrupted.
+ self.syncbuf = self.syncbuf[-4:] + [pdata[0]]
+ if self.syncbuf == [0x00, 0x00, 0x00, 0x00, 0x80]:
+ self.buf = self.syncbuf
+ self.syncbuf = []
+
+ # See if it is ready to be decoded.
+ ptype = self.get_packet_type(self.buf[0])
+ if hasattr(self, 'handle_' + ptype):
+ func = getattr(self, 'handle_' + ptype)
+ data = func(self.buf)
+ else:
+ data = self.fallback(self.buf)
+
+ if data is not None:
+ if data:
+ self.put(self.startsample, es, self.out_ann, data)
+ self.buf = []