vscode在MacOS下无法调试C/C++语言 查病 和 解决

调试,是每个Coder必须要具备的能力,然而具备了能力却没有环境和工具的支持,那岂不是“巧妇难为无米之炊”?
我就遇上了这样“操蛋”的事情,事情是这样的,且容我慢慢道来。

起因【笔试中无法调试0w0!

昨天,也就是大疆笔试,做三道算法题的时候,调试没法用了(其实是早就没法用了,只是一直没管)。然后就计划着做完笔试之后把这个事情搞定了,于是回到家就开始一直google查查查。

怀疑一:gdb没有签名

这个问题可就是历史遗留问题了,Mac把gcc和g++链接到clang和clang++之后就能够感觉的出来,苹果对gnu这些东西不是很待见。

每次brew升级完gdb之后,gdb的代码签名就失效了,只好每次都重新应用签名。好在签名的命令还算简单:

1
2
3
# gdb-code-sign keychain中gdb代码签名的名字
# /usr/local/bin/gdb gdb命令的软链接地址
codesign -fs gdb-code-sign /usr/local/bin/gdb

生成签名证书

不过还是要说说这个gdb-code-sign的这个certification要怎么generate出来。

运行Keychan Access.app,也就是钥匙串,这个东西管理了记录在系统中的所有钥匙,包括wifi密码,网络证书等等,只要有系统管理员的密码就可以随意查看(chrome里头记录的密码看不到,因为chrome不把它们记录在系统中,但是Safari的可以)。

Keychain Access.app

选择 Keychain Access -> Certificate Assistant -> Create a Certifitate…,在名字处取一个自己能够分辨的名字,然后选择代码签名(Code sign),在Mojave版本的MacOS中就只要点击确定两下就可以生成了。

Create Certificate

生成证书之后,讲生成的三个同名项目全都移到System目录下,分别是证书(Self-signed Certificate)、公钥(Public Key)和私钥(Private Key)。

接着双击生成的证书,将信任一栏改为任何时候都信任。

Trust the Certificate

这时候就可以关闭Keychain了。

给gdb应用签名

接着打开终端,使用sudo -i进入root权限。

我一开始使用的是上面的那条命令,但是怎么都签不好,开始调试之后都显示(例如我调试一个叫做sleep的程序):

1
2
3
4
(gdb) r
Starting program: /Users/chenke/Documents/ProgramerMaker/C:C++/C/test/sleep
Unable to find Mach task port for process-id 65245: (os/kern) failure (0x5).
(please check gdb is codesigned - see taskgated(8))

只好又去查。发现有两个解决办法:

  1. 运行gdb的时候使用sudo gdb xxx.out
  2. 在Mojave里头需要附加一个xml的配置文件,并且签名的命令也要做出一些改动。

因为后面要给vscode用的调试器,总不能每次调试都输一次密码吧。所以不行,得一劳永逸。所以进行第二个解决办法。

将一个文件命名为gdb.xml或者别的什么都可以,然后写入下面的内容,然后保存。

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.debugger</key>
<true/>
</dict>
</plist>

在本目录下,使用root权限,重新运行签名命令:

1
codesign --entitlements gdb.xml -fs gdb-code-sign /usr/local/bin/gdb

签名完了之后,再运行gdb调试,这回就好了一些了,可以run,但是会卡住。。。显示:

1
2
3
4
(gdb) r
Starting program: /Users/chenke/Documents/ProgramerMaker/C:C++/C/test/sleep
[New Thread 0xd03 of process 65791]
[New Thread 0xf03 of process 65791]

如果gdb能用了,但是因为什么原因导致调试无法进行,这就很难受。所以就放弃gdb了…

折腾lldb

重新给lldb签名之后,发现lldb能用,而且跑的很欢块,并且很开心的一点就是,无论是用gcc还是clang,还是g++或是clang++编译的文件,都可以使用lldb调试,这也在之后的编译工具的选择上给出了多个选项。

怀疑二:vscode没有配置好

其实到了这里,我就有点虚了,因为配置文件一直都是一套的,不应该存在以前能用但是现在不能用的情况,毕竟微软的东西不能说不向后兼容吧- -!

经过一番查找之后,发现最新的配置文件版本已经到了2.0.0,而我还在用0.2.0,哈哈,所以说还是过时了【手动捂脸。不过怎么说,就算是过时,那也不应该让编译和运行出错呀。

经过了一番修改,终于将vscode的运行配置更新了一遍。C和C++两套配置因为要调用不同的编译器,编译命令也不同,所以得分成两块,C用gcc或者clang或者llvm-gcc来编译,C++用g++或者clang++或者llvm-g++来编译。

launch.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"preLaunchTask": "build c",
"name": "Launch C",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/${fileBasenameNoExtension}",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": true,
"MIMode": "lldb",
}
]
}

经过这顿折腾,我也理解了一些其中的参数的意义所在,就比如:

  • preLaunchTask

运行调试任务前需要运行的任务,这个任务要在tasks.json中配置,而这里填写的就是名字,或者标签等标识这个task用的属性。

  • program

要运行的程序的名字,这个和tasks.json中编译选项中的文件路径是对应的,必须相同,不然vscode就无法找到这个文件来运行。

  • args

运行调试的参数,会直接传入lldb或者gdb的。

  • stopAtEntry

字面意思,在调试的时候会在入口处暂停,点击继续才会开始调试,例如C/C++中的main函数,或者Python中的第一条语句,或者Golang中的 func main()

  • externalConsole

