Skip to content

Commit 51d3f42

Browse files
committed
[OpenMP][OMPD] GDB plugin code to leverage libompd to provide debugging
support for OpenMP programs. This is 5th of 6 patches started from https://reviews.llvm.org/D100181 This plugin code, when loaded in gdb, adds a few commands like ompd icv, ompd bt, ompd parallel. These commands create an interface for GDB to read the OpenMP runtime through libompd. Reviewed By: @dreachem Differential Revision: https://reviews.llvm.org/D100185
1 parent 00d1a1a commit 51d3f42

File tree

12 files changed

+5805
-0
lines changed

12 files changed

+5805
-0
lines changed

openmp/libompd/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,7 @@
1111
if(LIBOMP_OMPD_SUPPORT)
1212
set(OMPD_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/)
1313
add_subdirectory(src)
14+
if(NOT DISABLE_OMPD_GDB_PLUGIN)
15+
add_subdirectory(gdb-plugin)
16+
endif()
1417
endif()
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#
2+
#//===----------------------------------------------------------------------===//
3+
#//
4+
#// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
#// See https://llvm.org/LICENSE.txt for license information.
6+
#// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
#//
8+
#//===----------------------------------------------------------------------===//
9+
#
10+
11+
set (CMAKE_MODULE_PATH
12+
"${CMAKE_SOURCE_DIR}/libompd/"
13+
${CMAKE_MODULE_PATH}
14+
)
15+
16+
find_package (Python3 COMPONENTS Interpreter Development)
17+
18+
file(READ "/etc/os-release" OS_RELEASE)
19+
set(DIST "")
20+
string(REGEX MATCH "Debian|Ubuntu" DIST ${OS_RELEASE})
21+
22+
# UBUNTU and Debian package manager have its own patch in "pip" to avoid user
23+
# installed packages messing up with default paths.
24+
# https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1419695
25+
# Therfore, we have to use "--system" (specific to ubuntu and debian) when we
26+
# use system installed pip.(Not required if user installed pip on other paths.)
27+
# However, this has been taken care in pip for 20+ versions.
28+
# https://github.com/pypa/pip/commit/5f1468274987348b569aa586eeca4363494d0357
29+
30+
if(DIST)
31+
execute_process(COMMAND "${Python3_EXECUTABLE}"
32+
"-mpip"
33+
"--version"
34+
OUTPUT_VARIABLE PIP_VERSION_INFO
35+
RESULT_VARIABLE HAD_ERROR)
36+
string(REGEX REPLACE " " ";" PIP_VERSION_INFO "${PIP_VERSION_INFO}")
37+
list(GET PIP_VERSION_INFO 1 PIP_VERSION)
38+
set(PYSYSFLAG "")
39+
40+
if(PIP_VERSION VERSION_LESS "20.0.0")
41+
execute_process(COMMAND "${Python3_EXECUTABLE}"
42+
"-mpip"
43+
"install"
44+
"--help"
45+
OUTPUT_VARIABLE PIP_INSTALL_HELP
46+
RESULT_VARIABLE HAD_ERROR )
47+
string(REGEX MATCH "--system" SYSTEM_FLAG ${PIP_INSTALL_HELP})
48+
if (SYSTEM_FLAG)
49+
set(PYSYSFLAG "--system")
50+
endif()
51+
endif()
52+
endif()
53+
54+
include_directories (${OMPD_INCLUDE_PATH})
55+
include_directories (${LIBOMP_INCLUDE_DIR})
56+
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/python-module/ompd/__init__.py
57+
DEPENDS ompdModule.c ompdAPITests.c setup.py ompd/frame_filter.py ompd/__init__.py ompd/ompd_address_space.py ompd/ompd_callbacks.py ompd/ompd_handles.py ompd/ompd.py
58+
COMMAND ${CMAKE_COMMAND} -E env LIBOMP_INCLUDE_DIR=${LIBOMP_INCLUDE_DIR}
59+
${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/setup.py -v bdist_wheel -b ${CMAKE_CURRENT_BINARY_DIR}/build -d ${CMAKE_CURRENT_BINARY_DIR}
60+
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/setup.py clean --all
61+
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_CURRENT_SOURCE_DIR}/ompd.egg-info
62+
COMMAND ${Python3_EXECUTABLE} -m pip install ${PYSYSFLAG} -U -t ${CMAKE_CURRENT_BINARY_DIR}/python-module --no-index --find-links=${CMAKE_CURRENT_BINARY_DIR} ompd
63+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
64+
65+
add_custom_target(ompd_gdb_plugin ALL
66+
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/python-module/ompd/__init__.py
67+
COMMENT "Building the OMPD GDB plugin")
68+
69+
install(CODE "execute_process(COMMAND ${Python3_EXECUTABLE} -m pip install ${PYSYSFLAG} -U -t ${CMAKE_INSTALL_PREFIX}/share/gdb/python/gdb --no-index --find-links=${CMAKE_CURRENT_BINARY_DIR} ompd)")

