├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── feature_request.md │ └── question.md └── workflows │ └── main.yaml ├── .gitignore ├── .travis.yml ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── TODO.md ├── binary_modules ├── build.sh ├── package-lock.json └── package.json ├── debug_attributes.md ├── eslint.config.mjs ├── images ├── cortex-debug-settings.png ├── gdb-server.png ├── hex-off-dark.svg ├── hex-off-light.svg ├── hex-on-dark.svg ├── hex-on-light.svg ├── icon.png ├── reset-dark.svg ├── reset-light.svg ├── reset.svg └── vs-code-screenshot.png ├── options-doc.py.unused ├── package-lock.json ├── package.json ├── release.js ├── resources ├── grapher.html ├── rtos.css └── rtos.js ├── src ├── backend │ ├── backend.ts │ ├── disasm.ts │ ├── gdb_expansion.ts │ ├── mi2 │ │ └── mi2.ts │ ├── mi_parse.ts │ ├── server.ts │ └── symbols.ts ├── bmp.ts ├── common.ts ├── dbgmsgs.ts ├── docgen.ts ├── external.ts ├── frontend │ ├── addrranges.ts │ ├── ansi-helpers.ts │ ├── configprovider.ts │ ├── cortex_debug_session.ts │ ├── extension.ts │ ├── memory_content_provider.ts │ ├── memreadutils.ts │ ├── pty.ts │ ├── rtos │ │ └── DEPRECATED.txt │ ├── rtt_terminal.ts │ ├── server_console.ts │ ├── swo │ │ ├── advanced-decoder.ts │ │ ├── common.ts │ │ ├── core.ts │ │ ├── decoders │ │ │ ├── advanced.ts │ │ │ ├── binary.ts │ │ │ ├── common.ts │ │ │ ├── console.ts │ │ │ ├── graph.ts │ │ │ └── utils.ts │ │ └── sources │ │ │ ├── common.ts │ │ │ ├── fifo.ts │ │ │ ├── file.ts │ │ │ ├── serial.ts │ │ │ ├── socket.ts │ │ │ └── usb.ts │ ├── utils.ts │ └── views │ │ ├── live-watch.ts │ │ └── nodes │ │ ├── basenode.ts │ │ ├── fieldnode.ts │ │ └── registernode.ts ├── gdb.ts ├── global.d.ts ├── grapher │ ├── datasource.ts │ ├── main.ts │ ├── programstatsgraph.ts │ ├── timeseriesgraph.ts │ ├── types.ts │ └── xygraph.ts ├── jlink.ts ├── live-watch-monitor.ts ├── openocd.ts ├── pemicro.ts ├── pyocd.ts ├── qemu.ts ├── remote │ ├── .eslintrc.json │ ├── .gitignore │ ├── .vscode │ │ ├── extensions.json │ │ ├── launch.json │ │ ├── settings.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── extension.ts │ │ ├── interfaces.ts │ │ ├── server.ts │ │ ├── tcpportscanner.ts │ │ └── test │ │ │ ├── runTest.ts │ │ │ └── suite │ │ │ ├── extension.test.ts │ │ │ └── index.ts │ ├── tsconfig.json │ └── webpack.config.js ├── reporting.ts ├── stlink.ts ├── stutil.ts ├── symbols.ts └── tcpportscanner.ts ├── support ├── gdb-swo.init ├── gdbsupport.init ├── openocd-helpers.tcl └── swo-init.c ├── syntaxes ├── cortex-debug-disassembly.json └── cortex-debug-memoryview.json ├── test ├── runTests.ts └── suite │ ├── gdb_expansion.test.ts │ ├── index.ts │ ├── mi_parse.test.ts │ ├── serialport.test.ts │ └── tcpportscanner.test.ts ├── tsconfig.json └── webpack.config.js /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug/issue report. You MUST use this form unless you have a feature request 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | ### Please make you search through our existing [issues](https://github.com/Marus/cortex-debug/issues?q=type:issue) (both open and closed) 10 | It may help to look at these instructions in `Preview` mode. Please visit the correct repo to file an issue. If this is an issue with 11 | * Peripherals/SVD Viewer: [https://github.com/mcu-debug/peripheral-viewer](https://github.com/mcu-debug/peripheral-viewer) 12 | * Memory Viewer: [https://github.com/mcu-debug/memview](https://github.com/mcu-debug/memview) 13 | * RTOS Viewer: [https://github.com/mcu-debug/rtos-views](https://github.com/mcu-debug/rtos-views) 14 | * the debugger itself, continue below 15 | 16 | Please read our documentation as well. You have a lot of control over how Cortex-Debug works. Besides the top level [README.md](https://github.com/Marus/cortex-debug/blob/master/README.md), we have the following 17 | 18 | https://github.com/Marus/cortex-debug/wiki 19 | https://github.com/Marus/cortex-debug/wiki/Cortex-Debug-Under-the-hood 20 | https://github.com/Marus/cortex-debug/blob/master/debug_attributes.md 21 | 22 | *Finally, make sure all your external tools are configured properly and working. We print all the commands in the Debug Console. Most of the bug reports are from Linux users with improperly installed GNU tools. If you can't run those tools, neither can we.* 23 | 24 | Thank you for helping reduce the number of issues which is becoming overwhelming as most of them are not issues at all. You can delete all the above text and start filing the issue 25 | 26 | **Describe the bug** 27 | A clear and concise description of what the bug is. 28 | **To Reproduce** 29 | Steps to reproduce the behavior: 30 | 1. Start debug session 31 | 2. Click on '....' 32 | 3. Scroll down to '....' 33 | 4. See issue 34 | 35 | **Expected behavior** 36 | 37 | [comment]: <> A clear and concise description of what you expected to happen. 38 | 39 | **Screenshots** 40 | 41 | [comment]: <> If applicable, add screenshots to help explain your problem. 42 | 43 | **Environment (please complete the following information):** 44 | 45 | [comment]: <> Whenever possible, please make sure you are using the latest versions of VSCode and our extension 46 | 47 | - Cortex-Debug Version (this extension) [e.g. 0.2.3] 48 | - OS: [e.g. Linux Ubuntu 18.04 LTS, Windows 11, etc.] 49 | - GDB Version: [e.g. 1.11.1] 50 | - Compiler Toolchain Version: [e.g. arn-none-eabi V 11.1] 51 | 52 | **Please include `launch.json`** 53 | 54 | *Note: We are unlikely to look at the issue if you do not supply this* 55 | ``` 56 | Paste launch.json contents here 57 | ``` 58 | 59 | **Attach text from `Debug Console`** 60 | 61 | Please enable debug output in your launch.json (`"showDevDebugOutput": "raw"`). It this is too large, please attach it as a file 62 | ``` 63 | Paste Debug Console contents here 64 | ``` 65 | 66 | **Additional context** 67 | Add any other context about the problem here. 68 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please visit the correct repo to file an feature request. If this is an feature for 11 | * Peripherals/SVD Viewer: [https://github.com/mcu-debug/peripheral-viewer](https://github.com/mcu-debug/peripheral-viewer) 12 | * Memory Viewer: [https://github.com/mcu-debug/memview](https://github.com/mcu-debug/memview) 13 | * RTOS Viewer: [https://github.com/mcu-debug/rtos-views](https://github.com/mcu-debug/rtos-views) 14 | * the debugger itself, continue below 15 | 16 | Please read our documentation as well. You have a lot of control over how Cortex-Debug works. Besides the top level README.md, we have the following 17 | 18 | https://github.com/Marus/cortex-debug/wiki 19 | https://github.com/Marus/cortex-debug/wikiCortex-Debug-Under-the-hood 20 | https://github.com/Marus/cortex-debug/blob/master/debug_attributes.md 21 | 22 | **Is your feature request related to a problem? Please describe.** 23 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 24 | 25 | **Describe the solution you'd like** 26 | A clear and concise description of what you want to happen. 27 | 28 | **Describe alternatives you've considered** 29 | A clear and concise description of any alternative solutions or features you've considered. 30 | 31 | **Additional context** 32 | Add any other context or screenshots about the feature request here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General Question 3 | about: Ask for help from the community 4 | title: '' 5 | labels: 'question' 6 | assignees: '' 7 | --- 8 | While we monitor these questions, you will have better luck with https://stackoverflow.com/questions 9 | or general Google/Web searches. Even AI chatbots like ChatGPT are a good resource. We are hoping 10 | people with similar setups like yours will chime in and help. 11 | 12 | If a question goes unanswered in 30 days or if the conversion is no longer civil, we may close it. 13 | If your question got answered please close the topic. In the case that it turns into an issue 14 | with the extension, we will ask you to submit a proper issue report and close this topic 15 | 16 | Please remove all the above text and start asking your question. 17 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | setup: 11 | name: Setup 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Cache NPM Install 16 | id: cache-npm 17 | uses: actions/cache@v3 18 | with: 19 | path: ./node_modules 20 | key: npm-${{ hashFiles('./package-lock.json') }} 21 | - name: Install NPM dependencies 22 | if: steps.cache-npm.outputs.cache-hit != 'true' 23 | run: | 24 | npm install 25 | 26 | build: 27 | name: Build 28 | needs: setup 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v3 32 | - name: Load NPM install 33 | id: cache-npm 34 | uses: actions/cache@v3 35 | with: 36 | path: ./node_modules 37 | key: npm-${{ hashFiles('./package-lock.json') }} 38 | - name: Install VSCE for packaging 39 | run: npm install vsce 40 | - name: Package Binary 41 | run: ./node_modules/vsce/vsce package -o cortex-debug.vsix 42 | - name: Upload Artifact 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: cortex-debug.vsix 46 | path: ./cortex-debug.vsix 47 | 48 | lint: 49 | name: Lint 50 | needs: setup 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@v3 54 | - name: Load NPM install 55 | id: cache-npm 56 | uses: actions/cache@v3 57 | with: 58 | path: ./node_modules 59 | key: npm-${{ hashFiles('./package-lock.json') }} 60 | - name: Lint Project 61 | run: npm run lint 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | /node_modules 3 | /webview/node_modules 4 | /binary_modules/**/node_modules 5 | *.vsix 6 | .vscode-test 7 | .DS_Store 8 | .sass-cache 9 | /dist 10 | /tmp 11 | binary_modules/electron* 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - stable 5 | os: 6 | - osx 7 | - linux 8 | 9 | before_install: 10 | - if [ $TRAVIS_OS_NAME == "linux" ]; then 11 | export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; 12 | sh -e /etc/init.d/xvfb start; 13 | sleep 3; 14 | fi 15 | 16 | install: 17 | - npm install 18 | - npm run vscode:prepublish 19 | 20 | script: 21 | - npm test --silent 22 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "tobermory.es6-string-html", 4 | "dbaeumer.vscode-eslint" 5 | ] 6 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}"],//, "--unhandled-rejections=strict" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outFiles": [ 14 | "${workspaceRoot}/dist/*.js" 15 | ] 16 | }, 17 | { 18 | "name": "Debug Server", 19 | "type": "node", 20 | "request": "launch", 21 | "runtimeArgs": [ "--nolazy", "--trace-warnings"],//, "--unhandled-rejections=strict" ], 22 | "program": "${workspaceRoot}/dist/debugadapter.js", 23 | "stopOnEntry": false, 24 | "args": [ "--server=4711" ], 25 | "sourceMaps": true, 26 | "outFiles": [ 27 | "${workspaceRoot}/dist/*.js" 28 | ], 29 | "cwd": "${workspaceRoot}" 30 | }, 31 | { 32 | "name": "Launch Tests", 33 | "type": "extensionHost", 34 | "request": "launch", 35 | "runtimeExecutable": "${execPath}", 36 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test/suite/index" ], 37 | "stopOnEntry": false, 38 | "sourceMaps": true, 39 | "outFiles": [ 40 | "${workspaceRoot}/out/test/**/*.js" 41 | ], 42 | "preLaunchTask": "test-compile" 43 | } 44 | ], 45 | "compounds": [ 46 | { 47 | "name": "Extension + Debug Server", 48 | "configurations": ["Launch Extension", "Debug Server"] 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.tabSize": 4, 4 | "editor.insertSpaces": true, 5 | "editor.useTabStops": false, 6 | "eslint.useFlatConfig": true, 7 | "files.trimTrailingWhitespace": true, 8 | "files.exclude": { 9 | "out": false, 10 | "dist": true, 11 | "node_modules": true, 12 | "tmp": true 13 | }, 14 | "search.exclude": { 15 | "out": true, 16 | "dist": true, 17 | "node_modules": true, 18 | "binary_modules": true, 19 | "tmp": true 20 | }, 21 | "typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version 22 | "svg.preview.background": "transparent", 23 | "cSpell.words": [ 24 | "aarch", 25 | "bkpt", 26 | "cdasm", 27 | "cdmem", 28 | "cjtag", 29 | "cppdbg", 30 | "dprintf", 31 | "eabi", 32 | "hwthread", 33 | "insn", 34 | "insns", 35 | "instrs", 36 | "jlink", 37 | "jtag", 38 | "Logpoints", 39 | "nuttx", 40 | "Prio", 41 | "pyocd", 42 | "SEGGER", 43 | "stlink", 44 | "Syms", 45 | "XPERIPHERALS", 46 | "xtensa" 47 | ], 48 | "cmake.configureOnOpen": false 49 | } 50 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "test-compile", 8 | "type": "shell", 9 | "command": "npm", 10 | "args": ["run-script", "test-compile"] 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | typings/** 3 | out/** 4 | test/** 5 | src/** 6 | tmp/** 7 | **/*.map 8 | .gitignore 9 | tsconfig.json 10 | vsc-extension-quickstart.md 11 | webpack.config.js 12 | node_modules 13 | binary_modules 14 | 15 | !node_modules/@vscode/webview-ui-toolkit 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017-2023 Marcel Ball 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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 OR COPYRIGHT HOLDERS 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. 8 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Debug Protocol Related 2 | 3 | Low, medium, high are importance/usefulness. But they sometimes represent *bang-for-the-buck* as well where the effort could be high vs. relative usefulness. Some of there are here because there is new functionality in VSCode APIs 4 | 5 | * Low: `Set Value` in `Watch` Window 6 | 7 | # Other 8 | 9 | * Medium: Sooner or later we need to migrate our code base to eslint. This is very deinquent and a huge number of changes are required. I don't have all the expertise to do this smoothly 10 | * Low: WSL: First class support. This includes Docker, WSL and perhaps 'ssh'. VSCode and WSL seem to be maturing. Still not there but...there may be enough. 11 | * This may not be needed anymore since there is an effort to support a USB proxy mechanism in WSL. If that works out, then there is no need for us to do anything. As of Jun 2022, this is look more promising 12 | -------------------------------------------------------------------------------- /binary_modules/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ $# -ne 1 ]] ; then 4 | echo "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | if [[ ! -f ./package.json ]] ; then 9 | # Chances are you are not in the root directory of cortex-debug 10 | echo "Error: ./package.json does not exist" 11 | exit 1 12 | fi 13 | 14 | if ! command -v npm &> /dev/null ; then 15 | echo "'npm' could not be found. Please install NodeJS. Visit https://nodejs.org/en/download/" 16 | exit 17 | fi 18 | 19 | echo "Installing serialport and electron and rebuilding serialport for Electron version '$1'. This could take a while..." 20 | 21 | set -x 22 | npm install 23 | ./node_modules/.bin/electron-rebuild -v "$1" 24 | rm -fr electron-"$1" 25 | mkdir -p electron-"$1" 26 | mv ./node_modules electron-"$1"/node_modules 27 | 28 | -------------------------------------------------------------------------------- /binary_modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tmp", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "license": "MIT", 10 | "devDependencies": { 11 | "electron-rebuild": "^3.2.8" 12 | }, 13 | "dependencies": { 14 | "serialport": "^10.4.0", 15 | "usb": "^2.14.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from '@eslint/js'; 2 | import tseslint from 'typescript-eslint'; 3 | import stylistic from '@stylistic/eslint-plugin'; 4 | import globals from 'globals'; 5 | 6 | export default tseslint.config( 7 | { 8 | ignores: ['out/', 'dist/'], 9 | }, 10 | eslint.configs.recommended, 11 | tseslint.configs.recommendedTypeChecked, 12 | stylistic.configs.customize({ 13 | arrowParens: 'always', 14 | braceStyle: '1tbs', 15 | commaDangle: 'only-multiline', 16 | indent: 4, 17 | quotes: 'single', 18 | semi: 'always', 19 | }), 20 | { 21 | files: ['resources/*.js'], 22 | languageOptions: { 23 | globals: { 24 | acquireVsCodeApi: true, // available for VSCode WebViews 25 | }, 26 | }, 27 | }, 28 | { 29 | languageOptions: { 30 | globals: { 31 | ...globals.node, 32 | ...globals.browser, 33 | }, 34 | parserOptions: { 35 | project: true, 36 | tsconfigRootDir: import.meta.dirname, 37 | }, 38 | }, 39 | 40 | rules: { 41 | '@typescript-eslint/no-base-to-string': 'off', // 1 instance 42 | '@typescript-eslint/prefer-promise-reject-errors': ['error', { 43 | // for catch (e) { reject(e); } scenarios, until promises are refactored 44 | allowThrowingAny: true, 45 | allowThrowingUnknown: true, 46 | }], 47 | 48 | '@stylistic/indent-binary-ops': 'off', // this is a weird rule 49 | '@stylistic/max-len': ['error', { 50 | code: 160, 51 | ignoreTrailingComments: true, 52 | }], 53 | '@stylistic/max-statements-per-line': ['error', { 54 | ignoredNodes: ['IfStatement'], 55 | }], 56 | '@stylistic/member-delimiter-style': ['error', { 57 | multiline: { delimiter: 'semi' }, 58 | singleline: { delimiter: 'semi' }, 59 | }], 60 | '@stylistic/no-multi-spaces': ['error', { 61 | ignoreEOLComments: true, 62 | }], 63 | '@stylistic/quote-props': ['error', 'as-needed', { 64 | unnecessary: false, 65 | }], 66 | } 67 | }, 68 | { 69 | // the following rules are being heavily violated in the current codebase, 70 | // we should work on being able to enable them... 71 | rules: { 72 | '@typescript-eslint/no-unsafe-member-access': 'off', // 655 instances 73 | '@typescript-eslint/no-unsafe-call': 'off', // 381 instances 74 | '@typescript-eslint/no-unsafe-assignment': 'off', // 354 instances 75 | '@typescript-eslint/no-unsafe-argument': 'off', // 309 instances 76 | '@typescript-eslint/no-explicit-any': 'off', // 187 instances 77 | '@typescript-eslint/no-unused-vars': 'off', // 169 instances 78 | '@typescript-eslint/no-misused-promises': 'off', // 53 instances 79 | '@typescript-eslint/no-floating-promises': 'off', // 48 instances 80 | 'no-async-promise-executor': 'off', // 25 instances 81 | } 82 | }, 83 | { 84 | files: ['**/*.{js,mjs}'], 85 | extends: [tseslint.configs.disableTypeChecked], 86 | }, 87 | { 88 | files: ['**/*.js'], 89 | rules: { 90 | '@typescript-eslint/no-require-imports': 'off', 91 | } 92 | } 93 | ); 94 | -------------------------------------------------------------------------------- /images/cortex-debug-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marus/cortex-debug/0074bab85fabbcabc508c734abe16873376ac70a/images/cortex-debug-settings.png -------------------------------------------------------------------------------- /images/gdb-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marus/cortex-debug/0074bab85fabbcabc508c734abe16873376ac70a/images/gdb-server.png -------------------------------------------------------------------------------- /images/hex-off-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 0x 10 | 11 | -------------------------------------------------------------------------------- /images/hex-off-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 0x 10 | 11 | -------------------------------------------------------------------------------- /images/hex-on-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | n 10 | 11 | -------------------------------------------------------------------------------- /images/hex-on-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | n 10 | 11 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marus/cortex-debug/0074bab85fabbcabc508c734abe16873376ac70a/images/icon.png -------------------------------------------------------------------------------- /images/reset-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /images/reset-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /images/reset.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /images/vs-code-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marus/cortex-debug/0074bab85fabbcabc508c734abe16873376ac70a/images/vs-code-screenshot.png -------------------------------------------------------------------------------- /options-doc.py.unused: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pprint 4 | import json 5 | import re 6 | 7 | pp = pprint.PrettyPrinter() 8 | 9 | expected_diff_properties = ['overrideAttachCommands', 'overrideLaunchCommands', 10 | 'postAttachCommands', 'postLaunchCommands', 11 | 'preAttachCommands', 'preLaunchCommands', 12 | 'runToEntryPoint', 'runToMain', 'loadFiles'] 13 | 14 | def get_properties(pkg, type): 15 | attributes = pkg['contributes']['debuggers'][0]['configurationAttributes'][type] 16 | return dict([(p, attributes['properties'][p].get('description', '(unknown)')) 17 | for p in attributes['properties']]) 18 | 19 | 20 | with open('package.json') as f: 21 | package = json.load(f) 22 | 23 | attach_properties = get_properties(package, 'attach') 24 | launch_properties = get_properties(package, 'launch') 25 | extra_properties = list(set(list(attach_properties.keys() - 26 | launch_properties.keys()) + list(launch_properties.keys() - attach_properties.keys())) - set(expected_diff_properties)) 27 | extra_properties.sort() 28 | if len(extra_properties) > 0: 29 | print("WARNING: launch_properties and attach_properties DIFFER UNEXPECTEDLY:") 30 | print(extra_properties) 31 | 32 | # pp.pprint(attach_properties) 33 | 34 | with open('src/common.ts') as f: 35 | common_ts = f.read() 36 | 37 | config_args = re.search( 38 | '^export interface ConfigurationArguments extends DebugProtocol.LaunchRequestArguments {(.*?)^}$', common_ts, re.M + re.S).groups()[0] 39 | 40 | categories = {} 41 | category_name = '' 42 | category_list = ['armToolchainPath'] 43 | for line in config_args.splitlines(): 44 | line = line.strip() 45 | if len(line) == 0 or line.startswith('///') or line.startswith('pvt'): 46 | continue 47 | if line.startswith('// '): 48 | categories[category_name] = category_list 49 | category_name = line[3:] 50 | category_list = [] 51 | else: 52 | category_list.append(line.split(':')[0]) 53 | categories[category_name] = category_list 54 | 55 | # pp.pprint(categories) 56 | 57 | MISSING_ATTRIBUTES = ['extensionPath', 'registerUseNaturalFormat', 'variableUseNaturalFormat', 'toolchainPath'] 58 | 59 | all_properties = {**attach_properties, **launch_properties} 60 | 61 | with open('debug_attributes.md', 'w') as f: 62 | f.write('There are many `User/Workspace Settings` to control things globally. You can find these in the VSCode Settings UI. `launch.json`'); 63 | f.write(' can override some of those settings. There is a lot of functionality that is available via `Settings` and some may be useful in a'); 64 | f.write(' team environment and/or can be used across all cortex-debug sessions\n\n'); 65 | f.write('![](./images/cortex-debug-settings.png)\n\n'); 66 | f.write('The following attributes (properties) can be used in your `launch.json` to control various aspects of debugging.'); 67 | f.write(' Also `IntelliSense` is an invaluable aid while editing `launch.json`. With `IntelliSense`, you can hover over an attribute to get'); 68 | f.write(' more information and/or help you find attributes (just start typing a double-quote, use Tab key) and provide defaults/options.\n\n'); 69 | f.write('| Attribute | Applies To | Description |\n') 70 | f.write('| --------- | ---------- | ----------- |\n') 71 | for category in sorted(categories.keys()): 72 | if len(category) == 0: 73 | category_name = 'Common' 74 | else: 75 | category_name = category 76 | for attribute in sorted(categories[category]): 77 | if attribute in MISSING_ATTRIBUTES: 78 | continue 79 | if attribute in all_properties.keys(): 80 | if attribute in attach_properties.keys() and attribute in launch_properties.keys(): 81 | if attach_properties[attribute] != launch_properties[attribute]: 82 | print("WARNING: configuration property {} DIFFER UNEXPECTEDLY between attach and launch".format(attribute)) 83 | f.write('| {} | {} | {}\n'.format( 84 | attribute, category_name, all_properties[attribute])) 85 | else: 86 | f.write('| {} | {} | ????\n'.format( 87 | attribute, category_name)) 88 | -------------------------------------------------------------------------------- /release.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const os = require('os'); 3 | const child_process = require('child_process'); 4 | 5 | let prog = '??'; 6 | let tagName = ''; 7 | let isDryRun = false; 8 | let vsxAlso = false; 9 | let openVsxPat = ''; 10 | 11 | function errExit(...args) { 12 | console.error(`${prog}: Error:`, ...args); 13 | process.exit(1); 14 | } 15 | 16 | function ensureGitClean() { 17 | const gitStatus = child_process 18 | .execSync('git status --short') 19 | .toString() 20 | .trim(); 21 | if (gitStatus) { 22 | errExit('Uncommitted changes exist. exiting. Cannot continue'); 23 | } 24 | } 25 | 26 | function isPreRelease() { 27 | let obj; 28 | const path = './package.json'; 29 | try { 30 | const txt = fs.readFileSync(path); 31 | obj = JSON.parse(txt.toString()); 32 | } catch (e) { 33 | errExit(`Could not open/read file ${path}`, e); 34 | } 35 | const version = obj.version; 36 | if (!version) { 37 | errExit(`"version" property not found in ${path}`); 38 | } 39 | const parts = version.split('.'); 40 | const minor = parseInt(parts[1]); 41 | tagName = 'v' + version; 42 | return (minor % 2) === 1; 43 | } 44 | 45 | function vsceRun(pkgOnly) { 46 | const npx = 'npx'; 47 | const args = [npx, 'vsce', (pkgOnly ? 'package' : 'publish')]; 48 | if (isPreRelease()) { 49 | args.push('--pre-release'); 50 | if (vsxAlso) { 51 | console.log(`${prog}: Note: Not publishing to open-vsx because this is a pre-release`); 52 | vsxAlso = false; 53 | } 54 | } 55 | if (!isDryRun && !pkgOnly) { 56 | ensureGitClean(); 57 | } 58 | runProg(args, (code) => { 59 | if (!pkgOnly && (code === 0)) { 60 | let gitCmd = ['git', 'tag', tagName]; 61 | runProg(gitCmd, (code) => { 62 | if (code === 0) { 63 | gitCmd = ['git', 'push', 'origin', tagName]; 64 | runProg(gitCmd, (code) => { 65 | if (code !== 0) { 66 | errExit(`Failed '${gitCmd}'`); 67 | } 68 | }); 69 | } else { 70 | errExit(`Failed '${gitCmd}'`); 71 | } 72 | }); 73 | if (vsxAlso) { 74 | const vsxCmd = [npx, 'ovsx', 'publish', '-p', openVsxPat]; 75 | runProg(vsxCmd, (code) => { 76 | if (code !== 0) { 77 | errExit(`Failed '${vsxCmd}'`); 78 | } 79 | }); 80 | } 81 | } 82 | }); 83 | } 84 | 85 | function runProg(args, cb) { 86 | if (isDryRun) { 87 | args.unshift('echo'); 88 | } 89 | const cmd = args.join(' '); 90 | const arg0 = os.platform() === 'win32' ? 'cmd.exe' : args.shift(); 91 | if (os.platform() === 'win32') { 92 | args.unshift('/c'); 93 | } 94 | // console.log('Executing ' + arg0 + ' ' + args.join(' ')); 95 | const prog = child_process.spawn(arg0, args, { 96 | stdio: 'inherit' 97 | }); 98 | prog.on('error', (error) => { 99 | console.error(`Error running '${cmd}': ${error.message}`); 100 | if (cb) { 101 | cb(-1); 102 | } 103 | }); 104 | prog.on('close', (code) => { 105 | if (!isDryRun) { 106 | console.log(`'${cmd}' ... exited with code ${code}`); 107 | } 108 | if (cb) { 109 | cb(code); 110 | } 111 | }); 112 | } 113 | 114 | function run() { 115 | let isPkg = true; 116 | const [, prog, ...argv] = process.argv; 117 | while (argv.length) { 118 | switch (argv[0]) { 119 | case '-h': 120 | case '--help': { 121 | console.log(`Usage: node ${prog} [--dryrun] [--package] [--publish] [--vsx-also]`); 122 | console.log('\t--package is by default, true'); 123 | process.exit(0); 124 | break; 125 | } 126 | case '--dryrun': { 127 | isDryRun = true; 128 | console.log(`${prog}: This is a dryrun`); 129 | break; 130 | } 131 | case '--package': { 132 | isPkg = true; 133 | break; 134 | } 135 | case '--publish': { 136 | isPkg = false; 137 | break; 138 | } 139 | case '--vsx-also': { 140 | vsxAlso = true; 141 | openVsxPat = process.env.OPEN_VSX_PAT; 142 | if (!openVsxPat) { 143 | errExit('Environment variable OPEN_VSX_PAT not found'); 144 | } 145 | break; 146 | } 147 | default: { 148 | errExit(`Unknown argument '${argv[0]}'`); 149 | process.exit(1); 150 | } 151 | } 152 | argv.shift(); 153 | } 154 | if (isPkg) { 155 | vsxAlso = false; 156 | } 157 | vsceRun(isPkg); 158 | } 159 | 160 | run(); 161 | -------------------------------------------------------------------------------- /resources/grapher.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | Cortex-Debug Grapher 12 | 13 | 33 | 34 | 35 | 36 |

