]> sigrok.org Git - libsigrokdecode.git/blob - decoders/arm_etmv3/pd.py
c63714279acbb5b3c029e82d2cdd43d2f0acd337
[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 def parse_varint(bytes):
26     '''Parse an integer where the top bit is the continuation bit.
27     Returns value and number of parsed bytes.'''
28     v = 0
29     for i, b in enumerate(bytes):
30         v |= (b & 0x7F) << (i * 7)
31         if b & 0x80 == 0:
32             return v, i+1
33     return v, len(bytes)
34
35 def parse_uint(bytes):
36     '''Parse little-endian integer.'''
37     v = 0
38     for i, b in enumerate(bytes):
39         v |= b << (i * 8)
40     return v
41
42 def parse_exc_info(bytes):
43     '''Parse exception information bytes from a branch packet.'''
44     if len(bytes) < 1:
45         return None
46
47     excv, exclen = parse_varint(bytes)
48     if bytes[exclen - 1] & 0x80 != 0x00:
49         return None # Exception info not complete.
50
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)
54
55     ns = excv & 1
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)
62
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'''
67
68     addr, addrlen = parse_varint(bytes)
69
70     if bytes[addrlen-1] & 0x80 != 0x00:
71         return None # Branch address not complete.
72
73     have_exc_info = False
74     if branch_enc == 'original':
75         if addrlen == 5 and bytes[4] & 0x40:
76             have_exc_info = True
77     elif branch_enc == 'alternative':
78         if addrlen >= 2 and bytes[addrlen - 1] & 0x40:
79             have_exc_info = True
80             addr &= ~(1 << (addrlen * 7 - 1))
81
82     exc_info = None
83     if have_exc_info:
84         exc_info = parse_exc_info(bytes[addrlen:])
85         if exc_info is None:
86             return None # Exception info not complete.
87
88     if addrlen == 5:
89         # Possible change in CPU state.
90         if bytes[4] & 0xB8 == 0x08:
91             cpu_state = 'arm'
92         elif bytes[4] & 0xB0 == 0x10:
93             cpu_state = 'thumb'
94         elif bytes[4] & 0xA0 == 0x20:
95             cpu_state = 'jazelle'
96         else:
97             raise NotImplementedError('Unhandled branch byte 4: 0x%02x' % bytes[4])
98
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
106     else:
107         raise NotImplementedError('Unhandled state: ' + cpu_state)
108
109     # If the address wasn't full, fill in with the previous address.
110     if addrlen < 5:
111         bits = 7 * addrlen
112         addr |= ref_addr & (0xFFFFFFFF << bits)
113
114     return addr, addrlen, cpu_state, exc_info
115
116 class Decoder(srd.Decoder):
117     api_version = 2
118     id = 'arm_etmv3'
119     name = 'ARM ETMv3'
120     longname = 'ARM Embedded Trace Macroblock'
121     desc = 'Decode ETM instruction trace packets.'
122     license = 'gplv2+'
123     inputs = ['uart']
124     outputs = ['arm_etmv3']
125     annotations = (
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'),
137     )
138     annotation_rows = (
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,)),
147     )
148     options = (
149         {'id': 'objdump', 'desc': 'objdump path',
150             'default': 'arm-none-eabi-objdump'},
151         {'id': 'objdump_opts', 'desc': 'objdump options',
152             'default': '-lS'},
153         {'id': 'elffile', 'desc': '.elf path',
154             'default': ''},
155         {'id': 'branch_enc', 'desc': 'Branch encoding',
156             'default': 'alternative', 'values': ('alternative', 'original')},
157     )
158
159     def __init__(self, **kwargs):
160         self.buf = []
161         self.syncbuf = []
162         self.prevsample = 0
163         self.last_branch = 0
164         self.cpu_state = 'arm'
165         self.current_pc = 0
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 = {}
173
174     def start(self):
175         self.out_ann = self.register(srd.OUTPUT_ANN)
176         self.load_objdump()
177
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.
183         '''
184         if not (self.options['objdump'] and self.options['elffile']):
185             return
186
187         opts = [self.options['objdump']]
188         opts += self.options['objdump_opts'].split()
189         opts += [self.options['elffile']]
190
191         try:
192             disasm = subprocess.check_output(opts)
193         except subprocess.CalledProcessError:
194             return
195
196         disasm = disasm.decode('utf-8', 'replace')
197
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_-]+)>:.*')
202
203         prev_src = ''
204         prev_file = ''
205         prev_func = ''
206
207         for line in disasm.split('\n'):
208             m = instpat.match(line)
209             if m:
210                 addr = int(m.group(1), 16)
211                 raw = m.group(2)
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
217
218                 # Next address in direct sequence.
219                 ilen = len(raw.replace(' ', '')) // 2
220                 next_n = addr + ilen
221
222                 # Next address if branch is taken.
223                 bm = branchpat.match(disas)
224                 if bm:
225                     next_e = int(bm.group(2), 16)
226                 else:
227                     next_e = next_n
228
229                 self.next_instr_lookup[addr] = (next_n, next_e)
230             else:
231                 m = funcpat.match(line)
232                 if m:
233                     prev_func = m.group(1)
234                 else:
235                     m = filepat.match(line)
236                     if m:
237                         prev_file = m.group(1)
238                     else:
239                         prev_src = line.strip()
240
241     def flush_current_loc(self):
242         if self.current_loc is not None:
243             ss, es, loc, src = self.current_loc
244             if loc:
245                 self.put(ss, es, self.out_ann, [9, [loc]])
246             if src:
247                 self.put(ss, es, self.out_ann, [8, [src]])
248             self.current_loc = None
249
250     def flush_current_func(self):
251         if self.current_func is not None:
252             ss, es, func = self.current_func
253             if func:
254                 self.put(ss, es, self.out_ann, [10, [func]])
255             self.current_func = None
256
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
260         instructions.
261         '''
262
263         tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status))
264
265         for i, exec_status in enumerate(exec_status):
266             pc = self.current_pc
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))
271
272             self.put(ss, es, self.out_ann,
273                      [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]])
274
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)
279
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()
284
285             if self.current_loc is None:
286                 self.current_loc = [ss, es, new_loc, new_src]
287             else:
288                 self.current_loc[1] = es
289
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()
294
295             if self.current_func is None:
296                 self.current_func = [ss, es, new_func]
297             else:
298                 self.current_func[1] = es
299
300             # Report instruction every time.
301             if new_dis:
302                 if exec_status:
303                     a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]]
304                 else:
305                     a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]]
306                 self.put(ss, es, self.out_ann, a)
307
308             if exec_status:
309                 self.current_pc = target_e
310             else:
311                 self.current_pc = target_n
312
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"
316         '''
317         if byte & 0x01 == 0x01:
318             return 'branch'
319         elif byte == 0x00:
320             return 'a_sync'
321         elif byte == 0x04:
322             return 'cyclecount'
323         elif byte == 0x08:
324             return 'i_sync'
325         elif byte == 0x0C:
326             return 'trigger'
327         elif byte & 0xF3 in (0x20, 0x40, 0x60):
328             return 'ooo_data'
329         elif byte == 0x50:
330             return 'store_failed'
331         elif byte == 0x70:
332             return 'i_sync'
333         elif byte & 0xDF in (0x54, 0x58, 0x5C):
334             return 'ooo_place'
335         elif byte == 0x3C:
336             return 'vmid'
337         elif byte & 0xD3 == 0x02:
338             return 'data'
339         elif byte & 0xFB == 0x42:
340             return 'timestamp'
341         elif byte == 0x62:
342             return 'data_suppressed'
343         elif byte == 0x66:
344             return 'ignore'
345         elif byte & 0xEF == 0x6A:
346             return 'value_not_traced'
347         elif byte == 0x6E:
348             return 'context_id'
349         elif byte == 0x76:
350             return 'exception_exit'
351         elif byte == 0x7E:
352             return 'exception_entry'
353         elif byte & 0x81 == 0x80:
354             return 'p_header'
355         else:
356             return 'unknown'
357
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])]]
361
362     def handle_a_sync(self, buf):
363         if buf[-1] == 0x80:
364             return [0, ['Synchronization']]
365
366     def handle_exception_exit(self, buf):
367         return [2, 'Exception exit']
368
369     def handle_exception_entry(self, buf):
370         return [1, 'Exception entry']
371
372     def handle_i_sync(self, buf):
373         contextid_bytes = 0 # This is the default ETM config.
374
375         if len(buf) < 6:
376             return None # Packet definitely not full yet.
377
378         if buf[0] == 0x08: # No cycle count.
379             cyclecount = None
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
384
385         if len(buf) <= idx + 4:
386             return None
387         infobyte = buf[idx]
388         addr = parse_uint(buf[idx+1:idx+5])
389
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
396         thumb = addr & 1
397         addr &= 0xFFFFFFFE
398
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()
407
408         self.last_branch = addr
409         self.current_pc = addr
410
411         if jazelle:
412             self.cpu_state = 'jazelle'
413         elif thumb:
414             self.cpu_state = 'thumb'
415         else:
416             self.cpu_state = 'arm'
417
418         cycstr = ''
419         if cyclecount is not None:
420             cycstr = ', cyclecount %d' % cyclecount
421
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']])
425
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)]]
429
430     def handle_trigger(self, buf):
431         return [0, ['Trigger event', 'Trigger']]
432
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
438
439             self.instructions_executed([1] * e + [0] * n)
440
441             if n:
442                 return [3, ['%d instructions executed, %d skipped due to ' \
443                             'condition codes' % (e, n),
444                             '%d ins exec, %d skipped' % (e, n),
445                             '%dE,%dN' % (e, n)]]
446             else:
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')
454             txt2 = ('E', 'S')
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])]]
458         else:
459             return self.fallback(buf)
460
461     def handle_branch(self, buf):
462         if buf[-1] & 0x80 != 0x00:
463             return None # Not complete yet.
464
465         brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state,
466                                    self.options['branch_enc'])
467
468         if brinfo is None:
469             return None # Not complete yet.
470
471         addr, addrlen, cpu_state, exc_info = brinfo
472         self.last_branch = addr
473         self.current_pc = addr
474
475         txt = ''
476
477         if cpu_state != self.cpu_state:
478             txt += ', to %s state' % cpu_state
479             self.cpu_state = cpu_state
480
481         if exc_info:
482             ns, exc, cancel, altisa, hyp, resume = exc_info
483             if ns:
484                 txt += ', to non-secure state'
485             if exc:
486                 # TODO: Parse the exception value to text.
487                 txt += ', exception 0x%02x' % exc
488             if cancel:
489                 txt += ', instr cancelled'
490             if altisa:
491                 txt += ', to AltISA'
492             if hyp:
493                 txt += ', to hypervisor'
494             if resume:
495                 txt += ', instr resume 0x%02x' % resume
496
497         return [1, ['Branch to 0x%08x%s' % (addr, txt),
498                     'B 0x%08x%s' % (addr, txt)]]
499
500     def decode(self, ss, es, data):
501         ptype, rxtx, pdata = data
502
503         if ptype != 'DATA':
504             return
505
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()
512             self.buf = []
513         self.prevsample = es
514
515         self.buf.append(pdata[0])
516
517         # Store the start time of the packet.
518         if len(self.buf) == 1:
519             self.startsample = ss
520
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
527             self.syncbuf = []
528
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)
534         else:
535             data = self.fallback(self.buf)
536
537         if data is not None:
538             if data:
539                 self.put(self.startsample, es, self.out_ann, data)
540             self.buf = []