概述

有时候我们需要调试docker容器里的c++程序,可以使用命令行运行gdb调试,但是不如vscode的图形界面调试着直观。如何使用VSCode调试docker容器里的c++程序。

目前,我知道的有三种方法:

1. 通过vscode的remote-container插件

  • 优点:通过这个插件,会让你开发的时候感觉像是在直接在操作,几乎感受不到容器的存在。
  • 缺点:
    • (1)不知道是不是服务器有点辣鸡,但是总感觉开了这个之后,ssh老是断开连接,我感觉可能是开销比较大,或者是哪里不稳定(后面我把git自动更新代码的功能关掉了,git.autorefresh: false,用手动刷新取代,感觉流畅了一些)。
    • (2)有时候在安装vscode的server时,会出现权限的问题,需要改改权限之类的。
  • 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进入容器内部进行编译"的配置
  1. 主机需要安装sshpass工具,这个工具可以在命令行直接通过-p参数输入用户密码然后进行ssh登录(当然其它的办法也可以,比如设置免密登录之类的)
sudo apt-get install sshpass
  1. 编写配置文件
    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插件更强大一些)
在这里插入图片描述

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