3 # Copyright (C) 2017 Francis Deslauriers <francis.deslauriers@efficios.com>
5 # SPDX-License-Identifier: LGPL-2.1-only
13 def addr2line(executable
, addr
):
15 Uses binutils' addr2line to get function containing a given address
19 cmd
+= ['-e', executable
]
21 # Print function names
22 cmd
+= ['--functions']
24 # Expand inlined functions
25 cmd
+= ['--addresses', addr
]
27 status
= subprocess
.run(cmd
, stdout
=subprocess
.PIPE
, check
=True)
29 addr2line_output
= status
.stdout
.decode("utf-8").splitlines()
30 # addr2line's output is made of 3-tuples:
34 if len(addr2line_output
) % 3 != 0:
35 raise Exception('Unexpected addr2line output:\n\t{}'.format('\n\t'.join(addr2line_output
)))
38 for address_line_number
in range(0, len(addr2line_output
), 3):
39 function_name
= addr2line_output
[address_line_number
+ 1]
41 # Filter-out unresolved functions
42 if "??" not in function_name
:
43 function_names
.append(addr2line_output
[address_line_number
+ 1])
47 def extract_user_func_names(executable
, raw_callstack
):
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.
53 recorded_callstack
= set()
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
60 funcs
= addr2line(executable
, addr
)
61 recorded_callstack
.update(funcs
)
63 return recorded_callstack
65 def extract_kernel_func_names(raw_callstack
):
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.
73 recorded_callstack
= set()
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()
81 symbol
= line_tokens
[2]
82 addresses
.append(int(addr
, 16))
83 syms
.append({'addr':int(addr
, 16), 'symbol':symbol
})
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
)
91 # Remove commas and split on spaces
92 for addr
in raw_callstack
.replace(',', '').split(' '):
94 # Search the location of the address in the addresses list and
95 # deference this location in the syms list to add the associated
97 loc
= bisect
.bisect(addresses
, int(addr
, 16))
98 recorded_callstack
.add(syms
[loc
-1]['symbol'])
100 return recorded_callstack
102 # Regex capturing the callstack_user and callstack_kernel context
103 user_cs_rexp
='.*callstack_user\ \=\ \[(.*)\]\ .*\}, \{.*\}'
104 kernel_cs_rexp
='.*callstack_kernel\ \=\ \[(.*)\]\ .*\}, \{.*\}'
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
112 expected_callstack
= set()
113 recorded_callstack
= set()
116 if len(sys
.argv
) <= 2:
118 raise ValueError('USAGE: ./{} (--kernel|--user EXE) FUNC-NAMES'.format(sys
.argv
[0]))
120 # If the `--user` option is passed, save the next argument as the path
124 if sys
.argv
[argc
] in '--kernel':
125 rexp
= kernel_cs_rexp
127 elif sys
.argv
[argc
] in '--user':
131 executable
= sys
.argv
[argc
]
133 raise Exception('Unknown domain')
137 # Extract the function names that are expected to be found call stack of
139 for func
in sys
.argv
[argc
:]:
140 expected_callstack
.add(func
)
142 # Read the tested line for STDIN
144 for line
in sys
.stdin
:
148 # Extract the userspace callstack context of the event
149 m
= re
.match(rexp
, event_line
)
151 # If there is no match, exit with error
153 raise re
.error('Callstack not found in event line')
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
)
161 raise Exception('Unknown domain')
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')
170 if __name__
== '__main__':
This page took 0.039675 seconds and 4 git commands to generate.