外部的控制台,这个我是启用的,因为如果不启用的话,在vscode自带的DEBUG CONSOLE中只能输出程序信息,不能输入(或许是有办法的,但是我不会而已哈哈哈)。在调试程序的时候肯定会需要一些交互,所以这个还是要打开的。

  • MIMode

调试模式,这里选择的就是调试器的种类,选择lldb或者是gdb,我这里选择的是lldb,因为编译已经可以用gcc的优化了,而在MacOS上用lldb会更加的的心应手一些,毕竟也算是系统级的“亲儿子”。

tasks.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"tasks": [
{
"label": "build c",
"type": "shell",
"command": "gcc-9",
"args": [
"${file}",
"-o",
"${workspaceFolder}/${fileBasenameNoExtension}",
"-g",
// "-std=c++17"
],
"group": "build",
"presentation": {
"echo": true,
"focus": false,
"panel":"shared",
"showReuseMessage": true,
"clear": false,
"reveal": "silent"
},
}
],
"version": "2.0.0"
}
  • command

编译使用的命令,也就是编译器,这里要根据需求改变编译器的选择,例如gcc,g++,clang,clang++,llvm-gcc,llvm-g++这些。

  • args

编译选项,这里头的选项要按照顺序来输入,例如编译一个c文件是要用到gcc sleep.c -o sleep -g -std=c11,那么args里面的顺序就是要按照这样来填写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
"args": [
"${file}",
"-o",
"${workspaceFolder}/${fileBasenameNoExtension}",
"-g",
"-std=c11"
],```

### c_cpp_properties.json

虽然这个文件对编译调试不会造成什么影响,但是也放上来作为参考。这个c_cpp_properties是对vscode的自动补全有影响,它会根据系统类型去选择要引用的位置的文件,并检测联想出来的结果。

```json
{
"configurations": [
{
"name": "Mac",
"includePath": [
"${workspaceFolder}",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1",
"/usr/local/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/10.0.1/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include"
],
"defines": [],
"intelliSenseMode": "clang-x64",
"browse": {
"path": [
"${workspaceFolder}",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1",
"/usr/local/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/10.0.1/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
},
"macFrameworkPath": [
"/System/Library/Frameworks",
"/Library/Frameworks"
],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17"
},
{
"name": "Linux",
"includePath": [
"/usr/include",
"/usr/local/include",
"${workspaceFolder}"
],
"defines": [],
"intelliSenseMode": "clang-x64",
"browse": {
"path": [
"/usr/include",
"/usr/local/include",
"${workspaceFolder}"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
}
},
{
"name": "Win32",
"includePath": [
"C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include",
"${workspaceFolder}"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"intelliSenseMode": "msvc-x64",
"browse": {
"path": [
"C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include/*",
"${workspaceFolder}"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
}
}
],
"version": 4
}

到了这一步还是不行,使用vscode运行调试的时候,调试控制器台能够显示调试信息,但是调用的Terminal.app终端却没有程序运行的信息,也无法输入变量。

最终发现是终端模拟器程序的问题

在某个偶然,我发现我把iTerm关了之后,Terminal就能够承载调试的程序了,并且在重新开启iTerm之后,还是一样可以调试。

debug success

解决办法v1.0

(重要的事情说三遍)

关闭iTerm等第三方终端
关闭iTerm等第三方终端
关闭iTerm等第三方终端

虽然问题解决了(可以在vscode上愉快的调试了,并且可以输入输出变量和结果),但是让我还是有点匪夷所思,决定继续追查下去。

我在vscode中看到一个配置选项:"terminal.external.osxExec": "iTerm.app"这里原先的值是Terminal.app,后来我发现之后,根据字面意思,意思就是外部终端调用的是哪个程序,索性我改成了iTerm.app。

改成iTerm.app之后,我把iTerm关了,准备让vscode自动调用打开iTerm运行程序的时候,我发现系统弹出了一个辅助控制的窗口显示“是否允许iTerm控制Terminal”,我选了“Not Allow”。选了之后,系统调用出来的还是Terminal.app,并且上面变成了什么都没有,和之前一模一样。

为了做个对比,我特意去装了个Hyper终端(后来莫名发现这东西颜值高又好用除了有点不稳定),然后将terminal.external.osxExec改为Hyper.app,同样的,在运行调试的时候,和之前一样弹出了窗口,我选择了Allow。这次和相同的是,弹出来的还是Terminal.app,但是不同的是可以输入输出了!这让我really开心。

于是乎,我来到辅助功能这里一探究竟:
System Preferences -> Security & Privacy -> Privacy -> Automation

终于让我“盲生”发现了“华点”:

Automation setting

解决办法v2.0

在打开iTerm.app下面对Terminal.app的自动控制之后,再将terminal.external.osxExec改为iTerm的时候,同样的,也能进行正常的调试了。

我的理解是,系统没有允许iTerm去控制Terminal,但是vscode调用了iTerm并且权限下放的不够多,所以就没法使得Terminal“接住”这个调试任务(可能是被系统拦截了),所以就什么都看不到。

还想吐槽的是,微软的这个terminal.external.osxExec简直就是摆设,设置了也没法更换调试用的终端,并且还会导致这种玄妙的权限问题。


有机会再说说Hyper的安装和设置,嘿嘿,还挺有意思的~

Author: SmallXeon
Link: https://hexo.chensmallx.top/2019/08/07/vscode-debug-C-itermNternimal/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
一些推广链接
几个便宜量大的小✈场: FASTLINK, YToo, 论坛邀请注册: ,
便宜量大但是稳定性不足的VPS: Virmach, 价格略贵但好用的VPN: , ,