windows linux c++附加到进程 调试

发布于:2025-02-13 ⋅ 阅读:(155) ⋅ 点赞:(0)

前言

我们在编程时会出现 父exe 调用 子exe 的情况,同时我们还需要调试 子exe 的代码。
因此,我尝试写一个示例,并尝试在 windows 和 linux(统信系统) 下调试子exe。

同时为了跨平台,我们使用 cmake 和 纯c++库 来编写代码。
调试时必须使用 DEBUG 来编译代码。

代码

  1. 代码结构
MultiProcDemo/
├── CMakeLists.txt               # 主工程
├── parent/
│   ├── CMakeLists.txt           # parent子工程
│   └── parent.cpp                  # parent源码
└── child/
    ├── CMakeLists.txt           # child子工程
    └── child.cpp                  # child源码

  1. 主 CMakeLists.txt
# CMakeList.txt: MultiProcDemo 的 CMake 项目,在此处包括源代码并定义
# 项目特定的逻辑。
#
cmake_minimum_required (VERSION 3.8)

project ("MultiProcDemo")

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if (MSVC)
    # 禁用优化
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Od")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Od")
endif()

# 将所有可执行文件输出到统一目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

add_subdirectory(child)  # 先编译 child
add_subdirectory(parent)  # 后编译 parent
  1. parent CMakeLists.txt
add_executable(parent parent.cpp)
  1. parent.cpp
#include <iostream>
#include <thread>
#include <chrono>
#include <cstdlib>

#if defined(_WIN32)
#include <windows.h>
#elif defined(__linux__)
#include <cstdlib>
#include <unistd.h>
#endif

void launchChildInNewTerminal() {
    std::string command;

#if defined(_WIN32)
    command = "start cmd /K .\\child.exe";
    system(command.c_str());
#elif defined(__linux__)
    command = "xterm -e ./child &";
    system(command.c_str());
#endif
}

int main() {
    launchChildInNewTerminal();

    int count = 0;
    for (int i = 0; i < 20; ++i) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        count++;
        std::cout << "parent.exe: " << count << std::endl;
    }
    return 0;
}

  1. child CMakeLists.txt
add_executable(child child.cpp)
  1. child.cpp
#include <iostream>
#include <thread>
#include <chrono>

int main() {
    int count = 0;
    for (int i = 0; i < 20; ++i) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        count++;
        std::cout << "child.exe: " << count << std::endl;
    }
    return 0;
}

windows 下调试代码三种方式

一、vs

我们使用下图的附加到进程来调试代码,但由于我的环境有问题,vs调试不了。
在这里插入图片描述

二、windbg.exe

这是 vs 安装开发组件时自动安装的调试软件,不需要其它 IDE ,我们就可以使用它来调试代码。

  1. 路径 C:\Program Files (x86)\Windows Kits\10\Debuggers\x64
    在这里插入图片描述
  2. 打开 wingdb,将生成的 pdb 路径告诉 wingdb。“file”->“source file path…”
    在这里插入图片描述
  3. 运行 parent.exe,通过 “任务管理器” 获取 child 的 PID,我的是 16264。
    在这里插入图片描述
  4. wingdb 根据 PID 附加到进程 .“file”->“attach to a process…”
  5. 打断点
    在这里插入图片描述
    注意:
bu `child.cpp:8`

这里的 不是键盘右边的单引号,而是键盘左上角 esc 下的那个波浪键。

  1. 打完断点后,输入命令 ‘g’ 继续运行,左边会自动显示代码,我们可以用 F10 F11 来调试代码。
    在这里插入图片描述
    常用命令:
    一、基本流程控制与运行
    命令 说明 示例
    g 继续运行程序 g
    Ctrl+Break 暂停正在运行的程序(中断到调试器) -
    q 退出调试器 q

二、断点管理
命令 说明 示例
bp <address/function> 在指定地址/函数设置普通断点(需模块已加载) bp MyApp!main
bu 设置延迟断点(模块未加载时自动延迟绑定) bu MyApp!CrashFunction
bl 列出所有断点(含 ID 和状态) bl
bc 清除指定断点 bc 1
bd 禁用断点 bd 1
be 启用断点 be 1

三、查看代码与内存
命令 说明 示例
u

反汇编指定地址的代码 u MyApp!main
.asm no_code_bytes 反汇编时不显示机器码(简化输出) .asm no_code_bytes
d*
查看内存数据(db字节/dw字/dd双字等) db 0012ff44 (查看字节)
!address
查看内存地址属性(可读/可写/所属模块等) !address 0012ff44

