☰
Slicer 在 GNU/Linux 下的 C++ 调试实战:GDB 附加、执行器环境与 Core 转储分析
2026/10/13 13:59:40 网站建设 项目流程
  • 医疗健康
  • 图像处理
  • 数据可视化
  • 桌面应用
  • 3D渲染

【免费下载链接】Slicer

Multi-platform, free open source software for visualization and image computing.

项目地址:https://gitcode.com/gh_mirrors/sl/Slicer
点击查看免费下载

本文基于 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 = 0

2. 用--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-real

3. 附加进程并继续执行

$ 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 Slicer

GDB 会提示Slicer没有调试符号——这是正常的,因为它只是 launcher。接着设置跟随模式并运行:

(gdb) set follow-fork-mode child (gdb) run

GDB 启动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.

项目地址:https://gitcode.com/gh_mirrors/sl/Slicer
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询