|
@ -0,0 +1,12 @@
|
|||
If submitting a bug please make sure
|
||||
|
||||
- [ ] If you are using gdb
|
||||
- [ ] `gdb --version` >= 7.7.1
|
||||
- [ ] it works on the command line with `gdb`
|
||||
- [ ] `cwd` and `target` are properly set
|
||||
- [ ] If you are using lldb
|
||||
- [ ] `lldb --version` >= 3.7.1
|
||||
- [ ] it works on the command line with `lldb-mi`
|
||||
- [ ] `cwd` and `target` are properly set
|
||||
|
||||
Screenshots are helpful but not required
|
|
@ -0,0 +1,100 @@
|
|||
name: Release
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
strategy:
|
||||
matrix:
|
||||
os: ['ubuntu-latest']
|
||||
node-version: ['16.x']
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
#
|
||||
# Basic Setup
|
||||
#
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
#
|
||||
# Audit Versions for Consistency
|
||||
#
|
||||
- name: Query package.json Version
|
||||
id: package
|
||||
run: node -e "console.log('::set-output name=version::' + require('./package.json').version)"
|
||||
|
||||
- name: Query package-lock.json Version
|
||||
id: package-lock
|
||||
run: node -e "console.log('::set-output name=version::' + require('./package-lock.json').version)"
|
||||
|
||||
- name: Query Latest Changelog Version
|
||||
id: changelog
|
||||
shell: bash
|
||||
run: echo ::set-output name=version::$(grep --perl-regexp "^# " --max-count=1 CHANGELOG.md | awk '{print $2}')
|
||||
|
||||
- name: Audit package.json/package-lock.json/CHANGELOG.md/git tag Version Consistency
|
||||
shell: bash
|
||||
run: >
|
||||
test ${{ steps.package.outputs.version }} = ${{ steps.package-lock.outputs.version }} -a \
|
||||
${{ steps.package.outputs.version }} = ${{ steps.changelog.outputs.version }} -a \
|
||||
refs/tags/v${{ steps.package.outputs.version }} = ${{ github.ref }}
|
||||
|
||||
#
|
||||
# Install Dependencies
|
||||
#
|
||||
# NOTE:
|
||||
# Use the `clean-install` instead of just `install` so that the versions identified in the
|
||||
# package-lock.json file are used instead of attempting to install later versions that might
|
||||
# exist. This drives consistency between what the developers have been using and what is to
|
||||
# be released.
|
||||
#
|
||||
# NOTE:
|
||||
# Use the `--no-optional` switch to prevent installation of the `ssh2` optional dependency
|
||||
# (i.e., `cpu-features`) package which is used to provide accelerated crypto functionality,
|
||||
# but which is a native add-on and would require platform specific packages.
|
||||
#
|
||||
- name: Install Module Dependencies
|
||||
run: npm clean-install --no-optional
|
||||
|
||||
#
|
||||
# Package and Upload Extension
|
||||
#
|
||||
# NOTE:
|
||||
# The "vscode:prepublish" script in package.json will be executed to compile the extension
|
||||
# prior to packaging.
|
||||
#
|
||||
- name: Package Extension into .vsix file
|
||||
id: asset
|
||||
shell: bash
|
||||
run: >
|
||||
npx vsce package;
|
||||
echo ::set-output name=vsix_path::$(ls *.vsix)
|
||||
|
||||
- name: Upload .vsix file to Github as release asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: ${{ steps.asset.outputs.vsix_path }}
|
||||
asset_name: ${{ steps.asset.outputs.vsix_path }}
|
||||
asset_content_type: application/zip
|
||||
|
||||
#
|
||||
# Publish Extension
|
||||
#
|
||||
- name: Publish to VSCode Extension Marketplace
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VS_MARKETPLACE_TOKEN }}
|
||||
run: npx vsce publish --packagePath ${{ steps.asset.outputs.vsix_path }}
|
||||
|
||||
- name: Publish to Open VSX Registry
|
||||
env:
|
||||
OVSX_PAT: ${{ secrets.OPEN_VSX_TOKEN }}
|
||||
run: npx ovsx publish ${{ steps.asset.outputs.vsix_path }}
|
|
@ -0,0 +1,4 @@
|
|||
out
|
||||
node_modules
|
||||
*.vsix
|
||||
.vscode-test
|
|
@ -0,0 +1,20 @@
|
|||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- stable
|
||||
os:
|
||||
- osx
|
||||
- linux
|
||||
|
||||
before_install:
|
||||
- if [ $TRAVIS_OS_NAME == "linux" ]; then
|
||||
export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0;
|
||||
sh -e /etc/init.d/xvfb start;
|
||||
sleep 3;
|
||||
fi
|
||||
|
||||
script:
|
||||
- |
|
||||
npm install
|
||||
tsc
|
||||
node out/src/test/runTest.js
|
|
@ -0,0 +1,50 @@
|
|||
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "npm"
|
||||
},
|
||||
{
|
||||
"name": "code-debug server",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [ "--nolazy" ],
|
||||
"program": "${workspaceRoot}/src/gdb.ts",
|
||||
"stopOnEntry": false,
|
||||
"args": [ "--server=4711" ],
|
||||
"sourceMaps": true,
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/out/**/*.js"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "npm"
|
||||
},
|
||||
{
|
||||
"name": "Launch Tests",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceRoot}",
|
||||
"--extensionTestsPath=${workspaceRoot}/out/src/test/suite/index"
|
||||
],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "npm"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"editor.insertSpaces": false,
|
||||
"files.exclude": {
|
||||
"out": false // set this to true to hide the "out" folder with the compiled JS files
|
||||
},
|
||||
"search.exclude": {
|
||||
"out": true // set this to false to include "out" folder in search results
|
||||
},
|
||||
"typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Available variables which can be used inside of strings.
|
||||
// ${workspaceRoot}: the root folder of the team
|
||||
// ${file}: the current opened file
|
||||
// ${fileBasename}: the current opened file's basename
|
||||
// ${fileDirname}: the current opened file's dirname
|
||||
// ${fileExtname}: the current opened file's extension
|
||||
// ${cwd}: the current working directory of the spawned process
|
||||
|
||||
// A task runner that calls a custom npm script that compiles the extension.
|
||||
{
|
||||
"version": "2.0.0",
|
||||
|
||||
// Run in a shell so "npm" command is properly resolved to "npm.cmd" on Windows systems.
|
||||
"type": "shell",
|
||||
|
||||
// we want to run npm
|
||||
"command": "npm",
|
||||
|
||||
// we run the custom script "compile" as defined in package.json
|
||||
"args": ["run", "compile", "--loglevel", "silent"],
|
||||
|
||||
// The tsc compiler is started in background mode
|
||||
"isBackground": true,
|
||||
|
||||
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"tasks": []
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
.vscode/**
|
||||
typings/**
|
||||
out/test/**
|
||||
test/**
|
||||
src/**
|
||||
**/*.map
|
||||
.gitignore
|
||||
tsconfig.json
|
||||
vsc-extension-quickstart.md
|
|
@ -0,0 +1,82 @@
|
|||
# 0.26.0
|
||||
|
||||
* vscode dependency was increased from 1.28 to 1.55 along with the debug-adapter protocol to get rid of some outdated dependencies (@GitMensch)
|
||||
* SSH2 module updated from deprecated 0.8.9 to current 1.6.0 (@GitMensch),
|
||||
allowing connections with more modern key algorithms, improved error handling (including user messages passed on) and other improvements.
|
||||
See [SSH2 Update Notices](https://github.com/mscdex/ssh2/issues/935) for more details.
|
||||
* Path Substitutions working with attach+ssh configuration #293 (@brownts)
|
||||
* Path Substitutions working with LLDB #295 (@brownts)
|
||||
* Path Substitutions working with Windows-Style paths #294 (@brownts)
|
||||
* Breakpoints may be deleted when not recognized correctly #259 fixing #230 (@kvinwang)
|
||||
* New `stopAtConnect` configuration #299, #302 (@brownts)
|
||||
* New `stopAtEntry` configuration to run debugger to application's entry point #306 (@brownts)
|
||||
* New `ssh.sourceFileMap` configuration to allow multiple substitutions between local and ssh-remote and separate ssh working directory #298 (@GitMensch)
|
||||
* fix path translation for SSH to Win32 and for extended-remote without executable (attach to process) #323 (@GitMensch)
|
||||
* fix for race conditions on startup where breakpoints were not hit #304 (@brownts)
|
||||
* prevent "Not implemented stop reason (assuming exception)" in many cases #316 (@GitMensch),
|
||||
initial recognition of watchpoints
|
||||
* fix additional race conditions with setting breakpoints #313 (@brownts)
|
||||
* fix stack frame expansion in editor via use of the `startFrame` parameter #312 (@brownts)
|
||||
* allow specification of port/x11port via variable (as numeric string) #265 (@GitMensch)
|
||||
* Extra debugger arguments now work in all configurations #316, #338 fixing #206 (@GitMensch, @brownts)
|
||||
* Attaching to local PID now performs initialization prior to attaching #341 fixing #329 (@brownts)
|
||||
|
||||
# 0.25.1
|
||||
|
||||
* Remove the need for extra trust for debugging workspaces per guidance "for debug extensions" as noted in the [Workspace Trust Extension Guide](https://github.com/microsoft/vscode/issues/120251#issuecomment-825832603) (@GitMensch)
|
||||
* Fix simple value formatting list parsing with empty string as first argument (@nomtats)
|
||||
* don't abort if `set target-async` or `cd` fails in attach (brings in line with existing behavior from launch)
|
||||
|
||||
# 0.25.0
|
||||
|
||||
(released May 2020)
|
||||
|
||||
* Add support for path substitutions (`{"fromPath": "toPath"}`) for GDB and LLDB (@karljs)
|
||||
* Support up to 65535 threads instead of 256 threads (@ColdenCullen)
|
||||
* Improve thread names on embedded GDB, makes not all threads always have the same name (with @anshulrouthu)
|
||||
|
||||
# 0.24.0
|
||||
|
||||
* Added zig as supported language.
|
||||
* Fix example Debug Microcontroller template
|
||||
* Implement "Jump to Cursor" to skip instructions
|
||||
* Fix memory dump for theia
|
||||
|
||||
# 0.23.1
|
||||
|
||||
Fixes:
|
||||
* Breakpoints in SSH in other working directories properly resolved
|
||||
* Undefined/null paths don't crash stacktrace
|
||||
* Added kotlin to language list
|
||||
|
||||
# 0.23.0
|
||||
|
||||
(released March 2019)
|
||||
|
||||
* Normalize file paths in stack trace (fixes duplicate opening of files)
|
||||
* New Examine memory Location UI
|
||||
* Breakpoints in SSH on windows fixed (@HaronK)
|
||||
* Project code improvements (@simark)
|
||||
* Initial configurations contain valueFormatting now (@Yanpas)
|
||||
|
||||
# 0.22.0
|
||||
|
||||
(released March 2018)
|
||||
|
||||
* Support for using SSH agent
|
||||
* Support multi-threading (@LeszekSwirski)
|
||||
* Fixed GDB expansion logic with floats (Marcel Ball)
|
||||
* Fixed attach to PID template (@gentoo90)
|
||||
|
||||
# 0.21.0 / 0.21.1 / 0.21.2
|
||||
|
||||
(0.21.2 is pushed without changes to hopefully fix vscode installation)
|
||||
|
||||
* Several fixes to variable pretty printers by @gentoo90
|
||||
* Enabled breakpoints for crystal (@faustinoaq)
|
||||
|
||||
# 0.20.0
|
||||
|
||||
Added support for pretty printers in variable list (by @gentoo90), enable
|
||||
with `"valuesFormatting": "prettyPrinters"` if you have a pretty printer
|
||||
to get potentially improved display of variables.
|
|
@ -0,0 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org>
|
217
README.md
|
@ -1,39 +1,202 @@
|
|||
# native-debug
|
||||
# Debug
|
||||
|
||||
#### 介绍
|
||||
{**以下是 Gitee 平台说明,您可以替换此简介**
|
||||
Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台
|
||||
无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
|
||||
Native VSCode debugger. Supports both GDB and LLDB.
|
||||
|
||||
#### 软件架构
|
||||
软件架构说明
|
||||
## Installation
|
||||
|
||||
Press ctrl-p (cmd+p on OS X) and run `ext install webfreak.debug` in visual studio code and install GDB/LLDB. See `Usage` for details on how to set it up.
|
||||
|
||||
#### 安装教程
|
||||
![Preview](images/preview.png)
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
## Usage
|
||||
|
||||
#### 使用说明
|
||||
![Image with red circle around a gear and a red arrow pointing at GDB and LLDB](images/tutorial1.png)
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
Or if you already have an existing debugger in your project setup you can click "Create Configuration" or use the auto completion instead:
|
||||
|
||||
#### 参与贡献
|
||||
![Visual studio code debugger launch.json auto completion showing alternative way to create debuggers](images/tutorial1-alt.png)
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 新建 Feat_xxx 分支
|
||||
3. 提交代码
|
||||
4. 新建 Pull Request
|
||||
Open your project and click the debug button in your sidebar. At the top right press
|
||||
the little gear icon and select GDB or LLDB. It will automatically generate the configuration
|
||||
you need.
|
||||
|
||||
*Note: for LLDB you need to have `lldb-mi` in your PATH*
|
||||
|
||||
#### 特技
|
||||
If you are on OS X you can add `lldb-mi` to your path using
|
||||
`ln -s /Applications/Xcode.app/Contents/Developer/usr/bin/lldb-mi /usr/local/bin/lldb-mi` if you have Xcode.
|
||||
|
||||
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||||
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||||
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
![Default config with a red circle around the target](images/tutorial2.png)
|
||||
|
||||
Now you need to change `target` to the application you want to debug relative
|
||||
to the cwd. (Which is the workspace root by default)
|
||||
|
||||
Additionally you can set `terminal` if you want to run the program in a separate terminal with
|
||||
support for input. On Windows set it to an empty string (`""`) to enable this feature. On linux
|
||||
set it to an empty string (`""`) to use the default terminal emulator specified with `x-terminal-emulator`
|
||||
or specify a custom one. Note that it must support the `-e` argument.
|
||||
|
||||
Before debugging you need to compile your application first, then you can run it using
|
||||
the green start button in the debug sidebar. For this you could use the `preLaunchTask`
|
||||
argument vscode allows you to do. Debugging multithreaded applications is currently not
|
||||
implemented. Adding breakpoints while the program runs will not interrupt it immediately.
|
||||
For that you need to pause & resume the program once first. However adding breakpoints
|
||||
while its paused works as expected.
|
||||
|
||||
Extending variables is very limited as it does not support child values of variables.
|
||||
Watching expressions works partially but the result does not get properly parsed and
|
||||
it shows the raw output of the command. It will run `data-evaluate-expression`
|
||||
to check for variables.
|
||||
|
||||
While running you will get a console where you can manually type GDB/LLDB commands or MI
|
||||
commands prepended with a hyphen `-`. The console shows all output separated
|
||||
in `stdout` for the application, `stderr` for errors and `log` for log messages.
|
||||
|
||||
Some exceptions/signals like segmentation faults will be catched and displayed but
|
||||
it does not support for example most D exceptions.
|
||||
|
||||
Support exists for stopping at the entry point of the application. This is controlled
|
||||
through the `stopAtEntry` setting. This value may be either a boolean or a string. In
|
||||
the case of a boolean value of `false` (the default), this setting is disabled. In the
|
||||
case of a boolean value of `true`, if this is a launch configuration and the debugger
|
||||
supports the `start` (or `exec-run --start` MI feature, more specifically), than this
|
||||
will be used to run to the entry point of the application. Note that this appears to
|
||||
work fine for GDB, but LLDB doesn't necessarily seem to adhere to this, even though it may
|
||||
indicate that it supports this feature. The alternative configuration option for the
|
||||
`stopAtEntry` setting is to specify a string where the string represents the entry point
|
||||
itself. In this situation a temporary breakpoint will be set at the specified entry point
|
||||
and a normal run will occur for a launch configuration. This (setting a temporary
|
||||
breakpoint) is also the behavior that occurs when the debugger does not support the
|
||||
`start` feature and the `stopAtEntry` was set to `true`. In that case the entry point will
|
||||
default to "main". Thus, the most portable way to use this configuration is to explicitly
|
||||
specify the entry point of the application. In the case of an attach configuration, similar
|
||||
behavior will occur, however since there is no equivalent of the `start` command for
|
||||
attaching, a boolean value of `true` for the `stopAtEntry` setting in a launch configuration
|
||||
will automatically default to an entry point of "main", while a string value for this
|
||||
setting will be interpreted as the entry point, causing a temporary breakpoint to be set at
|
||||
that location prior to continuing execution. Note that stopping at the entry point for the
|
||||
attach configuration assumes that the entry point has not yet been entered at the time of
|
||||
attach, otherwise this will have no affect.
|
||||
|
||||
### Attaching to existing processes
|
||||
|
||||
Attaching to existing processes currently only works by specifying the PID in the
|
||||
`launch.json` and setting `request` to `"attach"`. You also need to specify the executable
|
||||
path for the debugger to find the debug symbols.
|
||||
|
||||
```
|
||||
"request": "attach",
|
||||
"executable": "./bin/executable",
|
||||
"target": "4285"
|
||||
```
|
||||
|
||||
This will attach to PID 4285 which should already run. GDB will pause the program on entering and LLDB will keep it running.
|
||||
|
||||
### Using `gdbserver` for remote debugging (GDB only)
|
||||
|
||||
You can also connect to a gdbserver instance and debug using that. For that modify the
|
||||
`launch.json` by setting `request` to `"attach"` and `remote` to `true` and specifing the
|
||||
port and optionally hostname in `target`.
|
||||
|
||||
```
|
||||
"request": "attach",
|
||||
"executable": "./bin/executable",
|
||||
"target": ":2345",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"remote": true
|
||||
```
|
||||
|
||||
This will attach to the running process managed by gdbserver on localhost:2345. You might
|
||||
need to hit the start button in the debug bar at the top first to start the program.
|
||||
|
||||
Control over whether the debugger should continue executing on connect can be configured
|
||||
by setting `stopAtConnect`. The default value is `false` so that execution will continue
|
||||
after connecting.
|
||||
|
||||
### Using ssh for debugging on remote
|
||||
|
||||
Debugging using ssh automatically converts all paths between client & server and also optionally
|
||||
redirects X11 output from the server to the client.
|
||||
Simply add a `ssh` object in your `launch` request.
|
||||
|
||||
```
|
||||
"request": "launch",
|
||||
"target": "./executable",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"ssh": {
|
||||
"forwardX11": true,
|
||||
"host": "192.168.178.57",
|
||||
"cwd": "/home/remoteUser/project/",
|
||||
"keyfile": "/path/to/.ssh/key", // OR
|
||||
"password": "password123",
|
||||
"user": "remoteUser",
|
||||
"x11host": "localhost",
|
||||
// x11port may also be specified as string containing only numbers (useful to use configuration variables)
|
||||
"x11port": 6000,
|
||||
// Optional, content will be executed on the SSH host before the debugger call.
|
||||
"bootstrap": "source /home/remoteUser/some-env"
|
||||
}
|
||||
```
|
||||
|
||||
`ssh.sourceFileMap` will be used to trim off local paths and map them to the server. This is
|
||||
required for basically everything except watched variables or user commands to work.
|
||||
|
||||
For backward compatibility you can also use `cwd` and `ssh.cwd` for the mapping, this is only used
|
||||
if the newer `ssh.sourceFileMap` is not configured.
|
||||
|
||||
For X11 forwarding to work you first need to enable it in your Display Manager and allow the
|
||||
connections. To allow connections you can either add an entry for applications or run `xhost +`
|
||||
in the console while you are debugging and turn it off again when you are done using `xhost -`.
|
||||
|
||||
Because some builds requires one or more environment files to be sourced before running any
|
||||
command, you can use the `ssh.bootstrap` option to add some extra commands which will be prepended
|
||||
to the debugger call (using `&&` to join both).
|
||||
|
||||
### Extra Debugger Arguments
|
||||
|
||||
Additional arguments can be supplied to the debugger if needed. These will be added when
|
||||
the debugger executable (e.g., gdb, lldb-mi, etc.) is launched. Extra debugger arguments
|
||||
are supplied via the `debugger_args` setting. Note that the behavior of escaping these
|
||||
options depends on the environment in which the debugger is started. For non-SSH
|
||||
debugging, the options are passed directly to the application and therefore no escaping is
|
||||
necessary (other than what is necessary for the JSON configuration). However, as a result
|
||||
of the options being passed directly to the application, care must be taken to place
|
||||
switches and switch values as separate entities in `debugger_args`, if they would normally
|
||||
be separated by a space. For example, supplying the option and value
|
||||
`-iex "set $foo = \"bar\""` would consist of the following `debugger_args`:
|
||||
```json
|
||||
"debugger_args" : ["-iex", "set $foo = \"bar\""]
|
||||
```
|
||||
If `=` is used to associate switches with their values, than the switch and value should
|
||||
be placed together instead. In fact, the following example shows 4 different ways in
|
||||
which to specify the same switch and value, using both short and long format, as well as
|
||||
switch values supplied as a separate parameter or supplied via the `=`:
|
||||
- ```json
|
||||
"debugger_args" : ["-iex", "set $foo = \"bar\""]
|
||||
```
|
||||
- ```json
|
||||
"debugger_args" : ["-iex=set $foo = \"bar\""]
|
||||
```
|
||||
- ```json
|
||||
"debugger_args" : ["--init-eval-command", "set $foo = \"bar\""]
|
||||
```
|
||||
- ```json
|
||||
"debugger_args" : ["--init-eval-command=set $foo = \"bar\""]
|
||||
```
|
||||
Where escaping is really necessary is when running the debugger over SSH. In this case,
|
||||
the options are not passed directly to the application, but are instead combined with the
|
||||
application name, joined together with any other options, and sent to the remote system to
|
||||
be parsed and executed. Thus, depending on the remote system, different escaping may be
|
||||
necessary. The following shows how the same command as above needs to be escaped
|
||||
differently based on whether the remote system is a POSIX or a Windows system.
|
||||
- SSH to Linux machine:
|
||||
```json
|
||||
"debugger_args": ["-iex", "'set $foo = \"bar\"'"]
|
||||
```
|
||||
- SSH to Windows machine:
|
||||
```json
|
||||
"debugger_args": ["-iex", "\"set $foo = \\\"bar\\\"\""]
|
||||
```
|
||||
You may need to experiment to find the correct escaping necessary for the command to be
|
||||
sent to the debugger as you intended.
|
||||
|
||||
## [Issues](https://github.com/WebFreak001/code-debug)
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
viewBox="0 0 128 128"
|
||||
height="128"
|
||||
width="128">
|
||||
<defs
|
||||
id="defs4">
|
||||
<filter
|
||||
id="filter3470"
|
||||
style="color-interpolation-filters:sRGB">
|
||||
<feFlood
|
||||
id="feFlood3472"
|
||||
result="flood"
|
||||
flood-color="rgb(0,0,0)"
|
||||
flood-opacity="0.501961" />
|
||||
<feComposite
|
||||
id="feComposite3474"
|
||||
result="composite1"
|
||||
operator="in"
|
||||
in2="SourceGraphic"
|
||||
in="flood" />
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur3476"
|
||||
result="blur"
|
||||
stdDeviation="5"
|
||||
in="composite1" />
|
||||
<feOffset
|
||||
id="feOffset3478"
|
||||
result="offset"
|
||||
dy="3"
|
||||
dx="0" />
|
||||
<feComposite
|
||||
id="feComposite3480"
|
||||
result="composite2"
|
||||
operator="over"
|
||||
in2="offset"
|
||||
in="SourceGraphic" />
|
||||
</filter>
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-924.36216)"
|
||||
id="layer1">
|
||||
<circle
|
||||
r="64"
|
||||
cy="988.36212"
|
||||
cx="64"
|
||||
id="path3338"
|
||||
style="fill:#00897b;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.53892212" />
|
||||
<g
|
||||
transform="matrix(1.2,0,0,1.2,-12.542854,-203.58019)"
|
||||
id="g4300">
|
||||
<g
|
||||
id="text3340"
|
||||
style="font-style:normal;font-weight:normal;font-size:8.38301659px;line-height:100%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">
|
||||
<path
|
||||
id="path4148"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:20.04474068px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
d="m 41.965515,1014.8288 -2.47623,0 -6.352069,-10.1105 0,10.1105 -2.47623,0 0,-14.2506 2.47623,0 6.371644,10.1496 0,-10.1496 2.456655,0 0,14.2506 z" />
|
||||
<path
|
||||
id="path4150"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:20.04474068px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
d="m 53.084082,1011.5108 -5.520133,0 -1.154922,3.318 -2.574105,0 5.383109,-14.2506 2.221756,0 5.392897,14.2506 -2.583893,0 -1.164709,-3.318 z m -4.825223,-1.9966 4.130313,0 -2.065157,-5.9116 -2.065156,5.9116 z" />
|
||||
<path
|
||||
id="path4152"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:20.04474068px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
d="m 67.579327,1002.5749 -4.443512,0 0,12.2539 -2.456655,0 0,-12.2539 -4.404362,0 0,-1.9967 11.304529,0 0,1.9967 z" />
|
||||
<path
|
||||
id="path4154"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:20.04474068px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
d="m 72.071776,1014.8288 -2.466443,0 0,-14.2506 2.466443,0 0,14.2506 z" />
|
||||
<path
|
||||
id="path4156"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:20.04474068px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
d="m 80.156229,1011.7262 3.621364,-11.148 2.720917,0 -5.128635,14.2506 -2.39793,0 -5.10906,-14.2506 2.711129,0 3.582215,11.148 z" />
|
||||
<path
|
||||
id="path4158"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:20.04474068px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
d="m 96.423004,1008.4669 -5.852907,0 0,4.3848 6.841442,0 0,1.9771 -9.317672,0 0,-14.2506 9.24916,0 0,1.9967 -6.77293,0 0,3.9345 5.852907,0 0,1.9575 z" />
|
||||
<path
|
||||
id="path4160"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:16.76603317px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
d="m 38.112729,1032.2779 0,-11.9196 3.520212,0 q 1.580002,0 2.799797,0.704 1.227981,0.7041 1.899277,1.9975 0.671296,1.2935 0.671296,2.9636 l 0,0.5976 q 0,1.6946 -0.679482,2.9799 -0.671297,1.2853 -1.923837,1.9811 -1.244354,0.6959 -2.857103,0.6959 l -3.43016,0 z m 2.071195,-10.2496 0,8.5959 1.350779,0 q 1.629121,0 2.496894,-1.0151 0.87596,-1.0233 0.892333,-2.9308 l 0,-0.6631 q 0,-1.9402 -0.843213,-2.9635 -0.843214,-1.0234 -2.447776,-1.0234 l -1.449017,0 z" />
|
||||
<path
|
||||
id="path4162"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:16.76603317px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
d="m 56.049438,1026.9566 -4.895551,0 0,3.6676 5.722391,0 0,1.6537 -7.793586,0 0,-11.9196 7.73628,0 0,1.67 -5.665085,0 0,3.291 4.895551,0 0,1.6373 z" />
|
||||
<path
|
||||
id="path4164"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:16.76603317px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
d="m 58.579078,1032.2779 0,-11.9196 4.085083,0 q 2.022076,0 3.078139,0.8105 1.056064,0.8104 1.056064,2.415 0,0.8186 -0.442073,1.4736 -0.442074,0.6549 -1.293474,1.0151 0.966012,0.262 1.489951,0.9906 0.532125,0.7204 0.532125,1.7355 0,1.6783 -1.080624,2.5788 -1.072436,0.9005 -3.078139,0.9005 l -4.347052,0 z m 2.071194,-5.3786 0,3.7249 2.300418,0 q 0.974198,0 1.522696,-0.483 0.548499,-0.483 0.548499,-1.3426 0,-1.8583 -1.899278,-1.8993 l -2.472335,0 z m 0,-1.5227 2.030262,0 q 0.966012,0 1.506324,-0.4338 0.548498,-0.4421 0.548498,-1.2444 0,-0.8841 -0.507566,-1.2771 -0.499379,-0.393 -1.563629,-0.393 l -2.013889,0 0,3.3483 z" />
|
||||
<path
|
||||
id="path4166"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:16.76603317px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
d="m 77.874752,1020.3583 0,7.9655 q 0,1.8993 -1.219795,3.0126 -1.211607,1.1052 -3.233683,1.1052 -2.046635,0 -3.250056,-1.0888 -1.203421,-1.097 -1.203421,-3.0372 l 0,-7.9573 2.063008,0 0,7.9737 q 0,1.1952 0.605804,1.8256 0.605804,0.6303 1.784665,0.6303 2.39047,0 2.39047,-2.5214 l 0,-7.9082 2.063008,0 z" />
|
||||
<path
|
||||
id="path4168"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:16.76603317px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
d="m 89.229483,1030.7306 q -0.646737,0.8433 -1.792852,1.2771 -1.146116,0.4339 -2.60332,0.4339 -1.498136,0 -2.652439,-0.6795 -1.154302,-0.6794 -1.784665,-1.9402 -0.622177,-1.2689 -0.646737,-2.9553 l 0,-0.9333 q 0,-2.7015 1.293473,-4.216 1.293474,-1.5227 3.610264,-1.5227 1.98933,0 3.160005,0.9823 1.170675,0.9824 1.408085,2.8326 l -2.030262,0 q -0.343835,-2.1531 -2.496895,-2.1531 -1.391712,0 -2.120314,1.007 -0.720415,0.9987 -0.744975,2.9389 l 0,0.9169 q 0,1.9321 0.810468,3.0045 0.818654,1.0642 2.267671,1.0642 1.588189,0 2.259485,-0.7204 l 0,-2.3331 -2.455962,0 0,-1.5719 4.51897,0 0,4.5681 z" />
|
||||
</g>
|
||||
<g
|
||||
id="g4170">
|
||||
<path
|
||||
style="fill:#ffffff"
|
||||
d="m 54.812555,949.43739 -3.622346,3.62235 4.161634,4.18806 c -1.901223,1.3103 -3.493878,3.03165 -4.650029,5.03565 l -7.219236,0 0,5.13842 5.369407,0 C 48.72352,968.26972 48.621,969.11755 48.621,969.99108 l 0,2.56922 -5.138422,0 0,5.13842 5.138422,0 0,2.56921 c 0,0.20262 0.0065,0.40371 0.01664,0.60389 l 10.260204,-10.26021 0,-3.18974 3.18974,0 14.578435,-14.57844 -3.406037,-3.40604 -5.574944,5.57495 c -1.181843,-0.28261 -2.364157,-0.43652 -3.648769,-0.43652 -1.284613,0 -2.466195,0.15391 -3.622346,0.43652 l -5.601367,-5.57495 z m 29.7774,13.02126 -4.963223,4.96322 4.963223,0 0,-4.96322 z m -5.323402,5.3234 -10.091861,10.09186 0,4.96323 -4.963228,0 -9.607385,9.60738 c 2.604379,2.02784 5.872939,3.23868 9.432189,3.23868 5.703686,0 10.662454,-3.10872 13.334453,-7.70764 l 7.219234,0 0,-5.13842 -5.369405,0 c 0.128467,-0.84784 0.230986,-1.69567 0.230986,-2.56921 l 0,-2.56921 5.138419,0 0,-5.13842 -5.138419,0 0,-2.56922 c 0,-0.75018 -0.08221,-1.48046 -0.184983,-2.20903 z m -35.783975,15.05509 0,3.18974 3.189737,-3.18974 -3.189737,0 z"
|
||||
id="path4310" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.53892212"
|
||||
id="rect4319"
|
||||
width="62.010242"
|
||||
height="5.3151774"
|
||||
x="-674.97266"
|
||||
y="731.51654"
|
||||
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0)" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 6.9 KiB |
|
@ -0,0 +1,181 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 128 128"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="icon.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter3470">
|
||||
<feFlood
|
||||
flood-opacity="0.501961"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood3472" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite3474" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="5"
|
||||
result="blur"
|
||||
id="feGaussianBlur3476" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="3"
|
||||
result="offset"
|
||||
id="feOffset3478" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite3480" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="45.957504"
|
||||
inkscape:cy="50.316562"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1034"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="46"
|
||||
inkscape:window-maximized="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3336"
|
||||
enabled="false" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-924.36216)">
|
||||
<circle
|
||||
style="fill:#00897b;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.53892212"
|
||||
id="path3338"
|
||||
cx="64"
|
||||
cy="988.36212"
|
||||
r="64" />
|
||||
<g
|
||||
id="g4300"
|
||||
transform="matrix(1.2,0,0,1.2,-12.542854,-203.58019)">
|
||||
<g
|
||||
style="font-style:normal;font-weight:normal;font-size:8.38301659px;line-height:100%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="text3340">
|
||||
<path
|
||||
d="m 41.965515,1014.8288 -2.47623,0 -6.352069,-10.1105 0,10.1105 -2.47623,0 0,-14.2506 2.47623,0 6.371644,10.1496 0,-10.1496 2.456655,0 0,14.2506 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:20.04474068px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
id="path4148"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 53.084082,1011.5108 -5.520133,0 -1.154922,3.318 -2.574105,0 5.383109,-14.2506 2.221756,0 5.392897,14.2506 -2.583893,0 -1.164709,-3.318 z m -4.825223,-1.9966 4.130313,0 -2.065157,-5.9116 -2.065156,5.9116 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:20.04474068px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
id="path4150"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 67.579327,1002.5749 -4.443512,0 0,12.2539 -2.456655,0 0,-12.2539 -4.404362,0 0,-1.9967 11.304529,0 0,1.9967 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:20.04474068px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
id="path4152"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 72.071776,1014.8288 -2.466443,0 0,-14.2506 2.466443,0 0,14.2506 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:20.04474068px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
id="path4154"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 80.156229,1011.7262 3.621364,-11.148 2.720917,0 -5.128635,14.2506 -2.39793,0 -5.10906,-14.2506 2.711129,0 3.582215,11.148 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:20.04474068px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
id="path4156"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 96.423004,1008.4669 -5.852907,0 0,4.3848 6.841442,0 0,1.9771 -9.317672,0 0,-14.2506 9.24916,0 0,1.9967 -6.77293,0 0,3.9345 5.852907,0 0,1.9575 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:20.04474068px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
id="path4158"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 38.112729,1032.2779 0,-11.9196 3.520212,0 q 1.580002,0 2.799797,0.704 1.227981,0.7041 1.899277,1.9975 0.671296,1.2935 0.671296,2.9636 l 0,0.5976 q 0,1.6946 -0.679482,2.9799 -0.671297,1.2853 -1.923837,1.9811 -1.244354,0.6959 -2.857103,0.6959 l -3.43016,0 z m 2.071195,-10.2496 0,8.5959 1.350779,0 q 1.629121,0 2.496894,-1.0151 0.87596,-1.0233 0.892333,-2.9308 l 0,-0.6631 q 0,-1.9402 -0.843213,-2.9635 -0.843214,-1.0234 -2.447776,-1.0234 l -1.449017,0 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:16.76603317px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
id="path4160"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 56.049438,1026.9566 -4.895551,0 0,3.6676 5.722391,0 0,1.6537 -7.793586,0 0,-11.9196 7.73628,0 0,1.67 -5.665085,0 0,3.291 4.895551,0 0,1.6373 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:16.76603317px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
id="path4162"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 58.579078,1032.2779 0,-11.9196 4.085083,0 q 2.022076,0 3.078139,0.8105 1.056064,0.8104 1.056064,2.415 0,0.8186 -0.442073,1.4736 -0.442074,0.6549 -1.293474,1.0151 0.966012,0.262 1.489951,0.9906 0.532125,0.7204 0.532125,1.7355 0,1.6783 -1.080624,2.5788 -1.072436,0.9005 -3.078139,0.9005 l -4.347052,0 z m 2.071194,-5.3786 0,3.7249 2.300418,0 q 0.974198,0 1.522696,-0.483 0.548499,-0.483 0.548499,-1.3426 0,-1.8583 -1.899278,-1.8993 l -2.472335,0 z m 0,-1.5227 2.030262,0 q 0.966012,0 1.506324,-0.4338 0.548498,-0.4421 0.548498,-1.2444 0,-0.8841 -0.507566,-1.2771 -0.499379,-0.393 -1.563629,-0.393 l -2.013889,0 0,3.3483 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:16.76603317px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
id="path4164"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 77.874752,1020.3583 0,7.9655 q 0,1.8993 -1.219795,3.0126 -1.211607,1.1052 -3.233683,1.1052 -2.046635,0 -3.250056,-1.0888 -1.203421,-1.097 -1.203421,-3.0372 l 0,-7.9573 2.063008,0 0,7.9737 q 0,1.1952 0.605804,1.8256 0.605804,0.6303 1.784665,0.6303 2.39047,0 2.39047,-2.5214 l 0,-7.9082 2.063008,0 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:16.76603317px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
id="path4166"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 89.229483,1030.7306 q -0.646737,0.8433 -1.792852,1.2771 -1.146116,0.4339 -2.60332,0.4339 -1.498136,0 -2.652439,-0.6795 -1.154302,-0.6794 -1.784665,-1.9402 -0.622177,-1.2689 -0.646737,-2.9553 l 0,-0.9333 q 0,-2.7015 1.293473,-4.216 1.293474,-1.5227 3.610264,-1.5227 1.98933,0 3.160005,0.9823 1.170675,0.9824 1.408085,2.8326 l -2.030262,0 q -0.343835,-2.1531 -2.496895,-2.1531 -1.391712,0 -2.120314,1.007 -0.720415,0.9987 -0.744975,2.9389 l 0,0.9169 q 0,1.9321 0.810468,3.0045 0.818654,1.0642 2.267671,1.0642 1.588189,0 2.259485,-0.7204 l 0,-2.3331 -2.455962,0 0,-1.5719 4.51897,0 0,4.5681 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:16.76603317px;font-family:Roboto;-inkscape-font-specification:'Roboto Medium';fill:#ffffff"
|
||||
id="path4168"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g4170">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4310"
|
||||
d="m 54.812555,949.43739 -3.622346,3.62235 4.161634,4.18806 c -1.901223,1.3103 -3.493878,3.03165 -4.650029,5.03565 l -7.219236,0 0,5.13842 5.369407,0 C 48.72352,968.26972 48.621,969.11755 48.621,969.99108 l 0,2.56922 -5.138422,0 0,5.13842 5.138422,0 0,2.56921 c 0,0.20262 0.0065,0.40371 0.01664,0.60389 l 10.260204,-10.26021 0,-3.18974 3.18974,0 14.578435,-14.57844 -3.406037,-3.40604 -5.574944,5.57495 c -1.181843,-0.28261 -2.364157,-0.43652 -3.648769,-0.43652 -1.284613,0 -2.466195,0.15391 -3.622346,0.43652 l -5.601367,-5.57495 z m 29.7774,13.02126 -4.963223,4.96322 4.963223,0 0,-4.96322 z m -5.323402,5.3234 -10.091861,10.09186 0,4.96323 -4.963228,0 -9.607385,9.60738 c 2.604379,2.02784 5.872939,3.23868 9.432189,3.23868 5.703686,0 10.662454,-3.10872 13.334453,-7.70764 l 7.219234,0 0,-5.13842 -5.369405,0 c 0.128467,-0.84784 0.230986,-1.69567 0.230986,-2.56921 l 0,-2.56921 5.138419,0 0,-5.13842 -5.138419,0 0,-2.56922 c 0,-0.75018 -0.08221,-1.48046 -0.184983,-2.20903 z m -35.783975,15.05509 0,3.18974 3.189737,-3.18974 -3.189737,0 z"
|
||||
style="fill:#ffffff" />
|
||||
<rect
|
||||
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0)"
|
||||
y="731.51654"
|
||||
x="-674.97266"
|
||||
height="5.3151774"
|
||||
width="62.010242"
|
||||
id="rect4319"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.53892212" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 89 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 53 KiB |
|
@ -0,0 +1,165 @@
|
|||
import { MINode } from "./mi_parse";
|
||||
import { DebugProtocol } from "vscode-debugprotocol/lib/debugProtocol";
|
||||
|
||||
export type ValuesFormattingMode = "disabled" | "parseText" | "prettyPrinters";
|
||||
|
||||
export interface Breakpoint {
|
||||
file?: string;
|
||||
line?: number;
|
||||
raw?: string;
|
||||
condition: string;
|
||||
countCondition?: string;
|
||||
}
|
||||
|
||||
export interface Thread {
|
||||
id: number;
|
||||
targetId: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface Stack {
|
||||
level: number;
|
||||
address: string;
|
||||
function: string;
|
||||
fileName: string;
|
||||
file: string;
|
||||
line: number;
|
||||
}
|
||||
|
||||
export interface Variable {
|
||||
name: string;
|
||||
valueStr: string;
|
||||
type: string;
|
||||
raw?: any;
|
||||
}
|
||||
|
||||
export interface SSHArguments {
|
||||
forwardX11: boolean;
|
||||
host: string;
|
||||
keyfile: string;
|
||||
password: string;
|
||||
useAgent: boolean;
|
||||
cwd: string;
|
||||
port: number;
|
||||
user: string;
|
||||
remotex11screen: number;
|
||||
x11port: number;
|
||||
x11host: string;
|
||||
bootstrap: string;
|
||||
sourceFileMap: { [index: string]: string };
|
||||
}
|
||||
|
||||
export interface IBackend {
|
||||
load(cwd: string, target: string, procArgs: string, separateConsole: string): Thenable<any>;
|
||||
ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean): Thenable<any>;
|
||||
attach(cwd: string, executable: string, target: string): Thenable<any>;
|
||||
connect(cwd: string, executable: string, target: string): Thenable<any>;
|
||||
start(runToStart: boolean): Thenable<boolean>;
|
||||
stop();
|
||||
detach();
|
||||
interrupt(): Thenable<boolean>;
|
||||
continue(): Thenable<boolean>;
|
||||
next(): Thenable<boolean>;
|
||||
step(): Thenable<boolean>;
|
||||
stepOut(): Thenable<boolean>;
|
||||
loadBreakPoints(breakpoints: Breakpoint[]): Thenable<[boolean, Breakpoint][]>;
|
||||
addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]>;
|
||||
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean>;
|
||||
clearBreakPoints(source?: string): Thenable<any>;
|
||||
getThreads(): Thenable<Thread[]>;
|
||||
getStack(startFrame: number, maxLevels: number, thread: number): Thenable<Stack[]>;
|
||||
getStackVariables(thread: number, frame: number): Thenable<Variable[]>;
|
||||
evalExpression(name: string, thread: number, frame: number): Thenable<any>;
|
||||
isReady(): boolean;
|
||||
changeVariable(name: string, rawValue: string): Thenable<any>;
|
||||
examineMemory(from: number, to: number): Thenable<any>;
|
||||
}
|
||||
|
||||
export class VariableObject {
|
||||
name: string;
|
||||
exp: string;
|
||||
numchild: number;
|
||||
type: string;
|
||||
value: string;
|
||||
threadId: string;
|
||||
frozen: boolean;
|
||||
dynamic: boolean;
|
||||
displayhint: string;
|
||||
hasMore: boolean;
|
||||
id: number;
|
||||
constructor(node: any) {
|
||||
this.name = MINode.valueOf(node, "name");
|
||||
this.exp = MINode.valueOf(node, "exp");
|
||||
this.numchild = parseInt(MINode.valueOf(node, "numchild"));
|
||||
this.type = MINode.valueOf(node, "type");
|
||||
this.value = MINode.valueOf(node, "value");
|
||||
this.threadId = MINode.valueOf(node, "thread-id");
|
||||
this.frozen = !!MINode.valueOf(node, "frozen");
|
||||
this.dynamic = !!MINode.valueOf(node, "dynamic");
|
||||
this.displayhint = MINode.valueOf(node, "displayhint");
|
||||
// TODO: use has_more when it's > 0
|
||||
this.hasMore = !!MINode.valueOf(node, "has_more");
|
||||
}
|
||||
|
||||
public applyChanges(node: MINode) {
|
||||
this.value = MINode.valueOf(node, "value");
|
||||
if (!!MINode.valueOf(node, "type_changed")) {
|
||||
this.type = MINode.valueOf(node, "new_type");
|
||||
}
|
||||
this.dynamic = !!MINode.valueOf(node, "dynamic");
|
||||
this.displayhint = MINode.valueOf(node, "displayhint");
|
||||
this.hasMore = !!MINode.valueOf(node, "has_more");
|
||||
}
|
||||
|
||||
public isCompound(): boolean {
|
||||
return this.numchild > 0 ||
|
||||
this.value === "{...}" ||
|
||||
(this.dynamic && (this.displayhint === "array" || this.displayhint === "map"));
|
||||
}
|
||||
|
||||
public toProtocolVariable(): DebugProtocol.Variable {
|
||||
const res: DebugProtocol.Variable = {
|
||||
name: this.exp,
|
||||
evaluateName: this.name,
|
||||
value: (this.value === void 0) ? "<unknown>" : this.value,
|
||||
type: this.type,
|
||||
variablesReference: this.id
|
||||
};
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// from https://gist.github.com/justmoon/15511f92e5216fa2624b#gistcomment-1928632
|
||||
export interface MIError extends Error {
|
||||
readonly name: string;
|
||||
readonly message: string;
|
||||
readonly source: string;
|
||||
}
|
||||
export interface MIErrorConstructor {
|
||||
new (message: string, source: string): MIError;
|
||||
readonly prototype: MIError;
|
||||
}
|
||||
|
||||
export const MIError: MIErrorConstructor = <any> class MIError {
|
||||
readonly name: string;
|
||||
readonly message: string;
|
||||
readonly source: string;
|
||||
public constructor(message: string, source: string) {
|
||||
Object.defineProperty(this, 'name', {
|
||||
get: () => (this.constructor as any).name,
|
||||
});
|
||||
Object.defineProperty(this, 'message', {
|
||||
get: () => message,
|
||||
});
|
||||
Object.defineProperty(this, 'source', {
|
||||
get: () => source,
|
||||
});
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `${this.message} (from ${this.source})`;
|
||||
}
|
||||
};
|
||||
Object.setPrototypeOf(MIError as any, Object.create(Error.prototype));
|
||||
MIError.prototype.constructor = MIError;
|
|
@ -0,0 +1,258 @@
|
|||
import { MINode } from "./mi_parse";
|
||||
|
||||
const resultRegex = /^([a-zA-Z_\-][a-zA-Z0-9_\-]*|\[\d+\])\s*=\s*/;
|
||||
const variableRegex = /^[a-zA-Z_\-][a-zA-Z0-9_\-]*/;
|
||||
const errorRegex = /^\<.+?\>/;
|
||||
const referenceStringRegex = /^(0x[0-9a-fA-F]+\s*)"/;
|
||||
const referenceRegex = /^0x[0-9a-fA-F]+/;
|
||||
const cppReferenceRegex = /^@0x[0-9a-fA-F]+/;
|
||||
const nullpointerRegex = /^0x0+\b/;
|
||||
const charRegex = /^(\d+) ['"]/;
|
||||
const numberRegex = /^\d+(\.\d+)?/;
|
||||
const pointerCombineChar = ".";
|
||||
|
||||
export function isExpandable(value: string): number {
|
||||
let match;
|
||||
value = value.trim();
|
||||
if (value.length == 0) return 0;
|
||||
else if (value.startsWith("{...}")) return 2; // lldb string/array
|
||||
else if (value[0] == '{') return 1; // object
|
||||
else if (value.startsWith("true")) return 0;
|
||||
else if (value.startsWith("false")) return 0;
|
||||
else if (match = nullpointerRegex.exec(value)) return 0;
|
||||
else if (match = referenceStringRegex.exec(value)) return 0;
|
||||
else if (match = referenceRegex.exec(value)) return 2; // reference
|
||||
else if (match = charRegex.exec(value)) return 0;
|
||||
else if (match = numberRegex.exec(value)) return 0;
|
||||
else if (match = variableRegex.exec(value)) return 0;
|
||||
else if (match = errorRegex.exec(value)) return 0;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
export function expandValue(variableCreate: Function, value: string, root: string = "", extra: any = undefined): any {
|
||||
const parseCString = () => {
|
||||
value = value.trim();
|
||||
if (value[0] != '"' && value[0] != '\'')
|
||||
return "";
|
||||
let stringEnd = 1;
|
||||
let inString = true;
|
||||
const charStr = value[0];
|
||||
let remaining = value.substr(1);
|
||||
let escaped = false;
|
||||
while (inString) {
|
||||
if (escaped)
|
||||
escaped = false;
|
||||
else if (remaining[0] == '\\')
|
||||
escaped = true;
|
||||
else if (remaining[0] == charStr)
|
||||
inString = false;
|
||||
|
||||
remaining = remaining.substr(1);
|
||||
stringEnd++;
|
||||
}
|
||||
const str = value.substr(0, stringEnd).trim();
|
||||
value = value.substr(stringEnd).trim();
|
||||
return str;
|
||||
};
|
||||
|
||||
const stack = [root];
|
||||
let parseValue, parseCommaResult, parseCommaValue, parseResult, createValue;
|
||||
let variable = "";
|
||||
|
||||
const getNamespace = (variable) => {
|
||||
let namespace = "";
|
||||
let prefix = "";
|
||||
stack.push(variable);
|
||||
stack.forEach(name => {
|
||||
prefix = "";
|
||||
if (name != "") {
|
||||
if (name.startsWith("["))
|
||||
namespace = namespace + name;
|
||||
else {
|
||||
if (namespace) {
|
||||
while (name.startsWith("*")) {
|
||||
prefix += "*";
|
||||
name = name.substr(1);
|
||||
}
|
||||
namespace = namespace + pointerCombineChar + name;
|
||||
} else
|
||||
namespace = name;
|
||||
}
|
||||
}
|
||||
});
|
||||
stack.pop();
|
||||
return prefix + namespace;
|
||||
};
|
||||
|
||||
const parseTupleOrList = () => {
|
||||
value = value.trim();
|
||||
if (value[0] != '{')
|
||||
return undefined;
|
||||
const oldContent = value;
|
||||
value = value.substr(1).trim();
|
||||
if (value[0] == '}') {
|
||||
value = value.substr(1).trim();
|
||||
return [];
|
||||
}
|
||||
if (value.startsWith("...")) {
|
||||
value = value.substr(3).trim();
|
||||
if (value[0] == '}') {
|
||||
value = value.substr(1).trim();
|
||||
return <any> "<...>";
|
||||
}
|
||||
}
|
||||
const eqPos = value.indexOf("=");
|
||||
const newValPos1 = value.indexOf("{");
|
||||
const newValPos2 = value.indexOf(",");
|
||||
let newValPos = newValPos1;
|
||||
if (newValPos2 != -1 && newValPos2 < newValPos1)
|
||||
newValPos = newValPos2;
|
||||
if (newValPos != -1 && eqPos > newValPos || eqPos == -1) { // is value list
|
||||
const values = [];
|
||||
stack.push("[0]");
|
||||
let val = parseValue();
|
||||
stack.pop();
|
||||
values.push(createValue("[0]", val));
|
||||
const remaining = value;
|
||||
let i = 0;
|
||||
while (true) {
|
||||
stack.push("[" + (++i) + "]");
|
||||
if (!(val = parseCommaValue())) {
|
||||
stack.pop();
|
||||
break;
|
||||
}
|
||||
stack.pop();
|
||||
values.push(createValue("[" + i + "]", val));
|
||||
}
|
||||
value = value.substr(1).trim(); // }
|
||||
return values;
|
||||
}
|
||||
|
||||
let result = parseResult(true);
|
||||
if (result) {
|
||||
const results = [];
|
||||
results.push(result);
|
||||
while (result = parseCommaResult(true))
|
||||
results.push(result);
|
||||
value = value.substr(1).trim(); // }
|
||||
return results;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const parsePrimitive = () => {
|
||||
let primitive: any;
|
||||
let match;
|
||||
value = value.trim();
|
||||
if (value.length == 0)
|
||||
primitive = undefined;
|
||||
else if (value.startsWith("true")) {
|
||||
primitive = "true";
|
||||
value = value.substr(4).trim();
|
||||
} else if (value.startsWith("false")) {
|
||||
primitive = "false";
|
||||
value = value.substr(5).trim();
|
||||
} else if (match = nullpointerRegex.exec(value)) {
|
||||
primitive = "<nullptr>";
|
||||
value = value.substr(match[0].length).trim();
|
||||
} else if (match = referenceStringRegex.exec(value)) {
|
||||
value = value.substr(match[1].length).trim();
|
||||
primitive = parseCString();
|
||||
} else if (match = referenceRegex.exec(value)) {
|
||||
primitive = "*" + match[0];
|
||||
value = value.substr(match[0].length).trim();
|
||||
} else if (match = cppReferenceRegex.exec(value)) {
|
||||
primitive = match[0];
|
||||
value = value.substr(match[0].length).trim();
|
||||
} else if (match = charRegex.exec(value)) {
|
||||
primitive = match[1];
|
||||
value = value.substr(match[0].length - 1);
|
||||
primitive += " " + parseCString();
|
||||
} else if (match = numberRegex.exec(value)) {
|
||||
primitive = match[0];
|
||||
value = value.substr(match[0].length).trim();
|
||||
} else if (match = variableRegex.exec(value)) {
|
||||
primitive = match[0];
|
||||
value = value.substr(match[0].length).trim();
|
||||
} else if (match = errorRegex.exec(value)) {
|
||||
primitive = match[0];
|
||||
value = value.substr(match[0].length).trim();
|
||||
} else {
|
||||
primitive = value;
|
||||
}
|
||||
return primitive;
|
||||
};
|
||||
|
||||
parseValue = () => {
|
||||
value = value.trim();
|
||||
if (value[0] == '"')
|
||||
return parseCString();
|
||||
else if (value[0] == '{')
|
||||
return parseTupleOrList();
|
||||
else
|
||||
return parsePrimitive();
|
||||
};
|
||||
|
||||
parseResult = (pushToStack: boolean = false) => {
|
||||
value = value.trim();
|
||||
const variableMatch = resultRegex.exec(value);
|
||||
if (!variableMatch)
|
||||
return undefined;
|
||||
value = value.substr(variableMatch[0].length).trim();
|
||||
const name = variable = variableMatch[1];
|
||||
if (pushToStack)
|
||||
stack.push(variable);
|
||||
const val = parseValue();
|
||||
if (pushToStack)
|
||||
stack.pop();
|
||||
return createValue(name, val);
|
||||
};
|
||||
|
||||
createValue = (name, val) => {
|
||||
let ref = 0;
|
||||
if (typeof val == "object") {
|
||||
ref = variableCreate(val);
|
||||
val = "Object";
|
||||
} else if (typeof val == "string" && val.startsWith("*0x")) {
|
||||
if (extra && MINode.valueOf(extra, "arg") == "1") {
|
||||
ref = variableCreate(getNamespace("*(" + name), { arg: true });
|
||||
val = "<args>";
|
||||
} else {
|
||||
ref = variableCreate(getNamespace("*" + name));
|
||||
val = "Object@" + val;
|
||||
}
|
||||
} else if (typeof val == "string" && val.startsWith("@0x")) {
|
||||
ref = variableCreate(getNamespace("*&" + name.substr));
|
||||
val = "Ref" + val;
|
||||
} else if (typeof val == "string" && val.startsWith("<...>")) {
|
||||
ref = variableCreate(getNamespace(name));
|
||||
val = "...";
|
||||
}
|
||||
return {
|
||||
name: name,
|
||||
value: val,
|
||||
variablesReference: ref
|
||||
};
|
||||
};
|
||||
|
||||
parseCommaValue = () => {
|
||||
value = value.trim();
|
||||
if (value[0] != ',')
|
||||
return undefined;
|
||||
value = value.substr(1).trim();
|
||||
return parseValue();
|
||||
};
|
||||
|
||||
parseCommaResult = (pushToStack: boolean = false) => {
|
||||
value = value.trim();
|
||||
if (value[0] != ',')
|
||||
return undefined;
|
||||
value = value.substr(1).trim();
|
||||
return parseResult(pushToStack);
|
||||
};
|
||||
|
||||
|
||||
value = value.trim();
|
||||
return parseValue();
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import * as ChildProcess from "child_process";
|
||||
import * as fs from "fs";
|
||||
|
||||
export function spawnTerminalEmulator(preferedEmulator: string): Thenable<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ttyFileOutput = "/tmp/vscode-gdb-tty-0" + Math.floor(Math.random() * 100000000).toString(36);
|
||||
ChildProcess.spawn(preferedEmulator || "x-terminal-emulator", ["-e", "sh -c \"tty > " + ttyFileOutput + " && sleep 4294967294\""]);
|
||||
let it = 0;
|
||||
const interval = setInterval(() => {
|
||||
if (fs.existsSync(ttyFileOutput)) {
|
||||
clearInterval(interval);
|
||||
const tty = fs.readFileSync(ttyFileOutput).toString("utf8");
|
||||
fs.unlinkSync(ttyFileOutput);
|
||||
return resolve(tty);
|
||||
}
|
||||
it++;
|
||||
if (it > 500)
|
||||
reject();
|
||||
}, 10);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,917 @@
|
|||
import { Breakpoint, IBackend, Thread, Stack, SSHArguments, Variable, VariableObject, MIError } from "../backend";
|
||||
import * as ChildProcess from "child_process";
|
||||
import { EventEmitter } from "events";
|
||||
import { parseMI, MINode } from '../mi_parse';
|
||||
import * as linuxTerm from '../linux/console';
|
||||
import * as net from "net";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { Client } from "ssh2";
|
||||
|
||||
export function escape(str: string) {
|
||||
return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
||||
}
|
||||
|
||||
const nonOutput = /^(?:\d*|undefined)[\*\+\=]|[\~\@\&\^]/;
|
||||
const gdbMatch = /(?:\d*|undefined)\(gdb\)/;
|
||||
const numRegex = /\d+/;
|
||||
|
||||
function couldBeOutput(line: string) {
|
||||
if (nonOutput.exec(line))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const trace = false;
|
||||
|
||||
export class MI2 extends EventEmitter implements IBackend {
|
||||
constructor(public application: string, public preargs: string[], public extraargs: string[], procEnv: any, public extraCommands: string[] = []) {
|
||||
super();
|
||||
|
||||
if (procEnv) {
|
||||
const env = {};
|
||||
// Duplicate process.env so we don't override it
|
||||
for (const key in process.env)
|
||||
if (process.env.hasOwnProperty(key))
|
||||
env[key] = process.env[key];
|
||||
|
||||
// Overwrite with user specified variables
|
||||
for (const key in procEnv) {
|
||||
if (procEnv.hasOwnProperty(key)) {
|
||||
if (procEnv === null)
|
||||
delete env[key];
|
||||
else
|
||||
env[key] = procEnv[key];
|
||||
}
|
||||
}
|
||||
this.procEnv = env;
|
||||
}
|
||||
}
|
||||
|
||||
load(cwd: string, target: string, procArgs: string, separateConsole: string): Thenable<any> {
|
||||
if (!path.isAbsolute(target))
|
||||
target = path.join(cwd, target);
|
||||
return new Promise((resolve, reject) => {
|
||||
this.isSSH = false;
|
||||
const args = this.preargs.concat(this.extraargs || []);
|
||||
this.process = ChildProcess.spawn(this.application, args, { cwd: cwd, env: this.procEnv });
|
||||
this.process.stdout.on("data", this.stdout.bind(this));
|
||||
this.process.stderr.on("data", this.stderr.bind(this));
|
||||
this.process.on("exit", (() => { this.emit("quit"); }).bind(this));
|
||||
this.process.on("error", ((err) => { this.emit("launcherror", err); }).bind(this));
|
||||
const promises = this.initCommands(target, cwd);
|
||||
if (procArgs && procArgs.length)
|
||||
promises.push(this.sendCommand("exec-arguments " + procArgs));
|
||||
if (process.platform == "win32") {
|
||||
if (separateConsole !== undefined)
|
||||
promises.push(this.sendCommand("gdb-set new-console on"));
|
||||
Promise.all(promises).then(() => {
|
||||
this.emit("debug-ready");
|
||||
resolve(undefined);
|
||||
}, reject);
|
||||
} else {
|
||||
if (separateConsole !== undefined) {
|
||||
linuxTerm.spawnTerminalEmulator(separateConsole).then(tty => {
|
||||
promises.push(this.sendCommand("inferior-tty-set " + tty));
|
||||
Promise.all(promises).then(() => {
|
||||
this.emit("debug-ready");
|
||||
resolve(undefined);
|
||||
}, reject);
|
||||
});
|
||||
} else {
|
||||
Promise.all(promises).then(() => {
|
||||
this.emit("debug-ready");
|
||||
resolve(undefined);
|
||||
}, reject);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean): Thenable<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.isSSH = true;
|
||||
this.sshReady = false;
|
||||
this.sshConn = new Client();
|
||||
|
||||
if (separateConsole !== undefined)
|
||||
this.log("stderr", "WARNING: Output to terminal emulators are not supported over SSH");
|
||||
|
||||
if (args.forwardX11) {
|
||||
this.sshConn.on("x11", (info, accept, reject) => {
|
||||
const xserversock = new net.Socket();
|
||||
xserversock.on("error", (err) => {
|
||||
this.log("stderr", "Could not connect to local X11 server! Did you enable it in your display manager?\n" + err);
|
||||
});
|
||||
xserversock.on("connect", () => {
|
||||
const xclientsock = accept();
|
||||
xclientsock.pipe(xserversock).pipe(xclientsock);
|
||||
});
|
||||
xserversock.connect(args.x11port, args.x11host);
|
||||
});
|
||||
}
|
||||
|
||||
const connectionArgs: any = {
|
||||
host: args.host,
|
||||
port: args.port,
|
||||
username: args.user
|
||||
};
|
||||
|
||||
if (args.useAgent) {
|
||||
connectionArgs.agent = process.env.SSH_AUTH_SOCK;
|
||||
} else if (args.keyfile) {
|
||||
if (require("fs").existsSync(args.keyfile))
|
||||
connectionArgs.privateKey = require("fs").readFileSync(args.keyfile);
|
||||
else {
|
||||
this.log("stderr", "SSH key file does not exist!");
|
||||
this.emit("quit");
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
connectionArgs.password = args.password;
|
||||
}
|
||||
|
||||
this.sshConn.on("ready", () => {
|
||||
this.log("stdout", "Running " + this.application + " over ssh...");
|
||||
const execArgs: any = {};
|
||||
if (args.forwardX11) {
|
||||
execArgs.x11 = {
|
||||
single: false,
|
||||
screen: args.remotex11screen
|
||||
};
|
||||
}
|
||||
let sshCMD = this.application + " " + this.preargs.concat(this.extraargs || []).join(" ");
|
||||
if (args.bootstrap) sshCMD = args.bootstrap + " && " + sshCMD;
|
||||
this.sshConn.exec(sshCMD, execArgs, (err, stream) => {
|
||||
if (err) {
|
||||
this.log("stderr", "Could not run " + this.application + "(" + sshCMD +") over ssh!");
|
||||
if (err === undefined) {
|
||||
err = "<reason unknown>"
|
||||
}
|
||||
this.log("stderr", err.toString());
|
||||
this.emit("quit");
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
this.sshReady = true;
|
||||
this.stream = stream;
|
||||
stream.on("data", this.stdout.bind(this));
|
||||
stream.stderr.on("data", this.stderr.bind(this));
|
||||
stream.on("exit", (() => {
|
||||
this.emit("quit");
|
||||
this.sshConn.end();
|
||||
}).bind(this));
|
||||
const promises = this.initCommands(target, cwd, attach);
|
||||
promises.push(this.sendCommand("environment-cd \"" + escape(cwd) + "\""));
|
||||
if (attach) {
|
||||
// Attach to local process
|
||||
promises.push(this.sendCommand("target-attach " + target));
|
||||
} else if (procArgs && procArgs.length)
|
||||
promises.push(this.sendCommand("exec-arguments " + procArgs));
|
||||
Promise.all(promises).then(() => {
|
||||
this.emit("debug-ready");
|
||||
resolve(undefined);
|
||||
}, reject);
|
||||
});
|
||||
}).on("error", (err) => {
|
||||
this.log("stderr", "Error running " + this.application + " over ssh!");
|
||||
if (err === undefined) {
|
||||
err = "<reason unknown>"
|
||||
}
|
||||
this.log("stderr", err.toString());
|
||||
this.emit("quit");
|
||||
reject();
|
||||
}).connect(connectionArgs);
|
||||
});
|
||||
}
|
||||
|
||||
protected initCommands(target: string, cwd: string, attach: boolean = false) {
|
||||
// We need to account for the possibility of the path type used by the debugger being different
|
||||
// than the path type where the extension is running (e.g., SSH from Linux to Windows machine).
|
||||
// Since the CWD is expected to be an absolute path in the debugger's environment, we can test
|
||||
// that to determine the path type used by the debugger and use the result of that test to
|
||||
// select the correct API to check whether the target path is an absolute path.
|
||||
const debuggerPath = path.posix.isAbsolute(cwd) ? path.posix : path.win32;
|
||||
|
||||
if (!debuggerPath.isAbsolute(target))
|
||||
target = debuggerPath.join(cwd, target);
|
||||
|
||||
const cmds = [
|
||||
this.sendCommand("gdb-set target-async on", true),
|
||||
new Promise(resolve => {
|
||||
this.sendCommand("list-features").then(done => {
|
||||
this.features = done.result("features");
|
||||
resolve(undefined);
|
||||
}, () => {
|
||||
// Default to no supported features on error
|
||||
this.features = [];
|
||||
resolve(undefined);
|
||||
});
|
||||
}),
|
||||
this.sendCommand("environment-directory \"" + escape(cwd) + "\"", true)
|
||||
];
|
||||
if (!attach)
|
||||
cmds.push(this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\""));
|
||||
if (this.prettyPrint)
|
||||
cmds.push(this.sendCommand("enable-pretty-printing"));
|
||||
for (let cmd of this.extraCommands) {
|
||||
cmds.push(this.sendCommand(cmd));
|
||||
}
|
||||
|
||||
return cmds;
|
||||
}
|
||||
|
||||
attach(cwd: string, executable: string, target: string): Thenable<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let args = [];
|
||||
if (executable && !path.isAbsolute(executable))
|
||||
executable = path.join(cwd, executable);
|
||||
args = this.preargs.concat(this.extraargs || []);
|
||||
this.process = ChildProcess.spawn(this.application, args, { cwd: cwd, env: this.procEnv });
|
||||
this.process.stdout.on("data", this.stdout.bind(this));
|
||||
this.process.stderr.on("data", this.stderr.bind(this));
|
||||
this.process.on("exit", (() => { this.emit("quit"); }).bind(this));
|
||||
this.process.on("error", ((err) => { this.emit("launcherror", err); }).bind(this));
|
||||
const promises = this.initCommands(target, cwd, true);
|
||||
if (target.startsWith("extended-remote")) {
|
||||
promises.push(this.sendCommand("target-select " + target));
|
||||
if (executable)
|
||||
promises.push(this.sendCommand("file-symbol-file \"" + escape(executable) + "\""));
|
||||
} else {
|
||||
// Attach to local process
|
||||
if (executable)
|
||||
promises.push(this.sendCommand("file-exec-and-symbols \"" + escape(executable) + "\""));
|
||||
promises.push(this.sendCommand("target-attach " + target));
|
||||
}
|
||||
Promise.all(promises).then(() => {
|
||||
this.emit("debug-ready");
|
||||
resolve(undefined);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
connect(cwd: string, executable: string, target: string): Thenable<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let args = [];
|
||||
if (executable && !path.isAbsolute(executable))
|
||||
executable = path.join(cwd, executable);
|
||||
args = this.preargs.concat(this.extraargs || []);
|
||||
if (executable)
|
||||
args = args.concat([executable]);
|
||||
this.process = ChildProcess.spawn(this.application, args, { cwd: cwd, env: this.procEnv });
|
||||
this.process.stdout.on("data", this.stdout.bind(this));
|
||||
this.process.stderr.on("data", this.stderr.bind(this));
|
||||
this.process.on("exit", (() => { this.emit("quit"); }).bind(this));
|
||||
this.process.on("error", ((err) => { this.emit("launcherror", err); }).bind(this));
|
||||
const promises = this.initCommands(target, cwd, true);
|
||||
promises.push(this.sendCommand("target-select remote " + target));
|
||||
Promise.all(promises).then(() => {
|
||||
this.emit("debug-ready");
|
||||
resolve(undefined);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
stdout(data) {
|
||||
if (trace)
|
||||
this.log("stderr", "stdout: " + data);
|
||||
if (typeof data == "string")
|
||||
this.buffer += data;
|
||||
else
|
||||
this.buffer += data.toString("utf8");
|
||||
const end = this.buffer.lastIndexOf('\n');
|
||||
if (end != -1) {
|
||||
this.onOutput(this.buffer.substr(0, end));
|
||||
this.buffer = this.buffer.substr(end + 1);
|
||||
}
|
||||
if (this.buffer.length) {
|
||||
if (this.onOutputPartial(this.buffer)) {
|
||||
this.buffer = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stderr(data) {
|
||||
if (typeof data == "string")
|
||||
this.errbuf += data;
|
||||
else
|
||||
this.errbuf += data.toString("utf8");
|
||||
const end = this.errbuf.lastIndexOf('\n');
|
||||
if (end != -1) {
|
||||
this.onOutputStderr(this.errbuf.substr(0, end));
|
||||
this.errbuf = this.errbuf.substr(end + 1);
|
||||
}
|
||||
if (this.errbuf.length) {
|
||||
this.logNoNewLine("stderr", this.errbuf);
|
||||
this.errbuf = "";
|
||||
}
|
||||
}
|
||||
|
||||
onOutputStderr(lines) {
|
||||
lines = <string[]> lines.split('\n');
|
||||
lines.forEach(line => {
|
||||
this.log("stderr", line);
|
||||
});
|
||||
}
|
||||
|
||||
onOutputPartial(line) {
|
||||
if (couldBeOutput(line)) {
|
||||
this.logNoNewLine("stdout", line);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
onOutput(lines) {
|
||||
lines = <string[]> lines.split('\n');
|
||||
lines.forEach(line => {
|
||||
if (couldBeOutput(line)) {
|
||||
if (!gdbMatch.exec(line))
|
||||
this.log("stdout", line);
|
||||
} else {
|
||||
const parsed = parseMI(line);
|
||||
if (this.debugOutput)
|
||||
this.log("log", "GDB -> App: " + JSON.stringify(parsed));
|
||||
let handled = false;
|
||||
if (parsed.token !== undefined) {
|
||||
if (this.handlers[parsed.token]) {
|
||||
this.handlers[parsed.token](parsed);
|
||||
delete this.handlers[parsed.token];
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
if (!handled && parsed.resultRecords && parsed.resultRecords.resultClass == "error") {
|
||||
this.log("stderr", parsed.result("msg") || line);
|
||||
}
|
||||
if (parsed.outOfBandRecord) {
|
||||
parsed.outOfBandRecord.forEach(record => {
|
||||
if (record.isStream) {
|
||||
this.log(record.type, record.content);
|
||||
} else {
|
||||
if (record.type == "exec") {
|
||||
this.emit("exec-async-output", parsed);
|
||||
if (record.asyncClass == "running") {
|
||||
this.status = 'running';
|
||||
this.emit("running", parsed);
|
||||
}
|
||||
else if (record.asyncClass == "stopped") {
|
||||
this.status = 'stopped';
|
||||
const reason = parsed.record("reason");
|
||||
if (reason === undefined) {
|
||||
if (trace)
|
||||
this.log("stderr", "stop (no reason given)");
|
||||
// attaching to a process stops, but does not provide a reason
|
||||
// also python generated interrupt seems to only produce this
|
||||
this.emit("step-other", parsed);
|
||||
} else {
|
||||
if (trace)
|
||||
this.log("stderr", "stop: " + reason);
|
||||
switch (reason) {
|
||||
case "breakpoint-hit":
|
||||
this.emit("breakpoint", parsed);
|
||||
break;
|
||||
case "watchpoint-trigger":
|
||||
case "read-watchpoint-trigger":
|
||||
case "access-watchpoint-trigger":
|
||||
this.emit("watchpoint", parsed);
|
||||
break;
|
||||
case "function-finished":
|
||||
// identical result -> send step-end
|
||||
// this.emit("step-out-end", parsed);
|
||||
// break;
|
||||
case "location-reached":
|
||||
case "end-stepping-range":
|
||||
this.emit("step-end", parsed);
|
||||
break;
|
||||
case "watchpoint-scope":
|
||||
case "solib-event":
|
||||
case "syscall-entry":
|
||||
case "syscall-return":
|
||||
// TODO: inform the user
|
||||
this.emit("step-end", parsed);
|
||||
break;
|
||||
case "fork":
|
||||
case "vfork":
|
||||
case "exec":
|
||||
// TODO: inform the user, possibly add second inferiour
|
||||
this.emit("step-end", parsed);
|
||||
break;
|
||||
case "signal-received":
|
||||
this.emit("signal-stop", parsed);
|
||||
break;
|
||||
case "exited-normally":
|
||||
this.emit("exited-normally", parsed);
|
||||
break;
|
||||
case "exited": // exit with error code != 0
|
||||
this.log("stderr", "Program exited with code " + parsed.record("exit-code"));
|
||||
this.emit("exited-normally", parsed);
|
||||
break;
|
||||
// case "exited-signalled": // consider handling that explicit possible
|
||||
// this.log("stderr", "Program exited because of signal " + parsed.record("signal"));
|
||||
// this.emit("stoped", parsed);
|
||||
// break;
|
||||
|
||||
default:
|
||||
this.log("console", "Not implemented stop reason (assuming exception): " + reason);
|
||||
this.emit("stopped", parsed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else
|
||||
this.log("log", JSON.stringify(parsed));
|
||||
} else if (record.type == "notify") {
|
||||
if (record.asyncClass == "thread-created") {
|
||||
this.emit("thread-created", parsed);
|
||||
} else if (record.asyncClass == "thread-exited") {
|
||||
this.emit("thread-exited", parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
handled = true;
|
||||
}
|
||||
if (parsed.token == undefined && parsed.resultRecords == undefined && parsed.outOfBandRecord.length == 0)
|
||||
handled = true;
|
||||
if (!handled)
|
||||
this.log("log", "Unhandled: " + JSON.stringify(parsed));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
start(runToStart: boolean): Thenable<boolean> {
|
||||
const options: string[] = [];
|
||||
if (runToStart)
|
||||
options.push("--start");
|
||||
const startCommand: string = ["exec-run"].concat(options).join(" ");
|
||||
return new Promise((resolve, reject) => {
|
||||
this.log("console", "Running executable");
|
||||
this.sendCommand(startCommand).then((info) => {
|
||||
if (info.resultRecords.resultClass == "running")
|
||||
resolve(undefined);
|
||||
else
|
||||
reject();
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.isSSH) {
|
||||
const proc = this.stream;
|
||||
const to = setTimeout(() => {
|
||||
proc.signal("KILL");
|
||||
}, 1000);
|
||||
this.stream.on("exit", function (code) {
|
||||
clearTimeout(to);
|
||||
});
|
||||
this.sendRaw("-gdb-exit");
|
||||
} else {
|
||||
const proc = this.process;
|
||||
const to = setTimeout(() => {
|
||||
process.kill(-proc.pid);
|
||||
}, 1000);
|
||||
this.process.on("exit", function (code) {
|
||||
clearTimeout(to);
|
||||
});
|
||||
this.sendRaw("-gdb-exit");
|
||||
}
|
||||
}
|
||||
|
||||
detach() {
|
||||
const proc = this.process;
|
||||
const to = setTimeout(() => {
|
||||
process.kill(-proc.pid);
|
||||
}, 1000);
|
||||
this.process.on("exit", function (code) {
|
||||
clearTimeout(to);
|
||||
});
|
||||
this.sendRaw("-target-detach");
|
||||
}
|
||||
|
||||
interrupt(): Thenable<boolean> {
|
||||
if (trace)
|
||||
this.log("stderr", "interrupt");
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendCommand("exec-interrupt").then((info) => {
|
||||
resolve(info.resultRecords.resultClass == "done");
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
continue(reverse: boolean = false): Thenable<boolean> {
|
||||
if (trace)
|
||||
this.log("stderr", "continue");
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendCommand("exec-continue" + (reverse ? " --reverse" : "")).then((info) => {
|
||||
resolve(info.resultRecords.resultClass == "running");
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
next(reverse: boolean = false): Thenable<boolean> {
|
||||
if (trace)
|
||||
this.log("stderr", "next");
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendCommand("exec-next" + (reverse ? " --reverse" : "")).then((info) => {
|
||||
resolve(info.resultRecords.resultClass == "running");
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
step(reverse: boolean = false): Thenable<boolean> {
|
||||
if (trace)
|
||||
this.log("stderr", "step");
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendCommand("exec-step" + (reverse ? " --reverse" : "")).then((info) => {
|
||||
resolve(info.resultRecords.resultClass == "running");
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
stepOut(reverse: boolean = false): Thenable<boolean> {
|
||||
if (trace)
|
||||
this.log("stderr", "stepOut");
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendCommand("exec-finish" + (reverse ? " --reverse" : "")).then((info) => {
|
||||
resolve(info.resultRecords.resultClass == "running");
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
goto(filename: string, line: number): Thenable<Boolean> {
|
||||
if (trace)
|
||||
this.log("stderr", "goto");
|
||||
return new Promise((resolve, reject) => {
|
||||
const target: string = '"' + (filename ? escape(filename) + ":" : "") + line + '"';
|
||||
this.sendCommand("break-insert -t " + target).then(() => {
|
||||
this.sendCommand("exec-jump " + target).then((info) => {
|
||||
resolve(info.resultRecords.resultClass == "running");
|
||||
}, reject);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
changeVariable(name: string, rawValue: string): Thenable<any> {
|
||||
if (trace)
|
||||
this.log("stderr", "changeVariable");
|
||||
return this.sendCommand("gdb-set var " + name + "=" + rawValue);
|
||||
}
|
||||
|
||||
loadBreakPoints(breakpoints: Breakpoint[]): Thenable<[boolean, Breakpoint][]> {
|
||||
if (trace)
|
||||
this.log("stderr", "loadBreakPoints");
|
||||
const promisses = [];
|
||||
breakpoints.forEach(breakpoint => {
|
||||
promisses.push(this.addBreakPoint(breakpoint));
|
||||
});
|
||||
return Promise.all(promisses);
|
||||
}
|
||||
|
||||
setBreakPointCondition(bkptNum, condition): Thenable<any> {
|
||||
if (trace)
|
||||
this.log("stderr", "setBreakPointCondition");
|
||||
return this.sendCommand("break-condition " + bkptNum + " " + condition);
|
||||
}
|
||||
|
||||
setEntryBreakPoint(entryPoint: string): Thenable<any> {
|
||||
return this.sendCommand("break-insert -t -f " + entryPoint);
|
||||
}
|
||||
|
||||
addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]> {
|
||||
if (trace)
|
||||
this.log("stderr", "addBreakPoint");
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.breakpoints.has(breakpoint))
|
||||
return resolve([false, undefined]);
|
||||
let location = "";
|
||||
if (breakpoint.countCondition) {
|
||||
if (breakpoint.countCondition[0] == ">")
|
||||
location += "-i " + numRegex.exec(breakpoint.countCondition.substr(1))[0] + " ";
|
||||
else {
|
||||
const match = numRegex.exec(breakpoint.countCondition)[0];
|
||||
if (match.length != breakpoint.countCondition.length) {
|
||||
this.log("stderr", "Unsupported break count expression: '" + breakpoint.countCondition + "'. Only supports 'X' for breaking once after X times or '>X' for ignoring the first X breaks");
|
||||
location += "-t ";
|
||||
} else if (parseInt(match) != 0)
|
||||
location += "-t -i " + parseInt(match) + " ";
|
||||
}
|
||||
}
|
||||
if (breakpoint.raw)
|
||||
location += '"' + escape(breakpoint.raw) + '"';
|
||||
else
|
||||
location += '"' + escape(breakpoint.file) + ":" + breakpoint.line + '"';
|
||||
this.sendCommand("break-insert -f " + location).then((result) => {
|
||||
if (result.resultRecords.resultClass == "done") {
|
||||
const bkptNum = parseInt(result.result("bkpt.number"));
|
||||
const newBrk = {
|
||||
file: breakpoint.file ? breakpoint.file : result.result("bkpt.file"),
|
||||
raw: breakpoint.raw,
|
||||
line: parseInt(result.result("bkpt.line")),
|
||||
condition: breakpoint.condition
|
||||
};
|
||||
if (breakpoint.condition) {
|
||||
this.setBreakPointCondition(bkptNum, breakpoint.condition).then((result) => {
|
||||
if (result.resultRecords.resultClass == "done") {
|
||||
this.breakpoints.set(newBrk, bkptNum);
|
||||
resolve([true, newBrk]);
|
||||
} else {
|
||||
resolve([false, undefined]);
|
||||
}
|
||||
}, reject);
|
||||
} else {
|
||||
this.breakpoints.set(newBrk, bkptNum);
|
||||
resolve([true, newBrk]);
|
||||
}
|
||||
} else {
|
||||
reject(result);
|
||||
}
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean> {
|
||||
if (trace)
|
||||
this.log("stderr", "removeBreakPoint");
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.breakpoints.has(breakpoint))
|
||||
return resolve(false);
|
||||
this.sendCommand("break-delete " + this.breakpoints.get(breakpoint)).then((result) => {
|
||||
if (result.resultRecords.resultClass == "done") {
|
||||
this.breakpoints.delete(breakpoint);
|
||||
resolve(true);
|
||||
} else resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
clearBreakPoints(source?: string): Thenable<any> {
|
||||
if (trace)
|
||||
this.log("stderr", "clearBreakPoints");
|
||||
return new Promise((resolve, reject) => {
|
||||
const promises = [];
|
||||
const breakpoints = this.breakpoints;
|
||||
this.breakpoints = new Map();
|
||||
breakpoints.forEach((k, index) => {
|
||||
if (index.file === source) {
|
||||
promises.push(this.sendCommand("break-delete " + k).then((result) => {
|
||||
if (result.resultRecords.resultClass == "done") resolve(true);
|
||||
else resolve(false);
|
||||
}));
|
||||
} else {
|
||||
this.breakpoints.set(index, k);
|
||||
}
|
||||
});
|
||||
Promise.all(promises).then(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
async getThreads(): Promise<Thread[]> {
|
||||
if (trace) this.log("stderr", "getThreads");
|
||||
|
||||
const command = "thread-info";
|
||||
const result = await this.sendCommand(command);
|
||||
const threads = result.result("threads");
|
||||
const ret: Thread[] = [];
|
||||
return threads.map(element => {
|
||||
const ret: Thread = {
|
||||
id: parseInt(MINode.valueOf(element, "id")),
|
||||
targetId: MINode.valueOf(element, "target-id")
|
||||
};
|
||||
|
||||
ret.name = MINode.valueOf(element, "details")
|
||||
|| undefined;
|
||||
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
async getStack(startFrame: number, maxLevels: number, thread: number): Promise<Stack[]> {
|
||||
if (trace) this.log("stderr", "getStack");
|
||||
|
||||
const options: string[] = [];
|
||||
|
||||
if (thread != 0)
|
||||
options.push("--thread " + thread);
|
||||
|
||||
const depth: number = (await this.sendCommand(["stack-info-depth"].concat(options).join(" "))).result("depth").valueOf();
|
||||
const lowFrame: number = startFrame ? startFrame : 0;
|
||||
const highFrame: number = (maxLevels ? Math.min(depth, lowFrame + maxLevels) : depth) - 1;
|
||||
|
||||
if (highFrame < lowFrame)
|
||||
return [];
|
||||
|
||||
options.push(lowFrame.toString());
|
||||
options.push(highFrame.toString());
|
||||
|
||||
const result = await this.sendCommand(["stack-list-frames"].concat(options).join(" "));
|
||||
const stack = result.result("stack");
|
||||
return stack.map(element => {
|
||||
const level = MINode.valueOf(element, "@frame.level");
|
||||
const addr = MINode.valueOf(element, "@frame.addr");
|
||||
const func = MINode.valueOf(element, "@frame.func");
|
||||
const filename = MINode.valueOf(element, "@frame.file");
|
||||
let file: string = MINode.valueOf(element, "@frame.fullname");
|
||||
if (file) {
|
||||
if (this.isSSH)
|
||||
file = path.posix.normalize(file);
|
||||
else
|
||||
file = path.normalize(file);
|
||||
}
|
||||
|
||||
let line = 0;
|
||||
const lnstr = MINode.valueOf(element, "@frame.line");
|
||||
if (lnstr)
|
||||
line = parseInt(lnstr);
|
||||
const from = parseInt(MINode.valueOf(element, "@frame.from"));
|
||||
return {
|
||||
address: addr,
|
||||
fileName: filename,
|
||||
file: file,
|
||||
function: func || from,
|
||||
level: level,
|
||||
line: line
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async getStackVariables(thread: number, frame: number): Promise<Variable[]> {
|
||||
if (trace)
|
||||
this.log("stderr", "getStackVariables");
|
||||
|
||||
const result = await this.sendCommand(`stack-list-variables --thread ${thread} --frame ${frame} --simple-values`);
|
||||
const variables = result.result("variables");
|
||||
const ret: Variable[] = [];
|
||||
for (const element of variables) {
|
||||
const key = MINode.valueOf(element, "name");
|
||||
const value = MINode.valueOf(element, "value");
|
||||
const type = MINode.valueOf(element, "type");
|
||||
ret.push({
|
||||
name: key,
|
||||
valueStr: value,
|
||||
type: type,
|
||||
raw: element
|
||||
});
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
examineMemory(from: number, length: number): Thenable<any> {
|
||||
if (trace)
|
||||
this.log("stderr", "examineMemory");
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendCommand("data-read-memory-bytes 0x" + from.toString(16) + " " + length).then((result) => {
|
||||
resolve(result.result("memory[0].contents"));
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
record(): Thenable<any> {
|
||||
if (trace)
|
||||
this.log("stderr", "record");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if (this.status != 'stopped') {
|
||||
resolve(this.isRecord ? {result:'0', oldValue:'1'} : { result:'0', oldValue:'0'});
|
||||
this.log("stderr", `WARNING: The program is ${this.status} and cannot ${this.isRecord ? 'stop' : 'start'} reverse debugging.`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.isRecord = !this.isRecord;
|
||||
this.sendCliCommand(this.isRecord ? "record" : "record stop").then((result) => {
|
||||
if (result.resultRecords.resultClass == "done") {
|
||||
resolve(this.isRecord ? {result:'1', oldValue:'0'} : { result:'1', oldValue:'1'});
|
||||
}
|
||||
else {
|
||||
this.isRecord = !this.isRecord; //restore
|
||||
resolve(this.isRecord ? {result:'0', oldValue:'1'} : { result:'0', oldValue:'0'});
|
||||
}
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
async evalExpression(name: string, thread: number, frame: number): Promise<MINode> {
|
||||
if (trace)
|
||||
this.log("stderr", "evalExpression");
|
||||
|
||||
let command = "data-evaluate-expression ";
|
||||
if (thread != 0) {
|
||||
command += `--thread ${thread} --frame ${frame} `;
|
||||
}
|
||||
command += name;
|
||||
|
||||
return await this.sendCommand(command);
|
||||
}
|
||||
|
||||
async varCreate(expression: string, name: string = "-", frame: string = "@"): Promise<VariableObject> {
|
||||
if (trace)
|
||||
this.log("stderr", "varCreate");
|
||||
const res = await this.sendCommand(`var-create ${this.quote(name)} ${frame} "${expression}"`);
|
||||
return new VariableObject(res.result(""));
|
||||
}
|
||||
|
||||
async varEvalExpression(name: string): Promise<MINode> {
|
||||
if (trace)
|
||||
this.log("stderr", "varEvalExpression");
|
||||
return this.sendCommand(`var-evaluate-expression ${this.quote(name)}`);
|
||||
}
|
||||
|
||||
async varListChildren(name: string): Promise<VariableObject[]> {
|
||||
if (trace)
|
||||
this.log("stderr", "varListChildren");
|
||||
//TODO: add `from` and `to` arguments
|
||||
const res = await this.sendCommand(`var-list-children --all-values ${this.quote(name)}`);
|
||||
const children = res.result("children") || [];
|
||||
const omg: VariableObject[] = children.map(child => new VariableObject(child[1]));
|
||||
return omg;
|
||||
}
|
||||
|
||||
async varUpdate(name: string = "*"): Promise<MINode> {
|
||||
if (trace)
|
||||
this.log("stderr", "varUpdate");
|
||||
return this.sendCommand(`var-update --all-values ${this.quote(name)}`);
|
||||
}
|
||||
|
||||
async varAssign(name: string, rawValue: string): Promise<MINode> {
|
||||
if (trace)
|
||||
this.log("stderr", "varAssign");
|
||||
return this.sendCommand(`var-assign ${this.quote(name)} ${rawValue}`);
|
||||
}
|
||||
|
||||
logNoNewLine(type: string, msg: string) {
|
||||
this.emit("msg", type, msg);
|
||||
}
|
||||
|
||||
log(type: string, msg: string) {
|
||||
this.emit("msg", type, msg[msg.length - 1] == '\n' ? msg : (msg + "\n"));
|
||||
}
|
||||
|
||||
sendUserInput(command: string, threadId: number = 0, frameLevel: number = 0): Thenable<MINode> {
|
||||
if (command.startsWith("-")) {
|
||||
return this.sendCommand(command.substr(1));
|
||||
} else {
|
||||
return this.sendCliCommand(command, threadId, frameLevel);
|
||||
}
|
||||
}
|
||||
|
||||
sendRaw(raw: string) {
|
||||
if (this.printCalls)
|
||||
this.log("log", raw);
|
||||
if (this.isSSH)
|
||||
this.stream.write(raw + "\n");
|
||||
else
|
||||
this.process.stdin.write(raw + "\n");
|
||||
}
|
||||
|
||||
sendCliCommand(command: string, threadId: number = 0, frameLevel: number = 0): Thenable<MINode> {
|
||||
let miCommand = "interpreter-exec ";
|
||||
if (threadId != 0) {
|
||||
miCommand += `--thread ${threadId} --frame ${frameLevel} `;
|
||||
}
|
||||
miCommand += `console "${command.replace(/[\\"']/g, '\\$&')}"`;
|
||||
return this.sendCommand(miCommand);
|
||||
}
|
||||
|
||||
sendCommand(command: string, suppressFailure: boolean = false): Thenable<MINode> {
|
||||
const sel = this.currentToken++;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.handlers[sel] = (node: MINode) => {
|
||||
if (node && node.resultRecords && node.resultRecords.resultClass === "error") {
|
||||
if (suppressFailure) {
|
||||
this.log("stderr", `WARNING: Error executing command '${command}'`);
|
||||
resolve(node);
|
||||
} else
|
||||
reject(new MIError(node.result("msg") || "Internal error", command));
|
||||
} else
|
||||
resolve(node);
|
||||
};
|
||||
this.sendRaw(sel + "-" + command);
|
||||
});
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this.isSSH ? this.sshReady : !!this.process;
|
||||
}
|
||||
|
||||
protected quote(text: string): string {
|
||||
// only escape if text contains non-word or non-path characters such as whitespace or quotes
|
||||
return /^-|[^\w\d\/_\-\.]/g.test(text) ? ('"' + escape(text) + '"') : text;
|
||||
}
|
||||
|
||||
isRecord:boolean = false;
|
||||
status: 'running' | 'stopped' | 'none' = 'none';
|
||||
prettyPrint: boolean = true;
|
||||
printCalls: boolean;
|
||||
debugOutput: boolean;
|
||||
features: string[];
|
||||
public procEnv: any;
|
||||
protected isSSH: boolean;
|
||||
protected sshReady: boolean;
|
||||
protected currentToken: number = 1;
|
||||
protected handlers: { [index: number]: (info: MINode) => any } = {};
|
||||
protected breakpoints: Map<Breakpoint, Number> = new Map();
|
||||
protected buffer: string;
|
||||
protected errbuf: string;
|
||||
protected process: ChildProcess.ChildProcess;
|
||||
protected stream;
|
||||
protected sshConn;
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
import { MI2, escape } from "./mi2";
|
||||
import { Breakpoint } from "../backend";
|
||||
import * as ChildProcess from "child_process";
|
||||
import * as path from "path";
|
||||
|
||||
export class MI2_LLDB extends MI2 {
|
||||
protected initCommands(target: string, cwd: string, attach: boolean = false) {
|
||||
// We need to account for the possibility of the path type used by the debugger being different
|
||||
// than the path type where the extension is running (e.g., SSH from Linux to Windows machine).
|
||||
// Since the CWD is expected to be an absolute path in the debugger's environment, we can test
|
||||
// that to determine the path type used by the debugger and use the result of that test to
|
||||
// select the correct API to check whether the target path is an absolute path.
|
||||
const debuggerPath = path.posix.isAbsolute(cwd) ? path.posix : path.win32;
|
||||
|
||||
if (!debuggerPath.isAbsolute(target))
|
||||
target = debuggerPath.join(cwd, target);
|
||||
|
||||
const cmds = [
|
||||
this.sendCommand("gdb-set target-async on"),
|
||||
new Promise(resolve => {
|
||||
this.sendCommand("list-features").then(done => {
|
||||
this.features = done.result("features");
|
||||
resolve(undefined);
|
||||
}, err => {
|
||||
this.features = [];
|
||||
resolve(undefined);
|
||||
});
|
||||
})
|
||||
];
|
||||
if (!attach)
|
||||
cmds.push(this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\""));
|
||||
for (let cmd of this.extraCommands) {
|
||||
cmds.push(this.sendCliCommand(cmd));
|
||||
}
|
||||
return cmds;
|
||||
}
|
||||
|
||||
attach(cwd: string, executable: string, target: string): Thenable<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const args = this.preargs.concat(this.extraargs || []);
|
||||
this.process = ChildProcess.spawn(this.application, args, { cwd: cwd, env: this.procEnv });
|
||||
this.process.stdout.on("data", this.stdout.bind(this));
|
||||
this.process.stderr.on("data", this.stderr.bind(this));
|
||||
this.process.on("exit", (() => { this.emit("quit"); }).bind(this));
|
||||
this.process.on("error", ((err) => { this.emit("launcherror", err); }).bind(this));
|
||||
const promises = this.initCommands(target, cwd, true);
|
||||
promises.push(this.sendCommand("file-exec-and-symbols \"" + escape(executable) + "\""));
|
||||
promises.push(this.sendCommand("target-attach " + target));
|
||||
Promise.all(promises).then(() => {
|
||||
this.emit("debug-ready");
|
||||
resolve(undefined);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
setBreakPointCondition(bkptNum, condition): Thenable<any> {
|
||||
return this.sendCommand("break-condition " + bkptNum + " \"" + escape(condition) + "\" 1");
|
||||
}
|
||||
|
||||
goto(filename: string, line: number): Thenable<Boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// LLDB parses the file differently than GDB...
|
||||
// GDB doesn't allow quoting only the file but only the whole argument
|
||||
// LLDB doesn't allow quoting the whole argument but rather only the file
|
||||
const target: string = (filename ? '"' + escape(filename) + '":' : "") + line;
|
||||
this.sendCliCommand("jump " + target).then(() => {
|
||||
this.emit("step-other", null);
|
||||
resolve(true);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { MI2_LLDB } from "./mi2lldb";
|
||||
import { Stack } from "../backend";
|
||||
import { MINode } from "../mi_parse";
|
||||
|
||||
export class MI2_Mago extends MI2_LLDB {
|
||||
getStack(startFrame: number, maxLevels: number, thread: number): Promise<Stack[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const command = "stack-list-frames";
|
||||
this.sendCommand(command).then((result) => {
|
||||
const stack = result.resultRecords.results;
|
||||
const ret: Stack[] = [];
|
||||
const remaining = [];
|
||||
const addToStack = (element) => {
|
||||
const level = MINode.valueOf(element, "frame.level");
|
||||
const addr = MINode.valueOf(element, "frame.addr");
|
||||
const func = MINode.valueOf(element, "frame.func");
|
||||
const filename = MINode.valueOf(element, "file");
|
||||
const file = MINode.valueOf(element, "fullname");
|
||||
let line = 0;
|
||||
const lnstr = MINode.valueOf(element, "line");
|
||||
if (lnstr)
|
||||
line = parseInt(lnstr);
|
||||
const from = parseInt(MINode.valueOf(element, "from"));
|
||||
ret.push({
|
||||
address: addr,
|
||||
fileName: filename || "",
|
||||
file: file || "<unknown>",
|
||||
function: func || from || "<unknown>",
|
||||
level: level,
|
||||
line: line
|
||||
});
|
||||
};
|
||||
stack.forEach(element => {
|
||||
if (element)
|
||||
if (element[0] == "stack") {
|
||||
addToStack(element[1]);
|
||||
} else remaining.push(element);
|
||||
});
|
||||
if (remaining.length)
|
||||
addToStack(remaining);
|
||||
resolve(ret);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,313 @@
|
|||
export interface MIInfo {
|
||||
token: number;
|
||||
outOfBandRecord: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[];
|
||||
resultRecords: { resultClass: string, results: [string, any][] };
|
||||
}
|
||||
|
||||
const octalMatch = /^[0-7]{3}/;
|
||||
function parseString(str: string): string {
|
||||
const ret = new Buffer(str.length * 4);
|
||||
let bufIndex = 0;
|
||||
|
||||
if (str[0] != '"' || str[str.length - 1] != '"')
|
||||
throw new Error("Not a valid string");
|
||||
str = str.slice(1, -1);
|
||||
let escaped = false;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (escaped) {
|
||||
let m;
|
||||
if (str[i] == '\\')
|
||||
bufIndex += ret.write('\\', bufIndex);
|
||||
else if (str[i] == '"')
|
||||
bufIndex += ret.write('"', bufIndex);
|
||||
else if (str[i] == '\'')
|
||||
bufIndex += ret.write('\'', bufIndex);
|
||||
else if (str[i] == 'n')
|
||||
bufIndex += ret.write('\n', bufIndex);
|
||||
else if (str[i] == 'r')
|
||||
bufIndex += ret.write('\r', bufIndex);
|
||||
else if (str[i] == 't')
|
||||
bufIndex += ret.write('\t', bufIndex);
|
||||
else if (str[i] == 'b')
|
||||
bufIndex += ret.write('\b', bufIndex);
|
||||
else if (str[i] == 'f')
|
||||
bufIndex += ret.write('\f', bufIndex);
|
||||
else if (str[i] == 'v')
|
||||
bufIndex += ret.write('\v', bufIndex);
|
||||
else if (str[i] == '0')
|
||||
bufIndex += ret.write('\0', bufIndex);
|
||||
else if (m = octalMatch.exec(str.substr(i))) {
|
||||
ret.writeUInt8(parseInt(m[0], 8), bufIndex++);
|
||||
i += 2;
|
||||
} else
|
||||
bufIndex += ret.write(str[i], bufIndex);
|
||||
escaped = false;
|
||||
} else {
|
||||
if (str[i] == '\\')
|
||||
escaped = true;
|
||||
else if (str[i] == '"')
|
||||
throw new Error("Not a valid string");
|
||||
else
|
||||
bufIndex += ret.write(str[i], bufIndex);
|
||||
}
|
||||
}
|
||||
return ret.slice(0, bufIndex).toString("utf8");
|
||||
}
|
||||
|
||||
export class MINode implements MIInfo {
|
||||
token: number;
|
||||
outOfBandRecord: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[];
|
||||
resultRecords: { resultClass: string, results: [string, any][] };
|
||||
|
||||
constructor(token: number, info: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[], result: { resultClass: string, results: [string, any][] }) {
|
||||
this.token = token;
|
||||
this.outOfBandRecord = info;
|
||||
this.resultRecords = result;
|
||||
}
|
||||
|
||||
record(path: string): any {
|
||||
if (!this.outOfBandRecord)
|
||||
return undefined;
|
||||
return MINode.valueOf(this.outOfBandRecord[0].output, path);
|
||||
}
|
||||
|
||||
result(path: string): any {
|
||||
if (!this.resultRecords)
|
||||
return undefined;
|
||||
return MINode.valueOf(this.resultRecords.results, path);
|
||||
}
|
||||
|
||||
static valueOf(start: any, path: string): any {
|
||||
if (!start)
|
||||
return undefined;
|
||||
const pathRegex = /^\.?([a-zA-Z_\-][a-zA-Z0-9_\-]*)/;
|
||||
const indexRegex = /^\[(\d+)\](?:$|\.)/;
|
||||
path = path.trim();
|
||||
if (!path)
|
||||
return start;
|
||||
let current = start;
|
||||
do {
|
||||
let target = pathRegex.exec(path);
|
||||
if (target) {
|
||||
path = path.substr(target[0].length);
|
||||
if (current.length && typeof current != "string") {
|
||||
const found = [];
|
||||
for (const element of current) {
|
||||
if (element[0] == target[1]) {
|
||||
found.push(element[1]);
|
||||
}
|
||||
}
|
||||
if (found.length > 1) {
|
||||
current = found;
|
||||
} else if (found.length == 1) {
|
||||
current = found[0];
|
||||
} else return undefined;
|
||||
} else return undefined;
|
||||
} else if (path[0] == '@') {
|
||||
current = [current];
|
||||
path = path.substr(1);
|
||||
} else {
|
||||
target = indexRegex.exec(path);
|
||||
if (target) {
|
||||
path = path.substr(target[0].length);
|
||||
const i = parseInt(target[1]);
|
||||
if (current.length && typeof current != "string" && i >= 0 && i < current.length) {
|
||||
current = current[i];
|
||||
} else if (i == 0) {
|
||||
} else return undefined;
|
||||
} else return undefined;
|
||||
}
|
||||
path = path.trim();
|
||||
} while (path);
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
const tokenRegex = /^\d+/;
|
||||
const outOfBandRecordRegex = /^(?:(\d*|undefined)([\*\+\=])|([\~\@\&]))/;
|
||||
const resultRecordRegex = /^(\d*)\^(done|running|connected|error|exit)/;
|
||||
const newlineRegex = /^\r\n?/;
|
||||
const endRegex = /^\(gdb\)\r\n?/;
|
||||
const variableRegex = /^([a-zA-Z_\-][a-zA-Z0-9_\-]*)/;
|
||||
const asyncClassRegex = /^[^,\r\n]+/;
|
||||
|
||||
export function parseMI(output: string): MINode {
|
||||
/*
|
||||
output ==>
|
||||
(
|
||||
exec-async-output = [ token ] "*" ("stopped" | others) ( "," variable "=" (const | tuple | list) )* \n
|
||||
status-async-output = [ token ] "+" ("stopped" | others) ( "," variable "=" (const | tuple | list) )* \n
|
||||
notify-async-output = [ token ] "=" ("stopped" | others) ( "," variable "=" (const | tuple | list) )* \n
|
||||
console-stream-output = "~" c-string \n
|
||||
target-stream-output = "@" c-string \n
|
||||
log-stream-output = "&" c-string \n
|
||||
)*
|
||||
[
|
||||
[ token ] "^" ("done" | "running" | "connected" | "error" | "exit") ( "," variable "=" (const | tuple | list) )* \n
|
||||
]
|
||||
"(gdb)" \n
|
||||
*/
|
||||
|
||||
let token = undefined;
|
||||
const outOfBandRecord = [];
|
||||
let resultRecords = undefined;
|
||||
|
||||
const asyncRecordType = {
|
||||
"*": "exec",
|
||||
"+": "status",
|
||||
"=": "notify"
|
||||
};
|
||||
const streamRecordType = {
|
||||
"~": "console",
|
||||
"@": "target",
|
||||
"&": "log"
|
||||
};
|
||||
|
||||
const parseCString = () => {
|
||||
if (output[0] != '"')
|
||||
return "";
|
||||
let stringEnd = 1;
|
||||
let inString = true;
|
||||
let remaining = output.substr(1);
|
||||
let escaped = false;
|
||||
while (inString) {
|
||||
if (escaped)
|
||||
escaped = false;
|
||||
else if (remaining[0] == '\\')
|
||||
escaped = true;
|
||||
else if (remaining[0] == '"')
|
||||
inString = false;
|
||||
|
||||
remaining = remaining.substr(1);
|
||||
stringEnd++;
|
||||
}
|
||||
let str;
|
||||
try {
|
||||
str = parseString(output.substr(0, stringEnd));
|
||||
} catch (e) {
|
||||
str = output.substr(0, stringEnd);
|
||||
}
|
||||
output = output.substr(stringEnd);
|
||||
return str;
|
||||
};
|
||||
|
||||
let parseValue, parseCommaResult, parseCommaValue, parseResult;
|
||||
|
||||
const parseTupleOrList = () => {
|
||||
if (output[0] != '{' && output[0] != '[')
|
||||
return undefined;
|
||||
const oldContent = output;
|
||||
const canBeValueList = output[0] == '[';
|
||||
output = output.substr(1);
|
||||
if (output[0] == '}' || output[0] == ']') {
|
||||
output = output.substr(1); // ] or }
|
||||
return [];
|
||||
}
|
||||
if (canBeValueList) {
|
||||
let value = parseValue();
|
||||
if (value !== undefined) { // is value list
|
||||
const values = [];
|
||||
values.push(value);
|
||||
const remaining = output;
|
||||
while ((value = parseCommaValue()) !== undefined)
|
||||
values.push(value);
|
||||
output = output.substr(1); // ]
|
||||
return values;
|
||||
}
|
||||
}
|
||||
let result = parseResult();
|
||||
if (result) {
|
||||
const results = [];
|
||||
results.push(result);
|
||||
while (result = parseCommaResult())
|
||||
results.push(result);
|
||||
output = output.substr(1); // }
|
||||
return results;
|
||||
}
|
||||
output = (canBeValueList ? '[' : '{') + output;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
parseValue = () => {
|
||||
if (output[0] == '"')
|
||||
return parseCString();
|
||||
else if (output[0] == '{' || output[0] == '[')
|
||||
return parseTupleOrList();
|
||||
else
|
||||
return undefined;
|
||||
};
|
||||
|
||||
parseResult = () => {
|
||||
const variableMatch = variableRegex.exec(output);
|
||||
if (!variableMatch)
|
||||
return undefined;
|
||||
output = output.substr(variableMatch[0].length + 1);
|
||||
const variable = variableMatch[1];
|
||||
return [variable, parseValue()];
|
||||
};
|
||||
|
||||
parseCommaValue = () => {
|
||||
if (output[0] != ',')
|
||||
return undefined;
|
||||
output = output.substr(1);
|
||||
return parseValue();
|
||||
};
|
||||
|
||||
parseCommaResult = () => {
|
||||
if (output[0] != ',')
|
||||
return undefined;
|
||||
output = output.substr(1);
|
||||
return parseResult();
|
||||
};
|
||||
|
||||
let match = undefined;
|
||||
|
||||
while (match = outOfBandRecordRegex.exec(output)) {
|
||||
output = output.substr(match[0].length);
|
||||
if (match[1] && token === undefined && match[1] !== "undefined") {
|
||||
token = parseInt(match[1]);
|
||||
}
|
||||
|
||||
if (match[2]) {
|
||||
const classMatch = asyncClassRegex.exec(output);
|
||||
output = output.substring(classMatch[0].length);
|
||||
const asyncRecord = {
|
||||
isStream: false,
|
||||
type: asyncRecordType[match[2]],
|
||||
asyncClass: classMatch[0],
|
||||
output: []
|
||||
};
|
||||
let result;
|
||||
while (result = parseCommaResult())
|
||||
asyncRecord.output.push(result);
|
||||
outOfBandRecord.push(asyncRecord);
|
||||
} else if (match[3]) {
|
||||
const streamRecord = {
|
||||
isStream: true,
|
||||
type: streamRecordType[match[3]],
|
||||
content: parseCString()
|
||||
};
|
||||
outOfBandRecord.push(streamRecord);
|
||||
}
|
||||
|
||||
output = output.replace(newlineRegex, "");
|
||||
}
|
||||
|
||||
if (match = resultRecordRegex.exec(output)) {
|
||||
output = output.substr(match[0].length);
|
||||
if (match[1] && token === undefined) {
|
||||
token = parseInt(match[1]);
|
||||
}
|
||||
resultRecords = {
|
||||
resultClass: match[2],
|
||||
results: []
|
||||
};
|
||||
let result;
|
||||
while (result = parseCommaResult())
|
||||
resultRecords.results.push(result);
|
||||
|
||||
output = output.replace(newlineRegex, "");
|
||||
}
|
||||
|
||||
return new MINode(token, <any> outOfBandRecord || [], resultRecords);
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
import * as vscode from "vscode";
|
||||
import * as net from "net";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as os from "os";
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider("debugmemory", new MemoryContentProvider()));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("code-debug.examineMemoryLocation", examineMemory));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("code-debug.getFileNameNoExt", () => {
|
||||
if (!vscode.window.activeTextEditor || !vscode.window.activeTextEditor.document || !vscode.window.activeTextEditor.document.fileName) {
|
||||
vscode.window.showErrorMessage("No editor with valid file name active");
|
||||
return;
|
||||
}
|
||||
const fileName = vscode.window.activeTextEditor.document.fileName;
|
||||
const ext = path.extname(fileName);
|
||||
return fileName.substr(0, fileName.length - ext.length);
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("code-debug.startRecord", handleRecord));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("code-debug.stopRecord", handleRecord));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("code-debug.getFileBasenameNoExt", () => {
|
||||
if (!vscode.window.activeTextEditor || !vscode.window.activeTextEditor.document || !vscode.window.activeTextEditor.document.fileName) {
|
||||
vscode.window.showErrorMessage("No editor with valid file name active");
|
||||
return;
|
||||
}
|
||||
const fileName = path.basename(vscode.window.activeTextEditor.document.fileName);
|
||||
const ext = path.extname(fileName);
|
||||
return fileName.substr(0, fileName.length - ext.length);
|
||||
}));
|
||||
|
||||
vscode.debug.onDidStartDebugSession(()=>{
|
||||
vscode.commands.executeCommand('setContext', 'code-debug.enableStartRecord', true);
|
||||
});
|
||||
}
|
||||
|
||||
const memoryLocationRegex = /^0x[0-9a-f]+$/;
|
||||
|
||||
function getMemoryRange(range: string) {
|
||||
if (!range)
|
||||
return undefined;
|
||||
range = range.replace(/\s+/g, "").toLowerCase();
|
||||
let index;
|
||||
if ((index = range.indexOf("+")) != -1) {
|
||||
const from = range.substr(0, index);
|
||||
let length = range.substr(index + 1);
|
||||
if (!memoryLocationRegex.exec(from))
|
||||
return undefined;
|
||||
if (memoryLocationRegex.exec(length))
|
||||
length = parseInt(length.substr(2), 16).toString();
|
||||
return "from=" + encodeURIComponent(from) + "&length=" + encodeURIComponent(length);
|
||||
} else if ((index = range.indexOf("-")) != -1) {
|
||||
const from = range.substr(0, index);
|
||||
const to = range.substr(index + 1);
|
||||
if (!memoryLocationRegex.exec(from))
|
||||
return undefined;
|
||||
if (!memoryLocationRegex.exec(to))
|
||||
return undefined;
|
||||
return "from=" + encodeURIComponent(from) + "&to=" + encodeURIComponent(to);
|
||||
} else if (memoryLocationRegex.exec(range))
|
||||
return "at=" + encodeURIComponent(range);
|
||||
else return undefined;
|
||||
}
|
||||
|
||||
function execCmd(func:any){
|
||||
const socketlists = path.join(os.tmpdir(), "code-debug-sockets");
|
||||
if (!fs.existsSync(socketlists)) {
|
||||
if (process.platform == "win32")
|
||||
return vscode.window.showErrorMessage("This command is not available on windows");
|
||||
else
|
||||
return vscode.window.showErrorMessage("No debugging sessions available");
|
||||
}
|
||||
fs.readdir(socketlists, (err, files) => {
|
||||
if (err) {
|
||||
if (process.platform == "win32")
|
||||
return vscode.window.showErrorMessage("This command is not available on windows");
|
||||
else
|
||||
return vscode.window.showErrorMessage("No debugging sessions available");
|
||||
}
|
||||
|
||||
if (files.length == 1)
|
||||
func(files[0]);
|
||||
else if (files.length > 0)
|
||||
vscode.window.showQuickPick(files, { placeHolder: "Running debugging instance" }).then(file => func(file));
|
||||
else if (process.platform == "win32")
|
||||
return vscode.window.showErrorMessage("This command is not available on windows");
|
||||
else
|
||||
vscode.window.showErrorMessage("No debugging sessions available");
|
||||
});
|
||||
}
|
||||
|
||||
function examineMemory() {
|
||||
const pickedFile = (file) => {
|
||||
vscode.window.showInputBox({ placeHolder: "Memory Location or Range", validateInput: range => getMemoryRange(range) === undefined ? "Range must either be in format 0xF00-0xF01, 0xF100+32 or 0xABC154" : "" }).then(range => {
|
||||
vscode.window.showTextDocument(vscode.Uri.parse("debugmemory://" + file + "?" + getMemoryRange(range)));
|
||||
});
|
||||
};
|
||||
execCmd(pickedFile);
|
||||
}
|
||||
|
||||
function handleRecord() {
|
||||
const record = (file) => {
|
||||
const conn = net.connect(path.join(os.tmpdir(), "code-debug-sockets", file));
|
||||
conn.write("record");
|
||||
conn.once("data", data => {
|
||||
if (data){
|
||||
let ret = JSON.parse(data.toString());
|
||||
if (ret && ret.result == '1'){
|
||||
console.log('successful to satrt reverse debugging.');
|
||||
}
|
||||
else {
|
||||
vscode.window.showErrorMessage(`Failed to ${ret.oldValue == '1' ? 'stop' : 'start'} reverse debugging`);
|
||||
}
|
||||
vscode.commands.executeCommand('setContext', 'code-debug.enableStartRecord', ret.oldValue == '1' ? true : false);
|
||||
}
|
||||
conn.destroy();
|
||||
});
|
||||
};
|
||||
execCmd(record);
|
||||
}
|
||||
|
||||
class MemoryContentProvider implements vscode.TextDocumentContentProvider {
|
||||
provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): Thenable<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const conn = net.connect(path.join(os.tmpdir(), "code-debug-sockets", uri.authority.toLowerCase()));
|
||||
let from, to;
|
||||
let highlightAt = -1;
|
||||
const splits = uri.query.split("&");
|
||||
if (splits[0].split("=")[0] == "at") {
|
||||
const loc = parseInt(splits[0].split("=")[1].substr(2), 16);
|
||||
highlightAt = 64;
|
||||
from = Math.max(loc - 64, 0);
|
||||
to = Math.max(loc + 768, 0);
|
||||
} else if (splits[0].split("=")[0] == "from") {
|
||||
from = parseInt(splits[0].split("=")[1].substr(2), 16);
|
||||
if (splits[1].split("=")[0] == "to") {
|
||||
to = parseInt(splits[1].split("=")[1].substr(2), 16);
|
||||
} else if (splits[1].split("=")[0] == "length") {
|
||||
to = from + parseInt(splits[1].split("=")[1]);
|
||||
} else return reject("Invalid Range");
|
||||
} else return reject("Invalid Range");
|
||||
if (to < from)
|
||||
return reject("Negative Range");
|
||||
conn.write("examineMemory " + JSON.stringify([from, to - from + 1]));
|
||||
conn.once("data", data => {
|
||||
let formattedCode = " 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n";
|
||||
var index: number = from;
|
||||
const hexString = data.toString();
|
||||
let x = 0;
|
||||
let asciiLine = "";
|
||||
let byteNo = 0;
|
||||
for (let i = 0; i < hexString.length; i += 2) {
|
||||
if (x == 0) {
|
||||
var addr = index.toString(16);
|
||||
while (addr.length < 16) addr = '0' + addr;
|
||||
formattedCode += addr + " ";
|
||||
}
|
||||
index++;
|
||||
|
||||
const digit = hexString.substr(i, 2);
|
||||
const digitNum = parseInt(digit, 16);
|
||||
if (digitNum >= 32 && digitNum <= 126)
|
||||
asciiLine += String.fromCharCode(digitNum);
|
||||
else
|
||||
asciiLine += ".";
|
||||
|
||||
if (highlightAt == byteNo) {
|
||||
formattedCode = formattedCode.slice(0, -1) + "[" + digit + "]";
|
||||
} else {
|
||||
formattedCode += digit + " ";
|
||||
}
|
||||
|
||||
if (x == 7)
|
||||
formattedCode += " ";
|
||||
|
||||
if (++x >= 16) {
|
||||
formattedCode += " " + asciiLine + "\n";
|
||||
x = 0;
|
||||
asciiLine = "";
|
||||
}
|
||||
byteNo++;
|
||||
}
|
||||
if (x > 0) {
|
||||
for (let i = 0; i <= 16 - x; i++) {
|
||||
formattedCode += " ";
|
||||
}
|
||||
if (x >= 8)
|
||||
formattedCode = formattedCode.slice(0, -2);
|
||||
else
|
||||
formattedCode = formattedCode.slice(0, -1);
|
||||
formattedCode += asciiLine;
|
||||
}
|
||||
resolve(center("Memory Range from 0x" + from.toString(16) + " to 0x" + to.toString(16), 84) + "\n\n" + formattedCode);
|
||||
conn.destroy();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function center(str: string, width: number): string {
|
||||
var left = true;
|
||||
while (str.length < width) {
|
||||
if (left) str = ' ' + str;
|
||||
else str = str + ' ';
|
||||
left = !left;
|
||||
}
|
||||
return str;
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
import { MI2DebugSession, RunCommand } from './mibase';
|
||||
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { MI2, escape } from "./backend/mi2/mi2";
|
||||
import { SSHArguments, ValuesFormattingMode } from './backend/backend';
|
||||
|
||||
export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
|
||||
cwd: string;
|
||||
target: string;
|
||||
gdbpath: string;
|
||||
env: any;
|
||||
debugger_args: string[];
|
||||
pathSubstitutions: { [index: string]: string };
|
||||
arguments: string;
|
||||
terminal: string;
|
||||
autorun: string[];
|
||||
stopAtEntry: boolean | string;
|
||||
ssh: SSHArguments;
|
||||
valuesFormatting: ValuesFormattingMode;
|
||||
printCalls: boolean;
|
||||
showDevDebugOutput: boolean;
|
||||
}
|
||||
|
||||
export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments {
|
||||
cwd: string;
|
||||
target: string;
|
||||
gdbpath: string;
|
||||
env: any;
|
||||
debugger_args: string[];
|
||||
pathSubstitutions: { [index: string]: string };
|
||||
executable: string;
|
||||
remote: boolean;
|
||||
autorun: string[];
|
||||
stopAtConnect: boolean;
|
||||
stopAtEntry: boolean | string;
|
||||
ssh: SSHArguments;
|
||||
valuesFormatting: ValuesFormattingMode;
|
||||
printCalls: boolean;
|
||||
showDevDebugOutput: boolean;
|
||||
}
|
||||
|
||||
class GDBDebugSession extends MI2DebugSession {
|
||||
protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
|
||||
response.body.supportsGotoTargetsRequest = true;
|
||||
response.body.supportsHitConditionalBreakpoints = true;
|
||||
response.body.supportsConfigurationDoneRequest = true;
|
||||
response.body.supportsConditionalBreakpoints = true;
|
||||
response.body.supportsFunctionBreakpoints = true;
|
||||
response.body.supportsEvaluateForHovers = true;
|
||||
response.body.supportsSetVariable = true;
|
||||
response.body.supportsStepBack = true;
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
|
||||
this.miDebugger = new MI2(args.gdbpath || "gdb", ["-q", "--interpreter=mi2"], args.debugger_args, args.env);
|
||||
this.setPathSubstitutions(args.pathSubstitutions);
|
||||
this.initDebugger();
|
||||
this.quit = false;
|
||||
this.attached = false;
|
||||
this.initialRunCommand = RunCommand.RUN;
|
||||
this.isSSH = false;
|
||||
this.started = false;
|
||||
this.crashed = false;
|
||||
this.setValuesFormattingMode(args.valuesFormatting);
|
||||
this.miDebugger.printCalls = !!args.printCalls;
|
||||
this.miDebugger.debugOutput = !!args.showDevDebugOutput;
|
||||
this.stopAtEntry = args.stopAtEntry;
|
||||
if (args.ssh !== undefined) {
|
||||
if (args.ssh.forwardX11 === undefined)
|
||||
args.ssh.forwardX11 = true;
|
||||
if (args.ssh.port === undefined)
|
||||
args.ssh.port = 22;
|
||||
if (args.ssh.x11port === undefined)
|
||||
args.ssh.x11port = 6000;
|
||||
if (args.ssh.x11host === undefined)
|
||||
args.ssh.x11host = "localhost";
|
||||
if (args.ssh.remotex11screen === undefined)
|
||||
args.ssh.remotex11screen = 0;
|
||||
this.isSSH = true;
|
||||
this.setSourceFileMap(args.ssh.sourceFileMap, args.ssh.cwd, args.cwd);
|
||||
this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, args.terminal, false).then(() => {
|
||||
if (args.autorun)
|
||||
args.autorun.forEach(command => {
|
||||
this.miDebugger.sendUserInput(command);
|
||||
});
|
||||
this.sendResponse(response);
|
||||
}, err => {
|
||||
this.sendErrorResponse(response, 102, `Failed to SSH: ${err.toString()}`);
|
||||
});
|
||||
} else {
|
||||
this.miDebugger.load(args.cwd, args.target, args.arguments, args.terminal).then(() => {
|
||||
if (args.autorun)
|
||||
args.autorun.forEach(command => {
|
||||
this.miDebugger.sendUserInput(command);
|
||||
});
|
||||
this.sendResponse(response);
|
||||
}, err => {
|
||||
this.sendErrorResponse(response, 103, `Failed to load MI Debugger: ${err.toString()}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void {
|
||||
this.miDebugger = new MI2(args.gdbpath || "gdb", ["-q", "--interpreter=mi2"], args.debugger_args, args.env);
|
||||
this.setPathSubstitutions(args.pathSubstitutions);
|
||||
this.initDebugger();
|
||||
this.quit = false;
|
||||
this.attached = !args.remote;
|
||||
this.initialRunCommand = !!args.stopAtConnect ? RunCommand.NONE : RunCommand.CONTINUE;
|
||||
this.isSSH = false;
|
||||
this.setValuesFormattingMode(args.valuesFormatting);
|
||||
this.miDebugger.printCalls = !!args.printCalls;
|
||||
this.miDebugger.debugOutput = !!args.showDevDebugOutput;
|
||||
this.stopAtEntry = args.stopAtEntry;
|
||||
if (args.ssh !== undefined) {
|
||||
if (args.ssh.forwardX11 === undefined)
|
||||
args.ssh.forwardX11 = true;
|
||||
if (args.ssh.port === undefined)
|
||||
args.ssh.port = 22;
|
||||
if (args.ssh.x11port === undefined)
|
||||
args.ssh.x11port = 6000;
|
||||
if (args.ssh.x11host === undefined)
|
||||
args.ssh.x11host = "localhost";
|
||||
if (args.ssh.remotex11screen === undefined)
|
||||
args.ssh.remotex11screen = 0;
|
||||
this.isSSH = true;
|
||||
this.setSourceFileMap(args.ssh.sourceFileMap, args.ssh.cwd, args.cwd);
|
||||
this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, "", undefined, true).then(() => {
|
||||
if (args.autorun)
|
||||
args.autorun.forEach(command => {
|
||||
this.miDebugger.sendUserInput(command);
|
||||
});
|
||||
this.sendResponse(response);
|
||||
}, err => {
|
||||
this.sendErrorResponse(response, 102, `Failed to SSH: ${err.toString()}`);
|
||||
});
|
||||
} else {
|
||||
if (args.remote) {
|
||||
this.miDebugger.connect(args.cwd, args.executable, args.target).then(() => {
|
||||
if (args.autorun)
|
||||
args.autorun.forEach(command => {
|
||||
this.miDebugger.sendUserInput(command);
|
||||
});
|
||||
this.sendResponse(response);
|
||||
}, err => {
|
||||
this.sendErrorResponse(response, 102, `Failed to attach: ${err.toString()}`);
|
||||
});
|
||||
} else {
|
||||
this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => {
|
||||
if (args.autorun)
|
||||
args.autorun.forEach(command => {
|
||||
this.miDebugger.sendUserInput(command);
|
||||
});
|
||||
this.sendResponse(response);
|
||||
}, err => {
|
||||
this.sendErrorResponse(response, 101, `Failed to attach: ${err.toString()}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add extra commands for source file path substitution in GDB-specific syntax
|
||||
protected setPathSubstitutions(substitutions: { [index: string]: string }): void {
|
||||
if (substitutions) {
|
||||
Object.keys(substitutions).forEach(source => {
|
||||
this.miDebugger.extraCommands.push("gdb-set substitute-path \"" + escape(source) + "\" \"" + escape(substitutions[source]) + "\"");
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugSession.run(GDBDebugSession);
|
|
@ -0,0 +1,126 @@
|
|||
import { MI2DebugSession, RunCommand } from './mibase';
|
||||
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { MI2_LLDB } from "./backend/mi2/mi2lldb";
|
||||
import { SSHArguments, ValuesFormattingMode } from './backend/backend';
|
||||
|
||||
export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
|
||||
cwd: string;
|
||||
target: string;
|
||||
lldbmipath: string;
|
||||
env: any;
|
||||
debugger_args: string[];
|
||||
pathSubstitutions: { [index: string]: string };
|
||||
arguments: string;
|
||||
autorun: string[];
|
||||
stopAtEntry: boolean | string;
|
||||
ssh: SSHArguments;
|
||||
valuesFormatting: ValuesFormattingMode;
|
||||
printCalls: boolean;
|
||||
showDevDebugOutput: boolean;
|
||||
}
|
||||
|
||||
export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments {
|
||||
cwd: string;
|
||||
target: string;
|
||||
lldbmipath: string;
|
||||
env: any;
|
||||
debugger_args: string[];
|
||||
pathSubstitutions: { [index: string]: string };
|
||||
executable: string;
|
||||
autorun: string[];
|
||||
stopAtConnect: boolean;
|
||||
stopAtEntry: boolean | string;
|
||||
valuesFormatting: ValuesFormattingMode;
|
||||
printCalls: boolean;
|
||||
showDevDebugOutput: boolean;
|
||||
}
|
||||
|
||||
class LLDBDebugSession extends MI2DebugSession {
|
||||
protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
|
||||
response.body.supportsGotoTargetsRequest = true;
|
||||
response.body.supportsHitConditionalBreakpoints = true;
|
||||
response.body.supportsConfigurationDoneRequest = true;
|
||||
response.body.supportsConditionalBreakpoints = true;
|
||||
response.body.supportsFunctionBreakpoints = true;
|
||||
response.body.supportsEvaluateForHovers = true;
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
|
||||
this.miDebugger = new MI2_LLDB(args.lldbmipath || "lldb-mi", [], args.debugger_args, args.env);
|
||||
this.setPathSubstitutions(args.pathSubstitutions);
|
||||
this.initDebugger();
|
||||
this.quit = false;
|
||||
this.attached = false;
|
||||
this.initialRunCommand = RunCommand.RUN;
|
||||
this.isSSH = false;
|
||||
this.started = false;
|
||||
this.crashed = false;
|
||||
this.setValuesFormattingMode(args.valuesFormatting);
|
||||
this.miDebugger.printCalls = !!args.printCalls;
|
||||
this.miDebugger.debugOutput = !!args.showDevDebugOutput;
|
||||
this.stopAtEntry = args.stopAtEntry;
|
||||
if (args.ssh !== undefined) {
|
||||
if (args.ssh.forwardX11 === undefined)
|
||||
args.ssh.forwardX11 = true;
|
||||
if (args.ssh.port === undefined)
|
||||
args.ssh.port = 22;
|
||||
if (args.ssh.x11port === undefined)
|
||||
args.ssh.x11port = 6000;
|
||||
if (args.ssh.x11host === undefined)
|
||||
args.ssh.x11host = "localhost";
|
||||
if (args.ssh.remotex11screen === undefined)
|
||||
args.ssh.remotex11screen = 0;
|
||||
this.isSSH = true;
|
||||
this.setSourceFileMap(args.ssh.sourceFileMap, args.ssh.cwd, args.cwd);
|
||||
this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, undefined, false).then(() => {
|
||||
if (args.autorun)
|
||||
args.autorun.forEach(command => {
|
||||
this.miDebugger.sendUserInput(command);
|
||||
});
|
||||
this.sendResponse(response);
|
||||
});
|
||||
} else {
|
||||
this.miDebugger.load(args.cwd, args.target, args.arguments, undefined).then(() => {
|
||||
if (args.autorun)
|
||||
args.autorun.forEach(command => {
|
||||
this.miDebugger.sendUserInput(command);
|
||||
});
|
||||
this.sendResponse(response);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void {
|
||||
this.miDebugger = new MI2_LLDB(args.lldbmipath || "lldb-mi", [], args.debugger_args, args.env);
|
||||
this.setPathSubstitutions(args.pathSubstitutions);
|
||||
this.initDebugger();
|
||||
this.quit = false;
|
||||
this.attached = true;
|
||||
this.initialRunCommand = !!args.stopAtConnect ? RunCommand.NONE : RunCommand.CONTINUE;
|
||||
this.isSSH = false;
|
||||
this.setValuesFormattingMode(args.valuesFormatting);
|
||||
this.miDebugger.printCalls = !!args.printCalls;
|
||||
this.miDebugger.debugOutput = !!args.showDevDebugOutput;
|
||||
this.stopAtEntry = args.stopAtEntry;
|
||||
this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => {
|
||||
if (args.autorun)
|
||||
args.autorun.forEach(command => {
|
||||
this.miDebugger.sendUserInput(command);
|
||||
});
|
||||
this.sendResponse(response);
|
||||
});
|
||||
}
|
||||
|
||||
// Add extra commands for source file path substitution in LLDB-specific syntax
|
||||
protected setPathSubstitutions(substitutions: { [index: string]: string }): void {
|
||||
if (substitutions) {
|
||||
Object.keys(substitutions).forEach(source => {
|
||||
this.miDebugger.extraCommands.push("settings append target.source-map " + source + " " + substitutions[source]);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugSession.run(LLDBDebugSession);
|
|
@ -0,0 +1,93 @@
|
|||
import { MI2DebugSession, RunCommand } from './mibase';
|
||||
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { MI2_Mago } from "./backend/mi2/mi2mago";
|
||||
import { SSHArguments, ValuesFormattingMode } from './backend/backend';
|
||||
|
||||
export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
|
||||
cwd: string;
|
||||
target: string;
|
||||
magomipath: string;
|
||||
env: any;
|
||||
debugger_args: string[];
|
||||
arguments: string;
|
||||
autorun: string[];
|
||||
valuesFormatting: ValuesFormattingMode;
|
||||
printCalls: boolean;
|
||||
showDevDebugOutput: boolean;
|
||||
}
|
||||
|
||||
export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments {
|
||||
cwd: string;
|
||||
target: string;
|
||||
magomipath: string;
|
||||
env: any;
|
||||
debugger_args: string[];
|
||||
executable: string;
|
||||
autorun: string[];
|
||||
stopAtConnect: boolean;
|
||||
valuesFormatting: ValuesFormattingMode;
|
||||
printCalls: boolean;
|
||||
showDevDebugOutput: boolean;
|
||||
}
|
||||
|
||||
class MagoDebugSession extends MI2DebugSession {
|
||||
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
|
||||
super(debuggerLinesStartAt1, isServer);
|
||||
}
|
||||
|
||||
protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
|
||||
response.body.supportsHitConditionalBreakpoints = true;
|
||||
response.body.supportsConfigurationDoneRequest = true;
|
||||
response.body.supportsConditionalBreakpoints = true;
|
||||
response.body.supportsFunctionBreakpoints = true;
|
||||
response.body.supportsEvaluateForHovers = true;
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
getThreadID() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
|
||||
this.miDebugger = new MI2_Mago(args.magomipath || "mago-mi", ["-q"], args.debugger_args, args.env);
|
||||
this.initDebugger();
|
||||
this.quit = false;
|
||||
this.attached = false;
|
||||
this.initialRunCommand = RunCommand.RUN;
|
||||
this.isSSH = false;
|
||||
this.started = false;
|
||||
this.crashed = false;
|
||||
this.setValuesFormattingMode(args.valuesFormatting);
|
||||
this.miDebugger.printCalls = !!args.printCalls;
|
||||
this.miDebugger.debugOutput = !!args.showDevDebugOutput;
|
||||
this.miDebugger.load(args.cwd, args.target, args.arguments, undefined).then(() => {
|
||||
if (args.autorun)
|
||||
args.autorun.forEach(command => {
|
||||
this.miDebugger.sendUserInput(command);
|
||||
});
|
||||
this.sendResponse(response);
|
||||
});
|
||||
}
|
||||
|
||||
protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void {
|
||||
this.miDebugger = new MI2_Mago(args.magomipath || "mago-mi", [], args.debugger_args, args.env);
|
||||
this.initDebugger();
|
||||
this.quit = false;
|
||||
this.attached = true;
|
||||
this.initialRunCommand = !!args.stopAtConnect ? RunCommand.NONE : RunCommand.CONTINUE;
|
||||
this.isSSH = false;
|
||||
this.setValuesFormattingMode(args.valuesFormatting);
|
||||
this.miDebugger.printCalls = !!args.printCalls;
|
||||
this.miDebugger.debugOutput = !!args.showDevDebugOutput;
|
||||
this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => {
|
||||
if (args.autorun)
|
||||
args.autorun.forEach(command => {
|
||||
this.miDebugger.sendUserInput(command);
|
||||
});
|
||||
this.sendResponse(response);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
DebugSession.run(MagoDebugSession);
|
|
@ -0,0 +1,798 @@
|
|||
import * as DebugAdapter from 'vscode-debugadapter';
|
||||
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, ThreadEvent, OutputEvent, ContinuedEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, MIError } from './backend/backend';
|
||||
import { MINode } from './backend/mi_parse';
|
||||
import { expandValue, isExpandable } from './backend/gdb_expansion';
|
||||
import { MI2 } from './backend/mi2/mi2';
|
||||
import { posix } from "path";
|
||||
import * as systemPath from "path";
|
||||
import * as net from "net";
|
||||
import * as os from "os";
|
||||
import * as fs from "fs";
|
||||
|
||||
class ExtendedVariable {
|
||||
constructor(public name, public options) {
|
||||
}
|
||||
}
|
||||
|
||||
export enum RunCommand { CONTINUE, RUN, NONE }
|
||||
|
||||
const STACK_HANDLES_START = 1000;
|
||||
const VAR_HANDLES_START = 512 * 256 + 1000;
|
||||
|
||||
export class MI2DebugSession extends DebugSession {
|
||||
protected variableHandles = new Handles<string | VariableObject | ExtendedVariable>(VAR_HANDLES_START);
|
||||
protected variableHandlesReverse: { [id: string]: number } = {};
|
||||
protected useVarObjects: boolean;
|
||||
protected quit: boolean;
|
||||
protected attached: boolean;
|
||||
protected initialRunCommand: RunCommand;
|
||||
protected stopAtEntry: boolean | string;
|
||||
protected isSSH: boolean;
|
||||
protected sourceFileMap: Map<string,string>;
|
||||
protected started: boolean;
|
||||
protected crashed: boolean;
|
||||
protected miDebugger: MI2;
|
||||
protected commandServer: net.Server;
|
||||
protected serverPath: string;
|
||||
|
||||
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
|
||||
super(debuggerLinesStartAt1, isServer);
|
||||
}
|
||||
|
||||
protected async initDebugger() {
|
||||
this.miDebugger.on("launcherror", this.launchError.bind(this));
|
||||
this.miDebugger.on("quit", this.quitEvent.bind(this));
|
||||
this.miDebugger.on("exited-normally", this.quitEvent.bind(this));
|
||||
this.miDebugger.on("stopped", this.stopEvent.bind(this));
|
||||
this.miDebugger.on("msg", this.handleMsg.bind(this));
|
||||
this.miDebugger.on("breakpoint", this.handleBreakpoint.bind(this));
|
||||
this.miDebugger.on("watchpoint", this.handleBreak.bind(this)); // consider to parse old/new, too (otherwise it is in the console only)
|
||||
this.miDebugger.on("step-end", this.handleBreak.bind(this));
|
||||
// this.miDebugger.on("step-out-end", this.handleBreak.bind(this)); // was combined into step-end
|
||||
this.miDebugger.on("step-other", this.handleBreak.bind(this));
|
||||
this.miDebugger.on("signal-stop", this.handlePause.bind(this));
|
||||
this.miDebugger.on("thread-created", this.threadCreatedEvent.bind(this));
|
||||
this.miDebugger.on("thread-exited", this.threadExitedEvent.bind(this));
|
||||
this.miDebugger.once("debug-ready", (() => this.sendEvent(new InitializedEvent())));
|
||||
try {
|
||||
const socketlists = systemPath.join(os.tmpdir(), "code-debug-sockets");
|
||||
if (fs.existsSync(socketlists)) {
|
||||
await cleanInvalidSocketPath(socketlists);
|
||||
}
|
||||
this.commandServer = net.createServer(c => {
|
||||
c.on("data", data => {
|
||||
const rawCmd = data.toString();
|
||||
const spaceIndex = rawCmd.indexOf(" ");
|
||||
let func = rawCmd;
|
||||
let args = [];
|
||||
if (spaceIndex != -1) {
|
||||
func = rawCmd.substr(0, spaceIndex);
|
||||
args = JSON.parse(rawCmd.substr(spaceIndex + 1));
|
||||
}
|
||||
Promise.resolve(this.miDebugger[func].apply(this.miDebugger, args)).then(data => {
|
||||
c.write(data instanceof Object ? JSON.stringify(data).toString() : data.toString());
|
||||
});
|
||||
});
|
||||
});
|
||||
this.commandServer.on("error", err => {
|
||||
if (process.platform != "win32")
|
||||
this.handleMsg("stderr", "Code-Debug WARNING: Utility Command Server: Error in command socket " + err.toString() + "\nCode-Debug WARNING: The examine memory location command won't work");
|
||||
});
|
||||
if (!fs.existsSync(systemPath.join(os.tmpdir(), "code-debug-sockets")))
|
||||
fs.mkdirSync(systemPath.join(os.tmpdir(), "code-debug-sockets"));
|
||||
this.commandServer.listen(this.serverPath = systemPath.join(os.tmpdir(), "code-debug-sockets", ("Debug-Instance-" + Math.floor(Math.random() * 36 * 36 * 36 * 36).toString(36)).toLowerCase()));
|
||||
} catch (e) {
|
||||
if (process.platform != "win32")
|
||||
this.handleMsg("stderr", "Code-Debug WARNING: Utility Command Server: Failed to start " + e.toString() + "\nCode-Debug WARNING: The examine memory location command won't work");
|
||||
}
|
||||
}
|
||||
|
||||
protected setValuesFormattingMode(mode: ValuesFormattingMode) {
|
||||
switch (mode) {
|
||||
case "disabled":
|
||||
this.useVarObjects = true;
|
||||
this.miDebugger.prettyPrint = false;
|
||||
break;
|
||||
case "prettyPrinters":
|
||||
this.useVarObjects = true;
|
||||
this.miDebugger.prettyPrint = true;
|
||||
break;
|
||||
case "parseText":
|
||||
default:
|
||||
this.useVarObjects = false;
|
||||
this.miDebugger.prettyPrint = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected handleMsg(type: string, msg: string) {
|
||||
if (type == "target")
|
||||
type = "stdout";
|
||||
if (type == "log")
|
||||
type = "stderr";
|
||||
this.sendEvent(new OutputEvent(msg, type));
|
||||
}
|
||||
|
||||
protected handleBreakpoint(info: MINode) {
|
||||
const event = new StoppedEvent("breakpoint", parseInt(info.record("thread-id")));
|
||||
(event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all";
|
||||
this.sendEvent(event);
|
||||
}
|
||||
|
||||
protected handleBreak(info?: MINode) {
|
||||
const event = new StoppedEvent("step", info ? parseInt(info.record("thread-id")) : 1);
|
||||
(event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info ? info.record("stopped-threads") == "all" : true;
|
||||
this.sendEvent(event);
|
||||
}
|
||||
|
||||
protected handlePause(info: MINode) {
|
||||
const event = new StoppedEvent("user request", parseInt(info.record("thread-id")));
|
||||
(event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all";
|
||||
this.sendEvent(event);
|
||||
}
|
||||
|
||||
protected stopEvent(info: MINode) {
|
||||
if (!this.started)
|
||||
this.crashed = true;
|
||||
if (!this.quit) {
|
||||
const event = new StoppedEvent("exception", parseInt(info.record("thread-id")));
|
||||
(event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all";
|
||||
this.sendEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
protected threadCreatedEvent(info: MINode) {
|
||||
this.sendEvent(new ThreadEvent("started", info.record("id")));
|
||||
}
|
||||
|
||||
protected threadExitedEvent(info: MINode) {
|
||||
this.sendEvent(new ThreadEvent("exited", info.record("id")));
|
||||
}
|
||||
|
||||
protected quitEvent() {
|
||||
this.quit = true;
|
||||
this.sendEvent(new TerminatedEvent());
|
||||
|
||||
if (this.serverPath)
|
||||
fs.unlink(this.serverPath, (err) => {
|
||||
console.error("Failed to unlink debug server");
|
||||
});
|
||||
}
|
||||
|
||||
protected launchError(err: any) {
|
||||
this.handleMsg("stderr", "Could not start debugger process, does the program exist in filesystem?\n");
|
||||
this.handleMsg("stderr", err.toString() + "\n");
|
||||
this.quitEvent();
|
||||
}
|
||||
|
||||
protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void {
|
||||
if (this.attached)
|
||||
this.miDebugger.detach();
|
||||
else
|
||||
this.miDebugger.stop();
|
||||
this.commandServer.close();
|
||||
this.commandServer = undefined;
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise<void> {
|
||||
try {
|
||||
if (this.useVarObjects) {
|
||||
let name = args.name;
|
||||
if (args.variablesReference >= VAR_HANDLES_START) {
|
||||
const parent = this.variableHandles.get(args.variablesReference) as VariableObject;
|
||||
name = `${parent.name}.${name}`;
|
||||
}
|
||||
|
||||
const res = await this.miDebugger.varAssign(name, args.value);
|
||||
response.body = {
|
||||
value: res.result("value")
|
||||
};
|
||||
} else {
|
||||
await this.miDebugger.changeVariable(args.name, args.value);
|
||||
response.body = {
|
||||
value: args.value
|
||||
};
|
||||
}
|
||||
this.sendResponse(response);
|
||||
} catch (err) {
|
||||
this.sendErrorResponse(response, 11, `Could not continue: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
protected setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void {
|
||||
const all = [];
|
||||
args.breakpoints.forEach(brk => {
|
||||
all.push(this.miDebugger.addBreakPoint({ raw: brk.name, condition: brk.condition, countCondition: brk.hitCondition }));
|
||||
});
|
||||
Promise.all(all).then(brkpoints => {
|
||||
const finalBrks = [];
|
||||
brkpoints.forEach(brkp => {
|
||||
if (brkp[0])
|
||||
finalBrks.push({ line: brkp[1].line });
|
||||
});
|
||||
response.body = {
|
||||
breakpoints: finalBrks
|
||||
};
|
||||
this.sendResponse(response);
|
||||
}, msg => {
|
||||
this.sendErrorResponse(response, 10, msg.toString());
|
||||
});
|
||||
}
|
||||
|
||||
protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void {
|
||||
this.miDebugger.clearBreakPoints(args.source.path).then(() => {
|
||||
let path = args.source.path;
|
||||
if (this.isSSH) {
|
||||
// convert local path to ssh path
|
||||
if (path.indexOf("\\") != -1)
|
||||
path = path.replace(/\\/g, "/").toLowerCase();
|
||||
// ideCWD is the local path, gdbCWD is the ssh path, both had the replacing \ -> / done up-front
|
||||
for (let [ideCWD, gdbCWD] of this.sourceFileMap) {
|
||||
if (path.startsWith(ideCWD)) {
|
||||
path = posix.relative(ideCWD, path);
|
||||
path = posix.join(gdbCWD, path); // we combined a guaranteed path with relative one (works with GDB both on GNU/Linux and Win32)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const all = args.breakpoints.map(brk => {
|
||||
return this.miDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition, countCondition: brk.hitCondition });
|
||||
});
|
||||
Promise.all(all).then(brkpoints => {
|
||||
const finalBrks = [];
|
||||
brkpoints.forEach(brkp => {
|
||||
// TODO: Currently all breakpoints returned are marked as verified,
|
||||
// which leads to verified breakpoints on a broken lldb.
|
||||
if (brkp[0])
|
||||
finalBrks.push(new DebugAdapter.Breakpoint(true, brkp[1].line));
|
||||
});
|
||||
response.body = {
|
||||
breakpoints: finalBrks
|
||||
};
|
||||
this.sendResponse(response);
|
||||
}, msg => {
|
||||
this.sendErrorResponse(response, 9, msg.toString());
|
||||
});
|
||||
}, msg => {
|
||||
this.sendErrorResponse(response, 9, msg.toString());
|
||||
});
|
||||
}
|
||||
|
||||
protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
|
||||
if (!this.miDebugger) {
|
||||
this.sendResponse(response);
|
||||
return;
|
||||
}
|
||||
this.miDebugger.getThreads().then(threads => {
|
||||
response.body = {
|
||||
threads: []
|
||||
};
|
||||
for (const thread of threads) {
|
||||
let threadName = thread.name || thread.targetId || "<unnamed>";
|
||||
response.body.threads.push(new Thread(thread.id, thread.id + ":" + threadName));
|
||||
}
|
||||
this.sendResponse(response);
|
||||
}).catch(error => {
|
||||
this.sendErrorResponse(response, 17, `Could not get threads: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Supports 65535 threads.
|
||||
protected threadAndLevelToFrameId(threadId: number, level: number) {
|
||||
return level << 16 | threadId;
|
||||
}
|
||||
protected frameIdToThreadAndLevel(frameId: number) {
|
||||
return [frameId & 0xffff, frameId >> 16];
|
||||
}
|
||||
|
||||
protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {
|
||||
this.miDebugger.getStack(args.startFrame, args.levels, args.threadId).then(stack => {
|
||||
const ret: StackFrame[] = [];
|
||||
stack.forEach(element => {
|
||||
let source = undefined;
|
||||
let path = element.file;
|
||||
if (path) {
|
||||
if (this.isSSH) {
|
||||
// convert ssh path to local path
|
||||
if (path.indexOf("\\") != -1)
|
||||
path = path.replace(/\\/g, "/").toLowerCase();
|
||||
// ideCWD is the local path, gdbCWD is the ssh path, both had the replacing \ -> / done up-front
|
||||
for (let [ideCWD, gdbCWD] of this.sourceFileMap) {
|
||||
if (path.startsWith(gdbCWD)) {
|
||||
path = posix.relative(gdbCWD, path); // only operates on "/" paths
|
||||
path = systemPath.resolve(ideCWD, path); // will do the conversion to "\" on Win32
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (process.platform === "win32") {
|
||||
if (path.startsWith("\\cygdrive\\") || path.startsWith("/cygdrive/")) {
|
||||
path = path[10] + ":" + path.substr(11); // replaces /cygdrive/c/foo/bar.txt with c:/foo/bar.txt
|
||||
}
|
||||
}
|
||||
source = new Source(element.fileName, path);
|
||||
}
|
||||
|
||||
ret.push(new StackFrame(
|
||||
this.threadAndLevelToFrameId(args.threadId, element.level),
|
||||
element.function + "@" + element.address,
|
||||
source,
|
||||
element.line,
|
||||
0));
|
||||
});
|
||||
response.body = {
|
||||
stackFrames: ret
|
||||
};
|
||||
this.sendResponse(response);
|
||||
}, err => {
|
||||
this.sendErrorResponse(response, 12, `Failed to get Stack Trace: ${err.toString()}`);
|
||||
});
|
||||
}
|
||||
|
||||
protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void {
|
||||
const promises: Thenable<any>[] = [];
|
||||
let entryPoint: string | undefined = undefined;
|
||||
let runToStart: boolean = false;
|
||||
// Setup temporary breakpoint for the entry point if needed.
|
||||
switch (this.initialRunCommand) {
|
||||
case RunCommand.CONTINUE:
|
||||
case RunCommand.NONE:
|
||||
if (typeof this.stopAtEntry == 'boolean' && this.stopAtEntry)
|
||||
entryPoint = "main"; // sensible default
|
||||
else if (typeof this.stopAtEntry == 'string')
|
||||
entryPoint = this.stopAtEntry;
|
||||
break;
|
||||
case RunCommand.RUN:
|
||||
if (typeof this.stopAtEntry == 'boolean' && this.stopAtEntry) {
|
||||
if (this.miDebugger.features.includes("exec-run-start-option"))
|
||||
runToStart = true;
|
||||
else
|
||||
entryPoint = "main"; // sensible fallback
|
||||
} else if (typeof this.stopAtEntry == 'string')
|
||||
entryPoint = this.stopAtEntry;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unhandled run command: ' + RunCommand[this.initialRunCommand]);
|
||||
}
|
||||
if (entryPoint)
|
||||
promises.push(this.miDebugger.setEntryBreakPoint(entryPoint));
|
||||
switch (this.initialRunCommand) {
|
||||
case RunCommand.CONTINUE:
|
||||
promises.push(this.miDebugger.continue().then(() => {
|
||||
// Some debuggers will provide an out-of-band status that they are stopped
|
||||
// when attaching (e.g., gdb), so the client assumes we are stopped and gets
|
||||
// confused if we start running again on our own.
|
||||
//
|
||||
// If we don't send this event, the client may start requesting data (such as
|
||||
// stack frames, local variables, etc.) since they believe the target is
|
||||
// stopped. Furthermore the client may not be indicating the proper status
|
||||
// to the user (may indicate stopped when the target is actually running).
|
||||
this.sendEvent(new ContinuedEvent(1, true));
|
||||
}));
|
||||
break;
|
||||
case RunCommand.RUN:
|
||||
promises.push(this.miDebugger.start(runToStart).then(() => {
|
||||
this.started = true;
|
||||
if (this.crashed)
|
||||
this.handlePause(undefined);
|
||||
}));
|
||||
break;
|
||||
case RunCommand.NONE:
|
||||
// Not all debuggers seem to provide an out-of-band status that they are stopped
|
||||
// when attaching (e.g., lldb), so the client assumes we are running and gets
|
||||
// confused when we don't actually run or continue. Therefore, we'll force a
|
||||
// stopped event to be sent to the client (just in case) to synchronize the state.
|
||||
const event: DebugProtocol.StoppedEvent = new StoppedEvent("pause", 1);
|
||||
event.body.description = "paused on attach";
|
||||
event.body.allThreadsStopped = true;
|
||||
this.sendEvent(event);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unhandled run command: ' + RunCommand[this.initialRunCommand]);
|
||||
}
|
||||
Promise.all(promises).then(() => {
|
||||
this.sendResponse(response);
|
||||
}).catch(err => {
|
||||
this.sendErrorResponse(response, 18, `Could not run/continue: ${err.toString()}`);
|
||||
});
|
||||
}
|
||||
|
||||
protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
|
||||
const scopes = new Array<Scope>();
|
||||
scopes.push(new Scope("Local", STACK_HANDLES_START + (parseInt(args.frameId as any) || 0), false));
|
||||
|
||||
response.body = {
|
||||
scopes: scopes
|
||||
};
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise<void> {
|
||||
const variables: DebugProtocol.Variable[] = [];
|
||||
let id: number | string | VariableObject | ExtendedVariable;
|
||||
if (args.variablesReference < VAR_HANDLES_START) {
|
||||
id = args.variablesReference - STACK_HANDLES_START;
|
||||
} else {
|
||||
id = this.variableHandles.get(args.variablesReference);
|
||||
}
|
||||
|
||||
const createVariable = (arg, options?) => {
|
||||
if (options)
|
||||
return this.variableHandles.create(new ExtendedVariable(arg, options));
|
||||
else
|
||||
return this.variableHandles.create(arg);
|
||||
};
|
||||
|
||||
const findOrCreateVariable = (varObj: VariableObject): number => {
|
||||
let id: number;
|
||||
if (this.variableHandlesReverse.hasOwnProperty(varObj.name)) {
|
||||
id = this.variableHandlesReverse[varObj.name];
|
||||
} else {
|
||||
id = createVariable(varObj);
|
||||
this.variableHandlesReverse[varObj.name] = id;
|
||||
}
|
||||
return varObj.isCompound() ? id : 0;
|
||||
};
|
||||
|
||||
if (typeof id == "number") {
|
||||
let stack: Variable[];
|
||||
try {
|
||||
const [threadId, level] = this.frameIdToThreadAndLevel(id);
|
||||
stack = await this.miDebugger.getStackVariables(threadId, level);
|
||||
for (const variable of stack) {
|
||||
if (this.useVarObjects) {
|
||||
try {
|
||||
const varObjName = `var_${id}_${variable.name}`;
|
||||
let varObj: VariableObject;
|
||||
try {
|
||||
const changes = await this.miDebugger.varUpdate(varObjName);
|
||||
const changelist = changes.result("changelist");
|
||||
changelist.forEach((change) => {
|
||||
const name = MINode.valueOf(change, "name");
|
||||
const vId = this.variableHandlesReverse[name];
|
||||
const v = this.variableHandles.get(vId) as any;
|
||||
v.applyChanges(change);
|
||||
});
|
||||
const varId = this.variableHandlesReverse[varObjName];
|
||||
varObj = this.variableHandles.get(varId) as any;
|
||||
} catch (err) {
|
||||
if (err instanceof MIError && err.message == "Variable object not found") {
|
||||
varObj = await this.miDebugger.varCreate(variable.name, varObjName);
|
||||
const varId = findOrCreateVariable(varObj);
|
||||
varObj.exp = variable.name;
|
||||
varObj.id = varId;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
variables.push(varObj.toProtocolVariable());
|
||||
} catch (err) {
|
||||
variables.push({
|
||||
name: variable.name,
|
||||
value: `<${err}>`,
|
||||
variablesReference: 0
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (variable.valueStr !== undefined) {
|
||||
let expanded = expandValue(createVariable, `{${variable.name}=${variable.valueStr})`, "", variable.raw);
|
||||
if (expanded) {
|
||||
if (typeof expanded[0] == "string")
|
||||
expanded = [
|
||||
{
|
||||
name: "<value>",
|
||||
value: prettyStringArray(expanded),
|
||||
variablesReference: 0
|
||||
}
|
||||
];
|
||||
variables.push(expanded[0]);
|
||||
}
|
||||
} else
|
||||
variables.push({
|
||||
name: variable.name,
|
||||
type: variable.type,
|
||||
value: "<unknown>",
|
||||
variablesReference: createVariable(variable.name)
|
||||
});
|
||||
}
|
||||
}
|
||||
response.body = {
|
||||
variables: variables
|
||||
};
|
||||
this.sendResponse(response);
|
||||
} catch (err) {
|
||||
this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`);
|
||||
}
|
||||
} else if (typeof id == "string") {
|
||||
// Variable members
|
||||
let variable;
|
||||
try {
|
||||
// TODO: this evals on an (effectively) unknown thread for multithreaded programs.
|
||||
variable = await this.miDebugger.evalExpression(JSON.stringify(id), 0, 0);
|
||||
try {
|
||||
let expanded = expandValue(createVariable, variable.result("value"), id, variable);
|
||||
if (!expanded) {
|
||||
this.sendErrorResponse(response, 2, `Could not expand variable`);
|
||||
} else {
|
||||
if (typeof expanded[0] == "string")
|
||||
expanded = [
|
||||
{
|
||||
name: "<value>",
|
||||
value: prettyStringArray(expanded),
|
||||
variablesReference: 0
|
||||
}
|
||||
];
|
||||
response.body = {
|
||||
variables: expanded
|
||||
};
|
||||
this.sendResponse(response);
|
||||
}
|
||||
} catch (e) {
|
||||
this.sendErrorResponse(response, 2, `Could not expand variable: ${e}`);
|
||||
}
|
||||
} catch (err) {
|
||||
this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`);
|
||||
}
|
||||
} else if (typeof id == "object") {
|
||||
if (id instanceof VariableObject) {
|
||||
// Variable members
|
||||
let children: VariableObject[];
|
||||
try {
|
||||
children = await this.miDebugger.varListChildren(id.name);
|
||||
const vars = children.map(child => {
|
||||
const varId = findOrCreateVariable(child);
|
||||
child.id = varId;
|
||||
return child.toProtocolVariable();
|
||||
});
|
||||
|
||||
response.body = {
|
||||
variables: vars
|
||||
};
|
||||
this.sendResponse(response);
|
||||
} catch (err) {
|
||||
this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`);
|
||||
}
|
||||
} else if (id instanceof ExtendedVariable) {
|
||||
const varReq = id;
|
||||
if (varReq.options.arg) {
|
||||
const strArr = [];
|
||||
let argsPart = true;
|
||||
let arrIndex = 0;
|
||||
const submit = () => {
|
||||
response.body = {
|
||||
variables: strArr
|
||||
};
|
||||
this.sendResponse(response);
|
||||
};
|
||||
const addOne = async () => {
|
||||
// TODO: this evals on an (effectively) unknown thread for multithreaded programs.
|
||||
const variable = await this.miDebugger.evalExpression(JSON.stringify(`${varReq.name}+${arrIndex})`), 0, 0);
|
||||
try {
|
||||
const expanded = expandValue(createVariable, variable.result("value"), varReq.name, variable);
|
||||
if (!expanded) {
|
||||
this.sendErrorResponse(response, 15, `Could not expand variable`);
|
||||
} else {
|
||||
if (typeof expanded == "string") {
|
||||
if (expanded == "<nullptr>") {
|
||||
if (argsPart)
|
||||
argsPart = false;
|
||||
else
|
||||
return submit();
|
||||
} else if (expanded[0] != '"') {
|
||||
strArr.push({
|
||||
name: "[err]",
|
||||
value: expanded,
|
||||
variablesReference: 0
|
||||
});
|
||||
return submit();
|
||||
}
|
||||
strArr.push({
|
||||
name: `[${(arrIndex++)}]`,
|
||||
value: expanded,
|
||||
variablesReference: 0
|
||||
});
|
||||
addOne();
|
||||
} else {
|
||||
strArr.push({
|
||||
name: "[err]",
|
||||
value: expanded,
|
||||
variablesReference: 0
|
||||
});
|
||||
submit();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.sendErrorResponse(response, 14, `Could not expand variable: ${e}`);
|
||||
}
|
||||
};
|
||||
addOne();
|
||||
} else
|
||||
this.sendErrorResponse(response, 13, `Unimplemented variable request options: ${JSON.stringify(varReq.options)}`);
|
||||
} else {
|
||||
response.body = {
|
||||
variables: id
|
||||
};
|
||||
this.sendResponse(response);
|
||||
}
|
||||
} else {
|
||||
response.body = {
|
||||
variables: variables
|
||||
};
|
||||
this.sendResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
protected pauseRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
|
||||
this.miDebugger.interrupt().then(done => {
|
||||
this.sendResponse(response);
|
||||
}, msg => {
|
||||
this.sendErrorResponse(response, 3, `Could not pause: ${msg}`);
|
||||
});
|
||||
}
|
||||
|
||||
protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void {
|
||||
this.miDebugger.continue(true).then(done => {
|
||||
this.sendResponse(response);
|
||||
}, msg => {
|
||||
this.sendErrorResponse(response, 2, `Could not continue: ${msg}`);
|
||||
});
|
||||
}
|
||||
|
||||
protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
|
||||
this.miDebugger.continue().then(done => {
|
||||
this.sendResponse(response);
|
||||
}, msg => {
|
||||
this.sendErrorResponse(response, 2, `Could not continue: ${msg}`);
|
||||
});
|
||||
}
|
||||
|
||||
protected stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void {
|
||||
this.miDebugger.step(true).then(done => {
|
||||
this.sendResponse(response);
|
||||
}, msg => {
|
||||
this.sendErrorResponse(response, 4, `Could not step back: ${msg} - Try running 'target record-full' before stepping back`);
|
||||
});
|
||||
}
|
||||
|
||||
protected stepInRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
|
||||
this.miDebugger.step().then(done => {
|
||||
this.sendResponse(response);
|
||||
}, msg => {
|
||||
this.sendErrorResponse(response, 4, `Could not step in: ${msg}`);
|
||||
});
|
||||
}
|
||||
|
||||
protected stepOutRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
|
||||
this.miDebugger.stepOut().then(done => {
|
||||
this.sendResponse(response);
|
||||
}, msg => {
|
||||
this.sendErrorResponse(response, 5, `Could not step out: ${msg}`);
|
||||
});
|
||||
}
|
||||
|
||||
protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
|
||||
this.miDebugger.next().then(done => {
|
||||
this.sendResponse(response);
|
||||
}, msg => {
|
||||
this.sendErrorResponse(response, 6, `Could not step over: ${msg}`);
|
||||
});
|
||||
}
|
||||
|
||||
protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void {
|
||||
const [threadId, level] = this.frameIdToThreadAndLevel(args.frameId);
|
||||
if (args.context == "watch" || args.context == "hover") {
|
||||
this.miDebugger.evalExpression(args.expression, threadId, level).then((res) => {
|
||||
response.body = {
|
||||
variablesReference: 0,
|
||||
result: res.result("value")
|
||||
};
|
||||
this.sendResponse(response);
|
||||
}, msg => {
|
||||
this.sendErrorResponse(response, 7, msg.toString());
|
||||
});
|
||||
} else {
|
||||
this.miDebugger.sendUserInput(args.expression, threadId, level).then(output => {
|
||||
if (typeof output == "undefined")
|
||||
response.body = {
|
||||
result: "",
|
||||
variablesReference: 0
|
||||
};
|
||||
else
|
||||
response.body = {
|
||||
result: JSON.stringify(output),
|
||||
variablesReference: 0
|
||||
};
|
||||
this.sendResponse(response);
|
||||
}, msg => {
|
||||
this.sendErrorResponse(response, 8, msg.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): void {
|
||||
this.miDebugger.goto(args.source.path, args.line).then(done => {
|
||||
response.body = {
|
||||
targets: [{
|
||||
id: 1,
|
||||
label: args.source.name,
|
||||
column: args.column,
|
||||
line : args.line
|
||||
}]
|
||||
};
|
||||
this.sendResponse(response);
|
||||
}, msg => {
|
||||
this.sendErrorResponse(response, 16, `Could not jump: ${msg}`);
|
||||
});
|
||||
}
|
||||
|
||||
protected gotoRequest(response: DebugProtocol.GotoResponse, args: DebugProtocol.GotoArguments): void {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
private addSourceFileMapEntry(gdbCWD :string, ideCWD : string): void {
|
||||
// if it looks like a Win32 path convert to "/"-style for comparisions and to all-lower-case
|
||||
if (ideCWD.indexOf("\\") != -1)
|
||||
ideCWD = ideCWD.replace(/\\/g, "/").toLowerCase();
|
||||
if (!ideCWD.endsWith("/"))
|
||||
ideCWD = ideCWD + "/"
|
||||
// ensure that we only replace complete paths
|
||||
if (gdbCWD.indexOf("\\") != -1)
|
||||
gdbCWD = gdbCWD.replace(/\\/g, "/").toLowerCase();
|
||||
if (!gdbCWD.endsWith("/"))
|
||||
gdbCWD = gdbCWD + "/"
|
||||
this.sourceFileMap.set(ideCWD, gdbCWD);
|
||||
}
|
||||
|
||||
protected setSourceFileMap(configMap: { [index: string]: string }, fallbackGDB :string, fallbackIDE : string): void {
|
||||
this.sourceFileMap = new Map<string, string>();
|
||||
if (configMap === undefined) {
|
||||
this.addSourceFileMapEntry(fallbackGDB, fallbackIDE);
|
||||
} else {
|
||||
for (let [gdbPath, localPath] of Object.entries(configMap)) {
|
||||
this.addSourceFileMapEntry(gdbPath, localPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function prettyStringArray(strings) {
|
||||
if (typeof strings == "object") {
|
||||
if (strings.length !== undefined)
|
||||
return strings.join(", ");
|
||||
else
|
||||
return JSON.stringify(strings);
|
||||
} else return strings;
|
||||
}
|
||||
|
||||
async function cleanInvalidSocketPath(socketlists:string){
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readdir(socketlists, (err, files) => {
|
||||
if (!err) {
|
||||
if (files.length == 0) resolve('');
|
||||
files.forEach((file)=>{
|
||||
try {
|
||||
const conn = net.connect(systemPath.join(socketlists, file));
|
||||
conn.setTimeout(200);
|
||||
conn.on('error',()=>{
|
||||
fs.unlink(systemPath.join(socketlists, file), (err) => {
|
||||
if (err)
|
||||
console.error("Failed to unlink invalid debug server");
|
||||
resolve('');
|
||||
});
|
||||
});
|
||||
}
|
||||
catch{
|
||||
fs.unlink(systemPath.join(socketlists, file), (err) => {
|
||||
if (err)
|
||||
console.error("Failed to unlink invalid debug server");
|
||||
resolve('');
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
resolve('');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import * as path from 'path';
|
||||
|
||||
import { runTests } from 'vscode-test';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// The folder containing the Extension Manifest package.json
|
||||
// Passed to `--extensionDevelopmentPath`
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
|
||||
|
||||
// The path to the extension test runner script
|
||||
// Passed to --extensionTestsPath
|
||||
const extensionTestsPath = path.resolve(__dirname, './suite/index');
|
||||
|
||||
// Download VS Code, unzip it and run the integration test
|
||||
await runTests({ extensionDevelopmentPath, extensionTestsPath });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
console.error('Failed to run tests');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,306 @@
|
|||
import * as assert from 'assert';
|
||||
import { expandValue, isExpandable } from '../../backend/gdb_expansion';
|
||||
|
||||
suite("GDB Value Expansion", () => {
|
||||
const variableCreate = (variable) => { return { expanded: variable }; };
|
||||
test("Various values", () => {
|
||||
assert.strictEqual(isExpandable(`false`), 0);
|
||||
assert.equal(expandValue(variableCreate, `false`), "false");
|
||||
assert.strictEqual(isExpandable(`5`), 0);
|
||||
assert.equal(expandValue(variableCreate, `5`), "5");
|
||||
assert.strictEqual(isExpandable(`"hello world!"`), 0);
|
||||
assert.equal(expandValue(variableCreate, `"hello world!"`), `"hello world!"`);
|
||||
assert.strictEqual(isExpandable(`0x7fffffffe956 "foobar"`), 0);
|
||||
assert.equal(expandValue(variableCreate, `0x7fffffffe956 "foobar"`), `"foobar"`);
|
||||
assert.strictEqual(isExpandable(`0x0`), 0);
|
||||
assert.equal(expandValue(variableCreate, `0x0`), "<nullptr>");
|
||||
assert.strictEqual(isExpandable(`0x000000`), 0);
|
||||
assert.equal(expandValue(variableCreate, `0x000000`), "<nullptr>");
|
||||
assert.strictEqual(isExpandable(`{...}`), 2);
|
||||
assert.equal(expandValue(variableCreate, `{...}`), "<...>");
|
||||
assert.strictEqual(isExpandable(`0x00abc`), 2);
|
||||
assert.equal(expandValue(variableCreate, `0x007ffff7ecb480`), "*0x007ffff7ecb480");
|
||||
assert.strictEqual(isExpandable(`{a = b, c = d}`), 1);
|
||||
assert.deepEqual(expandValue(variableCreate, `{a = b, c = d}`), [
|
||||
{
|
||||
name: "a",
|
||||
value: "b",
|
||||
variablesReference: 0
|
||||
}, {
|
||||
name: "c",
|
||||
value: "d",
|
||||
variablesReference: 0
|
||||
}]);
|
||||
assert.strictEqual(isExpandable(`{[0] = 0x400730 "foo", [1] = 0x400735 "bar"}`), 1);
|
||||
assert.deepEqual(expandValue(variableCreate, `{[0] = 0x400730 "foo", [1] = 0x400735 "bar"}`), [
|
||||
{
|
||||
name: "[0]",
|
||||
value: "\"foo\"",
|
||||
variablesReference: 0
|
||||
}, {
|
||||
name: "[1]",
|
||||
value: "\"bar\"",
|
||||
variablesReference: 0
|
||||
}]);
|
||||
assert.strictEqual(isExpandable(`{{a = b}}`), 1);
|
||||
assert.deepEqual(expandValue(variableCreate, `{{a = b}}`), [
|
||||
{
|
||||
name: "[0]",
|
||||
value: "Object",
|
||||
variablesReference: {
|
||||
expanded: [
|
||||
{
|
||||
name: "a",
|
||||
value: "b",
|
||||
variablesReference: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]);
|
||||
assert.deepEqual(expandValue(variableCreate, `{1, 2, 3, 4}`), [
|
||||
{
|
||||
name: "[0]",
|
||||
value: "1",
|
||||
variablesReference: 0
|
||||
}, {
|
||||
name: "[1]",
|
||||
value: "2",
|
||||
variablesReference: 0
|
||||
}, {
|
||||
name: "[2]",
|
||||
value: "3",
|
||||
variablesReference: 0
|
||||
}, {
|
||||
name: "[3]",
|
||||
value: "4",
|
||||
variablesReference: 0
|
||||
}]);
|
||||
});
|
||||
test("Error values", () => {
|
||||
assert.strictEqual(isExpandable(`<No data fields>`), 0);
|
||||
assert.equal(expandValue(variableCreate, `<No data fields>`), "<No data fields>");
|
||||
});
|
||||
test("Nested values", () => {
|
||||
assert.strictEqual(isExpandable(`{a = {b = e}, c = d}`), 1);
|
||||
assert.deepEqual(expandValue(variableCreate, `{a = {b = e}, c = d}`), [
|
||||
{
|
||||
name: "a",
|
||||
value: "Object",
|
||||
variablesReference: {
|
||||
expanded: [
|
||||
{
|
||||
name: "b",
|
||||
value: "e",
|
||||
variablesReference: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}, {
|
||||
name: "c",
|
||||
value: "d",
|
||||
variablesReference: 0
|
||||
}]);
|
||||
});
|
||||
test("Simple node", () => {
|
||||
assert.strictEqual(isExpandable(`{a = false, b = 5, c = 0x0, d = "foobar"}`), 1);
|
||||
const variables = expandValue(variableCreate, `{a = false, b = 5, c = 0x0, d = "foobar"}`);
|
||||
assert.equal(variables.length, 4);
|
||||
assert.equal(variables[0].name, "a");
|
||||
assert.equal(variables[0].value, "false");
|
||||
assert.equal(variables[1].name, "b");
|
||||
assert.equal(variables[1].value, "5");
|
||||
assert.equal(variables[2].name, "c");
|
||||
assert.equal(variables[2].value, "<nullptr>");
|
||||
assert.equal(variables[3].name, "d");
|
||||
assert.equal(variables[3].value, `"foobar"`);
|
||||
});
|
||||
test("Complex node", () => {
|
||||
const node = `{quit = false, _views = {{view = 0x7ffff7ece1e8, renderer = 0x7ffff7eccc50, world = 0x7ffff7ece480}}, deltaTimer = {_flagStarted = false, _timeStart = {length = 0}, _timeMeasured = {length = 0}}, _start = {callbacks = 0x0}, _stop = {callbacks = 0x0}}`;
|
||||
assert.strictEqual(isExpandable(node), 1);
|
||||
const variables = expandValue(variableCreate, node);
|
||||
assert.deepEqual(variables, [
|
||||
{
|
||||
name: "quit",
|
||||
value: "false",
|
||||
variablesReference: 0
|
||||
},
|
||||
{
|
||||
name: "_views",
|
||||
value: "Object",
|
||||
variablesReference: {
|
||||
expanded: [
|
||||
{
|
||||
name: "[0]",
|
||||
value: "Object",
|
||||
variablesReference: {
|
||||
expanded: [
|
||||
{
|
||||
name: "view",
|
||||
value: "Object@*0x7ffff7ece1e8",
|
||||
variablesReference: { expanded: "*_views[0].view" }
|
||||
},
|
||||
{
|
||||
name: "renderer",
|
||||
value: "Object@*0x7ffff7eccc50",
|
||||
variablesReference: { expanded: "*_views[0].renderer" }
|
||||
},
|
||||
{
|
||||
name: "world",
|
||||
value: "Object@*0x7ffff7ece480",
|
||||
variablesReference: { expanded: "*_views[0].world" }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "deltaTimer",
|
||||
value: "Object",
|
||||
variablesReference: {
|
||||
expanded: [
|
||||
{
|
||||
name: "_flagStarted",
|
||||
value: "false",
|
||||
variablesReference: 0
|
||||
},
|
||||
{
|
||||
name: "_timeStart",
|
||||
value: "Object",
|
||||
variablesReference: {
|
||||
expanded: [
|
||||
{
|
||||
name: "length",
|
||||
value: "0",
|
||||
variablesReference: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "_timeMeasured",
|
||||
value: "Object",
|
||||
variablesReference: {
|
||||
expanded: [
|
||||
{
|
||||
name: "length",
|
||||
value: "0",
|
||||
variablesReference: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "_start",
|
||||
value: "Object",
|
||||
variablesReference: {
|
||||
expanded: [
|
||||
{
|
||||
name: "callbacks",
|
||||
value: "<nullptr>",
|
||||
variablesReference: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "_stop",
|
||||
value: "Object",
|
||||
variablesReference: {
|
||||
expanded: [
|
||||
{
|
||||
name: "callbacks",
|
||||
value: "<nullptr>",
|
||||
variablesReference: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
test("Simple node with errors", () => {
|
||||
const node = `{_enableMipMaps = false, _minFilter = <incomplete type>, _magFilter = <incomplete type>, _wrapX = <incomplete type>, _wrapY = <incomplete type>, _inMode = 6408, _mode = 6408, _id = 1, _width = 1024, _height = 1024}`;
|
||||
assert.strictEqual(isExpandable(node), 1);
|
||||
const variables = expandValue(variableCreate, node);
|
||||
assert.deepEqual(variables, [
|
||||
{
|
||||
name: "_enableMipMaps",
|
||||
value: "false",
|
||||
variablesReference: 0
|
||||
},
|
||||
{
|
||||
name: "_minFilter",
|
||||
value: "<incomplete type>",
|
||||
variablesReference: 0
|
||||
},
|
||||
{
|
||||
name: "_magFilter",
|
||||
value: "<incomplete type>",
|
||||
variablesReference: 0
|
||||
},
|
||||
{
|
||||
name: "_wrapX",
|
||||
value: "<incomplete type>",
|
||||
variablesReference: 0
|
||||
},
|
||||
{
|
||||
name: "_wrapY",
|
||||
value: "<incomplete type>",
|
||||
variablesReference: 0
|
||||
},
|
||||
{
|
||||
name: "_inMode",
|
||||
value: "6408",
|
||||
variablesReference: 0
|
||||
},
|
||||
{
|
||||
name: "_mode",
|
||||
value: "6408",
|
||||
variablesReference: 0
|
||||
},
|
||||
{
|
||||
name: "_id",
|
||||
value: "1",
|
||||
variablesReference: 0
|
||||
},
|
||||
{
|
||||
name: "_width",
|
||||
value: "1024",
|
||||
variablesReference: 0
|
||||
},
|
||||
{
|
||||
name: "_height",
|
||||
value: "1024",
|
||||
variablesReference: 0
|
||||
}
|
||||
]);
|
||||
});
|
||||
test("lldb strings", () => {
|
||||
const node = `{ name = {...} }`;
|
||||
assert.strictEqual(isExpandable(node), 1);
|
||||
const variables = expandValue(variableCreate, node);
|
||||
assert.deepEqual(variables, [
|
||||
{
|
||||
name: "name",
|
||||
value: "...",
|
||||
variablesReference: { expanded: "name" }
|
||||
}
|
||||
]);
|
||||
});
|
||||
test("float values", () => {
|
||||
const node = `{ intval1 = 123, floatval1 = 123.456, intval2 = 3, floatval2 = 234.45 }`;
|
||||
const variables = expandValue(variableCreate, node);
|
||||
|
||||
assert.deepEqual(variables, [
|
||||
{ name: "intval1", value: "123", variablesReference: 0 },
|
||||
{ name: "floatval1", value: "123.456", variablesReference: 0 },
|
||||
{ name: "intval2", value: "3", variablesReference: 0 },
|
||||
{ name: "floatval2", value: "234.45", variablesReference: 0 }
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
import * as path from 'path';
|
||||
import * as Mocha from 'mocha';
|
||||
import * as glob from 'glob';
|
||||
|
||||
export function run(): Promise<void> {
|
||||
// Create the mocha test
|
||||
const mocha = new Mocha({
|
||||
ui: 'tdd',
|
||||
useColors: true
|
||||
});
|
||||
|
||||
const testsRoot = path.resolve(__dirname, '..');
|
||||
|
||||
return new Promise((c, e) => {
|
||||
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
// Add files to the test suite
|
||||
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
|
||||
|
||||
try {
|
||||
// Run the mocha test
|
||||
mocha.run(failures => {
|
||||
if (failures > 0) {
|
||||
e(new Error(`${failures} tests failed.`));
|
||||
} else {
|
||||
c();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
e(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
import * as assert from 'assert';
|
||||
import { parseMI, MINode } from '../../backend/mi_parse';
|
||||
|
||||
suite("MI Parse", () => {
|
||||
test("Very simple out of band record", () => {
|
||||
const parsed = parseMI(`*stopped`);
|
||||
assert.ok(parsed);
|
||||
assert.strictEqual(parsed.token, undefined);
|
||||
assert.strictEqual(parsed.outOfBandRecord.length, 1);
|
||||
assert.strictEqual(parsed.outOfBandRecord[0].isStream, false);
|
||||
assert.strictEqual(parsed.outOfBandRecord[0].asyncClass, "stopped");
|
||||
assert.strictEqual(parsed.outOfBandRecord[0].output.length, 0);
|
||||
assert.strictEqual(parsed.resultRecords, undefined);
|
||||
});
|
||||
test("Simple out of band record", () => {
|
||||
const parsed = parseMI(`4=thread-exited,id="3",group-id="i1"`);
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.token, 4);
|
||||
assert.equal(parsed.outOfBandRecord.length, 1);
|
||||
assert.equal(parsed.outOfBandRecord[0].isStream, false);
|
||||
assert.equal(parsed.outOfBandRecord[0].asyncClass, "thread-exited");
|
||||
assert.equal(parsed.outOfBandRecord[0].output.length, 2);
|
||||
assert.deepEqual(parsed.outOfBandRecord[0].output[0], ["id", "3"]);
|
||||
assert.deepEqual(parsed.outOfBandRecord[0].output[1], ["group-id", "i1"]);
|
||||
assert.equal(parsed.resultRecords, undefined);
|
||||
});
|
||||
test("Console stream output with new line", () => {
|
||||
const parsed = parseMI(`~"[Thread 0x7fffe993a700 (LWP 11002) exited]\\n"`);
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.token, undefined);
|
||||
assert.equal(parsed.outOfBandRecord.length, 1);
|
||||
assert.equal(parsed.outOfBandRecord[0].isStream, true);
|
||||
assert.equal(parsed.outOfBandRecord[0].content, "[Thread 0x7fffe993a700 (LWP 11002) exited]\n");
|
||||
assert.equal(parsed.resultRecords, undefined);
|
||||
});
|
||||
test("Unicode", () => {
|
||||
let parsed = parseMI(`~"[Depuraci\\303\\263n de hilo usando libthread_db enabled]\\n"`);
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.token, undefined);
|
||||
assert.equal(parsed.outOfBandRecord.length, 1);
|
||||
assert.equal(parsed.outOfBandRecord[0].isStream, true);
|
||||
assert.equal(parsed.outOfBandRecord[0].content, "[Depuración de hilo usando libthread_db enabled]\n");
|
||||
assert.equal(parsed.resultRecords, undefined);
|
||||
parsed = parseMI(`~"4\\t std::cout << \\"\\345\\245\\275\\345\\245\\275\\345\\255\\246\\344\\271\\240\\357\\274\\214\\345\\244\\251\\345\\244\\251\\345\\220\\221\\344\\270\\212\\" << std::endl;\\n"`);
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.token, undefined);
|
||||
assert.equal(parsed.outOfBandRecord.length, 1);
|
||||
assert.equal(parsed.outOfBandRecord[0].isStream, true);
|
||||
assert.equal(parsed.outOfBandRecord[0].content, `4\t std::cout << "好好学习,天天向上" << std::endl;\n`);
|
||||
assert.equal(parsed.resultRecords, undefined);
|
||||
});
|
||||
test("Empty line", () => {
|
||||
const parsed = parseMI(``);
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.token, undefined);
|
||||
assert.equal(parsed.outOfBandRecord.length, 0);
|
||||
assert.equal(parsed.resultRecords, undefined);
|
||||
});
|
||||
test("'(gdb)' line", () => {
|
||||
const parsed = parseMI(`(gdb)`);
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.token, undefined);
|
||||
assert.equal(parsed.outOfBandRecord.length, 0);
|
||||
assert.equal(parsed.resultRecords, undefined);
|
||||
});
|
||||
test("Simple result record", () => {
|
||||
const parsed = parseMI(`1^running`);
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.token, 1);
|
||||
assert.equal(parsed.outOfBandRecord.length, 0);
|
||||
assert.notEqual(parsed.resultRecords, undefined);
|
||||
assert.equal(parsed.resultRecords.resultClass, "running");
|
||||
assert.equal(parsed.resultRecords.results.length, 0);
|
||||
});
|
||||
test("Advanced out of band record (Breakpoint hit)", () => {
|
||||
const parsed = parseMI(`*stopped,reason="breakpoint-hit",disp="keep",bkptno="1",frame={addr="0x00000000004e807f",func="D main",args=[{name="args",value="..."}],file="source/app.d",fullname="/path/to/source/app.d",line="157"},thread-id="1",stopped-threads="all",core="0"`);
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.token, undefined);
|
||||
assert.equal(parsed.outOfBandRecord.length, 1);
|
||||
assert.equal(parsed.outOfBandRecord[0].isStream, false);
|
||||
assert.equal(parsed.outOfBandRecord[0].asyncClass, "stopped");
|
||||
assert.equal(parsed.outOfBandRecord[0].output.length, 7);
|
||||
assert.deepEqual(parsed.outOfBandRecord[0].output[0], ["reason", "breakpoint-hit"]);
|
||||
assert.deepEqual(parsed.outOfBandRecord[0].output[1], ["disp", "keep"]);
|
||||
assert.deepEqual(parsed.outOfBandRecord[0].output[2], ["bkptno", "1"]);
|
||||
const frame = [
|
||||
["addr", "0x00000000004e807f"],
|
||||
["func", "D main"],
|
||||
["args", [[["name", "args"], ["value", "..."]]]],
|
||||
["file", "source/app.d"],
|
||||
["fullname", "/path/to/source/app.d"],
|
||||
["line", "157"]
|
||||
];
|
||||
assert.deepEqual(parsed.outOfBandRecord[0].output[3], ["frame", frame]);
|
||||
assert.deepEqual(parsed.outOfBandRecord[0].output[4], ["thread-id", "1"]);
|
||||
assert.deepEqual(parsed.outOfBandRecord[0].output[5], ["stopped-threads", "all"]);
|
||||
assert.deepEqual(parsed.outOfBandRecord[0].output[6], ["core", "0"]);
|
||||
assert.equal(parsed.resultRecords, undefined);
|
||||
});
|
||||
test("Advanced result record", () => {
|
||||
const parsed = parseMI(`2^done,asm_insns=[src_and_asm_line={line="134",file="source/app.d",fullname="/path/to/source/app.d",line_asm_insn=[{address="0x00000000004e7da4",func-name="_Dmain",offset="0",inst="push %rbp"},{address="0x00000000004e7da5",func-name="_Dmain",offset="1",inst="mov %rsp,%rbp"}]}]`);
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.token, 2);
|
||||
assert.equal(parsed.outOfBandRecord.length, 0);
|
||||
assert.notEqual(parsed.resultRecords, undefined);
|
||||
assert.equal(parsed.resultRecords.resultClass, "done");
|
||||
assert.equal(parsed.resultRecords.results.length, 1);
|
||||
const asmInsns = [
|
||||
"asm_insns",
|
||||
[
|
||||
[
|
||||
"src_and_asm_line",
|
||||
[
|
||||
["line", "134"],
|
||||
["file", "source/app.d"],
|
||||
["fullname", "/path/to/source/app.d"],
|
||||
[
|
||||
"line_asm_insn",
|
||||
[
|
||||
[
|
||||
["address", "0x00000000004e7da4"],
|
||||
["func-name", "_Dmain"],
|
||||
["offset", "0"],
|
||||
["inst", "push %rbp"]
|
||||
],
|
||||
[
|
||||
["address", "0x00000000004e7da5"],
|
||||
["func-name", "_Dmain"],
|
||||
["offset", "1"],
|
||||
["inst", "mov %rsp,%rbp"]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
assert.deepEqual(parsed.resultRecords.results[0], asmInsns);
|
||||
assert.equal(parsed.result("asm_insns.src_and_asm_line.line_asm_insn[1].address"), "0x00000000004e7da5");
|
||||
});
|
||||
test("valueof children", () => {
|
||||
const obj = [
|
||||
[
|
||||
"frame",
|
||||
[
|
||||
["level", "0"],
|
||||
["addr", "0x0000000000435f70"],
|
||||
["func", "D main"],
|
||||
["file", "source/app.d"],
|
||||
["fullname", "/path/to/source/app.d"],
|
||||
["line", "5"]
|
||||
]
|
||||
],
|
||||
[
|
||||
"frame",
|
||||
[
|
||||
["level", "1"],
|
||||
["addr", "0x00000000004372d3"],
|
||||
["func", "rt.dmain2._d_run_main()"]
|
||||
]
|
||||
],
|
||||
[
|
||||
"frame",
|
||||
[
|
||||
["level", "2"],
|
||||
["addr", "0x0000000000437229"],
|
||||
["func", "rt.dmain2._d_run_main()"]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
assert.equal(MINode.valueOf(obj[0], "@frame.level"), "0");
|
||||
assert.equal(MINode.valueOf(obj[0], "@frame.addr"), "0x0000000000435f70");
|
||||
assert.equal(MINode.valueOf(obj[0], "@frame.func"), "D main");
|
||||
assert.equal(MINode.valueOf(obj[0], "@frame.file"), "source/app.d");
|
||||
assert.equal(MINode.valueOf(obj[0], "@frame.fullname"), "/path/to/source/app.d");
|
||||
assert.equal(MINode.valueOf(obj[0], "@frame.line"), "5");
|
||||
|
||||
assert.equal(MINode.valueOf(obj[1], "@frame.level"), "1");
|
||||
assert.equal(MINode.valueOf(obj[1], "@frame.addr"), "0x00000000004372d3");
|
||||
assert.equal(MINode.valueOf(obj[1], "@frame.func"), "rt.dmain2._d_run_main()");
|
||||
assert.equal(MINode.valueOf(obj[1], "@frame.file"), undefined);
|
||||
assert.equal(MINode.valueOf(obj[1], "@frame.fullname"), undefined);
|
||||
assert.equal(MINode.valueOf(obj[1], "@frame.line"), undefined);
|
||||
});
|
||||
test("empty string values", () => {
|
||||
const parsed = parseMI(`15^done,register-names=["r0","pc","","xpsr","","control"]`);
|
||||
const result = parsed.result('register-names');
|
||||
assert.deepEqual(result, ["r0", "pc", "", "xpsr", "", "control"]);
|
||||
});
|
||||
test("empty string value first and last", () => {
|
||||
const parsed = parseMI(`15^done,register-names=["","r0","pc","","xpsr","","control",""]`);
|
||||
const result = parsed.result('register-names');
|
||||
assert.deepEqual(result, ["","r0","pc","","xpsr","","control", ""]);
|
||||
});
|
||||
test("empty array values", () => {
|
||||
const parsed = parseMI(`15^done,foo={x=[],y="y"}`);
|
||||
assert.deepEqual(parsed.result('foo.x'), []);
|
||||
assert.equal(parsed.result('foo.y'), "y");
|
||||
});
|
||||
test("empty object values", () => {
|
||||
// GDB may send {} as empty array
|
||||
const parsed = parseMI(`15^done,foo={x={},y="y"}`);
|
||||
assert.deepEqual(parsed.result('foo.x'), []);
|
||||
assert.equal(parsed.result('foo.y'), "y");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "out",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"rootDir": ".",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "tslint-language-service"
|
||||
}
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".vscode-test"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": "tslint:recommended",
|
||||
"rules": {
|
||||
"no-null-keyword": true,
|
||||
"indent": [true, "tabs"],
|
||||
/* Rules in tslint:recommended that we don't follow yet. */
|
||||
"array-type": false,
|
||||
"arrow-parens": false,
|
||||
"arrow-return-shorthand": false,
|
||||
"ban-types": false,
|
||||
"class-name": false,
|
||||
"comment-format": false,
|
||||
"curly": false,
|
||||
"interface-name": false,
|
||||
"max-classes-per-file": false,
|
||||
"max-line-length": false,
|
||||
"member-access": false,
|
||||
"member-ordering": false,
|
||||
"no-angle-bracket-type-assertion": false,
|
||||
"no-bitwise": false,
|
||||
"no-conditional-assignment": false,
|
||||
"no-consecutive-blank-lines": false,
|
||||
"no-empty": false,
|
||||
"no-shadowed-variable": false,
|
||||
"no-unnecessary-initializer": false,
|
||||
"object-literal-shorthand": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"one-variable-per-declaration": false,
|
||||
"only-arrow-functions": false,
|
||||
"ordered-imports": false,
|
||||
"quotemark": false,
|
||||
"radix": false,
|
||||
"space-before-function-paren": false,
|
||||
"trailing-comma": false,
|
||||
"triple-equals": false
|
||||
}
|
||||
}
|