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