├── .eslintrc.yml ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE.txt ├── Makefile ├── NuGet.config ├── README.md ├── SECURITY.md ├── ThirdPartyNotices.txt ├── images ├── mono-debug-icon.png └── mono-debug.png ├── package-lock.json ├── package.json ├── package.nls.json ├── src ├── csharp │ ├── DebugSession.cs │ ├── Handles.cs │ ├── MonoDebug.cs │ ├── MonoDebugSession.cs │ ├── Protocol.cs │ ├── Utilities.cs │ └── mono-debug.csproj └── typescript │ ├── extension.ts │ ├── tests │ └── adapter.test.ts │ └── tsconfig.json └── testdata ├── fsharp ├── Program.fs └── fsharp.fsproj ├── output ├── Output.cs └── output.csproj ├── simple ├── Program.cs └── simple.csproj └── simple_break ├── Program.cs └── simple_break.csproj /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | es2021: true 4 | extends: 5 | - 'eslint:recommended' 6 | - 'plugin:@typescript-eslint/recommended' 7 | parser: '@typescript-eslint/parser' 8 | parserOptions: 9 | ecmaVersion: 12 10 | sourceType: module 11 | plugins: 12 | - '@typescript-eslint' 13 | rules: {} 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Extension CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - 'v*' 9 | pull_request: 10 | branches: 11 | - main 12 | release: 13 | types: 14 | - published 15 | 16 | permissions: 17 | contents: write 18 | 19 | jobs: 20 | build: 21 | 22 | runs-on: ubuntu-22.04 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | with: 27 | submodules: true 28 | 29 | - name: Setup Node 30 | uses: actions/setup-node@v1 31 | with: 32 | node-version: '18.x' 33 | 34 | - name: Setup .NET Core 35 | uses: actions/setup-dotnet@v4 36 | with: 37 | dotnet-version: '8.x' 38 | 39 | - name: Install NPM packages 40 | run: npm install 41 | 42 | - name: Build VSIX 43 | run: make 44 | 45 | - name: Set VSIX name variable 46 | id: vsix_name 47 | run: echo "::set-output name=filename::$(ls mono-debug-*.vsix)" 48 | 49 | - name: Run tests 50 | uses: GabrielBB/xvfb-action@v1.4 51 | with: 52 | run: make run-tests 53 | 54 | - name: Upload CI VSIX 55 | if: github.ref == 'refs/heads/main' 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: mono-debug-${{ github.sha }}.vsix 59 | path: ${{ steps.vsix_name.outputs.filename }} 60 | 61 | - name: Create Release 62 | if: success() && startsWith(github.ref, 'refs/tags/v') 63 | id: create_release 64 | uses: actions/create-release@v1 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | with: 68 | tag_name: ${{ github.ref }} 69 | release_name: Release ${{ github.ref }} 70 | draft: false 71 | prerelease: false 72 | 73 | - name: Upload Release VSIX 74 | if: success() && startsWith(github.ref, 'refs/tags/v') 75 | uses: actions/upload-release-asset@v1 76 | env: 77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 78 | with: 79 | upload_url: ${{ steps.create_release.outputs.upload_url }} 80 | asset_path: ${{ steps.vsix_name.outputs.filename }} 81 | asset_name: ${{ steps.vsix_name.outputs.filename }} 82 | asset_content_type: application/zip 83 | 84 | - name: Publish to VS Marketplace 85 | if: success() && startsWith(github.ref, 'refs/tags/v') 86 | run: make publish 87 | env: 88 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /out/ 2 | /extension/ 3 | /upload/ 4 | bin/ 5 | /node_modules/ 6 | /packages/ 7 | obj/ 8 | /mono-debug-*/ 9 | *.vsix 10 | *.zip 11 | *.exe 12 | *.exe.mdb 13 | mono-debug.userprefs 14 | npm-debug.log 15 | .vs/ 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/debugger-libs"] 2 | path = external/debugger-libs 3 | url = https://github.com/mono/debugger-libs.git 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "extensionHost", 6 | "request": "launch", 7 | "name": "Extension", 8 | "runtimeExecutable": "${execPath}", 9 | "args": [ 10 | "--extensionDevelopmentPath=${workspaceRoot}" 11 | ], 12 | "stopOnEntry": false, 13 | "outFiles": [ 14 | "${workspaceRoot}/out/**/*.js" 15 | ] 16 | }, 17 | { 18 | "type": "mono", 19 | "request": "launch", 20 | "name": "Server", 21 | "program": "${workspaceRoot}/bin/Debug/mono-debug.exe", 22 | "args": [ "--server=4711" ] 23 | }, 24 | { 25 | "type": "node", 26 | "request": "launch", 27 | "name": "Tests", 28 | "cwd": "${workspaceRoot}", 29 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 30 | "args": [ 31 | "-u", "tdd", 32 | "--timeout", "999999", 33 | "--colors", 34 | "./out/tests" 35 | ], 36 | "runtimeArgs": [ 37 | "--nolazy" 38 | ], 39 | "sourceMaps": true, 40 | "outFiles": [ "${workspaceRoot}/out/tests/**/*.js" ] 41 | } 42 | ], 43 | "compounds": [ 44 | { 45 | "name": "Extension + Server", 46 | "configurations": [ "Extension", "Server" ] 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "typescript.tsdk": "node_modules/typescript/lib" 4 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "command": "npm", 4 | "isBackground": true, 5 | "args": [ 6 | "run", "watch" 7 | ], 8 | "problemMatcher": "$tsc-watch", 9 | "tasks": [ 10 | { 11 | "label": "npm", 12 | "type": "shell", 13 | "command": "npm", 14 | "args": [ 15 | "run", 16 | "watch" 17 | ], 18 | "isBackground": true, 19 | "problemMatcher": "$tsc-watch", 20 | "group": { 21 | "_id": "build", 22 | "isDefault": false 23 | } 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | * 2 | */** 3 | **/.DS_Store 4 | !bin/Release/* 5 | !out/extension.js 6 | !node_modules/vscode-nls/**/* 7 | !node_modules/vscode-debugprotocol/**/* 8 | !package.json 9 | !package.nls.json 10 | !README.md 11 | !CHANGELOG.md 12 | !LICENSE.txt 13 | !ThirdPartyNotices.txt 14 | !images/* -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.16.3 2 | * Added support for configuring handled and unhandled exceptions in the Breakpoints window. 3 | 4 | ## 0.16.2 5 | 6 | * Don't use MONO_ENV_OPTIONS to pass debug options by default, it causes issues when starting subprocesses: [Issue #68](https://github.com/microsoft/vscode-mono-debug/issues/68) 7 | 8 | ## 0.16.1 9 | 10 | * Support debugging VB source files (.vb) 11 | * Raise minimum required VSCode version to 1.32 12 | 13 | ## 0.16.0 14 | 15 | * Update mono debugger-libs to latest version, resolves lots of bugs in the debugger: [PR #64](https://github.com/Microsoft/vscode-mono-debug/pull/64) 16 | * Use MONO_ENV_OPTIONS environment variable to pass debug options to mono, this allows custom executables that embed mono to be debugged: [PR #66](https://github.com/Microsoft/vscode-mono-debug/pull/66) 17 | 18 | ## 0.15.7 19 | 20 | * Thanks to [PR](https://github.com/Microsoft/vscode-mono-debug/pull/39) from [Jonathan Dick](https://github.com/Redth) the file extension .cake has been added to the list of supported extensions. This allows to launch Cake.exe with the mono soft debugger, and subsequently attach to it with this extension to debug cake scripts. 21 | 22 | ## 0.15.6 23 | 24 | * no longer creates an initial launch configuration with an obsolete `externalConsole` property. 25 | * fixed an issue with source-less stack frames rendered centered in the CALL STACK view. 26 | * Thanks to [PR](https://github.com/Microsoft/vscode-mono-debug/pull/35) from [VysotskiVadim](https://github.com/VysotskiVadim) it is now possible to debug Haxe (that got translated to C#). 27 | * Thanks to [PR](https://github.com/Microsoft/vscode-mono-debug/pull/33) from [VysotskiVadim](https://github.com/VysotskiVadim) the project now has a F# regression test. 28 | 29 | ## 0.15.5 30 | 31 | * Thanks to [PR](https://github.com/Microsoft/vscode-mono-debug/pull/28) from [AutonomicCoder](https://github.com/AutonomicCoder) [bug #21](https://github.com/Microsoft/vscode-mono-debug/issues/21) got fixed and mono-debug now supports protocol logging to a file. 32 | 33 | ## 0.15.4 34 | 35 | * Mono-debug now uses the terminal service provided by VS Code. You can now use the launch config attribute `console` for selecting one of `internalConsole`, `integratedTerminal`, `externalTerminal`. The 'integratedTerminal' and 'externalTerminal' can be further configured through workspace or user settings. 36 | * Added support for configuring handled and unhandled exceptions. Use the "Debug: Configure Exceptions" command. 37 | 38 | ## 0.15.3 39 | 40 | * Combined 'argument' scope with 'locals' scope in variables view 41 | * Enabled support for Windows by removing dependency on SDB, thanks for the PR from [t-h-e](https://github.com/t-h-e) - [Microsoft/vscode-mono-debug#15](https://github.com/Microsoft/vscode-mono-debug/pull/15) 42 | 43 | ## 0.14.0 44 | 45 | * Removed curly braces from variable values 46 | * Added CSX to debuggable extensions, thanks for the PR from [filipw](https://github.com/filipw) - [Microsoft/vscode-mono-debug#22](https://github.com/Microsoft/vscode-mono-debug/pull/22) 47 | 48 | ## 0.13.0 49 | 50 | * Enable support for showing variables values in source while stepping 51 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Microsoft Corporation 2 | 3 | All rights reserved. 4 | 5 | MIT License 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | MONO_DEBUG_RELEASE = "./bin/Release/mono-debug.exe" 3 | MONO_DEBUG_DEBUG = "./bin/Debug/mono-debug.exe" 4 | 5 | all: vsix 6 | @echo "vsix created" 7 | 8 | vsix: build 9 | ./node_modules/.bin/vsce package 10 | 11 | publish: 12 | ./node_modules/.bin/vsce publish 13 | 14 | build: $MONO_DEBUG_RELEASE 15 | node_modules/.bin/tsc -p ./src/typescript 16 | @echo "build finished" 17 | 18 | debug: $MONO_DEBUG_DEBUG 19 | node_modules/.bin/tsc -p ./src/typescript 20 | @echo "build finished" 21 | 22 | $MONO_DEBUG_RELEASE: 23 | dotnet build -c Release src/csharp/mono-debug.csproj 24 | 25 | $MONO_DEBUG_DEBUG: 26 | dotnet build -c Debug src/csharp/mono-debug.csproj 27 | 28 | tests: 29 | dotnet build /nologo testdata/simple 30 | dotnet build /nologo testdata/output 31 | dotnet build /nologo testdata/simple_break 32 | dotnet build /nologo testdata/fsharp 33 | 34 | run-tests: tests 35 | node_modules/.bin/mocha --timeout 10000 -u tdd ./out/tests 36 | 37 | lint: 38 | node_modules/.bin/eslint . --ext .ts,.tsx 39 | 40 | watch: 41 | node_modules/.bin/tsc -w -p ./src/typescript 42 | 43 | clean: 44 | git clean -xfd -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VS Code Mono Debug 2 | 3 | A simple VS Code debugger extension for the Mono VM. Its implementation was inspired by the [SDB](https://github.com/mono/sdb) command line debugger. 4 | 5 | ![Mono Debug](images/mono-debug.png) 6 | 7 | ## Installing Mono 8 | 9 | You can either download the latest Mono version for Linux, macOS, or Windows at the [Mono project website](https://www.mono-project.com/download/) or you can use your package manager. 10 | 11 | * On OS X: `brew install mono` 12 | * On Ubuntu, Debian, Raspbian: `sudo apt-get install mono-complete` 13 | * On CentOS: `yum install mono-complete` 14 | * On Fedora: `dnf install mono-complete` 15 | 16 | ## Enable Mono debugging 17 | 18 | To enable debugging of Mono based C# (and F#) programs, you have to pass the `-debug` option to the compiler: 19 | 20 | ```bash 21 | csc -debug Program.cs 22 | ``` 23 | 24 | If you want to attach the VS Code debugger to a Mono program, pass these additional arguments to the Mono runtime: 25 | 26 | ```bash 27 | mono --debug --debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:55555 Program.exe 28 | ``` 29 | 30 | The corresponding attach `launch.json` configuration looks like this: 31 | 32 | ```json 33 | { 34 | "version": "0.2.0", 35 | "configurations": [ 36 | { 37 | "name": "Attach to Mono", 38 | "request": "attach", 39 | "type": "mono", 40 | "address": "localhost", 41 | "port": 55555 42 | } 43 | ] 44 | } 45 | ``` 46 | 47 | ## Building the mono-debug extension 48 | 49 | [![build status](https://github.com/microsoft/vscode-mono-debug/workflows/Extension%20CI/badge.svg)](https://github.com/microsoft/vscode-mono-debug/actions) 50 | 51 | Building and using VS Code mono-debug requires a basic POSIX-like environment, a Bash-like 52 | shell, and an installed Mono framework. 53 | 54 | First, clone the mono-debug project: 55 | 56 | ```bash 57 | $ git clone --recursive https://github.com/microsoft/vscode-mono-debug 58 | ``` 59 | 60 | To build the extension vsix, run: 61 | 62 | ```bash 63 | $ cd vscode-mono-debug 64 | $ npm install 65 | $ make 66 | ``` 67 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /ThirdPartyNotices.txt: -------------------------------------------------------------------------------- 1 | vscode-mono-debug 2 | 3 | THIRD-PARTY SOFTWARE NOTICES AND INFORMATION 4 | Do Not Translate or Localize 5 | 6 | This project incorporates components from the projects listed below. The original copyright notices 7 | and licenses under which Microsoft received such components are set forth below. Microsoft reserves all rights not 8 | expressly granted herein, whether by implication, estoppel or otherwise. 9 | 10 | 11 | 1. SDB (https://github.com/mono/sdb) 12 | 2. Mono debugger-libs (https://github.com/mono/debugger-libs) 13 | 3. Newtonsoft.Json (https://github.com/JamesNK/Newtonsoft.Json) 14 | 4. NRefactory (https://github.com/xamarin/NRefactory) 15 | 5. Cecil (https://github.com/jbevain/cecil) 16 | 17 | 18 | %% SDB NOTICES AND INFORMATION BEGIN HERE 19 | ========================================= 20 | The MIT License (MIT) 21 | 22 | Copyright (c) 2015 Alex Rønne Petersen 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining a copy 25 | of this software and associated documentation files (the "Software"), to deal 26 | in the Software without restriction, including without limitation the rights 27 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | copies of the Software, and to permit persons to whom the Software is 29 | furnished to do so, subject to the following conditions: 30 | 31 | The above copyright notice and this permission notice shall be included in 32 | all copies or substantial portions of the Software. 33 | 34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 35 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 36 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 37 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 38 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 39 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 40 | THE SOFTWARE. 41 | ========================================= 42 | END OF SDB NOTICES AND INFORMATION 43 | 44 | %% debugger-libs NOTICES AND INFORMATION BEGIN HERE 45 | ========================================= 46 | The MIT License (MIT) 47 | 48 | Copyright (c) 2001-2011 Novell, Inc. (www.novell.com) 49 | Copyright (c) 2011-2015 Xamarin Inc. (www.xamarin.com) 50 | 51 | Permission is hereby granted, free of charge, to any person obtaining a copy 52 | of this software and associated documentation files (the "Software"), to deal 53 | in the Software without restriction, including without limitation the rights 54 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 55 | copies of the Software, and to permit persons to whom the Software is 56 | furnished to do so, subject to the following conditions: 57 | 58 | The above copyright notice and this permission notice shall be included in 59 | all copies or substantial portions of the Software. 60 | 61 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 62 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 63 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 64 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 65 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 66 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 67 | THE SOFTWARE. 68 | ========================================= 69 | END OF debugger-libs NOTICES AND INFORMATION 70 | 71 | %% newtonsoft.json NOTICES AND INFORMATION BEGIN HERE 72 | ========================================= 73 | The MIT License (MIT) 74 | 75 | Copyright (c) 2007 James Newton-King 76 | 77 | Permission is hereby granted, free of charge, to any person obtaining a copy of 78 | this software and associated documentation files (the "Software"), to deal in 79 | the Software without restriction, including without limitation the rights to 80 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 81 | the Software, and to permit persons to whom the Software is furnished to do so, 82 | subject to the following conditions: 83 | 84 | The above copyright notice and this permission notice shall be included in all 85 | copies or substantial portions of the Software. 86 | 87 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 88 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 89 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 90 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 91 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 92 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 93 | ========================================= 94 | END OF newtonsoft.json NOTICES AND INFORMATION 95 | 96 | %% NRefactory NOTICES AND INFORMATION BEGIN HERE 97 | ========================================= 98 | The MIT License (MIT) 99 | 100 | Copyright (c) 2010-2014 AlphaSierraPapa, Xamarin 101 | 102 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 103 | software and associated documentation files (the "Software"), to deal in the Software 104 | without restriction, including without limitation the rights to use, copy, modify, merge, 105 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 106 | to whom the Software is furnished to do so, subject to the following conditions: 107 | 108 | The above copyright notice and this permission notice shall be included in all copies or 109 | substantial portions of the Software. 110 | 111 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 112 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 113 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 114 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 115 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 116 | DEALINGS IN THE SOFTWARE. 117 | ========================================= 118 | END OF NRefactory NOTICES AND INFORMATION 119 | 120 | %% Cecil NOTICES AND INFORMATION BEGIN HERE 121 | ========================================= 122 | The MIT License (MIT) 123 | 124 | Copyright (c) 2008 - 2015 Jb Evain 125 | Copyright (c) 2008 - 2011 Novell, Inc. 126 | 127 | Permission is hereby granted, free of charge, to any person obtaining 128 | a copy of this software and associated documentation files (the 129 | "Software"), to deal in the Software without restriction, including 130 | without limitation the rights to use, copy, modify, merge, publish, 131 | distribute, sublicense, and/or sell copies of the Software, and to 132 | permit persons to whom the Software is furnished to do so, subject to 133 | the following conditions: 134 | 135 | The above copyright notice and this permission notice shall be 136 | included in all copies or substantial portions of the Software. 137 | 138 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 139 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 140 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 141 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 142 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 143 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 144 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 145 | ========================================= 146 | END OF Cecil NOTICES AND INFORMATION 147 | -------------------------------------------------------------------------------- /images/mono-debug-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-mono-debug/f20c3424267c29173de94060d314fa3b3f6f41b4/images/mono-debug-icon.png -------------------------------------------------------------------------------- /images/mono-debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-mono-debug/f20c3424267c29173de94060d314fa3b3f6f41b4/images/mono-debug.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mono-debug", 3 | "displayName": "Mono Debug", 4 | "version": "0.16.3", 5 | "publisher": "ms-vscode", 6 | "description": "Visual Studio Code debugger extension for Mono", 7 | "icon": "images/mono-debug-icon.png", 8 | "categories": [ 9 | "Debuggers" 10 | ], 11 | "author": { 12 | "name": "Microsoft Corporation" 13 | }, 14 | "license": "MIT", 15 | "private": true, 16 | "scripts": { 17 | "compile": "make build", 18 | "lint": "make lint", 19 | "watch": "make watch", 20 | "test": "make run-tests" 21 | }, 22 | "engines": { 23 | "vscode": "^1.32.0", 24 | "node": "^14.0.0" 25 | }, 26 | "dependencies": { 27 | "vscode-debugprotocol": "^1.42.0", 28 | "vscode-nls": "^5.0.0" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/microsoft/vscode-mono-debug.git" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/microsoft/vscode-mono-debug/issues" 36 | }, 37 | "devDependencies": { 38 | "@types/mocha": "^8.0.3", 39 | "@types/node": "^14.11.5", 40 | "@types/vscode": "^1.32.0", 41 | "@typescript-eslint/eslint-plugin": "^4.4.0", 42 | "@typescript-eslint/parser": "^4.4.0", 43 | "eslint": "^7.1.0", 44 | "mocha": "^10.8.2", 45 | "typescript": "^4.0.3", 46 | "vsce": "^2.10.0", 47 | "vscode-debugadapter-testsupport": "^1.42.0", 48 | "vscode-nls-dev": "^3.3.2", 49 | "vscode-test": "^1.4.0" 50 | }, 51 | "main": "./out/extension", 52 | "activationEvents": [ 53 | "onCommand:extension.mono-debug.configureExceptions", 54 | "onCommand:extension.mono-debug.startSession" 55 | ], 56 | "contributes": { 57 | "configuration": { 58 | "type": "object", 59 | "title": "%configuration.title%", 60 | "properties": { 61 | "mono-debug.exceptionOptions": { 62 | "type": "object", 63 | "additionalProperties": { 64 | "type": "string", 65 | "enum": [ 66 | "never", 67 | "always", 68 | "unhandled" 69 | ], 70 | "enumDescriptions": [ 71 | "%breakMode.never%", 72 | "%breakMode.always%", 73 | "%breakMode.unhandled%" 74 | ], 75 | "description": "%configuration.exceptionOptions.description2%", 76 | "default": "never" 77 | }, 78 | "description": "%configuration.exceptionOptions.description%", 79 | "default": { 80 | "System.Exception": "never", 81 | "System.SystemException": "never", 82 | "System.ArithmeticException": "never", 83 | "System.ArrayTypeMismatchException": "never", 84 | "System.DivideByZeroException": "never", 85 | "System.IndexOutOfRangeException": "never", 86 | "System.InvalidCastException": "never", 87 | "System.NullReferenceException": "never", 88 | "System.OutOfMemoryException": "never", 89 | "System.OverflowException": "never", 90 | "System.StackOverflowException": "never", 91 | "System.TypeInitializationException": "never" 92 | } 93 | } 94 | } 95 | }, 96 | "commands": [ 97 | { 98 | "command": "extension.mono-debug.configureExceptions", 99 | "title": "%configure.exceptions.command%", 100 | "category": "Debug" 101 | } 102 | ], 103 | "breakpoints": [ 104 | { 105 | "language": "csharp" 106 | }, 107 | { 108 | "language": "vb" 109 | }, 110 | { 111 | "language": "fsharp" 112 | } 113 | ], 114 | "debuggers": [ 115 | { 116 | "type": "mono", 117 | "label": "C# Mono", 118 | "program": "./bin/Release/mono-debug.exe", 119 | "osx": { 120 | "runtime": "mono" 121 | }, 122 | "linux": { 123 | "runtime": "mono" 124 | }, 125 | "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", 126 | "startSessionCommand": "extension.mono-debug.startSession", 127 | "initialConfigurations": [ 128 | { 129 | "name": "%mono.launch.config.name%", 130 | "type": "mono", 131 | "request": "launch", 132 | "program": "${workspaceRoot}/program.exe", 133 | "cwd": "${workspaceRoot}" 134 | }, 135 | { 136 | "name": "%mono.attach.config.name%", 137 | "type": "mono", 138 | "request": "attach", 139 | "address": "localhost", 140 | "port": 55555 141 | } 142 | ], 143 | "configurationAttributes": { 144 | "launch": { 145 | "required": [ 146 | "program" 147 | ], 148 | "properties": { 149 | "program": { 150 | "type": "string", 151 | "description": "%mono.launch.program.description%" 152 | }, 153 | "args": { 154 | "type": "array", 155 | "description": "%mono.launch.args.description%", 156 | "items": { 157 | "type": "string" 158 | }, 159 | "default": [] 160 | }, 161 | "cwd": { 162 | "type": "string", 163 | "description": "%mono.launch.cwd.description%", 164 | "default": "." 165 | }, 166 | "runtimeExecutable": { 167 | "type": [ 168 | "string", 169 | "null" 170 | ], 171 | "description": "%mono.launch.runtimeExecutable.description%", 172 | "default": null 173 | }, 174 | "runtimeArgs": { 175 | "type": "array", 176 | "description": "%mono.launch.runtimeArgs.description%", 177 | "items": { 178 | "type": "string" 179 | }, 180 | "default": [] 181 | }, 182 | "passDebugOptionsViaEnvironmentVariable": { 183 | "type": "boolean", 184 | "description": "%mono.launch.passDebugOptionsViaEnvironmentVariable.description%", 185 | "default": false 186 | }, 187 | "env": { 188 | "type": "object", 189 | "description": "%mono.launch.env.description%", 190 | "default": {} 191 | }, 192 | "externalConsole": { 193 | "type": "boolean", 194 | "deprecationMessage": "%mono.launch.externalConsole.deprecationMessage%", 195 | "default": true 196 | }, 197 | "console": { 198 | "type": "string", 199 | "enum": [ 200 | "internalConsole", 201 | "integratedTerminal", 202 | "externalTerminal" 203 | ], 204 | "enumDescriptions": [ 205 | "%mono.launch.console.internalConsole.description%", 206 | "%mono.launch.console.integratedTerminal.description%", 207 | "%mono.launch.console.externalTerminal.description%" 208 | ], 209 | "description": "%mono.launch.console.description%", 210 | "default": "internalConsole" 211 | } 212 | } 213 | }, 214 | "attach": { 215 | "required": [ 216 | "port" 217 | ], 218 | "properties": { 219 | "port": { 220 | "type": "number", 221 | "description": "%mono.attach.port.description%", 222 | "default": 55555 223 | }, 224 | "address": { 225 | "type": "string", 226 | "description": "%mono.attach.address.description%", 227 | "default": "undefined" 228 | } 229 | } 230 | } 231 | } 232 | } 233 | ] 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "mono.label": "C# Mono", 3 | 4 | "configure.exceptions.command": "Configure Exceptions", 5 | 6 | "configuration.title": "Mono Debug", 7 | "configuration.exceptionOptions.description": "Configure when exceptions should break (never, always, unhandled). Add more as needed.", 8 | "configuration.exceptionOptions.description2": "When should this thrown exception break?", 9 | 10 | "configure.exceptions": "Configure Exceptions", 11 | "breakMode.never": "never breaks", 12 | "breakMode.always": "breaks when exception handled or unhandled", 13 | "breakMode.unhandled": "breaks when exception unhandled", 14 | 15 | 16 | "mono.launch.config.name": "Launch", 17 | "mono.attach.config.name": "Attach", 18 | 19 | "mono.launch.program.description": "Absolute path to the program.", 20 | "mono.launch.args.description": "Command line arguments passed to the program.", 21 | "mono.launch.cwd.description": "Absolute path to the working directory of the program being debugged.", 22 | "mono.launch.runtimeExecutable.description": "Absolute path to the runtime executable to be used. Default is the runtime executable on the PATH.", 23 | "mono.launch.runtimeArgs.description": "Optional arguments passed to the runtime executable.", 24 | "mono.launch.passDebugOptionsViaEnvironmentVariable.description": "Use the MONO_ENV_OPTIONS environment variable to pass the Mono debugger options when launching a program. This is useful if you're embedding Mono in a custom executable.", 25 | "mono.launch.env.description": "Environment variables passed to the program.", 26 | "mono.launch.externalConsole.deprecationMessage": "Attribute 'externalConsole' is deprecated, use 'console' instead.", 27 | "mono.launch.console.description": "Where to launch the debug target.", 28 | "mono.launch.console.internalConsole.description": "VS Code Debug Console (which doesn't support to read input from a program)", 29 | "mono.launch.console.integratedTerminal.description": "VS Code's integrated terminal", 30 | "mono.launch.console.externalTerminal.description": "external terminal that can be configured via user settings", 31 | 32 | "mono.attach.port.description": "Debug port to attach to.", 33 | "mono.attach.address.description": "TCP/IP address. Default is \"localhost\"." 34 | } -------------------------------------------------------------------------------- /src/csharp/DebugSession.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.IO; 9 | 10 | 11 | namespace VSCodeDebug 12 | { 13 | // ---- Types ------------------------------------------------------------------------- 14 | 15 | public class Message 16 | { 17 | public int id { get; } 18 | public string format { get; } 19 | public dynamic variables { get; } 20 | public dynamic showUser { get; } 21 | public dynamic sendTelemetry { get; } 22 | 23 | public Message(int id, string format, dynamic variables = null, bool user = true, bool telemetry = false) { 24 | this.id = id; 25 | this.format = format; 26 | this.variables = variables; 27 | this.showUser = user; 28 | this.sendTelemetry = telemetry; 29 | } 30 | } 31 | 32 | public class StackFrame 33 | { 34 | public int id { get; } 35 | public Source source { get; } 36 | public int line { get; } 37 | public int column { get; } 38 | public string name { get; } 39 | public string presentationHint { get; } 40 | 41 | public StackFrame(int id, string name, Source source, int line, int column, string hint) { 42 | this.id = id; 43 | this.name = name; 44 | this.source = source; 45 | 46 | // These should NEVER be negative 47 | this.line = Math.Max(0, line); 48 | this.column = Math.Max(0, column); 49 | 50 | this.presentationHint = hint; 51 | } 52 | } 53 | 54 | public class Scope 55 | { 56 | public string name { get; } 57 | public int variablesReference { get; } 58 | public bool expensive { get; } 59 | 60 | public Scope(string name, int variablesReference, bool expensive = false) { 61 | this.name = name; 62 | this.variablesReference = variablesReference; 63 | this.expensive = expensive; 64 | } 65 | } 66 | 67 | public class Variable 68 | { 69 | public string name { get; } 70 | public string value { get; } 71 | public string type { get; } 72 | public int variablesReference { get; } 73 | 74 | public Variable(string name, string value, string type, int variablesReference = 0) { 75 | this.name = name; 76 | this.value = value; 77 | this.type = type; 78 | this.variablesReference = variablesReference; 79 | } 80 | } 81 | 82 | public class Thread 83 | { 84 | public int id { get; } 85 | public string name { get; } 86 | 87 | public Thread(int id, string name) { 88 | this.id = id; 89 | if (name == null || name.Length == 0) { 90 | this.name = string.Format("Thread #{0}", id); 91 | } else { 92 | this.name = name; 93 | } 94 | } 95 | } 96 | 97 | public class Source 98 | { 99 | public string name { get; } 100 | public string path { get; } 101 | public int sourceReference { get; } 102 | public string presentationHint { get; } 103 | 104 | public Source(string name, string path, int sourceReference, string hint) { 105 | this.name = name; 106 | this.path = path; 107 | this.sourceReference = sourceReference; 108 | this.presentationHint = hint; 109 | } 110 | } 111 | 112 | public class Breakpoint 113 | { 114 | public bool verified { get; } 115 | public int line { get; } 116 | 117 | public Breakpoint(bool verified, int line) { 118 | this.verified = verified; 119 | this.line = line; 120 | } 121 | } 122 | 123 | // ---- Events ------------------------------------------------------------------------- 124 | 125 | public class InitializedEvent : Event 126 | { 127 | public InitializedEvent() 128 | : base("initialized") { } 129 | } 130 | 131 | public class StoppedEvent : Event 132 | { 133 | public StoppedEvent(int tid, string reasn, string txt = null) 134 | : base("stopped", new { 135 | threadId = tid, 136 | reason = reasn, 137 | text = txt 138 | }) { } 139 | } 140 | 141 | public class ExitedEvent : Event 142 | { 143 | public ExitedEvent(int exCode) 144 | : base("exited", new { exitCode = exCode } ) { } 145 | } 146 | 147 | public class TerminatedEvent : Event 148 | { 149 | public TerminatedEvent() 150 | : base("terminated") { } 151 | } 152 | 153 | public class ThreadEvent : Event 154 | { 155 | public ThreadEvent(string reasn, int tid) 156 | : base("thread", new { 157 | reason = reasn, 158 | threadId = tid 159 | }) { } 160 | } 161 | 162 | public class OutputEvent : Event 163 | { 164 | public OutputEvent(string cat, string outpt) 165 | : base("output", new { 166 | category = cat, 167 | output = outpt 168 | }) { } 169 | } 170 | 171 | // ---- Response ------------------------------------------------------------------------- 172 | 173 | public class Capabilities : ResponseBody { 174 | 175 | public bool supportsConfigurationDoneRequest; 176 | public bool supportsFunctionBreakpoints; 177 | public bool supportsConditionalBreakpoints; 178 | public bool supportsEvaluateForHovers; 179 | public bool supportsExceptionFilterOptions; 180 | public dynamic[] exceptionBreakpointFilters; 181 | } 182 | 183 | public class ErrorResponseBody : ResponseBody { 184 | 185 | public Message error { get; } 186 | 187 | public ErrorResponseBody(Message error) { 188 | this.error = error; 189 | } 190 | } 191 | 192 | public class StackTraceResponseBody : ResponseBody 193 | { 194 | public StackFrame[] stackFrames { get; } 195 | public int totalFrames { get; } 196 | 197 | public StackTraceResponseBody(List frames, int total) { 198 | stackFrames = frames.ToArray(); 199 | totalFrames = total; 200 | } 201 | } 202 | 203 | public class ScopesResponseBody : ResponseBody 204 | { 205 | public Scope[] scopes { get; } 206 | 207 | public ScopesResponseBody(List scps) { 208 | scopes = scps.ToArray(); 209 | } 210 | } 211 | 212 | public class VariablesResponseBody : ResponseBody 213 | { 214 | public Variable[] variables { get; } 215 | 216 | public VariablesResponseBody(List vars) { 217 | variables = vars.ToArray(); 218 | } 219 | } 220 | 221 | public class ThreadsResponseBody : ResponseBody 222 | { 223 | public Thread[] threads { get; } 224 | 225 | public ThreadsResponseBody(List ths) { 226 | threads = ths.ToArray(); 227 | } 228 | } 229 | 230 | public class EvaluateResponseBody : ResponseBody 231 | { 232 | public string result { get; } 233 | public int variablesReference { get; } 234 | 235 | public EvaluateResponseBody(string value, int reff = 0) { 236 | result = value; 237 | variablesReference = reff; 238 | } 239 | } 240 | 241 | public class SetBreakpointsResponseBody : ResponseBody 242 | { 243 | public Breakpoint[] breakpoints { get; } 244 | 245 | public SetBreakpointsResponseBody(List bpts = null) { 246 | if (bpts == null) 247 | breakpoints = new Breakpoint[0]; 248 | else 249 | breakpoints = bpts.ToArray(); 250 | } 251 | } 252 | 253 | // ---- The Session -------------------------------------------------------- 254 | 255 | public abstract class DebugSession : ProtocolServer 256 | { 257 | private bool _clientLinesStartAt1 = true; 258 | private bool _clientPathsAreURI = true; 259 | 260 | 261 | public DebugSession() 262 | { 263 | } 264 | 265 | public void SendResponse(Response response, dynamic body = null) 266 | { 267 | if (body != null) { 268 | response.SetBody(body); 269 | } 270 | SendMessage(response); 271 | } 272 | 273 | public void SendErrorResponse(Response response, int id, string format, dynamic arguments = null, bool user = true, bool telemetry = false) 274 | { 275 | var msg = new Message(id, format, arguments, user, telemetry); 276 | var message = Utilities.ExpandVariables(msg.format, msg.variables); 277 | response.SetErrorBody(message, new ErrorResponseBody(msg)); 278 | SendMessage(response); 279 | } 280 | 281 | protected override void DispatchRequest(string command, dynamic args, Response response) 282 | { 283 | if (args == null) { 284 | args = new { }; 285 | } 286 | 287 | try { 288 | switch (command) { 289 | 290 | case "initialize": 291 | if (args.linesStartAt1 != null) { 292 | _clientLinesStartAt1 = (bool)args.linesStartAt1; 293 | } 294 | var pathFormat = (string)args.pathFormat; 295 | if (pathFormat != null) { 296 | switch (pathFormat) { 297 | case "uri": 298 | _clientPathsAreURI = true; 299 | break; 300 | case "path": 301 | _clientPathsAreURI = false; 302 | break; 303 | default: 304 | SendErrorResponse(response, 1015, "initialize: bad value '{_format}' for pathFormat", new { _format = pathFormat }); 305 | return; 306 | } 307 | } 308 | Initialize(response, args); 309 | break; 310 | 311 | case "launch": 312 | Launch(response, args); 313 | break; 314 | 315 | case "attach": 316 | Attach(response, args); 317 | break; 318 | 319 | case "disconnect": 320 | Disconnect(response, args); 321 | break; 322 | 323 | case "next": 324 | Next(response, args); 325 | break; 326 | 327 | case "continue": 328 | Continue(response, args); 329 | break; 330 | 331 | case "stepIn": 332 | StepIn(response, args); 333 | break; 334 | 335 | case "stepOut": 336 | StepOut(response, args); 337 | break; 338 | 339 | case "pause": 340 | Pause(response, args); 341 | break; 342 | 343 | case "stackTrace": 344 | StackTrace(response, args); 345 | break; 346 | 347 | case "scopes": 348 | Scopes(response, args); 349 | break; 350 | 351 | case "variables": 352 | Variables(response, args); 353 | break; 354 | 355 | case "source": 356 | Source(response, args); 357 | break; 358 | 359 | case "threads": 360 | Threads(response, args); 361 | break; 362 | 363 | case "setBreakpoints": 364 | SetBreakpoints(response, args); 365 | break; 366 | 367 | case "setFunctionBreakpoints": 368 | SetFunctionBreakpoints(response, args); 369 | break; 370 | 371 | case "setExceptionBreakpoints": 372 | SetExceptionBreakpoints(response, args); 373 | break; 374 | 375 | case "evaluate": 376 | Evaluate(response, args); 377 | break; 378 | 379 | default: 380 | SendErrorResponse(response, 1014, "unrecognized request: {_request}", new { _request = command }); 381 | break; 382 | } 383 | } 384 | catch (Exception e) { 385 | SendErrorResponse(response, 1104, "error while processing request '{_request}' (exception: {_exception})", new { _request = command, _exception = e.Message }); 386 | } 387 | 388 | if (command == "disconnect") { 389 | Stop(); 390 | } 391 | } 392 | 393 | public abstract void Initialize(Response response, dynamic args); 394 | 395 | public abstract void Launch(Response response, dynamic arguments); 396 | 397 | public abstract void Attach(Response response, dynamic arguments); 398 | 399 | public abstract void Disconnect(Response response, dynamic arguments); 400 | 401 | public virtual void SetFunctionBreakpoints(Response response, dynamic arguments) 402 | { 403 | } 404 | 405 | public virtual void SetExceptionBreakpoints(Response response, dynamic arguments) 406 | { 407 | } 408 | 409 | public abstract void SetBreakpoints(Response response, dynamic arguments); 410 | 411 | public abstract void Continue(Response response, dynamic arguments); 412 | 413 | public abstract void Next(Response response, dynamic arguments); 414 | 415 | public abstract void StepIn(Response response, dynamic arguments); 416 | 417 | public abstract void StepOut(Response response, dynamic arguments); 418 | 419 | public abstract void Pause(Response response, dynamic arguments); 420 | 421 | public abstract void StackTrace(Response response, dynamic arguments); 422 | 423 | public abstract void Scopes(Response response, dynamic arguments); 424 | 425 | public abstract void Variables(Response response, dynamic arguments); 426 | 427 | public abstract void Source(Response response, dynamic arguments); 428 | 429 | public abstract void Threads(Response response, dynamic arguments); 430 | 431 | public abstract void Evaluate(Response response, dynamic arguments); 432 | 433 | // protected 434 | 435 | protected int ConvertDebuggerLineToClient(int line) 436 | { 437 | return _clientLinesStartAt1 ? line : line - 1; 438 | } 439 | 440 | protected int ConvertClientLineToDebugger(int line) 441 | { 442 | return _clientLinesStartAt1 ? line : line + 1; 443 | } 444 | 445 | protected string ConvertDebuggerPathToClient(string path) 446 | { 447 | if (_clientPathsAreURI) { 448 | try { 449 | var uri = new System.Uri(path); 450 | return uri.AbsoluteUri; 451 | } 452 | catch { 453 | return null; 454 | } 455 | } 456 | else { 457 | return path; 458 | } 459 | } 460 | 461 | protected string ConvertClientPathToDebugger(string clientPath) 462 | { 463 | if (clientPath == null) { 464 | return null; 465 | } 466 | 467 | if (_clientPathsAreURI) { 468 | if (Uri.IsWellFormedUriString(clientPath, UriKind.Absolute)) { 469 | Uri uri = new Uri(clientPath); 470 | return uri.LocalPath; 471 | } 472 | Program.Log("path not well formed: '{0}'", clientPath); 473 | return null; 474 | } 475 | else { 476 | return clientPath; 477 | } 478 | } 479 | } 480 | } 481 | -------------------------------------------------------------------------------- /src/csharp/Handles.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System.Collections.Generic; 6 | 7 | namespace VSCodeDebug 8 | { 9 | public class Handles 10 | { 11 | private const int START_HANDLE = 1000; 12 | 13 | private int _nextHandle; 14 | private Dictionary _handleMap; 15 | 16 | public Handles() { 17 | _nextHandle = START_HANDLE; 18 | _handleMap = new Dictionary(); 19 | } 20 | 21 | public void Reset() 22 | { 23 | _nextHandle = START_HANDLE; 24 | _handleMap.Clear(); 25 | } 26 | 27 | public int Create(T value) 28 | { 29 | var handle = _nextHandle++; 30 | _handleMap[handle] = value; 31 | return handle; 32 | } 33 | 34 | public bool TryGet(int handle, out T value) 35 | { 36 | if (_handleMap.TryGetValue(handle, out value)) { 37 | return true; 38 | } 39 | return false; 40 | } 41 | 42 | public T Get(int handle, T dflt) 43 | { 44 | T value; 45 | if (_handleMap.TryGetValue(handle, out value)) { 46 | return value; 47 | } 48 | return dflt; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/csharp/MonoDebug.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.IO; 7 | using System.Net; 8 | using System.Net.Sockets; 9 | 10 | namespace VSCodeDebug 11 | { 12 | internal class Program 13 | { 14 | const int DEFAULT_PORT = 4711; 15 | 16 | private static bool trace_requests; 17 | private static bool trace_responses; 18 | static string LOG_FILE_PATH = null; 19 | 20 | private static void Main(string[] argv) 21 | { 22 | int port = -1; 23 | 24 | // parse command line arguments 25 | foreach (var a in argv) { 26 | switch (a) { 27 | case "--trace": 28 | trace_requests = true; 29 | break; 30 | case "--trace=response": 31 | trace_requests = true; 32 | trace_responses = true; 33 | break; 34 | case "--server": 35 | port = DEFAULT_PORT; 36 | break; 37 | default: 38 | if (a.StartsWith("--server=")) { 39 | if (!int.TryParse(a.Substring("--server=".Length), out port)) { 40 | port = DEFAULT_PORT; 41 | } 42 | } 43 | else if( a.StartsWith("--log-file=")) { 44 | LOG_FILE_PATH = a.Substring("--log-file=".Length); 45 | } 46 | break; 47 | } 48 | } 49 | 50 | if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("mono_debug_logfile")) == false) { 51 | LOG_FILE_PATH = Environment.GetEnvironmentVariable("mono_debug_logfile"); 52 | trace_requests = true; 53 | trace_responses = true; 54 | } 55 | 56 | if (port > 0) { 57 | // TCP/IP server 58 | Program.Log("waiting for debug protocol on port " + port); 59 | RunServer(port); 60 | } else { 61 | // stdin/stdout 62 | Program.Log("waiting for debug protocol on stdin/stdout"); 63 | RunSession(Console.OpenStandardInput(), Console.OpenStandardOutput()); 64 | } 65 | } 66 | 67 | static TextWriter logFile; 68 | 69 | public static void Log(bool predicate, string format, params object[] data) 70 | { 71 | if (predicate) 72 | { 73 | Log(format, data); 74 | } 75 | } 76 | 77 | public static void Log(string format, params object[] data) 78 | { 79 | try 80 | { 81 | Console.Error.WriteLine(format, data); 82 | 83 | if (LOG_FILE_PATH != null) 84 | { 85 | if (logFile == null) 86 | { 87 | logFile = File.CreateText(LOG_FILE_PATH); 88 | } 89 | 90 | string msg = string.Format(format, data); 91 | logFile.WriteLine(string.Format("{0} {1}", DateTime.UtcNow.ToLongTimeString(), msg)); 92 | } 93 | } 94 | catch (Exception ex) 95 | { 96 | if (LOG_FILE_PATH != null) 97 | { 98 | try 99 | { 100 | File.WriteAllText(LOG_FILE_PATH + ".err", ex.ToString()); 101 | } 102 | catch 103 | { 104 | } 105 | } 106 | 107 | throw; 108 | } 109 | } 110 | 111 | private static void RunSession(Stream inputStream, Stream outputStream) 112 | { 113 | DebugSession debugSession = new MonoDebugSession(); 114 | debugSession.TRACE = trace_requests; 115 | debugSession.TRACE_RESPONSE = trace_responses; 116 | debugSession.Start(inputStream, outputStream).Wait(); 117 | 118 | if (logFile!=null) 119 | { 120 | logFile.Flush(); 121 | logFile.Close(); 122 | logFile = null; 123 | } 124 | } 125 | 126 | private static void RunServer(int port) 127 | { 128 | TcpListener serverSocket = new TcpListener(IPAddress.Parse("127.0.0.1"), port); 129 | serverSocket.Start(); 130 | 131 | new System.Threading.Thread(() => { 132 | while (true) { 133 | var clientSocket = serverSocket.AcceptSocket(); 134 | if (clientSocket != null) { 135 | Program.Log(">> accepted connection from client"); 136 | 137 | new System.Threading.Thread(() => { 138 | using (var networkStream = new NetworkStream(clientSocket)) { 139 | try { 140 | RunSession(networkStream, networkStream); 141 | } 142 | catch (Exception e) { 143 | Console.Error.WriteLine("Exception: " + e); 144 | } 145 | } 146 | clientSocket.Close(); 147 | Console.Error.WriteLine(">> client connection closed"); 148 | }).Start(); 149 | } 150 | } 151 | }).Start(); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/csharp/MonoDebugSession.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Threading; 9 | using System.Linq; 10 | using System.Net; 11 | using Mono.Debugging.Client; 12 | 13 | 14 | namespace VSCodeDebug 15 | { 16 | public class MonoDebugSession : DebugSession 17 | { 18 | private const string MONO = "mono"; 19 | private readonly string[] MONO_EXTENSIONS = new String[] { 20 | ".cs", ".csx", 21 | ".cake", 22 | ".fs", ".fsi", ".ml", ".mli", ".fsx", ".fsscript", 23 | ".hx", 24 | ".vb" 25 | }; 26 | private const int MAX_CHILDREN = 100; 27 | private const int MAX_CONNECTION_ATTEMPTS = 10; 28 | private const int CONNECTION_ATTEMPT_INTERVAL = 500; 29 | 30 | private AutoResetEvent _resumeEvent = new AutoResetEvent(false); 31 | private bool _debuggeeExecuting = false; 32 | private readonly object _lock = new object(); 33 | private Mono.Debugging.Soft.SoftDebuggerSession _session; 34 | private volatile bool _debuggeeKilled = true; 35 | private ProcessInfo _activeProcess; 36 | private Mono.Debugging.Client.StackFrame _activeFrame; 37 | private long _nextBreakpointId = 0; 38 | private SortedDictionary _breakpoints; 39 | private List _catchpoints; 40 | private DebuggerSessionOptions _debuggerSessionOptions; 41 | 42 | private System.Diagnostics.Process _process; 43 | private Handles _variableHandles; 44 | private Handles _frameHandles; 45 | private ObjectValue _exception; 46 | private Dictionary _seenThreads = new Dictionary(); 47 | private bool _attachMode = false; 48 | private bool _terminated = false; 49 | private bool _stderrEOF = true; 50 | private bool _stdoutEOF = true; 51 | 52 | private dynamic exceptionOptionsFromDap; 53 | 54 | 55 | public MonoDebugSession() : base() 56 | { 57 | _variableHandles = new Handles(); 58 | _frameHandles = new Handles(); 59 | _seenThreads = new Dictionary(); 60 | 61 | _debuggerSessionOptions = new DebuggerSessionOptions { 62 | EvaluationOptions = EvaluationOptions.DefaultOptions 63 | }; 64 | 65 | _session = new Mono.Debugging.Soft.SoftDebuggerSession(); 66 | _session.Breakpoints = new BreakpointStore(); 67 | 68 | _breakpoints = new SortedDictionary(); 69 | _catchpoints = new List(); 70 | 71 | DebuggerLoggingService.CustomLogger = new CustomLogger(); 72 | 73 | _session.ExceptionHandler = ex => { 74 | return true; 75 | }; 76 | 77 | _session.LogWriter = (isStdErr, text) => { 78 | }; 79 | 80 | _session.TargetStopped += (sender, e) => { 81 | Stopped(); 82 | SendEvent(CreateStoppedEvent("step", e.Thread)); 83 | _resumeEvent.Set(); 84 | }; 85 | 86 | _session.TargetHitBreakpoint += (sender, e) => { 87 | Stopped(); 88 | SendEvent(CreateStoppedEvent("breakpoint", e.Thread)); 89 | _resumeEvent.Set(); 90 | }; 91 | 92 | _session.TargetExceptionThrown += (sender, e) => { 93 | Stopped(); 94 | var ex = DebuggerActiveException(); 95 | if (ex != null) { 96 | _exception = ex.Instance; 97 | SendEvent(CreateStoppedEvent("exception", e.Thread, ex.Message)); 98 | } 99 | _resumeEvent.Set(); 100 | }; 101 | 102 | _session.TargetUnhandledException += (sender, e) => { 103 | Stopped (); 104 | var ex = DebuggerActiveException(); 105 | if (ex != null) { 106 | _exception = ex.Instance; 107 | SendEvent(CreateStoppedEvent("exception", e.Thread, ex.Message)); 108 | } 109 | _resumeEvent.Set(); 110 | }; 111 | 112 | _session.TargetStarted += (sender, e) => { 113 | _activeFrame = null; 114 | }; 115 | 116 | _session.TargetReady += (sender, e) => { 117 | SetExceptionBreakpointsFromDap(exceptionOptionsFromDap); 118 | _activeProcess = _session.GetProcesses().SingleOrDefault(); 119 | }; 120 | 121 | _session.TargetExited += (sender, e) => { 122 | 123 | DebuggerKill(); 124 | 125 | _debuggeeKilled = true; 126 | 127 | Terminate("target exited"); 128 | 129 | _resumeEvent.Set(); 130 | }; 131 | 132 | _session.TargetInterrupted += (sender, e) => { 133 | _resumeEvent.Set(); 134 | }; 135 | 136 | _session.TargetEvent += (sender, e) => { 137 | }; 138 | 139 | _session.TargetThreadStarted += (sender, e) => { 140 | int tid = (int)e.Thread.Id; 141 | lock (_seenThreads) { 142 | _seenThreads[tid] = new Thread(tid, e.Thread.Name); 143 | } 144 | SendEvent(new ThreadEvent("started", tid)); 145 | }; 146 | 147 | _session.TargetThreadStopped += (sender, e) => { 148 | int tid = (int)e.Thread.Id; 149 | lock (_seenThreads) { 150 | _seenThreads.Remove(tid); 151 | } 152 | SendEvent(new ThreadEvent("exited", tid)); 153 | }; 154 | 155 | _session.OutputWriter = (isStdErr, text) => { 156 | SendOutput(isStdErr ? "stderr" : "stdout", text); 157 | }; 158 | } 159 | 160 | public override void Initialize(Response response, dynamic args) 161 | { 162 | OperatingSystem os = Environment.OSVersion; 163 | if (os.Platform != PlatformID.MacOSX && os.Platform != PlatformID.Unix && os.Platform != PlatformID.Win32NT) { 164 | SendErrorResponse(response, 3000, "Mono Debug is not supported on this platform ({_platform}).", new { _platform = os.Platform.ToString() }, true, true); 165 | return; 166 | } 167 | 168 | SendResponse(response, new Capabilities() { 169 | // This debug adapter does not need the configurationDoneRequest. 170 | supportsConfigurationDoneRequest = false, 171 | 172 | // This debug adapter does not support function breakpoints. 173 | supportsFunctionBreakpoints = false, 174 | 175 | // This debug adapter doesn't support conditional breakpoints. 176 | supportsConditionalBreakpoints = false, 177 | 178 | // This debug adapter does not support a side effect free evaluate request for data hovers. 179 | supportsEvaluateForHovers = false, 180 | 181 | supportsExceptionFilterOptions = true, 182 | exceptionBreakpointFilters = new dynamic[] { 183 | new { filter = "always", label = "All Exceptions", @default=false, supportsCondition=true, description="Break when an exception is thrown, even if it is caught later.", 184 | conditionDescription = "Comma-separated list of exception types to break on"}, 185 | new { filter = "uncaught", label = "Uncaught Exceptions", @default=false, supportsCondition=false, description="Breaks only on exceptions that are not handled."} 186 | } 187 | }); 188 | 189 | // Mono Debug is ready to accept breakpoints immediately 190 | SendEvent(new InitializedEvent()); 191 | } 192 | 193 | public override async void Launch(Response response, dynamic args) 194 | { 195 | _attachMode = false; 196 | 197 | SetExceptionBreakpoints(args.__exceptionOptions); 198 | 199 | // validate argument 'program' 200 | string programPath = getString(args, "program"); 201 | if (programPath == null) { 202 | SendErrorResponse(response, 3001, "Property 'program' is missing or empty.", null); 203 | return; 204 | } 205 | programPath = ConvertClientPathToDebugger(programPath); 206 | if (!File.Exists(programPath) && !Directory.Exists(programPath)) { 207 | SendErrorResponse(response, 3002, "Program '{path}' does not exist.", new { path = programPath }); 208 | return; 209 | } 210 | 211 | // validate argument 'cwd' 212 | var workingDirectory = (string)args.cwd; 213 | if (workingDirectory != null) { 214 | workingDirectory = workingDirectory.Trim(); 215 | if (workingDirectory.Length == 0) { 216 | SendErrorResponse(response, 3003, "Property 'cwd' is empty."); 217 | return; 218 | } 219 | workingDirectory = ConvertClientPathToDebugger(workingDirectory); 220 | if (!Directory.Exists(workingDirectory)) { 221 | SendErrorResponse(response, 3004, "Working directory '{path}' does not exist.", new { path = workingDirectory }); 222 | return; 223 | } 224 | } 225 | 226 | // validate argument 'runtimeExecutable' 227 | var runtimeExecutable = (string)args.runtimeExecutable; 228 | if (runtimeExecutable != null) { 229 | runtimeExecutable = runtimeExecutable.Trim(); 230 | if (runtimeExecutable.Length == 0) { 231 | SendErrorResponse(response, 3005, "Property 'runtimeExecutable' is empty."); 232 | return; 233 | } 234 | runtimeExecutable = ConvertClientPathToDebugger(runtimeExecutable); 235 | if (!File.Exists(runtimeExecutable)) { 236 | SendErrorResponse(response, 3006, "Runtime executable '{path}' does not exist.", new { path = runtimeExecutable }); 237 | return; 238 | } 239 | } 240 | 241 | 242 | // validate argument 'env' 243 | Dictionary env = new Dictionary(); 244 | var environmentVariables = args.env; 245 | if (environmentVariables != null) { 246 | foreach (var entry in environmentVariables) { 247 | env.Add((string)entry.Name, (string)entry.Value); 248 | } 249 | } 250 | 251 | const string host = "127.0.0.1"; 252 | int port = Utilities.FindFreePort(55555); 253 | 254 | string mono_path = runtimeExecutable; 255 | if (mono_path == null) { 256 | if (!Utilities.IsOnPath(MONO)) { 257 | SendErrorResponse(response, 3011, "Can't find runtime '{_runtime}' on PATH.", new { _runtime = MONO }); 258 | return; 259 | } 260 | mono_path = MONO; // try to find mono through PATH 261 | } 262 | 263 | 264 | var cmdLine = new List(); 265 | 266 | bool debug = !getBool(args, "noDebug", false); 267 | 268 | if (debug) { 269 | bool passDebugOptionsViaEnvironmentVariable = getBool(args, "passDebugOptionsViaEnvironmentVariable", false); 270 | 271 | if (passDebugOptionsViaEnvironmentVariable) { 272 | if (!env.ContainsKey("MONO_ENV_OPTIONS")) 273 | env["MONO_ENV_OPTIONS"] = $" --debug --debugger-agent=transport=dt_socket,server=y,address={host}:{port}"; 274 | else 275 | env["MONO_ENV_OPTIONS"] = $" --debug --debugger-agent=transport=dt_socket,server=y,address={host}:{port} " + env["MONO_ENV_OPTIONS"]; 276 | } 277 | else { 278 | cmdLine.Add("--debug"); 279 | cmdLine.Add($"--debugger-agent=transport=dt_socket,server=y,address={host}:{port}"); 280 | } 281 | } 282 | 283 | if (env.Count == 0) { 284 | env = null; 285 | } 286 | 287 | // add 'runtimeArgs' 288 | if (args.runtimeArgs != null) { 289 | string[] runtimeArguments = args.runtimeArgs.ToObject(); 290 | if (runtimeArguments != null && runtimeArguments.Length > 0) { 291 | cmdLine.AddRange(runtimeArguments); 292 | } 293 | } 294 | 295 | // add 'program' 296 | if (workingDirectory == null) { 297 | // if no working dir given, we use the direct folder of the executable 298 | workingDirectory = Path.GetDirectoryName(programPath); 299 | cmdLine.Add(Path.GetFileName(programPath)); 300 | } 301 | else { 302 | // if working dir is given and if the executable is within that folder, we make the program path relative to the working dir 303 | cmdLine.Add(Utilities.MakeRelativePath(workingDirectory, programPath)); 304 | } 305 | 306 | // add 'args' 307 | if (args.args != null) { 308 | string[] arguments = args.args.ToObject(); 309 | if (arguments != null && arguments.Length > 0) { 310 | cmdLine.AddRange(arguments); 311 | } 312 | } 313 | 314 | // what console? 315 | var console = getString(args, "console", null); 316 | if (console == null) { 317 | // continue to read the deprecated "externalConsole" attribute 318 | bool externalConsole = getBool(args, "externalConsole", false); 319 | if (externalConsole) { 320 | console = "externalTerminal"; 321 | } 322 | } 323 | 324 | if (console == "externalTerminal" || console == "integratedTerminal") { 325 | 326 | cmdLine.Insert(0, mono_path); 327 | var termArgs = new { 328 | kind = console == "integratedTerminal" ? "integrated" : "external", 329 | title = "Node Debug Console", 330 | cwd = workingDirectory, 331 | args = cmdLine.ToArray(), 332 | env 333 | }; 334 | 335 | var resp = await SendRequest("runInTerminal", termArgs); 336 | if (!resp.success) { 337 | SendErrorResponse(response, 3011, "Cannot launch debug target in terminal ({_error}).", new { _error = resp.message }); 338 | return; 339 | } 340 | 341 | } else { // internalConsole 342 | 343 | _process = new System.Diagnostics.Process(); 344 | _process.StartInfo.CreateNoWindow = true; 345 | _process.StartInfo.UseShellExecute = false; 346 | _process.StartInfo.WorkingDirectory = workingDirectory; 347 | _process.StartInfo.FileName = mono_path; 348 | _process.StartInfo.Arguments = Utilities.ConcatArgs(cmdLine.ToArray()); 349 | 350 | _stdoutEOF = false; 351 | _process.StartInfo.RedirectStandardOutput = true; 352 | _process.OutputDataReceived += (object sender, System.Diagnostics.DataReceivedEventArgs e) => { 353 | if (e.Data == null) { 354 | _stdoutEOF = true; 355 | } 356 | SendOutput("stdout", e.Data); 357 | }; 358 | 359 | _stderrEOF = false; 360 | _process.StartInfo.RedirectStandardError = true; 361 | _process.ErrorDataReceived += (object sender, System.Diagnostics.DataReceivedEventArgs e) => { 362 | if (e.Data == null) { 363 | _stderrEOF = true; 364 | } 365 | SendOutput("stderr", e.Data); 366 | }; 367 | 368 | _process.EnableRaisingEvents = true; 369 | _process.Exited += (object sender, EventArgs e) => { 370 | Terminate("runtime process exited"); 371 | }; 372 | 373 | if (env != null) { 374 | // we cannot set the env vars on the process StartInfo because we need to set StartInfo.UseShellExecute to true at the same time. 375 | // instead we set the env vars on MonoDebug itself because we know that MonoDebug lives as long as a debug session. 376 | foreach (var entry in env) { 377 | System.Environment.SetEnvironmentVariable(entry.Key, entry.Value); 378 | } 379 | } 380 | 381 | var cmd = string.Format("{0} {1}", mono_path, _process.StartInfo.Arguments); 382 | SendOutput("console", cmd); 383 | 384 | try { 385 | _process.Start(); 386 | _process.BeginOutputReadLine(); 387 | _process.BeginErrorReadLine(); 388 | } 389 | catch (Exception e) { 390 | SendErrorResponse(response, 3012, "Can't launch terminal ({reason}).", new { reason = e.Message }); 391 | return; 392 | } 393 | } 394 | 395 | if (debug) { 396 | Connect(IPAddress.Parse(host), port); 397 | } 398 | 399 | SendResponse(response); 400 | 401 | if (_process == null && !debug) { 402 | // we cannot track mono runtime process so terminate this session 403 | Terminate("cannot track mono runtime"); 404 | } 405 | } 406 | 407 | public override void Attach(Response response, dynamic args) 408 | { 409 | _attachMode = true; 410 | 411 | SetExceptionBreakpoints(args.__exceptionOptions); 412 | 413 | // validate argument 'address' 414 | var host = getString(args, "address"); 415 | if (host == null) { 416 | SendErrorResponse(response, 3007, "Property 'address' is missing or empty."); 417 | return; 418 | } 419 | 420 | // validate argument 'port' 421 | var port = getInt(args, "port", -1); 422 | if (port == -1) { 423 | SendErrorResponse(response, 3008, "Property 'port' is missing."); 424 | return; 425 | } 426 | 427 | IPAddress address = Utilities.ResolveIPAddress(host); 428 | if (address == null) { 429 | SendErrorResponse(response, 3013, "Invalid address '{address}'.", new { address = address }); 430 | return; 431 | } 432 | 433 | Connect(address, port); 434 | 435 | SendResponse(response); 436 | } 437 | 438 | public override void Disconnect(Response response, dynamic args) 439 | { 440 | if (_attachMode) { 441 | 442 | lock (_lock) { 443 | if (_session != null) { 444 | _debuggeeExecuting = true; 445 | _breakpoints.Clear(); 446 | _session.Breakpoints.Clear(); 447 | _session.Continue(); 448 | _session = null; 449 | } 450 | } 451 | 452 | } else { 453 | // Let's not leave dead Mono processes behind... 454 | if (_process != null) { 455 | _process.Kill(); 456 | _process = null; 457 | } else { 458 | PauseDebugger(); 459 | DebuggerKill(); 460 | 461 | while (!_debuggeeKilled) { 462 | System.Threading.Thread.Sleep(10); 463 | } 464 | } 465 | } 466 | 467 | SendResponse(response); 468 | } 469 | 470 | public override void Continue(Response response, dynamic args) 471 | { 472 | WaitForSuspend(); 473 | SendResponse(response); 474 | lock (_lock) { 475 | if (_session != null && !_session.IsRunning && !_session.HasExited) { 476 | _session.Continue(); 477 | _debuggeeExecuting = true; 478 | } 479 | } 480 | } 481 | 482 | public override void Next(Response response, dynamic args) 483 | { 484 | WaitForSuspend(); 485 | SendResponse(response); 486 | lock (_lock) { 487 | if (_session != null && !_session.IsRunning && !_session.HasExited) { 488 | _session.NextLine(); 489 | _debuggeeExecuting = true; 490 | } 491 | } 492 | } 493 | 494 | public override void StepIn(Response response, dynamic args) 495 | { 496 | WaitForSuspend(); 497 | SendResponse(response); 498 | lock (_lock) { 499 | if (_session != null && !_session.IsRunning && !_session.HasExited) { 500 | _session.StepLine(); 501 | _debuggeeExecuting = true; 502 | } 503 | } 504 | } 505 | 506 | public override void StepOut(Response response, dynamic args) 507 | { 508 | WaitForSuspend(); 509 | SendResponse(response); 510 | lock (_lock) { 511 | if (_session != null && !_session.IsRunning && !_session.HasExited) { 512 | _session.Finish(); 513 | _debuggeeExecuting = true; 514 | } 515 | } 516 | } 517 | 518 | public override void Pause(Response response, dynamic args) 519 | { 520 | SendResponse(response); 521 | PauseDebugger(); 522 | } 523 | 524 | public override void SetExceptionBreakpoints(Response response, dynamic args) 525 | { 526 | if (args.filterOptions != null) 527 | { 528 | if (_activeProcess != null) 529 | SetExceptionBreakpointsFromDap(args.filterOptions); 530 | else 531 | exceptionOptionsFromDap = args.filterOptions; 532 | } 533 | else 534 | SetExceptionBreakpoints(args.exceptionOptions); 535 | SendResponse(response); 536 | } 537 | 538 | public override void SetBreakpoints(Response response, dynamic args) 539 | { 540 | string path = null; 541 | if (args.source != null) { 542 | string p = (string)args.source.path; 543 | if (p != null && p.Trim().Length > 0) { 544 | path = p; 545 | } 546 | } 547 | if (path == null) { 548 | SendErrorResponse(response, 3010, "setBreakpoints: property 'source' is empty or misformed", null, false, true); 549 | return; 550 | } 551 | path = ConvertClientPathToDebugger(path); 552 | 553 | if (!HasMonoExtension(path)) { 554 | // we only support breakpoints in files mono can handle 555 | SendResponse(response, new SetBreakpointsResponseBody()); 556 | return; 557 | } 558 | 559 | var clientLines = args.lines.ToObject(); 560 | HashSet lin = new HashSet(); 561 | for (int i = 0; i < clientLines.Length; i++) { 562 | lin.Add(ConvertClientLineToDebugger(clientLines[i])); 563 | } 564 | 565 | // find all breakpoints for the given path and remember their id and line number 566 | var bpts = new List>(); 567 | foreach (var be in _breakpoints) { 568 | var bp = be.Value as Mono.Debugging.Client.Breakpoint; 569 | if (bp != null && bp.FileName == path) { 570 | bpts.Add(new Tuple((int)be.Key, (int)bp.Line)); 571 | } 572 | } 573 | 574 | HashSet lin2 = new HashSet(); 575 | foreach (var bpt in bpts) { 576 | if (lin.Contains(bpt.Item2)) { 577 | lin2.Add(bpt.Item2); 578 | } 579 | else { 580 | // Program.Log("cleared bpt #{0} for line {1}", bpt.Item1, bpt.Item2); 581 | 582 | BreakEvent b; 583 | if (_breakpoints.TryGetValue(bpt.Item1, out b)) { 584 | _breakpoints.Remove(bpt.Item1); 585 | _session.Breakpoints.Remove(b); 586 | } 587 | } 588 | } 589 | 590 | for (int i = 0; i < clientLines.Length; i++) { 591 | var l = ConvertClientLineToDebugger(clientLines[i]); 592 | if (!lin2.Contains(l)) { 593 | var id = _nextBreakpointId++; 594 | _breakpoints.Add(id, _session.Breakpoints.Add(path, l)); 595 | // Program.Log("added bpt #{0} for line {1}", id, l); 596 | } 597 | } 598 | 599 | var breakpoints = new List(); 600 | foreach (var l in clientLines) { 601 | breakpoints.Add(new Breakpoint(true, l)); 602 | } 603 | 604 | SendResponse(response, new SetBreakpointsResponseBody(breakpoints)); 605 | } 606 | 607 | public override void StackTrace(Response response, dynamic args) 608 | { 609 | int maxLevels = getInt(args, "levels", 10); 610 | int threadReference = getInt(args, "threadId", 0); 611 | 612 | WaitForSuspend(); 613 | 614 | ThreadInfo thread = DebuggerActiveThread(); 615 | if (thread.Id != threadReference) { 616 | // Program.Log("stackTrace: unexpected: active thread should be the one requested"); 617 | thread = FindThread(threadReference); 618 | if (thread != null) { 619 | thread.SetActive(); 620 | } 621 | } 622 | 623 | var stackFrames = new List(); 624 | int totalFrames = 0; 625 | 626 | var bt = thread.Backtrace; 627 | if (bt != null && bt.FrameCount >= 0) { 628 | 629 | totalFrames = bt.FrameCount; 630 | 631 | for (var i = 0; i < Math.Min(totalFrames, maxLevels); i++) { 632 | 633 | var frame = bt.GetFrame(i); 634 | 635 | string path = frame.SourceLocation.FileName; 636 | 637 | var hint = "subtle"; 638 | Source source = null; 639 | if (!string.IsNullOrEmpty(path)) { 640 | string sourceName = Path.GetFileName(path); 641 | if (!string.IsNullOrEmpty(sourceName)) { 642 | if (File.Exists(path)) { 643 | source = new Source(sourceName, ConvertDebuggerPathToClient(path), 0, "normal"); 644 | hint = "normal"; 645 | } else { 646 | source = new Source(sourceName, null, 1000, "deemphasize"); 647 | } 648 | } 649 | } 650 | 651 | var frameHandle = _frameHandles.Create(frame); 652 | string name = frame.SourceLocation.MethodName; 653 | int line = frame.SourceLocation.Line; 654 | stackFrames.Add(new StackFrame(frameHandle, name, source, ConvertDebuggerLineToClient(line), 0, hint)); 655 | } 656 | } 657 | 658 | SendResponse(response, new StackTraceResponseBody(stackFrames, totalFrames)); 659 | } 660 | 661 | public override void Source(Response response, dynamic arguments) { 662 | SendErrorResponse(response, 1020, "No source available"); 663 | } 664 | 665 | public override void Scopes(Response response, dynamic args) { 666 | 667 | int frameId = getInt(args, "frameId", 0); 668 | var frame = _frameHandles.Get(frameId, null); 669 | 670 | var scopes = new List(); 671 | 672 | if (frame.Index == 0 && _exception != null) { 673 | scopes.Add(new Scope("Exception", _variableHandles.Create(new ObjectValue[] { _exception }))); 674 | } 675 | 676 | var locals = new[] { frame.GetThisReference() }.Concat(frame.GetParameters()).Concat(frame.GetLocalVariables()).Where(x => x != null).ToArray(); 677 | if (locals.Length > 0) { 678 | scopes.Add(new Scope("Local", _variableHandles.Create(locals))); 679 | } 680 | 681 | SendResponse(response, new ScopesResponseBody(scopes)); 682 | } 683 | 684 | public override void Variables(Response response, dynamic args) 685 | { 686 | int reference = getInt(args, "variablesReference", -1); 687 | if (reference == -1) { 688 | SendErrorResponse(response, 3009, "variables: property 'variablesReference' is missing", null, false, true); 689 | return; 690 | } 691 | 692 | WaitForSuspend(); 693 | var variables = new List(); 694 | 695 | ObjectValue[] children; 696 | if (_variableHandles.TryGet(reference, out children)) { 697 | if (children != null && children.Length > 0) { 698 | 699 | bool more = false; 700 | if (children.Length > MAX_CHILDREN) { 701 | children = children.Take(MAX_CHILDREN).ToArray(); 702 | more = true; 703 | } 704 | 705 | if (children.Length < 20) { 706 | // Wait for all values at once. 707 | WaitHandle.WaitAll(children.Select(x => x.WaitHandle).ToArray()); 708 | foreach (var v in children) { 709 | variables.Add(CreateVariable(v)); 710 | } 711 | } 712 | else { 713 | foreach (var v in children) { 714 | v.WaitHandle.WaitOne(); 715 | variables.Add(CreateVariable(v)); 716 | } 717 | } 718 | 719 | if (more) { 720 | variables.Add(new Variable("...", null, null)); 721 | } 722 | } 723 | } 724 | 725 | SendResponse(response, new VariablesResponseBody(variables)); 726 | } 727 | 728 | public override void Threads(Response response, dynamic args) 729 | { 730 | var threads = new List(); 731 | var process = _activeProcess; 732 | if (process != null) { 733 | Dictionary d; 734 | lock (_seenThreads) { 735 | d = new Dictionary(_seenThreads); 736 | } 737 | foreach (var t in process.GetThreads()) { 738 | int tid = (int)t.Id; 739 | d[tid] = new Thread(tid, t.Name); 740 | } 741 | threads = d.Values.ToList(); 742 | } 743 | SendResponse(response, new ThreadsResponseBody(threads)); 744 | } 745 | 746 | public override void Evaluate(Response response, dynamic args) 747 | { 748 | string error = null; 749 | 750 | var expression = getString(args, "expression"); 751 | if (expression == null) { 752 | error = "expression missing"; 753 | } else { 754 | int frameId = getInt(args, "frameId", -1); 755 | var frame = _frameHandles.Get(frameId, null); 756 | if (frame != null) { 757 | if (frame.ValidateExpression(expression)) { 758 | var val = frame.GetExpressionValue(expression, _debuggerSessionOptions.EvaluationOptions); 759 | val.WaitHandle.WaitOne(); 760 | 761 | var flags = val.Flags; 762 | if (flags.HasFlag(ObjectValueFlags.Error) || flags.HasFlag(ObjectValueFlags.NotSupported)) { 763 | error = val.DisplayValue; 764 | if (error.IndexOf("reference not available in the current evaluation context") > 0) { 765 | error = "not available"; 766 | } 767 | } 768 | else if (flags.HasFlag(ObjectValueFlags.Unknown)) { 769 | error = "invalid expression"; 770 | } 771 | else if (flags.HasFlag(ObjectValueFlags.Object) && flags.HasFlag(ObjectValueFlags.Namespace)) { 772 | error = "not available"; 773 | } 774 | else { 775 | int handle = 0; 776 | if (val.HasChildren) { 777 | handle = _variableHandles.Create(val.GetAllChildren()); 778 | } 779 | SendResponse(response, new EvaluateResponseBody(val.DisplayValue, handle)); 780 | return; 781 | } 782 | } 783 | else { 784 | error = "invalid expression"; 785 | } 786 | } 787 | else { 788 | error = "no active stackframe"; 789 | } 790 | } 791 | SendErrorResponse(response, 3014, "Evaluate request failed ({_reason}).", new { _reason = error } ); 792 | } 793 | 794 | //---- private ------------------------------------------ 795 | 796 | private void SetExceptionBreakpointsFromDap(dynamic exceptionOptions) 797 | { 798 | if (exceptionOptions != null) { 799 | var exceptions = exceptionOptions.ToObject(); 800 | for (int i = 0; i < exceptions.Length; i++) { 801 | var exception = exceptions[i]; 802 | 803 | bool caught = exception.filterId == "always" ? true : false; 804 | if (exception.condition != null && exception.condition != "") { 805 | string[] conditionNames = exception.condition.ToString().Split(','); 806 | foreach (var conditionName in conditionNames) 807 | _session.EnableException(conditionName, caught); 808 | } 809 | else { 810 | _session.EnableException("System.Exception", caught); 811 | } 812 | } 813 | } 814 | } 815 | private void SetExceptionBreakpoints(dynamic exceptionOptions) 816 | { 817 | if (exceptionOptions != null) { 818 | 819 | // clear all existig catchpoints 820 | foreach (var cp in _catchpoints) { 821 | _session.Breakpoints.Remove(cp); 822 | } 823 | _catchpoints.Clear(); 824 | 825 | var exceptions = exceptionOptions.ToObject(); 826 | for (int i = 0; i < exceptions.Length; i++) { 827 | 828 | var exception = exceptions[i]; 829 | 830 | string exName = null; 831 | string exBreakMode = exception.breakMode; 832 | 833 | if (exception.path != null) { 834 | var paths = exception.path.ToObject(); 835 | var path = paths[0]; 836 | if (path.names != null) { 837 | var names = path.names.ToObject(); 838 | if (names.Length > 0) { 839 | exName = names[0]; 840 | } 841 | } 842 | } 843 | 844 | if (exName != null && exBreakMode == "always") { 845 | _catchpoints.Add(_session.Breakpoints.AddCatchpoint(exName)); 846 | } 847 | } 848 | } 849 | } 850 | 851 | private void SendOutput(string category, string data) { 852 | if (!String.IsNullOrEmpty(data)) { 853 | if (data[data.Length-1] != '\n') { 854 | data += '\n'; 855 | } 856 | SendEvent(new OutputEvent(category, data)); 857 | } 858 | } 859 | 860 | private void Terminate(string reason) { 861 | if (!_terminated) { 862 | 863 | // wait until we've seen the end of stdout and stderr 864 | for (int i = 0; i < 100 && (_stdoutEOF == false || _stderrEOF == false); i++) { 865 | System.Threading.Thread.Sleep(100); 866 | } 867 | 868 | SendEvent(new TerminatedEvent()); 869 | 870 | _terminated = true; 871 | _process = null; 872 | } 873 | } 874 | 875 | private StoppedEvent CreateStoppedEvent(string reason, ThreadInfo ti, string text = null) 876 | { 877 | return new StoppedEvent((int)ti.Id, reason, text); 878 | } 879 | 880 | private ThreadInfo FindThread(int threadReference) 881 | { 882 | if (_activeProcess != null) { 883 | foreach (var t in _activeProcess.GetThreads()) { 884 | if (t.Id == threadReference) { 885 | return t; 886 | } 887 | } 888 | } 889 | return null; 890 | } 891 | 892 | private void Stopped() 893 | { 894 | _exception = null; 895 | _variableHandles.Reset(); 896 | _frameHandles.Reset(); 897 | } 898 | 899 | private Variable CreateVariable(ObjectValue v) 900 | { 901 | var dv = v.DisplayValue; 902 | if (dv.Length > 1 && dv [0] == '{' && dv [dv.Length - 1] == '}') { 903 | dv = dv.Substring (1, dv.Length - 2); 904 | } 905 | return new Variable(v.Name, dv, v.TypeName, v.HasChildren ? _variableHandles.Create(v.GetAllChildren()) : 0); 906 | } 907 | 908 | private bool HasMonoExtension(string path) 909 | { 910 | foreach (var e in MONO_EXTENSIONS) { 911 | if (path.EndsWith(e)) { 912 | return true; 913 | } 914 | } 915 | return false; 916 | } 917 | 918 | private static bool getBool(dynamic container, string propertyName, bool dflt = false) 919 | { 920 | try { 921 | return (bool)container[propertyName]; 922 | } 923 | catch (Exception) { 924 | // ignore and return default value 925 | } 926 | return dflt; 927 | } 928 | 929 | private static int getInt(dynamic container, string propertyName, int dflt = 0) 930 | { 931 | try { 932 | return (int)container[propertyName]; 933 | } 934 | catch (Exception) { 935 | // ignore and return default value 936 | } 937 | return dflt; 938 | } 939 | 940 | private static string getString(dynamic args, string property, string dflt = null) 941 | { 942 | var s = (string)args[property]; 943 | if (s == null) { 944 | return dflt; 945 | } 946 | s = s.Trim(); 947 | if (s.Length == 0) { 948 | return dflt; 949 | } 950 | return s; 951 | } 952 | 953 | //----------------------- 954 | 955 | private void WaitForSuspend() 956 | { 957 | if (_debuggeeExecuting) { 958 | _resumeEvent.WaitOne(); 959 | _debuggeeExecuting = false; 960 | } 961 | } 962 | 963 | private ThreadInfo DebuggerActiveThread() 964 | { 965 | lock (_lock) { 966 | return _session == null ? null : _session.ActiveThread; 967 | } 968 | } 969 | 970 | private Backtrace DebuggerActiveBacktrace() { 971 | var thr = DebuggerActiveThread(); 972 | return thr == null ? null : thr.Backtrace; 973 | } 974 | 975 | private Mono.Debugging.Client.StackFrame DebuggerActiveFrame() { 976 | if (_activeFrame != null) 977 | return _activeFrame; 978 | 979 | var bt = DebuggerActiveBacktrace(); 980 | if (bt != null) 981 | return _activeFrame = bt.GetFrame(0); 982 | 983 | return null; 984 | } 985 | 986 | private ExceptionInfo DebuggerActiveException() { 987 | var bt = DebuggerActiveBacktrace(); 988 | return bt == null ? null : bt.GetFrame(0).GetException(); 989 | } 990 | 991 | private void Connect(IPAddress address, int port) 992 | { 993 | lock (_lock) { 994 | 995 | _debuggeeKilled = false; 996 | 997 | var args0 = new Mono.Debugging.Soft.SoftDebuggerConnectArgs(string.Empty, address, port) { 998 | MaxConnectionAttempts = MAX_CONNECTION_ATTEMPTS, 999 | TimeBetweenConnectionAttempts = CONNECTION_ATTEMPT_INTERVAL 1000 | }; 1001 | 1002 | _session.Run(new Mono.Debugging.Soft.SoftDebuggerStartInfo(args0), _debuggerSessionOptions); 1003 | 1004 | _debuggeeExecuting = true; 1005 | } 1006 | } 1007 | 1008 | private void PauseDebugger() 1009 | { 1010 | lock (_lock) { 1011 | if (_session != null && _session.IsRunning) 1012 | _session.Stop(); 1013 | } 1014 | } 1015 | 1016 | private void DebuggerKill() 1017 | { 1018 | lock (_lock) { 1019 | if (_session != null) { 1020 | 1021 | _debuggeeExecuting = true; 1022 | 1023 | if (!_session.HasExited) 1024 | _session.Exit(); 1025 | 1026 | _session.Dispose(); 1027 | _session = null; 1028 | } 1029 | } 1030 | } 1031 | } 1032 | } 1033 | -------------------------------------------------------------------------------- /src/csharp/Protocol.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.Text; 7 | using System.IO; 8 | using System.Threading.Tasks; 9 | using System.Text.RegularExpressions; 10 | using Newtonsoft.Json; 11 | using System.Collections.Generic; 12 | 13 | namespace VSCodeDebug 14 | { 15 | public class ProtocolMessage 16 | { 17 | public int seq; 18 | public string type; 19 | 20 | public ProtocolMessage() { 21 | } 22 | public ProtocolMessage(string typ) { 23 | type = typ; 24 | } 25 | public ProtocolMessage(string typ, int sq) { 26 | type = typ; 27 | seq = sq; 28 | } 29 | } 30 | 31 | public class Request : ProtocolMessage 32 | { 33 | public string command; 34 | public dynamic arguments; 35 | 36 | public Request() { 37 | } 38 | public Request(string cmd, dynamic arg) : base("request") { 39 | command = cmd; 40 | arguments = arg; 41 | } 42 | public Request(int id, string cmd, dynamic arg) : base("request", id) { 43 | command = cmd; 44 | arguments = arg; 45 | } 46 | } 47 | 48 | /* 49 | * subclasses of ResponseBody are serialized as the body of a response. 50 | * Don't change their instance variables since that will break the debug protocol. 51 | */ 52 | public class ResponseBody { 53 | // empty 54 | } 55 | 56 | public class Response : ProtocolMessage 57 | { 58 | public bool success; 59 | public string message; 60 | public int request_seq; 61 | public string command; 62 | public ResponseBody body; 63 | 64 | public Response() { 65 | } 66 | public Response(Request req) : base("response") { 67 | success = true; 68 | request_seq = req.seq; 69 | command = req.command; 70 | } 71 | 72 | public void SetBody(ResponseBody bdy) { 73 | success = true; 74 | body = bdy; 75 | } 76 | 77 | public void SetErrorBody(string msg, ResponseBody bdy = null) { 78 | success = false; 79 | message = msg; 80 | body = bdy; 81 | } 82 | } 83 | 84 | public class Event : ProtocolMessage 85 | { 86 | [JsonProperty(PropertyName = "event")] 87 | public string eventType { get; } 88 | public dynamic body { get; } 89 | 90 | public Event(string type, dynamic bdy = null) : base("event") { 91 | eventType = type; 92 | body = bdy; 93 | } 94 | } 95 | 96 | /* 97 | * The ProtocolServer can be used to implement a server that uses the VSCode debug protocol. 98 | */ 99 | public abstract class ProtocolServer 100 | { 101 | public bool TRACE; 102 | public bool TRACE_RESPONSE; 103 | 104 | protected const int BUFFER_SIZE = 4096; 105 | protected const string TWO_CRLF = "\r\n\r\n"; 106 | protected static readonly Regex CONTENT_LENGTH_MATCHER = new Regex(@"Content-Length: (\d+)"); 107 | 108 | protected static readonly Encoding Encoding = System.Text.Encoding.UTF8; 109 | 110 | private int _sequenceNumber; 111 | private Dictionary> _pendingRequests; 112 | 113 | private Stream _outputStream; 114 | 115 | private ByteBuffer _rawData; 116 | private int _bodyLength; 117 | 118 | private bool _stopRequested; 119 | 120 | 121 | public ProtocolServer() 122 | { 123 | _sequenceNumber = 1; 124 | _bodyLength = -1; 125 | _rawData = new ByteBuffer(); 126 | _pendingRequests = new Dictionary>(); 127 | } 128 | 129 | public async Task Start(Stream inputStream, Stream outputStream) 130 | { 131 | _outputStream = outputStream; 132 | 133 | byte[] buffer = new byte[BUFFER_SIZE]; 134 | 135 | _stopRequested = false; 136 | while (!_stopRequested) { 137 | var read = await inputStream.ReadAsync(buffer, 0, buffer.Length); 138 | 139 | if (read == 0) { 140 | // end of stream 141 | break; 142 | } 143 | 144 | if (read > 0) { 145 | _rawData.Append(buffer, read); 146 | ProcessData(); 147 | } 148 | } 149 | } 150 | 151 | public void Stop() 152 | { 153 | _stopRequested = true; 154 | } 155 | 156 | public void SendEvent(Event e) 157 | { 158 | SendMessage(e); 159 | } 160 | 161 | public Task SendRequest(string command, dynamic args) 162 | { 163 | var tcs = new TaskCompletionSource(); 164 | 165 | Request request = null; 166 | lock (_pendingRequests) { 167 | request = new Request(_sequenceNumber++, command, args); 168 | // wait for response 169 | _pendingRequests.Add(request.seq, tcs); 170 | } 171 | 172 | SendMessage(request); 173 | 174 | return tcs.Task; 175 | } 176 | 177 | protected abstract void DispatchRequest(string command, dynamic args, Response response); 178 | 179 | // ---- private ------------------------------------------------------------------------ 180 | 181 | private void ProcessData() 182 | { 183 | while (true) { 184 | if (_bodyLength >= 0) { 185 | if (_rawData.Length >= _bodyLength) { 186 | var buf = _rawData.RemoveFirst(_bodyLength); 187 | 188 | _bodyLength = -1; 189 | 190 | Dispatch(Encoding.GetString(buf)); 191 | 192 | continue; // there may be more complete messages to process 193 | } 194 | } 195 | else { 196 | string s = _rawData.GetString(Encoding); 197 | var idx = s.IndexOf(TWO_CRLF); 198 | if (idx != -1) { 199 | Match m = CONTENT_LENGTH_MATCHER.Match(s); 200 | if (m.Success && m.Groups.Count == 2) { 201 | _bodyLength = Convert.ToInt32(m.Groups[1].ToString()); 202 | 203 | _rawData.RemoveFirst(idx + TWO_CRLF.Length); 204 | 205 | continue; // try to handle a complete message 206 | } 207 | } 208 | } 209 | break; 210 | } 211 | } 212 | 213 | private void Dispatch(string req) 214 | { 215 | var message = JsonConvert.DeserializeObject(req); 216 | if (message != null) { 217 | switch (message.type) { 218 | 219 | case "request": 220 | { 221 | var request = JsonConvert.DeserializeObject(req); 222 | 223 | Program.Log(TRACE, "C {0}: {1}", request.command, JsonConvert.SerializeObject(request.arguments, Formatting.Indented)); 224 | 225 | var response = new Response(request); 226 | DispatchRequest(request.command, request.arguments, response); 227 | SendMessage(response); 228 | } 229 | break; 230 | 231 | case "response": 232 | { 233 | var response = JsonConvert.DeserializeObject(req); 234 | int seq = response.request_seq; 235 | lock (_pendingRequests) { 236 | if (_pendingRequests.ContainsKey(seq)) { 237 | var tcs = _pendingRequests[seq]; 238 | _pendingRequests.Remove(seq); 239 | tcs.SetResult(response); 240 | } 241 | } 242 | } 243 | break; 244 | } 245 | } 246 | } 247 | 248 | protected void SendMessage(ProtocolMessage message) 249 | { 250 | if (message.seq == 0) { 251 | message.seq = _sequenceNumber++; 252 | } 253 | 254 | Program.Log(TRACE_RESPONSE && message.type == "response", " R: {0}", JsonConvert.SerializeObject(message, Formatting.Indented)); 255 | 256 | if (message.type == "event" && message is Event) { 257 | var e = message as Event; 258 | Program.Log(TRACE, "E {0}: {1}", ((Event)message).eventType, JsonConvert.SerializeObject(e.body, Formatting.Indented)); 259 | } 260 | 261 | var data = ConvertToBytes(message); 262 | try { 263 | _outputStream.Write(data, 0, data.Length); 264 | _outputStream.Flush(); 265 | } 266 | catch (Exception) { 267 | // ignore 268 | } 269 | } 270 | 271 | private static byte[] ConvertToBytes(ProtocolMessage request) 272 | { 273 | var asJson = JsonConvert.SerializeObject(request); 274 | byte[] jsonBytes = Encoding.GetBytes(asJson); 275 | 276 | string header = string.Format("Content-Length: {0}{1}", jsonBytes.Length, TWO_CRLF); 277 | byte[] headerBytes = Encoding.GetBytes(header); 278 | 279 | byte[] data = new byte[headerBytes.Length + jsonBytes.Length]; 280 | System.Buffer.BlockCopy(headerBytes, 0, data, 0, headerBytes.Length); 281 | System.Buffer.BlockCopy(jsonBytes, 0, data, headerBytes.Length, jsonBytes.Length); 282 | 283 | return data; 284 | } 285 | } 286 | 287 | //-------------------------------------------------------------------------------------- 288 | 289 | class ByteBuffer 290 | { 291 | private byte[] _buffer; 292 | 293 | public ByteBuffer() { 294 | _buffer = new byte[0]; 295 | } 296 | 297 | public int Length { 298 | get { return _buffer.Length; } 299 | } 300 | 301 | public string GetString(Encoding enc) 302 | { 303 | return enc.GetString(_buffer); 304 | } 305 | 306 | public void Append(byte[] b, int length) 307 | { 308 | byte[] newBuffer = new byte[_buffer.Length + length]; 309 | System.Buffer.BlockCopy(_buffer, 0, newBuffer, 0, _buffer.Length); 310 | System.Buffer.BlockCopy(b, 0, newBuffer, _buffer.Length, length); 311 | _buffer = newBuffer; 312 | } 313 | 314 | public byte[] RemoveFirst(int n) 315 | { 316 | byte[] b = new byte[n]; 317 | System.Buffer.BlockCopy(_buffer, 0, b, 0, n); 318 | byte[] newBuffer = new byte[_buffer.Length - n]; 319 | System.Buffer.BlockCopy(_buffer, n, newBuffer, 0, _buffer.Length - n); 320 | _buffer = newBuffer; 321 | return b; 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/csharp/Utilities.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.Net.Sockets; 7 | using System.Net; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Text.RegularExpressions; 11 | using System.Reflection; 12 | using System.Diagnostics; 13 | 14 | using Mono.Debugging.Client; 15 | 16 | namespace VSCodeDebug 17 | { 18 | public class Utilities 19 | { 20 | private const string WHICH = "/usr/bin/which"; 21 | private const string WHERE = "where"; 22 | 23 | private static readonly Regex VARIABLE = new Regex(@"\{(\w+)\}"); 24 | 25 | private static char[] ARGUMENT_SEPARATORS = new char[] { ' ', '\t' }; 26 | 27 | /* 28 | * Enclose the given string in quotes if it contains space or tab characters. 29 | */ 30 | public static string Quote(string arg) 31 | { 32 | if (arg.IndexOfAny(ARGUMENT_SEPARATORS) >= 0) { 33 | return '"' + arg + '"'; 34 | } 35 | return arg; 36 | } 37 | 38 | /* 39 | * Is the given runtime executable on the PATH. 40 | */ 41 | public static bool IsOnPath(string runtime) 42 | { 43 | var process = new Process(); 44 | 45 | process.StartInfo.CreateNoWindow = true; 46 | process.StartInfo.UseShellExecute = false; 47 | process.StartInfo.RedirectStandardOutput = true; 48 | process.StartInfo.FileName = File.Exists(WHICH) ? WHICH : WHERE; 49 | process.StartInfo.Arguments = Quote(runtime); 50 | 51 | try { 52 | process.Start(); 53 | process.WaitForExit(); 54 | return process.ExitCode == 0; 55 | } catch (Exception) { 56 | // ignore 57 | } 58 | 59 | return false; 60 | } 61 | 62 | public static string ConcatArgs(string[] args) 63 | { 64 | var arg = ""; 65 | if (args != null) { 66 | foreach (var r in args) { 67 | if (arg.Length > 0) { 68 | arg += " "; 69 | } 70 | arg += Utilities.Quote(r); 71 | } 72 | } 73 | return arg; 74 | } 75 | 76 | /* 77 | * Resolve hostname, dotted-quad notation for IPv4, or colon-hexadecimal notation for IPv6 to IPAddress. 78 | * Returns null on failure. 79 | */ 80 | public static IPAddress ResolveIPAddress(string addressString) 81 | { 82 | try { 83 | IPAddress ipaddress = null; 84 | if (IPAddress.TryParse(addressString, out ipaddress)) { 85 | return ipaddress; 86 | } 87 | 88 | #if DNXCORE50 89 | IPHostEntry entry = Dns.GetHostEntryAsync(addressString).Result; 90 | #else 91 | IPHostEntry entry = Dns.GetHostEntry(addressString); 92 | #endif 93 | if (entry != null && entry.AddressList != null && entry.AddressList.Length > 0) { 94 | if (entry.AddressList.Length == 1) { 95 | return entry.AddressList[0]; 96 | } 97 | foreach (IPAddress address in entry.AddressList) { 98 | if (address.AddressFamily == AddressFamily.InterNetwork) { 99 | return address; 100 | } 101 | } 102 | } 103 | } 104 | catch (Exception) { 105 | // fall through 106 | } 107 | 108 | return null; 109 | } 110 | 111 | /* 112 | * Find a free socket port. 113 | */ 114 | public static int FindFreePort(int fallback) 115 | { 116 | TcpListener l = null; 117 | try { 118 | l = new TcpListener(IPAddress.Loopback, 0); 119 | l.Start(); 120 | return ((IPEndPoint)l.LocalEndpoint).Port; 121 | } 122 | catch (Exception) { 123 | // ignore 124 | } 125 | finally { 126 | l.Stop(); 127 | } 128 | return fallback; 129 | } 130 | 131 | public static string ExpandVariables(string format, dynamic variables, bool underscoredOnly = true) 132 | { 133 | if (variables == null) { 134 | variables = new { }; 135 | } 136 | Type type = variables.GetType(); 137 | return VARIABLE.Replace(format, match => { 138 | string name = match.Groups[1].Value; 139 | if (!underscoredOnly || name.StartsWith("_")) { 140 | 141 | PropertyInfo property = type.GetProperty(name); 142 | if (property != null) { 143 | object value = property.GetValue(variables, null); 144 | return value.ToString(); 145 | } 146 | return '{' + name + ": not found}"; 147 | } 148 | return match.Groups[0].Value; 149 | }); 150 | } 151 | 152 | /** 153 | * converts the given absPath into a path that is relative to the given dirPath. 154 | */ 155 | public static string MakeRelativePath(string dirPath, string absPath) 156 | { 157 | if (!dirPath.EndsWith("/")) { 158 | dirPath += "/"; 159 | } 160 | if (absPath.StartsWith(dirPath)) { 161 | return absPath.Replace(dirPath, ""); 162 | } 163 | return absPath; 164 | /* 165 | Uri uri1 = new Uri(path); 166 | Uri uri2 = new Uri(dir_path); 167 | return uri2.MakeRelativeUri(uri1).ToString(); 168 | */ 169 | } 170 | } 171 | 172 | class CustomLogger : ICustomLogger 173 | { 174 | public void LogError(string message, Exception ex) 175 | { 176 | } 177 | 178 | public void LogAndShowException(string message, Exception ex) 179 | { 180 | } 181 | 182 | public void LogMessage(string format, params object[] args) 183 | { 184 | } 185 | 186 | public string GetNewDebuggerLogFilename() 187 | { 188 | return null; 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/csharp/mono-debug.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net472 6 | false 7 | ..\..\bin\$(Configuration) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/typescript/extension.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | *--------------------------------------------------------*/ 4 | 5 | 'use strict'; 6 | 7 | import * as vscode from 'vscode'; 8 | import * as nls from 'vscode-nls'; 9 | import { DebugProtocol } from 'vscode-debugprotocol'; 10 | 11 | const localize = nls.config()(); 12 | 13 | const configuration = vscode.workspace.getConfiguration('mono-debug'); 14 | 15 | export function activate(context: vscode.ExtensionContext) { 16 | context.subscriptions.push(vscode.commands.registerCommand('extension.mono-debug.configureExceptions', () => configureExceptions())); 17 | context.subscriptions.push(vscode.commands.registerCommand('extension.mono-debug.startSession', config => startSession(config))); 18 | } 19 | 20 | export function deactivate() { 21 | // do nothing. 22 | } 23 | 24 | //----- configureExceptions --------------------------------------------------------------------------------------------------- 25 | 26 | // we store the exception configuration in the workspace or user settings as 27 | type ExceptionConfigurations = { [exception: string]: DebugProtocol.ExceptionBreakMode; }; 28 | 29 | // if the user has not configured anything, we populate the exception configurationwith these defaults 30 | const DEFAULT_EXCEPTIONS : ExceptionConfigurations = { 31 | "System.Exception": "never", 32 | "System.SystemException": "never", 33 | "System.ArithmeticException": "never", 34 | "System.ArrayTypeMismatchException": "never", 35 | "System.DivideByZeroException": "never", 36 | "System.IndexOutOfRangeException": "never", 37 | "System.InvalidCastException": "never", 38 | "System.NullReferenceException": "never", 39 | "System.OutOfMemoryException": "never", 40 | "System.OverflowException": "never", 41 | "System.StackOverflowException": "never", 42 | "System.TypeInitializationException": "never" 43 | }; 44 | 45 | class BreakOptionItem implements vscode.QuickPickItem { 46 | label!: string; 47 | description!: string; 48 | breakMode!: DebugProtocol.ExceptionBreakMode; 49 | } 50 | 51 | // the possible exception options converted into QuickPickItem 52 | const OPTIONS = [ 'never', 'always', 'unhandled' ].map((bm: string) : BreakOptionItem => { 53 | const breakMode = bm; 54 | return { 55 | label: translate(breakMode), 56 | description: '', 57 | breakMode: breakMode 58 | } 59 | }); 60 | 61 | class ExceptionItem implements vscode.QuickPickItem { 62 | label!: string; 63 | description!: string; 64 | model!: DebugProtocol.ExceptionOptions; 65 | } 66 | 67 | function translate(mode: DebugProtocol.ExceptionBreakMode): string { 68 | switch (mode) { 69 | case 'never': 70 | return localize('breakmode.never', "Never break"); 71 | case 'always': 72 | return localize('breakmode.always', "Always break"); 73 | case 'unhandled': 74 | return localize('breakmode.unhandled', "Break when unhandled"); 75 | default: 76 | return mode; 77 | } 78 | } 79 | 80 | function getModel() : ExceptionConfigurations { 81 | 82 | let model = DEFAULT_EXCEPTIONS; 83 | if (configuration) { 84 | const exceptionOptions = configuration.get('exceptionOptions'); 85 | if (exceptionOptions) { 86 | model = exceptionOptions; 87 | } 88 | } 89 | return model; 90 | } 91 | 92 | function configureExceptions() : void { 93 | 94 | const options: vscode.QuickPickOptions = { 95 | placeHolder: localize('select.exception', "First Select Exception"), 96 | matchOnDescription: true, 97 | matchOnDetail: true 98 | }; 99 | 100 | const exceptionItems: vscode.QuickPickItem[] = []; 101 | const model = getModel(); 102 | for (const exception in model) { 103 | exceptionItems.push({ 104 | label: exception, 105 | description: model[exception] !== 'never' ? `⚡ ${translate(model[exception])}` : '' 106 | }); 107 | } 108 | 109 | vscode.window.showQuickPick(exceptionItems, options).then(exceptionItem => { 110 | 111 | if (exceptionItem) { 112 | 113 | const options: vscode.QuickPickOptions = { 114 | placeHolder: localize('select.break.option', "Then Select Break Option"), 115 | matchOnDescription: true, 116 | matchOnDetail: true 117 | }; 118 | 119 | vscode.window.showQuickPick(OPTIONS, options).then(item => { 120 | if (item) { 121 | model[exceptionItem.label] = item.breakMode; 122 | if (configuration) { 123 | configuration.update('exceptionOptions', model); 124 | } 125 | setExceptionBreakpoints(model); 126 | } 127 | }); 128 | } 129 | }); 130 | } 131 | 132 | function setExceptionBreakpoints(model: ExceptionConfigurations) : void { 133 | 134 | const args: DebugProtocol.SetExceptionBreakpointsArguments = { 135 | filters: [], 136 | exceptionOptions: convertToExceptionOptions(model) 137 | } 138 | if (vscode.debug.activeDebugSession) 139 | vscode.debug.activeDebugSession.customRequest('setExceptionBreakpoints', args); 140 | } 141 | 142 | function convertToExceptionOptions(model: ExceptionConfigurations) : DebugProtocol.ExceptionOptions[] { 143 | 144 | const exceptionItems: DebugProtocol.ExceptionOptions[] = []; 145 | for (const exception in model) { 146 | exceptionItems.push({ 147 | path: [ { names: [ exception ] } ], 148 | breakMode: model[exception] 149 | }); 150 | } 151 | return exceptionItems; 152 | } 153 | 154 | //----- configureExceptions --------------------------------------------------------------------------------------------------- 155 | 156 | /** 157 | * The result type of the startSession command. 158 | */ 159 | class StartSessionResult { 160 | status!: 'ok' | 'initialConfiguration' | 'saveConfiguration'; 161 | content?: string; // launch.json content for 'save' 162 | } 163 | 164 | function startSession(config: any) : StartSessionResult { 165 | 166 | if (config && !config.__exceptionOptions) { 167 | config.__exceptionOptions = convertToExceptionOptions(getModel()); 168 | } 169 | 170 | vscode.commands.executeCommand('vscode.startDebug', config); 171 | 172 | return { 173 | status: 'ok' 174 | }; 175 | } 176 | -------------------------------------------------------------------------------- /src/typescript/tests/adapter.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | "use strict"; 7 | 8 | import assert = require('assert'); 9 | import * as Path from 'path'; 10 | import {DebugClient} from 'vscode-debugadapter-testsupport'; 11 | import {DebugProtocol} from 'vscode-debugprotocol'; 12 | 13 | suite('Node Debug Adapter', () => { 14 | 15 | const PROJECT_ROOT = Path.join(__dirname, '../../'); 16 | const DATA_ROOT = Path.join(PROJECT_ROOT, 'testdata/'); 17 | 18 | const DEBUG_ADAPTER = Path.join(PROJECT_ROOT, 'bin/Release/mono-debug.exe'); 19 | 20 | 21 | let dc: DebugClient; 22 | 23 | setup( () => { 24 | dc = new DebugClient('mono', DEBUG_ADAPTER, 'mono'); 25 | return dc.start(); 26 | }); 27 | 28 | teardown( () => dc.stop() ); 29 | 30 | 31 | suite('basic', () => { 32 | 33 | test('unknown request should produce error', done => { 34 | dc.send('illegal_request').then(() => { 35 | done(new Error("does not report error on unknown request")); 36 | }).catch(() => { 37 | done(); 38 | }); 39 | }); 40 | }); 41 | 42 | suite('initialize', () => { 43 | 44 | test('should produce error for invalid \'pathFormat\'', done => { 45 | dc.initializeRequest({ 46 | adapterID: 'mono', 47 | linesStartAt1: true, 48 | columnsStartAt1: true, 49 | pathFormat: 'url' 50 | }).then(response => { 51 | done(new Error("does not report error on invalid 'pathFormat' attribute")); 52 | }).catch(err => { 53 | // error expected 54 | done(); 55 | }); 56 | }); 57 | }); 58 | 59 | suite('launch', () => { 60 | 61 | test('should run program to the end', () => { 62 | 63 | const PROGRAM = Path.join(DATA_ROOT, 'simple/bin/Debug/simple.exe'); 64 | 65 | return Promise.all([ 66 | dc.configurationSequence(), 67 | dc.launch({ program: PROGRAM }), 68 | dc.waitForEvent('terminated') 69 | ]); 70 | }); 71 | 72 | test('should run program to the end (and not stop on Debugger.Break())', () => { 73 | 74 | const PROGRAM = Path.join(DATA_ROOT, 'simple_break/bin/Debug/simple_break.exe'); 75 | 76 | return Promise.all([ 77 | dc.configurationSequence(), 78 | dc.launch({ program: PROGRAM, noDebug: true }), 79 | dc.waitForEvent('terminated') 80 | ]); 81 | }); 82 | 83 | test('should stop on debugger statement', () => { 84 | 85 | const PROGRAM = Path.join(DATA_ROOT, 'simple_break/bin/Debug/simple_break.exe'); 86 | const DEBUGGER_LINE = 11; 87 | 88 | return Promise.all([ 89 | dc.configurationSequence(), 90 | dc.launch({ program: PROGRAM }), 91 | dc.assertStoppedLocation('step', { line: DEBUGGER_LINE }) 92 | ]); 93 | }); 94 | }); 95 | 96 | suite('setBreakpoints', () => { 97 | 98 | const PROGRAM = Path.join(DATA_ROOT, 'simple/bin/Debug/simple.exe'); 99 | const SOURCE = Path.join(DATA_ROOT, 'simple/Program.cs'); 100 | const BREAKPOINT_LINE = 13; 101 | 102 | test('should stop on a breakpoint', () => { 103 | return dc.hitBreakpoint({ program: PROGRAM }, { path: SOURCE, line: BREAKPOINT_LINE } ); 104 | }); 105 | }); 106 | 107 | suite('output events', () => { 108 | 109 | const PROGRAM = Path.join(DATA_ROOT, 'output/bin/Debug/output.exe'); 110 | 111 | test('stdout and stderr events should be complete and in correct order', () => { 112 | return Promise.all([ 113 | dc.configurationSequence(), 114 | dc.launch({ program: PROGRAM }), 115 | dc.assertOutput('stdout', "Hello stdout 0\nHello stdout 1\nHello stdout 2\n"), 116 | dc.assertOutput('stderr', "Hello stderr 0\nHello stderr 1\nHello stderr 2\n") 117 | ]); 118 | }); 119 | }); 120 | 121 | suite('FSharp Tests', () => { 122 | 123 | const PROGRAM = Path.join(DATA_ROOT, 'fsharp/bin/Debug/fsharp.exe'); 124 | const SOURCE = Path.join(DATA_ROOT, 'fsharp/Program.fs'); 125 | const BREAKPOINT_LINE = 8; 126 | 127 | test('should stop on a breakpoint in an fsharp program', () => { 128 | return dc.hitBreakpoint({ program: PROGRAM }, { path: SOURCE, line: BREAKPOINT_LINE } ); 129 | }); 130 | }); 131 | }); -------------------------------------------------------------------------------- /src/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "lib": ["ES2019"], 6 | "outDir": "../../out", 7 | "sourceMap": true, 8 | "strict": true 9 | } 10 | } -------------------------------------------------------------------------------- /testdata/fsharp/Program.fs: -------------------------------------------------------------------------------- 1 | open System 2 | open System.Diagnostics 3 | open System.Threading 4 | 5 | [] 6 | let main _ = 7 | Thread.Sleep 300 8 | printfn "Hello World" 9 | printfn "The End." 10 | 0 -------------------------------------------------------------------------------- /testdata/fsharp/fsharp.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net4.7.2 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /testdata/output/Output.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace Simple 5 | { 6 | class Simple { 7 | public static void Main(string[] args) { 8 | for (int i = 0; i < 3; i++) { 9 | Console.Error.WriteLine("Hello stderr " + i); 10 | } 11 | for (int i = 0; i < 3; i++) { 12 | Console.WriteLine("Hello stdout " + i); 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /testdata/output/output.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net4.7.2 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /testdata/simple/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | 5 | namespace Tests 6 | { 7 | class Simple { 8 | 9 | public static void Main(string[] args) { 10 | 11 | Thread.Sleep(100); // wait a bit so that debugger gets enough time to set breakpoints 12 | 13 | Console.WriteLine("Hello World!"); 14 | 15 | Console.WriteLine("The End."); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /testdata/simple/simple.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net4.7.2 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /testdata/simple_break/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace Tests 5 | { 6 | class Simple { 7 | 8 | public static void Main(string[] args) { 9 | 10 | if (System.Diagnostics.Debugger.IsAttached) { 11 | Debugger.Break(); 12 | } 13 | 14 | Console.WriteLine("Hello World!"); 15 | 16 | Console.WriteLine("The End."); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /testdata/simple_break/simple_break.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net4.7.2 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------