Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions doc/src/stat_environment_variables.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -202,5 +202,24 @@ Several environment variables influence STAT and its dependent packages. Note t
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>STAT_INTELGT_MAX_THREADS=<replaceable class="parameter">number</replaceable></term>
<listitem>
<para>
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.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>STAT_INTELGT_EXPR_FILTER=<replaceable class="parameter">expression</replaceable></term>
<listitem>
<para>
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.
</para>
<para>
<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.
</para>
</listitem>
</varlistentry>
</variablelist>

Binary file modified doc/userguide/stat_userguide.pdf
Binary file not shown.
92 changes: 91 additions & 1 deletion scripts/oneapi_gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,109 @@ def get_thread_list(self):
Gets the list of threads in the target process. For
Intel(R) Distribution for GDB* this function extracts active
threads ID list.

NOTE: STAT_INTELGT_EXPR_FILTER is validated for few security checks
before it is evaluated by GDB.
"""
filter_expr_str = os.environ.get("STAT_INTELGT_EXPR_FILTER")

cmd = (
'thread apply all -q -c printf "%d.%d\\n", '
'$_inferior, $_thread'
)

# Thread apply commmand may end up sending commands like "shell rm *",
# so this list ensure to not allow these system commands.
danger_list = ['shell', 'call', 'python', 'source', 'dump', 'restore']

if filter_expr_str and filter_expr_str.strip():
filter_expr_str = filter_expr_str.strip()

# Check for unsecure characters and also if it is a valid input
# for GDB.
# Each line may be treated as separate command in GDB and
# fail the expression evaluation.
if '\n' in filter_expr_str or '\r' in filter_expr_str:
logging.error('STAT_INTELGT_EXPR_FILTER contains newline')
logging.error(f'Invalid expression: "{repr(filter_expr_str)}"')
# Check for dangerous GDB commands that could execute arbitrary code.
elif any(keyword in filter_expr_str.lower()
for keyword in danger_list):
logging.error(f'STAT_INTELGT_EXPR_FILTER should not use: '
f'{danger_list}')
logging.error(f'Invalid expression: "{filter_expr_str}"')
else:
# Check if the expression is valid by evaluating it with GDB.
validate_cmd = f'print {filter_expr_str}'
logging.debug(f'Validating filter expression: {validate_cmd}')
validation_result = self.communicate(validate_cmd)

logging.debug(f'GDB validation result: {validation_result}')
is_valid = False

if validation_result:
for line in validation_result:
line_str = line.strip()

# GDB successfully evaluated the expression.
if line_str.startswith('$') and '=' in line_str:
is_valid = True
logging.info(f'Using expression filter: '
f'"{filter_expr_str}"')
break

if re.search(r"(No symbol.*in current context) \
|(cannot subscript something of type)",
line_str, re.IGNORECASE):
is_valid = True
logging.info(f'Using expression filter '
f'"{filter_expr_str}"')
break

if not is_valid:
logging.warning(f'STAT_INTELGT_EXPR_FILTER expression has'
f'syntax error: "{filter_expr_str}"')
logging.warning(f'GDB returned: {validation_result}')
else:
cmd = (
f'thread apply all -q -s '
f'eval "printf %s, $_inferior, $_thread", '
f'({filter_expr_str} ? "\\\"%d.%d\\n\\\"" : "\\\"\\\"")'
)

# Require only in case of cray-stat.
try:
self.flushInput()
except AttributeError:
# Function is only available in cray-stat.
pass

logging.info(f'gdb-oneapi: {cmd}')

tids = self.communicate(cmd)
if not tids:
if filter_expr_str:
logging.warning(f'Expression filter "{filter_expr_str}" '
'resulted in zero threads')
return []

logging.debug(f'{tids}')
max_threads = os.environ.get("STAT_INTELGT_MAX_THREADS")

if max_threads and max_threads.strip():
try:
int_max_threads = int(max_threads)
if int_max_threads <= 0:
logging.warning(f'STAT_INTELGT_MAX_THREADS value {max_threads}'
'must be positive, ignoring')
else:
tids = tids[:int_max_threads]
logging.info(f'Limiting to first {int_max_threads} '
f'threads for callstack capture')
except ValueError:
logging.warning(f'STAT_INTELGT_MAX_THREADS value "{max_threads}" '
'is invalid, must be a positive integer')

logging.debug(f'Filtered thread IDs list: {tids}')

return tids

Expand Down