Skip to content

Commit 891fff6

Browse files
tbirdsodzenanz
authored andcommitted
DOC: Add section on debugging Python wrappings
1 parent c4d9284 commit 891fff6

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed

SoftwareGuide/Latex/DevelopmentGuidelines/CreateAModule.tex

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ \subsection{CMakeLists.txt}
472472
473473
474474
\subsection{Class wrap files}
475+
\label{subsec:ClassWrapFiles}
475476
476477
Wrapping specification for classes is written in the module's \code{*.wrap}
477478
CMake script files. These files call wrapping CMake macros, and they specify
@@ -929,6 +930,300 @@ \subsubsection{Wrapping Tests}
929930
\end{minted}
930931
931932
933+
\subsection{Debugging Strategies}
934+
\label{subsec:DebuggingStrategies}
935+
936+
ITK wrappings allow users to make use of ITK classes in other languages for various
937+
purposes, such as relying on ITK Python wrappings to sidestep C++ compilation steps
938+
for rapid prototyping. However, this often introduces additional complexity in the
939+
process of identifying and localizing issues in C++ classes or even in the wrapping
940+
process itself. Fortunately, language-specific tools are available to assist in
941+
the debugging process. In this section we focus on strategies for investigating
942+
ITK Python code generated with SWIG.
943+
944+
\subsubsection{Swig Python Architecture}
945+
\label{subsubsec:SwigArchitecture}
946+
947+
ITK 5.x uses SWIG (Simplified Wrapper and Interface Generator) to distill ITK C++
948+
classes into Python modules. This largely takes place in four distinct stages:
949+
950+
\begin{itemize}
951+
952+
\item \code{.wrap} CMake files, included in the ITK source tree in the \code{Wrapping}
953+
folder for each module to define class template instantiations to be wrapped.
954+
These are discussed in Section~\ref{subsec:ClassWrapFiles}.
955+
956+
\item SWIG \code{.cpp} source files generated at compile time at \code{Wrapping/Modules}
957+
under the ITK build tree. These C++ files explicitly implement the class and template
958+
instantiations defined in the class \code{.wrap} files in the source tree.
959+
Debug symbols will be generated for these files.
960+
961+
\item SWIG compiled code. For Python wrappings these are generated as Python
962+
\code{.pyd} (Windows) or \code{.so} (Linux or macOS) binaries and Python \code{.py} modules at
963+
\code{Wrapping/Generators/Python/itk} in the ITK build tree.
964+
965+
\item Additional Python configuration files are generated in the
966+
\code{Wrapping/Generators/Python} directory and its subdirectories.
967+
\code{WrapITK.pth} provides the path for a Python environment to find the ITK
968+
module, while \code{\_\_init\_\_.py} allows the module under development to be loaded correctly at
969+
runtime. \code{<module\_name>Config.py} defines module dependencies and class
970+
template definitions, while \code{<module\_name>\_snake\_case.py} maps
971+
C++-style filter pipeline executions to Pythonic snake case functions.
972+
973+
\end{itemize}
974+
975+
ITK Python pre-compiled wheels may be obtained from the PyPI package index and contain
976+
pre-compiled binaries without debugging symbols. To debug the native binaries a local build
977+
must be created as in Section~\ref{sec:UsingCMakeForConfiguringAndBuildingITK} with a
978+
\code{Debug} or \code{RelWithDebInfo} CMake build configuration as described below.
979+
980+
\begin{minted}[baselinestretch=1,fontsize=\footnotesize,linenos=false,bgcolor=ltgray]{bash}
981+
python -m pip install itk
982+
\end{minted}
983+
984+
985+
\subsubsection{Python Runtime Tracing}
986+
\label{subsubsec:PythonRuntimeTracing}
987+
988+
ITK Python contains glue behavior to make ITK classes behave in a Pythonic manner,
989+
such as querying object attributes, joining class names to template instantiations, and more.
990+
Python-specific behavior in ITK Python may be investigated with the
991+
Python Debugger module, \code{pdb}.
992+
993+
Tracing can be performed on a function call with \code{pdb.run}, or by editing ITK Python
994+
files to add a \code{pdb.set\_trace()} statement inline. In the following code snippet
995+
an image is loaded and the debugger is set up to trace through an image cast operation.
996+
997+
\begin{minted}[baselinestretch=1,fontsize=\footnotesize,linenos=false,bgcolor=ltgray]{bash}
998+
(venv-itk) > python
999+
>>> import itk
1000+
>>> import pdb
1001+
>>> image = itk.imread(r'myimage.mha',pixel_type=itk.F)
1002+
>>> pdb.runeval('itk.cast_image_filter(image,ttype=[type(image),itk.Image[itk.UC,2]])')
1003+
> <string>(1)<module>()
1004+
(Pdb)
1005+
\end{minted}
1006+
1007+
At this point the debugger can step into and through Python code for translating ITK
1008+
type names and getting the correct template instantiations to run the cast operation. More
1009+
information on Python debugger commands can be found at
1010+
\href{https://docs.python.org/3/library/pdb.html}{https://docs.python.org/3/library/pdb.html}.
1011+
1012+
While the Python debugger is useful, it does not allow us to examine implementation
1013+
details of ITK classes in the native binary. A deeper investigation may be necessary for localizing
1014+
errors in ITK classes.
1015+
1016+
1017+
\subsubsection{C++ Runtime Tracing}
1018+
\label{subsubsec:CppRuntimeTracing}
1019+
1020+
As discussed in Section~\ref{subsubsec:SwigArchitecture}, SWIG wrapping generates
1021+
C++ source files and manages the interface and ownership semantics between
1022+
Python objects and C++ objects during ITK compilation.
1023+
When binary debug symbols are available, a running Python process may be attached to step through
1024+
ITK or ITK SWIG sources at runtime. Several steps are required to set up and execute debugging:
1025+
1026+
\begin{enumerate}
1027+
1028+
\item The ITK build must be configured so that debug symbols
1029+
are generated. Python wrapping must also be enabled.
1030+
This is accomplished by setting the CMake variables
1031+
1032+
\code{CMAKE\_BUILD\_TYPE:STRING="RelWithDebInfo"} and \code{ITK\_WRAP\_PYTHON:BOOL="On"}.
1033+
Note that the "RelWithDebInfo" build type is strongly encouraged over a "Debug"
1034+
build as the former will build against a standard Python distribution.
1035+
See Section~\ref{sec:UsingCMakeForConfiguringAndBuildingITK} for a detailed
1036+
explanation of how to build ITK locally with CMake.
1037+
1038+
\item A Python virtual environment must be appropriately configured with
1039+
\code{WrapITK.pth} so that the ITK debug build can be \code{import}ed.
1040+
See Section~\ref{sec:Wrapping} for further explanation.
1041+
1042+
\item The ITK modules to be debugged must be loaded in a new Python session initialized
1043+
from the given virtual environment. Given that ITK Python uses lazy loading, it is
1044+
pragmatic to use \code{itk.force\_load()} to ensure that all possible debug symbols
1045+
are made available. The \code{os} module can be used to identify the PID of the given session.
1046+
1047+
\begin{minted}[baselinestretch=1,fontsize=\footnotesize,linenos=false,bgcolor=ltgray]{bash}
1048+
(venv-itk) > python
1049+
>>> import itk
1050+
>>> itk.force_load()
1051+
>>> import os
1052+
>>> os.getpid()
1053+
99999
1054+
\end{minted}
1055+
1056+
\item The Python session may be attached to a debugger using the returned PID.
1057+
1058+
\begin{itemize}
1059+
1060+
\item On a Windows operating system, Microsoft Visual Studio 2019 or a similar platform
1061+
can be used for attaching to a running process for debugging. Select "Attach To Process"
1062+
from the Debug menu, choose "Native" code, and then search for the process PID. If
1063+
debug symbols were generated correctly then ITK modules will appear under the list of
1064+
loaded modules.
1065+
1066+
In Visual Studio, debugging can be enabled right from the start if the Python script
1067+
is loaded into Visual Studio as part of a "Python Project".
1068+
Mixed mode debugging needs to be enabled, as per Visual Studio
1069+
documentation\footnote{
1070+
\url{https://docs.microsoft.com/en-us/visualstudio/python/debugging-mixed-mode-c-cpp-python-in-visual-studio}}.
1071+
Starting a project like this is an order of magnitude slower, as many debug symbols need to be loaded and examined.
1072+
1073+
\item On a Linux operating system the GNU Project Debugger \code{gdb} can be used
1074+
for attaching to a running Python process, reading symbol files, and setting breakpoints.
1075+
The following example attaches to a running process and sets a breakpoint inside
1076+
an \code{itk.Image} Python object. It may be necessary to elevate user permissions
1077+
to allow \code{gdb} to attach to the running process.
1078+
1079+
\begin{minted}[baselinestretch=1,fontsize=\footnotesize,linenos=false,bgcolor=ltgray]{bash}
1080+
(venv-itk) > gdb
1081+
(gdb) > attach 99999
1082+
Reading symbols from
1083+
/path/to/ITK-build/Wrapping/Generators/Python/itk/_ITKPyBasePython.so...
1084+
Reading symbols from
1085+
/path/to/ITK-build/Wrapping/Generators/Python/itk/_ITKCommonPython.so...
1086+
(gdb) > break /path/to/ITK-build/Wrapping/Modules/ITKCommon/itkImagePython.cpp:<ln>
1087+
Breakpoint 1 at ...:
1088+
file /path/to/ITK-build/Wrapping/Modules/ITKCommon/itkImagePython.cpp, line <ln>.
1089+
(gdb) > break /path/to/ITK-source/Core/Common/include/itkImage.hxx:<ln>
1090+
Breakpoint 2 at ...:
1091+
/path/to/ITK-source/Modules/Core/Common/include/itkImage.hxx:<ln>
1092+
(gdb) > c
1093+
1094+
... continue in Python session until breakpoint is hit ...
1095+
\end{minted}
1096+
1097+
\item On a macOS operating system the LLDB debugger can be used for attaching to a
1098+
Python process in much the same way as GDB on Linux, with a few extra security
1099+
requirements and slightly different command syntax. The following example
1100+
attaches to a running process and sets a breakpoint inside an \code{itk.Image}
1101+
Python object.
1102+
1103+
\begin{minted}[baselinestretch=1,fontsize=\footnotesize,linenos=false,bgcolor=ltgray]{bash}
1104+
(venv-itk) > lldb
1105+
(lldb) process attach -- pid 99999
1106+
Process 99999 stopped
1107+
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
1108+
frame #0: 0x00007fff203ec656 libsystem_kernel.dylib`__select + 10
1109+
libsystem_kernel.dylib`__select:
1110+
-> 0x7fff203ec656 <+10>: jae 0x7fff203ec660 ; <+20>
1111+
0x7fff203ec658 <+12>: movq %rax, %rdi
1112+
0x7fff203ec65b <+15>: jmp 0x7fff203e56bd ; cerror
1113+
0x7fff203ec660 <+20>: retq
1114+
Target 0: (Python) stopped.
1115+
1116+
Executable module set to
1117+
"/Library/Frameworks/Python.framework/Versions/3.9/
1118+
Resources/Python.app/Contents/MacOS/Python".
1119+
Architecture set to: x86_64h-apple-macosx-.
1120+
1121+
(lldb) breakpoint set
1122+
-f /path/to/ITK/Modules/Core/Common/include/itkImage.hxx
1123+
--line <ln>
1124+
Breakpoint 1: 172 locations.
1125+
(lldb) continue
1126+
1127+
... continue in Python session until breakpoint is hit ...
1128+
\end{minted}
1129+
1130+
LLDB command syntax is documented at \url{https://lldb.llvm.org/index.html}.
1131+
1132+
Developers may find it necessary to examine the follow security concepts
1133+
and requirements in order to permit \code{lldb} to attach to Python:
1134+
1135+
\begin{itemize}
1136+
1137+
\item The Python process must be authorized for debugging. MacOS relies on the
1138+
process of "hardened" runtimes to mitigate security concerns, which reduces
1139+
the ability of debuggers such as \code{lldb} and other processes to attach
1140+
to and intercept the functions of other programs. Python distributions
1141+
are intentionally "hardened" in this way, but additional settings can be
1142+
enabled to allow just-in-time debugging.
1143+
1144+
\item It may be necessary to enter developer mode to allow Python debugging.
1145+
This is accomplished with the following command in the developer console:
1146+
1147+
\begin{minted}[baselinestretch=1,fontsize=\footnotesize,linenos=false,bgcolor=ltgray]{bash}
1148+
DevToolsSecurity -enable
1149+
\end{minted}
1150+
1151+
\item It may be necessary to add entitlements to the Python executable so that
1152+
\code{lldb} can attach to the hardened process. This may be accomplished by
1153+
updating a \code{.plist} entitlements file and setting the executable entitlements.
1154+
1155+
\begin{minted}[baselinestretch=1,fontsize=\footnotesize,linenos=false,bgcolor=ltgray]{bash}
1156+
codesign -d --entitlements :-
1157+
"/path/to/python" >> "/tmp/path/to/python_entitlements.plist"
1158+
/usr/libexec/PlistBuddy -c
1159+
"Add :com.apple.security.get-task-allow bool true"
1160+
"/tmp/path/to/python_entitlements.plist"
1161+
/usr/libexec/PlistBuddy -c
1162+
"Add :com.apple.security.cs.allow-jit bool true"
1163+
"/tmp/path/to/python_entitlements.plist"
1164+
codesign --force --options runtime --sign -
1165+
--entitlements "/tmp/path/to/python_entitlements.plist"
1166+
"/path/to/python"
1167+
\end{minted}
1168+
1169+
\item If attaching to the process continues to fail, log files can
1170+
be dumped with \code{log collect} and opened in the Console application.
1171+
\code{lldb} error messages will be listed as \code{debugserver} entries.
1172+
1173+
\end{itemize}
1174+
1175+
\end{itemize}
1176+
1177+
\end{enumerate}
1178+
1179+
With these steps completed the respective debugger can be used to set breakpoints
1180+
and step through C++ source code for a respective ITK Python execution.
1181+
1182+
The debugger can also be used for examining runtime failures and crashes
1183+
either at the time the error occurs or posthumously with a dump file.
1184+
1185+
\begin{itemize}
1186+
1187+
\item On Windows, Microsoft Visual Studio will catch process aborts
1188+
to allow the attached process to be examined before exit. Stacks, threads,
1189+
and variables are made available to the user for backtracing via the
1190+
\code{Debug} toolbar menu. Dump files in the minidump format may also
1191+
be manually saved and reloaded later for investigation\footnote{
1192+
\url{https://docs.microsoft.com/en-us/visualstudio/debugger/using-dump-files?view=vs-2022}}.
1193+
1194+
\item On Linux, \code{gdb} will catch process aborts at runtime to allow
1195+
a developer to examine the program state before it exits.
1196+
If allowed, core dumps can also be generated on program failures
1197+
to allow posthumous debugging. The following sample
1198+
configures a Linux system to remove the default limit of 0 for allowable
1199+
coredump size and to write out coredump files to the \code{/tmp/} directory.
1200+
1201+
\begin{minted}[baselinestretch=1,fontsize=\footnotesize,linenos=false,bgcolor=ltgray]{bash}
1202+
> ulimit -c unlimited
1203+
> sudo bash -c 'echo "/tmp/coredump-%e.%p" > /proc/sys/kernel/core_pattern'
1204+
\end{minted}
1205+
1206+
More information is available in Python documentation\footnote{
1207+
\url{https://pythondev.readthedocs.io/debug_tools.html\#create-a-core-dump-file}}.
1208+
1209+
\item On MacOS, \code{lldb} will catch process aborts at runtime
1210+
and may also be used to examine core dumps. The following sample
1211+
configures a MacOS system to write out core dump files to the \code{/cores/}
1212+
directory and then runs \code{lldb} to inspect a core dump.
1213+
1214+
\begin{minted}[baselinestretch=1,fontsize=\footnotesize,linenos=false,bgcolor=ltgray]{bash}
1215+
(venv-itk) > ulimit -c unlimited
1216+
1217+
... Run process and generate a core dump ...
1218+
1219+
(venv-itk) > lldb
1220+
(lldb) target create "python3" --core "/cores/core.1007"
1221+
Core file '/cores/core.1007' (x86_64) was loaded.
1222+
\end{minted}
1223+
1224+
\end{itemize}
1225+
1226+
9321227
\section{Third-Party Dependencies}
9331228
\label{sec:ThirdParty}
9341229
\index{module!third-party}

0 commit comments

Comments
 (0)