docs: Add supported versions and fix-backport policy
[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
6a871bbe 13
591ee332
FD
14def addr2line(executable, addr):
15 """
6a871bbe 16 Uses binutils' addr2line to get function containing a given address
591ee332 17 """
6a871bbe 18 cmd = ["addr2line"]
591ee332 19
6a871bbe 20 cmd += ["-e", executable]
591ee332
FD
21
22 # Print function names
6a871bbe 23 cmd += ["--functions"]
591ee332
FD
24
25 # Expand inlined functions
6a871bbe 26 cmd += ["--addresses", addr]
591ee332 27
40b2a4a7
OD
28 status = subprocess.run(cmd, stdout=subprocess.PIPE, check=True)
29
9613cdd0
JG
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:
6a871bbe
KS
36 raise Exception(
37 "Unexpected addr2line output:\n\t{}".format("\n\t".join(addr2line_output))
38 )
9613cdd0
JG
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
591ee332 49
6a871bbe 50
591ee332
FD
51def extract_user_func_names(executable, raw_callstack):
52 """
6a871bbe
KS
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.
591ee332
FD
56 """
57 recorded_callstack = set()
58
59 # Remove commas and split on spaces
6a871bbe 60 for index, addr in enumerate(raw_callstack.replace(",", "").split(" ")):
591ee332
FD
61 # Consider only the elements starting with '0x' which are the
62 # addresses recorded in the callstack
6a871bbe 63 if "0x" in addr[:2]:
591ee332
FD
64 funcs = addr2line(executable, addr)
65 recorded_callstack.update(funcs)
66
67 return recorded_callstack
68
6a871bbe 69
591ee332
FD
70def extract_kernel_func_names(raw_callstack):
71 """
6a871bbe
KS
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.
591ee332
FD
77 """
78 recorded_callstack = set()
6a871bbe
KS
79 syms = []
80 addresses = []
591ee332 81 # We read kallsyms file and save the output
6a871bbe 82 with open("/proc/kallsyms") as kallsyms_f:
591ee332
FD
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))
6a871bbe 88 syms.append({"addr": int(addr, 16), "symbol": symbol})
591ee332
FD
89
90 # Save the address and symbol in a sorted list of tupple
6a871bbe 91 syms = sorted(syms, key=lambda k: k["addr"])
591ee332
FD
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
6a871bbe
KS
97 for addr in raw_callstack.replace(",", "").split(" "):
98 if "0x" in addr[:2]:
591ee332
FD
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))
6a871bbe 103 recorded_callstack.add(syms[loc - 1]["symbol"])
591ee332
FD
104
105 return recorded_callstack
106
6a871bbe 107
591ee332 108# Regex capturing the callstack_user and callstack_kernel context
6a871bbe
KS
109user_cs_rexp = ".*callstack_user\ \=\ \[(.*)\]\ .*\}, \{.*\}"
110kernel_cs_rexp = ".*callstack_kernel\ \=\ \[(.*)\]\ .*\}, \{.*\}"
111
591ee332
FD
112
113def main():
114 """
6a871bbe
KS
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.
591ee332
FD
118 """
119 expected_callstack = set()
120 recorded_callstack = set()
6a871bbe 121 cs_type = None
591ee332
FD
122
123 if len(sys.argv) <= 2:
124 print(sys.argv)
6a871bbe
KS
125 raise ValueError(
126 "USAGE: ./{} (--kernel|--user EXE) FUNC-NAMES".format(sys.argv[0])
127 )
591ee332
FD
128
129 # If the `--user` option is passed, save the next argument as the path
130 # to the executable
6a871bbe
KS
131 argc = 1
132 executable = None
133 if sys.argv[argc] in "--kernel":
591ee332 134 rexp = kernel_cs_rexp
6a871bbe
KS
135 cs_type = "kernel"
136 elif sys.argv[argc] in "--user":
591ee332 137 rexp = user_cs_rexp
6a871bbe
KS
138 cs_type = "user"
139 argc += 1
591ee332
FD
140 executable = sys.argv[argc]
141 else:
6a871bbe 142 raise Exception("Unknown domain")
591ee332 143
6a871bbe 144 argc += 1
591ee332
FD
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:
6a871bbe 162 raise re.error("Callstack not found in event line")
591ee332
FD
163 else:
164 raw_callstack = str(m.group(1))
6a871bbe
KS
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)
591ee332 169 else:
6a871bbe 170 raise Exception("Unknown domain")
591ee332
FD
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:
6a871bbe 175 raise Exception("Expected function name not found in recorded callstack")
591ee332
FD
176
177 sys.exit(0)
178
6a871bbe
KS
179
180if __name__ == "__main__":
591ee332 181 main()
This page took 0.055217 seconds and 5 git commands to generate.