openmp/libompd/gdb-plugin/README.txt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Instructions to use OpenMP specific debugging support for debugging C/C++ OpenMP programs through the gdb plugin are as follows:
2+
===============================================================================================================================
3+
4+
Include libompd.so directory to LD_LIBRARY_PATH
5+
$ export LD_LIBRARY_PATH=<installed_dir/lib/> or <build dir/libompd/src/> :$LD_LIBRARY_PATH
6+
7+
Set OMP_DEBUG to enabled
8+
$ export OMP_DEBUG=enabled
9+
10+
Compile the program to be debugged with '-g' and '-fopenmp' options as shown for a sample C source file xyz.c
11+
$ clang -g -fopenmp xyz.c -o xyz.out
12+
13+
NOTE:
14+
The program to be debugged needs to have a dynamic link dependency on 'libomp.so' for OpenMP-specific debugging to work correctly.
15+
The user can check this using ldd on the generated binary i.e. xyz.out
16+
17+
Debug the binary xyz.out by invoking gdb with the plugin as shown below. Please note that plugin '<..>/ompd/__init__.py' should be used
18+
19+
$ gdb -x <build_dir/libompd/gdb-plugin/python-module/ompd/__init__.py> or <installed_dir/share/gdb/python/gdb/ompd/__init__.py> ./xyz.out
20+
21+
- The gdb command 'help ompd' lists the subcommands available for OpenMP-specific debugging.
22+
- The command 'ompd init' needs to be run first to load the libompd.so available in the $LD_LIBRARY_PATH environment variable, and to initialize the OMPD library.
23+
- The 'ompd init' command starts the program run, and the program stops at a temporary breakpoint at the OpenMP internal location ompd_dll_locations_valid().
24+
- The user can 'continue' from the temporary breakpoint for further debugging.
25+
- The user may place breakpoints at the OpenMP internal locations 'ompd_bp_thread_begin' and 'ompd_bp_thread_end' to catch the OpenMP thread begin and thread end events.
26+
- Similarly, 'ompd_bp_task_begin' and 'ompd_bp_task_end' breakpoints may be used to catch the OpenMP task begin and task end events; 'ompd_bp_parallel_begin' and 'ompd_bp_parallel_end' to catch OpenMP parallel begin and parallel end events.
27+
28+
List of OMPD subcommands that can be used in GDB:
29+
- ompd init -- Finds and initializes the OMPD library; looks for the OMPD library libompd.so under $LD_LIBRARY_PATH, and if not found, under the directory in which the OMP library libomp.so is installed.
30+
- ompd icvs -- Displays the values of OpenMP Internal Control Variables.
31+
- ompd parallel -- Displays the details of the current and enclosing parallel regions.
32+
- ompd threads -- Provides information on threads of the current context.
33+
- ompd bt [off | on | on continued] -- Sets the filtering mode for "bt" output on or off, or to trace worker threads back to master threads. When ‘ompd bt on’ is used, the subsequent ‘bt’ command filters out the OpenMP runtime frames to a large extent, displaying only the user application frames. When ‘ompd bt on continued’ is used, the subsequent ‘bt’ command shows the user application frames for the current thread, and continues to trace the thread parents, up to the master thread.
34+
- ompd step -- Executes "step" command into user application frames, skipping OpenMP runtime frames as much as possible.
35+
36+
37+
NOTES:
38+
(1) Debugging code that runs on an offloading device is not supported yet.
39+
(2) The OMPD plugin requires an environment with Python version 3.5 or above. The gdb that is used with the OMPD plugin also needs to be based on Python version 3.5 or above.
40+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import sys
2+
import os.path
3+
import traceback
4+
5+
if __name__ == "__main__":
6+
try:
7+
sys.path.append(os.path.dirname(__file__))
8+
9+
import ompd
10+
ompd.main()
11+
print('OMPD GDB support loaded')
12+
print('Run \'ompd init\' to start debugging')
13+
except Exception as e:
14+
traceback.print_exc()
15+
print('Error: OMPD support could not be loaded', e)
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import gdb
2+
import ompdModule
3+
import itertools
4+
from gdb.FrameDecorator import FrameDecorator
5+
import ompd
6+
from ompd_handles import ompd_task, ompd_parallel, ompd_thread
7+
import traceback
8+
from tempfile import NamedTemporaryFile
9+
10+
11+
class OmpdFrameDecorator(FrameDecorator):
12+
13+
def __init__(self, fobj, curr_task_handle):
14+
"""Initializes a FrameDecorator with the given GDB Frame object. The global OMPD address space defined in
15+
ompd.py is set as well.
16+
"""
17+
super(OmpdFrameDecorator, self).__init__(fobj)
18+
self.addr_space = ompd.addr_space
19+
self.fobj = None
20+
if isinstance(fobj, gdb.Frame):
21+
self.fobj = fobj
22+
elif isinstance(fobj, FrameDecorator):
23+
self.fobj = fobj.inferior_frame()
24+
self.curr_task_handle = curr_task_handle
25+
26+
def function(self):
27+
"""This appends the name of a frame that is printed with the information whether the task started in the frame
28+
is implicit or explicit. The ICVs are evaluated to determine that.
29+
"""
30+
name = str(self.fobj.name())
31+
32+
if self.curr_task_handle is None:
33+
return name
34+
35+
icv_value = ompdModule.call_ompd_get_icv_from_scope(self.curr_task_handle, ompd.icv_map['implicit-task-var'][1], ompd.icv_map['implicit-task-var'][0])
36+
if icv_value == 0:
37+
name = '@thread %i: %s "#pragma omp task"' % (gdb.selected_thread().num, name)
38+
elif icv_value == 1:
39+
name = '@thread %i: %s "#pragma omp parallel"' % (gdb.selected_thread().num, name)
40+
else:
41+
name = '@thread %i: %s' % (gdb.selected_thread().num, name)
42+
return name
43+
44+
class OmpdFrameDecoratorThread(FrameDecorator):
45+
46+
def __init__(self, fobj):
47+
"""Initializes a FrameDecorator with the given GDB Frame object."""
48+
super(OmpdFrameDecoratorThread, self).__init__(fobj)
49+
if isinstance(fobj, gdb.Frame):
50+
self.fobj = fobj
51+
elif isinstance(fobj, FrameDecorator):
52+
self.fobj = fobj.inferior_frame()
53+
54+
def function(self):
55+
name = str(self.fobj.name())
56+
return '@thread %i: %s' % (gdb.selected_thread().num, name)
57+
58+
class FrameFilter():
59+
60+
def __init__(self, addr_space):
61+
"""Initializes the FrameFilter, registers is in the GDB runtime and saves the given OMPD address space capsule.
62+
"""
63+
self.addr_space = addr_space
64+
self.name = "Filter"
65+
self.priority = 100
66+
self.enabled = True
67+
gdb.frame_filters[self.name] = self
68+
self.switched_on = False
69+
self.continue_to_master = False
70+
71+
def set_switch(self, on_off):
72+
"""Prints output when executing 'ompd bt on' or 'ompd bt off'.
73+
"""
74+
self.switched_on = on_off
75+
if self.switched_on:
76+
print('Enabled filter for "bt" output successfully.')
77+
else:
78+
print('Disabled filter for "bt" output successfully.')
79+
80+
def set_switch_continue(self, on_off):
81+
"""Prints output when executing 'ompd bt on continued'."
82+
"""
83+
self.continue_to_master = on_off
84+
if self.continue_to_master:
85+
print('Enabled "bt" mode that continues backtrace on to master thread for worker threads.')
86+
else:
87+
print('Disabled "bt" mode that continues onto master thread.')
88+
89+
def get_master_frames_for_worker(self, past_thread_num, latest_sp):
90+
"""Prints master frames for worker thread with id past_thread_num.
91+
"""
92+
gdb.execute('t 1')
93+
gdb.execute('ompd bt on')
94+
gdb.execute('bt')
95+
96+
frame = gdb.newest_frame()
97+
98+
while frame.older() is not None:
99+
print('master frame sp:', str(frame.read_register('sp')))
100+
yield OmpdFrameDecorator(frame)
101+
frame = frame.older()
102+
print('latest sp:', str(latest_sp))
103+
104+
gdb.execute('ompd bt on continued')
105+
gdb.execute('t %d' % int(past_thread_num))
106+
107+
108+
def filter_frames(self, frame_iter):
109+
"""Iterates through frames and only returns those that are relevant to the application
110+
being debugged. The OmpdFrameDecorator is applied automatically.
111+
"""
112+
curr_thread_num = gdb.selected_thread().num
113+
is_no_omp_thread = False
114+
if curr_thread_num in self.addr_space.threads:
115+
curr_thread_obj = self.addr_space.threads[curr_thread_num]
116+
self.curr_task = curr_thread_obj.get_current_task()
117+
self.frames = self.curr_task.get_task_frame()
118+
else:
119+
is_no_omp_thread = True
120+
print('Thread %d is no OpenMP thread, printing all frames:' % curr_thread_num)
121+
122+
stop_iter = False
123+
for x in frame_iter:
124+
if is_no_omp_thread:
125+
yield OmpdFrameDecoratorThread(x)
126+
continue
127+
128+
if x.inferior_frame().older() is None:
129+
continue
130+
if self.curr_task.task_handle is None:
131+
continue
132+
133+
gdb_sp = int(str(x.inferior_frame().read_register('sp')), 16)
134+
gdb_sp_next_new = int(str(x.inferior_frame()).split(",")[0].split("=")[1], 16)
135+
if x.inferior_frame().older():
136+
gdb_sp_next = int(str(x.inferior_frame().older().read_register('sp')), 16)
137+
else:
138+
gdb_sp_next = int(str(x.inferior_frame().read_register('sp')), 16)
139+
while(1):
140+
(ompd_enter_frame, ompd_exit_frame) = self.frames
141+
142+
if (ompd_enter_frame != 0 and gdb_sp_next_new < ompd_enter_frame):
143+
break
144+
if (ompd_exit_frame != 0 and gdb_sp_next_new < ompd_exit_frame):
145+
if x.inferior_frame().older().older() and int(str(x.inferior_frame().older().older().read_register('sp')), 16) < ompd_exit_frame:
146+
if self.continue_to_master:
147+
yield OmpdFrameDecoratorThread(x)
148+
else:
149+
yield OmpdFrameDecorator(x, self.curr_task.task_handle)
150+
else:
151+
yield OmpdFrameDecorator(x, self.curr_task.task_handle)
152+
break
153+
sched_task_handle = self.curr_task.get_scheduling_task_handle()
154+
155+
if(sched_task_handle is None):
156+
stop_iter = True
157+
break
158+
159+
self.curr_task = self.curr_task.get_scheduling_task()
160+
self.frames = self.curr_task.get_task_frame()
161+
if stop_iter:
162+
break
163+
164+
# implementation of "ompd bt continued"
165+
if self.continue_to_master:
166+
167+
orig_thread = gdb.selected_thread().num
168+
gdb_threads = dict([(t.num, t) for t in gdb.selected_inferior().threads()])
169+
170+
# iterate through generating tasks until outermost task is reached
171+
while(1):
172+
# get OMPD thread id for master thread (systag in GDB output)
173+
try:
174+
master_num = self.curr_task.get_task_parallel().get_thread_in_parallel(0).get_thread_id()
175+
except:
176+
break
177+
# search for thread id without the "l" for long via "thread find" and get GDB thread num from output
178+
hex_str = str(hex(master_num))
179+
thread_output = gdb.execute('thread find %s' % hex_str[0:len(hex_str)-1], to_string=True).split(" ")
180+
if thread_output[0] == "No":
181+
raise ValueError('Master thread num could not be found!')
182+
gdb_master_num = int(thread_output[1])
183+
# get task that generated last task of worker thread
184+
try:
185+
self.curr_task = self.curr_task.get_task_parallel().get_task_in_parallel(0).get_generating_task()
186+
except:
187+
break;
188+
self.frames = self.curr_task.get_task_frame()
189+
(enter_frame, exit_frame) = self.frames
190+
if exit_frame == 0:
191+
print('outermost generating task was reached')
192+
break
193+
194+
# save GDB num for worker thread to change back to it later
195+
worker_thread = gdb.selected_thread().num
196+
197+
# use InferiorThread.switch()
198+
gdb_threads = dict([(t.num, t) for t in gdb.selected_inferior().threads()])
199+
gdb_threads[gdb_master_num].switch()
200+
print('#### switching to thread %i ####' % gdb_master_num)
201+
202+
frame = gdb.newest_frame()
203+
stop_iter = False
204+
205+
while(not stop_iter):
206+
if self.curr_task.task_handle is None:
207+
break
208+
self.frames = self.curr_task.get_task_frame()
209+
210+
while frame:
211+
if self.curr_task.task_handle is None:
212+
break
213+
214+
gdb_sp_next_new = int(str(frame).split(",")[0].split("=")[1], 16)
215+
216+
if frame.older():
217+
gdb_sp_next = int(str(frame.older().read_register('sp')), 16)
218+
else:
219+
gdb_sp_next = int(str(frame.read_register('sp')), 16)
220+
221+
while(1):
222+
(ompd_enter_frame, ompd_exit_frame) = self.frames
223+
224+
if (ompd_enter_frame != 0 and gdb_sp_next_new < ompd_enter_frame):
225+
break
226+
if (ompd_exit_frame == 0 or gdb_sp_next_new < ompd_exit_frame):
227+
if ompd_exit_frame == 0 or frame.older() and frame.older().older() and int(str(frame.older().older().read_register('sp')), 16) < ompd_exit_frame:
228+
yield OmpdFrameDecoratorThread(frame)
229+
else:
230+
yield OmpdFrameDecorator(frame, self.curr_task.task_handle)
231+
break
232+
sched_task_handle = ompdModule.call_ompd_get_scheduling_task_handle(self.curr_task.task_handle)
233+
234+
if(sched_task_handle is None):
235+
stop_iter = True
236+
break
237+
self.curr_task = self.curr_task.get_generating_task()
238+
self.frames = self.curr_task.get_task_frame()
239+
240+
frame = frame.older()
241+
break
242+
243+
gdb_threads[worker_thread].switch()
244+
245+
gdb_threads[orig_thread].switch()
246+
247+
248+
def filter(self, frame_iter):
249+
"""Function is called automatically with every 'bt' executed. If switched on, this will only let revelant frames be printed
250+
or all frames otherwise. If switched on, a FrameDecorator will be applied to state whether '.ompd_task_entry.' refers to an
251+
explicit or implicit task.
252+
"""
253+
if self.switched_on:
254+
return self.filter_frames(frame_iter)
255+
else:
256+
return frame_iter

0 commit comments

Comments
 (0)