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