]> sigrok.org Git - libsigrokdecode.git/blob - decoders/arm_etmv3/pd.py
all decoders: introduce a reset() method
[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, see <http://www.gnu.org/licenses/>.
18 ##
19
20 import sigrokdecode as srd
21 import subprocess
22 import re
23
24 # See ETMv3 Signal Protocol table 7-11: 'Encoding of Exception[8:0]'.
25 exc_names = [
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'
29 ]
30
31 for i in range(8, 496):
32     exc_names.append('IRQ%d' % i)
33
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.'''
37     v = 0
38     for i, b in enumerate(bytes):
39         v |= (b & 0x7F) << (i * 7)
40         if b & 0x80 == 0:
41             return v, i+1
42     return v, len(bytes)
43
44 def parse_uint(bytes):
45     '''Parse little-endian integer.'''
46     v = 0
47     for i, b in enumerate(bytes):
48         v |= b << (i * 8)
49     return v
50
51 def parse_exc_info(bytes):
52     '''Parse exception information bytes from a branch packet.'''
53     if len(bytes) < 1:
54         return None
55
56     excv, exclen = parse_varint(bytes)
57     if bytes[exclen - 1] & 0x80 != 0x00:
58         return None # Exception info not complete.
59
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)
63
64     ns = excv & 1
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)
71
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'''
76
77     addr, addrlen = parse_varint(bytes)
78
79     if bytes[addrlen-1] & 0x80 != 0x00:
80         return None # Branch address not complete.
81
82     addr_bits = 7 * addrlen
83
84     have_exc_info = False
85     if branch_enc == 'original':
86         if addrlen == 5 and bytes[4] & 0x40:
87             have_exc_info = True
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):
91             have_exc_info = True
92             addr &= ~(1 << addr_bits)
93
94     exc_info = None
95     if have_exc_info:
96         exc_info = parse_exc_info(bytes[addrlen:])
97         if exc_info is None:
98             return None # Exception info not complete.
99
100     if addrlen == 5:
101         # Possible change in CPU state.
102         if bytes[4] & 0xB8 == 0x08:
103             cpu_state = 'arm'
104         elif bytes[4] & 0xB0 == 0x10:
105             cpu_state = 'thumb'
106         elif bytes[4] & 0xA0 == 0x20:
107             cpu_state = 'jazelle'
108         else:
109             raise NotImplementedError('Unhandled branch byte 4: 0x%02x' % bytes[4])
110
111     # Shift the address according to current CPU state.
112     if cpu_state == 'arm':
113         addr = (addr & 0xFFFFFFFE) << 1
114         addr_bits += 1
115     elif cpu_state == 'thumb':
116         addr = addr & 0xFFFFFFFE
117     elif cpu_state == 'jazelle':
118         addr = (addr & 0xFFFFFFFFE) >> 1
119         addr_bits -= 1
120     else:
121         raise NotImplementedError('Unhandled state: ' + cpu_state)
122
123     # If the address wasn't full, fill in with the previous address.
124     if addrlen < 5:
125         addr |= ref_addr & (0xFFFFFFFF << addr_bits)
126
127     return addr, addrlen, cpu_state, exc_info
128
129 class Decoder(srd.Decoder):
130     api_version = 3
131     id = 'arm_etmv3'
132     name = 'ARM ETMv3'
133     longname = 'ARM Embedded Trace Macroblock'
134     desc = 'Decode ETM instruction trace packets.'
135     license = 'gplv2+'
136     inputs = ['uart']
137     outputs = ['arm_etmv3']
138     annotations = (
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'),
150     )
151     annotation_rows = (
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,)),
160     )
161     options = (
162         {'id': 'objdump', 'desc': 'objdump path',
163             'default': 'arm-none-eabi-objdump'},
164         {'id': 'objdump_opts', 'desc': 'objdump options',
165             'default': '-lSC'},
166         {'id': 'elffile', 'desc': '.elf path',
167             'default': ''},
168         {'id': 'branch_enc', 'desc': 'Branch encoding',
169             'default': 'alternative', 'values': ('alternative', 'original')},
170     )
171
172     def __init__(self):
173         self.reset()
174
175     def reset(self):
176         self.buf = []
177         self.syncbuf = []
178         self.prevsample = 0
179         self.last_branch = 0
180         self.cpu_state = 'arm'
181         self.current_pc = 0
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 = {}
189
190     def start(self):
191         self.out_ann = self.register(srd.OUTPUT_ANN)
192         self.load_objdump()
193
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.
199         '''
200         if not (self.options['objdump'] and self.options['elffile']):
201             return
202
203         opts = [self.options['objdump']]
204         opts += self.options['objdump_opts'].split()
205         opts += [self.options['elffile']]
206
207         try:
208             disasm = subprocess.check_output(opts)
209         except subprocess.CalledProcessError:
210             return
211
212         disasm = disasm.decode('utf-8', 'replace')
213
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*<([^>]+)>:.*')
218
219         prev_src = ''
220         prev_file = ''
221         prev_func = ''
222
223         for line in disasm.split('\n'):
224             m = instpat.match(line)
225             if m:
226                 addr = int(m.group(1), 16)
227                 raw = m.group(2)
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
233
234                 # Next address in direct sequence.
235                 ilen = len(raw.replace(' ', '')) // 2
236                 next_n = addr + ilen
237
238                 # Next address if branch is taken.
239                 bm = branchpat.match(disas)
240                 if bm:
241                     next_e = int(bm.group(2), 16)
242                 else:
243                     next_e = next_n
244
245                 self.next_instr_lookup[addr] = (next_n, next_e)
246             else:
247                 m = funcpat.match(line)
248                 if m:
249                     prev_func = m.group(1)
250                     prev_src = None
251                 else:
252                     m = filepat.match(line)
253                     if m:
254                         prev_file = m.group(1)
255                         prev_src = None
256                     else:
257                         prev_src = line.strip()
258
259     def flush_current_loc(self):
260         if self.current_loc is not None:
261             ss, es, loc, src = self.current_loc
262             if loc:
263                 self.put(ss, es, self.out_ann, [9, [loc]])
264             if src:
265                 self.put(ss, es, self.out_ann, [8, [src]])
266             self.current_loc = None
267
268     def flush_current_func(self):
269         if self.current_func is not None:
270             ss, es, func = self.current_func
271             if func:
272                 self.put(ss, es, self.out_ann, [10, [func]])
273             self.current_func = None
274
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
278         instructions.
279         '''
280
281         if len(exec_status) == 0:
282             return
283
284         tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status))
285
286         for i, exec_status in enumerate(exec_status):
287             pc = self.current_pc
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))
292
293             self.put(ss, es, self.out_ann,
294                      [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]])
295
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)
300
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()
305
306             if self.current_loc is None:
307                 self.current_loc = [ss, es, new_loc, new_src]
308             else:
309                 self.current_loc[1] = es
310
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()
315
316             if self.current_func is None:
317                 self.current_func = [ss, es, new_func]
318             else:
319                 self.current_func[1] = es
320
321             # Report instruction every time.
322             if new_dis:
323                 if exec_status:
324                     a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]]
325                 else:
326                     a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]]
327                 self.put(ss, es, self.out_ann, a)
328
329             if exec_status:
330                 self.current_pc = target_e
331             else:
332                 self.current_pc = target_n
333
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"
337         '''
338         if byte & 0x01 == 0x01:
339             return 'branch'
340         elif byte == 0x00:
341             return 'a_sync'
342         elif byte == 0x04:
343             return 'cyclecount'
344         elif byte == 0x08:
345             return 'i_sync'
346         elif byte == 0x0C:
347             return 'trigger'
348         elif byte & 0xF3 in (0x20, 0x40, 0x60):
349             return 'ooo_data'
350         elif byte == 0x50:
351             return 'store_failed'
352         elif byte == 0x70:
353             return 'i_sync'
354         elif byte & 0xDF in (0x54, 0x58, 0x5C):
355             return 'ooo_place'
356         elif byte == 0x3C:
357             return 'vmid'
358         elif byte & 0xD3 == 0x02:
359             return 'data'
360         elif byte & 0xFB == 0x42:
361             return 'timestamp'
362         elif byte == 0x62:
363             return 'data_suppressed'
364         elif byte == 0x66:
365             return 'ignore'
366         elif byte & 0xEF == 0x6A:
367             return 'value_not_traced'
368         elif byte == 0x6E:
369             return 'context_id'
370         elif byte == 0x76:
371             return 'exception_exit'
372         elif byte == 0x7E:
373             return 'exception_entry'
374         elif byte & 0x81 == 0x80:
375             return 'p_header'
376         else:
377             return 'unknown'
378
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])]]
382
383     def handle_a_sync(self, buf):
384         if buf[-1] == 0x80:
385             return [0, ['Synchronization']]
386
387     def handle_exception_exit(self, buf):
388         return [2, ['Exception exit']]
389
390     def handle_exception_entry(self, buf):
391         return [2, ['Exception entry']]
392
393     def handle_i_sync(self, buf):
394         contextid_bytes = 0 # This is the default ETM config.
395
396         if len(buf) < 6:
397             return None # Packet definitely not full yet.
398
399         if buf[0] == 0x08: # No cycle count.
400             cyclecount = None
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
405
406         if len(buf) <= idx + 4:
407             return None
408         infobyte = buf[idx]
409         addr = parse_uint(buf[idx+1:idx+5])
410
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
417         thumb = addr & 1
418         addr &= 0xFFFFFFFE
419
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()
428
429         self.last_branch = addr
430         self.current_pc = addr
431
432         if jazelle:
433             self.cpu_state = 'jazelle'
434         elif thumb:
435             self.cpu_state = 'thumb'
436         else:
437             self.cpu_state = 'arm'
438
439         cycstr = ''
440         if cyclecount is not None:
441             cycstr = ', cyclecount %d' % cyclecount
442
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']])
446
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)]]
450
451     def handle_trigger(self, buf):
452         return [0, ['Trigger event', 'Trigger']]
453
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
459
460             self.instructions_executed([1] * e + [0] * n)
461
462             if n:
463                 return [3, ['%d instructions executed, %d skipped due to ' \
464                             'condition codes' % (e, n),
465                             '%d ins exec, %d skipped' % (e, n),
466                             '%dE,%dN' % (e, n)]]
467             else:
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')
475             txt2 = ('E', 'S')
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])]]
479         else:
480             return self.fallback(buf)
481
482     def handle_branch(self, buf):
483         if buf[-1] & 0x80 != 0x00:
484             return None # Not complete yet.
485
486         brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state,
487                                    self.options['branch_enc'])
488
489         if brinfo is None:
490             return None # Not complete yet.
491
492         addr, addrlen, cpu_state, exc_info = brinfo
493         self.last_branch = addr
494         self.current_pc = addr
495
496         txt = ''
497
498         if cpu_state != self.cpu_state:
499             txt += ', to %s state' % cpu_state
500             self.cpu_state = cpu_state
501
502         annidx = 1
503
504         if exc_info:
505             annidx = 2
506             ns, exc, cancel, altisa, hyp, resume = exc_info
507             if ns:
508                 txt += ', to non-secure state'
509             if exc:
510                 if exc < len(exc_names):
511                     txt += ', exception %s' % exc_names[exc]
512                 else:
513                     txt += ', exception 0x%02x' % exc
514             if cancel:
515                 txt += ', instr cancelled'
516             if altisa:
517                 txt += ', to AltISA'
518             if hyp:
519                 txt += ', to hypervisor'
520             if resume:
521                 txt += ', instr resume 0x%02x' % resume
522
523         return [annidx, ['Branch to 0x%08x%s' % (addr, txt),
524                          'B 0x%08x%s' % (addr, txt)]]
525
526     def decode(self, ss, es, data):
527         ptype, rxtx, pdata = data
528
529         if ptype != 'DATA':
530             return
531
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()
538             self.buf = []
539         self.prevsample = es
540
541         self.buf.append(pdata[0])
542
543         # Store the start time of the packet.
544         if len(self.buf) == 1:
545             self.startsample = ss
546
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
553             self.syncbuf = []
554
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)
560         else:
561             data = self.fallback(self.buf)
562
563         if data is not None:
564             if data:
565                 self.put(self.startsample, es, self.out_ann, data)
566             self.buf = []