| 
									
										
										
										
											2019-10-10 17:10:47 +09:00
										 |  |  | #!/usr/bin/env python | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #  | 
					
						
							|  |  |  | # Prints a BPF program call trace with instruction counts for each call | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # This script requires a dump file containing the instruction dump of the ELF | 
					
						
							|  |  |  | # and a trace file that contains the trace output of the BPF VM | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # You can create the dump file with do.sh: | 
					
						
							|  |  |  | #  $ do.sh dump <project> | 
					
						
							|  |  |  | # Or directly: | 
					
						
							|  |  |  | #  $ llvm-objdump -print-imm-hex --source --disassemble <ELF file path> | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # You can create the trace file by running the program and setting RUST_LOG: | 
					
						
							|  |  |  | # Set RUST_LOG | 
					
						
							|  |  |  | #  $ export RUST_LOG=solana_rbpf=trace | 
					
						
							|  |  |  | # Capture output via `> trace.txt 2>&1` | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import re | 
					
						
							|  |  |  | import sys | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # regular expressions to search for | 
					
						
							|  |  |  | rxs_symbols = { | 
					
						
							|  |  |  |     'start' : re.compile(r'Disassembly of section \.text.*\n'), | 
					
						
							|  |  |  |     'symbol': re.compile(r'^(?!LBB)([<\S].+)\n'), | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | rxs_ixs = { | 
					
						
							|  |  |  |     'ixs': re.compile(r' *(\d+) *.*\n'), | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | rxs_call = { | 
					
						
							|  |  |  |     'entry': re.compile(r'BPF: +0 .+pc *(\d+) *.*\n'), | 
					
						
							|  |  |  |     'call' : re.compile(r'BPF: +(\d+) .+pc *(\d+) *call.*\n'), | 
					
						
							|  |  |  |     'exit' : re.compile(r'BPF: +(\d+) .+exit\n'), | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | rxs_called = { | 
					
						
							|  |  |  |     'pc': re.compile(r'BPF: +\d+ .+pc +(\d+).*\n'), | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # parse a line | 
					
						
							|  |  |  | def parse_line(rxs, line): | 
					
						
							|  |  |  |     for key, rx in rxs.items(): | 
					
						
							|  |  |  |         match = rx.search(line) | 
					
						
							|  |  |  |         if match: | 
					
						
							|  |  |  |             return key, match | 
					
						
							|  |  |  |     return None, None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # main | 
					
						
							|  |  |  | if __name__ == '__main__': | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if len(sys.argv) < 3: | 
					
						
							|  |  |  |         print('Error: Must specify a dump and trace file') | 
					
						
							|  |  |  |         sys.exit('    Usage: ' + sys.argv[0] + ' dump_file trace_file') | 
					
						
							|  |  |  |     dumppath = sys.argv[1] | 
					
						
							|  |  |  |     tracepath = sys.argv[2] | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     # parse the dump file to create a map | 
					
						
							|  |  |  |     # of instruction numbers to symbols | 
					
						
							|  |  |  |     symbols = {} | 
					
						
							|  |  |  |     with open(dumppath, 'r') as file_object: | 
					
						
							|  |  |  |         line = file_object.readline() | 
					
						
							|  |  |  |         while line: | 
					
						
							|  |  |  |             key, match = parse_line(rxs_symbols, line) | 
					
						
							|  |  |  |             if key == 'start': | 
					
						
							|  |  |  |                 line = file_object.readline() | 
					
						
							|  |  |  |                 while line: | 
					
						
							|  |  |  |                     key, match = parse_line(rxs_symbols, line) | 
					
						
							|  |  |  |                     if key == 'symbol': | 
					
						
							|  |  |  |                         symbol =  match.group(1) | 
					
						
							|  |  |  |                         line = file_object.readline() | 
					
						
							|  |  |  |                         key, match = parse_line(rxs_ixs, line) | 
					
						
							|  |  |  |                         if key == 'ixs': | 
					
						
							|  |  |  |                             ixs = int(match.group(1)) | 
					
						
							|  |  |  |                             symbols[ixs] = symbol | 
					
						
							|  |  |  |                     line = file_object.readline() | 
					
						
							|  |  |  |             line = file_object.readline() | 
					
						
							|  |  |  |     if len(symbols) == 0: | 
					
						
							|  |  |  |         sys.exit("Error: No instruction dump in: " + dumppath) | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     # parse the trace file to build a call list | 
					
						
							|  |  |  |     calls = [] # all the calls made | 
					
						
							|  |  |  |     with open(tracepath, 'r') as file_object: | 
					
						
							|  |  |  |         exits = [] # instruction counts at each call exit | 
					
						
							|  |  |  |         call_nums = [] # used to match up exits to calls | 
					
						
							|  |  |  |         frame = 1 | 
					
						
							| 
									
										
										
										
											2020-02-11 10:50:57 -08:00
										 |  |  |         max_frame = 0 | 
					
						
							| 
									
										
										
										
											2019-10-10 17:10:47 +09:00
										 |  |  |         num_calls = 0 | 
					
						
							|  |  |  |         line = file_object.readline() | 
					
						
							|  |  |  |         while line: | 
					
						
							| 
									
										
										
										
											2020-02-11 10:50:57 -08:00
										 |  |  |             # print line | 
					
						
							| 
									
										
										
										
											2019-10-10 17:10:47 +09:00
										 |  |  |             key, match = parse_line(rxs_call, line) | 
					
						
							|  |  |  |             if key == 'call': | 
					
						
							|  |  |  |                 ixs_count = int(match.group(1)) | 
					
						
							| 
									
										
										
										
											2020-02-11 10:50:57 -08:00
										 |  |  |                 calling_pc = int(match.group(2)) | 
					
						
							| 
									
										
										
										
											2019-10-10 17:10:47 +09:00
										 |  |  |                 line = file_object.readline() | 
					
						
							|  |  |  |                 key, match = parse_line(rxs_called, line) | 
					
						
							| 
									
										
										
										
											2020-02-11 10:50:57 -08:00
										 |  |  |                 while key != 'pc': | 
					
						
							|  |  |  |                     line = file_object.readline() | 
					
						
							|  |  |  |                     key, match = parse_line(rxs_called, line) | 
					
						
							|  |  |  |                 called_pc = int(match.group(1)) | 
					
						
							|  |  |  |                 if calling_pc + 1 == called_pc: | 
					
						
							|  |  |  |                     calls.append((ixs_count, frame, 'Solana system call')) | 
					
						
							|  |  |  |                     exits.append(ixs_count) | 
					
						
							|  |  |  |                     num_calls += 1 | 
					
						
							|  |  |  |                 else: | 
					
						
							| 
									
										
										
										
											2019-10-10 17:10:47 +09:00
										 |  |  |                     calls.append((ixs_count, frame, symbols[called_pc])) | 
					
						
							|  |  |  |                     call_nums.append(num_calls) | 
					
						
							|  |  |  |                     exits.append(0) | 
					
						
							|  |  |  |                     num_calls += 1 | 
					
						
							|  |  |  |                     frame += 1 | 
					
						
							| 
									
										
										
										
											2020-02-11 10:50:57 -08:00
										 |  |  |                     max_frame = max(frame, max_frame) | 
					
						
							| 
									
										
										
										
											2019-10-10 17:10:47 +09:00
										 |  |  |             else: | 
					
						
							|  |  |  |                 if key == 'entry': | 
					
						
							| 
									
										
										
										
											2020-02-11 10:50:57 -08:00
										 |  |  |                     pc = int(match.group(1)) | 
					
						
							|  |  |  |                     calls.append((0, 0, symbols[pc])) | 
					
						
							|  |  |  |                     call_nums.append(num_calls) | 
					
						
							|  |  |  |                     exits.append(0) | 
					
						
							|  |  |  |                     num_calls += 1 | 
					
						
							| 
									
										
										
										
											2019-10-10 17:10:47 +09:00
										 |  |  |                 elif key == 'exit': | 
					
						
							|  |  |  |                     ixs_count = int(match.group(1)) | 
					
						
							|  |  |  |                     num = call_nums.pop() | 
					
						
							|  |  |  |                     exits[num] = ixs_count | 
					
						
							|  |  |  |                     frame -= 1 | 
					
						
							|  |  |  |                 line = file_object.readline() | 
					
						
							| 
									
										
										
										
											2020-02-11 10:50:57 -08:00
										 |  |  |     if len(calls) == 0: | 
					
						
							|  |  |  |         sys.exit("Error: No valid trace in : " + tracepath) | 
					
						
							| 
									
										
										
										
											2019-10-10 17:10:47 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # print the call trace with instruction counts for each call | 
					
						
							| 
									
										
										
										
											2020-02-11 10:50:57 -08:00
										 |  |  |     print "Max frame depth: ", max_frame - 1 | 
					
						
							|  |  |  |     print "Call trace:" | 
					
						
							|  |  |  |     print "Ins num   (Ins count):  Symbol called" | 
					
						
							| 
									
										
										
										
											2019-10-10 17:10:47 +09:00
										 |  |  |     for call, exit in zip(calls, exits): | 
					
						
							| 
									
										
										
										
											2020-02-11 10:50:57 -08:00
										 |  |  |         if exit == 0: | 
					
						
							|  |  |  |             count = " No return" | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             count = "%9d" % (exit - call[0]) | 
					
						
							|  |  |  |         print("%9d (%s): %s %s" % (call[0], count, ' |  ' * call[1], call[2])) | 
					
						
							|  |  |  |     if frame != 0: | 
					
						
							|  |  |  |         print "Error: Program did not exit gracefully" |