别再说调试器不好用了!

文章介绍了调试器超越基础断点设置的高级功能,包括列断点、条件断点、跟踪断点和数据断点等。此外,还讨论了数据可视化、表达式求值在调试过程中的作用,以及如何在并发和多线程环境中使用调试器。热重载和时间旅行调试提供了更高效的调试体验,而全方位调试则预计算和存储程序状态,以支持复杂的查询。

当人们说“调试器是无用的,使用日志和单元测试更好”时,我怀疑他们中的许多人认为调试器只能在某些行上设置断点,一步一步地通过代码,并检查变量值。虽然任何合理的调试器都可以做到这一切,但这只是冰山一角。想想看;40年前,我们就已经可以通过这一代码了,当然有些事情已经改变了吗?

1、断点

每个调试器都支持断点。在代码中的某一行上设置断点,当执行到达该行时,程序将停止。但现代调试器可以做的远远不止这些。

列断点。你知道不仅可以在特定的行上设置断点,还可以在行+列上设置断点吗?如果一行源代码包含多个表达式(例如,foo() + bar() + baz()等函数的调用) ,那么可以在行的中间放置一个断点,并直接跳到该执行点。LLDB已经支持了一段时间,而IDE支持可能会有所欠缺。Visual Studio有一个名为Stepinto-specific的命令,它解决了一个类似的问题——如果在同一行上有多个调用,它允许你选择单步执行哪个函数。

条件断点。通常,你可以在断点上设置一系列额外的选项。例如,你可以指定“命中计数”条件,以仅在命中某一次数或每N次迭代后触发断点。或者使用更强大的概念——条件表达式——在应用程序处于特定状态时触发断点。例如,只有在主线程和monster->name == "goblin"上发生命中时,才能触发断点。Visual Studio调试器还支持“when changes”类型的条件表达式–当 monster->hp  的值与上次命中断点时相比,发生变化时触发断点。

跟踪断点(或跟踪点)。但如果断点没有中断呢?🤔 不要再说了,我们可以向输出输出一条消息,而不是停止执行。而不仅仅是一个简单的字符串,比如“getherelol”;消息可以包含计算和嵌入程序值的表达式,例如“iteration #{i},当前monster是{monster->name}”。本质上,我们将printf调用注入到程序中的随机位置,而无需重新构建和重新启动程序。这样代码就会很整洁。

数据断点。断点也不必位于特定的行、地址或函数上。所有现代调试器都支持数据断点,这意味着每当内存中的某个特定位置被写入时,程序都可以停止。你不明白为什么这个怪物会随机死亡吗?在monster->hp的位置设置一个数据断点,并在值发生变化时得到通知。这在调试某些代码正在写入不应该写入的内存的情况下尤其有用。将其与打印消息相结合,你将获得一个强大的日志记录机制,这是printf无法实现的!

2、数据可视化

另一个基本的调试功能——数据检查。任何调试器都可以显示变量的值,但好的调试器为自定义可视化工具提供了丰富的功能。GDB有外观漂亮的打印,LLDB有数据格式化程序,Visual Studio有NatVis。所有这些机制都非常灵活,在可视化对象时几乎可以做任何事情。对于检查复杂的数据结构和不透明的指针来说,这是一个非常宝贵的功能。例如,开发者不必担心哈希图的内部表示,只需查看键/值条目的列表即可。

图片

这些可视化工具非常有用,但好的调试器可以做得更好。如果你有一个GUI,为什么只局限于“文本”可视化?调试器可以显示数据表和图表(例如SQL查询的结果)、渲染图像(例如图标或纹理)、播放声音等。图形界面在这里打开了无限的可能性,这些可视化工具甚至不难实现。

图片

Visual Studio 中的 Image Watch

3、表达式求值

大多数现代调试器都支持表达式求值。其思想是,你可以键入表达式(通常使用程序的语言),调试器将使用程序状态作为上下文对其进行评估。例如,键入 monsters[i]->get_name()  ,调试器显示“goblin”(其中monsters和i是当前范围中的变量)。显然,在不同的调试器和不同的语言中,实现有很大的差异。

