Misc: add pyproject.toml
[lttng-tools.git] / tests / utils / parse-callstack.py
CommitLineData
40b2a4a7 1#!/usr/bin/env python3
591ee332 2#
9d16b343 3# Copyright (C) 2017 Francis Deslauriers <francis.deslauriers@efficios.com>
591ee332 4#
9d16b343 5# SPDX-License-Identifier: LGPL-2.1-only
591ee332 6#
591ee332
FD
7
8import sys
9import bisect
10import subprocess
11import re
12
13def addr2line(executable, addr):
14 """
15 Uses binutils' addr2line to get function containing a given address
16 """
17 cmd =['addr2line']
18
19 cmd += ['-e', executable]
20
21 # Print function names
22 cmd += ['--functions']
23
24 # Expand inlined functions
25 cmd += ['--addresses', addr]
26
40b2a4a7
OD
27 status = subprocess.run(cmd, stdout=subprocess.PIPE, check=True)
28
9613cdd0
JG
29 addr2line_output = status.stdout.decode("utf-8").splitlines()
30 # addr2line's output is made of 3-tuples:
31 # - address
32 # - function name
33 # - source location
34 if len(addr2line_output) % 3 != 0:
35 raise Exception('Unexpected addr2line output:\n\t{}'.format('\n\t'.join(addr2line_output)))
36
37 function_names = []
38 for address_line_number in range(0, len(addr2line_output), 3):
39 function_name = addr2line_output[address_line_number + 1]
40
41 # Filter-out unresolved functions
42 if "??" not in function_name:
43 function_names.append(addr2line_output[address_line_number + 1])
44
45 return function_names
591ee332
FD
46
47def extract_user_func_names(executable, raw_callstack):
48 """
49 Given a callstack from the Babeltrace CLI output, returns a set
50 containing the name of the functions. This assumes that the binary have
51 not changed since the execution.
52 """
53 recorded_callstack = set()
54
55 # Remove commas and split on spaces
56 for index, addr in enumerate(raw_callstack.replace(',', '').split(' ')):
57 # Consider only the elements starting with '0x' which are the
58 # addresses recorded in the callstack
59 if '0x' in addr[:2]:
60 funcs = addr2line(executable, addr)
61 recorded_callstack.update(funcs)
62
63 return recorded_callstack
64
65def extract_kernel_func_names(raw_callstack):
66 """
67 Given a callstack from the Babeltrace CLI output, returns a set
68 containing the name of the functions.
69 Uses the /proc/kallsyms procfile to find the symbol associated with an
70 address. This function should only be used if the user is root or has
71 access to /proc/kallsyms.
72 """
73 recorded_callstack = set()
74 syms=[]
75 addresses=[]
76 # We read kallsyms file and save the output
77 with open('/proc/kallsyms') as kallsyms_f:
78 for line in kallsyms_f:
79 line_tokens = line.split()
80 addr = line_tokens[0]
81 symbol = line_tokens[2]
82 addresses.append(int(addr, 16))
83 syms.append({'addr':int(addr, 16), 'symbol':symbol})
84
85 # Save the address and symbol in a sorted list of tupple
86 syms = sorted(syms, key=lambda k:k['addr'])
87 # We save the list of addresses in a seperate sorted list to easily bisect
88 # the closer address of a symbol.
89 addresses = sorted(addresses)
90
91 # Remove commas and split on spaces
92 for addr in raw_callstack.replace(',', '').split(' '):
93 if '0x' in addr[:2]:
94 # Search the location of the address in the addresses list and
95 # deference this location in the syms list to add the associated
96 # symbol.
97 loc = bisect.bisect(addresses, int(addr, 16))
98 recorded_callstack.add(syms[loc-1]['symbol'])
99
100 return recorded_callstack
101
102# Regex capturing the callstack_user and callstack_kernel context
103user_cs_rexp='.*callstack_user\ \=\ \[(.*)\]\ .*\}, \{.*\}'
104kernel_cs_rexp='.*callstack_kernel\ \=\ \[(.*)\]\ .*\}, \{.*\}'
105
106def main():
107 """
108 Reads a line from stdin and expect it to be a wellformed Babeltrace CLI
109 output containing containing a callstack context of the domain passed
110 as argument.
111 """
112 expected_callstack = set()
113 recorded_callstack = set()
114 cs_type=None
115
116 if len(sys.argv) <= 2:
117 print(sys.argv)
118 raise ValueError('USAGE: ./{} (--kernel|--user EXE) FUNC-NAMES'.format(sys.argv[0]))
119
120 # If the `--user` option is passed, save the next argument as the path
121 # to the executable
122 argc=1
123 executable=None
124 if sys.argv[argc] in '--kernel':
125 rexp = kernel_cs_rexp
126 cs_type='kernel'
127 elif sys.argv[argc] in '--user':
128 rexp = user_cs_rexp
129 cs_type='user'
130 argc+=1
131 executable = sys.argv[argc]
132 else:
133 raise Exception('Unknown domain')
134
135 argc+=1
136
137 # Extract the function names that are expected to be found call stack of
138 # the current events
139 for func in sys.argv[argc:]:
140 expected_callstack.add(func)
141
142 # Read the tested line for STDIN
143 event_line = None
144 for line in sys.stdin:
145 event_line = line
146 break
147
148 # Extract the userspace callstack context of the event
149 m = re.match(rexp, event_line)
150
151 # If there is no match, exit with error
152 if m is None:
153 raise re.error('Callstack not found in event line')
154 else:
155 raw_callstack = str(m.group(1))
156 if cs_type in 'user':
157 recorded_callstack=extract_user_func_names(executable, raw_callstack)
158 elif cs_type in 'kernel':
159 recorded_callstack=extract_kernel_func_names(raw_callstack)
160 else:
161 raise Exception('Unknown domain')
162
163 # Verify that all expected function are present in the callstack
164 for e in expected_callstack:
165 if e not in recorded_callstack:
166 raise Exception('Expected function name not found in recorded callstack')
167
168 sys.exit(0)
169
170if __name__ == '__main__':
171 main()
This page took 0.050209 seconds and 4 git commands to generate.