Skip to content

Commit afca1c6

Browse files
committed
Add thread filtering support for gdb-oneapi callstack capture.
Add two new environment variables to control thread selection during callstack capture: - STAT_GDB_MAX_THREADS: Limit capture to first N threads - STAT_INTELGT_EXPR_FILTER: Filter threads using GDB expression STAT_INTELGT_MAX_THREADS=100 will capture the first 100 threads. STAT_INTELGT_EXPR_FILTER allows selective thread capture using GDB expressions (e.g., "\$_thread <= 50" to capture only threads 1-50). Updated documentation to describe both environment variables with usage examples. The user guide pdf file is also regenerated accordingly. Signed-off-by: Abdul Basit Ijaz <abdul.b.ijaz@intel.com>
1 parent d4913e2 commit afca1c6

File tree

3 files changed

+110
-1
lines changed

3 files changed

+110
-1
lines changed

doc/src/stat_environment_variables.sgml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,24 @@ Several environment variables influence STAT and its dependent packages. Note t
202202
</para>
203203
</listitem>
204204
</varlistentry>
205+
<varlistentry>
206+
<term>STAT_INTELGT_MAX_THREADS=<replaceable class="parameter">number</replaceable></term>
207+
<listitem>
208+
<para>
209+
Set to a positive integer to limit the number of threads used for call-stack capture. STAT will capture stack traces from only the first N threads. For example, <literal>STAT_INTELGT_MAX_THREADS=100</literal> will capture the first 100 threads. If the value is not a positive integer, this setting is ignored and all threads are captured.
210+
</para>
211+
</listitem>
212+
</varlistentry>
213+
<varlistentry>
214+
<term>STAT_INTELGT_EXPR_FILTER=<replaceable class="parameter">expression</replaceable></term>
215+
<listitem>
216+
<para>
217+
Set to a GDB expression to selectively filter threads for call-stack capture. Only threads where the expression evaluates to true (non-zero) will have their stack traces captured. The new line characters and system commands keywords like <literal>shell, call, python, source, dump, restore</literal> are not allowed. The expression is evaluated in the context of each thread using GDB's thread apply command. For example, <literal>STAT_INTELGT_EXPR_FILTER="\$_thread &lt;= 50"</literal> will capture only threads with ID 50 or lower. The expression must be a valid GDB expression that returns a boolean value.
218+
</para>
219+
<para>
220+
<emphasis role="bold">Security Note:</emphasis> This expression is directly evaluated by GDB. For security, the expression is validated to reject dangerous GDB commands such as <literal>shell, call, python</literal> and multi-line input. Only use this feature in trusted environments where environment variables are controlled.
221+
</para>
222+
</listitem>
223+
</varlistentry>
205224
</variablelist>
206225

doc/userguide/stat_userguide.pdf

-3.22 KB
Binary file not shown.

scripts/oneapi_gdb.py

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,109 @@ def get_thread_list(self):
102102
Gets the list of threads in the target process. For
103103
Intel(R) Distribution for GDB* this function extracts active
104104
threads ID list.
105+
106+
NOTE: STAT_INTELGT_EXPR_FILTER is validated for few security checks
107+
before it is evaluated by GDB.
105108
"""
109+
filter_expr_str = os.environ.get("STAT_INTELGT_EXPR_FILTER")
110+
106111
cmd = (
107112
'thread apply all -q -c printf "%d.%d\\n", '
108113
'$_inferior, $_thread'
109114
)
110115

116+
# Thread apply commmand may end up sending commands like "shell rm *",
117+
# so this list ensure to not allow these system commands.
118+
danger_list = ['shell', 'call', 'python', 'source', 'dump', 'restore']
119+
120+
if filter_expr_str and filter_expr_str.strip():
121+
filter_expr_str = filter_expr_str.strip()
122+
123+
# Check for unsecure characters and also if it is a valid input
124+
# for GDB.
125+
# Each line may be treated as separate command in GDB and
126+
# fail the expression evaluation.
127+
if '\n' in filter_expr_str or '\r' in filter_expr_str:
128+
logging.error('STAT_INTELGT_EXPR_FILTER contains newline')
129+
logging.error(f'Invalid expression: "{repr(filter_expr_str)}"')
130+
# Check for dangerous GDB commands that could execute arbitrary code.
131+
elif any(keyword in filter_expr_str.lower()
132+
for keyword in danger_list):
133+
logging.error(f'STAT_INTELGT_EXPR_FILTER should not use: '
134+
f'{danger_list}')
135+
logging.error(f'Invalid expression: "{filter_expr_str}"')
136+
else:
137+
# Check if the expression is valid by evaluating it with GDB.
138+
validate_cmd = f'print {filter_expr_str}'
139+
logging.debug(f'Validating filter expression: {validate_cmd}')
140+
validation_result = self.communicate(validate_cmd)
141+
142+
logging.debug(f'GDB validation result: {validation_result}')
143+
is_valid = False
144+
145+
if validation_result:
146+
for line in validation_result:
147+
line_str = line.strip()
148+
149+
# GDB successfully evaluated the expression.
150+
if line_str.startswith('$') and '=' in line_str:
151+
is_valid = True
152+
logging.info(f'Using expression filter: '
153+
f'"{filter_expr_str}"')
154+
break
155+
156+
if re.search(r"(No symbol.*in current context) \
157+
|(cannot subscript something of type)",
158+
line_str, re.IGNORECASE):
159+
is_valid = True
160+
logging.info(f'Using expression filter '
161+
f'"{filter_expr_str}"')
162+
break
163+
164+
if not is_valid:
165+
logging.warning(f'STAT_INTELGT_EXPR_FILTER expression has'
166+
f'syntax error: "{filter_expr_str}"')
167+
logging.warning(f'GDB returned: {validation_result}')
168+
else:
169+
cmd = (
170+
f'thread apply all -q -s '
171+
f'eval "printf %s, $_inferior, $_thread", '
172+
f'({filter_expr_str} ? "\\\"%d.%d\\n\\\"" : "\\\"\\\"")'
173+
)
174+
175+
# Require only in case of cray-stat.
176+
try:
177+
self.flushInput()
178+
except AttributeError:
179+
# Function is only available in cray-stat.
180+
pass
181+
111182
logging.info(f'gdb-oneapi: {cmd}')
112183

113184
tids = self.communicate(cmd)
114185
if not tids:
186+
if filter_expr_str:
187+
logging.warning(f'Expression filter "{filter_expr_str}" '
188+
'resulted in zero threads')
115189
return []
116190

117-
logging.debug(f'{tids}')
191+
max_threads = os.environ.get("STAT_INTELGT_MAX_THREADS")
192+
193+
if max_threads and max_threads.strip():
194+
try:
195+
int_max_threads = int(max_threads)
196+
if int_max_threads <= 0:
197+
logging.warning(f'STAT_INTELGT_MAX_THREADS value {max_threads}'
198+
'must be positive, ignoring')
199+
else:
200+
tids = tids[:int_max_threads]
201+
logging.info(f'Limiting to first {int_max_threads} '
202+
f'threads for callstack capture')
203+
except ValueError:
204+
logging.warning(f'STAT_INTELGT_MAX_THREADS value "{max_threads}" '
205+
'is invalid, must be a positive integer')
206+
207+
logging.debug(f'Filtered thread IDs list: {tids}')
118208

119209
return tids
120210

0 commit comments

Comments
 (0)