]> sigrok.org Git - libsigrokdecode.git/blob - decoders/arm_etmv3/pd.py
decoders: Various cosmetic/consistency/typo fixes.
[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 v3'
134     desc = 'ARM ETM v3 instruction trace protocol.'
135     license = 'gplv2+'
136     inputs = ['uart']
137     outputs = ['arm_etmv3']
138     tags = ['Debug/trace']
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.reset()
175
176     def reset(self):
177         self.buf = []
178         self.syncbuf = []
179         self.prevsample = 0
180         self.last_branch = 0
181         self.cpu_state = 'arm'
182         self.current_pc = 0
183         self.current_loc = None
184         self.current_func = None
185         self.next_instr_lookup = {}
186         self.file_lookup = {}
187         self.func_lookup = {}
188         self.disasm_lookup = {}
189         self.source_lookup = {}
190
191     def start(self):
192         self.out_ann = self.register(srd.OUTPUT_ANN)
193         self.load_objdump()
194
195     def load_objdump(self):
196         '''Parse disassembly obtained from objdump into two tables:
197         next_instr_lookup: Find the next PC addr from current PC.
198         disasm_lookup: Find the instruction text from current PC.
199         source_lookup: Find the source code line from current PC.
200         '''
201         if not (self.options['objdump'] and self.options['elffile']):
202             return
203
204         opts = [self.options['objdump']]
205         opts += self.options['objdump_opts'].split()
206         opts += [self.options['elffile']]
207
208         try:
209             disasm = subprocess.check_output(opts)
210         except subprocess.CalledProcessError:
211             return
212
213         disasm = disasm.decode('utf-8', 'replace')
214
215         instpat = re.compile('\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*')
216         branchpat = re.compile('(b|bl|b..|bl..|cbnz|cbz)(?:\.[wn])?\s+(?:r[0-9]+,\s*)?([0-9a-fA-F]+)')
217         filepat = re.compile('[^\s]+[/\\\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?')
218         funcpat = re.compile('[0-9a-fA-F]+\s*<([^>]+)>:.*')
219
220         prev_src = ''
221         prev_file = ''
222         prev_func = ''
223
224         for line in disasm.split('\n'):
225             m = instpat.match(line)
226             if m:
227                 addr = int(m.group(1), 16)
228                 raw = m.group(2)
229                 disas = m.group(3).strip().replace('\t', ' ')
230                 self.disasm_lookup[addr] = disas
231                 self.source_lookup[addr] = prev_src
232                 self.file_lookup[addr] = prev_file
233                 self.func_lookup[addr] = prev_func
234
235                 # Next address in direct sequence.
236                 ilen = len(raw.replace(' ', '')) // 2
237                 next_n = addr + ilen
238
239                 # Next address if branch is taken.
240                 bm = branchpat.match(disas)
241                 if bm:
242                     next_e = int(bm.group(2), 16)
243                 else:
244                     next_e = next_n
245
246                 self.next_instr_lookup[addr] = (next_n, next_e)
247             else:
248                 m = funcpat.match(line)
249                 if m:
250                     prev_func = m.group(1)
251                     prev_src = None
252                 else:
253                     m = filepat.match(line)
254                     if m:
255                         prev_file = m.group(1)
256                         prev_src = None
257                     else:
258                         prev_src = line.strip()
259
260     def flush_current_loc(self):
261         if self.current_loc is not None:
262             ss, es, loc, src = self.current_loc
263             if loc:
264                 self.put(ss, es, self.out_ann, [9, [loc]])
265             if src:
266                 self.put(ss, es, self.out_ann, [8, [src]])
267             self.current_loc = None
268
269     def flush_current_func(self):
270         if self.current_func is not None:
271             ss, es, func = self.current_func
272             if func:
273                 self.put(ss, es, self.out_ann, [10, [func]])
274             self.current_func = None
275
276     def instructions_executed(self, exec_status):
277         '''Advance program counter based on executed instructions.
278         Argument is a list of False for not executed and True for executed
279         instructions.
280         '''
281
282         if len(exec_status) == 0:
283             return
284
285         tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status))
286
287         for i, exec_status in enumerate(exec_status):
288             pc = self.current_pc
289             default_next = pc + 2 if self.cpu_state == 'thumb' else pc + 4
290             target_n, target_e = self.next_instr_lookup.get(pc, (default_next, default_next))
291             ss = self.startsample + round(tdelta * i)
292             es = self.startsample + round(tdelta * (i+1))
293
294             self.put(ss, es, self.out_ann,
295                      [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]])
296
297             new_loc = self.file_lookup.get(pc)
298             new_src = self.source_lookup.get(pc)
299             new_dis = self.disasm_lookup.get(pc)
300             new_func = self.func_lookup.get(pc)
301
302             # Report source line only when it changes.
303             if self.current_loc is not None:
304                 if new_loc != self.current_loc[2] or new_src != self.current_loc[3]:
305                     self.flush_current_loc()
306
307             if self.current_loc is None:
308                 self.current_loc = [ss, es, new_loc, new_src]
309             else:
310                 self.current_loc[1] = es
311
312             # Report function name only when it changes.
313             if self.current_func is not None:
314                 if new_func != self.current_func[2]:
315                     self.flush_current_func()
316
317             if self.current_func is None:
318                 self.current_func = [ss, es, new_func]
319             else:
320                 self.current_func[1] = es
321
322             # Report instruction every time.
323             if new_dis:
324                 if exec_status:
325                     a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]]
326                 else:
327                     a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]]
328                 self.put(ss, es, self.out_ann, a)
329
330             if exec_status:
331                 self.current_pc = target_e
332             else:
333                 self.current_pc = target_n
334
335     def get_packet_type(self, byte):
336         '''Identify packet type based on its first byte.
337            See ARM IHI0014Q section "ETMv3 Signal Protocol" "Packet Types"
338         '''
339         if byte & 0x01 == 0x01:
340             return 'branch'
341         elif byte == 0x00:
342             return 'a_sync'
343         elif byte == 0x04:
344             return 'cyclecount'
345         elif byte == 0x08:
346             return 'i_sync'
347         elif byte == 0x0C:
348             return 'trigger'
349         elif byte & 0xF3 in (0x20, 0x40, 0x60):
350             return 'ooo_data'
351         elif byte == 0x50:
352             return 'store_failed'
353         elif byte == 0x70:
354             return 'i_sync'
355         elif byte & 0xDF in (0x54, 0x58, 0x5C):
356             return 'ooo_place'
357         elif byte == 0x3C:
358             return 'vmid'
359         elif byte & 0xD3 == 0x02:
360             return 'data'
361         elif byte & 0xFB == 0x42:
362             return 'timestamp'
363         elif byte == 0x62:
364             return 'data_suppressed'
365         elif byte == 0x66:
366             return 'ignore'
367         elif byte & 0xEF == 0x6A:
368             return 'value_not_traced'
369         elif byte == 0x6E:
370             return 'context_id'
371         elif byte == 0x76:
372             return 'exception_exit'
373         elif byte == 0x7E:
374             return 'exception_entry'
375         elif byte & 0x81 == 0x80:
376             return 'p_header'
377         else:
378             return 'unknown'
379
380     def fallback(self, buf):
381         ptype = self.get_packet_type(buf[0])
382         return [0, ['Unhandled ' + ptype + ': ' + ' '.join(['%02x' % b for b in buf])]]
383
384     def handle_a_sync(self, buf):
385         if buf[-1] == 0x80:
386             return [0, ['Synchronization']]
387
388     def handle_exception_exit(self, buf):
389         return [2, ['Exception exit']]
390
391     def handle_exception_entry(self, buf):
392         return [2, ['Exception entry']]
393
394     def handle_i_sync(self, buf):
395         contextid_bytes = 0 # This is the default ETM config.
396
397         if len(buf) < 6:
398             return None # Packet definitely not full yet.
399
400         if buf[0] == 0x08: # No cycle count.
401             cyclecount = None
402             idx = 1 + contextid_bytes # Index to info byte.
403         elif buf[0] == 0x70: # With cycle count.
404             cyclecount, cyclen = parse_varint(buf[1:6])
405             idx = 1 + cyclen + contextid_bytes
406
407         if len(buf) <= idx + 4:
408             return None
409         infobyte = buf[idx]
410         addr = parse_uint(buf[idx+1:idx+5])
411
412         reasoncode = (infobyte >> 5) & 3
413         reason = ('Periodic', 'Tracing enabled', 'After overflow', 'Exit from debug')[reasoncode]
414         jazelle = (infobyte >> 4) & 1
415         nonsec = (infobyte >> 3) & 1
416         altisa = (infobyte >> 2) & 1
417         hypervisor = (infobyte >> 1) & 1
418         thumb = addr & 1
419         addr &= 0xFFFFFFFE
420
421         if reasoncode == 0 and self.current_pc != addr:
422             self.put(self.startsample, self.prevsample, self.out_ann,
423                      [0, ['WARN: Unexpected PC change 0x%08x -> 0x%08x' % \
424                      (self.current_pc, addr)]])
425         elif reasoncode != 0:
426             # Reset location when the trace has been interrupted.
427             self.flush_current_loc()
428             self.flush_current_func()
429
430         self.last_branch = addr
431         self.current_pc = addr
432
433         if jazelle:
434             self.cpu_state = 'jazelle'
435         elif thumb:
436             self.cpu_state = 'thumb'
437         else:
438             self.cpu_state = 'arm'
439
440         cycstr = ''
441         if cyclecount is not None:
442             cycstr = ', cyclecount %d' % cyclecount
443
444         if infobyte & 0x80: # LSIP packet
445             self.put(self.startsample, self.prevsample, self.out_ann,
446                      [0, ['WARN: LSIP I-Sync packet not implemented']])
447
448         return [0, ['I-Sync: %s, PC 0x%08x, %s state%s' % \
449                     (reason, addr, self.cpu_state, cycstr), \
450                     'I-Sync: %s 0x%08x' % (reason, addr)]]
451
452     def handle_trigger(self, buf):
453         return [0, ['Trigger event', 'Trigger']]
454
455     def handle_p_header(self, buf):
456         # Only non cycle-accurate mode supported.
457         if buf[0] & 0x83 == 0x80:
458             n = (buf[0] >> 6) & 1
459             e = (buf[0] >> 2) & 15
460
461             self.instructions_executed([1] * e + [0] * n)
462
463             if n:
464                 return [3, ['%d instructions executed, %d skipped due to ' \
465                             'condition codes' % (e, n),
466                             '%d ins exec, %d skipped' % (e, n),
467                             '%dE,%dN' % (e, n)]]
468             else:
469                 return [3, ['%d instructions executed' % e,
470                             '%d ins exec' % e, '%dE' % e]]
471         elif buf[0] & 0xF3 == 0x82:
472             i1 = (buf[0] >> 3) & 1
473             i2 = (buf[0] >> 2) & 1
474             self.instructions_executed([not i1, not i2])
475             txt1 = ('executed', 'skipped')
476             txt2 = ('E', 'S')
477             return [3, ['Instruction 1 %s, instruction 2 %s' % (txt1[i1], txt1[i2]),
478                         'I1 %s, I2 %s' % (txt2[i1], txt2[i2]),
479                         '%s,%s' % (txt2[i1], txt2[i2])]]
480         else:
481             return self.fallback(buf)
482
483     def handle_branch(self, buf):
484         if buf[-1] & 0x80 != 0x00:
485             return None # Not complete yet.
486
487         brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state,
488                                    self.options['branch_enc'])
489
490         if brinfo is None:
491             return None # Not complete yet.
492
493         addr, addrlen, cpu_state, exc_info = brinfo
494         self.last_branch = addr
495         self.current_pc = addr
496
497         txt = ''
498
499         if cpu_state != self.cpu_state:
500             txt += ', to %s state' % cpu_state
501             self.cpu_state = cpu_state
502
503         annidx = 1
504
505         if exc_info:
506             annidx = 2
507             ns, exc, cancel, altisa, hyp, resume = exc_info
508             if ns:
509                 txt += ', to non-secure state'
510             if exc:
511                 if exc < len(exc_names):
512                     txt += ', exception %s' % exc_names[exc]
513                 else:
514                     txt += ', exception 0x%02x' % exc
515             if cancel:
516                 txt += ', instr cancelled'
517             if altisa:
518                 txt += ', to AltISA'
519             if hyp:
520                 txt += ', to hypervisor'
521             if resume:
522                 txt += ', instr resume 0x%02x' % resume
523
524         return [annidx, ['Branch to 0x%08x%s' % (addr, txt),
525                          'B 0x%08x%s' % (addr, txt)]]
526
527     def decode(self, ss, es, data):
528         ptype, rxtx, pdata = data
529
530         if ptype != 'DATA':
531             return
532
533         # Reset packet if there is a long pause between bytes.
534         # This helps getting the initial synchronization.
535         self.byte_len = es - ss
536         if ss - self.prevsample > 16 * self.byte_len:
537             self.flush_current_loc()
538             self.flush_current_func()
539             self.buf = []
540         self.prevsample = es
541
542         self.buf.append(pdata[0])
543
544         # Store the start time of the packet.
545         if len(self.buf) == 1:
546             self.startsample = ss
547
548         # Keep separate buffer for detection of sync packets.
549         # Sync packets override everything else, so that we can regain sync
550         # even if some packets are corrupted.
551         self.syncbuf = self.syncbuf[-4:] + [pdata[0]]
552         if self.syncbuf == [0x00, 0x00, 0x00, 0x00, 0x80]:
553             self.buf = self.syncbuf
554             self.syncbuf = []
555
556         # See if it is ready to be decoded.
557         ptype = self.get_packet_type(self.buf[0])
558         if hasattr(self, 'handle_' + ptype):
559             func = getattr(self, 'handle_' + ptype)
560             data = func(self.buf)
561         else:
562             data = self.fallback(self.buf)
563
564         if data is not None:
565             if data:
566                 self.put(self.startsample, es, self.out_ann, data)
567             self.buf = []