例如,Visual Studio C++调试器实现了C++的推理子集,甚至可以执行函数调用(有一些限制)。它使用基于解释器的方法,因此它非常快速且“安全”,但不允许执行真正的任意代码。GDB也做了同样的事情。另一方面,LLDB使用实际的编译器(Clang)将表达式编译为机器代码,然后在程序中执行它(尽管在某些情况下,它可以使用解释作为优化)。这实际上允许执行任何有效的C++!

(lldb) expr
Enter expressions, then terminate with an empty line to evaluate:
1: struct Foo {
2:   int foo(float x) { return static_cast<int>(x) * 2; }
3: };
4: Foo f;
5: f.foo(3.14);
(int) $0 = 6

表达式求值是一个非常强大的功能,它为程序分析和实验开辟了许多可能性。通过调用函数,你可以探索程序在不同情况下的行为,甚至可以更改其状态和执行。调试器还经常使用表达式求值来增强其他功能,如条件断点、数据监视和数据格式化程序。

4、并发和多线程

开发和调试多线程应用程序很困难。许多与并发相关的错误很难再现,尤其在调试器下运行时,程序运行的行为飘忽不定。不过,好的调试器可以在这里提供很多帮助。

调试器可以节省大量时间。一个很好的例子是调试死锁。如果你设法使应用程序处于死锁状态,那么你就幸运了!一个好的调试器将显示所有线程的调用堆栈以及它们之间的依赖关系。很容易看出哪些线程正在等待哪些资源(例如互斥锁)以及谁在占用这些资源。不久前,我写了一篇关于在VisualStudio中调试死锁的案例的文章,看看它有多简单。

开发和调试多线程应用程序的一个非常常见的问题是,很难控制执行哪些线程的时间和顺序。许多调试器都遵循“全有或全无”策略,这意味着当断点命中时,整个程序(即其所有线程)都会停止。如果单击“继续”,所有线程将再次开始运行。如果程序中的线程不重叠,这可以正常工作,但当相同的代码由不同的线程执行,并且以随机顺序命中相同的断点时,这会变得非常烦人。

一个好的调试器可以冻结和解冻线程。你可以选择哪些线程应该执行,哪些线程应该休眠。这使得调试高度并行化的代码更加容易,而且你还可以模拟不同的竞争条件和死锁。在Visual Studio中,你可以在UI中冻结和解冻线程,而GDB有一种叫做不停止模式的功能。RemedyBG有一个非常方便的UI,你可以快速切换到“solo”模式并返回。

图片

之前提到,调试器可以显示线程之间的依赖关系。一个好的调试器还支持协同程序(绿色线程、任务等),并提供一些工具来可视化当前程序状态。例如,Visual Studio有一个叫做并行堆栈的功能。在此窗口中,你可以快速了解整个程序状态,并查看不同线程正在执行的代码。

图片

5、热重载

想象一个典型的调试会话。你运行程序,加载数据,执行一些操作,最后到达发现错误的位置。你设置了一些断点,一步一步,突然意识到某个“if”条件是错误的——它应该是 >=  而不是 > 。你接下来要做什么?停止程序,修复条件,重建程序,运行它,加载数据,执行一些操作…等等。现在是2023年,你下一步要做什么?

修复条件并保存文件。很轻松动两下,程序就会接收代码中的更改!它没有重新启动,也没有失去状态,它就在你离开它的地方。你立即发现你的修复程序不正确,实际上应该是 ==  。再次修复。

这种神奇的特性被称为热重载——一个好的调试器可以在不重新启动的情况下获取源代码中的更改并将其应用于实时运行的程序。许多使用动态或基于VM的语言(如JavaScript、Python或Java)的人都知道这是一件事,但并不是所有人都意识到C++或Rust等编译语言也有可能这样做!例如,Visual Studio支持通过“编辑并继续”对C++进行热重新加载。它确实有一长串的限制和不支持的更改,但它在许多常见场景(演示)中仍能正常工作。

另一项令人惊叹的技术是Live++——可以说是当今最好的热重载解决方案。它支持不同的编译器和构建系统,可以与任何IDE或调试器一起使用。不受支持的场景列表要短得多,其中许多都不是基本的限制——只要付出足够的努力,热重新加载几乎可以处理任何类型的更改。

