- 医疗健康
- 图像处理
- 数据可视化
- 桌面应用
- 3D渲染
【免费下载链接】Slicer
Multi-platform, free open source software for visualization and image computing.
本文基于 Slicer 官方文档Docs/developer_guide/debugging/linuxcpp.md,系统讲解在 GNU/Linux 上调试 Slicer C++ 代码的四种 GDB 工作流(附加运行中进程、通过执行器启动参数、跟随 fork 子进程、exec-wrapper 包装脚本),以及崩溃 core 文件的获取与分析方法。读完后你将能够绕过 Slicer 双层可执行结构(Slicer启动器 +SlicerApp-real真实进程)带来的调试障碍,独立完成断点调试与 segfault 回溯,并可进一步衔接 Qt Creator、VS Code 等跨平台 IDE 调试方案。
为什么 Linux 下调试 Slicer 比常规 Qt 项目麻烦
Slicer 在 Linux 上并不是"一个可执行文件"的结构。从构建系统 CMake/SlicerMacroBuildApplication.cmake 可以看到,在非 Apple 平台上真实的应用二进制会被命名为<应用名>App-real:
set(executable_name ${SLICERAPP_APPLICATION_NAME}) if(NOT APPLE) set(executable_name ${executable_name}App-real) endif()即最终产生两个可执行文件:
Slicer:由 CTKAppLauncher 生成的启动器(launcher),负责设置LD_LIBRARY_PATH、PATH、PYTHONPATH等环境变量后 fork/exec 出真实进程;SlicerApp-real:真正运行的应用程序,包含全部符号,断点必须打在这里。
这就是本文所有调试技巧的共同出发点:要么在SlicerApp-real运行时把它 attach 进 GDB,要么设法让 GDB 越过启动器直接调试真实进程。
方法一:GDB 附加到运行中进程(推荐)
文档将 attach 方式列为推荐方案,流程分四步。
1. 解除 yama ptrace 限制
自 Ubuntu 10.10 起,内核的 yama LSM 默认禁止普通用户 ptrace 非子进程,只有父进程才能附加到自己的子进程。临时放开:
$ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope永久放开则编辑/etc/sysctl.d/10-ptrace.conf,将:
kernel.yama.ptrace_scope = 1改为:
kernel.yama.ptrace_scope = 02. 用--attach-process拿到 PID
$ ./Slicer --attach-process该参数会在加载任何模块之前弹出一个显示当前进程PID的消息框,因此既方便 attach,也适合调试"模块加载阶段"就发生的崩溃。这个行为可以在源码中确认:Base/QTCore/qSlicerCoreApplication.cxx 中,应用初始化环境时检查该参数并弹出提示:
// allow a debugger to be attached during startup if (qApp->arguments().contains("--attach-process")) { // Message for developers - do not translate. QString msg(/*no tr*/ "This message box is here to give you time to attach " "your debugger to process [PID %1]"); QMessageBox::information(nullptr, /*no tr*/ "Attach process", msg.arg(QCoreApplication::applicationPid())); }如果不使用该参数,也可以直接查询进程:
$ ps -Afww | grep SlicerApp-real3. 附加进程并继续执行
$ gdb --pid $PIDABOVE进入 GDB 后执行:
(gdb) continue让被暂停的进程恢复运行,此时即可正常下断点。
小技巧:在 GDB 中打印
QString需要借助 Qt 提供的调试辅助函数,可参考文档给出的 printQString 资料(文档原文指向 silmor.de 的printqstring页面,属于 Qt 通用技巧)。
方法二:利用执行器的启动参数
Slicer 的执行器(app launcher)本身就提供了"携带 Slicer 环境启动其他程序"的能力,这是 CTKAppLauncher 的通用特性,在 CMake/SlicerMacroBuildApplication.cmake 中通过ctkAppLauncherAppendExtraAppToLaunchToList逐个注册附加可执行程序(例如构建树上自动注册--gnome-terminal、--xterm、--designer等选项)。
与调试相关的两个选项:
--launch <executable> [<parameters>]:执行任意程序,且程序继承 Slicer 的完整环境变量。例如Slicer --launch /usr/bin/gnome-terminal启动一个 gnome-terminal(随后即可在该终端里直接对SlicerApp-real运行 GDB);--gdb:直接启动 GDB,并在调试器环境内执行SlicerApp-real。
方法三:让 GDB 跟随 fork 出的子进程
既然Slicer只是启动器,可以直接对启动器下 GDB,再让 GDB 切换到 fork 出来的SlicerApp-real:
gdb SlicerGDB 会提示Slicer没有调试符号——这是正常的,因为它只是 launcher。接着设置跟随模式并运行:
(gdb) set follow-fork-mode child (gdb) runGDB 启动Slicer后会自动 attach 到 fork 出的SlicerApp-real,之后即可正常调试。
方法四:使用 exec-wrapper 直接调试(适合 IDE 集成)
exec-wrapper 方案用一个包装脚本模拟 app launcher 的环境变量行为,使 GDB 或任何"控制 GDB 的程序"(如 IDE)可以在不 attach的情况下交互式调试。
包装脚本的核心思想与 CMake/SlicerBlockCTKAppLauncherSettings.cmake 中定义的 launcher 设置一一对应(该 CMake 文件分"构建树 / 安装树"两组维护SLICER_LIBRARY_PATHS、SLICER_PATHS、SLICER_ENVVARS等变量,最终生成SlicerLauncherSettings.ini):
[LibraryPaths]的内容进入LD_LIBRARY_PATH;[Paths]的内容进入PATH;[EnvironmentVariables]中的每一项逐一设置(如SLICER_HOME、PYTHONHOME、PYTHONPATH、QT_PLUGIN_PATH等)。
然后启动 GDB:
(gdb) set exec-wrapper ./WrapSlicer (gdb) exec-file ./bin/SlicerQT-real (gdb) run(SlicerQT-real为文档撰写时期的进程名,对应现版本的SlicerApp-real。)
关闭线程事件噪声
由于 VTK 和 ITK 包含大量多线程过滤器,渲染和处理期间 GDB 默认会刷屏输出类似:
[New Thread 0x7fff8378f700 (LWP 20510)] [Thread 0x7fff8b0aa700 (LWP 20506) exited]用以下命令关闭:
set print thread-events off生成 wrapper 脚本的实用技巧
最简单的方式是让 launcher 自己"示范"环境变量。执行:
Slicer --launch `which gnome-terminal`或
Slicer --gnome-terminal然后在弹出的终端中查看LD_LIBRARY_PATH的取值(注意:文档提醒通过--xterm启动时该变量似乎不会被设置),再对照[EnvironmentVariables]检查其余变量。
文档附了一份当时的SlicerLauncherSettings.ini完整示例,展示了 launcher 配置的五个主要区块([General]、[Application]、[ExtraApplicationToLaunch]、[LibraryPaths]、[Paths]、[EnvironmentVariables])。关键部分摘录如下(完整文件见 Docs/developer_guide/debugging/linuxcpp.md):
[General] launcherSplashImagePath=... launcherSplashScreenHideDelayMs=3000 additionalLauncherHelpShortArgument=-h additionalLauncherHelpLongArgument=--help additionalLauncherNoSplashArguments=--no-splash,--help,--version,--home,--program-path,--no-main-window [Application] path=<APPLAUNCHER_DIR>/bin/./SlicerQT-real arguments= [ExtraApplicationToLaunch] gnome-terminal/shortArgument= gnome-terminal/help=Start gnome-terminal gnome-terminal/path=/usr/bin/gnome-terminal gnome-terminal/arguments= gdb/shortArgument= gdb/help=Start gdb gdb/path=/usr/bin/gdb gdb/arguments= [LibraryPaths] 1\path=... 8\path=<APPLAUNCHER_DIR>/bin/. 9\path=../lib/Slicer-4.0/qt-loadable-modules ... size=18 [Paths] 1\path=<APPLAUNCHER_DIR>/bin/. ... size=5 [EnvironmentVariables] QT_PLUGIN_PATH=<APPLAUNCHER_DIR>/bin<PATHSEP>... SLICER_HOME=... PYTHONHOME=... PYTHONPATH=... TCL_LIBRARY=... TK_LIBRARY=... TCLLIBPATH=...其中<APPLAUNCHER_DIR>、<PATHSEP>是 CTKAppLauncher 的占位符,分别在运行时被替换为启动器所在目录与平台路径分隔符。当前仓库中这些路径并非手维护,而是由 CMake/SlicerBlockCTKAppLauncherSettings.cmake 程序化生成——例如构建树的SLICER_LIBRARY_PATHS_BUILD、SLICER_PATHS_BUILD、SLICER_PYTHONPATH_BUILD以及安装树对应的*_INSTALLED变量——阅读该文件即可理解任何一次生成的SlicerLauncherSettings.ini的来龙去脉。
如果--launch方式不可用,上述 ini 设置可以直接翻译为如下 bash 包装脚本:
#!/bin/bash BASE_DIR=/cmn/git/Slicer4-sb/ APPLAUNCHER_DIR=$BASE_DIR/Slicer-build LD_PATHS=" /cmn/git/Slicer4-sb/VTK-build/bin/. /cmn/git/Slicer4-sb/CTK-build/CTK-build/bin/. /usr/lib /cmn/git/Slicer4-sb/ITKv3-build/bin/. /cmn/git/Slicer4-sb/SlicerExecutionModel-build/ModuleDescriptionParser/bin/. /cmn/git/Slicer4-sb/teem-build/bin/. /cmn/git/Slicer4-sb/LibArchive-install/lib $APPLAUNCHER_DIR/bin/. ../lib/Slicer-4.0/qt-loadable-modules $APPLAUNCHER_DIR/lib/Slicer-4.0/cli-modules/. $APPLAUNCHER_DIR/lib/Slicer-4.0/qt-loadable-modules/. /cmn/git/Slicer4-sb/tcl-build/lib /cmn/git/Slicer4-sb/OpenIGTLink-build /cmn/git/Slicer4-sb/OpenIGTLink-build/bin/. /cmn/git/Slicer4-sb/CTK-build/PythonQt-build/. /cmn/git/Slicer4-sb/python-build/lib /cmn/git/Slicer4-sb/python-build/lib/python2.6/site-packages/numpy/core /cmn/git/Slicer4-sb/python-build/lib/python2.6/site-packages/numpy/lib " for STR in $LD_PATHS; do LD_LIBRARY_PATH="${STR}:${LD_LIBRARY_PATH}"; done QT_PLUGIN_PATH=$APPLAUNCHER_DIR/bin:/cmn/git/Slicer4-sb/CTK-build/CTK-build/bin:/usr/lib/qt4/plugins SLICER_HOME=/cmn/git/Slicer4-sb/Slicer-build PYTHONHOME=/cmn/git/Slicer4-sb/python-build PYTHONPATH=$APPLAUNCHER_DIR:/bin:$APPLAUNCHER_DIR:/bin/Python:/cmn/git/Slicer4-sb/python-build/lib/python2.6/site-packages:$APPLAUNCHER_DIR/lib/Slicer-4.0/qt-loadable-modules/.:$APPLAUNCHER_DIR/lib/Slicer-4.0/qt-loadable-modules/Python TCL_LIBRARY=/cmn/git/Slicer4-sb/tcl-build/lib/tcl8.4 TK_LIBRARY=/cmn/git/Slicer4-sb/tcl-build/lib/tk8.4 TCLLIBPATH=/cmn/git/Slicer4-sb/tcl-build/lib/itcl3.2:/cmn/git/Slicer4-sb/tcl-build/lib/itk3.2 export QTPLUGIN_PATH=$QT_PLUGIN_PATH export SLICER_HOME=$SLICER_HOME export PYTHONHOME=$PYTHONHOME export PYTHONPATH=$PYTHONPATH export TCL_LIBRARY=$TCL_LIBRARY export TK_LIBRARY=$TK_LIBRARY export TCLLIBPATH=$TCLLIBPATH export LD_LIBRARY_PATH=$LD_LIBRARY_PATH exec "$@"脚本末尾的exec "$@"是关键:GDB 的exec-wrapper机制会用"脚本 + 被调试程序"作为命令执行,真实进程成为脚本的子进程,exec保证 GDB attach 到的就是它本身。
分析段错误(Segmentation Fault)
在构建树中复现并加载 core
$ ulimit -c unlimited $ ./Slicer ... make it crash $ ./Slicer --gdb ./bin/SlicerApp-real (gdb) core core (gdb) backtrace ...在已安装的 Slicer 中
$ ulimit -c unlimited $ ./Slicer ... make it crash $ ./Slicer --launch bash $ gdb ./bin/SlicerApp-real (gdb) core core (gdb) backtrace ...已安装版本用--launch bash开一个继承 Slicer 环境的 shell,再在其中启动 GDB 加载SlicerApp-real对应的 core 文件。
注意:GDB 自身依赖 Python,而 GDB 链接的 Python 与 Slicer 自带的 Python 并非同一套,可能造成启动问题。若 GDB 因缺少
_sysconfigdata__linux_x86_64-linux-gnu.py而无法启动,需要把 Slicer 的 sysconfigdata 文件复制为期望的文件名,例如:cd ~/Slicer-4.13.0-2021-09-10-linux-amd64/lib/Python/lib/python3.6 cp _sysconfigdata_m_linux2_.py _sysconfigdata__linux_x86_64-linux-gnu.py
systemd 系统上的 coredump 处理
在启用 systemd 的发行版中,coredump 由 systemd 的 coredump 守护进程接管,以.lz4压缩格式存储在:
/var/lib/systemd/coredump/core.SlicerApp-real.xxxx.lz4即使设置了ulimit -c unlimited,coredump 仍可能被截断(truncated)。用coredumpctl list查看最近的 coredump 及对应 PID:
coredumpctl list Thu 2018-05-24 16:37:46 EDT 22544 1000 1000 11 missing /usr/lib/firefox/firefox Fri 2018-05-25 15:50:52 EDT 14721 1000 1000 6 truncated /path/Slicer-build/bin/SlicerApp-real Mon 2018-05-28 11:35:43 EDT 17249 1000 1000 6 present /path/Slicer-build/bin/SlicerApp-real若状态为truncated,可修改/etc/systemd/coredump.conf提高默认最大文件尺寸:
[Coredump] #Storage=external #Compress=yes ProcessSizeMax=8G ExternalSizeMax=8G JournalSizeMax=6G #MaxUse= #KeepFree=修改后再次触发崩溃,core 文件状态应变为present。随后用coredumpctl直接拉起 GDB(不带 PID 时默认使用最新一个 coredump):
coredumpctl gdb $PID若要在其他 IDE 或 ddd 中使用,需先解压 coredump(可通过修改coredump.conf的Compress选项,或直接导出):
coredumpctl dump $PID > /tmp/corefile进一步:跨平台 IDE 调试
文档最后指出,上述 GDB 工作流可与 IDE 集成,仓库中对应有三篇姊妹篇文档可继续深入:
- Debugging using CodeLite
- Debugging using Qt Creator
- Debugging using Visual Studio Code
其中 Qt Creator 篇给出了典型做法:先用./Slicer --launch /path/to/qtcreator启动 IDE 以继承 Slicer 环境(这正是前文--launch选项的应用),再打开CMakeLists.txt选择 Slicer-build 目录进行构建配置。
总结:四种方式的选择依据
| 场景 | 推荐方式 | | | | | 快速对运行中的 Slicer 下断点 |--attach-process+gdb --pid(需先放开 yama ptrace_scope) | | 需要带 Slicer 环境的手动调试 |Slicer --gdb或Slicer --launch bash| | 不想预先启动 Slicer,让 GDB 全程托管 |set follow-fork-mode child跟随 fork | | IDE 集成、需要完整控制 GDB 生命周期 | exec-wrapper 包装脚本 | | 程序已崩溃,只剩 core 文件 |ulimit -c unlimited复现 +core/backtrace;systemd 系统改用coredumpctl|
所有流程的共同前提是要清楚Slicer(launcher)与SlicerApp-real(真实进程)的分工:断点、符号、core 分析都必须针对后者进行,而 launcher 的价值在于替你准备好一整套可复现的调试环境。
- 医疗健康
- 图像处理
- 数据可视化
- 桌面应用
- 3D渲染
【免费下载链接】Slicer
Multi-platform, free open source software for visualization and image computing.
相关推荐
Ceph 开发环境 GDB 调试指南:附加运行进程与 Teuthology 故障分析
Ceph 开发环境 GDB 调试指南:附加运行进程与 Teuthology 故障分析 GDB(GNU Project Debugger)是分析进程执行流程、定位
存储分布式文件系统对象存储后端高可用tflite-micro 在 QEMU 上运行 Cortex-M 单元测试:环境搭建、测试执行与 GDB 调试实战指南
tflite micro 在 QEMU 上运行 Cortex M 单元测试:环境搭建、测试执行与 GDB 调试实战指南 本篇技术指南以 qemu.md http
人工智能深度学习推理引擎本地部署嵌入式物联网UE4SS C++ Mod 调试实战:使用 await_debugger 附加调试器逐行排查
UE4SS C++ Mod 调试实战:使用 await_debugger 附加调试器逐行排查 本文面向 UE4SS C++ Mod 开发者:当日志输出不足以定位
游戏开发逆向工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考