Cortex-Debug SWO/RTT Grapher

37 |
38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /resources/rtos.css: -------------------------------------------------------------------------------- 1 | .threads-grid { 2 | gap: 0; 3 | text-align: left; 4 | } 5 | 6 | .threads-header-row { 7 | border: 1px solid var(--vscode-panel-border); 8 | } 9 | 10 | .threads-row { 11 | border: 1px solid var(--vscode-panel-border); 12 | } 13 | 14 | .threads-header-cell { 15 | padding: 0px; 16 | color: var(--vscode-symbolIcon-constructorForeground); 17 | font-family: var(--vscode-editor-font-family); 18 | font-weight: var(--vscode-editor-font-weight); 19 | font-size: var(--vscode-editor-font-size); 20 | } 21 | 22 | .threads-cell { 23 | padding: 0px; 24 | font-family: var(--vscode-editor-font-family); 25 | font-weight: var(--vscode-editor-font-weight); 26 | font-size: var(--vscode-editor-font-size); 27 | } 28 | 29 | .whitespacePreserve { 30 | white-space: pre-wrap 31 | } 32 | 33 | vscode-link { 34 | font-family: var(--vscode-editor-font-family); 35 | font-weight: var(--vscode-editor-font-weight); 36 | font-size: var(--vscode-editor-font-size); 37 | } 38 | 39 | .running { 40 | color: var(--vscode-symbolIcon-classForeground); 41 | font-weight: bold; 42 | } 43 | 44 | .backgroundPercent { 45 | background: linear-gradient(to right, var(--vscode-charts-blue) 0% var(--rtosview-percentage-active, 0%), rgba(0, 0, 0, 0) var(--rtosview-percentage-active, 0%) 100%); 46 | background-clip: padding-box; 47 | } 48 | 49 | .centerAlign { 50 | text-align: center; 51 | } 52 | 53 | .rightAlign { 54 | text-align: right; 55 | } 56 | 57 | .collapse-button { 58 | color: var(--vscode-textLink-activeForeground); 59 | background-color: rgba(0, 0, 0, 0); 60 | cursor: pointer; 61 | padding: 0; 62 | width: 100%; 63 | border: none; 64 | text-align: left; 65 | font-family: var(--vscode-editor-font-family); 66 | font-weight: var(--vscode-editor-font-weight); 67 | font-size: var(--vscode-editor-font-size); 68 | outline: none; 69 | } 70 | 71 | .collapse-button:after { 72 | content: '\002B'; 73 | font-weight: bold; 74 | float: right; 75 | margin-left: 5px; 76 | margin-right: 5px; 77 | } 78 | 79 | .collapse-button.active:after { 80 | content: "\2212"; 81 | } 82 | 83 | .collapse { 84 | padding: 0 18px; 85 | max-height: 0; 86 | overflow: hidden; 87 | transition: max-height 0.2s ease-out; 88 | } 89 | 90 | .help-button { 91 | font-style: italic; 92 | color: var(--vscode-textLink-activeForeground); 93 | background-color: var(--vscode-textBlockQuote-background); 94 | cursor: pointer; 95 | padding: 4px; 96 | width: 100%; 97 | border: none; 98 | text-align: left; 99 | outline: none; 100 | font-family: var(--vscode-editor-font-family); 101 | font-weight: var(--vscode-editor-font-weight); 102 | font-size: var(--vscode-editor-font-size); 103 | } 104 | 105 | .help-button:after { 106 | content: '\002B'; 107 | font-weight: bold; 108 | float: right; 109 | margin-left: 5px; 110 | } 111 | 112 | .help-button.active:after { 113 | content: "\2212"; 114 | } 115 | 116 | .help { 117 | padding: 0 18px; 118 | max-height: 0; 119 | overflow: hidden; 120 | transition: max-height 0.2s ease-out; 121 | 122 | background-color: var(--vscode-textBlockQuote-background); 123 | } 124 | -------------------------------------------------------------------------------- /resources/rtos.js: -------------------------------------------------------------------------------- 1 | // Get access to the VS Code API from within the webview context 2 | const vscode = acquireVsCodeApi(); 3 | 4 | // Just like a regular webpage we need to wait for the webview 5 | // DOM to load before we can reference any of the HTML elements 6 | // or toolkit components 7 | window.addEventListener('load', main); 8 | 9 | // Main function that gets executed once the webview DOM loads 10 | function main() { 11 | const refreshButton = document.getElementById('refresh-button'); 12 | if (refreshButton) { 13 | refreshButton.addEventListener('click', refreshClicked); 14 | } 15 | 16 | setVSCodeMessageListener(); 17 | 18 | setupFoldButtons(); 19 | setupHelpButton(); 20 | } 21 | 22 | function setupFoldButtons() { 23 | var coll = document.getElementsByClassName('collapse-button'); 24 | var i; 25 | 26 | for (i = 0; i < coll.length; i++) { 27 | coll[i].addEventListener('click', function () { 28 | this.classList.toggle('active'); 29 | var content = this.nextElementSibling; 30 | if (content.style.maxHeight) { 31 | content.style.maxHeight = null; 32 | } else { 33 | content.style.maxHeight = 'None'; 34 | } 35 | }); 36 | } 37 | } 38 | 39 | function setupHelpButton() { 40 | var coll = document.getElementsByClassName('help-button'); 41 | var i; 42 | 43 | for (i = 0; i < coll.length; i++) { 44 | coll[i].addEventListener('click', function () { 45 | this.classList.toggle('active'); 46 | var content = this.nextElementSibling; 47 | if (content.style.maxHeight) { 48 | content.style.maxHeight = null; 49 | } else { 50 | content.style.maxHeight = content.scrollHeight + 'px'; 51 | } 52 | }); 53 | } 54 | } 55 | 56 | function refreshClicked() { 57 | // Passes a message back to the extension context 58 | vscode.postMessage({ 59 | type: 'refresh', 60 | body: {} 61 | }); 62 | } 63 | 64 | // Sets up an event listener to listen for messages passed from the extension context 65 | // and executes code based on the message that is received 66 | function setVSCodeMessageListener() { 67 | window.addEventListener('message', (event) => { 68 | const command = event.data.command; 69 | const data = JSON.parse(event.data.payload); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /src/backend/backend.ts: -------------------------------------------------------------------------------- 1 | import { MINode } from './mi_parse'; 2 | import { DebugProtocol } from '@vscode/debugprotocol'; 3 | import { toStringDecHexOctBin } from '../common'; 4 | import { hexFormat } from '../frontend/utils'; 5 | 6 | export interface OurSourceBreakpoint extends DebugProtocol.SourceBreakpoint { 7 | file?: string; 8 | raw?: string; // Used for function name as well and old style address breakpoints 9 | // What we get from gdb below 10 | address?: string; 11 | number?: number; 12 | } 13 | 14 | export interface OurInstructionBreakpoint extends DebugProtocol.InstructionBreakpoint { 15 | address: number; 16 | number: number; 17 | } 18 | 19 | export interface OurDataBreakpoint extends DebugProtocol.DataBreakpoint { 20 | number?: number; 21 | } 22 | 23 | export interface Stack { 24 | level: number; 25 | address: string; 26 | function: string; 27 | fileName: string; 28 | file: string; 29 | line: number; 30 | } 31 | 32 | export interface Variable { 33 | name: string; 34 | valueStr: string; 35 | type: string; 36 | raw?: any; 37 | } 38 | 39 | export interface IBackend { 40 | start(cwd: string, init: string[]): Thenable; 41 | connect(target: string[]): Thenable; 42 | stop(); 43 | detach(); 44 | interrupt(arg: string): Thenable; 45 | continue(threadId: number): Thenable; 46 | next(threadId: number, instruction: boolean): Thenable; 47 | step(threadId: number, instruction: boolean): Thenable; 48 | stepOut(threadId: number): Thenable; 49 | addBreakPoint(breakpoint: OurSourceBreakpoint): Promise; 50 | removeBreakpoints(breakpoints: number[]): Promise; 51 | getStack(threadId: number, startLevel: number, maxLevels: number): Thenable; 52 | getStackVariables(thread: number, frame: number): Thenable; 53 | evalExpression(name: string, threadId: number, frameId: number): Thenable; 54 | isRunning(): boolean; 55 | changeVariable(name: string, rawValue: string): Thenable; 56 | examineMemory(from: number, to: number): Thenable; 57 | } 58 | 59 | export class VariableObject { 60 | public name: string; 61 | public curDisplayName: string; 62 | public exp: string; 63 | public numchild: number; 64 | public type: string; 65 | public value: string; 66 | public threadId: string; 67 | public frozen: boolean; 68 | public dynamic: boolean; 69 | public displayhint: string; 70 | public hasMore: boolean; 71 | public id: number; 72 | public fullExp: string; 73 | public parent: number; // Variable Reference 74 | public children: { [name: string]: string }; // Field-name to Gdb-variable map 75 | constructor(p: number, node: any) { 76 | this.parent = p; 77 | this.name = MINode.valueOf(node, 'name'); 78 | this.curDisplayName = this.name; 79 | this.exp = MINode.valueOf(node, 'exp'); 80 | this.numchild = parseInt(MINode.valueOf(node, 'numchild')); 81 | this.type = MINode.valueOf(node, 'type'); 82 | this.value = MINode.valueOf(node, 'value'); 83 | this.threadId = MINode.valueOf(node, 'thread-id'); 84 | this.frozen = !!MINode.valueOf(node, 'frozen'); 85 | this.dynamic = !!MINode.valueOf(node, 'dynamic'); 86 | this.displayhint = MINode.valueOf(node, 'displayhint'); 87 | this.children = {}; 88 | // TODO: use has_more when it's > 0 89 | this.hasMore = !!MINode.valueOf(node, 'has_more'); 90 | } 91 | 92 | public createToolTip(name: string, value: string): string { 93 | let ret = this.type; 94 | if (this.isCompound()) { 95 | return ret; 96 | } 97 | 98 | let val = 0; 99 | if ((/^0[xX][0-9A-Fa-f]+/.test(value)) || /^[-]?[0-9]+/.test(value)) { 100 | val = parseInt(value.toLowerCase()); 101 | 102 | ret += ' ' + name + ';\n'; 103 | ret += toStringDecHexOctBin(val); 104 | } 105 | return ret; 106 | } 107 | 108 | public applyChanges(node: MINode) { 109 | this.value = MINode.valueOf(node, 'value'); 110 | /* 111 | if (this.value === undefined) { 112 | this.value = def; 113 | } 114 | */ 115 | const typeChanged = MINode.valueOf(node, 'type_changed'); 116 | if (typeChanged === 'true') { 117 | this.type = MINode.valueOf(node, 'new_type') || this.type; 118 | } 119 | this.dynamic = !!MINode.valueOf(node, 'dynamic'); 120 | this.displayhint = MINode.valueOf(node, 'displayhint'); 121 | this.hasMore = !!MINode.valueOf(node, 'has_more'); 122 | } 123 | 124 | public isCompound(): boolean { 125 | return this.numchild > 0 126 | || this.value === '{...}' 127 | || (this.dynamic && (this.displayhint === 'array' || this.displayhint === 'map')); 128 | } 129 | 130 | public toProtocolVariable(newName?: string): DebugProtocol.Variable { 131 | const res: DebugProtocol.Variable = { 132 | name: newName || this.exp, 133 | evaluateName: this.fullExp || this.exp, 134 | value: (this.value === void 0) ? '' : this.value, 135 | type: this.type, 136 | presentationHint: { 137 | kind: this.displayhint 138 | }, 139 | variablesReference: this.id 140 | }; 141 | this.tryAddMemoryReference(res); 142 | this.curDisplayName = res.name; 143 | 144 | res.type = this.createToolTip(res.name, res.value); // This ends up becoming a tool-tip 145 | return res; 146 | } 147 | 148 | public toProtocolEvaluateResponseBody(): DebugProtocol.EvaluateResponse['body'] { 149 | const res: DebugProtocol.EvaluateResponse['body'] = { 150 | result: this.value, 151 | type: this.type, 152 | presentationHint: { 153 | kind: this.displayhint 154 | }, 155 | variablesReference: this.id 156 | }; 157 | this.tryAddMemoryReference(res); 158 | return res; 159 | } 160 | 161 | private tryAddMemoryReference(result: object): void { 162 | if ((this.numchild > 0 || this.type === 'void *') 163 | && this.value.startsWith('0x')) { 164 | result['memoryReference'] = hexFormat(parseInt(this.value)); 165 | } 166 | } 167 | } 168 | 169 | // from https://gist.github.com/justmoon/15511f92e5216fa2624b#gistcomment-1928632 170 | export interface MIError extends Error { 171 | readonly name: string; 172 | readonly message: string; 173 | readonly source: string; 174 | } 175 | 176 | export interface MIErrorConstructor { 177 | readonly prototype: MIError; 178 | new (message: string, source: string): MIError; 179 | } 180 | 181 | export const MIError: MIErrorConstructor = class MIError { 182 | public readonly name: string; 183 | public readonly message: string; 184 | public readonly source: string; 185 | public constructor(message: string, source: string) { 186 | Object.defineProperty(this, 'name', { 187 | get: () => this.constructor.name 188 | }); 189 | Object.defineProperty(this, 'message', { 190 | get: () => message 191 | }); 192 | Object.defineProperty(this, 'source', { 193 | get: () => source 194 | }); 195 | Error.captureStackTrace(this, this.constructor); 196 | } 197 | 198 | public toString() { 199 | return `${this.message} (from ${this.source})`; 200 | } 201 | } as any; 202 | Object.setPrototypeOf(MIError as any, Object.create(Error.prototype)); 203 | MIError.prototype.constructor = MIError; 204 | -------------------------------------------------------------------------------- /src/bmp.ts: -------------------------------------------------------------------------------- 1 | import { DebugProtocol } from '@vscode/debugprotocol'; 2 | import { ConfigurationArguments, GDBServerController, SWOConfigureEvent, genDownloadCommands, getGDBSWOInitCommands } from './common'; 3 | import * as os from 'os'; 4 | import { EventEmitter } from 'events'; 5 | 6 | export class BMPServerController extends EventEmitter implements GDBServerController { 7 | public readonly name: string = 'BMP'; 8 | public readonly portsNeeded: string[] = []; 9 | 10 | private args: ConfigurationArguments; 11 | private ports: { [name: string]: number }; 12 | 13 | constructor() { 14 | super(); 15 | } 16 | 17 | public setPorts(ports: { [name: string]: number }): void { 18 | this.ports = ports; 19 | } 20 | 21 | public setArguments(args: ConfigurationArguments): void { 22 | this.args = args; 23 | } 24 | 25 | public customRequest(command: string, response: DebugProtocol.Response, args: any): boolean { 26 | return false; 27 | } 28 | 29 | public initCommands(): string[] { 30 | const commands: string[] = [ 31 | `target-select extended-remote ${this.args.BMPGDBSerialPort}` 32 | ]; 33 | 34 | if (this.args.powerOverBMP === 'enable') { 35 | commands.push('interpreter-exec console "monitor tpwr enable"'); 36 | // sleep for 100 ms. MCU need some time to boot up after power up 37 | commands.push('interpreter-exec console "shell sleep 0.1"'); 38 | } else if (this.args.powerOverBMP === 'disable') { 39 | commands.push('interpreter-exec console "monitor tpwr disable"'); 40 | } else { 41 | // keep last power state (do nothing) 42 | } 43 | 44 | if (this.args.interface === 'jtag') { // TODO: handle ctag in when this server supports it 45 | commands.push('interpreter-exec console "monitor jtag_scan"'); 46 | } else { 47 | commands.push('interpreter-exec console "monitor swdp_scan"'); 48 | } 49 | 50 | commands.push( 51 | `interpreter-exec console "attach ${this.args.targetId}"`, 52 | 'interpreter-exec console "set mem inaccessible-by-default off"' 53 | ); 54 | 55 | return commands; 56 | } 57 | 58 | public launchCommands(): string[] { 59 | const commands = [ 60 | ...genDownloadCommands(this.args, []), 61 | 'interpreter-exec console "SoftwareReset 1"' 62 | ]; 63 | return commands; 64 | } 65 | 66 | public attachCommands(): string[] { 67 | const commands: string[] = []; 68 | return commands; 69 | } 70 | 71 | public resetCommands(): string[] { 72 | const commands: string[] = [ 73 | 'interpreter-exec console "SoftwareReset"' 74 | ]; 75 | return commands; 76 | } 77 | 78 | public swoAndRTTCommands(): string[] { 79 | const commands: string[] = []; 80 | if (this.args.swoConfig.enabled) { 81 | const swocommands = this.SWOConfigurationCommands(); 82 | commands.push(...swocommands); 83 | } 84 | return commands; 85 | } 86 | 87 | private SWOConfigurationCommands(): string[] { 88 | const commands = getGDBSWOInitCommands(this.args.swoConfig); 89 | const swoFrequency = this.args.swoConfig.swoFrequency; 90 | const encoding = this.args.swoConfig.swoEncoding === 'manchester' ? 1 : 2; 91 | 92 | if (this.args.swoConfig.source === 'probe') { 93 | commands.push(encoding === 2 ? `monitor traceswo ${swoFrequency}` : 'monitor traceswo'); 94 | } 95 | 96 | return commands.map((c) => `interpreter-exec console "${c}"`); 97 | } 98 | 99 | public serverExecutable(): string { 100 | return null; 101 | } 102 | 103 | public allocateRTTPorts(): Promise { 104 | return Promise.resolve(); 105 | } 106 | 107 | public serverArguments(): string[] { 108 | return []; 109 | } 110 | 111 | public initMatch(): RegExp { 112 | return null; 113 | } 114 | 115 | public serverLaunchStarted(): void {} 116 | public serverLaunchCompleted(): void { 117 | if (this.args.swoConfig.enabled) { 118 | if (this.args.swoConfig.source === 'probe') { 119 | this.emit('event', new SWOConfigureEvent({ 120 | type: 'usb', 121 | args: this.args, 122 | device: this.args.swoConfig.swoPath || 'Black Magic Probe', 123 | port: this.args.swoConfig.swoPort || 'Black Magic Trace Capture' 124 | })); 125 | } else { 126 | this.emit('event', new SWOConfigureEvent({ 127 | type: 'serial', 128 | args: this.args, 129 | device: this.args.swoConfig.source, 130 | baudRate: this.args.swoConfig.swoFrequency 131 | })); 132 | } 133 | } 134 | } 135 | 136 | public debuggerLaunchStarted(): void {} 137 | public debuggerLaunchCompleted(): void {} 138 | } 139 | -------------------------------------------------------------------------------- /src/dbgmsgs.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { HrTimer } from './common'; 3 | 4 | export class CortexDebugChannel { 5 | private static vscodeDebugChannel: vscode.OutputChannel; 6 | private static globalHrTimer = new HrTimer(); 7 | 8 | public static createDebugChanne() { 9 | if (!CortexDebugChannel.vscodeDebugChannel) { 10 | const options: object = { 11 | log: true 12 | }; 13 | // (options as any).loglevel = vscode.LogLevel.Trace; 14 | CortexDebugChannel.vscodeDebugChannel = vscode.window.createOutputChannel('Cortex-Debug'); 15 | CortexDebugChannel.vscodeDebugChannel.hide(); 16 | } 17 | } 18 | 19 | public static debugMessage(msg: string): void { 20 | if (CortexDebugChannel.vscodeDebugChannel) { 21 | const ts = CortexDebugChannel.globalHrTimer.createDateTimestamp(); 22 | CortexDebugChannel.vscodeDebugChannel.appendLine(ts + ' ' + msg); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/docgen.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | 3 | function handleObject(obj: any, prop: string, appliesTo, stream: fs.WriteStream) { 4 | const keys = Object.keys(obj).sort(); 5 | for (const key of keys) { 6 | const child = obj[key]; 7 | const chProp = prop + '
.' + key; 8 | const chType = getType(child); 9 | stream.write(`| ${chProp} | ${chType} | ${appliesTo} | ${child.description} |\n`); 10 | if (child.properties) { 11 | handleObject(child.properties, chProp, appliesTo, stream); 12 | } 13 | } 14 | } 15 | 16 | function getType(obj: any): string { 17 | const pipe = ' | '; 18 | if (Array.isArray(obj.type)) { 19 | return obj.type.join(pipe) as string; 20 | } 21 | if (obj.properties) { 22 | return 'object'; 23 | } 24 | if ((obj.type === 'array') && obj.items) { 25 | if (typeof obj.items === 'string') { 26 | return obj.items + '[]'; 27 | } else if (obj.items.properties) { 28 | return 'object[]'; 29 | } else if (obj.items.anyOf || obj.items.oneOf) { 30 | return newFunction(obj.items); 31 | } else if (obj.items.type) { 32 | return getType(obj.items) + '[]'; 33 | } 34 | } else if (obj.anyOf || obj.oneOf) { 35 | return newFunction(obj); 36 | } else if (obj.type) { 37 | return obj.type as string; 38 | } else { 39 | return '??'; 40 | } 41 | 42 | function newFunction(obj: any) { 43 | const ary = []; 44 | let isComplex = false; 45 | for (const item of (obj.anyOf || obj.oneOf)) { 46 | const tmp = getType(item); 47 | if (ary.findIndex((s) => s === tmp) === -1) { 48 | ary.push(getType(item)); 49 | } 50 | if (item.itemms || item.properties || item.anyOf || item.oneOf) { 51 | isComplex = true; 52 | } 53 | } 54 | if (isComplex) { 55 | return '{' + ary.join(pipe) + '}'; 56 | } 57 | return ary.join(pipe); 58 | } 59 | } 60 | 61 | function writeHeader(f: fs.WriteStream) { 62 | f.write('There are many `User/Workspace Settings` to control things globally. You can find these in the VSCode Settings UI. `launch.json`'); 63 | f.write(' can override some of those settings. There is a lot of functionality that is available via `Settings` and some may be useful in a'); 64 | f.write(' team environment and/or can be used across all cortex-debug sessions\n\n'); 65 | f.write('![](./images/cortex-debug-settings.png)\n\n'); 66 | f.write('The following attributes (properties) can be used in your `launch.json` to control various aspects of debugging.'); 67 | f.write(' Also `IntelliSense` is an invaluable aid while editing `launch.json`. With `IntelliSense`, you can hover over an attribute to get'); 68 | f.write(' more information and/or help you find attributes (just start typing a double-quote, use Tab key) and provide defaults/options.\n\n'); 69 | f.write('If the type is marked as `{...}` it means that it is a complex item can have multiple types. Possibly consult our Wiki\n'); 70 | } 71 | 72 | export function packageJSONtoMd(path: string, outPath: string) { 73 | let obj: any; 74 | try { 75 | const txt = fs.readFileSync(path); 76 | obj = JSON.parse(txt.toString()); 77 | } catch (e) { 78 | console.error(`Error: Could not open/read file ${path}`, e); 79 | return 1; 80 | } 81 | const dbgSections = obj?.contributes?.debuggers; 82 | if (!dbgSections) { 83 | console.error(`No "debuggers" found in file ${path}`); 84 | return 1; 85 | } 86 | for (const dbgSection of dbgSections) { 87 | const attrs = dbgSection.configurationAttributes; 88 | const attach = attrs?.attach?.properties; 89 | const launch = attrs?.launch?.properties; 90 | if (!attach) { 91 | console.error('"attach" properties not found'); 92 | return 1; 93 | } 94 | if (!launch) { 95 | console.error('"launch" properties not found'); 96 | return 1; 97 | } 98 | let attachProps = Object.keys(attach); 99 | let launchProps = Object.keys(launch); 100 | const common = {}; 101 | for (const prop of launchProps) { 102 | if (attach[prop]?.deprecated || launch[prop]?.deprecated) { 103 | delete launch[prop]; 104 | delete attach[prop]; 105 | continue; 106 | } 107 | if (attachProps.findIndex((str) => str === prop) !== -1) { 108 | common[prop] = launch[prop]; 109 | if (launch[prop].description !== attach[prop].description) { 110 | console.warn(`Warning: Description does not match for property ${prop} between attach and launch`); 111 | } 112 | delete launch[prop]; 113 | delete attach[prop]; 114 | } 115 | } 116 | attachProps = Object.keys(attach).sort(); 117 | launchProps = Object.keys(launch).sort(); 118 | const commonProps = Object.keys(common).sort(); 119 | const allProps = attachProps.concat(launchProps).concat(commonProps).sort(); 120 | // console.log('launch', launchProps); 121 | // console.log('attach', attachProps); 122 | // console.log('common', commonProps); 123 | try { 124 | const stream = fs.createWriteStream(outPath); 125 | writeHeader(stream); 126 | stream.write('| Attribute | Type | Launch/ Attach | Description |\n'); 127 | stream.write('| --------- | ---- | ---------------- | ----------- |\n'); 128 | for (const prop of allProps) { 129 | let obj = common[prop]; 130 | let appliesTo = 'Both'; 131 | if (!obj) { 132 | obj = launch[prop]; 133 | if (obj) { 134 | appliesTo = 'Launch'; 135 | } else { 136 | obj = attach[prop]; 137 | appliesTo = 'Attach'; 138 | } 139 | } 140 | const objType = getType(obj); 141 | stream.write(`| ${prop} | ${objType} | ${appliesTo} | ${obj.description} |\n`); 142 | if (obj.properties) { 143 | handleObject(obj.properties, prop, appliesTo, stream); 144 | } 145 | } 146 | } catch (e) { 147 | console.error(`Could not write to file ${outPath}`); 148 | } 149 | break; 150 | } 151 | } 152 | 153 | packageJSONtoMd('./package.json', './debug_attributes.md'); 154 | -------------------------------------------------------------------------------- /src/external.ts: -------------------------------------------------------------------------------- 1 | import { DebugProtocol } from '@vscode/debugprotocol'; 2 | import { ConfigurationArguments, GDBServerController, SWOConfigureEvent, calculatePortMask, genDownloadCommands } from './common'; 3 | import * as os from 'os'; 4 | import * as tmp from 'tmp'; 5 | import { EventEmitter } from 'events'; 6 | import * as ChildProcess from 'child_process'; 7 | 8 | export class ExternalServerController extends EventEmitter implements GDBServerController { 9 | public readonly name: string = 'External'; 10 | public readonly portsNeeded: string[] = []; 11 | private swoPath: string; 12 | 13 | private args: ConfigurationArguments; 14 | private ports: { [name: string]: number }; 15 | 16 | constructor() { 17 | super(); 18 | this.swoPath = tmp.tmpNameSync(); 19 | } 20 | 21 | public setPorts(ports: { [name: string]: number }): void { 22 | this.ports = ports; 23 | } 24 | 25 | public setArguments(args: ConfigurationArguments): void { 26 | this.args = args; 27 | if (this.args.swoConfig.enabled) { 28 | if (os.platform() === 'win32') { 29 | this.swoPath = this.swoPath.replace(/\\/g, '/'); 30 | } 31 | } 32 | } 33 | 34 | public customRequest(command: string, response: DebugProtocol.Response, args: any): boolean { 35 | return false; 36 | } 37 | 38 | public initCommands(): string[] { 39 | const target = this.args.gdbTarget; 40 | return [ 41 | `target-select extended-remote ${target}` 42 | ]; 43 | } 44 | 45 | public launchCommands(): string[] { 46 | const commands = [ 47 | ...genDownloadCommands(this.args, ['interpreter-exec console "monitor reset halt"']), 48 | 'interpreter-exec console "monitor reset halt"' 49 | ]; 50 | 51 | return commands; 52 | } 53 | 54 | public attachCommands(): string[] { 55 | const commands = [ 56 | 'interpreter-exec console "monitor halt"' 57 | ]; 58 | 59 | return commands; 60 | } 61 | 62 | public swoAndRTTCommands(): string[] { 63 | return []; 64 | } 65 | 66 | public resetCommands(): string[] { 67 | const commands: string[] = [ 68 | 'interpreter-exec console "monitor reset halt"' 69 | ]; 70 | 71 | return commands; 72 | } 73 | 74 | public serverExecutable(): string { 75 | return null; 76 | } 77 | 78 | public allocateRTTPorts(): Promise { 79 | return Promise.resolve(); 80 | } 81 | 82 | public serverArguments(): string[] { 83 | return []; 84 | } 85 | 86 | public initMatch(): RegExp { 87 | return null; 88 | } 89 | 90 | public serverLaunchStarted(): void { 91 | if (this.args.swoConfig.enabled && this.args.swoConfig.source === 'probe' && os.platform() !== 'win32') { 92 | const mkfifoReturn = ChildProcess.spawnSync('mkfifo', [this.swoPath]); 93 | this.emit('event', new SWOConfigureEvent({ 94 | type: 'fifo', 95 | args: this.args, 96 | path: this.swoPath 97 | })); 98 | } 99 | } 100 | 101 | public serverLaunchCompleted(): void { 102 | if (this.args.swoConfig.enabled) { 103 | if (this.args.swoConfig.source === 'probe' && os.platform() === 'win32') { 104 | this.emit('event', new SWOConfigureEvent({ 105 | type: 'file', 106 | args: this.args, 107 | path: this.swoPath 108 | })); 109 | } else if (this.args.swoConfig.source === 'socket') { 110 | this.emit('event', new SWOConfigureEvent({ 111 | type: 'socket', 112 | args: this.args, 113 | port: this.args.swoConfig.swoPort 114 | })); 115 | } else if (this.args.swoConfig.source === 'file') { 116 | this.emit('event', new SWOConfigureEvent({ 117 | type: 'file', 118 | args: this.args, 119 | path: this.args.swoConfig.swoPath 120 | })); 121 | } else if (this.args.swoConfig.source === 'serial') { 122 | this.emit('event', new SWOConfigureEvent({ 123 | type: 'serial', 124 | args: this.args, 125 | device: this.args.swoConfig.swoPath, 126 | baudRate: this.args.swoConfig.swoFrequency 127 | })); 128 | } 129 | } 130 | } 131 | 132 | public debuggerLaunchStarted(): void {} 133 | public debuggerLaunchCompleted(): void {} 134 | } 135 | -------------------------------------------------------------------------------- /src/frontend/addrranges.ts: -------------------------------------------------------------------------------- 1 | export class AddrRange { 2 | constructor(public base: number, public length: number) { 3 | } 4 | 5 | /** return next address after this addr. range */ 6 | public nxtAddr() { 7 | return this.base + this.length; 8 | } 9 | 10 | /** return last address in this range */ 11 | public endAddr() { 12 | return this.nxtAddr() - 1; 13 | } 14 | } 15 | export class AddressRangesUtils { 16 | constructor() { 17 | } 18 | 19 | /** 20 | * Returns a set of address ranges that have 0 < length <= maxBytes 21 | * 22 | * @param ranges array of ranges to check an split 23 | * @param maxBytes limit of each range 24 | * @param dbgMsg To output debug messages -- name of address space 25 | * @param dbgLen To output debug messages -- total length of addr space 26 | */ 27 | public static splitIntoChunks(ranges: AddrRange[], maxBytes: number, dbgMsg: string = '', dbgLen: number = 0): AddrRange[] { 28 | const newRanges = new Array(); 29 | for (const r of ranges) { 30 | while (r.length > maxBytes) { 31 | newRanges.push(new AddrRange(r.base, maxBytes)); 32 | r.base += maxBytes; 33 | r.length -= maxBytes; 34 | } 35 | if (r.length > 0) { // Watch out, can be negative 36 | newRanges.push(r); 37 | } 38 | } 39 | const logIt = false; 40 | if (newRanges.length && logIt) { 41 | AddressRangesUtils.consoleLog(dbgMsg, newRanges[0].base, dbgLen, newRanges); 42 | } 43 | return newRanges; 44 | } 45 | 46 | public static consoleLog(prefix: string, base: number, len: number, ranges: AddrRange[]): void { 47 | console.log(prefix + ` base=0x${base.toString(16)}, totalLen=${len}, #ranges=${ranges.length}\n`); 48 | let bc = 0; 49 | for (const range of ranges) { 50 | bc += range.length; 51 | console.log(`**** 0x${range.base.toString(16)}, len=${range.length}, cum-bytes=${bc}\n`); 52 | } 53 | const diff = len - bc; 54 | if ((bc > 0) && (len > 0)) { 55 | const percent = (diff / len) * 100; 56 | console.log(prefix + ` totalLen=${len}, savings=${diff} ${percent.toFixed(2)}%`); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/frontend/ansi-helpers.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @stylistic/no-multi-spaces */ 2 | export const ESC = '\x1b'; // ASCII escape character 3 | export const CSI = ESC + '['; // control sequence introducer 4 | export const BOLD = CSI + '1m'; 5 | export const RESET = CSI + '0m'; 6 | export const BR_MAGENTA_FG = CSI + '95m'; // Bright magenta foreground 7 | export const BR_GREEN_FG = CSI + '92m'; // Bright green foreground 8 | /* eslint-enable */ 9 | 10 | export function greenFormat(msg) { 11 | return BR_GREEN_FG + msg + RESET; 12 | } 13 | 14 | export function magentaFormat(msg) { 15 | return BR_MAGENTA_FG + msg + RESET; 16 | } 17 | -------------------------------------------------------------------------------- /src/frontend/cortex_debug_session.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'console'; 2 | import * as vscode from 'vscode'; 3 | import { ConfigurationArguments, ChainedConfig } from '../common'; 4 | import { RTTCore, SWOCore } from './swo/core'; 5 | import { SWORTTSource } from './swo/sources/common'; 6 | import { SocketRTTSource } from './swo/sources/socket'; 7 | 8 | export class CDebugSession { 9 | public swo: SWOCore = null; 10 | public rtt: RTTCore = null; 11 | public swoSource: SWORTTSource = null; 12 | public rttPortMap: { [channel: number]: SocketRTTSource } = {}; 13 | // Status can be 'none' before the session actually starts but this object 14 | // may have been created before that actually happens due to SWO, RTT, chained 15 | // launches, etc 16 | public status: 'started' | 'stopped' | 'running' | 'exited' | 'none' = 'none'; 17 | 18 | protected parent: CDebugSession = null; 19 | protected children: CDebugSession[] = []; 20 | private static ROOT = new CDebugSession(null, null); // Dummy node for all sessions trees 21 | public static CurrentSessions: CDebugSession[] = []; // This may stuff that never fully got created 22 | public usedPorts: Set = new Set(); 23 | 24 | constructor(public session: vscode.DebugSession, public config: ConfigurationArguments | vscode.DebugConfiguration) { 25 | if (session) { 26 | CDebugSession.CurrentSessions.push(this); 27 | } 28 | } 29 | 30 | public getRoot(): CDebugSession { 31 | return this.parent && this.parent.parent ? this.parent.getRoot() : this; 32 | } 33 | 34 | public hasChildren(): boolean { 35 | return this.children.length > 0; 36 | } 37 | 38 | public moveToRoot() { 39 | if (this.parent) { 40 | this.remove(); 41 | CDebugSession.ROOT.add(this); 42 | } 43 | } 44 | 45 | public broadcastDFS(cb: (s: CDebugSession) => void, fromRoot: boolean = true) { 46 | const root = fromRoot ? this.getRoot() : this; 47 | root._broadcastDFS(cb); 48 | } 49 | 50 | protected _broadcastDFS(cb: (s: CDebugSession) => void) { 51 | for (const child of this.children) { 52 | child._broadcastDFS(cb); 53 | } 54 | cb(this); 55 | } 56 | 57 | private remove() { 58 | this.parent.removeChild(this); 59 | } 60 | 61 | public add(child: CDebugSession) { 62 | assert(!child.parent, 'child already has a parent?'); 63 | if (this.children.find((x) => x === child)) { 64 | assert(false, 'child already exists'); 65 | } else { 66 | this.children.push(child); 67 | child.parent = this; 68 | } 69 | } 70 | 71 | private removeChild(child: CDebugSession) { 72 | this.children = this.children.filter((x) => x !== child); 73 | child.parent = null; 74 | } 75 | 76 | public stopAll() { 77 | this.broadcastDFS((arg) => { 78 | vscode.debug.stopDebugging(arg.session); 79 | }); 80 | } 81 | 82 | public static RemoveSession(session: vscode.DebugSession) { 83 | const s = CDebugSession.FindSession(session); 84 | if (s) { 85 | s.status = 'exited'; 86 | s.remove(); 87 | CDebugSession.CurrentSessions = CDebugSession.CurrentSessions.filter((s) => s.session.id !== session.id); 88 | } else { 89 | console.error(`Where did session ${session.id} go?`); 90 | } 91 | } 92 | 93 | public static FindSession(session: vscode.DebugSession) { 94 | return CDebugSession.FindSessionById(session.id); 95 | } 96 | 97 | public static FindSessionById(id: string) { 98 | const ret = CDebugSession.CurrentSessions.find((x) => x.session.id === id); 99 | return ret; 100 | } 101 | 102 | public static GetSession(session: vscode.DebugSession, config?: ConfigurationArguments): CDebugSession { 103 | const prev = CDebugSession.FindSessionById(session.id); 104 | if (prev) { 105 | prev.config = config || prev.config; 106 | return prev; 107 | } 108 | return new CDebugSession(session, config || session.configuration); 109 | } 110 | 111 | // Call this method after session actually started. It inserts new session into the session tree 112 | public static NewSessionStarted(session: vscode.DebugSession): CDebugSession { 113 | const newSession = CDebugSession.GetSession(session); // May have already in the global list 114 | newSession.status = 'started'; 115 | if (session.parentSession && (session.parentSession.type === 'cortex-debug')) { 116 | const parent = CDebugSession.FindSession(session.parentSession); 117 | if (!parent) { 118 | vscode.window.showErrorMessage( 119 | `Internal Error: Have parent for new session, Parent = ${session.parentSession.name} but can't find it`); 120 | } else { 121 | parent.add(newSession); // Insert into tree 122 | } 123 | } else { 124 | CDebugSession.ROOT.add(newSession); 125 | } 126 | return newSession; 127 | } 128 | 129 | public static getAllUsedPorts(): number[] { 130 | const ports = new Set(); 131 | for (const s of CDebugSession.CurrentSessions) { 132 | if ((s.status === 'started') || (s.status === 'stopped') || (s.status === 'running')) { 133 | for (const p of s.usedPorts.values()) { 134 | ports.add(p); 135 | } 136 | } 137 | } 138 | return Array.from(ports.values()); 139 | } 140 | 141 | public addUsedPorts(ports: number[]) { 142 | for (const p of ports) { 143 | this.usedPorts.add(p); 144 | } 145 | } 146 | } 147 | 148 | export class CDebugChainedSessionItem { 149 | public static SessionsStack: CDebugChainedSessionItem[] = []; 150 | constructor(public parent: CDebugSession, public config: ChainedConfig, public options: vscode.DebugSessionOptions) { 151 | CDebugChainedSessionItem.SessionsStack.push(this); 152 | } 153 | 154 | public static FindByName(name: string): CDebugChainedSessionItem { 155 | return this.SessionsStack.find((x) => x.config.name === name); 156 | } 157 | 158 | public static RemoveItem(item: CDebugChainedSessionItem) { 159 | CDebugChainedSessionItem.SessionsStack = CDebugChainedSessionItem.SessionsStack.filter((x) => x !== item); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/frontend/memreadutils.ts: -------------------------------------------------------------------------------- 1 | import { rawListeners } from 'process'; 2 | import * as vscode from 'vscode'; 3 | import { AddrRange, AddressRangesUtils } from './addrranges'; 4 | 5 | /** Has utility functions to read memory in chunks into a storage space */ 6 | export class MemReadUtils { 7 | /** 8 | * Make one or more memory reads and update values. For the caller, it should look like a single 9 | * memory read but, if one read fails, all reads are considered as failed. 10 | * 11 | * @param startAddr The start address of the memory region. Everything else is relative to `startAddr` 12 | * @param specs The chunks of memory to read and and update. Addresses should be >= `startAddr`, Can have gaps, overlaps, etc. 13 | * @param storeTo This is where read-results go. The first element represents item at `startAddr` 14 | */ 15 | public static readMemoryChunks( 16 | session: vscode.DebugSession, startAddr: number, specs: AddrRange[], storeTo: number[]): Promise { 17 | const promises = specs.map((r) => { 18 | return new Promise((resolve, reject) => { 19 | const addr = '0x' + r.base.toString(16); 20 | session.customRequest('read-memory', { address: addr, length: r.length }).then((data) => { 21 | let dst = r.base - startAddr; 22 | const bytes: number[] = data.bytes; 23 | for (const byte of bytes) { 24 | storeTo[dst++] = byte; 25 | } 26 | resolve(true); 27 | }, (e) => { 28 | let dst = r.base - startAddr; 29 | // tslint:disable-next-line: prefer-for-of 30 | for (let ix = 0; ix < r.length; ix++) { 31 | storeTo[dst++] = 0xff; 32 | } 33 | reject(e); 34 | }); 35 | }); 36 | }); 37 | 38 | return new Promise(async (resolve, reject) => { 39 | const results = await Promise.all(promises.map((p) => p.catch((e) => {}))); 40 | const errs: string[] = []; 41 | results.map((e) => { 42 | if (e instanceof Error) { 43 | errs.push(e.message); 44 | } 45 | }); 46 | if (errs.length !== 0) { 47 | reject(new Error(errs.join('\n'))); 48 | } else { 49 | resolve(true); 50 | } 51 | }); 52 | } 53 | 54 | public static readMemory( 55 | session: vscode.DebugSession, startAddr: number, length: number, storeTo: number[]): Promise { 56 | const maxChunk = (4 * 1024); 57 | const ranges = AddressRangesUtils.splitIntoChunks([new AddrRange(startAddr, length)], maxChunk); 58 | return MemReadUtils.readMemoryChunks(session, startAddr, ranges, storeTo); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/frontend/rtos/DEPRECATED.txt: -------------------------------------------------------------------------------- 1 | The source code in this directory is no deprecated. The RTOS Views have 2 | been moved to https://github.com/mcu-debug/rtos-views 3 | -------------------------------------------------------------------------------- /src/frontend/swo/advanced-decoder.ts: -------------------------------------------------------------------------------- 1 | import { SWODecoderConfig } from './common'; 2 | 3 | export interface SWOAdvancedDecoderConfig extends SWODecoderConfig { 4 | decoder: string; // Path to decoder JS file 5 | config: any; 6 | ports: number[]; // List of ITM/RTT ports 7 | } 8 | 9 | export interface AdvancedDecoder { 10 | /** 11 | * @param config: Contains the swo configuration from launch.json. Do not modify this object 12 | * @param outputData: function to call to output data to the OUTPUT Window 13 | * @param graphData: Function that emits a message to the grapher. Not sure how this works 14 | * 15 | * I am documenting what I know. Please make a PR with edits if you know more. Do not use 16 | * the parameters outputData or graphData until after init is ready. If you wish to do any debug 17 | * prints, use console.log(...) 18 | * 19 | * Note that typeName() and outputLabel() are used to create a name for the OUTPUT panel. So, you 20 | * can't use outputData/graphData in those methods either. 21 | */ 22 | init( 23 | config: SWOAdvancedDecoderConfig, 24 | outputData: (output: string, timestamp?: boolean) => void, 25 | graphData: (data: number, id: string) => void 26 | ): void; 27 | 28 | typeName(): string; // Used to create the OUTPUT Panel name 29 | outputLabel(): string; // Used to create the OUTPUT Panel name 30 | 31 | /** 32 | * 33 | * @param port: the ITM Port 34 | * @param data: Data just received 35 | */ 36 | softwareEvent(port: number, data: Buffer): void; 37 | 38 | /** 39 | * SWV Sync packet received. Probably can be ignored 40 | */ 41 | synchronized(): void; 42 | /** 43 | * SWV Sync lost. Probably can be ignored 44 | */ 45 | lostSynchronization(): void; 46 | 47 | /** 48 | * Do any cleanup you want to. Again do not use any functions passed to init from here 49 | * because they are in the process of being destroyed. Even if it works, we cannot guarantee 50 | * that in the future 51 | */ 52 | dispose?(): void; 53 | } 54 | -------------------------------------------------------------------------------- /src/frontend/swo/common.ts: -------------------------------------------------------------------------------- 1 | import { TextEncoding } from '../../common'; 2 | 3 | export interface SWODecoderConfig { 4 | type: string; 5 | } 6 | 7 | export interface SWOBasicDecoderConfig extends SWODecoderConfig { 8 | port: number; 9 | logfile: string; 10 | } 11 | 12 | export interface SWOConsoleDecoderConfig extends SWOBasicDecoderConfig { 13 | label: string; 14 | encoding: TextEncoding; 15 | showOnStartup: boolean; 16 | timestamp: boolean; 17 | } 18 | 19 | export interface SWOBinaryDecoderConfig extends SWOBasicDecoderConfig { 20 | encoding: string; 21 | scale: number; 22 | label: string; 23 | } 24 | 25 | export interface SWOGraphDecoderConfig extends SWOBasicDecoderConfig { 26 | encoding: string; 27 | scale: number; 28 | graphId: string; 29 | } 30 | 31 | export interface GraphConfiguration { 32 | type: string; 33 | label: string; 34 | } 35 | 36 | export interface TimeseriesGraphConfiguration extends GraphConfiguration { 37 | minimum: number; 38 | maximum: number; 39 | timespan: number; 40 | plots: { 41 | graphId: string; 42 | label: string; 43 | color: string; 44 | }[]; 45 | } 46 | 47 | export interface XYGraphConfiguration extends GraphConfiguration { 48 | xPort: number; 49 | yPort: number; 50 | xMinimum: number; 51 | xMaximum: number; 52 | yMinimum: number; 53 | yMaximum: number; 54 | initialX: number; 55 | initialY: number; 56 | timespan: number; 57 | xGraphId: string; 58 | yGraphId: string; 59 | } 60 | 61 | export interface GrapherMessage { 62 | timestamp?: number; 63 | type: 'configure' | 'status' | 'data' | 'program-counter' | 'init'; 64 | } 65 | 66 | export interface GrapherStatusMessage extends GrapherMessage { 67 | status: 'stopped' | 'terminated' | 'continued'; 68 | } 69 | 70 | export interface GrapherDataMessage extends GrapherMessage { 71 | id: string; 72 | data: number; 73 | } 74 | 75 | export interface GrapherProgramCounterMessage extends GrapherMessage { 76 | function: string; 77 | counter: number; 78 | } 79 | 80 | export interface GrapherConfigurationMessage extends GrapherMessage { 81 | graphs: [GraphConfiguration]; 82 | status: 'stopped' | 'terminated' | 'continued'; 83 | } 84 | 85 | export enum PacketType { 86 | HARDWARE = 1, 87 | SOFTWARE, 88 | TIMESTAMP 89 | } 90 | 91 | export enum TimestampType { 92 | CURRENT, 93 | DELAYED, 94 | EVENT_DELAYED, 95 | EVENT_TIME_DELAYED 96 | } 97 | 98 | export interface TimestampPacket { 99 | type: TimestampType; 100 | timestamp: number; 101 | } 102 | 103 | export interface Packet { 104 | type: PacketType; 105 | port: number; 106 | size: number; 107 | data: Buffer; 108 | } 109 | -------------------------------------------------------------------------------- /src/frontend/swo/decoders/advanced.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { SWORTTDecoder } from './common'; 3 | import { GrapherDataMessage } from '../common'; 4 | import { SWOAdvancedDecoderConfig, AdvancedDecoder } from '../advanced-decoder'; 5 | import { EventEmitter } from 'events'; 6 | import { Packet } from '../common'; 7 | import { CortexDebugChannel } from '../../../dbgmsgs'; 8 | import { HrTimer } from '../../../common'; 9 | 10 | declare function __webpack_require__(); 11 | declare const __non_webpack_require__: NodeRequire; 12 | const dynamicRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require; 13 | 14 | export class SWORTTAdvancedProcessor extends EventEmitter implements SWORTTDecoder { 15 | private output: vscode.OutputChannel; 16 | public readonly format: string = 'advanced'; 17 | private ports: number[]; 18 | private decoder: AdvancedDecoder; 19 | private timer = new HrTimer(); 20 | private static outputPanels = new Map(); 21 | 22 | constructor(config: SWOAdvancedDecoderConfig) { 23 | super(); 24 | this.ports = []; 25 | 26 | const decoderPath = config.decoder; 27 | const resolved = dynamicRequire.resolve(decoderPath); 28 | if (dynamicRequire.cache[resolved]) { delete dynamicRequire.cache[resolved]; } // Force reload 29 | 30 | const decoderModule = dynamicRequire(decoderPath); 31 | 32 | if (decoderModule && decoderModule.default) { 33 | const decoderClass = decoderModule.default; 34 | this.ports = config.ports; 35 | 36 | try { 37 | this.decoder = new decoderClass(); 38 | } catch (e) { 39 | throw new Error(`Error instantiating decoder class: ${e.toString()}`); 40 | } 41 | try { 42 | this.decoder.init(config, this.displayOutput.bind(this), this.graphData.bind(this)); 43 | const name = `SWO/RTT: ${this.decoder.outputLabel() || ''} [type: ${this.decoder.typeName()}]`; 44 | let panel = SWORTTAdvancedProcessor.outputPanels.get(name); 45 | if (!panel) { 46 | panel = vscode.window.createOutputChannel(name); 47 | SWORTTAdvancedProcessor.outputPanels.set(name, panel); 48 | } else { 49 | panel.clear(); 50 | } 51 | this.output = panel; 52 | } catch (e) { 53 | throw new Error(`Error initializing decoder class. Potential issues with outputLabel(), typeName() or init(): ${e.toString()}`); 54 | } 55 | } else { 56 | throw new Error(`Unable to load decoder class from: ${config.decoder}`); 57 | } 58 | } 59 | 60 | public softwareEvent(packet: Packet) { 61 | if (this.ports.indexOf(packet.port) !== -1) { 62 | if (this.decoder) { 63 | try { 64 | this.decoder.softwareEvent(packet.port, packet.data); 65 | } catch (e) { 66 | CortexDebugChannel.debugMessage('Error: in softwareEvent() for decoder ' + e.toString()); 67 | } 68 | } 69 | } 70 | } 71 | 72 | public hardwareEvent(event: Packet) {} 73 | 74 | public synchronized() { 75 | try { 76 | this.decoder?.synchronized(); 77 | } catch (e) { 78 | CortexDebugChannel.debugMessage('Error: in synchronized() for decoder ' + e.toString()); 79 | } 80 | } 81 | 82 | public lostSynchronization() { 83 | try { 84 | this.decoder?.lostSynchronization(); 85 | } catch (e) { 86 | CortexDebugChannel.debugMessage('Error: in lostSynchronization() for decoder ' + e.toString()); 87 | } 88 | } 89 | 90 | public displayOutput(output: string, timestamp: boolean = false) { 91 | if (this.output) { 92 | if (timestamp) { 93 | output = this.timer.createDateTimestamp() + ' ' + output; 94 | } 95 | this.output.append(output); 96 | } else { 97 | CortexDebugChannel.debugMessage(`Error: displayOutput(${output}) called before decoder was fully initialized`); 98 | } 99 | } 100 | 101 | public graphData(data: number, id: string) { 102 | const message: GrapherDataMessage = { type: 'data', data: data, id: id }; 103 | this.emit('message', message); 104 | } 105 | 106 | public dispose() { 107 | try { 108 | // We are recycling these now. So, do not dispose() 109 | // this.output.dispose(); 110 | } finally { 111 | this.output = undefined; 112 | this.close(); 113 | } 114 | } 115 | 116 | public close() { 117 | if (this.decoder?.dispose) { 118 | try { 119 | this.decoder.dispose(); 120 | } catch (e) { 121 | CortexDebugChannel.debugMessage('Error: in dispose() for decoder ' + e.toString()); 122 | } 123 | } 124 | this.decoder = undefined; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/frontend/swo/decoders/binary.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as fs from 'fs'; 3 | import { SWORTTDecoder } from './common'; 4 | import { SWOBinaryDecoderConfig } from '../common'; 5 | import { decoders as DECODER_MAP } from './utils'; 6 | import { Packet } from '../common'; 7 | import { IPtyTerminalOptions, PtyTerminal } from '../../pty'; 8 | import { HrTimer, TerminalInputMode } from '../../../common'; 9 | 10 | function parseEncoded(buffer: Buffer, encoding: string) { 11 | return DECODER_MAP[encoding] ? DECODER_MAP[encoding](buffer) : DECODER_MAP.unsigned(buffer); 12 | } 13 | 14 | export class SWOBinaryProcessor implements SWORTTDecoder { 15 | private output: vscode.OutputChannel; 16 | public readonly format: string = 'binary'; 17 | private port: number; 18 | private scale: number; 19 | private encoding: string; 20 | private useTerminal = true; 21 | private ptyTerm: PtyTerminal = null; 22 | private hrTimer: HrTimer = new HrTimer(); 23 | private logFd: number = -1; 24 | private logfile: string; 25 | 26 | constructor(config: SWOBinaryDecoderConfig) { 27 | this.port = config.port; 28 | this.scale = config.scale || 1; 29 | this.encoding = (config.encoding || 'unsigned').replace('.', '_'); 30 | this.useTerminal = 'useTerminal' in config ? (config as any).useTerminal : true; // TODO: Remove 31 | 32 | if (this.useTerminal) { 33 | this.createVSCodeTerminal(config); 34 | } else { 35 | this.createVSCodeChannel(config); 36 | } 37 | if (config.logfile) { 38 | this.logfile = config.logfile; 39 | try { 40 | this.logFd = fs.openSync(config.logfile, 'w'); 41 | } catch (e) { 42 | const msg = `Could not open file ${config.logfile} for writing. ${e.toString()}`; 43 | vscode.window.showErrorMessage(msg); 44 | } 45 | } 46 | } 47 | 48 | private createVSCodeTerminal(config: SWOBinaryDecoderConfig) { 49 | const options: IPtyTerminalOptions = { 50 | name: this.createName(config), 51 | prompt: '', 52 | inputMode: TerminalInputMode.DISABLED 53 | }; 54 | this.ptyTerm = PtyTerminal.findExisting(options.name); 55 | if (this.ptyTerm) { 56 | this.ptyTerm.clearTerminalBuffer(); 57 | } else { 58 | this.ptyTerm = new PtyTerminal(options); 59 | this.ptyTerm.terminal.show(); 60 | } 61 | } 62 | 63 | private createVSCodeChannel(config: SWOBinaryDecoderConfig) { 64 | const chName = this.createName(config); 65 | this.output = vscode.window.createOutputChannel(chName); 66 | } 67 | 68 | private createName(config: SWOBinaryDecoderConfig) { 69 | return `SWO:${config.label || ''}[port:${this.port}, enc:${this.encoding}]`; 70 | } 71 | 72 | public softwareEvent(packet: Packet) { 73 | if (packet.port !== this.port) { return; } 74 | 75 | const date = new Date(); 76 | 77 | const hexvalue = packet.data.toString('hex'); 78 | const decodedValue = parseEncoded(packet.data, this.encoding); 79 | const scaledValue = decodedValue * this.scale; 80 | const timestamp = this.hrTimer.createDateTimestamp(); 81 | 82 | const str = `${timestamp} ${hexvalue} - ${decodedValue} - ${scaledValue}`; 83 | if (this.useTerminal) { 84 | this.ptyTerm.write(str + '\n'); 85 | } else { 86 | this.output.appendLine(str); 87 | } 88 | 89 | if (this.logFd >= 0) { 90 | try { 91 | fs.writeSync(this.logFd, packet.data); 92 | } catch (e) { 93 | const msg = `Could not write to file ${this.logfile} for writing. ${e.toString()}`; 94 | vscode.window.showErrorMessage(msg); 95 | try { 96 | fs.closeSync(this.logFd); 97 | } catch (closeErr) { 98 | console.error('decoder.logCloseError', closeErr); 99 | } 100 | this.logFd = -1; 101 | } 102 | } 103 | } 104 | 105 | public hardwareEvent(event: Packet) {} 106 | public synchronized() {} 107 | public lostSynchronization() {} 108 | 109 | public dispose() { 110 | if (this.output) { 111 | this.output.dispose(); 112 | this.output = null; 113 | } 114 | if (this.ptyTerm) { 115 | this.ptyTerm.dispose(); 116 | this.ptyTerm = null; 117 | } 118 | this.close(); 119 | } 120 | 121 | public close() { 122 | if (this.logFd >= 0) { 123 | fs.closeSync(this.logFd); 124 | this.logFd = -1; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/frontend/swo/decoders/common.ts: -------------------------------------------------------------------------------- 1 | import { Packet } from '../common'; 2 | 3 | export interface SWORTTDecoder { 4 | format: string; 5 | 6 | softwareEvent(buffer: Packet): void; 7 | hardwareEvent(event: Packet): void; 8 | synchronized(): void; 9 | lostSynchronization(): void; 10 | close(): void; 11 | 12 | dispose(): void; 13 | } 14 | -------------------------------------------------------------------------------- /src/frontend/swo/decoders/console.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as fs from 'fs'; 3 | 4 | import { SWORTTDecoder } from './common'; 5 | import { SWOConsoleDecoderConfig } from '../common'; 6 | import { Packet } from '../common'; 7 | import { IPtyTerminalOptions, PtyTerminal } from '../../pty'; 8 | import { HrTimer, TerminalInputMode, TextEncoding } from '../../../common'; 9 | 10 | export class SWOConsoleProcessor implements SWORTTDecoder { 11 | private positionCount: number; 12 | private output: vscode.OutputChannel; 13 | private position: number = 0; 14 | private timeout: any = null; 15 | public readonly format: string = 'console'; 16 | private port: number; 17 | private encoding: TextEncoding; 18 | private showOutputTimer: NodeJS.Timeout = null; 19 | private useTerminal = true; 20 | private ptyTerm: PtyTerminal = null; 21 | private timestamp: boolean = false; 22 | private hrTimer: HrTimer = new HrTimer(); 23 | private logFd: number = -1; 24 | private logfile: string; 25 | 26 | constructor(config: SWOConsoleDecoderConfig) { 27 | this.port = config.port; 28 | this.encoding = config.encoding || TextEncoding.UTF8; 29 | this.timestamp = !!config.timestamp; 30 | this.useTerminal = 'useTerminal' in config ? (config as any).useTerminal : true; // TODO: Remove 31 | if (this.useTerminal) { 32 | this.createVSCodeTerminal(config); 33 | } else { 34 | this.createVSCodeChanne(config); 35 | } 36 | if (config.logfile) { 37 | this.logfile = config.logfile; 38 | try { 39 | this.logFd = fs.openSync(config.logfile, 'w'); 40 | } catch (e) { 41 | const msg = `Could not open file ${config.logfile} for writing. ${e.toString()}`; 42 | vscode.window.showErrorMessage(msg); 43 | } 44 | } 45 | } 46 | 47 | private createName(config: SWOConsoleDecoderConfig) { 48 | // Try to keep it small while still having enough info 49 | const basic = `SWO:${config.label || ''}[port:${this.port}`; 50 | 51 | if (this.useTerminal) { 52 | return basic + '] console'; 53 | } else { 54 | return basic + ', type: console]'; 55 | } 56 | } 57 | 58 | private createVSCodeTerminal(config: SWOConsoleDecoderConfig) { 59 | const options: IPtyTerminalOptions = { 60 | name: this.createName(config), 61 | prompt: '', 62 | inputMode: TerminalInputMode.DISABLED 63 | }; 64 | this.ptyTerm = PtyTerminal.findExisting(options.name); 65 | if (this.ptyTerm) { 66 | this.ptyTerm.clearTerminalBuffer(); 67 | } else { 68 | this.ptyTerm = new PtyTerminal(options); 69 | this.ptyTerm.on('close', () => { 70 | this.ptyTerm = null; 71 | }); 72 | if (config.showOnStartup) { 73 | this.ptyTerm.terminal.show(); 74 | } 75 | } 76 | } 77 | 78 | private createVSCodeChanne(config: SWOConsoleDecoderConfig) { 79 | this.output = vscode.window.createOutputChannel(this.createName(config)); 80 | 81 | // A work-around. A blank display will appear if the output is shown immediately 82 | if (config.showOnStartup) { 83 | this.showOutputTimer = setTimeout(() => { 84 | this.output.show(true); 85 | this.showOutputTimer = null; 86 | }, 1); 87 | } 88 | } 89 | 90 | private pushOutput(str: string) { 91 | if (str) { 92 | if (this.useTerminal) { 93 | if (this.ptyTerm) { 94 | this.ptyTerm.write(str); 95 | } 96 | } else { 97 | this.output.append(str); 98 | } 99 | } 100 | } 101 | 102 | private createDateHeaderUs(): string { 103 | if (this.timestamp) { 104 | return this.hrTimer.createDateTimestamp() + ' '; 105 | } else { 106 | return ''; 107 | } 108 | } 109 | 110 | private logFileWrite(text: string) { 111 | if ((this.logFd < 0) || (text === '')) { 112 | return; 113 | } 114 | try { 115 | fs.writeSync(this.logFd, text); 116 | } catch (e) { 117 | const msg = `Could not write to file ${this.logfile}. ${e.toString()}`; 118 | vscode.window.showErrorMessage(msg); 119 | try { 120 | fs.closeSync(this.logFd); 121 | } catch (closeErr) { 122 | console.error('decoder.logCloseError', closeErr); 123 | } 124 | this.logFd = -1; 125 | } 126 | } 127 | 128 | public softwareEvent(packet: Packet) { 129 | if (packet.port !== this.port) { return; } 130 | let text = ''; 131 | const letters = packet.data.toString(this.encoding); 132 | for (const letter of letters) { 133 | if (this.timeout) { 134 | clearTimeout(this.timeout); 135 | this.timeout = null; 136 | } 137 | 138 | if (letter === '\n') { 139 | text += '\n'; 140 | this.pushOutput('\n'); 141 | this.position = 0; 142 | continue; 143 | } 144 | 145 | if (this.position === 0) { 146 | const timestampHeader = this.createDateHeaderUs(); 147 | text += timestampHeader; 148 | this.pushOutput(timestampHeader); 149 | } 150 | 151 | text += letter; 152 | this.pushOutput(letter); 153 | this.position += 1; 154 | 155 | if (this.timestamp && (this.position > 0)) { 156 | if (this.timeout) { 157 | clearTimeout(this.timeout); 158 | } 159 | this.timeout = setTimeout(() => { 160 | text += '\n'; 161 | this.pushOutput('\n'); 162 | this.position = 0; 163 | this.timeout = null; 164 | }, 5000); 165 | } 166 | } 167 | this.logFileWrite(text); 168 | } 169 | 170 | public hardwareEvent(event: Packet) {} 171 | public synchronized() {} 172 | public lostSynchronization() {} 173 | 174 | public dispose() { 175 | if (this.output) { 176 | this.output.dispose(); 177 | this.output = null; 178 | } 179 | if (this.ptyTerm) { 180 | this.ptyTerm.dispose(); 181 | this.ptyTerm = null; 182 | } 183 | this.close(); 184 | } 185 | 186 | public close() { 187 | if (this.logFd >= 0) { 188 | fs.closeSync(this.logFd); 189 | this.logFd = -1; 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/frontend/swo/decoders/graph.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as fs from 'fs'; 3 | import { SWORTTDecoder } from './common'; 4 | import { decoders as DECODER_MAP } from './utils'; 5 | import { EventEmitter } from 'events'; 6 | import { SWOGraphDecoderConfig, GrapherDataMessage } from '../common'; 7 | import { Packet } from '../common'; 8 | 9 | function parseEncoded(buffer: Buffer, encoding: string) { 10 | return DECODER_MAP[encoding] ? DECODER_MAP[encoding](buffer) : DECODER_MAP.unsigned(buffer); 11 | } 12 | 13 | export class SWORTTGraphProcessor extends EventEmitter implements SWORTTDecoder { 14 | public readonly format: string = 'graph'; 15 | private port: number; 16 | private scale: number; 17 | private encoding: string; 18 | private graphId: string; 19 | private logFd: number = -1; 20 | private logfile: string; 21 | 22 | constructor(config: SWOGraphDecoderConfig) { 23 | super(); 24 | // core.socketServer.registerProcessor(this); 25 | this.port = config.port; 26 | this.encoding = config.encoding || 'unsigned'; 27 | this.scale = config.scale || 1; 28 | this.graphId = config.graphId; 29 | if (config.logfile) { 30 | this.logfile = config.logfile; 31 | try { 32 | this.logFd = fs.openSync(config.logfile, 'w'); 33 | } catch (e) { 34 | const msg = `Could not open file ${config.logfile} for writing. ${e.toString()}`; 35 | vscode.window.showErrorMessage(msg); 36 | } 37 | } 38 | } 39 | 40 | public softwareEvent(packet: Packet) { 41 | if (packet.port !== this.port) { return; } 42 | 43 | const raw = packet.data.toString('hex'); 44 | const decodedValue = parseEncoded(packet.data, this.encoding); 45 | const scaledValue = decodedValue * this.scale; 46 | 47 | const message: GrapherDataMessage = { type: 'data', data: scaledValue, id: this.graphId }; 48 | this.emit('message', message); 49 | 50 | if (this.logFd >= 0) { 51 | try { 52 | fs.writeSync(this.logFd, packet.data); 53 | } catch (e) { 54 | const msg = `Could not write to file ${this.logfile} for writing. ${e.toString()}`; 55 | vscode.window.showErrorMessage(msg); 56 | try { 57 | fs.closeSync(this.logFd); 58 | } catch (closeErr) { 59 | console.error('decoder.logCloseError', closeErr); 60 | } 61 | this.logFd = -1; 62 | } 63 | } 64 | } 65 | 66 | public hardwareEvent(event: Packet) {} 67 | public synchronized() {} 68 | public lostSynchronization() {} 69 | public dispose() { this.close(); } 70 | 71 | public close() { 72 | if (this.logFd >= 0) { 73 | fs.closeSync(this.logFd); 74 | this.logFd = -1; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/frontend/swo/decoders/utils.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from 'binary-parser'; 2 | 3 | const SignedParser = new Parser().endianess('little').int32('value'); 4 | const UnsignedParser = new Parser().endianess('little').uint32('value'); 5 | const FloatParser = new Parser().endianess('little').floatle('value'); 6 | 7 | export function parseFloat(buffer: Buffer): number { 8 | if (buffer.length < 4) { 9 | const tmp = Buffer.alloc(4); 10 | buffer.copy(tmp); 11 | buffer = tmp; 12 | } 13 | 14 | const result: { value: number } = FloatParser.parse(buffer); 15 | return result.value; 16 | } 17 | 18 | export function parseSigned(buffer: Buffer): number { 19 | if (buffer.length < 4) { 20 | const tmp = Buffer.alloc(4); 21 | buffer.copy(tmp); 22 | buffer = tmp; 23 | } 24 | 25 | const result: { value: number } = SignedParser.parse(buffer); 26 | return result.value; 27 | } 28 | 29 | export function parseUnsigned(buffer: Buffer): number { 30 | if (buffer.length < 4) { 31 | const tmp = Buffer.alloc(4); 32 | buffer.copy(tmp); 33 | buffer = tmp; 34 | } 35 | 36 | const result: { value: number } = UnsignedParser.parse(buffer); 37 | return result.value; 38 | } 39 | 40 | export function parseQ(buffer: Buffer, mask: number, shift: number) { 41 | const value = parseSigned(buffer); 42 | 43 | const fractional = value & mask; 44 | const integer = value >> shift; 45 | 46 | return integer + (fractional / mask); 47 | } 48 | 49 | export function parseUQ(buffer: Buffer, mask: number, shift: number) { 50 | const value = parseUnsigned(buffer); 51 | 52 | const fractional = value & mask; 53 | const integer = value >>> shift; 54 | 55 | return integer + (fractional / mask); 56 | } 57 | 58 | export const decoders: { [key: string]: (buf: Buffer) => number } = { 59 | signed: parseSigned, 60 | float: parseFloat, 61 | Q8_24: (buffer) => parseQ(buffer, 0xFFFFFF, 24), 62 | Q16_16: (buffer) => parseQ(buffer, 0xFFFF, 16), 63 | Q24_8: (buffer) => parseQ(buffer, 0xFF, 8), 64 | UQ8_24: (buffer) => parseUQ(buffer, 0xFFFFFF, 24), 65 | UQ16_16: (buffer) => parseUQ(buffer, 0xFFFF, 16), 66 | UQ24_8: (buffer) => parseUQ(buffer, 0xFF, 8), 67 | unsigned: parseUnsigned 68 | }; 69 | -------------------------------------------------------------------------------- /src/frontend/swo/sources/common.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | 3 | export interface SWORTTSource extends EventEmitter { 4 | connected: boolean; 5 | dispose(); 6 | } 7 | -------------------------------------------------------------------------------- /src/frontend/swo/sources/fifo.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { EventEmitter } from 'events'; 3 | import { SWORTTSource } from './common'; 4 | 5 | export class FifoSWOSource extends EventEmitter implements SWORTTSource { 6 | private stream: fs.ReadStream; 7 | public connected: boolean = false; 8 | 9 | constructor(private SWOPath: string) { 10 | super(); 11 | this.stream = fs.createReadStream(this.SWOPath, { highWaterMark: 128, encoding: null, autoClose: false }); 12 | this.stream.on('data', (buffer) => { 13 | this.emit('data', buffer); 14 | }); 15 | this.stream.on('close', (buffer) => { 16 | this.emit('disconnected'); 17 | }); 18 | this.connected = true; 19 | } 20 | 21 | public dispose() { 22 | this.stream.close(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/frontend/swo/sources/file.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as vscode from 'vscode'; 3 | import { EventEmitter } from 'events'; 4 | import { SWORTTSource } from './common'; 5 | 6 | export class FileSWOSource extends EventEmitter implements SWORTTSource { 7 | public connected: boolean = false; 8 | private fd: number = null; 9 | private interval: any = null; 10 | 11 | constructor(private SWOPath: string, private timeout: number = 500) { 12 | super(); 13 | 14 | // We are on a reading end and a file may not have yet been created. Could have used 15 | // a file watcher but we may run out of handles (VSCode uses a LOT of them). Also, 16 | // intentionally did not use setInterval to progressively wait longer and not let 17 | // setInterval callbacks stackup 18 | const start = Date.now(); 19 | function openFile(retryTime = 1) { 20 | if ((timeout <= 0) || fs.existsSync(SWOPath)) { 21 | fs.open(SWOPath, 'r', (err, fd) => { 22 | if (err) { 23 | vscode.window.showWarningMessage(`Unable to open path: ${SWOPath} - Unable to read SWO data.`); 24 | } else { 25 | this.fd = fd; 26 | this.interval = setInterval(this.read.bind(this), 2); 27 | this.connected = true; 28 | this.emit('connected'); 29 | } 30 | }); 31 | } else if (timeout > 0) { 32 | const delta = Date.now() - start; 33 | if (delta >= timeout) { 34 | vscode.window.showWarningMessage(`SWO File ${SWOPath} does not exist even after timeout of ${timeout}ms.`); 35 | } else { 36 | setTimeout(openFile, Math.min(10, retryTime + 1)); 37 | } 38 | } 39 | } 40 | openFile(1); 41 | } 42 | 43 | private read() { 44 | const buf: Buffer = Buffer.alloc(64); 45 | fs.read(this.fd, buf, 0, 64, null, (err, bytesRead, buffer) => { 46 | if (bytesRead > 0) { 47 | this.emit('data', buffer.slice(0, bytesRead)); 48 | } 49 | }); 50 | } 51 | 52 | public dispose() { 53 | this.emit('disconnected'); 54 | clearInterval(this.interval); 55 | fs.closeSync(this.fd); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/frontend/swo/sources/serial.ts: -------------------------------------------------------------------------------- 1 | import { SWORTTSource } from './common'; 2 | import { EventEmitter } from 'events'; 3 | import type { SerialPort } from 'serialport'; 4 | import * as vscode from 'vscode'; 5 | 6 | export class SerialSWOSource extends EventEmitter implements SWORTTSource { 7 | private serialPort: SerialPort = null; 8 | public connected: boolean = false; 9 | 10 | constructor(private device: string, private baudRate: number) { 11 | super(); 12 | 13 | // eslint-disable-next-line @typescript-eslint/no-require-imports 14 | const { SerialPort } = require('serialport'); 15 | this.serialPort = new SerialPort({ path: device, baudRate: baudRate, autoOpen: false }); 16 | this.serialPort.on('data', (buffer) => { 17 | this.emit('data', buffer); 18 | }); 19 | this.serialPort.on('error', (error) => { 20 | vscode.window.showErrorMessage(`Unable to open serial port: ${device} - ${error.toString()}`); 21 | }); 22 | this.serialPort.on('open', () => { 23 | this.connected = true; 24 | this.emit('connected'); 25 | }); 26 | this.serialPort.open(); 27 | } 28 | 29 | public dispose() { 30 | if (this.serialPort) { 31 | this.serialPort.close(); 32 | this.emit('disconnected'); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/frontend/swo/sources/usb.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'stream'; 2 | import { SWORTTSource } from './common'; 3 | import { promisify } from 'util'; 4 | import * as vscode from 'vscode'; 5 | import * as usb from 'usb'; 6 | 7 | /* 8 | * NOTE: using legacy node-usb interface, because the modern 9 | * WebUSB-compatible version doesn't contain a way to interrupt pending 10 | * transfer, leading to problems with getting rid of the connection 11 | */ 12 | 13 | export class UsbSWOSource extends EventEmitter implements SWORTTSource { 14 | private dev?: usb.Device; 15 | private iface?: usb.Interface; 16 | private ep?: usb.InEndpoint; 17 | 18 | constructor( 19 | private readonly device: string, 20 | private readonly port: string 21 | ) { 22 | super(); 23 | 24 | this.start(); 25 | } 26 | 27 | private async findDevice(): Promise< 28 | | { 29 | dev: usb.Device; 30 | config: usb.ConfigDescriptor; 31 | iface: usb.InterfaceDescriptor; 32 | endpoint: usb.EndpointDescriptor; 33 | productName: string; 34 | } 35 | | undefined 36 | > { 37 | console.info('Looking for USB devices matching', this.device); 38 | const devs = usb.getDeviceList(); 39 | for (const dev of devs) { 40 | dev.open(); 41 | const { deviceDescriptor: dd } = dev; 42 | const getStringDescriptor = promisify(dev.getStringDescriptor.bind(dev)); 43 | const productName = await getStringDescriptor(dd.iProduct); 44 | if (productName.match(this.device)) { 45 | console.info( 46 | 'Found device', 47 | productName, 48 | 'VID', 49 | dd.idVendor.toString(16), 50 | 'PID', 51 | dd.idProduct.toString(16), 52 | 'Serial', 53 | await getStringDescriptor(dd.iSerialNumber) 54 | ); 55 | 56 | for (const cfg of dev.allConfigDescriptors) { 57 | for (const iface of cfg.interfaces) { 58 | for (const alt of iface) { 59 | const interfaceName = await getStringDescriptor(alt.iInterface); 60 | if (interfaceName?.match(this.port)) { 61 | for (const ep of alt.endpoints) { 62 | if ((ep.bmAttributes & 3) === usb.usb.LIBUSB_TRANSFER_TYPE_BULK 63 | && ep.bEndpointAddress & usb.usb.LIBUSB_ENDPOINT_IN) { 64 | console.info( 65 | 'Matched config', 66 | cfg.bConfigurationValue, 67 | 'interface', 68 | alt.bInterfaceNumber, 69 | 'alternate', 70 | alt.bAlternateSetting, 71 | 'endpoint', 72 | ep.bEndpointAddress 73 | ); 74 | return { 75 | dev, 76 | config: cfg, 77 | iface: alt, 78 | endpoint: ep, 79 | productName 80 | }; 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | console.warn('Couldn\'t match interface named', this.port); 89 | } 90 | dev.close(); 91 | } 92 | console.warn('Matching device not found'); 93 | return undefined; 94 | } 95 | 96 | public async start() { 97 | const { dev, config, iface, endpoint, productName } = (await this.findDevice()) ?? {}; 98 | if (!dev) { 99 | vscode.window.showErrorMessage( 100 | `Couldn't find a device matching '${this.device}' with interface '${this.port}` 101 | ); 102 | return; 103 | } 104 | 105 | console.debug('Connecting to', productName); 106 | dev.open(); 107 | this.dev = dev; 108 | console.debug('Selecting configuration', config.bConfigurationValue); 109 | await promisify(dev.setConfiguration.bind(dev))(config.bConfigurationValue); 110 | console.debug('Claiming interface', iface.bInterfaceNumber); 111 | this.iface = dev.interface(iface.bInterfaceNumber); 112 | this.iface.claim(); 113 | if (iface.bAlternateSetting) { 114 | console.debug('Selecting alternate', iface.bAlternateSetting); 115 | await dev.interface(iface.iInterface).setAltSettingAsync(iface.bAlternateSetting); 116 | } 117 | console.debug('Reading from endpoint', endpoint.bEndpointAddress); 118 | 119 | this.ep = this.iface.endpoint(endpoint.bEndpointAddress) as usb.InEndpoint; 120 | this.ep.on('data', (buffer: Buffer) => { 121 | console.debug(buffer.length, 'bytes received'); 122 | this.emit('data', buffer); 123 | }); 124 | this.ep.on('error', (error) => { 125 | console.error('Unexpected polling error', error); 126 | }); 127 | this.ep.startPoll(); 128 | 129 | this.emit('connected'); 130 | } 131 | 132 | public get connected() { 133 | return !!this.ep; 134 | } 135 | 136 | public async dispose() { 137 | if (this.ep) { 138 | console.debug('Stopping polling...'); 139 | await promisify(this.ep.stopPoll.bind(this.ep))(); 140 | this.ep = undefined; 141 | console.debug('Polling stopped'); 142 | } 143 | if (this.iface) { 144 | console.debug('Releasing interface...'); 145 | await this.iface.releaseAsync(); 146 | this.iface = undefined; 147 | console.debug('Interface released'); 148 | } 149 | if (this.dev) { 150 | console.debug('Closing device...'); 151 | this.dev.close(); 152 | this.dev = undefined; 153 | console.debug('Device closed'); 154 | } 155 | this.emit('disconnected'); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/frontend/utils.ts: -------------------------------------------------------------------------------- 1 | export function hexFormat(value: number, padding: number = 8, includePrefix: boolean = true): string { 2 | let base = (value >>> 0).toString(16); 3 | base = base.padStart(padding, '0'); 4 | return includePrefix ? '0x' + base : base; 5 | } 6 | 7 | export function binaryFormat(value: number, padding: number = 0, includePrefix: boolean = true, group: boolean = false): string { 8 | let base = (value >>> 0).toString(2); 9 | while (base.length < padding) { 10 | base = '0' + base; 11 | } 12 | 13 | if (group) { 14 | const nibRem = 4 - (base.length % 4); 15 | for (let i = 0; i < nibRem; i++) { 16 | base = '0' + base; 17 | } 18 | const groups = base.match(/[01]{4}/g); 19 | base = groups.join(' '); 20 | 21 | base = base.substring(nibRem); 22 | } 23 | 24 | return includePrefix ? '0b' + base : base; 25 | } 26 | 27 | export function createMask(offset: number, width: number) { 28 | let r = 0; 29 | const a = offset; 30 | const b = offset + width - 1; 31 | for (let i = a; i <= b; i++) { 32 | r = (r | (1 << i)) >>> 0; 33 | } 34 | return r; 35 | } 36 | 37 | export function extractBits(value: number, offset: number, width: number) { 38 | const mask = createMask(offset, width); 39 | const bvalue = ((value & mask) >>> offset) >>> 0; 40 | return bvalue; 41 | } 42 | 43 | export function parseInteger(value: string): number { 44 | if ((/^0b([01]+)$/i).test(value)) { 45 | return parseInt(value.substring(2), 2); 46 | } 47 | if ((/^0x([0-9a-f]+)$/i).test(value)) { 48 | return parseInt(value.substring(2), 16); 49 | } 50 | if ((/^[0-9]+/i).test(value)) { 51 | return parseInt(value, 10); 52 | } 53 | if ((/^#[0-1]+/i).test(value)) { 54 | return parseInt(value.substring(1), 2); 55 | } 56 | return undefined; 57 | } 58 | 59 | export function parseDimIndex(spec: string, count: number): string[] { 60 | if (spec.indexOf(',') !== -1) { 61 | const components = spec.split(',').map((c) => c.trim()); 62 | if (components.length !== count) { 63 | throw new Error('dimIndex Element has invalid specification.'); 64 | } 65 | return components; 66 | } 67 | 68 | if (/^([0-9]+)-([0-9]+)$/i.test(spec)) { 69 | const parts = spec.split('-').map((p) => parseInteger(p)); 70 | const start = parts[0]; 71 | const end = parts[1]; 72 | 73 | const numElements = end - start + 1; 74 | if (numElements < count) { 75 | throw new Error('dimIndex Element has invalid specification.'); 76 | } 77 | 78 | const components: string[] = []; 79 | for (let i = 0; i < count; i++) { 80 | components.push(`${start + i}`); 81 | } 82 | 83 | return components; 84 | } 85 | 86 | if (/^[a-zA-Z]-[a-zA-Z]$/.test(spec)) { 87 | const start = spec.charCodeAt(0); 88 | const end = spec.charCodeAt(2); 89 | 90 | const numElements = end - start + 1; 91 | if (numElements < count) { 92 | throw new Error('dimIndex Element has invalid specification.'); 93 | } 94 | 95 | const components: string[] = []; 96 | for (let i = 0; i < count; i++) { 97 | components.push(String.fromCharCode(start + i)); 98 | } 99 | 100 | return components; 101 | } 102 | 103 | return []; 104 | } 105 | -------------------------------------------------------------------------------- /src/frontend/views/nodes/basenode.ts: -------------------------------------------------------------------------------- 1 | import { Command, TreeItem, DebugSession } from 'vscode'; 2 | import { NumberFormat, NodeSetting } from '../../../common'; 3 | import { AddrRange } from '../../addrranges'; 4 | 5 | export abstract class BaseNode { 6 | public expanded: boolean; 7 | 8 | constructor(protected readonly parent?: BaseNode) { 9 | this.expanded = false; 10 | } 11 | 12 | public getParent(): BaseNode | undefined { 13 | return this.parent; 14 | } 15 | 16 | public abstract getChildren(): BaseNode[] | Promise; 17 | public abstract getTreeItem(): TreeItem | Promise; 18 | 19 | public getCommand(): Command | undefined { 20 | return undefined; 21 | } 22 | 23 | public abstract getCopyValue(): string | undefined; 24 | } 25 | 26 | export abstract class PeripheralBaseNode extends BaseNode { 27 | public format: NumberFormat; 28 | public pinned: boolean; 29 | public readonly name: string; 30 | public session: DebugSession; 31 | 32 | constructor(protected readonly parent?: PeripheralBaseNode) { 33 | super(parent); 34 | this.format = NumberFormat.Auto; 35 | this.pinned = false; 36 | } 37 | 38 | public selected(): Thenable { 39 | return Promise.resolve(false); 40 | } 41 | 42 | public abstract performUpdate(): Thenable; 43 | public abstract updateData(): Thenable; 44 | 45 | public abstract getChildren(): PeripheralBaseNode[] | Promise; 46 | public abstract getPeripheral(): PeripheralBaseNode; 47 | 48 | public abstract collectRanges(ary: AddrRange[]): void; // Append addr range(s) to array 49 | 50 | public abstract saveState(path?: string): NodeSetting[]; 51 | public abstract findByPath(path: string[]): PeripheralBaseNode; 52 | } 53 | 54 | export abstract class ClusterOrRegisterBaseNode extends PeripheralBaseNode { 55 | public readonly offset: number; 56 | } 57 | -------------------------------------------------------------------------------- /src/frontend/views/nodes/fieldnode.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from './basenode'; 2 | import { TreeItem, TreeItemCollapsibleState, TreeItemLabel } from 'vscode'; 3 | import { RegisterNode } from './registernode'; 4 | import { toStringDecHexOctBin } from '../../../common'; 5 | 6 | export class FieldNode extends BaseNode { 7 | public prevValue: string = ''; 8 | public value: string = ''; 9 | 10 | constructor(public name: string, private offset: number, private size: number, private register: RegisterNode) { 11 | super(register); 12 | } 13 | 14 | public getChildren(): BaseNode[] | Promise { 15 | return []; 16 | } 17 | 18 | public getTreeItem(): TreeItem | Promise { 19 | const value = this.register.extractBits(this.offset, this.size); 20 | this.value = value.toString(); 21 | 22 | const label: TreeItemLabel = { 23 | label: this.name + ' ' + this.value 24 | }; 25 | if (this.prevValue && (this.prevValue !== this.value)) { 26 | label.highlights = [[this.name.length + 1, label.label.length]]; 27 | } 28 | this.prevValue = this.value; 29 | 30 | const ti = new TreeItem(label, TreeItemCollapsibleState.None); 31 | // ti.description = this.value; 32 | ti.contextValue = 'field'; 33 | ti.tooltip = '$' + this.register.name + '.' + this.name + '\n' + toStringDecHexOctBin(value); 34 | 35 | return ti; 36 | } 37 | 38 | public getCopyValue(): string | undefined { 39 | const value = this.register.extractBits(this.offset, this.size); 40 | return value.toString(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/frontend/views/nodes/registernode.ts: -------------------------------------------------------------------------------- 1 | import { TreeItem, TreeItemLabel, TreeItemCollapsibleState } from 'vscode'; 2 | 3 | import { BaseNode } from './basenode'; 4 | import { FieldNode } from './fieldnode'; 5 | import { NodeSetting, toStringDecHexOctBin } from '../../../common'; 6 | 7 | import { hexFormat, binaryFormat, createMask, extractBits } from '../../utils'; 8 | 9 | export interface RegisterValue { 10 | number: number; 11 | value: string; 12 | } 13 | 14 | export class RegisterNode extends BaseNode { 15 | private fields: FieldNode[]; 16 | private currentValue: number; 17 | private currentNaturalValue: string; 18 | private prevNaturalValue: string; 19 | 20 | constructor(public name: string, public index: number) { 21 | super(null); 22 | 23 | this.name = name; 24 | 25 | if (name.toUpperCase() === 'XPSR' || name.toUpperCase() === 'CPSR') { 26 | this.fields = [ 27 | new FieldNode('Negative Flag (N)', 31, 1, this), 28 | new FieldNode('Zero Flag (Z)', 30, 1, this), 29 | new FieldNode('Carry or borrow flag (C)', 29, 1, this), 30 | new FieldNode('Overflow Flag (V)', 28, 1, this), 31 | new FieldNode('Saturation Flag (Q)', 27, 1, this), 32 | new FieldNode('GE', 16, 4, this), 33 | new FieldNode('Interrupt Number', 0, 8, this), 34 | new FieldNode('ICI/IT', 25, 2, this), 35 | new FieldNode('ICI/IT', 10, 6, this), 36 | new FieldNode('Thumb State (T)', 24, 1, this) 37 | ]; 38 | } else if (name.toUpperCase() === 'CONTROL') { 39 | this.fields = [ 40 | new FieldNode('FPCA', 2, 1, this), 41 | new FieldNode('SPSEL', 1, 1, this), 42 | new FieldNode('nPRIV', 0, 1, this) 43 | ]; 44 | } 45 | 46 | this.currentValue = 0x00; 47 | this.currentNaturalValue = '0x00000000'; 48 | this.prevNaturalValue = ''; 49 | } 50 | 51 | public extractBits(offset: number, width: number): number { 52 | return extractBits(this.currentValue, offset, width); 53 | } 54 | 55 | public getTreeItem(): TreeItem | Promise { 56 | const state = this.fields && this.fields.length > 0 57 | ? (this.expanded ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed) 58 | : TreeItemCollapsibleState.None; 59 | 60 | const label: TreeItemLabel = { 61 | label: this.name + ' ' + this.currentNaturalValue 62 | }; 63 | if (this.prevNaturalValue && (this.prevNaturalValue !== this.currentNaturalValue)) { 64 | label.highlights = [[this.name.length + 1, label.label.length]]; 65 | } 66 | this.prevNaturalValue = this.currentNaturalValue; 67 | 68 | const item = new TreeItem(label, state); 69 | // item.description = this.currentNaturalValue; 70 | item.contextValue = 'register'; 71 | if (!(/^[sd][0-9]/.test(this.name))) { 72 | item.tooltip = '$' + this.name + '\n' + toStringDecHexOctBin(parseInt(this.currentNaturalValue)); 73 | } 74 | 75 | return item; 76 | } 77 | 78 | public getChildren(): FieldNode[] { 79 | return this.fields; 80 | } 81 | 82 | public setValue(newValue: string) { 83 | this.currentNaturalValue = newValue; 84 | if (this.name.toUpperCase() === 'CONTROL' || this.name.toUpperCase() === 'XPSR' || this.name.toUpperCase() === 'CPSR') { 85 | if (this.currentNaturalValue.startsWith('0x')) { 86 | this.currentValue = parseInt(this.currentNaturalValue, 16); 87 | } else { 88 | this.currentValue = parseInt(this.currentNaturalValue, 10); 89 | if (this.currentValue < 0) { 90 | // convert to unsigned 32 bit quantity 91 | const tmp = (this.currentValue & 0xffffffff) >>> 0; 92 | this.currentValue = tmp; 93 | } 94 | let cv = this.currentValue.toString(16); 95 | while (cv.length < 8) { 96 | cv = '0' + cv; 97 | } 98 | this.currentNaturalValue = '0x' + cv; 99 | } 100 | } 101 | } 102 | 103 | public getCopyValue(): string { 104 | return this.currentNaturalValue; 105 | } 106 | 107 | public _saveState(): NodeSetting[] { 108 | const settings: NodeSetting[] = []; 109 | if (this.fields && this.fields.length > 0 && this.expanded) { 110 | settings.push({ node: this.name, expanded: this.expanded }); 111 | } 112 | 113 | return settings; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare const __COMMIT_HASH__: string; // Source: webpack plugin defined in webpack.config.js 2 | -------------------------------------------------------------------------------- /src/grapher/datasource.ts: -------------------------------------------------------------------------------- 1 | import { GraphPoint, GrapherDataMessage, GrapherProgramCounterMessage } from './types'; 2 | 3 | export class GraphDataSource { 4 | private data: { 5 | [graphId: string]: GraphPoint[]; 6 | } = {}; 7 | 8 | private subscriptions: { 9 | // tslint:disable-next-line:ban-types 10 | [graphId: string]: ((p: GraphPoint) => void)[]; 11 | } = {}; 12 | 13 | private counterStats: { 14 | [fnName: string]: number; 15 | } = {}; 16 | 17 | constructor() { 18 | } 19 | 20 | public receivedProgramCounterMessage(message: GrapherProgramCounterMessage) { 21 | if (!this.counterStats[message.function]) { this.counterStats[message.function] = 0; } 22 | this.counterStats[message.function] += 1; 23 | } 24 | 25 | public getProgramCounterStats() { 26 | return { ...this.counterStats }; 27 | } 28 | 29 | public receiveDataMessage(message: GrapherDataMessage) { 30 | const gp: GraphPoint = { 31 | timestamp: message.timestamp, 32 | value: message.data 33 | }; 34 | 35 | const graphId = message.id; 36 | if (!this.data[graphId]) { this.data[graphId] = []; } 37 | 38 | if (this.data[graphId]) { 39 | this.data[graphId].push(gp); 40 | } 41 | 42 | if (this.subscriptions[graphId]) { 43 | this.subscriptions[graphId].forEach((fn) => fn(gp)); 44 | } 45 | } 46 | 47 | public getData(graphId: string, start: number, end: number, pad: boolean = true): GraphPoint[] { 48 | let data: GraphPoint[] = this.data[graphId]; 49 | if (!data) { return []; } 50 | 51 | data = data.filter((gp) => gp.timestamp >= start && gp.timestamp <= end); 52 | if (pad && data.length >= 1) { 53 | const ep = data[data.length - 1]; 54 | data.push({ timestamp: end, value: ep.value }); 55 | } 56 | 57 | return data; 58 | } 59 | 60 | public sampleData(graphId: string, sampleSize: number, start: number = null, end: number = null): GraphPoint[] { 61 | let data: GraphPoint[] = this.data[graphId]; 62 | if (!data) { return []; } 63 | 64 | if (start === null) { start = 0; } 65 | if (end == null) { end = new Date().getTime(); } 66 | 67 | data = data.filter((gp) => gp.timestamp >= start && gp.timestamp <= end); 68 | 69 | if (data.length > sampleSize * 1.5) { 70 | const sampleRate = Math.round(data.length / sampleSize); 71 | data = data.filter((gp, idx) => idx % sampleRate === 0); 72 | } 73 | 74 | return data; 75 | } 76 | 77 | public oldestPoint(graphId: string): GraphPoint { 78 | return this.data[graphId] ? this.data[graphId][0] : null; 79 | } 80 | 81 | public subscribe(graphId: string, callback: (point: GraphPoint) => void) { 82 | if (!this.subscriptions[graphId]) { this.subscriptions[graphId] = []; } 83 | this.subscriptions[graphId].push(callback); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/grapher/main.ts: -------------------------------------------------------------------------------- 1 | import { TimeseriesGraph } from './timeseriesgraph'; 2 | import { XYGraph } from './xygraph'; 3 | import { ProgramStatsGraph } from './programstatsgraph'; 4 | import { 5 | Graph, GrapherConfigurationMessage, TimeseriesGraphConfiguration, XYGraphConfiguration, 6 | GrapherMessage, GrapherDataMessage, GrapherStatusMessage, GrapherProgramCounterMessage 7 | } from './types'; 8 | import { GraphDataSource } from './datasource'; 9 | 10 | interface VSCodeAPI { 11 | postMessage(msg: any): void; 12 | getState(): any; 13 | setState(state: any): void; 14 | } 15 | 16 | declare function acquireVsCodeApi(): VSCodeAPI; 17 | declare const window: Window; 18 | declare global { 19 | interface Window { 20 | datasource: GraphDataSource; 21 | addEventListener(event: string, callback: (event: any) => void); 22 | } 23 | } 24 | 25 | function init() { 26 | // let datasource: GraphDataSource = null; 27 | const graphs: Graph[] = []; 28 | 29 | function processConfiguration(message: GrapherConfigurationMessage) { 30 | window.datasource = new GraphDataSource(); 31 | 32 | message.graphs.forEach((config: any) => { 33 | if (config.type === 'realtime') { 34 | const graph = new TimeseriesGraph(config as TimeseriesGraphConfiguration, window.datasource); 35 | graphs.push(graph); 36 | if (message.status === 'stopped' || message.status === 'terminated') { graph.stop(); } 37 | } else if (config.type === 'x-y-plot') { 38 | const graph = new XYGraph(config as XYGraphConfiguration, window.datasource); 39 | graphs.push(graph); 40 | if (message.status === 'stopped' || message.status === 'terminated') { graph.stop(); } 41 | } 42 | }); 43 | 44 | // const psg: ProgramStatsGraph = new ProgramStatsGraph(window.datasource); 45 | // if (message.status === 'stopped' || message.status === 'terminated') { psg.stop(); } 46 | // graphs.push(psg); 47 | } 48 | 49 | function processStatus(message: GrapherStatusMessage) { 50 | if (message.status === 'stopped' || message.status === 'terminated') { 51 | graphs.forEach((g) => g.stop()); 52 | } else if (message.status === 'continued') { 53 | graphs.forEach((g) => g.continue()); 54 | } 55 | } 56 | 57 | function processData(message: GrapherDataMessage) { 58 | if (window.datasource) { 59 | window.datasource.receiveDataMessage(message); 60 | } 61 | } 62 | 63 | function processProgramCounter(message: GrapherProgramCounterMessage) { 64 | window.datasource.receivedProgramCounterMessage(message); 65 | } 66 | 67 | window.addEventListener('message', (event) => { 68 | const message: GrapherMessage = event.data; 69 | switch (message.type) { 70 | case 'configure': 71 | processConfiguration(message as GrapherConfigurationMessage); 72 | break; 73 | case 'data': 74 | processData(message as GrapherDataMessage); 75 | break; 76 | case 'status': 77 | processStatus(message as GrapherStatusMessage); 78 | break; 79 | case 'program-counter': 80 | processProgramCounter(message as GrapherProgramCounterMessage); 81 | break; 82 | default: 83 | console.log(`Got unrecognized message type: ${message.type}`); 84 | break; 85 | } 86 | }); 87 | 88 | const vscode = acquireVsCodeApi(); 89 | vscode.postMessage({ type: 'init' }); 90 | } 91 | 92 | init(); 93 | -------------------------------------------------------------------------------- /src/grapher/programstatsgraph.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-return */ 2 | import * as d3 from 'd3'; 3 | import { Graph } from './types'; 4 | import { GraphDataSource } from './datasource'; 5 | 6 | declare const window: Window; 7 | declare global { 8 | interface Window { 9 | datasource: GraphDataSource; 10 | addEventListener(event: string, callback: (event: any) => void); 11 | requestAnimationFrame(callback: () => void); 12 | } 13 | } 14 | 15 | interface FunctionStat { 16 | name: string; 17 | count: number; 18 | } 19 | 20 | function djb2(str: string): number { 21 | let hash = 5381; 22 | for (let i = 0; i < str.length; i++) { 23 | hash = ((hash << 5) + hash) + str.charCodeAt(i); /* hash * 33 + c */ 24 | } 25 | return hash; 26 | } 27 | 28 | const COLORS: string[] = [ 29 | '#C0392B', '#9B59B6', '#2980B9', '#1ABC9C', '#16A085', 30 | '#F1C40F', '#E67E22', '#D35400', '#E74C3C', '#8E44AD', 31 | '#3498DB', '#2ECC71', '#F39C12', 32 | ]; 33 | 34 | function hashStringToColor(str: string): string { 35 | if (!str || str === '**Other**') { 36 | return '#CCCCCC'; 37 | } 38 | 39 | return COLORS[Math.abs(djb2(str)) % COLORS.length]; 40 | } 41 | 42 | export class ProgramStatsGraph implements Graph { 43 | private svg: any; 44 | private width: number = 700; 45 | private height: number = 350; 46 | 47 | private margins = { 48 | top: 30, 49 | right: 20, 50 | left: 20, 51 | bottom: 30 52 | }; 53 | 54 | private pie: any; 55 | private g: any; 56 | private arc: any; 57 | private label: any; 58 | private legend: any; 59 | private path: any; 60 | 61 | private stopped: boolean = false; 62 | 63 | private processData(data: { [name: string]: number }) { 64 | let counts: FunctionStat[] = []; 65 | 66 | // tslint:disable-next-line:forin 67 | for (const key in data) { 68 | counts.push({ name: key, count: data[key] }); 69 | } 70 | 71 | counts.sort((a, b) => { 72 | if (a.count < b.count) { return 1; } 73 | if (a.count > b.count) { return -1; } 74 | return 0; 75 | }); 76 | 77 | if (counts.length > 10) { 78 | const top9 = counts.slice(0, 9); 79 | const remainder = counts.slice(9); 80 | 81 | let total = 0; 82 | remainder.forEach((c) => total += c.count); 83 | 84 | counts = top9; 85 | counts.push({ name: '**Other**', count: total }); 86 | } 87 | 88 | return counts; 89 | } 90 | 91 | constructor(private datasource: GraphDataSource) { 92 | const wrapper = d3.select('.graph-container').append('div').attr('class', 'graph-wrapper'); 93 | wrapper.append('h3').text('Program Stats'); 94 | 95 | this.svg = wrapper.append('svg') 96 | .attr('width', this.width + this.margins.left + this.margins.right) 97 | .attr('height', this.height + this.margins.top + this.margins.bottom); 98 | this.g = this.svg.append('g').attr('transform', `translate(${(this.height / 2) + this.margins.left}, ${(this.height / 2) + this.margins.top})`); 99 | this.legend = this.svg.append('g').attr('transform', `translate(${this.height + this.margins.left + 40}, ${this.margins.top})`); 100 | 101 | this.pie = d3.pie().sort(null).value((d) => d.count); 102 | 103 | const radius = this.height / 2; 104 | 105 | this.path = d3.arc() 106 | .outerRadius(radius) 107 | .innerRadius(0); 108 | 109 | window.requestAnimationFrame(this.updateGraph.bind(this)); 110 | } 111 | 112 | public updateGraph() { 113 | if (!this.stopped) { 114 | const data = this.processData(this.datasource.getProgramCounterStats()); 115 | 116 | this.g.selectAll('.arc').remove(); 117 | const arc = this.g.selectAll('.arc') 118 | .data(this.pie(data)) 119 | .enter().append('g').attr('class', 'arc'); 120 | 121 | arc.append('path') 122 | .attr('d', this.path) 123 | .attr('fill', (d: any, index: number) => hashStringToColor(d.data.name)) 124 | .attr('fill-opacity', 0.65) 125 | .attr('stroke', (d: any, index: number) => hashStringToColor(d.data.name)); 126 | 127 | this.legend.selectAll('.entry').remove(); 128 | const entry = this.legend.selectAll('.entry') 129 | .data(data) 130 | .enter().append('g').attr('class', 'entry').attr('transform', (d: any, index: number) => `translate(0,${index * 35})`); 131 | 132 | entry.append('rect') 133 | .attr('width', 30) 134 | .attr('height', 20) 135 | .attr('fill', (d: any, index: number) => hashStringToColor(d.name)) 136 | .attr('fill-opacity', 0.65) 137 | .attr('stroke', (d: any, index: number) => hashStringToColor(d.name)); 138 | entry.append('text').text((d: any) => d.name).attr('x', 40).attr('y', 15); 139 | } 140 | 141 | window.requestAnimationFrame(this.updateGraph.bind(this)); 142 | } 143 | 144 | public stop() { 145 | this.stopped = true; 146 | } 147 | 148 | public continue() { 149 | this.stopped = false; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/grapher/types.ts: -------------------------------------------------------------------------------- 1 | export interface GraphConfiguration { 2 | type: string; 3 | label: string; 4 | } 5 | 6 | export interface TimeseriesGraphConfiguration extends GraphConfiguration { 7 | minimum: number; 8 | maximum: number; 9 | timespan: number; 10 | plots: { 11 | graphId: string; 12 | label: string; 13 | color: string; 14 | }[]; 15 | } 16 | 17 | export interface XYGraphConfiguration extends GraphConfiguration { 18 | xPort: number; 19 | yPort: number; 20 | xMinimum: number; 21 | xMaximum: number; 22 | yMinimum: number; 23 | yMaximum: number; 24 | initialX: number; 25 | initialY: number; 26 | timespan: number; 27 | xGraphId: string; 28 | yGraphId: string; 29 | } 30 | 31 | export interface GrapherMessage { 32 | id: number; 33 | timestamp: number; 34 | type: 'configure' | 'status' | 'data' | 'program-counter' | 'init'; 35 | } 36 | 37 | export interface GrapherStatusMessage extends GrapherMessage { 38 | status: 'stopped' | 'terminated' | 'continued'; 39 | } 40 | 41 | export interface GrapherDataMessage extends GrapherMessage { 42 | data: number; 43 | } 44 | 45 | export interface GrapherProgramCounterMessage extends GrapherMessage { 46 | function: string; 47 | counter: number; 48 | } 49 | 50 | export interface GrapherConfigurationMessage extends GrapherMessage { 51 | graphs: [GraphConfiguration]; 52 | status: 'stopped' | 'terminated' | 'continued'; 53 | } 54 | 55 | export interface Graph { 56 | stop(): void; 57 | continue(): void; 58 | } 59 | 60 | export interface GraphPoint { 61 | timestamp: number; 62 | value: number; 63 | } 64 | -------------------------------------------------------------------------------- /src/grapher/xygraph.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-return */ 2 | import * as d3 from 'd3'; 3 | import { Graph, XYGraphConfiguration, GraphPoint } from './types'; 4 | import { GraphDataSource } from './datasource'; 5 | 6 | declare const window: Window; 7 | declare global { 8 | interface Window { 9 | datasource: GraphDataSource; 10 | addEventListener(event: string, callback: (event: any) => void); 11 | requestAnimationFrame(callback: () => void); 12 | } 13 | } 14 | 15 | interface XYGraphPoint { 16 | timestamp: number; 17 | x: number; 18 | y: number; 19 | } 20 | 21 | export class XYGraph implements Graph { 22 | private label: string; 23 | 24 | private x: d3.ScaleLinear; 25 | private y: d3.ScaleLinear; 26 | 27 | private span: number = 10 * 1000; 28 | 29 | private stopped: boolean = false; 30 | private line: d3.Line; 31 | 32 | private svg: any; 33 | private g: any; 34 | private yAxis: any; 35 | private xAxis: any; 36 | private path: any; 37 | 38 | private height: number = 350; 39 | private width: number = 350; 40 | private margins = { 41 | top: 30, 42 | right: 30, 43 | left: 40, 44 | bottom: 60 45 | }; 46 | 47 | private currentX: number; 48 | private currentY: number; 49 | 50 | private points: XYGraphPoint[]; 51 | 52 | constructor(protected configuration: XYGraphConfiguration, private datasource: GraphDataSource) { 53 | this.span = configuration.timespan * 1000; 54 | 55 | this.x = d3.scaleLinear().range([0, this.width]).domain([configuration.xMinimum || 0, configuration.xMaximum || 65535]); 56 | this.y = d3.scaleLinear().range([this.height, 0]).domain([configuration.yMinimum || 0, configuration.yMaximum || 65535]); 57 | 58 | this.currentX = configuration.initialX || (((configuration.xMinimum || 0) + (configuration.xMaximum || 65535)) / 2); 59 | this.currentY = configuration.initialY || (((configuration.yMinimum || 0) + (configuration.yMaximum || 65535)) / 2); 60 | 61 | this.line = d3.line().x((d) => this.x(d.x)).y((d) => this.y(d.y)); 62 | 63 | const wrapper = d3.select('.graph-container').append('div').attr('class', 'graph-wrapper'); 64 | wrapper.append('h3').text(configuration.label); 65 | 66 | this.svg = wrapper.append('svg') 67 | .attr('width', this.width + this.margins.left + this.margins.right) 68 | .attr('height', this.height + this.margins.top + this.margins.bottom); 69 | this.g = this.svg.append('g').attr('transform', `translate(${this.margins.left},${this.margins.top})`); 70 | 71 | this.xAxis = this.g.append('g').attr('transform', `translate(0,${this.height})`).call(d3.axisBottom(this.x)); 72 | this.yAxis = this.g.append('g').call(d3.axisLeft(this.y)); 73 | 74 | datasource.subscribe(configuration.xGraphId, this.receivedX.bind(this)); 75 | datasource.subscribe(configuration.yGraphId, this.receivedY.bind(this)); 76 | 77 | this.path = this.g.append('path') 78 | .attr('fill', 'none') 79 | .attr('stroke', 'steelblue') 80 | .attr('stroke-linejoin', 'round') 81 | .attr('stroke-linecap', 'round') 82 | .attr('stroke-width', 1.5); 83 | 84 | window.requestAnimationFrame(this.updateGraph.bind(this)); 85 | 86 | this.points = []; 87 | } 88 | 89 | public stop() { 90 | this.stopped = true; 91 | } 92 | 93 | public continue() { 94 | this.stopped = false; 95 | } 96 | 97 | public receivedX(point: GraphPoint) { 98 | const xy = { 99 | timestamp: point.timestamp, 100 | y: this.currentY, 101 | x: point.value 102 | }; 103 | 104 | this.currentX = point.value; 105 | this.points.push(xy); 106 | } 107 | 108 | public receivedY(point: GraphPoint) { 109 | const xy = { 110 | timestamp: point.timestamp, 111 | y: point.value, 112 | x: this.currentX 113 | }; 114 | 115 | this.currentY = point.value; 116 | this.points.push(xy); 117 | } 118 | 119 | public updateGraph() { 120 | if (!this.stopped) { 121 | try { 122 | const now = new Date().getTime(); 123 | const limit = now - this.span; 124 | 125 | if (this.points.length > 0) { 126 | const last = this.points[this.points.length - 1]; 127 | 128 | this.points = this.points.filter((xy) => xy.timestamp >= limit); 129 | if (this.points.length === 0) { this.points.push(last); } 130 | 131 | this.path.datum(this.points).attr('d', this.line); 132 | } 133 | } catch (e) { 134 | console.log('Error Updating Plot: ', e); 135 | } 136 | } 137 | 138 | window.requestAnimationFrame(this.updateGraph.bind(this)); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/pemicro.ts: -------------------------------------------------------------------------------- 1 | import { DebugProtocol } from '@vscode/debugprotocol'; 2 | import { GDBServerController, ConfigurationArguments, createPortName, SWOConfigureEvent, genDownloadCommands } from './common'; 3 | import * as os from 'os'; 4 | import { EventEmitter } from 'events'; 5 | 6 | export class PEServerController extends EventEmitter implements GDBServerController { 7 | public portsNeeded: string[] = ['gdbPort', 'swoPort', 'consolePort']; 8 | public name: 'PE'; 9 | 10 | private args: ConfigurationArguments; 11 | private ports: { [name: string]: number }; 12 | 13 | constructor() { 14 | super(); 15 | } 16 | 17 | public setPorts(ports: { [name: string]: number }): void { 18 | this.ports = ports; 19 | } 20 | 21 | public setArguments(args: ConfigurationArguments): void { 22 | this.args = args; 23 | } 24 | 25 | public customRequest(command: string, response: DebugProtocol.Response, args: any): boolean { 26 | return false; 27 | } 28 | 29 | public initCommands(): string[] { 30 | const gdbport = this.ports[createPortName(this.args.targetProcessor)]; 31 | 32 | return [ 33 | `target-select extended-remote localhost:${gdbport}` 34 | ]; 35 | } 36 | 37 | public launchCommands(): string[] { 38 | const commands = [ 39 | ...genDownloadCommands(this.args, ['interpreter-exec console "monitor _reset"']), 40 | 'interpreter-exec console "monitor _reset"' 41 | ]; 42 | 43 | return commands; 44 | } 45 | 46 | public attachCommands(): string[] { 47 | const commands = [ 48 | 'interpreter-exec console "monitor halt"' 49 | ]; 50 | 51 | return commands; 52 | } 53 | 54 | public resetCommands(): string[] { 55 | const commands: string[] = [ 56 | 'interpreter-exec console "monitor _reset"' 57 | ]; 58 | 59 | return commands; 60 | } 61 | 62 | public swoAndRTTCommands(): string[] { 63 | // No commands needed for SWO. All are sent on the streaming port 64 | return []; 65 | } 66 | 67 | public serverExecutable() { 68 | console.log('Getting Exec'); 69 | if (this.args.serverpath) { 70 | return this.args.serverpath; 71 | } 72 | if (os.platform() === 'win32') { 73 | return 'pegdbserver_console.exe'; 74 | } else { 75 | return 'pegdbserver_console'; 76 | } 77 | } 78 | 79 | public allocateRTTPorts(): Promise { 80 | return Promise.resolve(); 81 | } 82 | 83 | public serverArguments(): string[] { 84 | const gdbport = this.ports['gdbPort']; 85 | 86 | let serverargs: string[] = []; 87 | 88 | serverargs.push('-startserver'); 89 | serverargs.push('-singlesession'); 90 | serverargs.push(`-device=${this.args.device}`); 91 | serverargs.push(`-serverport=${gdbport}`); 92 | 93 | if (this.args.ipAddress) { 94 | serverargs.push(`-serverip=${this.args.ipAddress}`); 95 | } 96 | 97 | if (this.args.rtos) { 98 | serverargs.push(`-kernal=${this.args.rtos}`); 99 | } 100 | 101 | if (this.args.interface === 'jtag') { // TODO: handle ctag in when this server supports it 102 | serverargs.push('-usejtag'); 103 | } 104 | 105 | if (this.args.configFiles) { 106 | serverargs.push(`-configfile=${this.args.configFiles[0]}`); 107 | } 108 | 109 | if (this.args.swoConfig.enabled) { 110 | const source = this.args.swoConfig.source; 111 | if (source === 'socket') { 112 | const swoPort = this.ports[createPortName(this.args.targetProcessor, 'swoPort')]; 113 | serverargs.push(`-streamingport=${swoPort}`); 114 | } 115 | } 116 | 117 | if (this.args.serverArgs) { 118 | serverargs = serverargs.concat(this.args.serverArgs); 119 | } 120 | 121 | return serverargs; 122 | } 123 | 124 | public initMatch(): RegExp { 125 | return /All Servers Running/g; 126 | } 127 | 128 | public serverLaunchStarted(): void {} 129 | public serverLaunchCompleted(): void {} 130 | public debuggerLaunchStarted(): void {} 131 | public debuggerLaunchCompleted(): void { 132 | if (this.args.swoConfig.enabled) { 133 | const source = this.args.swoConfig.source; 134 | if (source === 'socket') { 135 | const swoPortNm = createPortName(this.args.targetProcessor, 'swoPort'); 136 | this.emit('event', new SWOConfigureEvent({ 137 | type: 'socket', 138 | args: this.args, 139 | port: this.ports[swoPortNm].toString(10) 140 | })); 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/pyocd.ts: -------------------------------------------------------------------------------- 1 | import { DebugProtocol } from '@vscode/debugprotocol'; 2 | import { ConfigurationArguments, GDBServerController, SWOConfigureEvent, createPortName, genDownloadCommands, getGDBSWOInitCommands } from './common'; 3 | import { EventEmitter } from 'events'; 4 | 5 | export class PyOCDServerController extends EventEmitter implements GDBServerController { 6 | public readonly name: string = 'PyOCD'; 7 | public readonly portsNeeded: string[] = ['gdbPort', 'consolePort', 'swoPort']; 8 | 9 | private args: ConfigurationArguments; 10 | private ports: { [name: string]: number }; 11 | 12 | constructor() { 13 | super(); 14 | } 15 | 16 | public setPorts(ports: { [name: string]: number }): void { 17 | this.ports = ports; 18 | } 19 | 20 | public setArguments(args: ConfigurationArguments): void { 21 | this.args = args; 22 | } 23 | 24 | public customRequest(command: string, response: DebugProtocol.Response, args: any): boolean { 25 | return false; 26 | } 27 | 28 | public initCommands(): string[] { 29 | const gdbport = this.ports[createPortName(this.args.targetProcessor)]; 30 | 31 | return [ 32 | `target-select extended-remote localhost:${gdbport}`, 33 | // Following needed for SWO and accessing some peripherals. 34 | // Generally not a good thing to do 35 | 'interpreter-exec console "set mem inaccessible-by-default off"' 36 | ]; 37 | } 38 | 39 | public launchCommands(): string[] { 40 | const commands = [ 41 | ...genDownloadCommands(this.args, ['interpreter-exec console "monitor reset halt"']), 42 | 'interpreter-exec console "monitor reset halt"' 43 | ]; 44 | return commands; 45 | } 46 | 47 | public attachCommands(): string[] { 48 | const commands = [ 49 | 'interpreter-exec console "monitor halt"' 50 | ]; 51 | return commands; 52 | } 53 | 54 | public resetCommands(): string[] { 55 | const commands: string[] = [ 56 | 'interpreter-exec console "monitor reset"' 57 | ]; 58 | return commands; 59 | } 60 | 61 | public swoAndRTTCommands(): string[] { 62 | const commands: string[] = []; 63 | if (this.args.swoConfig.enabled) { 64 | const swocommands = this.SWOConfigurationCommands(); 65 | commands.push(...swocommands); 66 | } 67 | return commands; 68 | } 69 | 70 | private SWOConfigurationCommands(): string[] { 71 | const commands = getGDBSWOInitCommands(this.args.swoConfig); 72 | return commands.map((c) => `interpreter-exec console "${c}"`); 73 | } 74 | 75 | public serverExecutable(): string { 76 | const exeName = 'pyocd'; 77 | const ret = this.args.serverpath ? this.args.serverpath : exeName; 78 | return ret; 79 | } 80 | 81 | public allocateRTTPorts(): Promise { 82 | return Promise.resolve(); 83 | } 84 | 85 | public serverArguments(): string[] { 86 | const gdbport = this.ports['gdbPort']; 87 | const telnetport = this.ports['consolePort']; 88 | 89 | let serverargs = [ 90 | 'gdbserver', 91 | '--port', gdbport.toString(), 92 | '--telnet-port', telnetport.toString() 93 | ]; 94 | 95 | if (this.args.boardId) { 96 | serverargs.push('--board'); 97 | serverargs.push(this.args.boardId); 98 | } 99 | 100 | if (this.args.targetId) { 101 | serverargs.push('--target'); 102 | serverargs.push(this.args.targetId.toString()); 103 | } 104 | 105 | if (this.args.cmsisPack) { 106 | serverargs.push('--pack'); 107 | serverargs.push(this.args.cmsisPack.toString()); 108 | } 109 | 110 | if (this.args.swoConfig.enabled) { 111 | const source = this.args.swoConfig.source; 112 | if ((source === 'probe') || (source === 'socket') || (source === 'file')) { 113 | const swoPort = this.ports[createPortName(this.args.targetProcessor, 'swoPort')]; 114 | const cpuF = this.args.swoConfig.cpuFrequency; 115 | const swoF = this.args.swoConfig.swoFrequency || '1'; 116 | const args = [ 117 | '-O', 'enable_swv=1', 118 | '-O', 'swv_raw_enable=true', 119 | '-O', `swv_raw_port=${swoPort}`, 120 | '-O', `swv_system_clock=${cpuF}`, 121 | '-O', `swv_clock=${swoF}`]; 122 | serverargs.push(...args); 123 | } 124 | } 125 | 126 | if (this.args.serverArgs) { 127 | serverargs = serverargs.concat(this.args.serverArgs); 128 | } 129 | return serverargs; 130 | } 131 | 132 | public initMatch(): RegExp { 133 | return /GDB server started (at|on) port/; 134 | } 135 | 136 | public serverLaunchStarted(): void {} 137 | public serverLaunchCompleted(): void { 138 | if (this.args.swoConfig.enabled) { 139 | const source = this.args.swoConfig.source; 140 | if ((source === 'probe') || (source === 'socket') || (source === 'file')) { 141 | const swoPortNm = createPortName(this.args.targetProcessor, 'swoPort'); 142 | this.emit('event', new SWOConfigureEvent({ 143 | type: 'socket', 144 | args: this.args, 145 | port: this.ports[swoPortNm].toString(10) 146 | })); 147 | } else if (source === 'serial') { 148 | this.emit('event', new SWOConfigureEvent({ 149 | type: 'serial', 150 | args: this.args, 151 | device: this.args.swoConfig.source, 152 | baudRate: this.args.swoConfig.swoFrequency 153 | })); 154 | } 155 | } 156 | } 157 | 158 | public debuggerLaunchStarted(): void {} 159 | public debuggerLaunchCompleted(): void {} 160 | } 161 | -------------------------------------------------------------------------------- /src/qemu.ts: -------------------------------------------------------------------------------- 1 | import { DebugProtocol } from '@vscode/debugprotocol'; 2 | import { GDBServerController, ConfigurationArguments, createPortName } from './common'; 3 | import * as os from 'os'; 4 | import { EventEmitter } from 'events'; 5 | import { sync as commandExistsSync } from 'command-exists'; 6 | 7 | const EXECUTABLE_NAMES = ['qemu-system-arm']; 8 | 9 | export class QEMUServerController extends EventEmitter implements GDBServerController { 10 | public portsNeeded: string[] = ['gdbPort']; 11 | public name: 'QEMU'; 12 | 13 | private args: ConfigurationArguments; 14 | private ports: { [name: string]: number }; 15 | 16 | constructor() { 17 | super(); 18 | } 19 | 20 | public setPorts(ports: { [name: string]: number }): void { 21 | this.ports = ports; 22 | } 23 | 24 | public setArguments(args: ConfigurationArguments): void { 25 | this.args = args; 26 | } 27 | 28 | public customRequest(command: string, response: DebugProtocol.Response, args: any): boolean { 29 | return false; 30 | } 31 | 32 | public initCommands(): string[] { 33 | const gdbport = this.ports[createPortName(this.args.targetProcessor)]; 34 | 35 | return [ 36 | `target-select extended-remote localhost:${gdbport}` 37 | ]; 38 | } 39 | 40 | public launchCommands(): string[] { 41 | const commands: string[] = []; 42 | return commands; 43 | } 44 | 45 | public attachCommands(): string[] { 46 | const commands: string[] = []; 47 | return commands; 48 | } 49 | 50 | public resetCommands(): string[] { 51 | const commands: string[] = [ 52 | 'interpreter-exec console "monitor stop"', 53 | 'interpreter-exec console "monitor system_reset"' 54 | ]; 55 | 56 | return commands; 57 | } 58 | 59 | public swoAndRTTCommands(): string[] { 60 | return []; 61 | } 62 | 63 | public serverExecutable() { 64 | if (this.args.serverpath) { 65 | return this.args.serverpath; 66 | } 67 | for (const name of EXECUTABLE_NAMES) { 68 | if (commandExistsSync(name)) { return name; } 69 | } 70 | return 'qemu-system-arm'; 71 | } 72 | 73 | public allocateRTTPorts(): Promise { 74 | return Promise.resolve(); 75 | } 76 | 77 | public serverArguments(): string[] { 78 | const gdbport = this.ports['gdbPort']; 79 | 80 | let cmdargs = [ 81 | '-cpu', this.args.cpu, 82 | '-machine', this.args.machine, 83 | '-nographic', 84 | '-semihosting-config', 'enable=on,target=native', 85 | '-gdb', 'tcp::' + gdbport.toString(), 86 | '-S', 87 | '-kernel', this.args.executable 88 | ]; 89 | 90 | if (this.args.serverArgs) { 91 | cmdargs = cmdargs.concat(this.args.serverArgs); 92 | } 93 | 94 | return cmdargs; 95 | } 96 | 97 | public initMatch(): RegExp { 98 | return null; 99 | } 100 | 101 | public serverLaunchStarted(): void {} 102 | public serverLaunchCompleted(): void {} 103 | public debuggerLaunchStarted(): void {} 104 | public debuggerLaunchCompleted(): void {} 105 | } 106 | -------------------------------------------------------------------------------- /src/remote/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "out", 21 | "dist", 22 | "**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /src/remote/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | /node_modules 3 | *.vsix 4 | .vscode-test 5 | .DS_Store 6 | .sass-cache 7 | /dist 8 | /tmp -------------------------------------------------------------------------------- /src/remote/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher"] 5 | } 6 | -------------------------------------------------------------------------------- /src/remote/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/dist/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/**/*.js", 30 | "${workspaceFolder}/dist/**/*.js" 31 | ], 32 | "preLaunchTask": "tasks: watch-tests" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /src/remote/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files 5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true // set this to false to include "dist" folder in search results 10 | }, 11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 12 | "typescript.tsc.autoDetect": "off" 13 | } -------------------------------------------------------------------------------- /src/remote/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": [ 10 | "$ts-webpack-watch", 11 | "$tslint-webpack-watch" 12 | ], 13 | "isBackground": true, 14 | "presentation": { 15 | "reveal": "never", 16 | "group": "watchers" 17 | }, 18 | "group": { 19 | "kind": "build", 20 | "isDefault": true 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "watch-tests", 26 | "problemMatcher": "$tsc-watch", 27 | "isBackground": true, 28 | "presentation": { 29 | "reveal": "never", 30 | "group": "watchers" 31 | }, 32 | "group": "build" 33 | }, 34 | { 35 | "label": "tasks: watch-tests", 36 | "dependsOn": [ 37 | "npm: watch", 38 | "npm: watch-tests" 39 | ], 40 | "problemMatcher": [] 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /src/remote/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/** 4 | node_modules/** 5 | src/** 6 | .gitignore 7 | .yarnrc 8 | webpack.config.js 9 | vsc-extension-quickstart.md 10 | **/tsconfig.json 11 | **/.eslintrc.json 12 | **/*.map 13 | **/*.ts 14 | -------------------------------------------------------------------------------- /src/remote/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "cortex-debug-remote" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /src/remote/README.md: -------------------------------------------------------------------------------- 1 | # cortex-debug-remote 2 | 3 | This extension provides host services for the Cortex-Debug extension running in remote mode 4 | -------------------------------------------------------------------------------- /src/remote/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cortex-debug-remote", 3 | "displayName": "cortex-debug-remote", 4 | "description": "Extension to support VSCode remote developent with Cortex-Debug", 5 | "version": "0.0.1", 6 | "engines": { 7 | "vscode": "^1.63.0" 8 | }, 9 | "categories": [ 10 | "Other" 11 | ], 12 | "activationEvents": [ 13 | "onDebugResolve:cortex-debug", 14 | "onCommand:cortex-debug-remote.helloWorld" 15 | ], 16 | "main": "./dist/extension.js", 17 | "publisher": "marus25", 18 | "license": "MIT", 19 | "extensionKind": [ 20 | "ui" 21 | ], 22 | "contributes": { 23 | "commands": [ 24 | { 25 | "command": "cortex-debug-remote.helloWorld", 26 | "title": "Hello World" 27 | } 28 | ] 29 | }, 30 | "scripts": { 31 | "vscode:prepublish": "npm run package", 32 | "compile": "webpack", 33 | "watch": "webpack --watch", 34 | "package": "webpack --mode production --devtool hidden-source-map", 35 | "compile-tests": "tsc -p . --outDir out", 36 | "watch-tests": "tsc -p . -w --outDir out", 37 | "pretest": "npm run compile-tests && npm run compile && npm run lint", 38 | "lint": "eslint src --ext ts", 39 | "test": "node ./out/test/runTest.js" 40 | }, 41 | "devDependencies": { 42 | "@types/command-exists": "^1.2.0", 43 | "@types/glob": "^7.2.0", 44 | "@types/mocha": "^9.0.0", 45 | "@types/node": "14.x", 46 | "@types/vscode": "^1.63.0", 47 | "@typescript-eslint/eslint-plugin": "^5.9.1", 48 | "@typescript-eslint/parser": "^5.9.1", 49 | "@vscode/test-electron": "^2.0.3", 50 | "eslint": "^8.6.0", 51 | "glob": "^7.2.0", 52 | "mocha": "^11.1.0", 53 | "ts-loader": "^9.2.6", 54 | "typescript": "^4.5.4", 55 | "webpack": "^5.94.0", 56 | "webpack-cli": "^4.9.1" 57 | }, 58 | "dependencies": { 59 | "command-exists": "^1.2.9", 60 | "ip": "^1.1.9", 61 | "vscode-jsonrpc": "^6.0.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/remote/src/extension.ts: -------------------------------------------------------------------------------- 1 | // The module 'vscode' contains the VS Code extensibility API 2 | // Import the module and reference it with the alias vscode in your code below 3 | import { openStdin } from 'process'; 4 | import * as vscode from 'vscode'; 5 | import * as os from 'os'; 6 | import { Server } from './server'; 7 | import * as Interfaces from './interfaces'; 8 | 9 | export class CortexDebugRemote { 10 | private server: Server; 11 | private port: number = -1; 12 | 13 | constructor(public context: vscode.ExtensionContext) { 14 | console.log('in CortexDebugRemote::constructor'); 15 | vscode.window.showInformationMessage('cortex-debug-remote activated!'); 16 | const disposable = vscode.commands.registerCommand('cortex-debug-remote.helloWorld', this.hello.bind(this)); 17 | 18 | context.subscriptions.push(disposable); 19 | this.server = new Server(context); 20 | this.server.startServer().then((p) => { 21 | vscode.window.showInformationMessage(`CortexDebugRemote: Started server @ ${this.server.ipAddr}:${p}`); 22 | this.port = p; 23 | }, (e: any) => { 24 | vscode.window.showErrorMessage(`CortexDebugRemote: Error starting server: ${e.toString()}`); 25 | }); 26 | } 27 | 28 | public hello(arg: string): Interfaces.helloReturn { 29 | const str = `in Hello() from cortex-debug-remote! arg: ${arg}`; 30 | const ret = this.server.hello(arg); 31 | vscode.window.showInformationMessage(str + JSON.stringify(ret, undefined, 4)); 32 | console.log(str, ret); 33 | return ret; 34 | } 35 | } 36 | 37 | // this method is called when your extension is activated 38 | // your extension is activated the very first time the command is executed 39 | export function activate(context: vscode.ExtensionContext) { 40 | return new CortexDebugRemote(context); 41 | } 42 | 43 | // this method is called when your extension is deactivated 44 | export function deactivate() {} 45 | -------------------------------------------------------------------------------- /src/remote/src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { InterfaceIpsSet } from './tcpportscanner'; 2 | 3 | export enum RpcFuncNames { 4 | hello = 'hello', 5 | findFreePorts = 'findFreePorts', 6 | startGdbServer = 'startGdbServer', 7 | endGdbServer = 'endGdbServer', 8 | stdin = 'stdin', 9 | stdout = 'stdout', 10 | stderr = 'stderr' 11 | } 12 | 13 | export enum RpcEeventNames { 14 | stdout = 'stdout', 15 | stderr = 'stderr', 16 | serverExited = 'serverExited' 17 | } 18 | 19 | export interface helloReturn { 20 | port: number; 21 | host: string; 22 | addrs: InterfaceIpsSet; 23 | platform: NodeJS.Platform; 24 | release: string; 25 | version: string; 26 | hostname: string; 27 | mySessionId: string; 28 | mySettings: { [key: string]: any }; 29 | } 30 | 31 | export interface findFreePortsArgs { 32 | bindToAll: boolean; 33 | min: number; 34 | max: number; 35 | retrieve?: number; // default = 1 36 | consecutive?: boolean; // default = false 37 | doLog?: boolean; // default = false 38 | } 39 | 40 | export interface startGdbServerArgs { 41 | application: string; 42 | args: string[]; 43 | cwd?: string | null | undefined; 44 | } 45 | 46 | export interface stdinArgs { 47 | data: Buffer; 48 | encoding: 'utf8' | 'ascii'; 49 | } 50 | 51 | export interface eventArgs { 52 | type: RpcEeventNames; 53 | data: Buffer | number; // Always 'utf8'. number is exit-code if any 54 | } 55 | -------------------------------------------------------------------------------- /src/remote/src/server.ts: -------------------------------------------------------------------------------- 1 | import * as net from 'net'; 2 | import * as os from 'os'; 3 | import * as vscode from 'vscode'; 4 | import * as rpc from 'vscode-jsonrpc/node'; 5 | import { TcpPortScanner, InterfaceIpsSet } from './tcpportscanner'; 6 | import * as Interfaces from './interfaces'; 7 | 8 | export class Server { 9 | public readonly ipAddr: string = ''; 10 | public readonly allIps: InterfaceIpsSet; 11 | public port: number = -1; 12 | private server: net.Server | undefined; 13 | private useHost = '0.0.0.0'; // Maybe we should actual host-ip instead of opening it up 14 | 15 | constructor(public context: vscode.ExtensionContext) { 16 | this.allIps = TcpPortScanner.getExternalIPv4Addresses(); 17 | this.ipAddr = this.allIps.defaultIp || '127.0.0.1'; 18 | } 19 | 20 | public startServer(): Promise { 21 | return new Promise(async (resolve, reject) => { 22 | try { 23 | const start = 43473 + Math.floor(Math.random() * 10); 24 | const args = { min: start, max: start + 1000 }; 25 | const ports = await TcpPortScanner.findFreePorts(args); 26 | if (!ports || ports.length < 1) { 27 | throw new Error('Internal error: zero ports returned!?'); 28 | } 29 | this.port = ports[0]; 30 | this.server = new net.Server(this.newConnection.bind(this)); 31 | this.server.on(('error'), (e) => { 32 | // if (e.code === 'EADDRINUSE'), we have a bug or in findFreePorts or someone stole it 33 | reject(e); 34 | }); 35 | this.server.on('listening', () => { 36 | resolve(this.port); 37 | }); 38 | this.server.on('close', () => { 39 | this.server?.listen(this.port, this.useHost); 40 | }); 41 | this.server.listen(this.port, this.useHost); 42 | } catch (e: any) { 43 | reject(e); 44 | } 45 | }); 46 | } 47 | 48 | public hello(sessionId: string): Interfaces.helloReturn { 49 | const config = vscode.workspace.getConfiguration('cortex-debug'); 50 | const settings: { [key: string]: any } = {}; 51 | for (const [key, value] of Object.entries(config)) { 52 | if ((typeof value !== 'function') && (value !== null) && (value !== undefined)) { 53 | settings[key] = value; 54 | } 55 | } 56 | const ret: Interfaces.helloReturn = { 57 | port: this.port, 58 | host: this.ipAddr, 59 | addrs: this.allIps, 60 | platform: os.platform(), 61 | release: os.release(), 62 | version: os.version(), 63 | hostname: os.hostname(), 64 | mySessionId: vscode.env.sessionId, 65 | mySettings: settings 66 | }; 67 | return ret; 68 | } 69 | 70 | private newConnection(socket: net.Socket) { 71 | const client = new Client(socket, this); 72 | } 73 | } 74 | 75 | class Client { 76 | private connection: rpc.MessageConnection; 77 | 78 | constructor(protected socket: net.Socket, protected server: Server) { 79 | socket.setKeepAlive(true); 80 | this.connection = rpc.createMessageConnection( 81 | new rpc.StreamMessageReader(this.socket, 'utf-8'), 82 | new rpc.StreamMessageWriter(this.socket, 'utf-8') 83 | ); 84 | 85 | const notification = new rpc.NotificationType('event'); 86 | this.connection.onNotification(notification, (param: Interfaces.eventArgs) => { 87 | console.log(param); 88 | }); 89 | 90 | this.connection.onRequest( 91 | new rpc.RequestType1(Interfaces.RpcFuncNames.hello), 92 | this.hello.bind(this)); 93 | this.connection.onRequest( 94 | new rpc.RequestType2(Interfaces.RpcFuncNames.findFreePorts), 95 | this.findFreePorts.bind(this)); 96 | this.connection.onRequest( 97 | new rpc.RequestType2(Interfaces.RpcFuncNames.startGdbServer), 98 | this.startGdbServer.bind(this)); 99 | this.connection.onRequest( 100 | new rpc.RequestType1(Interfaces.RpcFuncNames.endGdbServer), 101 | this.endGdbServer.bind(this)); 102 | this.connection.onRequest( 103 | new rpc.RequestType2(Interfaces.RpcFuncNames.stdin), 104 | this.stdin.bind(this)); 105 | 106 | this.connection.listen(); 107 | const arg: Interfaces.eventArgs = { 108 | type: Interfaces.RpcEeventNames.stdout, 109 | data: Buffer.from('Message from server') 110 | }; 111 | this.connection.sendNotification(notification, arg); 112 | } 113 | 114 | public hello(sessionId: string): Interfaces.helloReturn { 115 | return this.server.hello(sessionId); 116 | } 117 | 118 | private async findFreePorts(sessionId: string, args: Interfaces.findFreePortsArgs): Promise { 119 | if (sessionId !== vscode.env.sessionId) { 120 | return []; 121 | } 122 | const ports = await TcpPortScanner.findFreePorts(args); 123 | return ports || []; 124 | } 125 | 126 | private startGdbServer(sessionId: string, args: Interfaces.startGdbServerArgs): boolean { 127 | return false; 128 | } 129 | 130 | private endGdbServer(sessionId: string): boolean { 131 | return false; 132 | } 133 | 134 | private stdin(sessionId: string, data: string): boolean { 135 | return false; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/remote/src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/remote/src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/remote/src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run((failures) => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /src/remote/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "lib": [ 6 | "ES2020" 7 | ], 8 | "sourceMap": true, 9 | "rootDir": "src", 10 | "strict": true /* enable all strict type-checking options */ 11 | /* Additional Checks */ 12 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 13 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 14 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 15 | }, 16 | "exclude": [ 17 | "node_modules", 18 | ".vscode-test" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/remote/webpack.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | // @ts-check 8 | /** @typedef {import('webpack').Configuration} WebpackConfig **/ 9 | 10 | /** @type WebpackConfig */ 11 | const extensionConfig = { 12 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 13 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') 14 | 15 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 16 | output: { 17 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 18 | path: path.resolve(__dirname, 'dist'), 19 | filename: 'extension.js', 20 | libraryTarget: 'commonjs2' 21 | }, 22 | externals: { 23 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 24 | // modules added here also need to be added in the .vscodeignore file 25 | }, 26 | resolve: { 27 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 28 | extensions: ['.ts', '.js'] 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.ts$/, 34 | exclude: /node_modules/, 35 | use: [ 36 | { 37 | loader: 'ts-loader' 38 | } 39 | ] 40 | } 41 | ] 42 | }, 43 | devtool: 'nosources-source-map', 44 | infrastructureLogging: { 45 | level: 'log', // enables logging required for problem matchers 46 | }, 47 | }; 48 | module.exports = [extensionConfig]; 49 | -------------------------------------------------------------------------------- /src/reporting.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as os from 'os'; 3 | import * as path from 'path'; 4 | import * as fs from 'fs'; 5 | import * as ua from 'universal-analytics'; 6 | import { ConfigurationArguments } from './common'; 7 | 8 | import { v4 as uuidv4 } from 'uuid'; 9 | 10 | const extension = vscode.extensions.getExtension('marus25.cortex-debug'); 11 | const extensionId = extension.id; 12 | const extensionVersion = extension.packageJSON.version; 13 | const trackingId = 'UA-113901869-1'; 14 | 15 | const VSCODE_VERSION_DIMENSION = 'cd1'; 16 | const EXTENSION_VERSION_DIMENSION = 'cd2'; 17 | const DEVICE_ID_DIMENSION = 'cd3'; 18 | const RTOS_TYPE_DIMENSION = 'cd4'; 19 | const GDB_SERVER_TYPE_DIMENSION = 'cd5'; 20 | const PLATFORM_TYPE_DIMENSION = 'cd6'; 21 | const PLATFORM_RELEASE_DIMENSION = 'cd7'; 22 | const NODE_VERSION_DIMENSION = 'cd8'; 23 | 24 | let analytics: any; 25 | 26 | let uuid: string = null; 27 | const sessionStarts: { [id: string]: Date } = {}; 28 | 29 | interface UserSettings { 30 | uuid: string; 31 | } 32 | 33 | function getUUID(): string { 34 | if (!uuid) { 35 | const settingspath = path.join(os.homedir(), '.cortex-debug'); 36 | if (fs.existsSync(settingspath)) { 37 | const data = fs.readFileSync(settingspath, 'utf8'); 38 | const settings: UserSettings = JSON.parse(data); 39 | uuid = settings.uuid; 40 | } else { 41 | uuid = uuidv4(); 42 | const settings: UserSettings = { uuid: uuid }; 43 | fs.writeFileSync(settingspath, JSON.stringify(settings), 'utf8'); 44 | } 45 | } 46 | 47 | return uuid; 48 | } 49 | 50 | function telemetryEnabled(): boolean { 51 | const telemetry = vscode.workspace.getConfiguration('telemetry'); 52 | const cortexDebug = vscode.workspace.getConfiguration('cortex-debug'); 53 | 54 | return telemetry.get('enableTelemetry', false) && cortexDebug.get('enableTelemetry', false); 55 | } 56 | 57 | function activate(context: vscode.ExtensionContext) { 58 | if (!telemetryEnabled()) { return; } 59 | 60 | analytics = ua(trackingId, getUUID()); 61 | analytics.set(EXTENSION_VERSION_DIMENSION, extensionVersion); 62 | analytics.set(VSCODE_VERSION_DIMENSION, vscode.version); 63 | analytics.set(PLATFORM_TYPE_DIMENSION, os.platform()); 64 | analytics.set(PLATFORM_RELEASE_DIMENSION, os.release()); 65 | analytics.set(NODE_VERSION_DIMENSION, process.versions.node); 66 | } 67 | 68 | function deactivate() { 69 | if (!telemetryEnabled()) { return; } 70 | } 71 | 72 | function sendEvent(category: string, action: string, label?: string, value?: number, options: { [key: string]: string } = {}) { 73 | if (!telemetryEnabled()) { return; } 74 | 75 | analytics.event(category, action, label, Math.round(value), options).send(); 76 | } 77 | 78 | function beginSession(id: string, opts: ConfigurationArguments) { 79 | if (!telemetryEnabled()) { return; } 80 | 81 | if (opts.rtos) { analytics.set(RTOS_TYPE_DIMENSION, opts.rtos); } 82 | if (opts.device) { analytics.set(DEVICE_ID_DIMENSION, opts.device); } 83 | analytics.set(GDB_SERVER_TYPE_DIMENSION, opts.servertype); 84 | 85 | analytics.screenview('Debug Session', 'Cortex-Debug', extensionVersion, extensionId); 86 | analytics.event('Session', 'Started', '', 0, { sessionControl: 'start' }); 87 | 88 | if (opts.swoConfig.enabled) { 89 | analytics.event('SWO', 'Used'); 90 | } 91 | if (opts.rttConfig.enabled) { 92 | analytics.event('RTT', 'Used'); 93 | } 94 | if (opts.graphConfig.length > 0) { 95 | analytics.event('Graphing', 'Used'); 96 | } 97 | 98 | sessionStarts[id] = new Date(); 99 | 100 | analytics.send(); 101 | } 102 | 103 | function endSession(id: string) { 104 | if (!telemetryEnabled()) { return; } 105 | 106 | const startTime = sessionStarts[id]; 107 | if (startTime) { 108 | const endTime = new Date(); 109 | const time = (endTime.getTime() - startTime.getTime()) / 1000; 110 | delete sessionStarts[id]; 111 | 112 | setTimeout(() => { 113 | analytics.event('Session', 'Completed', '', Math.round(time), { sessionControl: 'end' }).send(); 114 | }, 500); 115 | } 116 | } 117 | 118 | export default { 119 | beginSession: beginSession, 120 | endSession: endSession, 121 | activate: activate, 122 | deactivate: deactivate, 123 | sendEvent: sendEvent 124 | }; 125 | -------------------------------------------------------------------------------- /src/stutil.ts: -------------------------------------------------------------------------------- 1 | import { DebugProtocol } from '@vscode/debugprotocol'; 2 | import { GDBServerController, ConfigurationArguments, SWOConfigureEvent, createPortName, genDownloadCommands, getGDBSWOInitCommands } from './common'; 3 | import * as os from 'os'; 4 | import { EventEmitter } from 'events'; 5 | 6 | export class STUtilServerController extends EventEmitter implements GDBServerController { 7 | public readonly name: string = 'ST-Util'; 8 | public readonly portsNeeded: string[] = ['gdbPort']; 9 | 10 | private args: ConfigurationArguments; 11 | private ports: { [name: string]: number }; 12 | 13 | constructor() { 14 | super(); 15 | } 16 | 17 | public setPorts(ports: { [name: string]: number }): void { 18 | this.ports = ports; 19 | } 20 | 21 | public setArguments(args: ConfigurationArguments): void { 22 | this.args = args; 23 | } 24 | 25 | public customRequest(command: string, response: DebugProtocol.Response, args: any): boolean { 26 | return false; 27 | } 28 | 29 | public initCommands(): string[] { 30 | const gdbport = this.ports[createPortName(this.args.targetProcessor)]; 31 | 32 | return [ 33 | `target-select extended-remote localhost:${gdbport}` 34 | ]; 35 | } 36 | 37 | public launchCommands(): string[] { 38 | const commands = [ 39 | 'interpreter-exec console "monitor halt"', 40 | ...genDownloadCommands(this.args, ['interpreter-exec console "monitor reset"']), 41 | 'interpreter-exec console "monitor reset"' 42 | ]; 43 | return commands; 44 | } 45 | 46 | public attachCommands(): string[] { 47 | const commands = [ 48 | 'interpreter-exec console "monitor halt"' 49 | ]; 50 | return commands; 51 | } 52 | 53 | public resetCommands(): string[] { 54 | const commands: string[] = [ 55 | 'interpreter-exec console "monitor halt"', 56 | 'interpreter-exec console "monitor reset"' 57 | ]; 58 | return commands; 59 | } 60 | 61 | public swoAndRTTCommands(): string[] { 62 | const commands: string[] = []; 63 | if (this.args.swoConfig.enabled && this.args.swoConfig.source !== 'probe') { 64 | const swocommands = this.SWOConfigurationCommands(); 65 | commands.push(...swocommands); 66 | } 67 | return commands; 68 | } 69 | 70 | private SWOConfigurationCommands(): string[] { 71 | const commands = getGDBSWOInitCommands(this.args.swoConfig); 72 | return commands.map((c) => `interpreter-exec console "${c}"`); 73 | } 74 | 75 | public serverExecutable(): string { 76 | if (this.args.serverpath) { 77 | return this.args.serverpath; 78 | } 79 | return os.platform() === 'win32' ? 'st-util.exe' : 'st-util'; 80 | } 81 | 82 | public allocateRTTPorts(): Promise { 83 | return Promise.resolve(); 84 | } 85 | 86 | public serverArguments(): string[] { 87 | const gdbport = this.ports['gdbPort']; 88 | 89 | let serverargs = ['-p', gdbport.toString(), '--no-reset']; 90 | if (this.args.v1) { 91 | serverargs.push('--stlinkv1'); 92 | } 93 | 94 | if (this.args.serialNumber) { 95 | serverargs.push('--serial', this.args.serialNumber); 96 | } 97 | 98 | if (this.args.serverArgs) { 99 | serverargs = serverargs.concat(this.args.serverArgs); 100 | } 101 | 102 | return serverargs; 103 | } 104 | 105 | public initMatch(): RegExp { 106 | return /Listening at \*/g; 107 | } 108 | 109 | public serverLaunchStarted(): void {} 110 | public serverLaunchCompleted(): void { 111 | if (this.args.swoConfig.enabled && this.args.swoConfig.source !== 'probe') { 112 | this.emit('event', new SWOConfigureEvent({ 113 | type: 'serial', 114 | args: this.args, 115 | device: this.args.swoConfig.source, 116 | baudRate: this.args.swoConfig.swoFrequency 117 | })); 118 | } 119 | } 120 | 121 | public debuggerLaunchStarted(): void {} 122 | public debuggerLaunchCompleted(): void {} 123 | } 124 | -------------------------------------------------------------------------------- /src/symbols.ts: -------------------------------------------------------------------------------- 1 | import { DisassemblyInstruction } from './common'; 2 | 3 | export enum SymbolType { 4 | Function, 5 | File, 6 | Object, 7 | Normal 8 | } 9 | 10 | export enum SymbolScope { 11 | Local, 12 | Global, 13 | Neither, 14 | Both 15 | } 16 | 17 | export interface SymbolInformation { 18 | addressOrig: number; 19 | address: number; 20 | length: number; 21 | name: string; 22 | file: number | string; // The actual file name parsed (more reliable with nm) 23 | section?: string; // Not available with nm 24 | type: SymbolType; 25 | scope: SymbolScope; 26 | isStatic: boolean; 27 | // line?: number; // Only available when using nm 28 | instructions: DisassemblyInstruction[]; 29 | hidden: boolean; 30 | } 31 | -------------------------------------------------------------------------------- /support/gdb-swo.init: -------------------------------------------------------------------------------- 1 | # To get SWO working, can do it in you C files or let the debugger do it for you by calling the function 2 | # defined below. For a C implementation see swo-init.c file in this directory or see 3 | # https://github.com/Marus/cortex-debug/tree/master/support 4 | # 5 | # What is defined below still has limitations because it is generic and does not have any device specific 6 | # details as we have no way of knowing those details 7 | # 8 | # a) The Trace hardware may not be using the cpu clock. It may be divided by 2 already. Or it is not even 9 | # enabled. If it is simply a matter of division, then you can adjust the pre-scalar below 10 | # b) The SWO IO may not be properly configured or hooked up to the 11 | # c) Your HW uses custome base addresses that are not even documented in your CMSIS headers (they might contain 12 | # defaults from ARM). Make sure all the bases addresses below are correct and adjust if necessary 13 | # 14 | # If you need to make a custom version of this, copy this file to your project and use the preLaunch/preAttach 15 | # commands to override the default implementation. For example 16 | # 17 | # "preLaunchCommands": [ 18 | # "source \"${workspaceFolder}/myswo.init\"" 19 | # ] 20 | # 21 | 22 | # 23 | # Following variables are auto-defined by Cortex-Debug 24 | # 25 | # $swoFreq -- Baud rate (freq in Hz) from launch.json (swoConfig.swoFrequency) 26 | # $cpuFreq -- CPU Freq. in Hz from launch.json (swoConfig.cpuFrequency) 27 | # $swoPortMask -- The ITM ports to enable, calculated based on ports used in swoConfig (launch.json) 28 | # $swoFormat -- NRZ/UART(2) vs Manchester(1) 29 | # 30 | 31 | set $swoFormat = 0x00000002 32 | 33 | # We wish we could do this whole thing in python some sane language but python is not enabled 34 | # in many distributions of gdb 35 | 36 | # Instead of overriding the whole SWO_Init definition, you can also add your own functionality using 37 | # the Pre/Post definitions and loading them via preLaunchCommands in launch.json. We define empty 38 | # ones below 39 | define SWO_Init_Pre 40 | end 41 | define SWO_Init_Post 42 | end 43 | 44 | define SWO_Init 45 | set language c 46 | 47 | SWO_Init_Pre 48 | 49 | set language c 50 | 51 | # Enable trace 52 | # set *($DCB_BASE + 0x0000C) = (1 << 24) 53 | 54 | # TPI->SPPR Pin protocolo register to NRZ/Manchester 55 | set *($TPI_BASE + 0x000F0) = $swoFormat 56 | 57 | # TPI->ACPR = Prescalar. Change the following if your Prescalar calculation is different if your input clock is different## 58 | set *($TPI_BASE + 0x00010) = ($cpuFreq / $swoFreq) - 1 59 | 60 | # ITM->LAR To be able to access rest of the ITM 61 | set *($ITM_BASE + 0x00FB0) = 0xC5ACCE55 62 | 63 | # ITM->TCR = ITM_TCR_TraceBusID_Msk | ITM_TCR_SWOENA_Msk | ITM_TCR_SYNCENA_Msk | ITM_TCR_ITMENA_Msk 64 | set *($ITM_BASE + 0x00E80) = 0x1000D 65 | 66 | # ITM->TPR = ITM_TPR_PRIVMASK_Msk 67 | set *($ITM_BASE + 0x00E40) = 0xF 68 | 69 | # ITM->TER = bit-mask calculated by Cortex-Debug. You can set it to 0xFFFFFFFF if you like 70 | set *($ITM_BASE + 0x00E00) = $swoPortMask 71 | 72 | # DWT_CTRL 73 | set *($DWT_BASE) = 0x400003FE 74 | 75 | # TPI->FFCR (Formatter and Flush Control Register) 76 | set *($TPI_BASE + 0x00304) = 0x00000100 77 | 78 | SWO_Init_Post 79 | 80 | set language auto 81 | end 82 | 83 | document SWO_Init 84 | Usage: SWO_Init 85 | Initializes relevant CoreSight component registers to enable SWO. It relies 86 | on a few global variables $swoFreq, $cpuFreq and $swoPortMask 87 | end 88 | -------------------------------------------------------------------------------- /support/gdbsupport.init: -------------------------------------------------------------------------------- 1 | # The following are ARM CoreSight blocks but the Silicon Vendors are free to chose 2 | # alternate base addresses. Check with your vendor documentation 3 | set language c 4 | set $ITM_BASE = 0xE0000000 5 | set $DWT_BASE = 0xE0001000 6 | set $SCS_BASE = 0xE000E000 7 | set $SCB_BASE = $SCS_BASE + 0xD00 8 | set $DCB_BASE = $SCB_BASE + 0xF0 9 | set $TPI_BASE = 0xE0040000 10 | set language auto 11 | 12 | # 13 | # Help needed: There are many useful functions here but most of them use hardcoded addresses 14 | # that may not be the same for all devices. Wish we can port all these functions to something 15 | # more usable (modifiable). In the meantime we are going to use the function defined in gdb-swo.Initializes 16 | # 17 | define EnableITMAccess 18 | set language c 19 | set *($DCB_BASE + 0xC) |= 0x1000000 20 | set *($ITM_BASE + 0xFB0) = 0xC5ACCE55 21 | set language auto 22 | end 23 | 24 | define BaseSWOSetup 25 | set language c 26 | 27 | # NRZ is the default format 28 | set $format = 2 29 | if $argc > 1 30 | # format specified explicitly 31 | set $format = $arg1 32 | end 33 | 34 | set *($TPI_BASE + 0x304) = 0x100 35 | set *($TPI_BASE + 0x0F0) = $format 36 | set *($TPI_BASE + 0x010) = $arg0 37 | set *($DWT_BASE) &= ~(0x8000) 38 | set *($DWT_BASE) |= 0xBFF 39 | set language auto 40 | end 41 | 42 | define SetITMTimestampFrequency 43 | set language c 44 | set *($ITM_BASE + 0xE80) &= ~(0x3 << 10) 45 | set *($ITM_BASE + 0xE80) |= ($arg0 << 10) 46 | set language auto 47 | end 48 | 49 | define SetITMTimestampPrescale 50 | set language c 51 | set *($ITM_BASE + 0xE80) &= ~(0x3 << 8) 52 | set *($ITM_BASE + 0xE80) |= ($arg0 << 8) 53 | set language auto 54 | end 55 | 56 | define EnableITMPorts 57 | set language c 58 | set *($ITM_BASE + 0xE00) |= $arg0 59 | set language auto 60 | end 61 | 62 | define DisableITMPorts 63 | set language c 64 | set *($ITM_BASE + 0xE00) &= ~($arg0) 65 | set language auto 66 | end 67 | 68 | define SetITMId 69 | set language c 70 | set *($ITM_BASE + 0xE80) &= ~(0x7F << 16) 71 | set *($ITM_BASE + 0xE80) |= ($arg0 << 16) 72 | set language auto 73 | end 74 | 75 | define ITMGlobalEnable 76 | set language c 77 | set $busy = ((*($ITM_BASE + 0xE80) & 0x800000)) 78 | while ($busy) 79 | set $busy = ((*($ITM_BASE + 0xE80) & 0x800000)) 80 | end 81 | 82 | set *($ITM_BASE + 0xE80) |= 0x1 83 | 84 | set $busy = ((*($ITM_BASE + 0xE80) & 0x800000)) 85 | while ($busy) 86 | set $busy = ((*($ITM_BASE + 0xE80) & 0x800000)) 87 | end 88 | set language auto 89 | end 90 | 91 | define ITMGlobalDisable 92 | set language c 93 | set $busy = ((*($ITM_BASE + 0xE80) & 0x800000)) 94 | while ($busy) 95 | set $busy = ((*($ITM_BASE + 0xE80) & 0x800000)) 96 | end 97 | 98 | set *($ITM_BASE + 0xE80) &= ~(0x1) 99 | 100 | set $busy = ((*($ITM_BASE + 0xE80) & 0x800000)) 101 | while ($busy) 102 | set $busy = ((*($ITM_BASE + 0xE80) & 0x800000)) 103 | end 104 | set language auto 105 | end 106 | 107 | define ITMTimestampEnable 108 | set language c 109 | set *($ITM_BASE + 0xE80) |= 0x302 110 | set language auto 111 | end 112 | 113 | define ITMTimestampDisable 114 | set language c 115 | set *($ITM_BASE + 0xE80) &= ~(0x2) 116 | set language auto 117 | end 118 | 119 | define ITMSyncEnable 120 | set language c 121 | set *($ITM_BASE + 0xE80) |= 0x4 122 | set language auto 123 | end 124 | 125 | define ITMSyncDisable 126 | set language c 127 | set *($ITM_BASE + 0xE80) &= ~(0x4) 128 | set language auto 129 | end 130 | 131 | define ITMDWTTransferEnable 132 | set language c 133 | set *($ITM_BASE + 0xE80) |= 0x8 134 | set language auto 135 | end 136 | 137 | define ITMDWTTransferDisable 138 | set language c 139 | set *($ITM_BASE + 0xE80) &= (~0x8) 140 | set language auto 141 | end 142 | 143 | define EnableDWTSync 144 | set language c 145 | set *($DWT_BASE) |= 0x800 146 | set language auto 147 | end 148 | 149 | define DisableDWTSync 150 | set language c 151 | set *($DWT_BASE) &= ~(0x800) 152 | set language auto 153 | end 154 | 155 | define EnablePCSample 156 | set language c 157 | set *($DWT_BASE + 0x004) = 0x0 158 | set *($DWT_BASE) |= 0x1201 159 | set language auto 160 | end 161 | 162 | define DisablePCSample 163 | set language c 164 | set *($DWT_BASE) &= ~(0x1000) 165 | set language auto 166 | end 167 | 168 | define EnableCycleCountEvent 169 | set language c 170 | set *($DWT_BASE) |= 0x400000 171 | set language auto 172 | end 173 | 174 | define DisableCycleCountEvent 175 | set language c 176 | set *($DWT_BASE) &= ~(0x400000) 177 | set language auto 178 | end 179 | 180 | define EnableFoldedEvent 181 | set language c 182 | set *($DWT_BASE) |= 0x200000 183 | set language auto 184 | end 185 | 186 | define DisableFoldedEvent 187 | set language c 188 | set *($DWT_BASE) &= ~(0x200000) 189 | set language auto 190 | end 191 | 192 | define EnableLSUCountEvent 193 | set language c 194 | set *($DWT_BASE) |= 0x100000 195 | set language auto 196 | end 197 | 198 | define DisableLSUCountEvent 199 | set language c 200 | set *($DWT_BASE) &= ~(0x100000) 201 | set language auto 202 | end 203 | 204 | define EnableSleepCountEvent 205 | set language c 206 | set *($DWT_BASE) |= 0x80000 207 | set language auto 208 | end 209 | 210 | define DisableSleepCountEvent 211 | set language c 212 | set *($DWT_BASE) &= ~(0x80000) 213 | set language auto 214 | end 215 | 216 | define EnableInterruptOverheadEvent 217 | set language c 218 | set *($DWT_BASE) |= 0x40000 219 | set language auto 220 | end 221 | 222 | define DisableInterruptOverheadEvent 223 | set language c 224 | set *($DWT_BASE) &= ~(0x40000) 225 | set language auto 226 | end 227 | 228 | define EnableCPICountEvent 229 | set language c 230 | set *($DWT_BASE) |= 0x20000 231 | set language auto 232 | end 233 | 234 | define DisableCPICountEvent 235 | set language c 236 | set *($DWT_BASE) &= ~(0x20000) 237 | set language auto 238 | end 239 | 240 | define SoftwareReset 241 | set language c 242 | 243 | set $halt = 0 244 | 245 | if $argc 246 | set $halt = $arg0 247 | end 248 | 249 | if $halt 250 | set $demcr = *($DCB_BASE + 0xC) 251 | set *($DCB_BASE + 0xC) |= 1 252 | end 253 | 254 | set *0xE000ED0C = 0x05FA0004 255 | set $busy = (*0xE000ED0C & 0x4) 256 | while ($busy) 257 | set $busy = (*0xE000ED0C & 0x4) 258 | end 259 | 260 | if $halt 261 | # restore DEMCR value 262 | set *($DCB_BASE + 0xC) = $demcr 263 | end 264 | 265 | set language auto 266 | end 267 | -------------------------------------------------------------------------------- /support/openocd-helpers.tcl: -------------------------------------------------------------------------------- 1 | # 2 | # Cortex-Debug extension calls this function during initialization. You can copy this 3 | # file, modify it and specifyy it as one of the config files supplied in launch.json 4 | # preferably at the beginning. 5 | # 6 | # Note that this file simply defines a function for use later when it is time to configure 7 | # for SWO. 8 | # 9 | set USE_SWO 0 10 | proc CDSWOConfigure { CDCPUFreqHz CDSWOFreqHz CDSWOOutput } { 11 | # We don't create/configure the entire TPIU which requires advanced knowledge of the device 12 | # like which DAP/AP ports to use, what their bases addresses are, etc. That should already 13 | # be done by the config files from the Silicon Vendor 14 | catch {tpiu init}; # we are allowed to call this multiple times. So, call it just in case 15 | set tipu_names [tpiu names] 16 | if { [llength $tipu_names] == 0 } { 17 | puts stderr "[info script]: Error: Could not find TPIU/SWO names. Perhaps it hasn't been created?" 18 | } else { 19 | set mytpiu [lindex $tipu_names 0] 20 | # We don't create/configure the entire TPIU which requires advanced knowledge of the device 21 | # like which DAP/AP ports to use, what their bases addresses are, etc. That should already 22 | # be done by the config files from the Silicon Vendor 23 | puts "[info script]: $mytpiu configure -protocol uart -output $CDSWOOutput -traceclk $CDCPUFreqHz -pin-freq $CDSWOFreqHz" 24 | $mytpiu configure -protocol uart -output $CDSWOOutput -traceclk $CDCPUFreqHz -pin-freq $CDSWOFreqHz 25 | $mytpiu enable 26 | } 27 | } 28 | 29 | # 30 | # The following function may not work in a multi-core setup. You may want to overide this function 31 | # to enable RTOS detection for each core appropriately. This function must be called before `init` and 32 | # after all the targets are created. 33 | # 34 | proc CDRTOSConfigure { rtos } { 35 | set target [target current] 36 | if { $target != "" } { 37 | puts "[info script]: $target configure -rtos $rtos" 38 | $target configure -rtos $rtos 39 | } else { 40 | # Maybe this function was called too early? 41 | puts stderr "[info script]: Error: No current target. Could not configure target for RTOS" 42 | } 43 | } 44 | 45 | # 46 | # CDLiveWatchSetup 47 | # This function must be called before the init is called and after all the targets are created. You can create 48 | # a custom version of this function (even empty) if you already setup the gdb-max-connections elsewhere 49 | # 50 | # We increment all gdb-max-connections by one if it is already a non-zero. Note that if it was already set to -1, 51 | # we leave it alone as it means unlimited connections 52 | # 53 | proc CDLiveWatchSetup {} { 54 | try { 55 | foreach tgt [target names] { 56 | set nConn [$tgt cget -gdb-max-connections] 57 | if { $nConn > 0 } { 58 | incr nConn 59 | $tgt configure -gdb-max-connections $nConn 60 | puts "[info script]: Info: Setting gdb-max-connections for target '$tgt' to $nConn" 61 | } 62 | } 63 | } on error {} { 64 | puts stderr "[info script]: Error: Failed to increase gdb-max-connections for current target. Live variables will not work" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /support/swo-init.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The following is from 3 | * https://community.st.com/s/question/0D53W00000IWs2g/how-to-enable-swo-with-stlink-utility-on-stm32f407gdisc1 4 | * But there are other implementations that are virtually identical 5 | * https://mcuoneclipse.com/2016/10/17/tutorial-using-single-wire-output-swo-with-arm-cortex-m-and-eclipse/#more-19719 6 | */ 7 | //------------------------------------------------------------------------------------------------- 8 | /* 9 | Initialize the SWO trace port for debug message printing 10 | portMask : Stimulus bit mask to be configured 11 | cpuCoreFreqHz : CPU core clock frequency in Hz 12 | baudrate : SWO frequency in Hz 13 | */ 14 | 15 | void swoInit (uint32_t portMask, uint32_t cpuCoreFreqHz, uint32_t baudrate) 16 | { 17 | uint32_t SWOPrescaler = (cpuCoreFreqHz / baudrate) - 1u ; // baudrate in Hz, note that cpuCoreFreqHz is expected to match the CPU core clock 18 | 19 | CoreDebug->DEMCR = CoreDebug_DEMCR_TRCENA_Msk; // Debug Exception and Monitor Control Register (DEMCR): enable trace in core debug 20 | // Uncomment following for ST devices 21 | // DBGMCU->CR = 0x00000027u ; // DBGMCU_CR : TRACE_IOEN DBG_STANDBY DBG_STOP DBG_SLEEP 22 | TPI->SPPR = 0x00000002u ; // Selected PIN Protocol Register: Select which protocol to use for trace output (2: SWO) 23 | TPI->ACPR = SWOPrescaler ; // Async Clock Prescaler Register: Scale the baud rate of the asynchronous output 24 | ITM->LAR = 0xC5ACCE55u ; // ITM Lock Access Register: C5ACCE55 enables more write access to Control Register 0xE00 :: 0xFFC 25 | ITM->TCR = 0x0001000Du ; // ITM Trace Control Register 26 | ITM->TPR = ITM_TPR_PRIVMASK_Msk ; // ITM Trace Privilege Register: All stimulus ports 27 | ITM->TER = portMask ; // ITM Trace Enable Register: Enabled tracing on stimulus ports. One bit per stimulus port. 28 | DWT->CTRL = 0x400003FEu ; // Data Watchpoint and Trace Register 29 | TPI->FFCR = 0x00000100u ; // Formatter and Flush Control Register 30 | 31 | // ITM/SWO works only if enabled from debugger. 32 | // If ITM stimulus 0 is not free, don't try to send data to SWO 33 | if (ITM->PORT [0].u8 == 1) 34 | { 35 | bItmAvailable = 1 ; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /syntaxes/cortex-debug-disassembly.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cortex-Debug Disassembly", 3 | "scopeName": "source.cortex-debug-disassembly", 4 | "uuid": "7500223D-3F3F-47C2-8020-354566B001BD", 5 | "patterns": [ 6 | { 7 | "comment": "Address, bytes and opcode", 8 | "name": "meta.instruction", 9 | "match": "^(0x[A-Za-z0-9]{8}):\\s([a-zA-Z0-9]{2}\\s)+\\s+([\\w\\.]+)", 10 | "captures": { 11 | "1": { "name": "constant.numeric" }, 12 | "3": { "name": "keyword.opcode" } 13 | } 14 | }, 15 | { 16 | "comment": "Numeric constant", 17 | "name": "constant.numeric", 18 | "match": "(0x[0-9A-Fa-f]+)|(#[0-9]+)" 19 | }, 20 | { 21 | "comment": "Register", 22 | "name": "variable.language", 23 | "match": "\b((r[0-9])|(r1[0-5])|(sp)|(lr)|(pc)|(xpsr)|(msp)|(psp)|(primask)|(basepri)|(s[0-9])|(s[1-2][0-9])|(s3[0-1]))\b" 24 | }, 25 | { 26 | "comment": "End of line comment", 27 | "name": "comment.line.semicolon", 28 | "match": ";.*$" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /syntaxes/cortex-debug-memoryview.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cortex-Debug Memory View", 3 | "scopeName": "source.cortex-debug-memoryview", 4 | "patterns": [ 5 | { 6 | "comment": "Heading", 7 | "name": "keyword.header.hex", 8 | "match": "^ Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F \\t$" 9 | }, 10 | { 11 | "comment": "Address", 12 | "name": "keyword.address.hex", 13 | "match": "^([a-fA-F\\d]{8}:)" 14 | }, 15 | { 16 | "comment": "ASCII", 17 | "name": "comment.ascii.hex", 18 | "match": " [^\\s]*$" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /test/runTests.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to the extension test script 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export async function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd' 9 | }); 10 | 11 | (mocha.options as any).color = true; 12 | 13 | const testsRoot = path.resolve(__dirname, '..'); 14 | 15 | const files = await glob.glob('**/**.test.js', { cwd: testsRoot }); 16 | 17 | // Add files to the test suite 18 | files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); 19 | 20 | try { 21 | // Run the mocha test 22 | await new Promise((c, e) => { 23 | mocha.run((failures) => { 24 | if (failures > 0) { 25 | e(new Error(`${failures} tests failed.`)); 26 | } else { 27 | c(); 28 | } 29 | }); 30 | }); 31 | } catch (err) { 32 | console.error(err); 33 | throw err; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/suite/serialport.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | suite('Serial Port tests', () => { 4 | test('Serial Port list', async () => { 5 | let SerialPort; 6 | try { 7 | SerialPort = module.require('serialport').SerialPort; 8 | } catch (e) { 9 | assert.fail(e); 10 | } 11 | 12 | try { 13 | const ports = await SerialPort.list(); 14 | for (const port of ports) { 15 | console.log('\tFound port: ' + port.path); 16 | } 17 | } catch (e) { 18 | assert.fail(e); 19 | } 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/suite/tcpportscanner.test.ts: -------------------------------------------------------------------------------- 1 | // Author to Blame: haneefdm on github 2 | 3 | import * as assert from 'assert'; 4 | import * as http from 'http'; 5 | import { TcpPortScanner } from '../../src/tcpportscanner'; 6 | 7 | /** 8 | * Sorry, this is a slow test because we are testing timeouts. Hate anything time related 9 | * because you never know how well it works on a slow/loaded machine. And we are dealing 10 | * with tcp ports that can open/close randomly, so there can be false failures but hopefully 11 | * no false positives. If your computer is quiet enough, we should be able to get through 12 | * the test fine 13 | */ 14 | suite('TcpPortScanner Tests', () => { 15 | test('TcpPortScanner finder/waitfor(open/close) tests', async () => { 16 | let hrStart = process.hrtime(); 17 | function timeIt(reset: boolean = false): string { 18 | const hrEnd = process.hrtime(hrStart); 19 | const ms = (hrEnd[1] / 1e6).toFixed(2); 20 | const ret = `${hrEnd[0]}s ${ms}ms`; 21 | if (reset) { 22 | hrStart = process.hrtime(); 23 | } 24 | return ret; 25 | } 26 | const doLog = false; 27 | const args = { 28 | min: 51000, 29 | max: 52000, 30 | retrieve: 4, 31 | consecutive: false, 32 | doLog: doLog 33 | }; 34 | let ports: number[]; 35 | const hostNameOrIp = '0.0.0.0'; 36 | timeIt(); 37 | await TcpPortScanner.findFreePorts(args, hostNameOrIp).then((ret) => { 38 | if (doLog) { console.log('Found free ports', ret, timeIt()); } 39 | ports = ret; 40 | assert.strictEqual(ports.length, args.retrieve, `wrong number of ports ${ports.join(',')}`); 41 | assert.strictEqual(ports[0] >= args.min, true); 42 | assert.strictEqual(ports[args.retrieve - 1] <= args.max, true); 43 | assert.deepStrictEqual(ports, ports.sort(), `ports are not ordered? ${ports.join(',')}`); 44 | }).catch((e) => { 45 | assert.fail(`TcpPortScanner.find failed, ${timeIt()} ` + e); 46 | }); 47 | 48 | const port = ports[1]; 49 | timeIt(); 50 | await TcpPortScanner.waitForPortOpen(port, hostNameOrIp, false, 100, 100).then(() => { 51 | assert.fail(`0: Timeout expected on port ${port} ${timeIt()}`); 52 | }, async (err) => { 53 | if (doLog) { console.log(`0: Timeout: Success waiting on port ${port} ${timeIt()} `, err.message); } 54 | assert.strictEqual(err.message, 'timeout'); 55 | 56 | // Lets create a server, but don't start listening for a while. In the meantime, we start looking for 57 | // ports to get open 58 | const server = http.createServer(); 59 | server.on('error', (err: any) => { 60 | assert.fail(`Could not start http server on port ${port}`); 61 | }); 62 | setTimeout(() => { 63 | server.listen(port, () => { 64 | if (doLog) { console.log(`Http server is listening on ${port}`); } 65 | }); 66 | }, 200); // Enough time to get waitForPortOpen to get started and working 67 | if (doLog) { console.log('Waiting for http server to start...'); } 68 | 69 | // See if the server started on the requested port. We do it two ways in (near) parallel 70 | // Both should succeed with the same timeout. See above when LISTEN starts 71 | TcpPortScanner.waitForPortOpen(port, hostNameOrIp, true, 50, 1000).then(() => { 72 | if (doLog) { console.log(`1. Success server port ${port} is ready ${timeIt()}`); } 73 | }, (err) => { 74 | if (doLog) { console.log(`1. Timeout: Failed waiting on port ${port} to open ${timeIt()}`, err); } 75 | assert.fail('unexpected timeout ' + err); 76 | }); 77 | await TcpPortScanner.waitForPortOpenOSUtl(port, 50, 1000, false, doLog).then(() => { 78 | if (doLog) { console.log(`1.1 Success server port ${port} is ready ${timeIt()}`); } 79 | }, (err) => { 80 | if (doLog) { console.log(`1.1 Timeout: Failed waiting on port ${port} to open ${timeIt()}`, err); } 81 | assert.fail('unexpected timeout ' + err); 82 | }); 83 | 84 | // Lets see if consecutive ports request works while server is still running. It should 85 | // skip the port we are already using 86 | args.consecutive = true; 87 | timeIt(true); 88 | await TcpPortScanner.findFreePorts(args, hostNameOrIp).then((ret) => { 89 | if (doLog) { console.log('Found free consecutive ports', ret, timeIt()); } 90 | const newPorts = ret; 91 | assert.strictEqual(newPorts.length, args.retrieve, `wrong number of ports ${newPorts.join(',')}`); 92 | assert.strictEqual(newPorts[0] >= args.min, true); 93 | assert.strictEqual(newPorts[args.retrieve - 1] <= args.max, true); 94 | assert.deepStrictEqual(newPorts, newPorts.sort(), `ports are not ordered? ${newPorts.join(',')}`); 95 | assert.strictEqual(newPorts.find((p) => p === port), undefined, `used port ${port} found as unused`); 96 | for (let ix = 1; ix < args.retrieve; ix++) { 97 | assert.strictEqual(newPorts[ix - 1] + 1, newPorts[ix], `ports are not consecutive ${newPorts.join(',')}`); 98 | } 99 | }).catch((e) => { 100 | assert.fail(`TcpPortScanner.find consecutive failed ${timeIt()} ` + e); 101 | }); 102 | 103 | server.close(); 104 | timeIt(); 105 | await TcpPortScanner.waitForPortClosed(port, hostNameOrIp, true, 50, 1000).then(() => { 106 | if (doLog) { console.log(`2. Success Server port ${port} is closed ${timeIt()}`); } 107 | }, (err) => { 108 | if (doLog) { console.log(`2. Timeout: Failed waiting on port ${port} to close ${timeIt()}`, err); } 109 | assert.strictEqual(err.message, 'timeout'); 110 | assert.fail('Why is the server still running? ' + err); 111 | }); 112 | }); 113 | }).timeout(4000); 114 | }); 115 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": ".", 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | "binary_modules", 15 | "src/remote", 16 | ".vscode-test" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const child_process = require('child_process'); 5 | const webpack = require('webpack'); // to access built-in plugins 6 | 7 | const gitStatus = child_process 8 | .execSync('git status --short') 9 | .toString() 10 | .trim(); 11 | const commitHash = child_process 12 | .execSync('git rev-parse --short HEAD') 13 | .toString() 14 | .trim() + (gitStatus === '' ? '' : '+dirty'); 15 | 16 | const extensionConfig = { 17 | target: 'node', 18 | entry: './src/frontend/extension.ts', 19 | output: { 20 | path: path.resolve(__dirname, 'dist'), 21 | filename: 'extension.js', 22 | libraryTarget: 'commonjs2', 23 | devtoolModuleFilenameTemplate: '../[resource-path]' 24 | }, 25 | devtool: 'source-map', 26 | externals: { 27 | vscode: 'vscode', 28 | serialport: 'serialport', 29 | usb: 'usb' 30 | }, 31 | resolve: { 32 | extensions: ['.ts', '.js'] 33 | }, 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.ts$/, 38 | exclude: /node_modules/, 39 | use: [ 40 | { 41 | loader: 'ts-loader' 42 | } 43 | ] 44 | } 45 | ] 46 | } 47 | }; 48 | 49 | const adapterConfig = { 50 | target: 'node', 51 | entry: './src/gdb.ts', 52 | output: { 53 | path: path.resolve(__dirname, 'dist'), 54 | filename: 'debugadapter.js', 55 | libraryTarget: 'commonjs2', 56 | devtoolModuleFilenameTemplate: '../[resource-path]' 57 | }, 58 | devtool: 'source-map', 59 | externals: { 60 | vscode: 'vscode', 61 | serialport: 'serialport', 62 | usb: 'usb', 63 | }, 64 | resolve: { 65 | extensions: ['.ts', '.js'] 66 | }, 67 | module: { 68 | rules: [ 69 | { 70 | test: /\.ts$/, 71 | exclude: /node_modules/, 72 | use: [ 73 | { 74 | loader: 'ts-loader' 75 | } 76 | ] 77 | } 78 | ] 79 | }, 80 | plugins: [ 81 | new webpack.DefinePlugin({ 82 | __COMMIT_HASH__: JSON.stringify(commitHash) 83 | }) 84 | ] 85 | }; 86 | 87 | const grapherConfig = { 88 | target: 'web', 89 | entry: { 90 | 'grapher': './src/grapher/main.ts' 91 | }, 92 | output: { 93 | path: path.resolve(__dirname, 'dist'), 94 | filename: '[name].bundle.js', 95 | devtoolModuleFilenameTemplate: '../[resource-path]' 96 | }, 97 | devtool: 'source-map', 98 | externals: { 99 | vscode: 'vscode', 100 | serialport: 'serialport' 101 | }, 102 | resolve: { 103 | extensions: ['.ts', '.js'] 104 | }, 105 | module: { 106 | rules: [ 107 | { 108 | test: /\.ts$/, 109 | exclude: /node_modules/, 110 | use: [ 111 | { 112 | loader: 'ts-loader' 113 | } 114 | ] 115 | } 116 | ] 117 | } 118 | }; 119 | 120 | const docgenConfig = { 121 | target: 'node', 122 | entry: './src/docgen.ts', 123 | output: { 124 | path: path.resolve(__dirname, 'dist'), 125 | filename: 'docgen.js', 126 | libraryTarget: 'commonjs2', 127 | devtoolModuleFilenameTemplate: '../[resource-path]' 128 | }, 129 | devtool: 'source-map', 130 | resolve: { 131 | extensions: ['.ts', '.js'] 132 | }, 133 | module: { 134 | rules: [ 135 | { 136 | test: /\.ts$/, 137 | exclude: /node_modules/, 138 | use: [ 139 | { 140 | loader: 'ts-loader' 141 | } 142 | ] 143 | } 144 | ] 145 | }, 146 | plugins: [ 147 | new webpack.DefinePlugin({ 148 | __COMMIT_HASH__: JSON.stringify(commitHash) 149 | }) 150 | ] 151 | }; 152 | 153 | module.exports = [extensionConfig, adapterConfig, grapherConfig, docgenConfig]; 154 | --------------------------------------------------------------------------------