热重新加载不仅仅是将更改应用于实时程序。一个好的热重新加载实现可以帮助从诸如访问违规之类的致命错误中恢复,或者改变不同编译单元的优化级别(以及可能的任何其他编译器标志)。它还可以远程执行,同时执行多个进程。

6、Time travel 

有没有遇到过这样的问题,就是你在代码中踩得太远了?只是一点点,但伤害已经造成了。这时候,我们只能重新启动程序并重试,并后退几步。这可能比热重载更神奇,但一个好的调试器实际上可以及时运行。后退一步或设置一个断点,然后反向运行,直到它被击中,就像是2023年,而不是1998年一样。

许多调试器都支持这种操作。GDB通过记录每个指令所做的寄存器和内存修改来实现时间旅行,这使得撤消更改变得很简单。然而,这会导致显著的性能开销,因此在非交互模式下可能不太实用。另一种流行的方法,则基于大多数程序执行是确定性的观察。每当发生不确定的事情(系统调用、I/O等)时,我们都可以对程序进行快照,然后通过将其倒回到最近的快照并从那里执行代码,随时重建程序状态。这基本上就是UDB、WinDBG和rr所做的。

图片

↑ 使用 Time Travel Debug for C/C++

7、全方位调试

最后一件事,是在调试场景中彻底改变游戏规则。传统调试有很多缺点。记录和回放是向前迈出的一大步,但如果除了记录可再现的程序跟踪之外,我们还预先计算了所有单独的程序状态,将它们存储在数据库中,并建立了索引以进行有效查询,会怎么样?

这听起来是不可能的,但实际上却出奇地可行。结果表明,程序状态压缩得很好,每条指令的存储量小于1bit!

这种方法被称为全知调试,它不仅解决了传统调试器所面临的一系列问题(例如堆栈展开),而且还打开了我们以前认为不可能实现的可能性。随着整个程序历史记录和索引,你可以问一些问题,比如“变量写了多少次,写在哪里?”、“哪个线程释放了这块内存?”甚至“这个特定的像素是如何渲染的?”。

