目录
概述
有时候我们需要调试docker容器里的c++程序,可以使用命令行运行gdb调试,但是不如vscode的图形界面调试着直观。如何使用VSCode调试docker容器里的c++程序。
目前,我知道的有三种方法:
1. 通过vscode的remote-container插件
- 优点:通过这个插件,会让你开发的时候感觉像是在直接在操作,几乎感受不到容器的存在。
- 缺点:
- (1)不知道是不是服务器有点辣鸡,但是总感觉开了这个之后,ssh老是断开连接,我感觉可能是开销比较大,或者是哪里不稳定(后面我把git自动更新代码的功能关掉了,
git.autorefresh: false
,用手动刷新取代,感觉流畅了一些)。 - (2)有时候在安装vscode的server时,会出现权限的问题,需要改改权限之类的。
- (1)不知道是不是服务器有点辣鸡,但是总感觉开了这个之后,ssh老是断开连接,我感觉可能是开销比较大,或者是哪里不稳定(后面我把git自动更新代码的功能关掉了,
- ps:如果要调试远程服务器上的容器,那需要先使用remote-ssh连接远程服务器,再用remote-container插件。
2. 通过ssh远程调试容器内部
开启容器的时候,设置一下端口映射,让主机可以通过ssh服务访问docker容器。然后,利用gdb的远程调试功能,就可以在主机上面调试docker容器里的代码了。参考:https://lemariva.com/blog/2020/10/vscode-c-development-and-debugging-containers
- 优点:开销没那么大,只要支持gdb server和ssh两个功能即可。
- 缺点:
- (1)在安装容器的时候需要安装sshd服务才行,因为我们需要ssd连接容器内部。
- (2)除此之外,功能没有remote-container插件那么强大。
3. (个人感觉是最优解) 通过vscode的pipeTransport功能
参考文档: https://code.visualstudio.com/docs/cpp/pipe-transport。
优点:不需要下载任何东西,也不依赖ssh服务。只需要在vscode配置文件中添加命令前缀(pipeTransport参数)。
缺点:功能还是没有remote-container插件强大。
方法一 通过vscode的remote-container插件
只需要在应用商店搜索该插件即可,方法很简单,与remote-ssh插件基本一样。
在主机上面运行过docker之后(docker run ...
,可以用--name
指定名词),remote-container插件即可检索到这个容器,右键容器,再找到对应的选项,即可连接进去容器内部。方法简单,相关资料较多,具体步骤略。
可以参考博客
方法二 通过ssh远程调试容器内部
2.1、基于你原来的镜像,然后再加一层,该层主要是安装gdb、openssh-server,然后开启sshd服务,暴露出来22端口
参考dockerfile(参考连接)
# 原始镜像
FROM debian:bullseye
LABEL description="Container for use with VSCode"
# install build dependencies to build and debug
RUN apt-get update \
&& apt-get install -y g++ build-essential make cmake gdb gdbserver \
rsync zip openssh-server git
# configure SSH for communication with VSCode
RUN mkdir -p /var/run/sshd
# chpasswd用来更改root用户的密码,"root:root"即将root用户密码设置为root
# 然后使用sed修改sshd_config文件里的"PermitRootLogin prohibit-password"为"PermitRootLogin yes"
# 再修改/etc/pam.d/sshd里的“session\s*required\s*pam_loginuid.so”为"session optional pam_loginuid.so"??
RUN echo 'root:root' | chpasswd \
&& sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config \
&& sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
WORKDIR /
CMD ["/usr/sbin/sshd", "-D"]
# 开放22端口
EXPOSE 22
2.2、编译程序(手动编译 或者 vscode自动编译)
2.2.1 编译方案选择
从编译方法上,有两种方法:
(1)直接通过docker run
进入容器内部进行编译
(2)通过ssh进入容器内部进行编译
从编译途径上,有两种途径:
(1)手动输入命令,或者写个shell脚本编译。
(2)使用vscode的task.json
我个人喜欢写个脚本,里面使用docker run ...
进行编译,不过还是贴上task.json的方法,因为感觉以后可能有地方会用到。
2.2.2 [手动编译] docker run
手动写一个脚本,然后运行docker run ....
编译。
编译完成后再开始调试。
2.2.3 [自动编译] 配置tasks.json文件自动编译
(参考链接)
tasks.json文件会在调试程序前自动运行,在里面写上编译命令就可以实现"调试时自动编译程序“的功能。
2.2.3.1 [方法1] tasks.json文件:通过ssh进入容器内部进行编译"的配置
- 主机需要安装sshpass工具,这个工具可以在命令行直接通过
-p
参数输入用户密码然后进行ssh登录(当然其它的办法也可以,比如设置免密登录之类的)
sudo apt-get install sshpass
- 编写配置文件
tasks.json文件内容如下(注意编译命令需要自行设置):
{
"version": "2.0.0",
"tasks": [
{
"label": "build-in-container",
"type": "shell",
"command": "/usr/bin/sshpass",
"args": [
"-p",
"root", // "-p"参数指定密码
"ssh",
"root@localhost", // user@ip
"-p",
"2222", // 端口映射
"/source/build.sh" // 这里的编译命令需要自行设置
],
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
2.2.3.2 [方法2] tasks.json文件:通过docker run
进入容器内部进行编译"的配置
理论上也可以配置一下task.json里的命令,让他通过docker run
来编译,具体略
2.3、修改程序调试配置文件(配置launch.json文件)
launch.json文件模板示例(依然是通过sshpass,然后利用vscode的Pipe transport,vscode官方docker示例):
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "build/app", // [需修改] 目标文件
"args": [],
"stopAtEntry": true, // 默认在程序入口处,即main函数处停下来
"cwd": "/source", // [需修改] 工作目录
"environment": [],
"externalConsole": true,
// [可选] sourceFileMap是用来做主机与客机的路径映射
// 参考 https://code.visualstudio.com/docs/cpp/pipe-transport
"sourceFileMap": { "/source": "${workspaceFolder}" },
"pipeTransport": { //
"debuggerPath": "/usr/bin/gdb",
"pipeProgram": "/usr/bin/sshpass", // 类似task.json里面的
"pipeArgs": [
"-p",
"root",
"ssh",
"root@localhost",
"-p",
"2222"
],
"pipeCwd": ""
},
"MIMode": "gdb"
}
]
}
2.4、运行容器
docker run -d -p 2222:22 --security-opt seccomp:unconfined -v $PWD:/source --name gdb-cpp-image gdb-cpp-image
2.5、调试代码
快捷键F5,开启调试功能。
开启调试后,
- vscode会根据tasks.json中的内容,编译程序;编译成功后,会继续根据launch.json里面的内容,调试程序。
- 如果tasks.json不存在,则跳过编译,直接根据launch.json里面的内容,调试程序。
方法三 通过vscode的pipeTransport功能
3.1 运行容器
sudo docker run -d --security-opt seccomp:unconfined -v $hostPath:$guestpath --name gdb-cpp-image imagename
3.2 配置launch.json
launch.json文件类似于方法二的配置,不同的是里面的pipeTransport参数,这里不是通过ssh,而是直接通过docker
ssh需要docker镜像提供sshd服务,而基于docker就没有这个要求了。这是该方法的优势。
"pipeTransport": {
"pipeCwd": "${workspaceFolder}",
"pipeProgram": "docker",
"pipeArgs": [
"exec",
"-i",
"hello_gdb",
"sh",
"-c"
],
"debuggerPath": "/usr/bin/gdb"
},
3.3 launch.json 参考示例
3.3.1 参考示例1(通过docker exec运行已有容器)
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "build/a.out", // [需修改] 目标文件
"args": ["arg1", "arg2"], // 参数
"stopAtEntry": true,
"cwd": "/home/...", // [需修改] 工作目录
"environment": [],
"externalConsole": true,
// [可选] sourceFileMap是用来做主机与客机的路径映射
// 参考 https://code.visualstudio.com/docs/cpp/pipe-transport
"sourceFileMap": { "/source": "${workspaceFolder}" },
"pipeTransport": {
"pipeCwd": "${workspaceFolder}",
"pipeProgram": "docker",
"pipeArgs": [
"exec", // exec是运行已有的容器,也可以使用docker run,原理差不多
"-i",
"gdb-cpp-image", // 镜像名字
"sh",
"-c"
],
"debuggerPath": "/usr/bin/gdb"
},
"MIMode": "gdb"
}
]
}
3.3.2 参考示例2(通过docker run启动新容器)
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "build/GCN3_X86/gem5.opt", // [需修改] 目标文件
"args": [""], // 参数
"stopAtEntry": true,
"cwd": "/home/lmy/gem5-gcn3/gem5-default", // [需修改] 工作目录
"environment": [],
"externalConsole": true,
// [可选] sourceFileMap是用来做主机与客机的路径映射
// 参考 https://code.visualstudio.com/docs/cpp/pipe-transport
"sourceFileMap": { "/source": "${workspaceFolder}" },
"pipeTransport": {
"pipeCwd": "${workspaceFolder}",
"pipeProgram": "docker",
"pipeArgs": [
"run",
"-i",
"--security-opt","seccomp=unconfined",
"--volume","/home/lmy/gem5-gcn3:/home/lmy/gem5-gcn3",
"-w","/home/lmy/gem5-gcn3/gem5-default",
"gem5-debug-image", # [需修改] 镜像名称
"sh",
"-c"
],
"debuggerPath": "/usr/bin/gdb"
},
"MIMode": "gdb"
}
]
}
3.4 调试
按键F5
断点不生效的解决办法
我测试方法三的时候,有时候出现设置了断点,但是断点不能准确被触发,貌似是因为vscode一开始没检索到那个断点的位置,于是我尝试在调试控制台直接用-exec b 断点位置
,发现可以正常触发。
如下图,vscode提示Module containing this breaknoint has not vet loaded or the breaknoint address could not be obtained
如下图,断点被成功触发,虽然显示是异常,但是不影响。
不过这样需要每次调试运行之后都输入一次。(在便捷性上还是remote-container插件更强大一些)