VScode c++用gdb单步调试和异常捕获绕过标准库文件

这个最主要的目的我原本还是用于竞赛的(如果出异常的话),可惜中文网站如果按照这个标题查根本找不到相关内容。
下面我说一下操作方法,首先C/C++如果要想调试,必须自己写launch.json和tasks.json。使用Coderun插件的请考虑自己通过加输出调试。
这里我简单讲讲,但不细致介绍。
首先是launch.json:
在configurations数组中的所有项其实是会被vscode识别成vscode调试
其中的一个选项。
一般直接在同一目录下编译一个文件.exe(tasks.json配置)
所以最终的launch.json的一个C++用G++编译的configurations可以这样配置:

		{
            "name": "G++",
            "type": "cppdbg",
            "request": "launch",
            "program": "${fileDirname}\\${fileBasenameNoExtension}.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": true,
            "internalConsoleOptions": "neverOpen",
            "MIMode": "gdb",
            "miDebuggerPath": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "G++"
        }

其中,miDebuggerPath是配置gdb程序的关键,这里我直接把MinGW的bin文件塞到了Path环境变量里,所以这样是肯定能访问到的。
接下来去配置preLaunchTask项的任务。直接在.vscode文件夹里(即这个launch.json的文件夹)新建一个tasks.json:

{
    "tasks": [
        {
            "type": "shell",
            "label": "G++",
            "command": "g++",
            "args": [
                "-g",
                "${file}",
                "-o",
                "${fileDirname}\\${fileBasenameNoExtension}.exe",
                "-Wall",
                "-Wno-unused-but-set-variable",
                "-DDEBUG"
            ],
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": false,
                "panel": "shared",
                "showReuseMessage": false,
                "clear": false
            }
        },
        {
            "type": "shell",
            "label": "G++ Masm",
            "command": "g++",
            "args": [
                "-S",
                "${file}",
                "-o",
                "${fileDirname}\\${fileBasenameNoExtension}.s",
                "-masm=intel",
                "-Wno-unused-but-set-variable",
                "-DDEBUG"
            ],
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": false,
                "panel": "shared",
                "showReuseMessage": true,
                "clear": false
            }
        }
    ],
    "version": "2.0.0"
}

同理,此处的command是shell命令,可以理解为type是shell时,相当于用cmd执行command的命令,然后后面的args串就是编译。
这种方案的编译+调试过程是单文件的,而且每次启动都会执行。做增量编译的话,不建议用这种方案,可以考虑cmake,或者自己写一个编译代理控制台程序:先检查是否有配置文件,没有则编译,然后配置文件的编译时间是否跟进程的创建时间一致,如果不一致也编译。都一致的话就跳过。这种方案可以设计成非常粗陋的增量编译方案。增量编译主要是最开始那个时间而已,我对此没有太大的需求,我甚至自己都懒得这么写。

接下来是关键,如果使用调试了之后,vscode采用的是step,所以单步调试只要gdb带有文件输出,就会定位到那个头文件上(如果自己设调试控制台里-exec next的话也就是说执行gdb的next是不会跳到文件的)。所以会打开标准库的文件,咋一看好像没啥问题,但是对于比赛来说就非常容易判断为作弊。而且,如果你采用vscode的intellisense的话,专门针对于c++配置编译器路径实现补全也必须要先标定位置,无论如何,把include之类的删了是非常危险的,最好的办法还是防止它打开。

这里我通过翻阅大量的资料得出的结论是必须配置gdb的skip(可能还有防止gdb导入符号表的),而且还要关闭一个vscode的配置(这个配置是会影响是否捕获发生在标准库内部的常规异常)

回到launch.json:
在setupCommands数组里配置成如此:

"setupCommands": [
    {
        "description": "为 gdb 启用整齐打印",
        "text": "-enable-pretty-printing",
        "ignoreFailures": true
    },
    {
        "description": "防止gdb打开标准库函数",
        "text": "-interpreter-exec console \"skip -rfu std::.*\"",
        "ignoreFailures": false
    }
]

其中-interpreter-exec console是在进程进入调试阶段配置一条命令,该字符串只能包含一个命令行(即\"\"内部的\n不会被分隔为多个语句执行只会执行最后一个),如果要输入多个行的话,要在\"\"外面写上\n再套一串-interpreter-exec console \"...\"
skip -rfu std::.*是gdb的skip命令,它的作用是使用正则串的方法绕过标准库名称空间的所有函数(这个指令可以直接百度搜到)。因为函数才是运行流程的本质,因此这样就可以绕过所有标准库发生的step(单步调试)。当然,这个的gdb至少要7.3以上。直接下载8.1的应该没这个问题。如果采用DevC++弄的mingw可能就用不了(不支持skip -rfu)。
这里配置完了单步调试的防止进入,差不多让step功能等价于next,对于非标准库名称空间可能还会有问题,比如boost,可以考虑再补个这个名称空间的。

但是即使这样还是无法完全避免打开文件,比如完成以上配置后,在标准库内部执行上发生的异常就会被捕获并打开标准库文件,还有一个必须要加的就是vscode不加载符号表的作用。
还是launch.json:

"symbolLoadInfo": {
    "loadAll": false,
    "exceptionList": ""
}

在configurations的该块中加入symbolLoadInfo并将loadAll配置为false。
这样可以避免gdb捕获异常到某个内部的符号表时打开相应的文件。
没有加loadAllfalse的
以上是没有配置为false的情况,段错误发生在内部时必然打开。
loadAll为true
可以看见不会打开erase一个end时出现的错误的stl_algobase.h
但有问题的是,如此也定位不到了具体在哪个行发生问题了(目前我还没有更好的解决方案)。你必须自行在关键位置加断点,如果此处的单步调试出问题exception了,那就说明这里有问题。

同时,只配置其中一个都是不行的,比如skip只能防止单步调试的打开,而异常照样打开,而配置loadAll看起来的意思是不加载符号表,但事实上单步调试一样会开,它仅有防止异常打开外部符号表的功能。

提供一个完整的launch.json参考:

{
    "version": "0.2.0",
    "configurations": [

        {
            "name": "G++",
            "type": "cppdbg",
            "request": "launch",
            "program": "${fileDirname}\\${fileBasenameNoExtension}.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": true,
            "internalConsoleOptions": "neverOpen",
            "MIMode": "gdb",
            "miDebuggerPath": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },{
                    "description": "防止gdb打开标准库函数",
                    "text": "-interpreter-exec console \"skip -rfu std::.*\"",
                    "ignoreFailures": false
                }
            ],
            "symbolLoadInfo": {
                "loadAll": false,
                "exceptionList": ""
            },
            "preLaunchTask": "G++"
        }
    ]
}
  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值