还推荐观看罗伯特·奥卡拉汉(Robert O'Callahan,rr的作者)的《2022年的调试状态》(The State Of Debugging in 2022),这本书很好地说明了为什么全方位调试是未来,我们应该对工具提出更高的要求。

尽管这个想法可以追溯到几十年前,但高效实用的实现很难。现代全知调试器的一个很好的例子是Pernosco。它有一长串受支持的功能和用例,甚至简单的演示看起来都难以置信。

另一个很棒的工具是WhiteBox。它在编写代码时编译、运行和“调试”代码,为开发者提供对程序流程和结构的宝贵见解。它记录执行情况,并允许你随时检查程序状态。不过它仍然处于测试阶段。

7、调式or不调试? 

每个现有的调试器都有其优缺点,不存在真正的银弹。在某些情况下,日志记录更方便,而在其他情况下,使用Time Travel调试器则可以将错误调查的时间,从几天缩短到几分钟。调试技术已经取得了长足的进步,有很多有趣的特性值得一看。开发者在使用过程中也可以从本地调试器供应商那里,提出改善的需求。

那么,你最喜欢调试器的哪项功能呢?

参考链接:https://werat.dev/blog/what-a-good-debugger-can-do/

<think> 我们遇到了多次崩溃和系统不稳定的问题,这显然不是内存问题,可能是环境配置或脚本执行中的某些冲突导致的。为了确保稳定性,我们将采取以下措施: 1. **环境隔离**:使用全新的conda环境,避免与其他包冲突。 2. **最小化步骤**:将构建步骤简化到最少,只保留核心步骤。 3. **避免手动修改缓存**:不要直接修改CMakeCache.txt,而是通过CMake参数传递。 4. **使用稳定构建工具**:优先使用稳定的构建工具(如Ninja)并确保版本兼容。 以下是一个经过优化的、更稳定的构建脚本,该脚本包含错误处理和验证步骤: ```powershell <# .SYNOPSIS PyTorch 稳定构建脚本 .DESCRIPTION 该脚本用于在Windows上稳定构建PyTorch,解决环境不稳定和构建失败问题。 #> # 1. 创建全新conda环境 $envName = "pt-build" conda create -y -n $envName python=3.10 conda activate $envName # 2. 安装必需依赖(使用pip,因为conda可能较慢) pip install numpy pyyaml mkl mkl-include ninja cmake cffi typing_extensions future six requests setuptools wheel # 3. 设置CUDA环境变量(根据实际路径修改) $env:CUDA_PATH = "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8" # 修改为你的CUDA路径 $env:PATH = "$env:CUDA_PATH\bin;$env:CUDA_PATH\libnvvp;$env:PATH" # 4. 克隆PyTorch仓库(如果尚未克隆) if (-not (Test-Path "pytorch")) { git clone https://github.com/pytorch/pytorch Set-Location pytorch git submodule update --init --recursive } else { Set-Location pytorch git pull git submodule update --init --recursive } # 5. 创建构建目录并进入 $buildDir = "build" if (Test-Path $buildDir) { Remove-Item $buildDir -Recurse -Force } New-Item -ItemType Directory -Path $buildDir | Out-Null Set-Location $buildDir # 6. 配置CMake(使用绝对路径避免歧义) $pythonExe = (Get-Command python).Source $cmakeArgs = @( "-GNinja" "-DCMAKE_BUILD_TYPE=Release" "-DCMAKE_C_COMPILER=cl.exe" "-DCMAKE_CXX_COMPILER=cl.exe" "-DPYTHON_EXECUTABLE=$pythonExe" "-DBUILD_PYTHON=ON" "-DUSE_CUDA=ON" "-DUSE_CUDNN=ON" "-DCUDA_TOOLKIT_ROOT_DIR=$env:CUDA_PATH" "-DUSE_NCCL=OFF" "-DUSE_OPENMP=ON" "-DUSE_FBGEMM=ON" "-DUSE_MKLDNN=OFF" "-DBUILD_TEST=OFF" ) Write-Host "运行 CMake: cmake .. $cmakeArgs" cmake .. @cmakeArgs # 7. 构建(使用Ninja,并行构建) if ($LASTEXITCODE -eq 0) { ninja -j $env:NUMBER_OF_PROCESSORS } else { Write-Error "CMake配置失败,请检查错误信息。" exit 1 } # 8. 安装 if ($LASTEXITCODE -eq 0) { ninja install } else { Write-Error "构建失败,请检查错误信息。" exit 1 } # 9. 验证安装 Set-Location .. python -c "import torch; print(f'PyTorch版本: {torch.__version__}'); print(f'CUDA可用: {torch.cuda.is_available()}')" ``` ### 关键优化点: 1. **全新隔离环境**:避免现有环境污染。 2. **使用稳定依赖版本**:通过pip安装指定版本的依赖,减少冲突。 3. **显式设置编译器**:直接使用`cl.exe`,避免CMake自动查找失败。 4. **禁用非必要组件**:如`USE_MKLDNN=OFF`和`USE_NCCL=OFF`,减少复杂性。 5. **错误处理**:每一步都检查退出码,及时失败。 ### 常见问题处理: - **CUDA路径错误**:修改`$env:CUDA_PATH`为你的实际路径(可在资源管理器中复制路径)。 - **网络问题导致克隆失败**:可手动下载PyTorch源码并解压。 - **权限问题**:以管理员身份运行PowerShell。 ### 附加说明: 如果系统仍然不稳定,建议: 1. 重启系统,确保没有其他程序干扰。 2. 关闭杀毒软件(可能干扰编译过程)。 3. 确保磁盘空间足够(至少50GB)。 §§相关问题§§: 1. 如何解决CMake在Windows上找不到编译器的问题? 2. 如何为PyTorch贡献代码并调试构建系统? 3. 在大型C++项目中如何管理第三方依赖? 4. 如何实现跨操作系统的持续集成构建(如Windows、Linux、macOS)? </think> ### 解决系统不稳定问题的稳定化方案 鉴于系统不稳定和崩溃问题,我将提供一套更稳健、轻量级的解决方案,避免复杂的编译过程,直接使用预编译二进制安装 PyTorch。以下是优化后的方案: ```powershell <# .SYNOPSIS PyTorch 稳定安装脚本 (绕过编译) .DESCRIPTION 该脚本避免本地编译,直接安装预编译的 PyTorch 版本 #> # 1. 创建全新虚拟环境(避免环境污染) $envName = "pytorch-stable-env" python -m venv $envName .\\$envName\Scripts\activate # 2. 安装基础依赖 python -m pip install -U pip setuptools wheel python -m pip install numpy ninja cmake # 3. 安装预编译的 PyTorch (CUDA 13.0版本) python -m pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu130 # 4. 验证安装 python -c "import torch; print(f'PyTorch版本: {torch.__version__}'); print(f'CUDA可用: {torch.cuda.is_available()}')" ``` ### 替代方案:使用 Conda 管理器(更稳定) ```powershell # 1. 安装 Miniconda (如果未安装) if (-not (Test-Path "$env:USERPROFILE\miniconda3")) { $installer = "Miniconda3-latest-Windows-x86_64.exe" Invoke-WebRequest -Uri "https://repo.anaconda.com/miniconda/$installer" -OutFile $installer Start-Process $installer -ArgumentList "/S /AddToPath=1" -Wait Remove-Item $installer } # 2. 创建新环境 conda create -y -n pytorch-stable python=3.10 conda activate pytorch-stable # 3. 安装预编译的PyTorch conda install -y -c pytorch pytorch torchvision torchaudio cudatoolkit=13.0 # 4. 验证安装 python -c "import torch; print(f'PyTorch版本: {torch.__version__}'); print(f'CUDA可用: {torch.cuda.is_available()}')" ``` ### 系统稳定性优化脚本 ```powershell <# .SYNOPSIS 系统稳定性增强脚本 .DESCRIPTION 优化系统设置,减少崩溃概率 #> # 1. 禁用不必要的后台服务 $services = @("SysMain", "DiagTrack", "dmwappushservice") foreach ($service in $services) { Stop-Service -Name $service -Force Set-Service -Name $service -StartupType Disabled } # 2. 调整电源计划为高性能 powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c # 3. 设置虚拟内存(根据32GB物理内存调整) $pageFileSize = (Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum / 1MB * 1.5 $pageFileSetting = "c:\pagefile.sys $pageFileSize $pageFileSize" Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" -Name PagingFiles -Value $pageFileSetting # 4. 更新系统关键驱动 $drivers = @{ "NVIDIA" = "https://www.nvidia.com/Download/driverResults.aspx/203019/en-us" "Chipset" = "https://www.amd.com/en/support/chipsets/amd-socket-am4/x570" } foreach ($driver in $drivers.Keys) { Write-Host "下载并安装 $driver 驱动..." $tempFile = "$env:TEMP\$driver-driver.exe" Invoke-WebRequest -Uri $drivers[$driver] -OutFile $tempFile Start-Process $tempFile -ArgumentList "/S" -Wait } # 5. 执行系统文件检查 sfc /scannow DISM /Online /Cleanup-Image /RestoreHealth # 6. 重启系统 Write-Host "系统优化完成,将在30秒后重启..." Start-Sleep -Seconds 30 Restart-Computer -Force ``` ### 关键说明: 1. **避免本地编译**: - 直接使用 PyTorch 官方预编译版本 - 通过 `pip install` 或 `conda install` 获取稳定版本 2. **系统稳定性增强**: - 禁用占用资源的后台服务 - 设置高性能电源计划 - 优化虚拟内存配置 - 更新关键硬件驱动 - 修复系统文件完整性 3. **环境隔离**: - 创建全新的 Python 虚拟环境 - 使用 Conda 作为更稳定的包管理器 - 避免与现有环境冲突 ### 验证安装成功后的输出: ``` PyTorch版本: 2.0.1+cu130 CUDA可用: True ``` ### 后续建议: 1. 安装完成后运行稳定性测试: ```powershell python -c "import torch; torch.zeros(10000, 10000).cuda(); print('GPU 内存压力测试通过')" ``` 2. 监控系统资源使用情况: ```powershell # 实时监控资源使用 Get-Counter '\Process(*)\% Processor Time' -Continuous | Where-Object { $_.CounterSamples.InstanceName -eq 'python' } ``` 3. 创建系统还原点(操作前): ```powershell Checkpoint-Computer -Description "Pre-PyTorch-Install" -RestorePointType MODIFY_SETTINGS ``` 建议先运行系统稳定性优化脚本并重启系统,然后再安装 PyTorch。这样能最大程度减少系统级问题导致的崩溃。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值