这个最主要的目的我原本还是用于竞赛的(如果出异常的话),可惜中文网站如果按照这个标题查根本找不到相关内容。 下面我说一下操作方法,首先C/C++如果要想调试,必须自己写launch.json和tasks.json。使用Coderun插件的请考虑自己通过加输出调试。 这里我简单讲讲,但不细致介绍。 首先是launch.json: 在configurations数组中的所有项其实是会被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捕获异常到某个内部的符号表时打开相应的文件。 以上是没有配置为false的情况,段错误发生在内部时必然打开。 可以看见不会打开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++"
}
]
}
|