Skip to content

Commit

Permalink
Add support for gdb-oneapi (#43)
Browse files Browse the repository at this point in the history
* Generic core_file_merger and gdb fixes

Several parts of the core_file_merger script had to
be tweaked to make it run on Ubuntu 20.04 + python3.8
without any fatal errors. Unclear if it affects other
debugger backends - seems so.

In the gdb script on resume function, after sending
continue command buffer is flushed to prevent deadlock.
For serial attach scenario deadlock was inevitable on
Ubuntu 20.04 + python3.8.

Co-authored-by: Oguzhan Karakaya <oguzhan.karakaya@intel.com>

* gdb-oneapi support in STAT

Corefile merger and generic gdb_oneapi driver is implemented.
Frame parser is pretified and function names are abbreviated
programmatically. Script "oneapi_gdb_test.py" is provided for
testing regex parser methotds implemented for the driver.

System test is done using stat serial attach mechanism.
After setting STAT_GDB environment variable as gdb_oneapi,
stat-cl -G -w -t 5 -I <PIDs to attach> can be run to generate
bt graph. Additionally, STATmain merge --oneapi -c /path/to/core
can be run to generate bt graph from core file.

Co-authored-by: Oguzhan Karakaya <oguzhan.karakaya@intel.com>
  • Loading branch information
moguzhankarakaya and moguzhankarakaya authored Jul 20, 2023
1 parent 19858dc commit a66f90e
Show file tree
Hide file tree
Showing 6 changed files with 487 additions and 5 deletions.
2 changes: 1 addition & 1 deletion scripts/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ dist_bin_SCRIPTS = run_one_sec
bin_SCRIPTS = STAT stat-cl stat-script stat-core-merger attach-helper
python_PYTHON = STATmain.py stat_merge_base.py STATmerge.py core_file_merger.py bg_core_backtrace.py attach_helper.py STAThelper.py
if ENABLE_GDB
python_PYTHON += stat_cuda_gdb.py cuda_gdb.py gdb.py roc_gdb.py
python_PYTHON += stat_cuda_gdb.py cuda_gdb.py gdb.py roc_gdb.py oneapi_gdb.py
endif
if ENABLE_GUI
python_PYTHON += STATGUI.py STATview.py
Expand Down
39 changes: 36 additions & 3 deletions scripts/core_file_merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
interrupt
"""

__copyright__ = """Copyright (c) 2007-2020, Lawrence Livermore National Security, LLC."""
__copyright__ = """Copyright (c) 2007-2022, Lawrence Livermore National Security, LLC."""
__license__ = """Produced at the Lawrence Livermore National Laboratory
Written by Dane Gardner, Gregory Lee <lee218@llnl.gov>, Dorian Arnold, Matthew LeGendre, Dong Ahn, Bronis de Supinski, Barton Miller, Martin Schulz, Niklas Nielson, Nicklas Bo Jensen, Jesper Nielson, and Sven Karlsson.
LLNL-CODE-750488.
Expand Down Expand Up @@ -169,6 +169,36 @@ def open(self, corefile, executable = None):
self.subprocess = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
return self.readlines()

class GdbOneapi(Gdb):

def __init__ (self, options):
Gdb.__init__(self, options)
self.executable = "GPU"

def open(self, corefile, executable = None):
args = []
args.append('gdb-oneapi')
args.append('-ex')
args.append("set pagination 0")
args.append('-ex')
args.append("cd %s" %(self.directory))
args.append('-ex')
args.append("path %s" %(self.objectpath))
args.append('-ex')
args.append("directory %s" %(self.sourcepath))
args.append('-ex')
args.append("set filename-display absolute")
args.append('-ex')
target_string = "target core "
self.corefile = corefile
if os.path.isabs(self.corefile):
target_string += " %s" % (self.corefile)
else:
target_string += " %s/%s" % (self.coredir, self.corefile)
args.append(target_string)
self.subprocess = subprocess.Popen(args, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
return self.readlines()


###############################################################################
class CoreFile:
Expand Down Expand Up @@ -353,6 +383,8 @@ def process_core(self):
logging.info("Connecting gdb to the core file (%s)"%self.coreData['coreFile'])
if CoreFile.__options['cuda'] == 1:
gdb = CudaGdb(CoreFile.__options)
elif CoreFile.__options['oneapi'] == 1:
gdb = GdbOneapi(CoreFile.__options)
else:
gdb = Gdb(CoreFile.__options)
executable = gdb.executable
Expand Down Expand Up @@ -468,7 +500,7 @@ def process_core(self):
in_thread,functions = True,[]

#In some cases, gdb will quit the stack trace early
elif 'Backtrace stopped: ' in line:
elif 'Backtrace stopped: ' in line and CoreFile.__options['oneapi'] != 1:
logging.critical("GDB: Backtrace stopped")
if not force:
sys.exit(2)
Expand Down Expand Up @@ -513,6 +545,7 @@ def __init__(self):
self.arg_map["force"] = StatMergerArgs.StatMergerArgElement("r", False, int, 0, "whether to force parsing on warnings and errors")
self.arg_map["threads"] = StatMergerArgs.StatMergerArgElement("T", False, int, 1, "max number of threads")
self.arg_map["cuda"] = StatMergerArgs.StatMergerArgElement("C", False, int, 0, "set if running on cuda cores")
self.arg_map["oneapi"] = StatMergerArgs.StatMergerArgElement("n", False, int, 0, "set if using gdb-oneapi")

self.arg_map["jobid"] = self.StatMergerArgElement("j", False, None, None, "[LW] delineate traces based on Job ID in the core file")
self.arg_map["exe"] = StatMergerArgs.StatMergerArgElement("x", True, str, "NULL", "[LW] the executable path")
Expand Down Expand Up @@ -639,7 +672,7 @@ def STATmerge_main(arg_list):
if os.stat(filename).st_size == 0:
empty_files.append(filename)
else:
p = subprocess.Popen(['file', filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf8')
p = subprocess.Popen(['file', filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf8', universal_newlines=True)
output, error = p.communicate()
if output.find('core file') != -1:
file_types['full'] = True
Expand Down
1 change: 1 addition & 0 deletions scripts/gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ def resume(self):
logging.info('GDB resume PID %d' %(self.pid))
command = "continue\n"
ret = self.subprocess.stdin.write(command)
self.subprocess.stdin.flush()
lines = self.readlines('Continuing')
logging.debug('%s' %(repr(lines)))
return check_lines(lines)
Expand Down
226 changes: 226 additions & 0 deletions scripts/oneapi_gdb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
#!/bin/env python

"""@package STATview
Visualizes dot graphs outputted by STAT."""

__copyright__ = """Modifications Copyright (C) 2022 Intel Corporation
SPDX-License-Identifier: BSD-3-Clause"""
__license__ = """Produced by Intel Corporation for Lawrence Livermore National Security, LLC.
Written by M. Oguzhan Karakaya oguzhan.karakaya@intel.com
LLNL-CODE-750488.
All rights reserved.
This file is part of STAT. For details, see http://www.github.com/LLNL/STAT. Please also read STAT/LICENSE.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the disclaimer below.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the disclaimer (as noted below) in the documentation and/or other materials provided with the distribution.
Neither the name of the LLNS/LLNL nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL LAWRENCE LIVERMORE NATIONAL SECURITY, LLC, THE U.S. DEPARTMENT OF ENERGY OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
__author__ = ["M. Oguzhan Karakaya <oguzhan.karakaya@intel.com>"]
__version_major__ = 4
__version_minor__ = 1
__version_revision__ = 0
__version__ = "%d.%d.%d" %(__version_major__, __version_minor__, __version_revision__)

import sys
import os
import logging
import re
from gdb import GdbDriver, check_lines

def expand_simd_spec(simd):
"""
Converts a merged simd specifier into a list of individual simd lanes.
`simd` string is expected to be either an individual lane number
or a comma-separated range list inside square brackets.
"""
if simd[0] == '[':
simd = simd[1:]
lanes = []
for item1 in simd.split(" "):
for item2 in item1.split(","):
if item2.isnumeric():
lanes += item2
else:
pair = item2.split("-")
for num in range(int(pair[0]), int(pair[1])+1):
lanes += str(num)
return lanes

def parse_thread_info_line(line, parse_simd_lanes = False):
"""
Extracts thread IDs from a `thread info` output produced by
Intel(R) Distribution for GDB*. See oneapi_gdb_test.py for
sample inputs and outputs.
"""
tid_single_simd = re.compile(
r"^[\s|*]*(\d+(\.?\d+)?)(?:(?::(\d+))?)\s*(?:Thread|LWP)")
tid_multiple_simd = re.compile(
r"^[\s|*]*(\d+(\.?\d+)?)(?:(?::\[(.*?)\])?)\s*(?:Thread|LWP)")
match = re.search(tid_multiple_simd, line)
if not match:
return []
if not parse_simd_lanes:
return [ match[1] ]
if not match[3]:
match = re.search(tid_single_simd, line)
if not match[3]:
return [ match[1] ]
return [ match[1] + ":" + match[3]]
return [ match[1] + ":" + x for x in expand_simd_spec(match[3]) ]

def clean_cpp_template_brackets_and_call_signature(string):
"""
Prettify function name output by Intel(R) Distribution for GDB*.
Long template parameters and function signatures are collapsed.
"""
sub_strings = []
(found, begin, end, counter) = (False, 0, 0, 0)
for idx, char in enumerate(string):
if char == "<" and not found:
found = True
begin = idx
counter = 1
elif char == "<" and found:
counter += 1
elif char == ">" and found and counter > 1:
counter -= 1
elif char == ">" and found and counter == 1:
end = idx
counter -= 1
found = False
sub_strings.append(string[begin:end+1:])
# Removing template variables from the function name
for sub_string in sub_strings:
string = string.replace(sub_string, "<...>")
# Removing call signature for the function name
ptrn = re.compile(r"\([^()]+\)(?=[^()]*$)")
match = ptrn.findall(string)
if match:
string = string.replace(match[-1], "(...)")

return string

def parse_frameinfo_from_backtrace(string):
"""
Takes the gdb backtrace output and call
clean_cpp_template_brackets_and_call_signature function over
extracted function names. See oneapi_gdb_test.py for
sample inputs and outputs.
"""
function = 'Unknown'
source = '??'
linenum = 0
error = True

frame_fromat = re.compile(
r"^#\d+\s+(?:0x\S+)*(?: in )*(.*)\s\(.*\)\s(?:at|from)\s([^:]+)(?::)?(\d+)?$")
match = frame_fromat.match(string)
if match:
error = False
function, source, linenum = match.groups()
if linenum is None:
linenum = 0
else:
linenum = int(linenum)
function = clean_cpp_template_brackets_and_call_signature(function)
return {'function':function,
'source':source,
'linenum':linenum,
'error':error}

class OneAPIGdbDriver(GdbDriver):
""" A class to drive Intel(R) Distribution for GDB* """

gdb_command = 'gdb-oneapi'
parse_simd_lanes = False
if 'STAT_COLLECT_SIMD_BT' in os.environ and \
os.environ['STAT_COLLECT_SIMD_BT'] == "1":
parse_simd_lanes = True

def get_thread_list(self):
"""
Gets the list of threads in the target process. For
Intel(R) Distribution for GDB* this function extracts SIMD
lane information as part of a thread ID.
It is to improve the resulting representation by adding the
information on number of active SIMD lanes along with stack
trace information.
"""
logging.info('gdb-oneapi: info threads')
tids = []
lines = self.communicate("info threads")
logging.debug('%s', repr(lines))
for line in lines:
if "inactive" in line:
continue
tids += parse_thread_info_line(line, self.parse_simd_lanes)
return tids

def bt(self, thread_id):
"""
Gets a backtrace from the requested thread id.
returns list of frames, where each frame is a map of attributes.
"""
logging.info('GDB thread bt ID %s' %(thread_id))
ret = []

lines = self.communicate("thread apply %s bt" %thread_id)

lines = lines[2:]
for line_num, line in enumerate(lines):
if line[0] != '#':
continue
logging.debug('Parsing line #%d: %d', line_num, line)
ret.append(parse_frameinfo_from_backtrace(line))
return ret

def attach(self):
"""
Attaches to the target process and checkes if there is
an error related to gdbserver-gt at the gdb console output.
"""
logging.info('GDB attach to PID %d' %(self.pid))
lines = self.communicate("attach %d" %self.pid)
# We need to check if there is an error with starting
# gdbserver-gt and inform the user for possible missing
# device backtrace features.
ptrn = re.compile(
r"^intelgt\:\s*(\S*)\s*failed to start\.")
for line in lines:
match = ptrn.match(line)
if match:
gdbserver_name = match.groups()[0]
logging.error("""
OneAPI %s initialization failed for process %d. Device backtrace will not be available.""",
gdbserver_name, self.pid)
logging.debug('%s', repr(lines))
return check_lines(lines)

def target_core(self, path):
"""
Intel(R) Distribution for GDB* targets a core dump
file given by path.
"""
logging.info('GDB open core %s' %(path))
self.communicate("target core %s" %path)

if __name__ == "__main__":
gdb = OneAPIGdbDriver(0, 'debug', 'log.txt')
if gdb.launch() is False:
sys.stderr.write('gdb launch failed\n')
sys.exit(1)
gdb.target_core(sys.argv[1])
thread_ids = gdb.get_thread_list()
thread_ids.sort()
for tid in thread_ids:
bt = gdb.bt(tid)
print("Thread", tid)
for i, frame in enumerate(bt):
print("%d) %s:%s" %(i, frame['source'], frame['linenum']))
print()
gdb.close()
Loading

0 comments on commit a66f90e

Please sign in to comment.