Fix: time constants already defined on macOS
[lttng-tools.git] / tests / utils / parse-callstack.py
1 #! /usr/bin/python3
2
3 # Copyright (C) - 2017 Francis Deslauriers <francis.deslauriers@efficios.com>
4 #
5 # This library is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License as published by the
7 # Free Software Foundation; version 2.1 of the License.
8 #
9 # This library is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12 # for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public License
15 # along with this library; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
18 import sys
19 import bisect
20 import subprocess
21 import re
22
23 def addr2line(executable, addr):
24 """
25 Uses binutils' addr2line to get function containing a given address
26 """
27 cmd =['addr2line']
28
29 cmd += ['-e', executable]
30
31 # Print function names
32 cmd += ['--functions']
33
34 # Expand inlined functions
35 cmd += ['--addresses', addr]
36
37 addr2line_output = subprocess.getoutput(' '.join(cmd))
38
39 # Omit the last 2 lines as the caller of main can not be determine
40 fcts = [addr2line_output.split()[-2]]
41
42 fcts = [ f for f in fcts if '??' not in f]
43
44 return fcts
45
46 def extract_user_func_names(executable, raw_callstack):
47 """
48 Given a callstack from the Babeltrace CLI output, returns a set
49 containing the name of the functions. This assumes that the binary have
50 not changed since the execution.
51 """
52 recorded_callstack = set()
53
54 # Remove commas and split on spaces
55 for index, addr in enumerate(raw_callstack.replace(',', '').split(' ')):
56 # Consider only the elements starting with '0x' which are the
57 # addresses recorded in the callstack
58 if '0x' in addr[:2]:
59 funcs = addr2line(executable, addr)
60 recorded_callstack.update(funcs)
61
62 return recorded_callstack
63
64 def extract_kernel_func_names(raw_callstack):
65 """
66 Given a callstack from the Babeltrace CLI output, returns a set
67 containing the name of the functions.
68 Uses the /proc/kallsyms procfile to find the symbol associated with an
69 address. This function should only be used if the user is root or has
70 access to /proc/kallsyms.
71 """
72 recorded_callstack = set()
73 syms=[]
74 addresses=[]
75 # We read kallsyms file and save the output
76 with open('/proc/kallsyms') as kallsyms_f:
77 for line in kallsyms_f:
78 line_tokens = line.split()
79 addr = line_tokens[0]
80 symbol = line_tokens[2]
81 addresses.append(int(addr, 16))
82 syms.append({'addr':int(addr, 16), 'symbol':symbol})
83
84 # Save the address and symbol in a sorted list of tupple
85 syms = sorted(syms, key=lambda k:k['addr'])
86 # We save the list of addresses in a seperate sorted list to easily bisect
87 # the closer address of a symbol.
88 addresses = sorted(addresses)
89
90 # Remove commas and split on spaces
91 for addr in raw_callstack.replace(',', '').split(' '):
92 if '0x' in addr[:2]:
93 # Search the location of the address in the addresses list and
94 # deference this location in the syms list to add the associated
95 # symbol.
96 loc = bisect.bisect(addresses, int(addr, 16))
97 recorded_callstack.add(syms[loc-1]['symbol'])
98
99 return recorded_callstack
100
101 # Regex capturing the callstack_user and callstack_kernel context
102 user_cs_rexp='.*callstack_user\ \=\ \[(.*)\]\ .*\}, \{.*\}'
103 kernel_cs_rexp='.*callstack_kernel\ \=\ \[(.*)\]\ .*\}, \{.*\}'
104
105 def main():
106 """
107 Reads a line from stdin and expect it to be a wellformed Babeltrace CLI
108 output containing containing a callstack context of the domain passed
109 as argument.
110 """
111 expected_callstack = set()
112 recorded_callstack = set()
113 cs_type=None
114
115 if len(sys.argv) <= 2:
116 print(sys.argv)
117 raise ValueError('USAGE: ./{} (--kernel|--user EXE) FUNC-NAMES'.format(sys.argv[0]))
118
119 # If the `--user` option is passed, save the next argument as the path
120 # to the executable
121 argc=1
122 executable=None
123 if sys.argv[argc] in '--kernel':
124 rexp = kernel_cs_rexp
125 cs_type='kernel'
126 elif sys.argv[argc] in '--user':
127 rexp = user_cs_rexp
128 cs_type='user'
129 argc+=1
130 executable = sys.argv[argc]
131 else:
132 raise Exception('Unknown domain')
133
134 argc+=1
135
136 # Extract the function names that are expected to be found call stack of
137 # the current events
138 for func in sys.argv[argc:]:
139 expected_callstack.add(func)
140
141 # Read the tested line for STDIN
142 event_line = None
143 for line in sys.stdin:
144 event_line = line
145 break
146
147 # Extract the userspace callstack context of the event
148 m = re.match(rexp, event_line)
149
150 # If there is no match, exit with error
151 if m is None:
152 raise re.error('Callstack not found in event line')
153 else:
154 raw_callstack = str(m.group(1))
155 if cs_type in 'user':
156 recorded_callstack=extract_user_func_names(executable, raw_callstack)
157 elif cs_type in 'kernel':
158 recorded_callstack=extract_kernel_func_names(raw_callstack)
159 else:
160 raise Exception('Unknown domain')
161
162 # Verify that all expected function are present in the callstack
163 for e in expected_callstack:
164 if e not in recorded_callstack:
165 raise Exception('Expected function name not found in recorded callstack')
166
167 sys.exit(0)
168
169 if __name__ == '__main__':
170 main()
This page took 0.034211 seconds and 5 git commands to generate.