]> sigrok.org Git - libsigrokdecode.git/blob - decoders/arm_etmv3/pd.py
367ceb8aaecf69742e1ef91305def83ea5696fc6
[libsigrokdecode.git] / decoders / arm_etmv3 / pd.py
1 ##
2 ## This file is part of the libsigrokdecode project.
3 ##
4 ## Copyright (C) 2015 Petteri Aimonen <jpa@sigrok.mail.kapsi.fi>
5 ##
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.
10 ##
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.
15 ##
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
19 ##
20
21 import sigrokdecode as srd
22 import subprocess
23 import re
24
25 # See ETMv3 Signal Protocol table 7-11: 'Encoding of Exception[8:0]'.
26 exc_names = [
27     'No exception', 'IRQ1', 'IRQ2', 'IRQ3', 'IRQ4', 'IRQ5', 'IRQ6', 'IRQ7',
28     'IRQ0', 'UsageFault', 'NMI', 'SVC', 'DebugMon', 'MemManage', 'PendSV',
29     'SysTick', 'Reserved', 'Reset', 'BusFault', 'Reserved', 'Reserved'
30 ]
31
32 for i in range(8, 496):
33     exc_names.append('IRQ%d' % i)
34
35 def parse_varint(bytes):
36     '''Parse an integer where the top bit is the continuation bit.
37     Returns value and number of parsed bytes.'''
38     v = 0
39     for i, b in enumerate(bytes):
40         v |= (b & 0x7F) << (i * 7)
41         if b & 0x80 == 0:
42             return v, i+1
43     return v, len(bytes)
44
45 def parse_uint(bytes):
46     '''Parse little-endian integer.'''
47     v = 0
48     for i, b in enumerate(bytes):
49         v |= b << (i * 8)
50     return v
51
52 def parse_exc_info(bytes):
53     '''Parse exception information bytes from a branch packet.'''
54     if len(bytes) < 1:
55         return None
56
57     excv, exclen = parse_varint(bytes)
58     if bytes[exclen - 1] & 0x80 != 0x00:
59         return None # Exception info not complete.
60
61     if exclen == 2 and excv & (1 << 13):
62         # Exception byte 1 was skipped, fix up the decoding.
63         excv = (excv & 0x7F) | ((excv & 0x3F80) << 7)
64
65     ns = excv & 1
66     exc = ((excv >> 1) & 0x0F) | ((excv >> 7) & 0x1F0)
67     cancel = (excv >> 5) & 1
68     altisa = (excv >> 6) & 1
69     hyp = (excv >> 12) & 1
70     resume = (excv >> 14) & 0x0F
71     return (ns, exc, cancel, altisa, hyp, resume)
72
73 def parse_branch_addr(bytes, ref_addr, cpu_state, branch_enc):
74     '''Parse encoded branch address.
75        Returns addr, addrlen, cpu_state, exc_info.
76        Returns None if packet is not yet complete'''
77
78     addr, addrlen = parse_varint(bytes)
79
80     if bytes[addrlen-1] & 0x80 != 0x00:
81         return None # Branch address not complete.
82
83     addr_bits = 7 * addrlen
84
85     have_exc_info = False
86     if branch_enc == 'original':
87         if addrlen == 5 and bytes[4] & 0x40:
88             have_exc_info = True
89     elif branch_enc == 'alternative':
90         addr_bits -= 1 # Top bit of address indicates exc_info.
91         if addrlen >= 2 and addr & (1 << addr_bits):
92             have_exc_info = True
93             addr &= ~(1 << addr_bits)
94
95     exc_info = None
96     if have_exc_info:
97         exc_info = parse_exc_info(bytes[addrlen:])
98         if exc_info is None:
99             return None # Exception info not complete.
100
101     if addrlen == 5:
102         # Possible change in CPU state.
103         if bytes[4] & 0xB8 == 0x08:
104             cpu_state = 'arm'
105         elif bytes[4] & 0xB0 == 0x10:
106             cpu_state = 'thumb'
107         elif bytes[4] & 0xA0 == 0x20:
108             cpu_state = 'jazelle'
109         else:
110             raise NotImplementedError('Unhandled branch byte 4: 0x%02x' % bytes[4])
111
112     # Shift the address according to current CPU state.
113     if cpu_state == 'arm':
114         addr = (addr & 0xFFFFFFFE) << 1
115         addr_bits += 1
116     elif cpu_state == 'thumb':
117         addr = addr & 0xFFFFFFFE
118     elif cpu_state == 'jazelle':
119         addr = (addr & 0xFFFFFFFFE) >> 1
120         addr_bits -= 1
121     else:
122         raise NotImplementedError('Unhandled state: ' + cpu_state)
123
124     # If the address wasn't full, fill in with the previous address.
125     if addrlen < 5:
126         addr |= ref_addr & (0xFFFFFFFF << addr_bits)
127
128     return addr, addrlen, cpu_state, exc_info
129
130 class Decoder(srd.Decoder):
131     api_version = 2
132     id = 'arm_etmv3'
133     name = 'ARM ETMv3'
134     longname = 'ARM Embedded Trace Macroblock'
135     desc = 'Decode ETM instruction trace packets.'
136     license = 'gplv2+'
137     inputs = ['uart']
138     outputs = ['arm_etmv3']
139     annotations = (
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'),
151     )
152     annotation_rows = (
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,)),
161     )
162     options = (
163         {'id': 'objdump', 'desc': 'objdump path',
164             'default': 'arm-none-eabi-objdump'},
165         {'id': 'objdump_opts', 'desc': 'objdump options',
166             'default': '-lSC'},
167         {'id': 'elffile', 'desc': '.elf path',
168             'default': ''},
169         {'id': 'branch_enc', 'desc': 'Branch encoding',
170             'default': 'alternative', 'values': ('alternative', 'original')},
171     )
172
173     def __init__(self):
174         self.buf = []
175         self.syncbuf = []
176         self.prevsample = 0
177         self.last_branch = 0
178         self.cpu_state = 'arm'
179         self.current_pc = 0
180         self.current_loc = None
181         self.current_func = None
182         self.next_instr_lookup = {}
183         self.file_lookup = {}
184         self.func_lookup = {}
185         self.disasm_lookup = {}
186         self.source_lookup = {}
187
188     def start(self):
189         self.out_ann = self.register(srd.OUTPUT_ANN)
190         self.load_objdump()
191
192     def load_objdump(self):
193         '''Parse disassembly obtained from objdump into two tables:
194         next_instr_lookup: Find the next PC addr from current PC.
195         disasm_lookup: Find the instruction text from current PC.
196         source_lookup: Find the source code line from current PC.
197         '''
198         if not (self.options['objdump'] and self.options['elffile']):
199             return
200
201         opts = [self.options['objdump']]
202         opts += self.options['objdump_opts'].split()
203         opts += [self.options['elffile']]
204
205         try:
206             disasm = subprocess.check_output(opts)
207         except subprocess.CalledProcessError:
208             return
209
210         disasm = disasm.decode('utf-8', 'replace')
211
212         instpat = re.compile('\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*')
213         branchpat = re.compile('(b|bl|b..|bl..|cbnz|cbz)(?:\.[wn])?\s+(?:r[0-9]+,\s*)?([0-9a-fA-F]+)')
214         filepat = re.compile('[^\s]+[/\\\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?')
215         funcpat = re.compile('[0-9a-fA-F]+\s*<([^>]+)>:.*')
216
217         prev_src = ''
218         prev_file = ''
219         prev_func = ''
220
221         for line in disasm.split('\n'):
222             m = instpat.match(line)
223             if m:
224                 addr = int(m.group(1), 16)
225                 raw = m.group(2)
226                 disas = m.group(3).strip().replace('\t', ' ')
227                 self.disasm_lookup[addr] = disas
228                 self.source_lookup[addr] = prev_src
229                 self.file_lookup[addr] = prev_file
230                 self.func_lookup[addr] = prev_func
231
232                 # Next address in direct sequence.
233                 ilen = len(raw.replace(' ', '')) // 2
234                 next_n = addr + ilen
235
236                 # Next address if branch is taken.
237                 bm = branchpat.match(disas)
238                 if bm:
239                     next_e = int(bm.group(2), 16)
240                 else:
241                     next_e = next_n
242
243                 self.next_instr_lookup[addr] = (next_n, next_e)
244             else:
245                 m = funcpat.match(line)
246                 if m:
247                     prev_func = m.group(1)
248                     prev_src = None
249                 else:
250                     m = filepat.match(line)
251                     if m:
252                         prev_file = m.group(1)
253                         prev_src = None
254                     else:
255                         prev_src = line.strip()
256
257     def flush_current_loc(self):
258         if self.current_loc is not None:
259             ss, es, loc, src = self.current_loc
260             if loc:
261                 self.put(ss, es, self.out_ann, [9, [loc]])
262             if src:
263                 self.put(ss, es, self.out_ann, [8, [src]])
264             self.current_loc = None
265
266     def flush_current_func(self):
267         if self.current_func is not None:
268             ss, es, func = self.current_func
269             if func:
270                 self.put(ss, es, self.out_ann, [10, [func]])
271             self.current_func = None
272
273     def instructions_executed(self, exec_status):
274         '''Advance program counter based on executed instructions.
275         Argument is a list of False for not executed and True for executed
276         instructions.
277         '''
278
279         if len(exec_status) == 0:
280             return
281
282         tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status))
283
284         for i, exec_status in enumerate(exec_status):
285             pc = self.current_pc
286             default_next = pc + 2 if self.cpu_state == 'thumb' else pc + 4
287             target_n, target_e = self.next_instr_lookup.get(pc, (default_next, default_next))
288             ss = self.startsample + round(tdelta * i)
289             es = self.startsample + round(tdelta * (i+1))
290
291             self.put(ss, es, self.out_ann,
292                      [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]])
293
294             new_loc = self.file_lookup.get(pc)
295             new_src = self.source_lookup.get(pc)
296             new_dis = self.disasm_lookup.get(pc)
297             new_func = self.func_lookup.get(pc)
298
299             # Report source line only when it changes.
300             if self.current_loc is not None:
301                 if new_loc != self.current_loc[2] or new_src != self.current_loc[3]:
302                     self.flush_current_loc()
303
304             if self.current_loc is None:
305                 self.current_loc = [ss, es, new_loc, new_src]
306             else:
307                 self.current_loc[1] = es
308
309             # Report function name only when it changes.
310             if self.current_func is not None:
311                 if new_func != self.current_func[2]:
312                     self.flush_current_func()
313
314             if self.current_func is None:
315                 self.current_func = [ss, es, new_func]
316             else:
317                 self.current_func[1] = es
318
319             # Report instruction every time.
320             if new_dis:
321                 if exec_status:
322                     a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]]
323                 else:
324                     a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]]
325                 self.put(ss, es, self.out_ann, a)
326
327             if exec_status:
328                 self.current_pc = target_e
329             else:
330                 self.current_pc = target_n
331
332     def get_packet_type(self, byte):
333         '''Identify packet type based on its first byte.
334            See ARM IHI0014Q section "ETMv3 Signal Protocol" "Packet Types"
335         '''
336         if byte & 0x01 == 0x01:
337             return 'branch'
338         elif byte == 0x00:
339             return 'a_sync'
340         elif byte == 0x04:
341             return 'cyclecount'
342         elif byte == 0x08:
343             return 'i_sync'
344         elif byte == 0x0C:
345             return 'trigger'
346         elif byte & 0xF3 in (0x20, 0x40, 0x60):
347             return 'ooo_data'
348         elif byte == 0x50:
349             return 'store_failed'
350         elif byte == 0x70:
351             return 'i_sync'
352         elif byte & 0xDF in (0x54, 0x58, 0x5C):
353             return 'ooo_place'
354         elif byte == 0x3C:
355             return 'vmid'
356         elif byte & 0xD3 == 0x02:
357             return 'data'
358         elif byte & 0xFB == 0x42:
359             return 'timestamp'
360         elif byte == 0x62:
361             return 'data_suppressed'
362         elif byte == 0x66:
363             return 'ignore'
364         elif byte & 0xEF == 0x6A:
365             return 'value_not_traced'
366         elif byte == 0x6E:
367             return 'context_id'
368         elif byte == 0x76:
369             return 'exception_exit'
370         elif byte == 0x7E:
371             return 'exception_entry'
372         elif byte & 0x81 == 0x80:
373             return 'p_header'
374         else:
375             return 'unknown'
376
377     def fallback(self, buf):
378         ptype = self.get_packet_type(buf[0])
379         return [0, ['Unhandled ' + ptype + ': ' + ' '.join(['%02x' % b for b in buf])]]
380
381     def handle_a_sync(self, buf):
382         if buf[-1] == 0x80:
383             return [0, ['Synchronization']]
384
385     def handle_exception_exit(self, buf):
386         return [2, ['Exception exit']]
387
388     def handle_exception_entry(self, buf):
389         return [2, ['Exception entry']]
390
391     def handle_i_sync(self, buf):
392         contextid_bytes = 0 # This is the default ETM config.
393
394         if len(buf) < 6:
395             return None # Packet definitely not full yet.
396
397         if buf[0] == 0x08: # No cycle count.
398             cyclecount = None
399             idx = 1 + contextid_bytes # Index to info byte.
400         elif buf[0] == 0x70: # With cycle count.
401             cyclecount, cyclen = parse_varint(buf[1:6])
402             idx = 1 + cyclen + contextid_bytes
403
404         if len(buf) <= idx + 4:
405             return None
406         infobyte = buf[idx]
407         addr = parse_uint(buf[idx+1:idx+5])
408
409         reasoncode = (infobyte >> 5) & 3
410         reason = ('Periodic', 'Tracing enabled', 'After overflow', 'Exit from debug')[reasoncode]
411         jazelle = (infobyte >> 4) & 1
412         nonsec = (infobyte >> 3) & 1
413         altisa = (infobyte >> 2) & 1
414         hypervisor = (infobyte >> 1) & 1
415         thumb = addr & 1
416         addr &= 0xFFFFFFFE
417
418         if reasoncode == 0 and self.current_pc != addr:
419             self.put(self.startsample, self.prevsample, self.out_ann,
420                      [0, ['WARN: Unexpected PC change 0x%08x -> 0x%08x' % \
421                      (self.current_pc, addr)]])
422         elif reasoncode != 0:
423             # Reset location when the trace has been interrupted.
424             self.flush_current_loc()
425             self.flush_current_func()
426
427         self.last_branch = addr
428         self.current_pc = addr
429
430         if jazelle:
431             self.cpu_state = 'jazelle'
432         elif thumb:
433             self.cpu_state = 'thumb'
434         else:
435             self.cpu_state = 'arm'
436
437         cycstr = ''
438         if cyclecount is not None:
439             cycstr = ', cyclecount %d' % cyclecount
440
441         if infobyte & 0x80: # LSIP packet
442             self.put(self.startsample, self.prevsample, self.out_ann,
443                      [0, ['WARN: LSIP I-Sync packet not implemented']])
444
445         return [0, ['I-Sync: %s, PC 0x%08x, %s state%s' % \
446                     (reason, addr, self.cpu_state, cycstr), \
447                     'I-Sync: %s 0x%08x' % (reason, addr)]]
448
449     def handle_trigger(self, buf):
450         return [0, ['Trigger event', 'Trigger']]
451
452     def handle_p_header(self, buf):
453         # Only non cycle-accurate mode supported.
454         if buf[0] & 0x83 == 0x80:
455             n = (buf[0] >> 6) & 1
456             e = (buf[0] >> 2) & 15
457
458             self.instructions_executed([1] * e + [0] * n)
459
460             if n:
461                 return [3, ['%d instructions executed, %d skipped due to ' \
462                             'condition codes' % (e, n),
463                             '%d ins exec, %d skipped' % (e, n),
464                             '%dE,%dN' % (e, n)]]
465             else:
466                 return [3, ['%d instructions executed' % e,
467                             '%d ins exec' % e, '%dE' % e]]
468         elif buf[0] & 0xF3 == 0x82:
469             i1 = (buf[0] >> 3) & 1
470             i2 = (buf[0] >> 2) & 1
471             self.instructions_executed([not i1, not i2])
472             txt1 = ('executed', 'skipped')
473             txt2 = ('E', 'S')
474             return [3, ['Instruction 1 %s, instruction 2 %s' % (txt1[i1], txt1[i2]),
475                         'I1 %s, I2 %s' % (txt2[i1], txt2[i2]),
476                         '%s,%s' % (txt2[i1], txt2[i2])]]
477         else:
478             return self.fallback(buf)
479
480     def handle_branch(self, buf):
481         if buf[-1] & 0x80 != 0x00:
482             return None # Not complete yet.
483
484         brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state,
485                                    self.options['branch_enc'])
486
487         if brinfo is None:
488             return None # Not complete yet.
489
490         addr, addrlen, cpu_state, exc_info = brinfo
491         self.last_branch = addr
492         self.current_pc = addr
493
494         txt = ''
495
496         if cpu_state != self.cpu_state:
497             txt += ', to %s state' % cpu_state
498             self.cpu_state = cpu_state
499
500         annidx = 1
501
502         if exc_info:
503             annidx = 2
504             ns, exc, cancel, altisa, hyp, resume = exc_info
505             if ns:
506                 txt += ', to non-secure state'
507             if exc:
508                 if exc < len(exc_names):
509                     txt += ', exception %s' % exc_names[exc]
510                 else:
511                     txt += ', exception 0x%02x' % exc
512             if cancel:
513                 txt += ', instr cancelled'
514             if altisa:
515                 txt += ', to AltISA'
516             if hyp:
517                 txt += ', to hypervisor'
518             if resume:
519                 txt += ', instr resume 0x%02x' % resume
520
521         return [annidx, ['Branch to 0x%08x%s' % (addr, txt),
522                          'B 0x%08x%s' % (addr, txt)]]
523
524     def decode(self, ss, es, data):
525         ptype, rxtx, pdata = data
526
527         if ptype != 'DATA':
528             return
529
530         # Reset packet if there is a long pause between bytes.
531         # This helps getting the initial synchronization.
532         self.byte_len = es - ss
533         if ss - self.prevsample > 16 * self.byte_len:
534             self.flush_current_loc()
535             self.flush_current_func()
536             self.buf = []
537         self.prevsample = es
538
539         self.buf.append(pdata[0])
540
541         # Store the start time of the packet.
542         if len(self.buf) == 1:
543             self.startsample = ss
544
545         # Keep separate buffer for detection of sync packets.
546         # Sync packets override everything else, so that we can regain sync
547         # even if some packets are corrupted.
548         self.syncbuf = self.syncbuf[-4:] + [pdata[0]]
549         if self.syncbuf == [0x00, 0x00, 0x00, 0x00, 0x80]:
550             self.buf = self.syncbuf
551             self.syncbuf = []
552
553         # See if it is ready to be decoded.
554         ptype = self.get_packet_type(self.buf[0])
555         if hasattr(self, 'handle_' + ptype):
556             func = getattr(self, 'handle_' + ptype)
557             data = func(self.buf)
558         else:
559             data = self.fallback(self.buf)
560
561         if data is not None:
562             if data:
563                 self.put(self.startsample, es, self.out_ann, data)
564             self.buf = []