四、堆栈与线程管理
命令 说明 示例
k 显示当前调用栈(简略) k
kvn 显示详细调用栈(带参数和框架编号) kvn
~ 列出所有线程 ~
~s 切换到指定线程 ~2s (切换到线程 2)
dv 查看当前函数的局部变量 dv
.frame 切换调用栈帧 .frame 1

五、符号与模块管理
命令 说明 示例
.sympath+

添加符号路径 .sympath+ C:\MyApp\pdb
.reload /f 强制重新加载所有符号 .reload /f
lm 列出所有已加载模块 lm
x ! 查找符号地址 x MyApp! Crash (搜索带 Crash 的函数/变量)
.load 加载调试器扩展(如 SOS) .load sos.dll (用于 .NET 调试)

六、变量与表达式
命令 说明 示例
? 计算表达式值 ? eax*4
r 查看/修改寄存器 r eax=5
dt 按类型解析内存(如结构体) dt MyStruct 0012ff44
dd L 连续查看内存块 dd 0012ff44 L4 (查看 4 个双字)

七、分析崩溃与异常
命令 说明 示例
!analyze -v 自动分析崩溃原因(包括错误码和堆栈) !analyze -v
.exr 查看异常记录 .exr 0012fe34
.dump /ma 生成完整内存转储文件 .dump /ma C:\crash.dmp
!peb 查看进程环境块(PEB)信息 !peb

八、扩展命令(SOS, etc.)
命令 说明 示例
!clrstack (SOS)查看托管代码堆栈 !clrstack
!dumpheap (SOS)查看托管堆对象统计 !dumpheap -stat
!threads (SOS)列出所有托管线程 !threads
!handle 查看进程句柄信息 !handle 4

九、典型调试流程示例

  1. 调试程序崩溃
# 启动 WinDbg 并附加到进程
windbg -pn MyApp.exe

# 自动分析崩溃原因
!analyze -v

# 查看崩溃时的调用栈
kvn

# 保存转储文件
.dump /ma C:\crash.dmp

  1. 调试死锁(多线程)
# 列出所有线程
~

# 切换到线程 3
~3s

# 查看该线程的调用栈
kvn

# 检查等待的锁
!locks

三、Qt

虽然是 Qt ,但是它在 windows 下使用的是 vs 的 msvc 编译器,调试也用的是 cdb,所以和上面的 vs 表现应当相同。

  1. 编译一次代码,选择 “调试”->“开始调试”->“attach to unstarted application”。
  2. 在下图蓝色框中将 child.exe 和具体路径填入,点击 “start watching”
    在这里插入图片描述
  3. 我们在 child.cpp 里打上断点。
    在这里插入图片描述
  4. 手动去文件夹下启动 parent.exe。
    在这里插入图片描述
  5. 快速切换至 Qt,点击“确定”。
    在这里插入图片描述
  6. 可开始用 F10 F11进行调试了
    在这里插入图片描述

linux 下调试代码两种方式

一、Qt

步骤和上面一样,Qt不愧是跨平台,就是好用。

二、GDB

如果确实没有装 Qt,这时候就得用 gdb 来调试。

  1. 编译源码
主 cmakeLists.txt 同目录下
mkdir build
cd build/
cmake .. -DCMAKE_BUILD_TYPE=Debug
make -j4
  1. 进入编译出的程序目录下
    请添加图片描述
  2. 打开终端
    执行 ./parent
    请添加图片描述
    可看到 parent 在一个终端中运行,child 在另一个终端运行。
  3. 查找 child PID
    请添加图片描述
  4. 根据 PID 启动 gdb
    请添加图片描述
  5. 使用 gdb 命令进行调试
    请添加图片描述
    常用gdb命令如下:
(gdb) break child.cpp:8   # 在 child.cpp 第 8 行设置断点
(gdb) break main        # 在 main 函数设置断点
(gdb) run               # 启动程序
(gdb) next              # 单步执行(不进入函数)
(gdb) step              # 单步执行(进入函数)
(gdb) print x           # 打印变量 x
(gdb) info breakpoints  # 查看所有断点
(gdb) continue          # 继续运行到下一个断点
(gdb) quit              # 退出 GDB

网站公告

今日签到

点亮在社区的每一天
去签到