招人啦,我们的团队是字节跳动终端基础架构团队,研发领域包括但不限于APP框架和基础组件、研发体系、自动化测试、APM、跨平台框架、端智能解决方案、Web开发引擎、Node.js基建以及下一代移动开发技术的预研等,目前在北京、上海、广州、深圳、杭州五地都均设有研发中心
详细见 https://zhuanlan.zhihu.com/p/149024639
由于业务里涉及到js和c++互相调用,为了调试方便,研究下如何在vscode里混合调试c++和js代码。
效果如下,代码见 https://github.com/hardfist/cpp2wasm
知乎视频www.zhihu.comc++开发和调试
在讲混合调试之前,先简单介绍下vscode下c++的开发和调试,后续所有都是在Mac OS环境下操作的。
前置条件
- 安装c++套件 c++ extension
- 安装lldb套件 vscode-lldb
- 确认安装clang
创建项目
$ mkdir helloworld && cd helloworld && code .
添加helloworld
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<string> msg {"Hello", "C++", "World", "from", "VS Code", "and the C++ extension!"};
for (const string& word : msg)
{
cout << word << " ";
}
cout << endl;
}
此时借助c++ 插件的intellisense功能我们就可以自动补全了
![2eaf4489f7d738f02c487ae3278f4b5e.png](https://i-blog.csdnimg.cn/blog_migrate/06588532d6fedf012cfbf7987b364abc.jpeg)
编译helloworld
手动编译
$ clang++ helloworld.cpp -o helloworld --std=c++11 // 因为使用了c++11语法,因此需要--std=c++11
创建task进行编译
为了避免每次修改代码都需要手动输入编译指令,我们可以创建编译task 通过 Terminal > Configure Default Build Task 就可以新建编译task,此时会有一些备选项,我们这里选择 C/C++ clang++ build active file
![4a7a4a2712f25a08dc252c442c44b648.png](https://i-blog.csdnimg.cn/blog_migrate/134dfbb95878d65d2f97d67d023ab8e9.jpeg)
默认生成如下配置
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "C/C++: clang++ build active file",
"command": "/usr/bin/clang++",
"args": [
"-std=c++11", // 自己配置,支持c++11
"-g",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
默认生成的配置不支持c++11,需要我们自行配置-std=c++11,支持c++11的编译。
调试
支持完编译后我们就可以继续进行调试,创建launch.json,添加配置,选择 c++启动 (或者使用lldb)
![396c97742662493905e7b96962cbae4c.png](https://i-blog.csdnimg.cn/blog_migrate/ca95fa5162f009583c80a884c6049980.jpeg)
修改program为当前文件对应的bin文件,修改后配置如下
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(lldb) 启动",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}/${fileBasenameNoExtension}",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "lldb"
}
]
}
此时在helloworld.cpp里添加断点,点击debug按钮既可以进入调试
![3681f8d1255d1dd014b6ef3eae1953f2.png](https://i-blog.csdnimg.cn/blog_migrate/4a838566e42613dee00cb269f724f401.jpeg)
js的调试
相比于c++的调试,js就简单很多,添加一个launch.json即可,选择 Node.js: Launch Program
![d8f5be8190593a76a65e088cf6a5e959.png](https://i-blog.csdnimg.cn/blog_migrate/642a64e7c32d01005a06cd66ab5c87e2.jpeg)
修改programe为当前文件,修改后配置如下
{
"name": "Launch Program",
"program": "${fileDirname}", // 调试当前选中的文件
"request": "launch",
"skipFiles": [
"<node_internals>/**" // 默认跳过node内部的js文件
],
"type": "pwa-node"
},
混合调试
因为c++和nodejs走的是不同的调试协议,因此很难通过一个configuration来调试两者。 我们以node官方的example https://github.com/nodejs/node-addon-examples/tree/master/1_hello_world/nan, 看看如何进行混合调试
addon编译
创建addon的编译script
"scripts": {
"start": "node index.js",
"build:dev": "node-gyp -j 16 build --debug", // 编译带上debug信息
"build": "node-gyp -j 16 build",
"rebuild:dev": "node-gyp -j 16 rebuild --debug",
"rebuild": "node-gyp -j 16 rebuild",
"clean": "node-gyp clean"
},
创建preLaunchTask
调试程序前,我们需要预先编译好可以debug的addon,因此我们创建一个preLaunchTask,选择rebuild:dev,其负责编译一个可以debug的addon版本
![5cb808618eab6e3990b036a9bc418a8a.png](https://i-blog.csdnimg.cn/blog_migrate/3431de2fab52cd276d11bb1904320ed8.jpeg)
创建launch.json
我们新创建一个启动node的launch,不过我们这里使用lldb来启动node,配置如下
{
"type": "lldb",
"request": "launch",
"name": "lldb:node",
"program": "/Users/yj/.nvm/versions/node/v12.16.1/bin/node", // 指向node入科
"args": ["examples/addon/index.js"], // 指向js入口
"cwd": "${workspaceFolder}"
},
此时我们启动lldb:node就可以进入node的addon进行debug
![5fdcdd42de93e58e3dba02bc85c92cae.png](https://i-blog.csdnimg.cn/blog_migrate/ae1e2a5d8832896d0eb6b7475d834a8c.jpeg)
这里存在的问题在于,我们这里实际上是使用lldb来调试node程序(此时的addon实际上是一个动态库),lldb实际上对js的调试是无感知的,因此我们这样实际是无法调试js的,在js里打下的断点也无法进入。
nodejs调试
实际上node的js里的调试功能实际上是通过chrome debugger protocol协议来实现的,因此我们只需要启动js的时候,开启inspector协议就然后就可以attach到js上进行调试。 修改配置,开启inspector协议
{
"type": "lldb",
"request": "launch",
"name": "lldb:node",
"program": "/Users/yj/.nvm/versions/node/v12.16.1/bin/node",
"args": ["--inspect-brk","examples/addon/index.js"], // 开启inspector协议
"cwd": "${workspaceFolder}"
},
此时我们重新启动lldb:node,发现在终端出来个websocket地址
![8c262749117ccce4af655f7c7d216f46.png](https://i-blog.csdnimg.cn/blog_migrate/ef50cdee72fa825ddbf0f6540b207e6a.png)
此时有实际上有两种方式来调试js了
在chrome里开个ws的地址进行调试
![970231efebebb553b9ccd59474230a76.png](https://i-blog.csdnimg.cn/blog_migrate/b1b193b098eb3b9bd4760c4285b650e5.jpeg)
此时我们虽然可以在chrome里调试js,在vscode里调试c++,但是来回穿梭仍然不够方便
在vscode里调试js
实际上当node开启inspector协议的时候,不仅可以通过ws attach到node的js上,实际上也可以通过http:port attach到js上。实际上我们的ws地址也是从这个http:port获取来的 nodejs在开启inspect的时候默认会启动一个inspect server,其地址为http:127.0.0.1:${inspect_port}
,其inspect_port默认为9229,当然也可以通过`node --inspect-brk --port:${inspect_port}`来指定其端口。 其暴露了一个接口/json
,通过该接口即可获得其对应的websocket地址
![3a44da2bca06be466230dd1ff028fb60.png](https://i-blog.csdnimg.cn/blog_migrate/bfff528cb04c14bb4bde91003ee035b1.jpeg)
下面我们来通过vscode来调试js,首先创建一个launch任务,此时我们选择 Node.js: attach
![f1c564a821518ca984c448ec78956518.png](https://i-blog.csdnimg.cn/blog_migrate/b8470fcac21cd01ea96f3aca0856cb48.jpeg)
我们选择通过port来attach到js上,配置如下
{
"type": "node",
"request": "attach",
"name": "node:attach",
"port": 9229, // 绑定到node.js默认的inspect的端口
"skipFiles": [
"<node_internals>/**"
]
},
此时我们只需要启动lldb:node启动调试,其会自动的停止在第一行js代码上。这样我们就实现了在vscode里混合调试c++和js
compound模式
借助于vscode的launch的compound功能,我们甚至可以避免手动的切换debugger,达到真正的一键调试,配置如下
"compounds": [
{
"name": "node_c++",
"configurations": ["lldb:node","node:attach"]
}
]
这样我们点击node_c++功能就可以实现一键调试