├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── .vscode ├── extensions.json └── launch.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets └── icons │ ├── checkpoint.svg │ └── run-to-event.svg ├── babel.config.json ├── dap_requests.js ├── docs ├── BUGS.MD ├── CHANGELOG.md ├── USAGE.md ├── add-hardware-watchpoint.gif ├── add-member-to-watch.gif ├── array-vars.gif ├── gdb_variable_length_struct.png ├── how_to_create_logs.gif ├── index.png ├── index_large.png ├── launchconfig.gif ├── logpoints.gif ├── toggle_hex.gif ├── watch_variable_frame_wildcard_specifier.gif ├── watch_variable_hex_format.png └── watch_variable_subscript.png ├── extension.js ├── jsconfig.json ├── modules ├── .gdb_rrinit ├── activateDebuggerExtension.js ├── buildMode.js ├── c++stdlib.ignore ├── commandsRegistry.js ├── constants.js ├── dap │ ├── base-process-handle.js │ ├── dap-base.js │ ├── dap-utils.js │ ├── gdb.js │ └── mdb.js ├── prettyprinter.js ├── providers │ ├── initializer.js │ ├── midas-gdb.js │ ├── midas-native.js │ └── midas-rr.js ├── python │ ├── apt_manager.py │ ├── config.py │ ├── dap-wrapper │ │ ├── dap.py │ │ ├── logger.py │ │ ├── rrinit │ │ ├── rrinit - full │ │ └── variables_reference.py │ ├── dnf_manager.py │ ├── midas_report.py │ ├── midas_utils.py │ ├── rr_commands.py │ └── rust.py ├── spawn.js ├── terminalInterface.js ├── toolchain.js ├── ui │ └── checkpoints │ │ ├── checkpoints.js │ │ ├── main.css │ │ ├── main.js │ │ ├── reset.css │ │ ├── ui_protocol.js │ │ └── vscode.css └── utils │ ├── installerProgress.js │ ├── kernelsettings.js │ ├── netutils.js │ ├── releaseNotes.js │ ├── rrutils.js │ ├── sysutils.js │ └── utils.js ├── package.json ├── pyproject.toml ├── release_notes ├── 0.19.0.md ├── 0.19.10.md ├── 0.19.11.md ├── 0.19.15.md ├── 0.19.17.md ├── 0.19.18.md ├── 0.19.2.md ├── 0.19.7.md ├── 0.19.8.md ├── 0.20.3.md ├── 0.20.4.md ├── 0.22.0.md ├── 0.22.2.md ├── 0.23.0.md ├── 0.23.1.md ├── 0.23.3.md └── template.md ├── test ├── cppworkspace │ ├── CMakeLists.txt │ ├── PrettyPrinters.py │ ├── attach │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── src │ │ │ └── main.cpp │ │ └── testspawn.sh │ ├── build.sh │ ├── include │ │ ├── date.hpp │ │ ├── number.hpp │ │ ├── string.hpp │ │ ├── todo.hpp │ │ ├── types.hpp │ │ └── vector.hpp │ ├── multi-process │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── src │ │ │ ├── ls.cpp │ │ │ └── main.cpp │ ├── printers │ │ ├── FooPrinter.js │ │ └── StdOptionalPrinter.js │ ├── rr │ │ ├── CMakeLists.txt │ │ ├── buffer_overflow.cpp │ │ └── segfault.cpp │ ├── simple_input │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── src │ │ │ ├── date.cpp │ │ │ ├── main.cpp │ │ │ └── todo.cpp │ ├── test │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── src │ │ │ ├── date.cpp │ │ │ ├── main.cpp │ │ │ ├── testcase_namespaces │ │ │ │ ├── baseclasses.cpp │ │ │ │ ├── baseclasses.hpp │ │ │ │ ├── derive.cpp │ │ │ │ ├── derive.hpp │ │ │ │ ├── enum.cpp │ │ │ │ ├── enum.hpp │ │ │ │ ├── exceptions.cpp │ │ │ │ ├── exceptions.hpp │ │ │ │ ├── longstack.cpp │ │ │ │ ├── longstack.hpp │ │ │ │ ├── pp.cpp │ │ │ │ ├── pp.hpp │ │ │ │ ├── statics.cpp │ │ │ │ ├── statics.hpp │ │ │ │ ├── structrequests.cpp │ │ │ │ ├── structrequests.hpp │ │ │ │ ├── test_freefloating_watch.cpp │ │ │ │ ├── test_freefloating_watch.hpp │ │ │ │ ├── test_ptrs.cpp │ │ │ │ └── test_ptrs.hpp │ │ │ └── todo.cpp │ │ └── testspawn.sh │ └── thread │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── src │ │ ├── date.cpp │ │ ├── main.cpp │ │ └── todo.cpp ├── runTest.js └── suite │ ├── buildcppworkspace.js │ ├── debugServer.js │ ├── extension.test.js │ ├── index.js │ └── midasDebugAdapter.test.js ├── tool-dependencies.json ├── vsc-extension-quickstart.md └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true, 7 | "mocha": true, 8 | "es2021": true 9 | }, 10 | "parser": "@babel/eslint-parser", 11 | "parserOptions": { 12 | "ecmaVersion": 12, 13 | "ecmaFeatures": { 14 | "jsx": true 15 | }, 16 | "sourceType": "module" 17 | }, 18 | "rules": { 19 | "no-const-assign": "warn", 20 | "no-this-before-super": "warn", 21 | "no-undef": "warn", 22 | "no-unreachable": "error", 23 | "no-unused-vars": "warn", 24 | "constructor-super": "warn", 25 | "valid-typeof": "warn", 26 | "indent": ["error", 2, { "SwitchCase": 1 }], 27 | "max-len": ["warn", 150] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode-test/ 3 | *.vsix 4 | package-lock.json 5 | .cache 6 | test/cppworkspace/test/.vscode 7 | test/cppworkspace/test/build 8 | test/cppworkspace/thread/.vscode/ 9 | test/cppworkspace/thread/build 10 | test/cppworkspace/simple_input/.vscode 11 | test/cppworkspace/simple_input/build 12 | test/cppworkspace/attach/build 13 | test/cppworkspace/attach/.vscode 14 | .vscode/settings.json 15 | __pycache__ 16 | 17 | **/build/** 18 | 19 | **/.vscode/launch.json 20 | test/cppworkspace/rr/.vscode/ 21 | rr-* 22 | test/cppworkspace/bin/* 23 | *.log 24 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "printWidth": 120, 4 | "singleQuote": false, 5 | "bracketSpacing": true 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "NathanRidley.autotrim", "ms-python.python"] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Test extension in cppworkspace", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}", 14 | "${workspaceFolder}/test/cppworkspace/" 15 | ] 16 | }, 17 | { 18 | "name": "Run Extension -- threaded project", 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "args": [ 22 | "--extensionDevelopmentPath=${workspaceFolder}", 23 | "${workspaceFolder}/test/cppworkspace/thread" 24 | ] 25 | }, 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | test/** 4 | .gitignore 5 | .yarnrc 6 | vsc-extension-quickstart.md 7 | **/jsconfig.json 8 | **/*.map 9 | **/.eslintrc.json -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # MIDAS - Debug Adapter Extension for VSCode 2 | 3 | ## Release 0.24.N 4 | 5 | ### Features 6 | 7 | - Changed `traceWorkspace` configuration property. It now specifies a "trace workspace" directory, i.e. a directory that contains trace directories (so an overload for `$_RR_TRACE_DIR`) 8 | - Added `ignoreStandardLibrary` to launch and attach configurations. It will attempt at ignoring most c++ standard library files while stepping. It's a best approximation approach. Defaults to `true`. 9 | - Extended support for the debugger backend "midas" which henceforth will be known as "midas-native" 10 | - Added context menu support for call stack, to resume/pause all threads 11 | - Make use of onActive item events so that the debugger backends can know what threads the user is inspecting in the UI 12 | - Added continueAll 13 | - Added pauseAll 14 | - continueAll/pauseAll is used for gdb's allStopMode, or MDB. 15 | - Deprecated allStopMode in launch.json; use noSingleThreadControl instead. It's a better name and will 16 | be also what this feature eventually be called by MDB. 17 | - ${workspaceFolder} and built-ins in launch.json, should now work for all sorts of config field values 18 | - Added new pretty printer features. 19 | - Support for using the canonical experimental debugger `mdb` 20 | - Added prettier to dev dependencies. use `npm run format` to format 21 | - Added `go` identifier to `package.json` 22 | 23 | ### Fixes 24 | 25 | - Removed old Midas version that relied on GDB/MI and gdb-js. The exclusively supported variant is midas DAP implementation. 26 | - Make validation of config happen after substition of variables like ${workspaceFolder} so that _any_ configuration items can use the short-hands/built ins 27 | - Refactors and cleaning up the inherent messiness of supporting what essentially is 3 backends; midas-native (mdb, future hopefully mdb+rr), midas-gdb (gdb) and midas-rr (gdb+rr). 28 | - Added program args as a configuration item for launched program when `use-dap` is true, which seems to have been missing 29 | - Build RR related bugs fixed 30 | - Pretty printer related issues fixed 31 | - Fixes to UI to behave more consistent. 32 | - Refactor of tool management, so that it can be extended to additional software like GDB as well (in the future) in-house debugger, caused Midas to ask every time for updates of tools even when having updated to last version. Fixed. 33 | - Fixed hex-formatting bug for register values with the sign bit set. 34 | - Greatly improved stability and reliability of disassembly outputs 35 | - Fixed bug in InstructionBreakpoint requests 36 | - Fixed bug when register contents did not display 37 | - Removed selected thread from UI. It behaves out of the box in a way I'm not wanting to deal with right now. 38 | - Fixed DAP interpreter bug where error in the DAP did not propagate (and therefore wasn't displayed) to the frontend. 39 | - Always make sure `non-stop` is set to off for rr sessions. 40 | 41 | ## Release 0.22.0 42 | 43 | ### Fixes 44 | 45 | - Fixed bug that made `Add to watch` disappear for member objects in the `Variables` list. You can now right click things 46 | in the list and add sub objects to `Watch` again. 47 | 48 | ### Features 49 | 50 | - Added `(Return Value)` variable to `Locals`, if the last command that executed was `stepOut` and it succeeded completely. 51 | This way you can inspect return values from a function. 52 | - Added `LogPoint` breakpoints that logs to the debug console and continues. 53 | 54 | ## Release 0.20.4 55 | 56 | ### Fixes 57 | 58 | - Fix the Get RR bug where it did not fire if the Midas config file gets into an invalid state, possibly by aborting a build etc. 59 | 60 | ## Release 0.20.3 61 | 62 | ### Fixes 63 | 64 | - Added connect timeout & remote timeout to 10000 for all attach 65 | 66 | ## Release 0.19.18 67 | 68 | ### Fixes 69 | 70 | - Make checkpoints in UI clickable to go-to source where they're set 71 | 72 | ### Features 73 | 74 | - Added ability to name checkpoints 75 | 76 | ## Release 0.19.17 77 | 78 | ### Fixes 79 | 80 | - Fixes ##188 where Midas couldn't be started because RR was recording. 81 | 82 | ### Features 83 | 84 | - Added `rrOptions` to `launch.json` for `midas-rr` debug sessions, which are command line options passed to RR. 85 | 86 | ## Release 0.19.15 87 | 88 | ### Fixes 89 | 90 | - Fixes `when`, and other RR commands "crashing" Midas. 91 | 92 | ### Features 93 | 94 | - Added run to event 95 | - Midas now uses the output from `rr gdbinit` directly to increase stability so that if RR changes it, we don't break. 96 | 97 | ## Release 0.19.11 98 | 99 | ### Fixes 100 | 101 | - Pretty printer-usage bug fix 102 | 103 | ## Release 0.19.10 104 | 105 | ### Fixes 106 | 107 | - Fixed bug where frame arguments weren't displayed properly 108 | 109 | ## Release 0.19.8 110 | 111 | ### Fixes 112 | 113 | - Fixed a bug where typedefs weren't recognized as the base type and as such wouldn't use pretty printers 114 | 115 | ## Release 0.19.7 116 | 117 | ### Fixes 118 | 119 | - Fixed a bug where RR did not get spawned correctly, not being able to replay fork-without-execs. 120 | 121 | ## Release 0.19.2 122 | 123 | ### Features 124 | 125 | - Added Checkpoint UI for RR sessions. The snapshot (camera icon) sets a checkpoint. And there will be a `Checkpoint` panel in the side bar during debugging from where the user can (re)start a checkpoint. 126 | 127 | ## Release 0.19.0 128 | 129 | ### Features 130 | 131 | - New DAP interpreter. This have introduced breaking changes to the configuration of Midas. Unless you're new, you should read [the readme](https://github.com/farre/midas). This change is a substantial overhaul and is supposed to make Midas more stable and performant. 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Andreas Farre, Simon Farre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/icons/checkpoint.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /assets/icons/run-to-event.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | N 5 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "shippedProposals": true 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /dap_requests.js: -------------------------------------------------------------------------------- 1 | let seq = 1; 2 | 3 | function serialize_request(request, args = {}) { 4 | const json = { 5 | seq, 6 | type: "request", 7 | command: request, 8 | arguments: args, 9 | }; 10 | seq += 1; 11 | const data = JSON.stringify(json); 12 | return data; 13 | } 14 | 15 | const StackTraceArguments = { 16 | threadId: 1, 17 | startFrame: null, 18 | levels: null, 19 | format: null, 20 | }; 21 | 22 | console.log(serialize_request("threads")); 23 | console.log(serialize_request("stacktrace", StackTraceArguments)); 24 | -------------------------------------------------------------------------------- /docs/BUGS.MD: -------------------------------------------------------------------------------- 1 | # Reported & Untreated bugs 2 | 3 | When watching a variable, you can only watch 1 instance of the same variable. Thus, if you watch a range `foo[2:4]` 4 | you can't also watch `foo[4:9]`, only one of them will be shown. This will be fixed in future releases. 5 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "midas" extension will be documented in this file. Changelog begins with version 0.1.1 additions prior to this unfortunately not registered. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [0.14.0] 8 | 9 | Added toggle-hex formatting functionality 10 | Added readMemory request - users can now use hex dump view of variables 11 | 12 | ## [0.12.0] 13 | 14 | Stable release of 0.11.1/5 15 | 16 | Fixed instruction-stepping during disassembly not working 17 | Fixed disassembly to work more reliably 18 | Fixed due to new implementation of a DAP-request 19 | Fixed RR reverse-continue bug 20 | Make step-back icon only display during RR session 21 | 22 | ## [0.11.1] 23 | 24 | Toolchain versioning management added 25 | Added auto complete for debug console for gdb commands 26 | Re-factored logging yet again 27 | 28 | ## [0.11.0] 29 | 30 | Added remote target support. Configurable via `remoteTargetConfig` on the debug session config in launch.json 31 | 32 | ## [0.5.5] - 2022-12-12 33 | 34 | - Added ability to install RR, or build it from source and install it locally in the extension folder. Resolving dependencies are performed by dnf or apt and requries sudo. 35 | 36 | ## [0.4.7] - 2022-07-06 37 | 38 | - Added ability to get range of pretty printed child values for watch variables 39 | 40 | ## [0.4.0] - 2022-05-23 41 | 42 | - Hooked up Midas to work with VSCode Disassembly view 43 | - Added "Create issue log" command 44 | - Added checkpoints UI for setting and returning to a checkpoint 45 | - Added scope-locking for watch variables, \* binds a watch variable 46 | to the first found variable with that name in the call stack. Subscript operators 47 | and hex formatting works for these as well. 48 | 49 | ## [0.2.0] 50 | 51 | - Added checkpoint UI 52 | 53 | ## [0.1.2] - 2022-04-25 54 | 55 | - Added the repl command "cancel" - sends an interrupt to GDB in case it is doing something that takes too long. 56 | - Added subscript to watch-variables 57 | - Added USAGE.md where functionality that not necessarily is all that intuitive is described. 58 | - Added hex formatting of WATCH variables. Formatting a watch expression is done by adding ",x" at the end. 59 | - Changed README to reflect new changes. 60 | - Added "externalConsole" as a configuration option. Currently working, although not in most preferrable way. 61 | - Added "externalConsole" config for rr debug sessions 62 | - Added TerminalInterface to wrap behavior of externally spawned consoles and VSCode's internal console 63 | - Added utility functions to rrutils.js, utils.js and netutils.js 64 | - Added different debugger types, for normal/replay (gdb / gdb+rr) in config file 65 | - Add watch variable functionality 66 | - Added ability to set watchpoints from UI 67 | - Added invalidate execution context command 68 | - Added VSCode requests to python: stackTraceRequest, variablesRequest, scopesRequest 69 | - Execute gdb commands from debug console 70 | - Build state when inside frame, to speed up stepping and continue commands 71 | - Initial release 72 | -------------------------------------------------------------------------------- /docs/USAGE.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | - [Setting watchpoints](#setting-watchpoints) 4 | - [Watch variables](#watch-variables) 5 | - [Find first operator](#find-first) 6 | - [Hexadecimal watch variable formatting](#hexadecimal-format-of-watch-variables) 7 | - [Watch variable subscript](#watch-variable-subscript) 8 | 9 | You can use GDB/rr from the debug console in VSCode as normal. No prefix commands with -exec etc, just type whatever commands you want. Notice however, that some commands might alter GDB state which might _not_ be seen by Midas, so if you ever come across a command that breaks Midas or make Midas behave strange, please be so kind and report it so that edge cases can be handled. 10 | 11 | ## Setting watchpoints 12 | 13 | Right click the variable in the variable window and pick the menu option for what watch point you want to set. The watchpoints are always set by address (location). 14 | 15 | The reasoning behind this, is that the re-evaluation of watch points when new scopes are entered will slow them down. Doing this defeats the purpose of fast hardware watchpoints. 16 | 17 | ## Watch variables 18 | 19 | Since the VSCode UI for Watch Variables isn't extendable as of now, the design decision has been to add "keywords" that change the output of the watch variable contents. These are described here. In the future 20 | a context menu will be added, for a more clearer use. Example shown below for how it works. 21 | 22 | ## Hexadecimal format of watch variables 23 | 24 | Unfortunately VSCode provides no good way to format individual values (due to not being fully DAP-compliant), so for now you can only toggle hex formatting for all variables on display. Right click in the "Variables" pane during 25 | a debugging session and click "Toggle hex formatting": 26 | 27 | ![Toggle hex](./toggle_hex.gif) 28 | 29 | In the "watch" window you can toggle hexadecimal formatting for values by just placing `,x` behind the variable name, so for foo: `foo,x`. 30 | 31 | ## Watch variables 32 | 33 | To add a contiguous set of variables to to watch (for instance they're behind some pointer or array), the syntax is the same as in GDB's command line: `*foo@10` (dereference foo and take 10 elements). 34 | ![Here's an example](./array-vars.gif) 35 | 36 | ## Debug Console - Midas Specific commands 37 | 38 | - cancel - Writing `cancel` in the console sends an interrupt to GDB causing it to cancel any operation it is doing (similar to the `ctrl+c` when using GDB on the command line). If GDB is stalled or doing something that is taking too long (like running a python command or something of that nature) this will cancel that operation. 39 | -------------------------------------------------------------------------------- /docs/add-hardware-watchpoint.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farre/midas/275011faba97906aef76151002b1ca7757a271ad/docs/add-hardware-watchpoint.gif -------------------------------------------------------------------------------- /docs/add-member-to-watch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farre/midas/275011faba97906aef76151002b1ca7757a271ad/docs/add-member-to-watch.gif -------------------------------------------------------------------------------- /docs/array-vars.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farre/midas/275011faba97906aef76151002b1ca7757a271ad/docs/array-vars.gif -------------------------------------------------------------------------------- /docs/gdb_variable_length_struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farre/midas/275011faba97906aef76151002b1ca7757a271ad/docs/gdb_variable_length_struct.png -------------------------------------------------------------------------------- /docs/how_to_create_logs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farre/midas/275011faba97906aef76151002b1ca7757a271ad/docs/how_to_create_logs.gif -------------------------------------------------------------------------------- /docs/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farre/midas/275011faba97906aef76151002b1ca7757a271ad/docs/index.png -------------------------------------------------------------------------------- /docs/index_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farre/midas/275011faba97906aef76151002b1ca7757a271ad/docs/index_large.png -------------------------------------------------------------------------------- /docs/launchconfig.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farre/midas/275011faba97906aef76151002b1ca7757a271ad/docs/launchconfig.gif -------------------------------------------------------------------------------- /docs/logpoints.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farre/midas/275011faba97906aef76151002b1ca7757a271ad/docs/logpoints.gif -------------------------------------------------------------------------------- /docs/toggle_hex.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farre/midas/275011faba97906aef76151002b1ca7757a271ad/docs/toggle_hex.gif -------------------------------------------------------------------------------- /docs/watch_variable_frame_wildcard_specifier.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farre/midas/275011faba97906aef76151002b1ca7757a271ad/docs/watch_variable_frame_wildcard_specifier.gif -------------------------------------------------------------------------------- /docs/watch_variable_hex_format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farre/midas/275011faba97906aef76151002b1ca7757a271ad/docs/watch_variable_hex_format.png -------------------------------------------------------------------------------- /docs/watch_variable_subscript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farre/midas/275011faba97906aef76151002b1ca7757a271ad/docs/watch_variable_subscript.png -------------------------------------------------------------------------------- /extension.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | const vscode = require("vscode"); 3 | const { activateExtension, deactivateExtension } = require("./modules/activateDebuggerExtension"); 4 | // this method is called when your extension is activated 5 | // your extension is activated the very first time the command is executed 6 | 7 | /** 8 | * @param {vscode.ExtensionContext} context 9 | */ 10 | async function activate(context) { 11 | return activateExtension(context); 12 | } 13 | 14 | // this method is called when your extension is deactivated 15 | function deactivate() {} 16 | 17 | module.exports = { 18 | activate, 19 | deactivate, 20 | }; 21 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2021", 5 | "checkJs": true /* Typecheck .js files. */, 6 | "lib": ["ES2021"] 7 | }, 8 | "exclude": ["node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /modules/.gdb_rrinit: -------------------------------------------------------------------------------- 1 | # define restart according to how rr does it. 2 | define restart 3 | run c$arg0 4 | end -------------------------------------------------------------------------------- /modules/buildMode.js: -------------------------------------------------------------------------------- 1 | const { DebugLogging } = require("./constants"); 2 | 3 | /** 4 | * @param { string } setting 5 | * @returns { { trace: boolean, pythonLogging: boolean } } 6 | */ 7 | function debugLogging(setting) { 8 | switch (setting.toLowerCase()) { 9 | case DebugLogging.Off: 10 | return { trace: false, pythonLogging: false }; 11 | case DebugLogging.Full: 12 | return { trace: true, pythonLogging: true }; 13 | } 14 | throw new Error(`Debug log settings set to incorrect value: ${setting}`); 15 | } 16 | 17 | module.exports = { 18 | debugLogging, 19 | }; 20 | -------------------------------------------------------------------------------- /modules/c++stdlib.ignore: -------------------------------------------------------------------------------- 1 | algorithm 2 | array 3 | cstdio 4 | expected 5 | fstream 6 | functional 7 | numeric 8 | optional 9 | ostream 10 | print 11 | queue 12 | set 13 | span 14 | sstream 15 | stack 16 | stacktrace 17 | string 18 | string_view 19 | system_error 20 | thread 21 | tuple 22 | unordered_map 23 | unordered_set 24 | utility 25 | variant 26 | vector 27 | bits/vector.tcc 28 | stl_map.h 29 | bits/unordered_map.h 30 | bits/unordered_set.h 31 | bits/allocator.h -------------------------------------------------------------------------------- /modules/constants.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Debug Adapter Protocol follow thisFormattingStyle, change the remaining in future commits. 4 | const CustomRequests = { 5 | ContinueAll: "continueAll", 6 | PauseAll: "pauseAll", 7 | ReverseFinish: "reverse-finish", 8 | RunToEvent: "run-to-event", 9 | ReloadMidasScripts: "hot-reload-scripts", 10 | SpawnConfig: "spawn-config", 11 | SetCheckpoint: "set-checkpoint", 12 | RestartCheckpoint: "restart-checkpoint", 13 | DeleteCheckpoint: "delete-checkpoint", 14 | ClearCheckpoints: "clear-checkpoints", 15 | }; 16 | 17 | // Custom requests that doesn't reach the backend, whether it is gdb or mdb 18 | // This is used for VSCode UI purposes, since there seems to be process isolation. 19 | // Someone thought it was a better idea to have it act like a browser instead of an editor. 20 | const CustomRequestsUI = { 21 | HasThread: "HasThreadId", 22 | OnSelectedThread: "OnSelectedThread", 23 | SetThreadStoppingBreakpoint: "NonProcessHaltingBreakpoint", 24 | }; 25 | 26 | const ProvidedAdapterTypes = { 27 | RR: "midas-rr", 28 | Gdb: "midas-gdb", 29 | Native: "midas-native", 30 | }; 31 | 32 | const Regexes = { 33 | MajorMinorPatch: /(\d+)\.(\d+)\.*((\d+))?/, 34 | WhiteSpace: /\s/, 35 | ForkedNoExec: /forked without exec/, 36 | }; 37 | 38 | const ContextKeys = { 39 | NoSingleThreadControl: "midas.noSingleThreadControl", 40 | Running: "midas.Running", 41 | DebugType: "midas.debugType", 42 | RRSession: "midas.rrSession", 43 | IsReplay: "midas.is-replay", 44 | NativeMode: "midas.native", 45 | }; 46 | 47 | function ContextKeyName(contextKey) { 48 | if (!contextKey.includes(".")) { 49 | throw new Error(`Object is not a context key!`); 50 | } 51 | const [, key] = contextKey.split("."); 52 | return key; 53 | } 54 | 55 | const DebugLogging = { 56 | Off: "off", 57 | Full: "full", 58 | }; 59 | 60 | module.exports = { 61 | CustomRequests, 62 | ProvidedAdapterTypes, 63 | Regexes, 64 | ContextKeys, 65 | DebugLogging, 66 | CustomRequestsUI, 67 | ContextKeyName, 68 | }; 69 | -------------------------------------------------------------------------------- /modules/dap/base-process-handle.js: -------------------------------------------------------------------------------- 1 | const { EventEmitter } = require("events"); 2 | const { serializeRequest } = require("./dap-utils"); 3 | const fs = require("fs"); 4 | const { spawn } = require("child_process"); 5 | 6 | /** 7 | * @import { Event, Response } from "./dap-base" 8 | * @typedef {import("@vscode/debugprotocol").DebugProtocol.Request } DAPRequest 9 | * @typedef {import("@vscode/debugprotocol").DebugProtocol.Response } DAPResponse 10 | */ 11 | 12 | // Represents the type that actually communicates with the debugger, e.g. via stdio or sockets 13 | // Some derived types may spawn a debugger process, some may just connect to a socket. It's up to the derived type. 14 | class DebuggerProcessBase { 15 | /** @type {EventEmitter} */ 16 | messages; 17 | 18 | /** @type { import("child_process").ChildProcess | null } */ 19 | #process; 20 | 21 | constructor(options) { 22 | this.options = options; 23 | this.#process = null; 24 | // @ts-ignore 25 | if (this.requestChannel === undefined) { 26 | throw new Error(`Derived type haven't provided 'comms' channel for which we send requests/recv responses over`); 27 | } 28 | 29 | this.messages = new EventEmitter(); 30 | } 31 | 32 | // Override by derived type. 33 | async initialize() { 34 | throw new Error("initialize must be overriden by derived type"); 35 | } 36 | 37 | get process() { 38 | return this.#process; 39 | } 40 | 41 | /** 42 | * Exec the debugger application at `path` with `args` 43 | * @param { string } path - path to the debugger (gdb, mdb. path to rr is handled elsewhere.) 44 | * @param { string[] } args - command line arguments for the debuggers 45 | */ 46 | spawnDebugger(path, args) { 47 | this.#process = spawn(path, args); 48 | } 49 | 50 | /** 51 | * @param {(response: Response) => void} cb 52 | */ 53 | connectResponse(cb) { 54 | this.messages.on("response", cb); 55 | } 56 | 57 | /** 58 | * @param {(response: Event) => void} cb 59 | */ 60 | connectEvents(cb) { 61 | this.messages.on("event", cb); 62 | } 63 | 64 | /** @throws { Error } */ 65 | sendRequest(req, args) { 66 | const output = serializeRequest(req.seq, req.command, args ?? req.arguments); 67 | this.requestChannel().write(output); 68 | } 69 | 70 | /** 71 | * @throws { Error } 72 | * @returns { string } 73 | */ 74 | path() { 75 | if (this.options.path == null) { 76 | throw new Error(`No path to debugger process provided`); 77 | } 78 | if (!fs.existsSync(this.options.path)) { 79 | throw new Error(`${this.options.path} doesn't exist`); 80 | } 81 | return this.options.path; 82 | } 83 | 84 | /** 85 | * @returns { import("./dap-utils").MidasCommunicationChannel } 86 | */ 87 | requestChannel() { 88 | throw new Error("Must be implemented by subclass"); 89 | } 90 | 91 | /** 92 | * Callee can `await` on .waitableSendRequest(...) for the response 93 | * @param { DAPRequest } req 94 | * @param {*} args 95 | * @returns { Promise } 96 | */ 97 | waitableSendRequest(req, args) { 98 | return new Promise((resolve, reject) => { 99 | this.messages.once(`${req.seq}`, (response) => { 100 | resolve(response); 101 | }); 102 | try { 103 | this.sendRequest(req, args); 104 | } catch (ex) { 105 | reject(ex); 106 | } 107 | }); 108 | } 109 | } 110 | 111 | module.exports = { 112 | DebuggerProcessBase 113 | }; 114 | -------------------------------------------------------------------------------- /modules/dap/dap-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import { EventEmitter } from "events" 3 | * @import { Event, Response } from "./dap-base" 4 | */ 5 | 6 | const net = require("net"); 7 | 8 | /** 9 | * @typedef { Event | Response } DAPMessage 10 | * @typedef { ( eventName: string | symbol, listener: (...args: any[]) => void, ) => EventEmitter } EventSubscriber 11 | * @typedef { ( buffer: string | Uint8Array, cb?: (err?: Error) => void) => boolean } WriteFn 12 | * @typedef { { recv : { on: EventSubscriber }, send: { write: WriteFn }}} DataChannel 13 | * @typedef {{ start: number, end: number, all_received: boolean }} PacketBufferMetaData 14 | */ 15 | 16 | function connect_socket(name, path, attempts, attempt_interval) { 17 | return new Promise((res, rej) => { 18 | const socket = new net.Socket(); 19 | socket.connect({ path: path }); 20 | 21 | socket.on("connect", () => { 22 | res(socket); 23 | }); 24 | 25 | socket.on("error", (error) => { 26 | socket.destroy(); 27 | 28 | if (attempts === 0) { 29 | rej(error); 30 | } else { 31 | setTimeout(() => { 32 | connect_socket(name, path, attempts - 1, attempt_interval + 50) 33 | .then(res) 34 | .catch(rej); 35 | }, attempt_interval); 36 | } 37 | }); 38 | }); 39 | } 40 | 41 | /** 42 | * Serialize request, preparing it to be sent over the wire to GDB 43 | * @param {number} seq 44 | * @param {string} request 45 | * @param {*} args 46 | * @returns {string} 47 | */ 48 | function serializeRequest(seq, request, args = {}) { 49 | const json = { 50 | seq, 51 | type: "request", 52 | command: request, 53 | arguments: args, 54 | }; 55 | const data = JSON.stringify(json); 56 | const length = data.length; 57 | const res = `Content-Length: ${length}\r\n\r\n${data}`; 58 | return res; 59 | } 60 | 61 | const MessageHeader = /Content-Length: (\d+)\s{4}/gm; 62 | 63 | /** 64 | * @param {string} contents 65 | * @returns { PacketBufferMetaData[] } 66 | */ 67 | function processBuffer(contents) { 68 | let m; 69 | const result = []; 70 | while ((m = MessageHeader.exec(contents)) !== null) { 71 | // This is necessary to avoid infinite loops with zero-width matches 72 | if (m.index === MessageHeader.lastIndex) { 73 | MessageHeader.lastIndex++; 74 | } 75 | // The result can be accessed through the `m`-variable. 76 | let contents_start = 0; 77 | m.forEach((match, groupIndex) => { 78 | if (groupIndex == 0) { 79 | contents_start = m.index + match.length; 80 | } 81 | 82 | if (groupIndex == 1) { 83 | const len = Number.parseInt(match); 84 | const all_received = contents_start + len <= contents.length; 85 | result.push({ start: contents_start, end: contents_start + len, all_received }); 86 | } 87 | }); 88 | } 89 | return result; 90 | } 91 | 92 | /** 93 | * Parses the contents in `buffer` using the packet metadata in `metadata`. 94 | * Returns what's remaining in the buffer that's not parsed. Not every packet is required 95 | * to have been handled 96 | * @param {string} buffer 97 | * @param {PacketBufferMetaData[]} metadata 98 | * @returns { { buffer: string, protocol_messages: DAPMessage[] } } 99 | */ 100 | function parseBuffer(buffer, metadata) { 101 | let parsed_end = 0; 102 | const res = []; 103 | for (const { start, end } of metadata.filter((i) => i.all_received)) { 104 | const data = buffer.slice(start, end); 105 | const json = JSON.parse(data); 106 | res.push(json); 107 | parsed_end = end; 108 | } 109 | buffer = buffer.slice(parsed_end); 110 | return { buffer, protocol_messages: res }; 111 | } 112 | 113 | class MidasCommunicationChannel { 114 | // TODO(simon): Use a better buffer here. Uint8 array? Low hanging fruit-optimization for another day. 115 | /** @type { string } */ 116 | buffer; 117 | 118 | /** @type { DataChannel | null } */ 119 | channel = null; 120 | 121 | constructor(name, emitter) { 122 | this.name = name; 123 | /** @type { EventEmitter } */ 124 | this.emitter = emitter; 125 | // TODO(simon): Do something much better. For now this is just easy enough, i.e. using a string. 126 | // We *really* should do something better here. But until it becomes a problem, let's just be stupid here 127 | this.buffer = ""; 128 | } 129 | 130 | /** 131 | * If `waitableSendRequest` has been called, this will emit the response to the waiter. 132 | * Otherwise it will be handled by the normal sendResponse routine 133 | * @param {*} msg 134 | */ 135 | reportResponse(msg) { 136 | if (!this.emitter.emit(`${msg.request_seq}`, msg)) { 137 | this.emitter.emit("response", msg); 138 | } 139 | } 140 | 141 | async connect() { 142 | this.channel = await this.resolveInputDataChannel().then((channel) => { 143 | channel.recv.on("data", (data) => { 144 | const str = data.toString(); 145 | this.buffer = this.buffer.concat(str); 146 | const packets = processBuffer(this.buffer).filter((i) => i.all_received); 147 | const { buffer: remaining_buffer, protocol_messages } = parseBuffer(this.buffer, packets); 148 | this.buffer = remaining_buffer; 149 | for (const msg of protocol_messages) { 150 | const type = msg.type; 151 | switch (type) { 152 | case "response": 153 | this.reportResponse(msg); 154 | break; 155 | default: 156 | this.emitter.emit(type, msg); 157 | } 158 | } 159 | }); 160 | return channel; 161 | }); 162 | } 163 | 164 | /** 165 | * Write `data` to output channel 166 | * @param { string } data 167 | * @throws { Error } 168 | */ 169 | write(data) { 170 | this.channel.send.write(data, (err) => { 171 | if (err) { 172 | console.error(`Failed to write ${data} to socket: ${err}`); 173 | throw err; 174 | } 175 | }); 176 | } 177 | 178 | /** 179 | * @returns { Promise } 180 | */ 181 | async resolveInputDataChannel() { 182 | throw new Error("Derived must implement this"); 183 | } 184 | } 185 | 186 | class UnixSocketCommunication extends MidasCommunicationChannel { 187 | constructor(path, emitter) { 188 | super(path, emitter); 189 | } 190 | 191 | /** 192 | * @returns { Promise } 193 | */ 194 | async resolveInputDataChannel() { 195 | const sock = await connect_socket(this.name, this.name, 10, 50); 196 | return { recv: sock, send: sock }; 197 | } 198 | 199 | async connect() { 200 | this.channel = await this.resolveInputDataChannel().then((channel) => { 201 | channel.recv.on("data", (data) => { 202 | const str = data.toString(); 203 | this.buffer = this.buffer.concat(str); 204 | const packets = processBuffer(this.buffer).filter((i) => i.all_received); 205 | const { buffer: remaining_buffer, protocol_messages } = parseBuffer(this.buffer, packets); 206 | console.log( 207 | `protocol messages=\n${JSON.stringify(protocol_messages, null, 2)}\nRemaining buffer=${remaining_buffer}`, 208 | ); 209 | this.buffer = remaining_buffer; 210 | for (const msg of protocol_messages) { 211 | const type = msg.type; 212 | switch (type) { 213 | case "response": 214 | this.reportResponse(msg); 215 | break; 216 | default: 217 | this.emitter.emit(type, msg); 218 | } 219 | } 220 | }); 221 | return channel; 222 | }); 223 | } 224 | } 225 | 226 | module.exports = { 227 | MidasCommunicationChannel, 228 | UnixSocketCommunication, 229 | MessageHeader, 230 | serializeRequest, 231 | parseBuffer, 232 | processBuffer, 233 | connect_socket, 234 | }; 235 | -------------------------------------------------------------------------------- /modules/dap/gdb.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { InitializedEvent } = require("@vscode/debugadapter"); 3 | const { getExtensionPathOf } = require("../utils/sysutils"); 4 | const { UnixSocketCommunication } = require("./dap-utils"); 5 | const { DebuggerProcessBase } = require("./base-process-handle"); 6 | const { MidasSessionBase } = require("./dap-base"); 7 | const { CustomRequests } = require("../constants"); 8 | 9 | class GdbProcess extends DebuggerProcessBase { 10 | constructor(options) { 11 | super(options); 12 | 13 | try { 14 | const p = this.path(); 15 | const args = this.spawnArgs(); 16 | this.spawnDebugger(p, args); 17 | } catch (ex) { 18 | console.log(`Creating instance of GdbProcess failed: ${ex}`); 19 | // re-throw exception - this must be a hard error 20 | throw ex; 21 | } 22 | 23 | this.commands_socket = new UnixSocketCommunication("/tmp/midas-commands", this.messages); 24 | this.events_socket = new UnixSocketCommunication("/tmp/midas-events", this.messages); 25 | } 26 | 27 | async initialize() { 28 | await this.commands_socket.connect(); 29 | await this.events_socket.connect(); 30 | } 31 | 32 | requestChannel() { 33 | return this.commands_socket; 34 | } 35 | 36 | /** overridden */ 37 | spawnArgs() { 38 | const args = [ 39 | ...this.options.options, 40 | "-q", 41 | "-ex", 42 | `source ${getExtensionPathOf("/modules/python/dap-wrapper/variables_reference.py")}`, 43 | "-ex", 44 | `source ${getExtensionPathOf("/modules/python/dap-wrapper/dap.py")}`, 45 | ]; 46 | return args; 47 | } 48 | } 49 | 50 | class GdbDAPSession extends MidasSessionBase { 51 | constructor(spawnConfig, terminal, checkpointsUI) { 52 | const cleanUpEmitter = null; 53 | super(GdbProcess, spawnConfig, terminal, checkpointsUI, null, cleanUpEmitter); 54 | if (this.spawnConfig.isRRSession()) { 55 | super.configureUserInterfaceFor({ 56 | sessionType: "midas-rr", 57 | singleThreadControl: !spawnConfig.noSingleThreadControl, 58 | nativeMode: false, 59 | isReplay: true, 60 | }); 61 | } else { 62 | super.configureUserInterfaceFor({ 63 | sessionType: "midas-gdb", 64 | singleThreadControl: !spawnConfig.noSingleThreadControl, 65 | nativeMode: false, 66 | isReplay: false, 67 | }); 68 | } 69 | } 70 | 71 | async initializeRequest(response, args) { 72 | if (this.spawnConfig?.ignoreStandardLibrary) { 73 | this.ignoreFileList = await this.spawnConfig.getCppStandardLibraryFileList().then((fileList) => { 74 | return fileList.map((line) => `skip file ${line}`); 75 | }); 76 | } 77 | this.dbg.initialize().then(async () => { 78 | args["trace"] = this.spawnConfig.trace; 79 | args["rr-session"] = this.spawnConfig.isRRSession(); 80 | args["rrinit"] = getExtensionPathOf("rrinit"); 81 | 82 | const res = await this.dbg.waitableSendRequest( 83 | { seq: 1, command: "initialize", arguments: args, type: "request" }, 84 | args, 85 | ); 86 | this.sendResponse(res); 87 | this.sendEvent(new InitializedEvent()); 88 | }); 89 | } 90 | 91 | launchRequest(response, args, request) { 92 | args["setupCommands"] = [...(this.spawnConfig?.setupCommands ?? []), ...(this.ignoreFileList ?? [])]; 93 | super.launchRequest(response, args, request); 94 | } 95 | 96 | attachRequest(response, args, request) { 97 | args["setupCommands"] = [...(this.spawnConfig?.setupCommands ?? []), ...(this.ignoreFileList ?? [])]; 98 | super.attachRequest(response, args, request); 99 | } 100 | 101 | PauseAll(request) { 102 | request.command = CustomRequests.PauseAll; 103 | request.arguments = {}; 104 | this.dbg.sendRequest(request); 105 | } 106 | 107 | ContinueAll(request) { 108 | request.command = CustomRequests.ContinueAll; 109 | request.arguments = {}; 110 | this.dbg.sendRequest(request); 111 | } 112 | 113 | OnSelectedThread(request, id) { 114 | request.command = "selectThread"; 115 | request.arguments = { threadId: id }; 116 | this.dbg.sendRequest(request); 117 | } 118 | } 119 | 120 | module.exports = { 121 | GdbDAPSession, 122 | }; 123 | -------------------------------------------------------------------------------- /modules/prettyprinter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { workspace, Uri } = require("vscode"); 4 | 5 | class PrettyPrinter { 6 | #pattern = ""; 7 | 8 | constructor(pattern) { 9 | this.#pattern = pattern; 10 | } 11 | 12 | get regexp() { 13 | return `(?<${this.name}>${this.#pattern})`; 14 | } 15 | 16 | get name() { 17 | return this.constructor.name; 18 | } 19 | 20 | // eslint-disable-next-line no-unused-vars 21 | valueHint(variable) {} 22 | 23 | // eslint-disable-next-line no-unused-vars 24 | valueExpanded(children) {} 25 | } 26 | 27 | class CachedValue extends PrettyPrinter { 28 | variable; 29 | constructor(variable) { 30 | super(""); 31 | this.variable = variable; 32 | } 33 | 34 | valueHint(varible) { 35 | varible.value = this.variable.value; 36 | } 37 | 38 | valueExpanded(variables) { 39 | variables.clear(); 40 | variables.push(this.variable); 41 | } 42 | } 43 | 44 | class Variable { 45 | #variable; 46 | #printer; 47 | constructor(printer, variable) { 48 | this.#printer = printer; 49 | this.#variable = variable; 50 | } 51 | 52 | get value() { 53 | return this.#variable.value; 54 | } 55 | 56 | set value(value) { 57 | this.#variable.value = value; 58 | } 59 | 60 | get name() { 61 | return this.#variable.name; 62 | } 63 | 64 | get type() { 65 | return this.#variable.type; 66 | } 67 | 68 | hasChildren() { 69 | return this.#variable.variablesReference > 0; 70 | } 71 | 72 | async children() { 73 | if (!this.hasChildren()) { 74 | return []; 75 | } 76 | 77 | const args = { variablesReference: this.#variable.variablesReference }; 78 | const request = { 79 | seq: this.#printer.seq_number, 80 | command: "variables", 81 | type: "request", 82 | arguments: args, 83 | }; 84 | 85 | const response = await this.#printer.session.dbg.waitableSendRequest(request, args); 86 | return response.body.variables.map((v) => new Variable(this.#printer, v)); 87 | } 88 | 89 | cache(value) { 90 | this.#printer.cache(this.#variable.variablesReference, value); 91 | } 92 | 93 | getRaw(session) { 94 | if (session == this.#printer.session) { 95 | return this.#variable; 96 | } 97 | } 98 | 99 | toLiteral() { 100 | this.#variable.variablesReference = 0; 101 | } 102 | } 103 | 104 | class Variables { 105 | #printer; 106 | #variables; 107 | constructor(printer, variables) { 108 | this.#printer = printer; 109 | this.#variables = variables; 110 | } 111 | 112 | value(index) { 113 | if (index < this.#variables.length) { 114 | return new Variable(this.#printer, this.#variables[index]); 115 | } 116 | } 117 | 118 | remove(index) { 119 | if (index < this.#variables.length) { 120 | this.#variables.splice(index, 1); 121 | } 122 | } 123 | 124 | clear() { 125 | this.#variables.length = 0; 126 | } 127 | 128 | push(value) { 129 | const raw = value.getRaw(this.#printer.session); 130 | this.#variables.push(raw); 131 | } 132 | 133 | update(index, value) { 134 | if (index < this.#variables.length) { 135 | const raw = value.getRaw(this.#printer.session); 136 | this.#variables[index] = raw; 137 | } 138 | } 139 | } 140 | 141 | class Printer { 142 | #regexp = / /; 143 | #printers = {}; 144 | #interceptions = new Map(); 145 | #session; 146 | #seq_number = 0xffffffff; 147 | 148 | get seq_number() { 149 | return this.#seq_number--; 150 | } 151 | 152 | constructor(session, regexp, printers) { 153 | console.log(regexp); 154 | this.#session = session; 155 | this.#regexp = new RegExp(`^${regexp}$`, "i"); 156 | this.#printers = printers; 157 | } 158 | 159 | get session() { 160 | return this.#session; 161 | } 162 | 163 | match(input) { 164 | const matches = this.#regexp.exec(input); 165 | if (!matches) { 166 | return null; 167 | } 168 | 169 | for (const [key, value] of Object.entries(matches.groups)) { 170 | if (value && value.length == input.length) { 171 | return this.#printers[key]; 172 | } 173 | } 174 | 175 | return null; 176 | } 177 | 178 | async prettify(variable) { 179 | // We explicitly forbid prettyprinters for literal types. 180 | if (variable.variablesReference === 0) { 181 | return; 182 | } 183 | 184 | const prettyprinter = this.match(variable.type); 185 | if (!prettyprinter) { 186 | return; 187 | } 188 | 189 | await prettyprinter.valueHint(new Variable(this, variable)); 190 | 191 | if (variable.variablesReference && !this.#interceptions.has(variable.variablesReference)) { 192 | this.#interceptions.set(variable.variablesReference, prettyprinter); 193 | } 194 | } 195 | 196 | print(request, args) { 197 | const printer = this.#interceptions.get(request.arguments.variablesReference); 198 | const promise = this.#session.dbg.waitableSendRequest(request, args); 199 | if (!printer) { 200 | return promise; 201 | } 202 | 203 | return promise.then(async (response) => { 204 | await printer.valueExpanded(new Variables(this, response.body.variables)); 205 | return response; 206 | }); 207 | } 208 | 209 | intercept(response, args, request) { 210 | const printer = this.#interceptions.get(request.arguments.variablesReference); 211 | if (!printer) { 212 | return false; 213 | } 214 | // We're intentionally not removing the interception here. 215 | // We let a new scopesRequest perform that operation for us. 216 | 217 | return true; 218 | } 219 | 220 | cache(variablesReference, value) { 221 | if (value instanceof Variable) { 222 | this.#interceptions.set(variablesReference, new CachedValue(value)); 223 | } 224 | } 225 | 226 | reset() { 227 | // We drop the old map on the floor, letting the garbage collector 228 | // get rid of the memory. 229 | this.#interceptions = new Map(); 230 | } 231 | } 232 | 233 | async function getFiles(path) { 234 | try { 235 | const files = await workspace.fs.readDirectory(path); 236 | return files; 237 | } catch (_) { 238 | return []; 239 | } 240 | } 241 | 242 | class PrinterFactory { 243 | #printers = {}; 244 | #session; 245 | 246 | async loadPrettyPrinters(directory) { 247 | const printerlib = { 248 | PrettyPrinter, 249 | }; 250 | let files = []; 251 | try { 252 | files = await getFiles(directory); 253 | } catch (_) {} 254 | 255 | for (const [file, type] of files) { 256 | if (type != 1) { 257 | continue; 258 | } 259 | try { 260 | const uri = Uri.joinPath(directory, file); 261 | const { create } = require(uri.path); 262 | this.add(create(printerlib)); 263 | } catch (e) { 264 | console.log(e.message); 265 | } 266 | } 267 | return this.printer(); 268 | } 269 | 270 | constructor(session) { 271 | this.#session = session; 272 | } 273 | 274 | add(printer) { 275 | this.#printers[printer.name] = printer; 276 | } 277 | 278 | printer() { 279 | if (!Object.keys(this.#printers).length) { 280 | return null; 281 | } 282 | 283 | const re = Object.values(this.#printers) 284 | .map((p) => p.regexp) 285 | .join("|"); 286 | return new Printer(this.#session, re, this.#printers); 287 | } 288 | } 289 | 290 | module.exports = { PrettyPrinter, PrinterFactory }; 291 | -------------------------------------------------------------------------------- /modules/providers/initializer.js: -------------------------------------------------------------------------------- 1 | const { isNothing, getVersion, requiresMinimum } = require("../utils/utils"); 2 | 3 | const InitExceptionTypes = { 4 | GdbNotFound: "GdbNotFound", 5 | GdbVersionUnknown: "GdbVersionUnknown", 6 | RRNotFound: "RRNotFound", 7 | NullConfig: "NullConfig", 8 | MdbNotFound: "MdbNotFound", 9 | }; 10 | 11 | async function gdbSettingsOk(config) { 12 | try { 13 | const version = await getVersion(config.gdbPath); 14 | requiresMinimum(version, { major: 9, minor: 1, patch: 0 }); 15 | } catch (e) { 16 | throw { type: InitExceptionTypes.GdbVersionUnknown, message: `GDB Version could not be determined. ${e}` }; 17 | } 18 | } 19 | 20 | class ConfigurationProviderInitializer { 21 | /** 22 | * @param {any} config 23 | * @param {any} initializer 24 | * @throws {{ type: string, message: string }} 25 | */ 26 | async defaultInitialize(config, initializer) { 27 | // if launch.json is missing or empty 28 | if (isNothing(config) || isNothing(config.type)) { 29 | throw { type: InitExceptionTypes.NullConfig, message: "No launch.json found" }; 30 | } 31 | if (config.ignoreStandardLibrary == null) { 32 | config.ignoreStandardLibrary = true; 33 | } 34 | await initializer(config); 35 | } 36 | } 37 | 38 | module.exports = { 39 | ConfigurationProviderInitializer, 40 | InitExceptionTypes, 41 | gdbSettingsOk, 42 | }; 43 | -------------------------------------------------------------------------------- /modules/providers/midas-gdb.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | const { ConfigurationProviderInitializer, InitExceptionTypes, gdbSettingsOk } = require("./initializer"); 3 | const { isNothing, resolveCommand, showErrorPopup, getPid, strEmpty, getAPI } = require("../utils/utils"); 4 | const { LaunchSpawnConfig, AttachSpawnConfig } = require("../spawn"); 5 | const { GdbDAPSession } = require("../dap/gdb"); 6 | 7 | const initializer = async (config) => { 8 | if (!config.hasOwnProperty("stopOnEntry")) { 9 | config.stopOnEntry = false; 10 | } 11 | if (!config.hasOwnProperty("trace")) { 12 | config.trace = "off"; 13 | } 14 | // allStopMode is legacy name from gdb. 15 | if (config.hasOwnProperty("allStopMode")) { 16 | vscode.window.showWarningMessage("allStopMode is a deprecated flag. Use noSingleThreadControl instead"); 17 | config.noSingleThreadControl = config.allStopMode; 18 | } 19 | 20 | if (!config.hasOwnProperty("noSingleThreadControl") && config.noSingleThreadControl == undefined) { 21 | config.noSingleThreadControl = true; 22 | } 23 | if (!config.hasOwnProperty("gdbPath")) { 24 | config.gdbPath = await getAPI().resolveToolPath("gdb"); 25 | if (config.gdbPath == undefined) { 26 | throw { type: InitExceptionTypes.GdbNotFound }; 27 | } 28 | } 29 | await gdbSettingsOk(config); 30 | if (!config.hasOwnProperty("setupCommands")) { 31 | config.setupCommands = []; 32 | } 33 | if (!config.hasOwnProperty("remoteTargetConfig")) { 34 | config.remoteTargetConfig = null; 35 | } 36 | if (!config.hasOwnProperty("externalConsole")) { 37 | config.externalConsole = null; 38 | } else { 39 | if (isNothing(config.externalConsole.path)) { 40 | throw new Error("Path field for externalConsole not provided in configuration"); 41 | } 42 | if (strEmpty(config.externalConsole.path)) { 43 | try { 44 | config.externalConsole.path = resolveCommand("x-terminal-emulator"); 45 | } catch (err) { 46 | throw new Error(`[externalConsole.path error]: ${err.message}`); 47 | } 48 | } 49 | } 50 | if (!config.program && config.remoteTargetConfig == null && config.request != "attach") { 51 | throw new Error("Program or remoteTargetConfig was not set. One of these fields has to be set in launch.json"); 52 | } 53 | }; 54 | 55 | class ConfigurationProvider extends ConfigurationProviderInitializer { 56 | get type() { 57 | return "midas-gdb"; 58 | } 59 | 60 | // eslint-disable-next-line no-unused-vars 61 | async resolveDebugConfiguration(folder, config, token) { 62 | return config; 63 | } 64 | 65 | // for now, we do not substitute any variables in the launch config, but we will. this will be used then. 66 | // @ts-ignore 67 | async resolveDebugConfigurationWithSubstitutedVariables(folder, config, token) { 68 | getAPI().clearChannelOutputs(); 69 | try { 70 | await super.defaultInitialize(config, initializer); 71 | } catch (err) { 72 | switch (err.type) { 73 | case InitExceptionTypes.GdbVersionUnknown: 74 | showErrorPopup("Incompatible GDB version", err.message, [ 75 | { 76 | title: "Download GDB source", 77 | action: async () => { 78 | await vscode.env.openExternal(vscode.Uri.parse("https://www.sourceware.org/gdb/current/")); 79 | }, 80 | }, 81 | ]).then((choice) => { 82 | if (choice) choice.action(); 83 | }); 84 | break; 85 | case InitExceptionTypes.GdbNotFound: 86 | vscode.window.showErrorMessage(`Gdb could not be found on your system`); 87 | break; 88 | default: 89 | console.log(`Unexpected exception: ${err}`); 90 | break; 91 | } 92 | return null; 93 | } 94 | 95 | if (config.request == "attach" && config.target == null) { 96 | if (!config.pid) { 97 | const pid = await getPid(); 98 | if (isNothing(pid)) { 99 | return null; 100 | } 101 | config.pid = pid; 102 | } 103 | } 104 | return config; 105 | } 106 | } 107 | 108 | class DebugAdapterFactory { 109 | /** 110 | * @param { vscode.DebugSession } session 111 | * @returns ProviderResult 112 | */ 113 | async createDebugAdapterDescriptor(session) { 114 | const config = session.configuration; 115 | let terminal = null; 116 | const midas_session = new GdbDAPSession(this.spawnConfig(config), terminal, null); 117 | return new vscode.DebugAdapterInlineImplementation(midas_session); 118 | } 119 | 120 | spawnConfig(config) { 121 | switch (config.request) { 122 | case "attach": 123 | return new AttachSpawnConfig(config); 124 | case "launch": 125 | return new LaunchSpawnConfig(config); 126 | default: 127 | throw new Error("Unknown request type"); 128 | } 129 | } 130 | } 131 | 132 | module.exports = { 133 | ConfigurationProvider, 134 | DebugAdapterFactory, 135 | }; 136 | -------------------------------------------------------------------------------- /modules/providers/midas-native.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | const { ConfigurationProviderInitializer, InitExceptionTypes } = require("./initializer"); 3 | const { showErrorPopup, getAPI } = require("../utils/utils"); 4 | const { MdbSpawnConfig } = require("../spawn"); 5 | const { MidasSessionController, MdbProcess, MidasNativeSession } = require("../dap/mdb"); 6 | const fs = require("fs"); 7 | const { EventEmitter } = require("events"); 8 | 9 | const initializer = async (config) => { 10 | if (config.hasOwnProperty("mdbPath")) { 11 | const mdbPath = config?.mdbPath ?? "mdb"; 12 | if (!fs.existsSync(mdbPath)) { 13 | throw { type: InitExceptionTypes.MdbNotFound, message: `MDB could not be found using '${mdbPath}'` }; 14 | } 15 | } 16 | 17 | if (!config.hasOwnProperty("stopOnEntry")) { 18 | config.stopOnEntry = false; 19 | } 20 | 21 | if (!config.hasOwnProperty("setupCommands")) { 22 | config.setupCommands = []; 23 | } 24 | }; 25 | 26 | class MdbConfigurationProvider extends ConfigurationProviderInitializer { 27 | get type() { 28 | return "midas-native"; 29 | } 30 | 31 | async resolveDebugConfiguration(folder, config, token) { 32 | return config; 33 | } 34 | 35 | async resolveDebugConfigurationWithSubstitutedVariables(folder, config, token) { 36 | if (config.RRSession == null) { 37 | config.RRSession = false; 38 | } 39 | 40 | if (MdbDebugAdapterFactory.RootSession == null) { 41 | getAPI().clearChannelOutputs(); 42 | try { 43 | await super.defaultInitialize(config, initializer); 44 | } catch (err) { 45 | switch (err.type) { 46 | case InitExceptionTypes.MdbNotFound: 47 | showErrorPopup(err.message, err.message, [ 48 | { 49 | title: "Download & build MDB?", 50 | action: async () => { 51 | await vscode.window.showInformationMessage("This feature is not implemented yet"); 52 | }, 53 | }, 54 | ]).then((choice) => { 55 | if (choice) choice.action(); 56 | }); 57 | break; 58 | default: 59 | showErrorPopup(`Unexpected fatal exception: ${err}`); 60 | throw err; 61 | } 62 | return null; 63 | } 64 | if (config.request == "attach" && config.attachArgs == null) { 65 | throw new Error("attachArgs field is missing. This field is required to determine target to attach to."); 66 | } 67 | if (config.request == "attach") { 68 | config.attachArguments = config.attachArgs; 69 | } 70 | 71 | return config; 72 | } else { 73 | // Assume MDB sends well-formed and sane config to itself. 74 | if (!config.childConfiguration) { 75 | throw new Error(`Child session could not spawn: No path was provided in the configuration`); 76 | } 77 | config.attachArguments = { 78 | type: "auto", 79 | processId: config.childConfiguration.processId 80 | }; 81 | return config; 82 | } 83 | } 84 | } 85 | 86 | class MdbDebugAdapterFactory { 87 | /** @type {MidasSessionController} */ 88 | static RootSession = null; 89 | #cp_ui; 90 | constructor(checkpointsUI) { 91 | this.#cp_ui = checkpointsUI; 92 | } 93 | /** 94 | * @param { vscode.DebugSession } session 95 | * @returns ProviderResult 96 | */ 97 | async createDebugAdapterDescriptor(session) { 98 | const config = session.configuration; 99 | let cleanUp = new EventEmitter(); 100 | cleanUp.on("shutdown", () => { 101 | MdbDebugAdapterFactory.RootSession = null; 102 | }); 103 | if (MdbDebugAdapterFactory.RootSession == null) { 104 | MdbDebugAdapterFactory.RootSession = new MidasSessionController( 105 | new MdbProcess(this.spawnConfig(config)), 106 | this.spawnConfig(config), 107 | null, 108 | this.#cp_ui, 109 | cleanUp, 110 | ); 111 | } 112 | 113 | return new vscode.DebugAdapterInlineImplementation( 114 | new MidasNativeSession(MdbDebugAdapterFactory.RootSession, this.spawnConfig(config)), 115 | ); 116 | } 117 | 118 | spawnConfig(config) { 119 | return new MdbSpawnConfig(config); 120 | } 121 | } 122 | 123 | module.exports = { 124 | MdbConfigurationProvider, 125 | MdbDebugAdapterFactory, 126 | }; 127 | -------------------------------------------------------------------------------- /modules/python/apt_manager.py: -------------------------------------------------------------------------------- 1 | import apt 2 | import apt_pkg 3 | import apt.progress.base 4 | import apt.progress 5 | import apt.cache 6 | import signal 7 | 8 | import midas_report 9 | 10 | USER_CANCELLED = False 11 | install_begun = False 12 | comms_address = "/tmp/rr-build-progress" 13 | 14 | # we install a signal handler for SIGUSR1, that we call from Midas (with sudo) 15 | def sig(sig, frame): 16 | global USER_CANCELLED 17 | USER_CANCELLED = True 18 | midas_report.get_logger().debug("User cancelled!") 19 | 20 | signal.signal(signal.SIGUSR1, sig) 21 | signal.signal(signal.SIGUSR2, sig) 22 | 23 | def did_cancel(): 24 | global USER_CANCELLED 25 | return USER_CANCELLED 26 | 27 | class MidasInstallProgress(apt.progress.base.InstallProgress, midas_report.MidasReport): 28 | def __init__(self, socket): 29 | midas_report.MidasReport.__init__(self, "install", socket) 30 | apt.progress.base.InstallProgress.__init__(self) 31 | self.last_known_progress = 0 32 | 33 | def conffile(self, current: str, new: str) -> None: 34 | super().conffile(current, new) 35 | self.report("conffile", { "current": current, "new": new }) 36 | 37 | def error(self, pkg: str, errormsg: str) -> None: 38 | super().error(pkg,errormsg) 39 | self.report("error", { "package": pkg, "msg": errormsg }) 40 | 41 | def processing(self, pkg: str, stage: str) -> None: 42 | super().processing(pkg=pkg,stage=stage) 43 | self.report("processing", { "package": pkg, "stage": stage }) 44 | 45 | def dpkg_status_change(self, pkg: str, status: str) -> None: 46 | super().dpkg_status_change(pkg=pkg, status=status) 47 | midas_report.get_logger().debug("pkg: {} - status: {}".format(pkg, status)) 48 | self.report("dpkg", { "package": pkg, "status": status }) 49 | 50 | def status_change(self, pkg: str, percent: float, status: str) -> None: 51 | super().status_change(pkg=pkg, percent=percent, status=status) 52 | if did_cancel(): 53 | self.report("cancel", "start") 54 | raise Exception 55 | increment = percent - self.last_known_progress 56 | self.last_known_progress = percent 57 | self.report("update", { "package": pkg, "increment": increment }) 58 | 59 | def start_update(self) -> None: 60 | super().start_update() 61 | global install_begun 62 | install_begun = True 63 | self.report("start") 64 | 65 | def finish_update(self) -> None: 66 | super().finish_update() 67 | self.report("finish") 68 | 69 | class MidasFetchProgress(apt.progress.base.AcquireProgress, midas_report.MidasReport): 70 | def __init__(self, socket, packages: list[str], total_download_bytes: int): 71 | midas_report.MidasReport.__init__(self, "download", socket) 72 | self.total_required = total_download_bytes 73 | self.packages = packages 74 | self.last_known_progress = 0 75 | self.was_cancelled = False 76 | 77 | def done(self, item: apt_pkg.AcquireItemDesc) -> None: 78 | super().done(item) 79 | done_package = item.shortdesc 80 | self.report("done", { "done": done_package }) 81 | 82 | def fail(self, item: apt_pkg.AcquireItemDesc) -> None: 83 | super().fail(item) 84 | self.report("error", { "package": item.shortdesc }) 85 | 86 | def fetch(self, item: apt_pkg.AcquireItemDesc) -> None: 87 | return super().fetch(item) 88 | 89 | def ims_hit(self, item: apt_pkg.AcquireItemDesc) -> None: 90 | return super().ims_hit(item) 91 | 92 | def media_change(self, media: str, drive: str) -> bool: 93 | return super().media_change(media, drive) 94 | 95 | def pulse(self, owner: apt_pkg.Acquire) -> bool: 96 | super().pulse(owner) 97 | if did_cancel(): 98 | self.was_cancelled = True 99 | self.report("cancel", "start") 100 | return False 101 | download_progress = round((self.current_bytes / self.total_required) * 100.0, 2) 102 | increment = download_progress - self.last_known_progress 103 | self.last_known_progress = download_progress 104 | self.report("update", { "bytes": self.current_bytes, "progress": download_progress, "increment": increment }) 105 | return True 106 | 107 | def start(self) -> None: 108 | super().start() 109 | self.report("start", { "packages": self.packages, "bytes" : self.total_required }) 110 | 111 | def stop(self) -> None: 112 | super().stop() 113 | if not self.was_cancelled: 114 | self.report("finish", { "bytes" : self.current_bytes }) 115 | try: 116 | fetch_socket = midas_report.MidasSocket(comms_address) 117 | install_socket = midas_report.MidasSocket(comms_address) 118 | DEPS = install_socket.wait_for_packages() 119 | 120 | cache = apt.Cache() 121 | cache.open() 122 | try: 123 | for package in DEPS: 124 | pkg = cache[package] 125 | if not pkg.is_installed: 126 | pkg.mark_install() 127 | except Exception as e: 128 | midas_report.get_logger().debug("Updating APT cache ({})".format(e)) 129 | # we might have to update cache if we have no cache. This can take some time though. 130 | cache.update() 131 | for package in DEPS: 132 | pkg = cache[package] 133 | if not pkg.is_installed: 134 | pkg.mark_install() 135 | 136 | packages = [x.name for x in cache.get_changes()] 137 | 138 | fetch_progress = MidasFetchProgress(socket=fetch_socket, packages=packages, total_download_bytes=cache.required_download) 139 | install_progress = MidasInstallProgress(socket=install_socket) 140 | if len(packages) == 0: 141 | midas_report.get_logger().debug("All required dependencies met") 142 | fetch_progress.start() 143 | fetch_progress.stop() 144 | install_progress.start_update() 145 | install_progress.finish_update() 146 | else: 147 | try: 148 | cache.commit(fetch_progress=fetch_progress, install_progress=install_progress) 149 | except Exception as e: 150 | midas_report.get_logger().debug("Exception or cancelled operation. {} Message: {}".format(type(e), e)) 151 | resolver = apt.cache.ProblemResolver(cache) 152 | midas_report.get_logger().debug("Deleting packages...") 153 | for package in packages: 154 | pkg = cache[package] 155 | resolver.remove(pkg) 156 | midas_report.get_logger().debug("Delete count: {}".format(cache.delete_count)) 157 | cache.commit() 158 | cache.close() 159 | action = "download" if not install_begun else "install" 160 | payload = {"type": "cancel", "action": action, "data": "done" } 161 | if not install_begun: 162 | fetch_socket.send_payload(payload) 163 | else: 164 | install_socket.send_payload(payload) 165 | fetch_socket.close() 166 | install_socket.close() 167 | except Exception as e: 168 | midas_report.get_logger().debug("Cache operation excetion caught: {}".format(e)) -------------------------------------------------------------------------------- /modules/python/config.py: -------------------------------------------------------------------------------- 1 | """the config module holds settings for Midas but it also keeps global state for the backend.""" 2 | 3 | import gdb 4 | import functools 5 | import time 6 | import logging 7 | import traceback 8 | 9 | global isDevelopmentBuild 10 | global setTrace 11 | global currentExecutionContext 12 | 13 | variableReferenceCounter = 0 14 | 15 | 16 | def next_variable_reference(): 17 | global variableReferenceCounter 18 | res = variableReferenceCounter + 1 19 | variableReferenceCounter += 1 20 | return res 21 | 22 | 23 | isDevelopmentBuild = False 24 | setTrace = False 25 | currentExecutionContext = None 26 | 27 | 28 | class ReferenceKey: 29 | 30 | def __init__(self, threadId, stackFrameId): 31 | self.threadId = threadId 32 | self.frameId = stackFrameId 33 | 34 | 35 | class VariableReferenceMap: 36 | 37 | def __init__(self): 38 | self.lookup = {} 39 | 40 | def add_mapping(self, variableReference, midasStackFrame): 41 | self.lookup[variableReference] = midasStackFrame.reference_key() 42 | 43 | def get_context(self, variableReference) -> ReferenceKey: 44 | return self.lookup.get(variableReference) 45 | 46 | 47 | variableReferences = VariableReferenceMap() 48 | 49 | 50 | def timeInvocation(f): 51 | if not isDevelopmentBuild: 52 | return f 53 | """Measure performance (time) of command or function""" 54 | 55 | @functools.wraps(f) 56 | def timer_decorator(*args, **kwargs): 57 | invokeBegin = time.perf_counter_ns() 58 | result = f(*args, **kwargs) 59 | invokeEnd = time.perf_counter_ns() 60 | logger = logging.getLogger("time-logger") 61 | # we don't need nano-second measuring, but the accuracy of the timer is nice. 62 | elapsed_time = int((invokeEnd - invokeBegin) / 1000) 63 | logger.info("{:<30} executed in {:>10,} microseconds".format(f.__qualname__, elapsed_time)) 64 | return result 65 | 66 | return timer_decorator 67 | 68 | 69 | def error_logger(): 70 | return logging.getLogger("error-logger") 71 | 72 | 73 | def update_logger(): 74 | return logging.getLogger("update-logger") 75 | 76 | 77 | def timing_logger(): 78 | return logging.getLogger("time-logger") 79 | 80 | 81 | def log_exception(logger, errmsg, exception): 82 | logger.error("{} Exception info: {}".format(errmsg, exception)) 83 | logger.error(traceback.format_exc()) 84 | logger.error("Current dev setting: {}".format(isDevelopmentBuild)) 85 | -------------------------------------------------------------------------------- /modules/python/dap-wrapper/logger.py: -------------------------------------------------------------------------------- 1 | import time 2 | import json 3 | import traceback 4 | from os import path 5 | import sys 6 | 7 | stdlibpath = path.dirname(path.realpath(__file__)) 8 | if sys.path.count(stdlibpath) == 0: 9 | sys.path.append(stdlibpath) 10 | 11 | this = sys.modules[__name__] 12 | 13 | 14 | class LogFile: 15 | def __init__(self, name): 16 | global stdlibpath 17 | self.name = name 18 | self.path = f"{stdlibpath}/{name}" 19 | self.file = open(self.path, "w") 20 | 21 | def log(self, msg): 22 | self.file.write(msg) 23 | 24 | def close(self): 25 | print(f"Flushing contents to {self.path}") 26 | self.file.flush() 27 | self.file.close() 28 | 29 | 30 | class Logger: 31 | def __init__(self): 32 | self.perf = None 33 | self.debug = None 34 | self.custom = {} 35 | 36 | def init_custom(self, log_name): 37 | self.custom[log_name] = LogFile(log_name) 38 | 39 | def init_perf_log(self, log_name): 40 | self.perf = LogFile(log_name) 41 | 42 | def init_debug_log(self, log_name): 43 | self.debug = LogFile(log_name) 44 | 45 | def log_request(self, fn, args): 46 | if self.debug is not None: 47 | self.debug.log(msg=f"[req]: [{fn}] <- {json.dumps(args)}\n") 48 | 49 | def log_response(self, fn, res): 50 | if self.debug is not None: 51 | self.debug.log(msg=f"[res]: [{fn}] -> {json.dumps(res)}\n") 52 | 53 | def log_exception(self, fn, exc): 54 | if self.debug is not None: 55 | self.debug.log( 56 | msg=f"[req exception]: [{fn}] -> {exc}\nStacktrace:\n{traceback.format_exc()}" 57 | ) 58 | 59 | def log_to(self, custom, msg): 60 | log = self.custom.get(custom) 61 | print(f"custom={msg}") 62 | if log is not None: 63 | log.log(msg) 64 | 65 | def log_msg(self, msg): 66 | if self.debug is not None: 67 | self.debug.log(msg) 68 | 69 | def perf_log(self, fn, msg): 70 | start = time.perf_counter_ns() 71 | res = fn() 72 | end = time.perf_counter_ns() 73 | self.perf.log(msg=f"[{msg}]: {(end-start) / (1000 * 1000)} ms\n") 74 | return res 75 | 76 | def atexit(self): 77 | self.perf.close() 78 | self.debug.close() 79 | for log in self.custom.values(): 80 | log.close() 81 | 82 | 83 | if not hasattr(this, "logger"): 84 | this.logger = Logger() 85 | 86 | import atexit 87 | def clean_up(): 88 | print("Closing & flushing potential logs") 89 | global this 90 | this.logger.atexit() 91 | 92 | 93 | atexit.register(clean_up) -------------------------------------------------------------------------------- /modules/python/dap-wrapper/rrinit: -------------------------------------------------------------------------------- 1 | define restart 2 | run c$arg0 3 | end 4 | document restart 5 | restart at checkpoint N 6 | checkpoints are created with the 'checkpoint' command 7 | end 8 | 9 | set unwindonsignal on 10 | handle SIGURG stop 11 | set tcp connect-timeout 10000 -------------------------------------------------------------------------------- /modules/python/dap-wrapper/rrinit - full: -------------------------------------------------------------------------------- 1 | # The gdbinit stuff - however, some of this stuff makes Midas unstable. So this file is not source (only rrinit is) 2 | define hookpost-back 3 | maintenance flush register-cache 4 | frame 5 | end 6 | 7 | define hookpost-forward 8 | maintenance flush register-cache 9 | frame 10 | end 11 | define restart 12 | run c$arg0 13 | end 14 | document restart 15 | restart at checkpoint N 16 | checkpoints are created with the 'checkpoint' command 17 | end 18 | define seek-ticks 19 | run t$arg0 20 | end 21 | document seek-ticks 22 | restart at given ticks value 23 | end 24 | define jump 25 | rr-denied jump 26 | end 27 | define hook-run 28 | rr-hook-run 29 | end 30 | define hookpost-continue 31 | rr-set-suppress-run-hook 1 32 | end 33 | define hookpost-step 34 | rr-set-suppress-run-hook 1 35 | end 36 | define hookpost-stepi 37 | rr-set-suppress-run-hook 1 38 | end 39 | define hookpost-next 40 | rr-set-suppress-run-hook 1 41 | end 42 | define hookpost-nexti 43 | rr-set-suppress-run-hook 1 44 | end 45 | define hookpost-finish 46 | rr-set-suppress-run-hook 1 47 | end 48 | define hookpost-reverse-continue 49 | rr-set-suppress-run-hook 1 50 | end 51 | define hookpost-reverse-step 52 | rr-set-suppress-run-hook 1 53 | end 54 | define hookpost-reverse-stepi 55 | rr-set-suppress-run-hook 1 56 | end 57 | define hookpost-reverse-finish 58 | rr-set-suppress-run-hook 1 59 | end 60 | define hookpost-run 61 | rr-set-suppress-run-hook 0 62 | end 63 | set unwindonsignal on 64 | handle SIGURG stop -------------------------------------------------------------------------------- /modules/python/midas_report.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import json 3 | import sys 4 | import os 5 | import logging 6 | import logging.handlers 7 | 8 | COMMS_ADDRESS = "/tmp/rr-build-progress" 9 | 10 | def resolveExtensionFile(fileName): 11 | extensionPath = os.path.dirname(os.path.realpath(__file__)) 12 | return "{}/../../{}".format(extensionPath, fileName) 13 | 14 | install_logger_handler = logging.handlers.WatchedFileHandler(resolveExtensionFile("rr-dependency-manager.log"), mode="w") 15 | install_logger_fmt = logging.Formatter(logging.BASIC_FORMAT) 16 | install_logger_handler.setFormatter(install_logger_fmt) 17 | 18 | install_logger = logging.getLogger("install-logger") 19 | install_logger.setLevel(logging.DEBUG) 20 | install_logger.addHandler(install_logger_handler) 21 | 22 | def get_logger(): 23 | return logging.getLogger("install-logger") 24 | 25 | def process_info_payload(): 26 | return { "pid": os.getpid(), "ppid": os.getppid() } 27 | 28 | class UserCancelledException(Exception): 29 | pass 30 | 31 | class MidasSocket(): 32 | requested_pkgs = None 33 | def __init__(self, addr: str) -> None: 34 | self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 35 | self.socket.connect(addr) 36 | 37 | def close(self): 38 | self.socket.close() 39 | 40 | def send_payload(self, json_: dict): 41 | payload = json.dumps(json_).encode("UTF-8") 42 | get_logger().debug("Sending payload {}".format(payload)) 43 | self.socket.send(payload) 44 | self.socket.send(b"\n") 45 | 46 | @staticmethod 47 | def has_received_pkglist_info(): 48 | return MidasSocket.requested_pkgs is not None 49 | 50 | def wait_for_packages(self) -> list[str]: 51 | get_logger().debug("Waiting for packages...") 52 | if MidasSocket.has_received_pkglist_info(): 53 | return MidasSocket.requested_pkgs 54 | header = self.socket.recv(4) 55 | payload_length = int.from_bytes(header, sys.byteorder) 56 | buffer = self.socket.recv(payload_length) 57 | res = buffer.decode("UTF-8").strip() 58 | get_logger().debug("Received payload: {}".format(res)) 59 | MidasSocket.requested_pkgs = res.split(" ") 60 | return MidasSocket.requested_pkgs 61 | 62 | class MidasReport(): 63 | def __init__(self, action: str, socket: MidasSocket): 64 | self.action = action 65 | self.socket = socket 66 | self.report("setup", process_info_payload()) 67 | 68 | def report(self, type, data=None): 69 | """ Send a report to Midas """ 70 | self.socket.send_payload({"type": type, "action": self.action, "data": data}) -------------------------------------------------------------------------------- /modules/python/midas_utils.py: -------------------------------------------------------------------------------- 1 | import gdb 2 | import json 3 | import sys 4 | import logging 5 | 6 | 7 | def type_is_primitive(valueType): 8 | try: 9 | for f in valueType.fields(): 10 | if hasattr(f, "enumval"): 11 | return True 12 | else: 13 | return False 14 | except TypeError: 15 | return True 16 | 17 | 18 | def get_members_recursively(field, memberList, statics): 19 | if field.bitsize > 0: 20 | misc_logger = logging.getLogger("update-logger") 21 | misc_logger.info("field {} is possibly a bitfield of size {}".format(field.name, field.bitsize)) 22 | if hasattr(field, 'bitpos'): 23 | if field.is_base_class: 24 | for f in field.type.fields(): 25 | get_members_recursively(f, memberList=memberList, statics=statics) 26 | else: 27 | if field.name is not None and not field.name.startswith("_vptr"): 28 | memberList.append(field.name) 29 | else: 30 | statics.append(field.name) 31 | 32 | 33 | def getFunctionBlock(frame) -> gdb.Block: 34 | block = frame.block() 35 | while not block.superblock.is_static and not block.superblock.is_global: 36 | block = block.superblock 37 | if block.is_static or block.is_global: 38 | return None 39 | return block 40 | 41 | 42 | def parse_command_args(arg_string, *argv): 43 | """Parses the arguments passed in the argument string for commands. 44 | `*argv` contains a variable amount of type informations, which arguments in the argument string will be converted to. 45 | `argv` can not be a longer list than arg_string as this will throw an exception. If the type list is shorter than parsed arguments 46 | the remaining args will be returned as strings""" 47 | 48 | parsed_arguments = gdb.string_to_argv(arg_string) 49 | if len(argv) > len(parsed_arguments): 50 | raise gdb.GdbError("Parsed arguments less than arguments in type list") 51 | index = 0 52 | result = [] 53 | for Type in argv: 54 | result.append(Type(parsed_arguments[index])) 55 | index += 1 56 | while index != len(parsed_arguments): 57 | result.append(parsed_arguments[index]) 58 | index += 1 59 | return result 60 | 61 | 62 | def prepare_command_response(cmdName, contents): 63 | return ''.format(cmdName, contents) 64 | 65 | 66 | def prepare_event_response(name, payload): 67 | return ''.format(name, payload) 68 | 69 | 70 | def send_response(name, result, prepareFnPtr): 71 | """Writes result of an operation to client stream.""" 72 | import config 73 | res = json.dumps(result, ensure_ascii=False) 74 | if config.isDevelopmentBuild: 75 | log = logging.getLogger("update-logger") 76 | log.debug("{} Response: {}".format(name, res)) 77 | packet = prepareFnPtr(name, res) 78 | sys.stdout.write(packet) 79 | sys.stdout.flush() 80 | 81 | 82 | def value_is_reference(type): 83 | code = type.code 84 | return code == gdb.TYPE_CODE_PTR or code == gdb.TYPE_CODE_REF or code == gdb.TYPE_CODE_RVALUE_REF 85 | 86 | 87 | # When parsing closely related blocks, this is faster than gdb.parse_and_eval on average. 88 | 89 | 90 | def get_closest(frame, name): 91 | block = frame.block() 92 | while (not block.is_static) and (not block.superblock.is_global): 93 | for symbol in block: 94 | if symbol.name == name: 95 | return symbol.value(frame) 96 | block = block.superblock 97 | return None 98 | 99 | 100 | def get_global(frame, name): 101 | b = frame.block().global_block 102 | for symbol in b: 103 | if symbol.name == name: 104 | return symbol.value(frame) 105 | return None 106 | 107 | 108 | def get_static(frame, name): 109 | b = frame.block().static_block 110 | for symbol in b: 111 | if symbol.name == name: 112 | return symbol.value(frame) 113 | return None 114 | 115 | 116 | # Function that is able to utilize pretty printers, so that we can resolve 117 | # a value (which often does _not_ have the same expression path as regular structured types). 118 | # For instance used for std::tuple, which has a difficult member "layout", with ambiguous names. 119 | # Since ContentsOf command always takes a full "expression path", now it doesn't matter if the sub-paths of the expression 120 | # contain non-member names; because if there's a pretty printer that rename the members (like in std::tuple, it's [1], [2], ... [N]) 121 | # these will be found and traversed properly, anyway 122 | 123 | 124 | def resolve_gdb_value(value, components): 125 | it = value 126 | while len(components) > 0: 127 | if value_is_reference(it.type): 128 | it = it.referenced_value() 129 | pp = gdb.default_visualizer(it) 130 | component = components.pop(0) 131 | if pp is not None: 132 | found = False 133 | for child in pp.children(): 134 | (name, val) = child 135 | if component == name: 136 | it = val 137 | found = True 138 | break 139 | if not found: 140 | raise NotImplementedError() 141 | else: 142 | it = it[component] 143 | return it 144 | -------------------------------------------------------------------------------- /modules/python/rr_commands.py: -------------------------------------------------------------------------------- 1 | import gdb 2 | import midas_utils 3 | import config 4 | 5 | # serializes string input according to the gdb protocol 6 | def gdb_protocol_serialize(string): 7 | result = "" 8 | pos = 0 9 | for curr_char in string: 10 | result += format(ord(curr_char), '02x') 11 | return result 12 | 13 | # serializes command 14 | def prepare_command(command): 15 | return "maint packet qRRCmd:{}".format(gdb_protocol_serialize(command)) 16 | 17 | def gdb_protocol_deserialize(result): 18 | result = result.split("received: ")[1] 19 | return bytearray.fromhex(result.replace('"', "")).decode() 20 | 21 | 22 | class SetCheckpointRequest(gdb.Command): 23 | 24 | def __init__(self): 25 | super(SetCheckpointRequest, self).__init__("gdbjs-rr-checkpoint", gdb.COMMAND_USER) 26 | self.name = "rr-checkpoint" 27 | 28 | def invoke(self, arg, from_tty): 29 | frame = gdb.newest_frame() 30 | sal = frame.find_sal() 31 | where = "{}:{}".format(sal.symtab.filename, sal.line) 32 | cmd = prepare_command("checkpoint") 33 | cmd_with_params = "{}:{}".format(cmd, gdb_protocol_serialize(where)) 34 | result = gdb.execute(cmd_with_params, to_string=True) 35 | result = gdb_protocol_deserialize(result) 36 | if " at " in result: 37 | midas_utils.send_response(self.name, {"checkpoint-set": True }, midas_utils.prepare_command_response) 38 | else: 39 | midas_utils.send_response(self.name, {"checkpoint-set": False }, midas_utils.prepare_command_response) 40 | 41 | 42 | class InfoCheckpoints(gdb.Command): 43 | 44 | def __init__(self): 45 | super(InfoCheckpoints, self).__init__('gdbjs-rr-info-checkpoints', gdb.COMMAND_USER) 46 | self.name = "rr-info-checkpoints" 47 | 48 | def invoke(self, arg, from_tty): 49 | try: 50 | cmd = prepare_command("info checkpoints") 51 | result = gdb.execute(cmd, to_string=True) 52 | result = gdb_protocol_deserialize(result) 53 | fromRepl = bool(arg) 54 | if fromRepl: 55 | midas_utils.send_response(self.name, result.replace("\n", "\n\r"), midas_utils.prepare_command_response) 56 | else: 57 | try: 58 | cps = result.splitlines()[1:] 59 | result = [] 60 | for cp_line in cps: 61 | [id, when, where] = cp_line.split("\t") 62 | sep = where.rfind(":") 63 | path = where[0:sep] 64 | line = where[(sep+1):] 65 | result.append({"id": id, "when": when, "where": { "path": path, "line": line }}) 66 | midas_utils.send_response(self.name, {"checkpoints": result }, midas_utils.prepare_command_response) 67 | except Exception as e: 68 | midas_utils.send_response(self.name, {"checkpoints": [] }, midas_utils.prepare_command_response) 69 | raise e 70 | except Exception as e: 71 | config.error_logger().error("Failed to invoke info checkpoints command: {}".format(e)) 72 | raise e 73 | 74 | class DeleteCheckpoint(gdb.Command): 75 | 76 | def __init__(self): 77 | super(DeleteCheckpoint, self).__init__('gdbjs-rr-delete-checkpoint', gdb.COMMAND_USER) 78 | self.name = "rr-delete-checkpoints" 79 | 80 | def invoke(self, arg, from_tty): 81 | # we must stop here; otherwise rr crashes. 82 | if len(arg) == 0: 83 | midas_utils.send_response(self.name, False, midas_utils.prepare_command_response) 84 | cmd = prepare_command("delete checkpoint") 85 | cmd_with_param = "{}:{}".format(cmd, gdb_protocol_serialize(arg)) 86 | result = gdb.execute(cmd_with_param, to_string=True) 87 | midas_utils.send_response(self.name, True, midas_utils.prepare_command_response) 88 | 89 | 90 | class RRWhen(gdb.Command): 91 | 92 | def __init__(self): 93 | super(RRWhen, self).__init__("gdbjs-rr-when", gdb.COMMAND_USER) 94 | self.name = "rr-when" 95 | 96 | def invoke(self, arg, from_tty): 97 | cmd = prepare_command("when") 98 | result = gdb.execute(cmd, to_string=True) 99 | result = gdb_protocol_deserialize(result) 100 | if arg is None or len(arg) == 0: 101 | first_whitespace = result.rfind(" ") 102 | evt = result[(first_whitespace+1):] 103 | midas_utils.send_response(self.name, { "event": evt }, midas_utils.prepare_command_response) 104 | else: 105 | midas_utils.send_response(self.name, result, midas_utils.prepare_command_response) -------------------------------------------------------------------------------- /modules/python/rust.py: -------------------------------------------------------------------------------- 1 | import gdb 2 | import midas_utils 3 | import subprocess 4 | import os 5 | 6 | def rustSysRoot(): 7 | res = subprocess.run(["rustc", "--print=sysroot"], capture_output=True, text=True) 8 | return res.stdout.strip() 9 | 10 | def rustPythonModuleDir(rustcsysroot): 11 | return f"{rustcsysroot}/lib/rustlib/etc" 12 | 13 | def rustSrc(rustcsysroot): 14 | return ("/builddir/build/BUILD/rustc-1.69.0-src/library/", f"{rustcsysroot}/lib/rustlib/rust/library/") 15 | 16 | 17 | 18 | class SetupRustGdb(gdb.Command): 19 | 20 | def __init__(self): 21 | super(SetupRustGdb, self).__init__("gdbjs-rust", gdb.COMMAND_USER) 22 | self.name = "r" 23 | 24 | def invoke(self, args, from_tty): 25 | 26 | sysroot = rustSysRoot() 27 | gdb_mod_dir = rustPythonModuleDir(sysroot) 28 | 29 | if "PYTHONPATH" in os.environ: 30 | print(f"PYTHONPATH: {os.environ['PYTHONPATH']}") 31 | os.environ["PYTHONPATH"] = f"{os.environ['PYTHONPATH']}:{gdb_mod_dir}" 32 | else: 33 | print(f"Setting PYTHONPATH TO: {gdb_mod_dir}") 34 | os.environ["PYTHONPATH"] = gdb_mod_dir 35 | (std_build, std_src) = rustSrc(sysroot) 36 | print(f"--directory {gdb_mod_dir}") 37 | gdb.execute(f"directory {gdb_mod_dir}") 38 | print(f"add-auto-load-safe-path {gdb_mod_dir}") 39 | gdb.execute(f"add-auto-load-safe-path {gdb_mod_dir}") 40 | print(f"set substitute-path {std_build} {std_src}") 41 | gdb.execute(f"set substitute-path {std_build} {std_src}") 42 | 43 | SetupRustGdb() -------------------------------------------------------------------------------- /modules/spawn.js: -------------------------------------------------------------------------------- 1 | const { getExtensionPathOf } = require("./utils/sysutils"); 2 | 3 | class SpawnConfig { 4 | /** @type {string} */ 5 | path; 6 | /** @type {string[]} */ 7 | options; 8 | /** @type {string} */ 9 | cwd; 10 | /** @type {string[]} */ 11 | setupCommands; 12 | /** @type {string} */ 13 | tracedProgramFilePath; 14 | 15 | /**@type {{path: string, closeTerminalOnEndOfSession: boolean, endSessionOnTerminalExit?: boolean }} */ 16 | externalConsole; 17 | 18 | /** @type {boolean} */ 19 | ignoreStandardLibrary; 20 | 21 | /** @type { string } - Path to the directory where Midas should look for DAP Pretty printers. */ 22 | prettyPrinterPath; 23 | 24 | /** 25 | * @param {*} launchJson - The settings in launch.json 26 | */ 27 | constructor(launchJson) { 28 | this.path = launchJson.gdbPath; 29 | const cwd = launchJson.cwd ? launchJson.cwd : null; 30 | this.cwd = cwd; 31 | this.options = launchJson.gdbOptions ?? []; 32 | this.setupCommands = launchJson.setupCommands; 33 | this.externalConsole = launchJson.externalConsole; 34 | this.trace = launchJson["trace"]; 35 | this.prettyPrinterPath = launchJson.prettyPrinterPath; 36 | this.ignoreStandardLibrary = launchJson["ignoreStandardLibrary"]; 37 | } 38 | 39 | get type() { 40 | return "midas-gdb"; 41 | } 42 | 43 | isRRSession() { 44 | return false; 45 | } 46 | 47 | disposeOnExit() { 48 | return (this.externalConsole ?? { closeTerminalOnEndOfSession: true }).closeTerminalOnEndOfSession; 49 | } 50 | 51 | get spawnType() { 52 | return null; 53 | } 54 | 55 | /** @returns { Promise } */ 56 | getCppStandardLibraryFileList() { 57 | if (!this.ignoreStandardLibrary) { 58 | return Promise.resolve([]); 59 | } 60 | return new Promise((resolve, reject) => { 61 | require("fs").readFile(getExtensionPathOf("/modules/c++stdlib.ignore"), "utf-8", (err, data) => { 62 | if (err) { 63 | reject(err); 64 | } 65 | resolve(data.split("\n").filter((line) => line.length > 0)); 66 | }); 67 | }); 68 | } 69 | } 70 | 71 | class LaunchSpawnConfig extends SpawnConfig { 72 | /** @type {string[]} */ 73 | inferiorArgs; 74 | 75 | /** 76 | * @param {*} launchJson - The settings in launch.json 77 | */ 78 | constructor(launchJson) { 79 | super(launchJson); 80 | this.inferiorArgs = launchJson.args ?? []; 81 | } 82 | 83 | get type() { 84 | return "midas-gdb"; 85 | } 86 | } 87 | 88 | class AttachSpawnConfig extends SpawnConfig { 89 | pid; 90 | /** 91 | * @param {*} launchJson - The settings in launch.json 92 | */ 93 | constructor(launchJson) { 94 | super(launchJson); 95 | this.pid = launchJson.pid; 96 | } 97 | 98 | get type() { 99 | return "midas-gdb"; 100 | } 101 | } 102 | 103 | class RRSpawnConfig extends SpawnConfig { 104 | serverAddress; 105 | constructor(launchJson) { 106 | super(launchJson); 107 | this.rrOptions = launchJson.rrOptions ?? []; 108 | this.serverAddress = launchJson.serverAddress; 109 | } 110 | 111 | get type() { 112 | return "midas-rr"; 113 | } 114 | 115 | isRRSession() { 116 | return true; 117 | } 118 | 119 | get spawnType() { 120 | return "rr"; 121 | } 122 | } 123 | 124 | class MdbSpawnConfig { 125 | // A parameter that informs mdb that the process that actually spawned it, was RR 126 | // which means MDB has to configure itself properly (in order to alleviate RR recording and be able to debug MDB easier.) 127 | // For instance, when recording Midas debugger under RR, we don't throw the entire systems thread resources in the thread pool 128 | // but just use 1 or 2 (because adding more than that under RR is pointless) 129 | #mdbRecordedUnderRr; 130 | 131 | constructor(config) { 132 | this.path = config.mdbPath ?? "mdb"; 133 | this.options = config.dbgArgs ?? []; 134 | this.debug = config.debug; 135 | this.#mdbRecordedUnderRr = config?.RRSession; 136 | this.isReplay = config?.attachArgs?.type == "rr"; 137 | this.prettyPrinterPath = config?.prettyPrinterPath; 138 | if (config.childConfiguration) { 139 | this.processId = config.childConfiguration.processId; 140 | this.autoAttached = true; 141 | } 142 | } 143 | 144 | get isBeingRecorded() { 145 | return this.#mdbRecordedUnderRr; 146 | } 147 | 148 | get attached() { 149 | return this.autoAttached ?? false; 150 | } 151 | } 152 | 153 | module.exports = { 154 | // Base type of all spawn configurations 155 | SpawnConfig, 156 | LaunchSpawnConfig, 157 | AttachSpawnConfig, 158 | RRSpawnConfig, 159 | MdbSpawnConfig, 160 | }; 161 | -------------------------------------------------------------------------------- /modules/terminalInterface.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require("child_process"); 2 | // Make our externally spawned consoles interchangeable with VSCode terminals 3 | class TerminalInterface { 4 | #process; 5 | #tty; 6 | #pid; 7 | #ppid; 8 | #children; 9 | /** 10 | * @param { import("child_process").ChildProcessWithoutNullStreams } process 11 | * @param { { path: string } } tty 12 | * @param { number } pid 13 | */ 14 | constructor(process, tty = null, pid = null, ppid, children) { 15 | this.#process = process; 16 | this.#tty = tty; 17 | this.#pid = pid; 18 | this.#ppid = ppid; 19 | this.#children = children; 20 | } 21 | 22 | get pid() { 23 | return this.#pid; 24 | } 25 | 26 | // Kills terminal 27 | dispose() { 28 | if (this.#ppid) { 29 | try { 30 | execSync(`kill ${this.#ppid}`); 31 | } catch (err) { 32 | console.log(`Process ${this.#ppid} is already dead`); 33 | } 34 | } 35 | this.#process.kill("SIGTERM"); 36 | } 37 | 38 | disposeChildren() { 39 | try { 40 | execSync(`kill ${this.#children}`); 41 | } catch (err) { 42 | console.log(`Process ${this.#ppid} is already dead`); 43 | } 44 | } 45 | 46 | registerExitAction(action) { 47 | this.#process.on("exit", action); 48 | } 49 | 50 | get tty() { 51 | return this.#tty; 52 | } 53 | } 54 | 55 | module.exports = { 56 | TerminalInterface, 57 | }; 58 | -------------------------------------------------------------------------------- /modules/ui/checkpoints/checkpoints.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | const vscode = require("vscode"); 3 | const { CustomRequests } = require("../../constants"); 4 | const { registerCommand } = require("vscode").commands; 5 | const { UI_REQUESTS, UI_MESSAGES } = require("./ui_protocol"); 6 | class CheckpointsViewProvider { 7 | /** @type {vscode.WebviewView} */ 8 | #view = null; 9 | #extensionUri; 10 | 11 | /** 12 | * @param {vscode.ExtensionContext} extensionContext 13 | */ 14 | constructor(extensionContext) { 15 | this.#extensionUri = extensionContext.extensionUri; 16 | this.checkpointIdToNameMap = new Map(); 17 | 18 | let setCheckpoint = registerCommand("midas.set-checkpoint", () => { 19 | vscode.debug.activeDebugSession.customRequest(CustomRequests.SetCheckpoint); 20 | }); 21 | 22 | let clearCheckpoints = registerCommand("midas.clear-checkpoints", () => { 23 | vscode.debug.activeDebugSession.customRequest(CustomRequests.ClearCheckpoints); 24 | }); 25 | 26 | extensionContext.subscriptions.push(setCheckpoint, clearCheckpoints); 27 | } 28 | 29 | /** 30 | * @returns {string} 31 | */ 32 | get type() { 33 | return "midas.checkpoints-ui"; 34 | } 35 | 36 | updateCheckpoints(checkpoints, show = true) { 37 | if (this.#view) { 38 | if (show) this.#view.show?.(true); 39 | for (let cp of checkpoints) { 40 | cp.name = this.checkpointIdToNameMap.get(cp.id) ?? `${cp.where.path}:${cp.where.line}`; 41 | } 42 | this.#view.webview.postMessage({ type: UI_MESSAGES.UpdateCheckpoints, payload: checkpoints }); 43 | } 44 | } 45 | 46 | /** 47 | * Gets resource `resource` from the Checkpoints UI module. 48 | * @param {string} resource 49 | * @returns {vscode.Uri} 50 | */ 51 | resourceUri(resource) { 52 | return vscode.Uri.joinPath(this.#extensionUri, "modules/ui/checkpoints", resource); 53 | } 54 | 55 | /** 56 | * Revolves a webview view. 57 | * 58 | * `resolveWebviewView` is called when a view first becomes visible. This may happen when the view is 59 | * first loaded or when the user hides and then shows a view again. 60 | * @param {vscode.WebviewView} webviewView Webview view to restore. The provider should take ownership of this view. The 61 | * provider must set the webview's `.html` and hook up all webview events it is interested in. 62 | * @param {vscode.WebviewViewResolveContext} context Additional metadata about the view being resolved. 63 | * @param {vscode.CancellationToken} token Cancellation token indicating that the view being provided is no longer needed. 64 | * 65 | * @return {Promise} 66 | */ 67 | // eslint-disable-next-line no-unused-vars 68 | async resolveWebviewView(webviewView, context, token) { 69 | this.#view = webviewView; 70 | this.#view.onDidDispose(() => { 71 | this.checkpointIdToNameMap.clear(); 72 | }); 73 | webviewView.webview.options = { 74 | // Allow scripts in the webview 75 | enableScripts: true, 76 | localResourceRoots: [this.#extensionUri], 77 | }; 78 | 79 | webviewView.webview.html = this.#createHTMLForWebView(webviewView.webview); 80 | 81 | webviewView.webview.onDidReceiveMessage(async (data) => { 82 | switch (data.type) { 83 | case UI_REQUESTS.DeleteCheckpoint: { 84 | this.checkpointIdToNameMap.delete(data.value); 85 | vscode.debug.activeDebugSession.customRequest(CustomRequests.DeleteCheckpoint, data.value); 86 | break; 87 | } 88 | case UI_REQUESTS.RunToCheckpoint: 89 | vscode.debug.activeDebugSession.customRequest(CustomRequests.RestartCheckpoint, data.value); 90 | break; 91 | case UI_REQUESTS.NameCheckpoint: 92 | const { checkpointId, name } = data.value; 93 | this.checkpointIdToNameMap.set(checkpointId, name); 94 | break; 95 | case UI_REQUESTS.GotoSourceLoc: 96 | const { path, line } = data.value; 97 | const doc = await vscode.workspace.openTextDocument(path); 98 | const editor = await vscode.window.showTextDocument(doc, { preview: false }); 99 | const position = new vscode.Position(line, 0); 100 | editor.revealRange(new vscode.Range(position, position), vscode.TextEditorRevealType.InCenter); 101 | break; 102 | } 103 | }); 104 | } 105 | 106 | /** 107 | * @param {vscode.Webview} webview 108 | * @returns 109 | */ 110 | #createHTMLForWebView(webview) { 111 | // Get the local path to main script run in the webview, then convert it to a uri we can use in the webview. 112 | const scriptUri = webview.asWebviewUri(this.resourceUri("main.js")); 113 | // Do the same for the stylesheet. 114 | const styleResetUri = webview.asWebviewUri(this.resourceUri("reset.css")); 115 | const styleVSCodeUri = webview.asWebviewUri(this.resourceUri("vscode.css")); 116 | const styleMainUri = webview.asWebviewUri(this.resourceUri("main.css")); 117 | const MessageProtocol = webview.asWebviewUri(this.resourceUri("ui_protocol.js")); 118 | const codiconsUri = webview.asWebviewUri( 119 | vscode.Uri.joinPath(this.#extensionUri, "node_modules", "@vscode/codicons", "dist", "codicon.css"), 120 | ); 121 | 122 | // Use a nonce to only allow a specific script to be run. 123 | const nonce = getNonce(); 124 | const serialize = JSON.stringify({ UI_MESSAGES, UI_REQUESTS }); 125 | return ` 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 |
141 |
142 |
143 | 144 | 145 | 148 | 149 | `; 150 | } 151 | } 152 | 153 | function getNonce() { 154 | let text = ""; 155 | const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 156 | for (let i = 0; i < 32; i++) { 157 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 158 | } 159 | return text; 160 | } 161 | 162 | module.exports = { 163 | CheckpointsViewProvider, 164 | }; 165 | -------------------------------------------------------------------------------- /modules/ui/checkpoints/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: transparent; 3 | } 4 | 5 | .color-list { 6 | list-style: none; 7 | padding: 0; 8 | } 9 | 10 | .color-entry { 11 | width: 100%; 12 | display: flex; 13 | margin-bottom: 0.4em; 14 | border: 1px solid var(--vscode-input-border); 15 | } 16 | 17 | .color-preview { 18 | width: 2em; 19 | height: 2em; 20 | } 21 | 22 | .color-preview:hover { 23 | outline: inset white; 24 | } 25 | 26 | .color-input { 27 | display: block; 28 | flex: 1; 29 | width: 100%; 30 | color: var(--vscode-input-foreground); 31 | background-color: var(--vscode-input-background); 32 | border: none; 33 | padding: 0 0.6em; 34 | } 35 | 36 | .add-color-button { 37 | display: block; 38 | border: none; 39 | margin: 0 auto; 40 | } 41 | -------------------------------------------------------------------------------- /modules/ui/checkpoints/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | // @ts-nocheck 3 | // eslint-disable-next-line no-unused-vars 4 | function setupUI(protocol) { 5 | const { UI_MESSAGES, UI_REQUESTS } = JSON.parse(protocol); 6 | (function () { 7 | const vscode = acquireVsCodeApi(); 8 | function create_row(container, cp) { 9 | const checkpointEventNumber = document.createElement("span"); 10 | checkpointEventNumber.textContent = `${cp.when}`; 11 | checkpointEventNumber.className = "checkpoints-list-when"; 12 | container.appendChild(checkpointEventNumber); 13 | 14 | const checkpointName = document.createElement("span"); 15 | checkpointName.textContent = cp.name; 16 | checkpointName.className = "file-path"; 17 | checkpointName.addEventListener("click", () => { 18 | const sourceLocPath = cp.where.path.split(" at ")[1]; 19 | 20 | if (sourceLocPath) 21 | vscode.postMessage({ type: UI_REQUESTS.GotoSourceLoc, value: { path: sourceLocPath, line: cp.where.line } }); 22 | }); 23 | 24 | checkpointName.addEventListener("keydown", (event) => { 25 | if (event.keyCode === 13) { 26 | checkpointName.contentEditable = false; 27 | event.target.blur(); 28 | } 29 | }); 30 | 31 | checkpointName.addEventListener("blur", (event) => { 32 | const payload = { checkpointId: cp.id, name: event.target.textContent }; 33 | vscode.postMessage({ type: UI_MESSAGES.NameCheckpoint, value: payload }); 34 | }); 35 | 36 | checkpointName.id = `cp-${cp.id}`; 37 | 38 | container.appendChild(checkpointName); 39 | 40 | const actionBar = document.createElement("div"); 41 | actionBar.className = "checkpoints-action-bar"; 42 | let actionContainer = document.createElement("ul"); 43 | 44 | const editButton = document.createElement("li"); 45 | editButton.className = "row-button codicon codicon-edit"; 46 | 47 | editButton.addEventListener("click", () => { 48 | checkpointName.contentEditable = true; 49 | checkpointName.focus(); 50 | }); 51 | 52 | const playButton = document.createElement("li"); 53 | playButton.className = "row-button codicon codicon-debug-continue"; 54 | 55 | playButton.addEventListener("click", () => { 56 | vscode.postMessage({ type: UI_REQUESTS.RunToCheckpoint, value: container.dataset.checkpointId }); 57 | }); 58 | 59 | const removeButton = document.createElement("li"); 60 | removeButton.className = "row-button codicon codicon-chrome-close"; 61 | 62 | removeButton.addEventListener("click", () => { 63 | vscode.postMessage({ type: UI_REQUESTS.DeleteCheckpoint, value: container.dataset.checkpointId }); 64 | }); 65 | 66 | actionContainer.appendChild(editButton); 67 | actionContainer.appendChild(playButton); 68 | actionContainer.appendChild(removeButton); 69 | actionBar.appendChild(actionContainer); 70 | container.appendChild(actionBar); 71 | } 72 | 73 | // Handle messages sent from the extension to the webview 74 | window.addEventListener("message", (event) => { 75 | const message = event.data; // The json data that the extension sent 76 | switch (message.type) { 77 | case UI_MESSAGES.ClearCheckpoints: { 78 | updateCheckpointsList([]); 79 | break; 80 | } 81 | case UI_MESSAGES.RemovedCheckpoint: { 82 | removeCheckpoint(message.payload); 83 | break; 84 | } 85 | case UI_MESSAGES.UpdateCheckpoints: { 86 | updateCheckpointsList(message.payload); 87 | break; 88 | } 89 | } 90 | }); 91 | 92 | /** 93 | * @param {Array} checkpoints 94 | */ 95 | function updateCheckpointsList(checkpoints) { 96 | const cp_list = document.querySelector(".checkpoints-list-rows"); 97 | cp_list.textContent = ""; 98 | let idx = 0; 99 | for (const cp of checkpoints) { 100 | const row = document.createElement("div"); 101 | row.className = "checkpoints-list-row"; 102 | row.dataIndex = idx; 103 | row.dataLastElement = idx == checkpoints.length - 1; 104 | row.dataset.index = idx; 105 | row.dataset.checkpointId = cp.id; 106 | row.ariaPosInSet = idx + 1; 107 | row.id = `cp-row-${cp.id}`; 108 | create_row(row, cp); 109 | cp_list.appendChild(row); 110 | 111 | idx += 1; 112 | } 113 | } 114 | 115 | function removeCheckpoint(checkpointId) { 116 | checkpoints = checkpoints.filter((cp) => cp.id != checkpointId); 117 | updateCheckpointsList(checkpoints); 118 | } 119 | })(); 120 | } 121 | 122 | // This script will be run within the webview itself 123 | // It cannot access the main VS Code APIs directly. 124 | -------------------------------------------------------------------------------- /modules/ui/checkpoints/reset.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | font-size: 13px; 4 | } 5 | 6 | *, 7 | *:before, 8 | *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | ol, 21 | ul { 22 | margin: 0; 23 | padding: 0; 24 | font-weight: normal; 25 | } 26 | 27 | img { 28 | max-width: 100%; 29 | height: auto; 30 | } 31 | -------------------------------------------------------------------------------- /modules/ui/checkpoints/ui_protocol.js: -------------------------------------------------------------------------------- 1 | const UI_MESSAGES = { 2 | AddCheckpoint: "add-checkpoint", 3 | ClearCheckpoints: "clear-checkpoint", 4 | RemovedCheckpoint: "removed-checkpoint", 5 | UpdateCheckpoints: "update-checkpoints", 6 | NameCheckpoint: "name-checkpoint", 7 | }; 8 | 9 | const UI_REQUESTS = { 10 | DeleteCheckpoint: "delete-checkpoint", 11 | RunToCheckpoint: "run-to-checkpoint", 12 | NameCheckpoint: "name-checkpoint", 13 | GotoSourceLoc: "goto-source-location", 14 | }; 15 | 16 | module.exports.UI_MESSAGES = UI_MESSAGES; 17 | module.exports.UI_REQUESTS = UI_REQUESTS; 18 | -------------------------------------------------------------------------------- /modules/ui/checkpoints/vscode.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --input-padding-vertical: 6px; 3 | --input-padding-horizontal: 4px; 4 | --input-margin-vertical: 4px; 5 | --input-margin-horizontal: 0; 6 | } 7 | 8 | body { 9 | padding: 0; 10 | color: var(--vscode-foreground); 11 | font-size: var(--vscode-font-size); 12 | font-weight: var(--vscode-font-weight); 13 | font-family: var(--vscode-font-family); 14 | background-color: var(--vscode-editor-background); 15 | } 16 | 17 | ol, 18 | ul { 19 | padding-left: var(--container-paddding); 20 | } 21 | 22 | body > *, 23 | form > * { 24 | margin-block-start: var(--input-margin-vertical); 25 | margin-block-end: var(--input-margin-vertical); 26 | } 27 | 28 | *:focus { 29 | outline-color: var(--vscode-focusBorder) !important; 30 | } 31 | 32 | a { 33 | color: var(--vscode-textLink-foreground); 34 | } 35 | 36 | a:hover, 37 | a:active { 38 | color: var(--vscode-textLink-activeForeground); 39 | } 40 | 41 | code { 42 | font-size: var(--vscode-editor-font-size); 43 | font-family: var(--vscode-editor-font-family); 44 | } 45 | 46 | button { 47 | border: none; 48 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 49 | width: 100%; 50 | text-align: center; 51 | outline: 1px solid transparent; 52 | outline-offset: 2px !important; 53 | color: var(--vscode-button-foreground); 54 | background: var(--vscode-button-background); 55 | } 56 | 57 | button:hover { 58 | cursor: pointer; 59 | background: var(--vscode-button-hoverBackground); 60 | } 61 | 62 | button:focus { 63 | outline-color: var(--vscode-focusBorder); 64 | } 65 | 66 | button.secondary { 67 | color: var(--vscode-button-secondaryForeground); 68 | background: var(--vscode-button-secondaryBackground); 69 | } 70 | 71 | button.secondary:hover { 72 | background: var(--vscode-button-secondaryHoverBackground); 73 | } 74 | 75 | input:not([type="checkbox"]), 76 | textarea { 77 | display: block; 78 | width: 100%; 79 | border: none; 80 | font-family: var(--vscode-font-family); 81 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 82 | color: var(--vscode-input-foreground); 83 | outline-color: var(--vscode-input-border); 84 | background-color: var(--vscode-input-background); 85 | } 86 | 87 | input::placeholder, 88 | textarea::placeholder { 89 | color: var(--vscode-input-placeholderForeground); 90 | } 91 | 92 | .checkpoints-list-row.selected { 93 | background-color: var(--vscode-selection-background); 94 | } 95 | 96 | .checkpoints-list-row.selected:hover { 97 | background-color: var(--vscode-inputOption-hoverBackground); 98 | } 99 | 100 | .checkpoints-list-row.selected { 101 | background-color: var(--vscode-inputOption-hoverBackground); 102 | } 103 | 104 | .checkpoints-list-row.selected.focused { 105 | background-color: #c4d9b1; 106 | } 107 | 108 | .checkpoints-list-row.selected.focused { 109 | color: var(--vscode-inputOption-hoverBackground); 110 | } 111 | 112 | .checkpoints-list-row.selected { 113 | background-color: var(--vscode-inputOption-hoverBackground); 114 | } 115 | 116 | .checkpoints-list-row.selected:hover { 117 | background-color: var(--vscode-inputOption-hoverBackground); 118 | } 119 | 120 | .checkpoints-list-row:hover:not(.selected):not(.focused) { 121 | background-color: var(--vscode-inputOption-hoverBackground); 122 | } 123 | 124 | .file-path { 125 | color: var(--vscode-editor-foreground); 126 | opacity: 0.7; 127 | margin-left: 0.9em; 128 | flex: 1; 129 | text-overflow: ellipsis; 130 | overflow: hidden; 131 | } 132 | 133 | .checkpoints-list-row { 134 | display: flex; 135 | box-sizing: border-box; 136 | overflow: hidden; 137 | width: 100%; 138 | flex-wrap: wrap; 139 | align-items: center; 140 | justify-content: center; 141 | margin-bottom: 2px; 142 | } 143 | 144 | .checkpoints-list-row .row-button { 145 | visibility: hidden; 146 | } 147 | 148 | .checkpoints-list-row:hover .row-button { 149 | visibility: visible; 150 | } 151 | 152 | .checkpoints-list-when { 153 | flex: 0 0 50px; 154 | user-select: none; 155 | } 156 | 157 | #icons { 158 | display: flex; 159 | flex-wrap: wrap; 160 | align-items: center; 161 | justify-content: center; 162 | } 163 | 164 | .icon { 165 | width: 100px; 166 | padding: 20px; 167 | font-size: 28px; 168 | display: flex; 169 | flex-direction: column; 170 | text-align: center; 171 | } 172 | 173 | .codicon { 174 | padding: 5px 5px; 175 | margin-right: 5px; 176 | font-size: 16px; 177 | display: flex; 178 | } 179 | 180 | .checkpoints-count-badge { 181 | padding: 3px 6px; 182 | border-radius: 11px; 183 | font-size: 11px; 184 | min-width: 18px; 185 | min-height: 18px; 186 | line-height: 11px; 187 | font-weight: 400; 188 | text-align: center; 189 | display: inline-block; 190 | box-sizing: border-box; 191 | background-color: var(--vscode-badge-background); 192 | color: var(--vscode-foreground-background); 193 | } 194 | 195 | .checkpoints-action-bar { 196 | display: flex; 197 | align-items: center; 198 | text-align: center; 199 | } 200 | 201 | .row-button { 202 | border-radius: 2px; 203 | font-size: 11px; 204 | min-width: 18px; 205 | min-height: 18px; 206 | line-height: 11px; 207 | font-weight: 400; 208 | text-align: center; 209 | } 210 | 211 | .row-button:hover { 212 | background-color: var(--vscode-widget-shadow); 213 | } 214 | 215 | .file-path { 216 | opacity: 0.7; 217 | text-overflow: ellipsis; 218 | overflow: hidden; 219 | flex: 1; 220 | user-select: none; 221 | } 222 | -------------------------------------------------------------------------------- /modules/utils/kernelsettings.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | // *nix utility functions for reading system/kernel variables 4 | 5 | /** 6 | * Read ptrace setting 7 | * @returns {number} 8 | */ 9 | function readPtraceScope() { 10 | let data = fs.readFileSync("/proc/sys/kernel/yama/ptrace_scope"); 11 | const setting = data.toString().trim(); 12 | const n = Number.parseInt(setting); 13 | if (Number.isNaN(n)) { 14 | throw new Error("Failed to read Yama security module setting"); 15 | } 16 | return n; 17 | } 18 | 19 | /** 20 | * Read kernel.perf_event_paranoid setting 21 | * @returns {number} 22 | */ 23 | function readPerfEventParanoid() { 24 | let data = fs.readFileSync("/proc/sys/kernel/perf_event_paranoid"); 25 | const setting = data.toString().trim(); 26 | const n = Number.parseInt(setting); 27 | if (Number.isNaN(n)) { 28 | throw new Error("Failed to read Perf Event Paranoid setting"); 29 | } 30 | return n; 31 | } 32 | 33 | module.exports = { 34 | readPtraceScope, 35 | readPerfEventParanoid, 36 | }; 37 | -------------------------------------------------------------------------------- /modules/utils/netutils.js: -------------------------------------------------------------------------------- 1 | const net = require("net"); 2 | 3 | /** 4 | * @param { net.Server } server 5 | * @param { number } portToCheck 6 | * @returns { Promise } 7 | */ 8 | function getPort(server, portToCheck) { 9 | return new Promise((resolve, reject) => { 10 | server.on("error", reject); 11 | server.listen({ port: portToCheck, host: "127.0.0.1" }, () => { 12 | const addressInfo = server.address(); 13 | resolve(addressInfo.port); 14 | }); 15 | }); 16 | } 17 | 18 | // todo(simon): create some random port generator, when serverAddress is not set in launch config 19 | async function getFreeRandomPort(portRange = { begin: 50505, end: 65000 }) { 20 | const server = net.createServer(); 21 | server.unref(); 22 | for (let port = portRange.begin; port < portRange.end; port++) { 23 | try { 24 | let p = await getPort(server, port); 25 | console.log(`port found on: ${p}`); 26 | server.close(); 27 | return p; 28 | } catch (err) { 29 | console.log(`port ${port} already taken`); 30 | } 31 | } 32 | throw new Error("Could not find port"); 33 | } 34 | 35 | module.exports = { 36 | getPort, 37 | getFreeRandomPort, 38 | }; 39 | -------------------------------------------------------------------------------- /modules/utils/releaseNotes.js: -------------------------------------------------------------------------------- 1 | "use strict;"; 2 | 3 | const vscode = require("vscode"); 4 | const path = require("path"); 5 | 6 | const versionRE = /([0-9]+)\.([0-9]+)\.([0-9]+)/; 7 | const notesRE = /(^# .*$)?(?:^##(?.*)$\s*(?(?:[\S\s](?!##))*))*/gm; 8 | 9 | class Version { 10 | constructor(major, minor, patch) { 11 | this.major = major; 12 | this.minor = minor; 13 | this.patch = patch; 14 | this.pre = +minor % 2 == 1; 15 | } 16 | 17 | toString() { 18 | return this.pre 19 | ? `${this.major}.pre${+this.minor + 1}.patch${this.patch}` 20 | : `${this.major}.${this.minor}.${this.patch}`; 21 | } 22 | 23 | includePreRelease() { 24 | return new Version(this.major, !this.pre ? this.minor - 1 : this.minor, 0); 25 | } 26 | } 27 | 28 | function toVersion(version) { 29 | const [, major, minor, patch] = versionRE.exec(version) ?? [0, 0, 0, 0]; 30 | return new Version(major, minor, patch); 31 | } 32 | 33 | function compare(version, other) { 34 | let order = ["major", "minor", "patch"]; 35 | let res = 0; 36 | for (let ord of order) { 37 | res = Math.sign(version[ord] - other[ord]); 38 | if (res) { 39 | return res; 40 | } 41 | } 42 | 43 | return res; 44 | } 45 | 46 | async function findReleaseNotes(folder, version) { 47 | let decoder = new TextDecoder(); 48 | const preVersion = version.includePreRelease(); 49 | let files = (await vscode.workspace.fs.readDirectory(folder)) 50 | .filter(([name, type]) => { 51 | if (type != vscode.FileType.File) { 52 | return false; 53 | } 54 | 55 | const fileVersion = toVersion(name); 56 | return compare(version, fileVersion) > -1 && compare(fileVersion, preVersion) > -1; 57 | }) 58 | .map(([fileName]) => fileName); 59 | 60 | files.sort((x, y) => compare(toVersion(y), toVersion(x))); 61 | 62 | let content = files.map(async (fileName) => { 63 | let filePath = vscode.Uri.joinPath(folder, fileName); 64 | let notes = decoder.decode(await vscode.workspace.fs.readFile(filePath)); 65 | let version = toVersion(fileName); 66 | return { version, notes }; 67 | }); 68 | 69 | return await Promise.all(content); 70 | } 71 | 72 | function addReleaseNote(entry, notes) { 73 | for (let match of [...notes.matchAll(notesRE)]) { 74 | const [, , category, note] = match; 75 | if (category && note) { 76 | entry[category] = entry[category] || []; 77 | entry[category].unshift(note); 78 | } 79 | } 80 | 81 | console.log(JSON.stringify(entry)); 82 | 83 | return entry; 84 | } 85 | 86 | async function getReleaseNotes() { 87 | let extension = vscode.extensions.getExtension("farrese.midas"); 88 | let version = toVersion(extension.packageJSON.version); 89 | let extensionPath = extension.extensionUri.fsPath; 90 | let notesPath = vscode.Uri.file(path.normalize(path.join(extensionPath, "release_notes"))); 91 | let entries = await findReleaseNotes(notesPath, version); 92 | 93 | let releaseNotes = entries.reduceRight( 94 | (accumulator, current) => { 95 | if (compare(current.version, accumulator.version) > 0) { 96 | if (Object.keys(accumulator.entry).length && !accumulator.version.pre) { 97 | accumulator.notes.unshift({ ...accumulator.entry, version: accumulator.version }); 98 | accumulator.entry = {}; 99 | } 100 | 101 | accumulator.version = current.version; 102 | } 103 | 104 | accumulator.entry = addReleaseNote(accumulator.entry, current.notes); 105 | 106 | return accumulator; 107 | }, 108 | { notes: [], entry: {}, version: toVersion("0.0.0") }, 109 | ); 110 | 111 | if (Object.keys(releaseNotes.entry)) { 112 | releaseNotes.notes.unshift({ ...releaseNotes.entry, version }); 113 | releaseNotes.entry = {}; 114 | } 115 | 116 | return renderReleaseNotes(releaseNotes.notes); 117 | } 118 | 119 | function render(version, notes) { 120 | let renderedNotes = ""; 121 | 122 | let keys = Object.keys(notes); 123 | keys.sort().reverse(); 124 | for (let key of keys) { 125 | renderedNotes = `\n### ${key} 126 | 127 | ${notes[key].join("\n")}${renderedNotes}`; 128 | } 129 | 130 | return `## ${version.pre ? "Pre-" : ""}Release ${version} 131 | 132 | ${renderedNotes}`; 133 | } 134 | 135 | function renderReleaseNotes(notes) { 136 | let renderedNotes = ""; 137 | for (let releaseNote of notes) { 138 | let { version, ...rest } = releaseNote; 139 | let keys = Object.keys(releaseNote); 140 | keys.sort(); 141 | renderedNotes = `${renderedNotes}${render(version, rest)}`; 142 | } 143 | 144 | return renderedNotes; 145 | } 146 | 147 | module.exports = { getReleaseNotes }; 148 | -------------------------------------------------------------------------------- /modules/utils/rrutils.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | const path = require("path"); 3 | const subprocess = require("child_process"); 4 | const { Regexes } = require("../constants"); 5 | 6 | /** 7 | * @param { string } rr - Path to rr 8 | * @param { string | "" } workspaceDir - Workspace directory containing N traces 9 | * @returns { Promise } 10 | */ 11 | async function getTraces(rr, workspaceDir = "") { 12 | return new Promise((resolve, reject) => { 13 | const command = `${rr} ls ${workspaceDir} -t -r`; 14 | subprocess.exec(command, (err, stdout, stderr) => { 15 | if (err) { 16 | reject(stderr); 17 | } else { 18 | let lines = stdout.split("\n"); 19 | const traces = lines 20 | .map((line) => line.split(" ")[0].trim()) 21 | .filter((trace) => trace.length > 0 && trace != "cpu_lock"); 22 | if (traces.length == 1 && traces[0] == "latest-trace") { 23 | reject(`No traces found by rr ps command`); 24 | } else { 25 | resolve(traces); 26 | } 27 | } 28 | }); 29 | }); 30 | } 31 | 32 | function* get_field(line) { 33 | let it = 0; 34 | let end = 0; 35 | let parts_generated = 0; 36 | while (it < line.length) { 37 | if (parts_generated < 3) { 38 | while (Regexes.WhiteSpace.test(line.charAt(it))) it++; 39 | end = it; 40 | while (!Regexes.WhiteSpace.test(line.charAt(end))) end++; 41 | const res = line.substring(it, end).trim(); 42 | it = end; 43 | parts_generated++; 44 | yield res; 45 | } else { 46 | const r = line.substring(it).trim(); 47 | it = line.length; 48 | yield r; 49 | } 50 | } 51 | return null; 52 | } 53 | 54 | /** 55 | * @param { string } data 56 | * @returns { { pid: string, ppid: string, exit: string, cmd: string, noexec: boolean }[] } 57 | */ 58 | function fallbackParseOfrrps(data) { 59 | return data 60 | .split("\n") 61 | .slice(1) 62 | .filter((line) => line.length > 2) 63 | .map((line) => { 64 | const [pid, ppid, exit, cmd] = [...get_field(line)]; 65 | return { pid, ppid, exit, cmd, noexec: Regexes.ForkedNoExec.test(line) }; 66 | }); 67 | } 68 | 69 | /** 70 | * @param { string } rr - path to RR 71 | * @param { string } trace - trace directory 72 | * @returns { Promise<{ value: string, label: string, description: string, detail: string, binary: string, noexec: boolean }[]> } 73 | */ 74 | async function getTraceInfo(rr, trace) { 75 | return new Promise((resolve, reject) => { 76 | subprocess.exec(`${rr} ps ${trace}`, (error, stdout, stderr) => { 77 | if (error) { 78 | reject(stderr); 79 | } else { 80 | const json = fallbackParseOfrrps(stdout); 81 | resolve(json); 82 | } 83 | }); 84 | }).then((picks) => { 85 | return picks.map(({ pid, ppid, exit, cmd, noexec }, index) => { 86 | let binary = cmd.trim(); 87 | try { 88 | // if forked, RR doesn't provide us with a binary, scan backwards in list to find forked-from process 89 | if (cmd.includes("forked without exec")) { 90 | for (let findBinaryIndex = index - 1; findBinaryIndex >= 0; findBinaryIndex--) { 91 | if (!picks[findBinaryIndex].cmd.includes("forked without exec")) { 92 | binary = picks[findBinaryIndex].cmd; 93 | } 94 | } 95 | } 96 | } catch (ex) { 97 | console.log(`Failed to prepare RR replay parameters: ${ex}`); 98 | throw new Error(`Failed to prepare RR replay parameters`); 99 | } 100 | return { 101 | value: pid, 102 | label: `${path.basename(cmd.split(" ")[0] ?? cmd)} (${pid})`, 103 | description: `PID: ${pid}, PPID: ${ppid === "--" ? "--" : +ppid}, EXIT: ${exit}`, 104 | detail: cmd.trim(), 105 | binary, 106 | noexec, 107 | }; 108 | }); 109 | }); 110 | } 111 | 112 | /** 113 | * Parse the required launch config `program` field from the field `cmd` of the `rr ps` result 114 | * @param {string} rr_ps_output_cmd - the `cmd` field returned from `tracePicked` 115 | * @returns {string} - path to binary to debug 116 | * @throws Throws an exception if a file can't be parsed from `rr_ps_output_cmd` 117 | */ 118 | function parseProgram(rr_ps_output_cmd) { 119 | return rr_ps_output_cmd.split(" ")[0]; 120 | } 121 | 122 | const tracePicked = async (rr, traceDirectory) => { 123 | if (traceDirectory == null || traceDirectory == undefined) { 124 | throw new Error("You did not pick a trace"); 125 | } 126 | const options = { 127 | canPickMany: false, 128 | ignoreFocusOut: true, 129 | title: "Select process to debug", 130 | }; 131 | return await vscode.window.showQuickPick(getTraceInfo(rr, traceDirectory), options).then((selection) => { 132 | if (selection) { 133 | const replay_parameters = { 134 | pid: selection.value, 135 | traceDirectory, 136 | cmd: selection.binary, 137 | noexec: selection.noexec, 138 | }; 139 | return replay_parameters; 140 | } 141 | return null; 142 | }); 143 | }; 144 | 145 | /** 146 | * 147 | * @param {*} rr 148 | * @returns { Promise } 149 | */ 150 | function getGdbInit(rr) { 151 | return new Promise((res, rej) => { 152 | subprocess.exec(`${rr} gdbinit`, (error, stdout) => { 153 | if (error) rej(error); 154 | else res(stdout.toString()); 155 | }); 156 | }); 157 | } 158 | 159 | async function generateGdbInit(rr) { 160 | return await getGdbInit(rr).then((data) => { 161 | // this is ugly copying. But... I don't care. This is run once on each update & build of RR 162 | // and involves at most a kb or two. 163 | const lines = data.split("\n"); 164 | let i = 0; 165 | for (; i < lines.length; ++i) { 166 | // remove everything after `define hook-run` as this stuff messes with us. 167 | if (lines[i].includes("define hook-run")) break; 168 | } 169 | const kept_lines = lines.splice(0, i); 170 | return kept_lines.join("\n"); 171 | }); 172 | } 173 | 174 | module.exports = { 175 | tracePicked, 176 | getTraces, 177 | parseProgram, 178 | generateGdbInit, 179 | }; 180 | -------------------------------------------------------------------------------- /modules/utils/sysutils.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | const { exec, spawn: _spawn, execSync } = require("child_process"); 3 | const fsp = require("fs/promises"); 4 | const fs = require("fs"); 5 | const Path = require("path"); 6 | 7 | /** 8 | * Returns a full constructed path of `fileOrDir` inside the extension directory. 9 | * Data found in this directory is not persistent and is removed on updates. Do not rely on persistent storage here. 10 | * Use APIManager.getExtensionGlobalStorage instead. 11 | * @param {string} fileOrDir 12 | * @returns { string } 13 | */ 14 | function getExtensionPathOf(fileOrDir = null) { 15 | if (fileOrDir != null) { 16 | if (fileOrDir[0] == "/") { 17 | fileOrDir = fileOrDir.substring(1); 18 | } 19 | return vscode.extensions.getExtension("farrese.midas").extensionPath + `/${fileOrDir}`; 20 | } else { 21 | return vscode.extensions.getExtension("farrese.midas").extensionPath; 22 | } 23 | } 24 | 25 | /** 26 | * Resolves full path to binary by using shell `which` command. 27 | * @param {string} binary 28 | * @returns {Promise} 29 | */ 30 | function which(binary) { 31 | return new Promise((resolve) => 32 | exec(`which ${binary}`, (err, stdout) => { 33 | if (err) { 34 | resolve(""); 35 | } 36 | if (stdout.charAt(stdout.length - 1) == "\n") { 37 | resolve(stdout.slice(0, stdout.length - 1)); 38 | } else { 39 | resolve(stdout); 40 | } 41 | }), 42 | ); 43 | } 44 | 45 | /** 46 | * Returns where `binary` exists. Can return items which are not executable binaries. 47 | * @param { string } binary 48 | * @returns { Promise } 49 | */ 50 | function whereis(binary) { 51 | return new Promise((resolve, reject) => { 52 | exec(`whereis ${binary}`, (err, stdout) => { 53 | if (err) reject(err); 54 | try { 55 | // whereis returns 56 | // binary: /path/to/first/binary /path/to/second/binary ... 57 | // therefore, strip `binary:` from output 58 | const result = stdout 59 | .toString() 60 | .substring(binary.length + 1) 61 | .trim() 62 | .split(" ") 63 | .filter((s) => s != ""); 64 | resolve(result); 65 | } catch (err) { 66 | console.log(`could not perform 'whereis': ${err}`); 67 | reject([]); 68 | } 69 | }); 70 | }); 71 | } 72 | 73 | /** 74 | * Executes `command` using sudo 75 | * @param {string[]} command - command to execute in sudo. Command and parameters passed as an array 76 | * @param {string} pass - password to sudo 77 | * @param {(...args: any[]) => void} exitCodeCallback - callback that runs on process exit 78 | * @returns 79 | */ 80 | async function sudo(command, pass, exitCodeCallback = null) { 81 | try { 82 | let _sudo = await which("sudo"); 83 | const args = ["-S", ...command]; 84 | let sudo = _spawn(_sudo, args, { stdio: "pipe", shell: true, env: sanitizeEnvVariables() }); 85 | sudo.on("error", () => { 86 | throw new Error(`Sudo failed`); 87 | }); 88 | if (exitCodeCallback != null) { 89 | sudo.on("exit", exitCodeCallback); 90 | } 91 | sudo.stderr.on("data", (data) => { 92 | if (data.includes("[sudo]")) { 93 | sudo.stdin.write(pass + "\n"); 94 | } 95 | }); 96 | return sudo; 97 | } catch (e) { 98 | vscode.window.showErrorMessage(`Failed to run sudo command ${command}`); 99 | } 100 | } 101 | 102 | /** 103 | * Resolve a symlink to where it points to. `fully` sets if 104 | * it should be resolved fully (i.e, if it's a symlink to a symlink etc 105 | * follow the entire chain to it's end). 106 | */ 107 | function resolveCommand(cmd) { 108 | if (fs.existsSync(cmd)) { 109 | return fs.realpathSync(cmd); 110 | } 111 | const whereis = execSync(`whereis ${cmd}`); 112 | const parts = whereis.toString().split(" "); 113 | if (parts.length < 2) { 114 | throw new Error(`${cmd} could not be resolved`); 115 | } 116 | for (const result of parts.slice(1)) { 117 | if (Path.basename(result) == cmd) { 118 | return result; 119 | } 120 | } 121 | throw new Error(`${cmd} could not properly be resolved. Try providing a fully qualified path`); 122 | } 123 | 124 | function sanitizeEnvVariables() { 125 | let ENV_VARS = { ...process.env }; 126 | if (ENV_VARS.VIRTUAL_ENV != null) { 127 | ENV_VARS.PATH = ENV_VARS.PATH.replaceAll(ENV_VARS.VIRTUAL_ENV.toString(), ""); 128 | } 129 | return ENV_VARS; 130 | } 131 | 132 | /** 133 | * @returns {Promise<{detail: string, label: string, alwaysShow: boolean, pid: string}[]>} 134 | */ 135 | async function getAllPidsForQuickPick() { 136 | const res = (await fsp.readdir("/proc", { withFileTypes: true })) 137 | .filter((dirent) => { 138 | try { 139 | if (!dirent.isDirectory()) return false; 140 | const number = Number.parseInt(dirent.name); 141 | return number.toString() == dirent.name; 142 | } catch (ex) { 143 | return false; 144 | } 145 | }) 146 | .map(async (dirent) => { 147 | return await fsp.readFile(`/proc/${dirent.name}/cmdline`).then((buf) => { 148 | const label = buf.toString().replace("\u0000", " "); 149 | 150 | return { 151 | detail: dirent.name, 152 | label: `${label} (${dirent.name})`, 153 | alwaysShow: true, 154 | pid: dirent.name, 155 | }; 156 | }); 157 | }); 158 | return Promise.all(res); 159 | } 160 | 161 | module.exports = { 162 | getExtensionPathOf, 163 | which, 164 | whereis, 165 | sudo, 166 | resolveCommand, 167 | sanitizeEnvVariables, 168 | getAllPidsForQuickPick, 169 | }; 170 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.yapf] 2 | COLUMN_LIMIT = 115 3 | BLANK_LINES_BETWEEN_TOP_LEVEL_IMPORTS_AND_VARIABLES = 0 -------------------------------------------------------------------------------- /release_notes/0.19.0.md: -------------------------------------------------------------------------------- 1 | # Release 0.19.0 2 | 3 | ## Features 4 | 5 | - New DAP interpreter. This have introduced breaking changes to the configuration of Midas. Unless you're new, you should read [the readme](https://github.com/farre/midas). This change is a substantial overhaul and is supposed to make Midas more stable and performant. 6 | -------------------------------------------------------------------------------- /release_notes/0.19.10.md: -------------------------------------------------------------------------------- 1 | # Release 0.19.10 2 | 3 | ## Fixes 4 | 5 | - Fixed bug where frame arguments weren't displayed properly 6 | -------------------------------------------------------------------------------- /release_notes/0.19.11.md: -------------------------------------------------------------------------------- 1 | # Release 0.19.11 2 | 3 | ## Fixes 4 | 5 | - Pretty printer-usage bug fix 6 | -------------------------------------------------------------------------------- /release_notes/0.19.15.md: -------------------------------------------------------------------------------- 1 | # Release 0.19.15 2 | 3 | ## Fixes 4 | 5 | - Fixes `when`, and other RR commands "crashing" Midas. 6 | 7 | ## Features 8 | 9 | - Added run to event 10 | - Midas now uses the output from `rr gdbinit` directly to increase stability so that if RR changes it, we don't break. 11 | -------------------------------------------------------------------------------- /release_notes/0.19.17.md: -------------------------------------------------------------------------------- 1 | # Release 0.19.17 2 | 3 | ## Fixes 4 | 5 | - Fixes #188 where Midas couldn't be started because RR was recording. 6 | 7 | ## Features 8 | 9 | - Added `rrOptions` to `launch.json` for `midas-rr` debug sessions, which are command line options passed to RR. 10 | -------------------------------------------------------------------------------- /release_notes/0.19.18.md: -------------------------------------------------------------------------------- 1 | # Release 0.19.18 2 | 3 | ## Fixes 4 | 5 | - Make checkpoints in UI clickable to go-to source where they're set 6 | 7 | ## Features 8 | 9 | - Added ability to name checkpoints 10 | -------------------------------------------------------------------------------- /release_notes/0.19.2.md: -------------------------------------------------------------------------------- 1 | # Release 0.19.2 2 | 3 | ## Features 4 | 5 | - Added Checkpoint UI for RR sessions. The snapshot (camera icon) sets a checkpoint. And there will be a `Checkpoint` panel in the side bar during debugging from where the user can (re)start a checkpoint. 6 | -------------------------------------------------------------------------------- /release_notes/0.19.7.md: -------------------------------------------------------------------------------- 1 | # Release 0.19.7 2 | 3 | ## Fixes 4 | 5 | - Fixed a bug where RR did not get spawned correctly, not being able to replay fork-without-execs. 6 | -------------------------------------------------------------------------------- /release_notes/0.19.8.md: -------------------------------------------------------------------------------- 1 | # Release 0.19.8 2 | 3 | ## Fixes 4 | 5 | - Fixed a bug where typedefs weren't recognized as the base type and as such wouldn't use pretty printers 6 | -------------------------------------------------------------------------------- /release_notes/0.20.3.md: -------------------------------------------------------------------------------- 1 | # Release 0.20.3 2 | 3 | ## Fixes 4 | 5 | - Added connect timeout & remote timeout to 10000 for all attach 6 | -------------------------------------------------------------------------------- /release_notes/0.20.4.md: -------------------------------------------------------------------------------- 1 | # Release 0.20.4 2 | 3 | ## Fixes 4 | 5 | - Fix the Get RR bug where it did not fire if the Midas config file gets into an invalid state, possibly by aborting a build etc. 6 | -------------------------------------------------------------------------------- /release_notes/0.22.0.md: -------------------------------------------------------------------------------- 1 | # Release 0.22.0 2 | 3 | ## Fixes 4 | 5 | - Fixed bug that made `Add to watch` disappear for member objects in the `Variables` list. You can now right click things 6 | in the list and add sub objects to `Watch` again. 7 | 8 | ## Features 9 | 10 | - Added `(Return Value)` variable to `Locals`, if the last command that executed was `stepOut` and it succeeded completely. 11 | This way you can inspect return values from a function. 12 | - Added `LogPoint` breakpoints that logs to the debug console and continues. 13 | -------------------------------------------------------------------------------- /release_notes/0.22.2.md: -------------------------------------------------------------------------------- 1 | # Release 0.22.2 2 | 3 | ## Features 4 | 5 | - Added `zen workaround` command, type `Zen` in VSCode control panel (ctrl+shift+p) and the command should appear 6 | -------------------------------------------------------------------------------- /release_notes/0.23.0.md: -------------------------------------------------------------------------------- 1 | # Release 0.23.0 2 | 3 | ## Fixes 4 | 5 | - Build RR related bugs fixed 6 | - Pretty printer related issues fixed 7 | 8 | ## Features 9 | 10 | - Added new pretty printer features. 11 | - Support for using the canonical experimental debugger `mdb` 12 | -------------------------------------------------------------------------------- /release_notes/0.23.1.md: -------------------------------------------------------------------------------- 1 | # Release 0.23.1 2 | 3 | ## Fixes 4 | 5 | - Make validation of config happen after substition of variables like ${workspaceFolder} so that _any_ configuration items can use the short-hands/built ins 6 | - Refactors and cleaning up the inherent messiness of supporting what essentially is 3 backends; midas-native (mdb, future hopefully mdb+rr), midas-gdb (gdb) and midas-rr (gdb+rr). 7 | - Added program args as a configuration item for launched program when `use-dap` is true, which seems to have been missing 8 | 9 | ## Features 10 | 11 | - Extended support for the debugger backend "midas" which henceforth will be known as "midas-native" 12 | - Added context menu support for call stack, to resume/pause all threads 13 | - Make use of onActive item events so that the debugger backends can know what threads the user is inspecting in the UI 14 | - Added continueAll 15 | - Added pauseAll 16 | - continueAll/pauseAll is used for gdb's allStopMode, or MDB. 17 | - Deprecated allStopMode in launch.json; use noSingleThreadControl instead. It's a better name and will 18 | be also what this feature eventually be called by MDB. 19 | - ${workspaceFolder} and built-ins in launch.json, should now work for all sorts of config field values 20 | -------------------------------------------------------------------------------- /release_notes/0.23.3.md: -------------------------------------------------------------------------------- 1 | # Release 0.23.3 2 | 3 | ## Features 4 | 5 | - Added `ignoreStandardLibrary` to launch and attach configurations. It will attempt at ignoring most c++ standard library headers while stepping. It's a best approximation approach. Defaults to `true`. If you want to add additional files from the standard library, add `"skip file foo"`, `"skip file bar"`, ... to the `setupCommands` field. 6 | 7 | ## Fixes 8 | 9 | - Removed old Midas version that relied on GDB/MI and gdb-js. The exclusively supported variant is midas DAP implementation. 10 | - Fixes to UI to behave more consistent. 11 | - Refactor of tool management, so that it can be extended to additional software like GDB as well (in the future) in-house debugger, caused Midas to ask every time for updates of tools even when having updated to last version. Fixed. 12 | - Fixed hex-formatting bug for register values with the sign bit set. 13 | - Make disassemble only disassemble functions and don't allow for scroll-disassembling as this caused jank and bugs. New system not perfect, but does not produce invalid results 14 | - Greatly improved stability and reliability of disassembly outputs 15 | - Fixed bug in InstructionBreakpoint requests 16 | -------------------------------------------------------------------------------- /release_notes/template.md: -------------------------------------------------------------------------------- 1 | [Only headings with ## will be included in generated release notes] 2 | 3 | # Release X.Y.Z 4 | 5 | ## Fixes 6 | 7 | - A fix 8 | 9 | ## Features 10 | 11 | - A feature 12 | 13 | [All subheaders with ## will be included and sorted in generated release notes] 14 | -------------------------------------------------------------------------------- /test/cppworkspace/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(cppworkspace) 3 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../bin) 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 5 | set(DEBUG_SETTINGS -g3) 6 | 7 | add_subdirectory(attach) 8 | add_subdirectory(rr) 9 | add_subdirectory(simple_input) 10 | add_subdirectory(test) 11 | add_subdirectory(thread) 12 | add_subdirectory(multi-process) 13 | 14 | # install(TARGETS attach CONFIGURATIONS Debug RUNTIME DESTINATION ${CMAKE_BINARY_DIR}/../bin) -------------------------------------------------------------------------------- /test/cppworkspace/PrettyPrinters.py: -------------------------------------------------------------------------------- 1 | import gdb 2 | import gdb.printing 3 | import re 4 | from time import perf_counter as pc 5 | 6 | 7 | class VectorPrinter: 8 | "Print a Vector" 9 | 10 | def __init__(self, val): 11 | self.val = val 12 | self.size = int(self.val["m_size"]) 13 | 14 | def children(self): 15 | for i in range(0, self.size): 16 | yield (f"{i}", (self.val["m_elements"] + i).dereference()) 17 | 18 | def children_range(self, start, end): 19 | for i in range(start, end): 20 | yield (f"{i}", (self.val["m_elements"] + i).dereference()) 21 | 22 | def to_string(self): 23 | return f"Vector is ...?" 24 | 25 | def display_hint(self): 26 | return 'array' 27 | 28 | def num_children(self): 29 | return self.size 30 | 31 | class VectorIteratorPrinter: 32 | "Print std::vector::iterator" 33 | 34 | def __init__(self, val): 35 | self.val = val 36 | 37 | def to_string(self): 38 | if not self.val['_M_current']: 39 | return 'non-dereferenceable iterator for std::vector' 40 | return str(self.val['_M_current'].dereference()) 41 | 42 | class StringPrinter: 43 | "Print a std::basic_string of some kind" 44 | 45 | def __init__(self, val, typename = None): 46 | self.val = val 47 | self.typename = typename 48 | 49 | def to_string(self): 50 | # Make sure &string works, too. 51 | type = self.val.type 52 | if type.code == gdb.TYPE_CODE_REF: 53 | type = type.target () 54 | 55 | # Calculate the length of the string so that to_string returns 56 | # the string according to length, not according to first null 57 | # encountered. 58 | ptr = self.val ["m_ptr"] 59 | return ptr.string(length = self.val["m_length"]) 60 | 61 | def display_hint (self): 62 | return 'string' 63 | 64 | def build_pretty_printer(): 65 | pp = gdb.printing.RegexpCollectionPrettyPrinter( 66 | "dap_library") 67 | pp.add_printer('String', '^String$', StringPrinter) 68 | pp.add_printer('Vector', '^Vector$', VectorPrinter) 69 | return pp 70 | 71 | def str_lookup_function(val): 72 | lookup_tag = val.type.tag 73 | if lookup_tag is None: 74 | return None 75 | regex = re.compile("^Vector<.*>$") 76 | if regex.match(lookup_tag): 77 | return VectorPrinter(val) 78 | return None 79 | 80 | def vec_lookup_function(val): 81 | lookup_tag = val.type.tag 82 | if lookup_tag is None: 83 | return None 84 | regex = re.compile("^String$") 85 | if regex.match(lookup_tag): 86 | return StringPrinter(val=val) 87 | return None 88 | 89 | gdb.selected_inferior().progspace.objfiles()[0].pretty_printers.append(str_lookup_function) 90 | gdb.selected_inferior().progspace.objfiles()[0].pretty_printers.append(vec_lookup_function) 91 | 92 | # gdb.printing.register_pretty_printer(gdb.current_objfile(), build_pretty_printer()) -------------------------------------------------------------------------------- /test/cppworkspace/attach/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(attach) 3 | set(CMAKE_CXX_STANDARD 20) 4 | 5 | add_executable(attach ./src/main.cpp) 6 | target_include_directories(attach PUBLIC ../include) 7 | 8 | target_compile_options(attach PUBLIC $<$: ${DEBUG_SETTINGS}>) -------------------------------------------------------------------------------- /test/cppworkspace/attach/README.md: -------------------------------------------------------------------------------- 1 | # For testing of GDB 2 | 3 | Building a debug build with cmake: 4 | 5 | ```bash 6 | # While in test folder 7 | mkdir build 8 | cd build 9 | cmake .. -DCMAKE_BUILD_TYPE=Debug 10 | cmake --build . 11 | ``` 12 | -------------------------------------------------------------------------------- /test/cppworkspace/attach/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main(int argc, const char** argv) { 9 | std::cout << "Hello world" << std::endl; 10 | auto pid = getpid(); 11 | std::cout << pid << std::endl; 12 | // let's interrupt here, so that we are given time to attach with gdb 13 | std::string buf; 14 | while(true) { 15 | std::cout << "input: (waiting for q to quit):"; 16 | std::getline(std::cin, buf); 17 | std::cout << "read: " << buf << std::endl; 18 | if(buf == "q") { 19 | break; 20 | } 21 | } 22 | std::cout << "swoosh" << std::endl; 23 | } 24 | -------------------------------------------------------------------------------- /test/cppworkspace/attach/testspawn.sh: -------------------------------------------------------------------------------- 1 | gdb build/testapp -iex "source ../../../modules/python/setup.py" -iex "source ../../../modules/python/midas.py" -------------------------------------------------------------------------------- /test/cppworkspace/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir build 3 | cd build 4 | cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Debug 5 | cmake --build . 6 | # place all binaries in cppworkspace/bin/ 7 | cmake --install . 8 | cd .. 9 | rm build -rf 10 | -------------------------------------------------------------------------------- /test/cppworkspace/include/date.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | struct Date { 4 | int day, month, year; 5 | friend std::ostream& operator<<(std::ostream& os, const Date& date) { 6 | if(date.day < 10) { 7 | os << date.year << "-" << date.month << "-" << '0' << date.day; 8 | } else { 9 | os << date.year << "-" << date.month << "-" << date.day; 10 | } 11 | return os; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /test/cppworkspace/include/number.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | template 6 | concept IsNumber = 7 | std::is_floating_point_v || std::is_integral_v; 8 | 9 | template constexpr auto number_type_name(const T &t) { 10 | if constexpr (std::is_same_v) { 11 | return "double"; 12 | } else if constexpr (std::is_same_v) { 13 | return "float"; 14 | } else { 15 | return "int"; 16 | } 17 | } 18 | 19 | /** 20 | * This is not supposed to be good C++ code, 21 | * template or otherwise. It's sole purpose is testing 22 | * how debugging templated C++ types in GDB and how it handles 23 | * that. 24 | */ 25 | template class Number { 26 | N value; 27 | 28 | public: 29 | explicit constexpr Number(N value) : value(value) {} 30 | constexpr Number(const Number &) = default; 31 | constexpr ~Number() = default; 32 | 33 | constexpr static Number sum(Number a, Number b) { 34 | auto result = Number{a.value + b.value}; 35 | return result; 36 | } 37 | 38 | constexpr friend auto &operator<<(std::ostream &os, const Number &number) { 39 | os << number_type_name(number.value) << ": " << number.value; 40 | return os; 41 | } 42 | 43 | private: 44 | constexpr bool is_zero() const { 45 | if constexpr (std::is_floating_point_v) { 46 | return this->value == 0.0; 47 | } else { 48 | return this->value == 0; 49 | } 50 | } 51 | }; -------------------------------------------------------------------------------- /test/cppworkspace/include/string.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | class String { 7 | public: 8 | String() : m_ptr{nullptr}, m_length(0) {} 9 | 10 | String(const std::string& std_string) noexcept : m_ptr{new char[std_string.size()]}, m_length{std_string.size()} { 11 | std::memcpy(m_ptr, std_string.c_str(), std_string.size()); 12 | } 13 | 14 | String(const char* str) : m_length(std::strlen(str)) { 15 | m_ptr = new char[m_length]; 16 | std::memcpy(m_ptr, str, m_length); 17 | } 18 | 19 | String(const String& copy) : m_ptr{new char[copy.m_length]}, m_length(copy.m_length) { 20 | std::memcpy(m_ptr, copy.m_ptr, m_length); 21 | } 22 | 23 | const char* c_str() const { 24 | return m_ptr; 25 | } 26 | 27 | const auto size() const { return m_length; } 28 | 29 | private: 30 | char* m_ptr; 31 | std::uint64_t m_length; 32 | }; -------------------------------------------------------------------------------- /test/cppworkspace/include/todo.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class Todo 8 | { 9 | public: 10 | Todo(const char *title, Date date); 11 | std::string_view title() const; 12 | int id() const; 13 | static int todo_count(); 14 | const Date &date() const; 15 | static void post_pone(Todo& t, Date d) 16 | { 17 | t.m_date = d; 18 | } 19 | static void post_pone(Todo* t, Date d) 20 | { 21 | t->m_date = d; 22 | } 23 | 24 | private: 25 | int m_id; 26 | Date m_date; 27 | std::string m_title; 28 | static int s_todo_id; 29 | }; 30 | -------------------------------------------------------------------------------- /test/cppworkspace/include/types.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template 4 | struct ResultPtr { 5 | constexpr ResultPtr() : m_result{Err{}}, status(Status::Err) {} 6 | ~ResultPtr() {} 7 | bool has_value() const { 8 | return std::holds_alternative(m_result); 9 | } 10 | 11 | bool has_err() const { 12 | return std::holds_alternative(m_result); 13 | } 14 | private: 15 | std::variant m_result; 16 | enum Status { 17 | Ok, 18 | Err 19 | } status; 20 | }; 21 | 22 | template 23 | struct Result { 24 | constexpr Result() : m_result{Err{}}, status(Status::Err) {} 25 | ~Result() {} 26 | bool has_value() const { 27 | return this->status == Status::Ok; 28 | } 29 | 30 | bool has_err() const { 31 | return this->status == Status::Err; 32 | } 33 | private: 34 | std::variant m_result; 35 | enum Status { 36 | Ok, 37 | Err 38 | } status; 39 | }; -------------------------------------------------------------------------------- /test/cppworkspace/include/vector.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | template 7 | class Vector { 8 | public: 9 | Vector() : m_elements(), m_size(0), m_cap() {} 10 | 11 | Vector(const Vector& copy) : m_size(copy.m_size), m_cap(copy.m_cap) { 12 | // Do it the sucky way. Enforce default construction. 13 | m_elements = new T[m_cap]; 14 | } 15 | 16 | ~Vector() { 17 | delete[] m_elements; 18 | } 19 | 20 | void push(T&& t) { 21 | m_elements[m_size] = t; 22 | m_size++; 23 | } 24 | 25 | void reserve(std::uint64_t size) { 26 | m_elements = new T[size]; 27 | m_size = 0; 28 | m_cap = size; 29 | } 30 | 31 | private: 32 | T* m_elements; 33 | std::uint64_t m_size; 34 | std::uint64_t m_cap; 35 | }; -------------------------------------------------------------------------------- /test/cppworkspace/multi-process/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(ForkAndExecListDir) 3 | set(CMAKE_CXX_STANDARD 20) 4 | 5 | add_executable(forkexec ./src/main.cpp) 6 | add_executable(listdir ./src/ls.cpp) 7 | 8 | target_compile_options(forkexec PUBLIC $<$:${DEBUG_SETTINGS}>) 9 | target_compile_options(listdir PUBLIC $<$:${DEBUG_SETTINGS}>) -------------------------------------------------------------------------------- /test/cppworkspace/multi-process/README.md: -------------------------------------------------------------------------------- 1 | # For testing of GDB 2 | 3 | Building a debug build with cmake: 4 | 5 | ```bash 6 | # While in thread folder 7 | mkdir build 8 | cd build 9 | cmake .. -DCMAKE_BUILD_TYPE=Debug 10 | cmake --build . 11 | ``` 12 | -------------------------------------------------------------------------------- /test/cppworkspace/multi-process/src/ls.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace fs = std::filesystem; 5 | 6 | int main() { 7 | std::cout << "Listing directory contents:" << std::endl; 8 | 9 | try { 10 | for (const auto& entry : fs::directory_iterator(".")) { 11 | std::cout << entry.path().filename().string() << std::endl; 12 | } 13 | } catch (const fs::filesystem_error& e) { 14 | std::cerr << "Error: " << e.what() << std::endl; 15 | return 1; 16 | } 17 | 18 | return 0; 19 | } -------------------------------------------------------------------------------- /test/cppworkspace/multi-process/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main() { 8 | pid_t pid = fork(); // Create a child process 9 | 10 | if (pid < 0) { 11 | std::cerr << "Fork failed!" << std::endl; 12 | return 1; 13 | } 14 | 15 | if (pid == 0) { 16 | // Child process 17 | std::cout << "Child process (PID: " << getpid() << ") executing ls_program..." << std::endl; 18 | 19 | // Use exec to run the second application 20 | execl("/home/prometheus/dev/midas/test/cppworkspace/bin/listdir", "/home/prometheus/dev/midas/test/cppworkspace/bin/listdir", nullptr); 21 | 22 | // If execl fails 23 | perror("execl failed"); 24 | return 1; 25 | } else { 26 | // Parent process 27 | std::cout << "Parent process (PID: " << getpid() << "), waiting for child..." << std::endl; 28 | wait(nullptr); // Wait for the child process to complete 29 | std::cout << "Child process finished." << std::endl; 30 | } 31 | 32 | return 0; 33 | } -------------------------------------------------------------------------------- /test/cppworkspace/printers/FooPrinter.js: -------------------------------------------------------------------------------- 1 | function create({ PrettyPrinter }) { 2 | class FooPrinter extends PrettyPrinter { 3 | constructor() { 4 | super("Foo"); 5 | } 6 | 7 | async valueHint(variable) { 8 | const children = await variable.children(); 9 | const data = children.map((child) => { 10 | return `${child.name} = ${child.value}`; 11 | }); 12 | variable.value = `Foo { ${data.join(", ")} }`; 13 | variable.toLiteral(); 14 | } 15 | } 16 | 17 | return new FooPrinter(); 18 | } 19 | 20 | module.exports = { create }; 21 | -------------------------------------------------------------------------------- /test/cppworkspace/printers/StdOptionalPrinter.js: -------------------------------------------------------------------------------- 1 | function create({ PrettyPrinter }) { 2 | class StdOptionalPrinter extends PrettyPrinter { 3 | constructor() { 4 | super(".*optional<.*>"); 5 | } 6 | 7 | async valueHint(variable) { 8 | const expanded = await variable.children(); 9 | const base = await expanded[0].children(); 10 | const payload = await base[1].children(); 11 | const payloadBase = await payload[0].children(); 12 | if (payloadBase.find((value) => value.name === "_M_engaged").value == "false") { 13 | variable.value = "no value"; 14 | variable.toLiteral(); 15 | } else { 16 | variable.cache(payloadBase.find((value) => value.name === "_M_payload")); 17 | } 18 | } 19 | 20 | async valueExpanded(variables) { 21 | if (variables.value(0).hasChildren()) { 22 | const children = await variables.value(0).children(); 23 | variables.update(0, children[0]); 24 | } 25 | } 26 | } 27 | 28 | return new StdOptionalPrinter(); 29 | } 30 | 31 | module.exports = { create }; 32 | -------------------------------------------------------------------------------- /test/cppworkspace/rr/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(rr) 3 | set(CMAKE_CXX_STANDARD 20) 4 | 5 | add_executable(buffer_overflow ./buffer_overflow.cpp) 6 | add_executable(segfault ./segfault.cpp) 7 | 8 | target_compile_options(buffer_overflow PUBLIC $<$:${DEBUG_SETTINGS}>) 9 | target_compile_options(segfault PUBLIC $<$:${DEBUG_SETTINGS}>) -------------------------------------------------------------------------------- /test/cppworkspace/rr/buffer_overflow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct StringView { 7 | const char* str; 8 | std::size_t len; 9 | StringView(const char* string) noexcept : str(string), len(0) { 10 | if(string == nullptr) { 11 | return; 12 | } 13 | for(auto it = str; *it != '\0'; it++) { 14 | len++; 15 | } 16 | } 17 | 18 | // this function is meant to do weird ish (like make future calls to string view segfault) 19 | void remove_prefix(std::size_t new_start) { 20 | for(; new_start > 0; new_start--) { 21 | str++; 22 | this->len--; 23 | } 24 | } 25 | const char* get() const { return str; } 26 | }; 27 | 28 | void use_ptr(int *ptr) { std::cout << "value: " << *ptr << std::endl; } 29 | 30 | int main() { 31 | 32 | std::string danger_danger{"hello world"}; 33 | StringView view{danger_danger.data()}; 34 | StringView* p = &view; 35 | std::cout << p->get() << std::endl; 36 | p->remove_prefix(10); 37 | std::cout << p->get() << std::endl; 38 | p->remove_prefix(5); 39 | // boom 40 | std::cout << p->get() << std::endl; 41 | } -------------------------------------------------------------------------------- /test/cppworkspace/rr/segfault.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void use_ptrs(int *ptr, std::size_t len) { 4 | auto index = 0; 5 | for (; len > 0; --len) { 6 | const auto value = *ptr; 7 | std::cout << "value: #" << index << ": " << value << std::endl; 8 | ++index; 9 | ++ptr; 10 | } 11 | } 12 | 13 | int main() { 14 | int **values = new int *[2]; 15 | auto iota = [cnt = 0](int *v, std::size_t len) mutable { 16 | while (len > 0) { 17 | *v = cnt; 18 | cnt++; 19 | len--; 20 | v++; 21 | } 22 | }; 23 | 24 | values[0] = new int[10]; 25 | iota(values[0], 10); 26 | values[1] = nullptr; 27 | use_ptrs(values[0], 10); 28 | use_ptrs(values[1], 10); 29 | } -------------------------------------------------------------------------------- /test/cppworkspace/simple_input/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(simple_input) 3 | set(CMAKE_CXX_STANDARD 20) 4 | 5 | add_executable(simple_input ./src/main.cpp ./src/todo.cpp) 6 | target_include_directories(simple_input PUBLIC ../include) 7 | target_link_libraries(simple_input pthread) 8 | 9 | target_compile_options(simple_input PUBLIC $<$:${DEBUG_SETTINGS}>) -------------------------------------------------------------------------------- /test/cppworkspace/simple_input/README.md: -------------------------------------------------------------------------------- 1 | # For testing of GDB 2 | 3 | Building a debug build with cmake: 4 | 5 | ```bash 6 | # While in thread folder 7 | mkdir build 8 | cd build 9 | cmake .. -DCMAKE_BUILD_TYPE=Debug 10 | cmake --build . 11 | ``` 12 | -------------------------------------------------------------------------------- /test/cppworkspace/simple_input/src/date.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | std::ostream& operator<<(std::ostream& os, const Date& date) { 3 | } 4 | -------------------------------------------------------------------------------- /test/cppworkspace/simple_input/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | std::mutex g_stdio_mutex; 12 | struct Foo { 13 | double x, y; 14 | }; 15 | 16 | struct Surface { 17 | int width; 18 | int height; 19 | // mapping onto the surface's dimensions 20 | struct Mapping { 21 | double min; 22 | double max; 23 | } x, y; 24 | }; 25 | 26 | auto broke_free(auto a, auto b, Foo test) { 27 | Foo bar = test; 28 | if(a != 4.0) { 29 | auto foo = a + 1; 30 | } 31 | return (a + b) > 4.0; 32 | } 33 | 34 | using Iterations = int; 35 | Iterations mandelbrot(double real, double imag, int limit = 100) { 36 | double re = real; 37 | double im = imag; 38 | 39 | for (int i = 0; i < limit; ++i) { 40 | double r2 = re * re; 41 | double i2 = im * im; 42 | 43 | if (broke_free(r2, i2, Foo{.x = r2, .y = i2})) return Iterations { i }; 44 | 45 | im = 2.0 * re * im + imag; 46 | re = r2 - i2 + real; 47 | } 48 | return Iterations { limit }; 49 | } 50 | 51 | // lets pretend this looks up cpus 52 | auto ncpus_to_use() { 53 | return 4; 54 | } 55 | 56 | 57 | 58 | void process_range(Surface surface, int y_start, int y_to) { 59 | const auto dx = (surface.x.max - surface.x.min) / static_cast(surface.width - 1); 60 | const auto dy = (surface.y.max - surface.y.min) / static_cast(surface.height - 1); 61 | auto copy = surface; 62 | // to test watch variables, set breakpoint on limit, and breakpoint on line 86, add watch variable copy.x (or copy.y). Then run and when stopped and select different threads 63 | int limit = 1200; 64 | auto escaped = 0; 65 | auto contained = 0; 66 | auto total = 0; 67 | const auto one_third = ((y_to - y_start) / 3) + y_start; 68 | const auto two_thirds = ((y_to - y_start) / 3) * 2 + y_start; 69 | bool hitOnce = false; 70 | for(auto x = 0; x < surface.width; x++) { 71 | for(auto y = y_start; y < y_to; ++y) { 72 | const auto r = mandelbrot(surface.x.min + x * dx , surface.y.max - y * dy, limit); 73 | if(r != limit) { 74 | contained++; 75 | } else { 76 | escaped++; 77 | } 78 | total++; 79 | if(y == one_third) { 80 | // this is for testing that watch variables work, and get updated, when different threads are selected. 81 | // to test: set a watch variable for copy.x or copy.y and see if it updates accordingly 82 | copy.x.max = y; 83 | copy.y.max = y; 84 | copy.x.min = y; 85 | copy.y.min = y; 86 | 87 | auto some_break_point_here2 = []{}; 88 | some_break_point_here2(); 89 | } 90 | if(y == two_thirds && !hitOnce) { 91 | hitOnce = true; 92 | auto some_break_point_here = []{}; 93 | some_break_point_here(); 94 | } 95 | } 96 | } 97 | { 98 | const std::lock_guard lock(g_stdio_mutex); 99 | std::cout << y_start << " -> " << y_to << " (" << total << ")" < env_variables; 107 | env_variables.reserve(10); 108 | 109 | const auto push_env_var_if = [&](auto env) { 110 | if(auto var = std::getenv(env); var) { 111 | env_variables.emplace_back(var); 112 | } 113 | }; 114 | 115 | push_env_var_if("PATH"); 116 | push_env_var_if("PWD"); 117 | push_env_var_if("USER"); 118 | push_env_var_if("USERNAME"); 119 | push_env_var_if("DISPLAY"); 120 | push_env_var_if("PATH"); 121 | push_env_var_if("SHELL"); 122 | push_env_var_if("HOME"); 123 | 124 | 125 | for(const auto& var : env_variables) { 126 | std::cout << var << std::endl; 127 | } 128 | } 129 | 130 | void process_tasks_and_run(int screen_width, int screen_height) { 131 | const auto jobs = ncpus_to_use(); 132 | const auto job_size = screen_height / jobs; 133 | std::vector tasks; 134 | tasks.reserve(jobs); 135 | const auto surface = Surface{ .width = screen_width, .height = screen_height, .x = {-2.0, 1.0 }, .y = { -1.0, 1.0 } }; 136 | for(auto i = 0; i < screen_height; i+=job_size) { 137 | tasks.push_back(std::thread{process_range, surface, i, i+job_size}); 138 | } 139 | std::cout << jobs << " jobs spun up" << std::endl; 140 | for(auto& t : tasks) t.join(); 141 | } 142 | 143 | auto test_evaluate_variables_when_passing_through_scopes() { 144 | std::cout << "in main, w and h are ints" << std::endl; 145 | float w = 3.14; 146 | float h = 66.6; 147 | std::cout << w << ", " << h << std::endl; 148 | } 149 | 150 | void tuple_tuples() { 151 | std::tuple, std::string> hmm{1, {2,3, "inner"}, "outer"}; 152 | std::cout << "tuples are... meh" << std::endl; 153 | } 154 | 155 | int main(int argc, const char **argv) { 156 | std::string buf; 157 | std::cout << "input some text:\n" << std::endl; 158 | std::getline(std::cin, buf); 159 | std::cout << "you wrote: " << buf << std::endl; 160 | std::string hw = "Hello World"; 161 | vecOfString(); 162 | tuple_tuples(); 163 | auto w = 200; 164 | auto h = 200; 165 | test_evaluate_variables_when_passing_through_scopes(); 166 | process_tasks_and_run(w, h); 167 | // lets be longer than a machine register 168 | static const auto foo = "foobar is something to say"; 169 | static constexpr auto bar = "saying barfoo is something nobody does"; 170 | constexpr auto baz = "baz is also kind of a cool word!!!!!!!!!!!!!!!"; 171 | constexpr const char *bazchar = "These types end up being wildly different"; 172 | std::cout << "Goodbye cruel world" << std::endl; 173 | } 174 | -------------------------------------------------------------------------------- /test/cppworkspace/simple_input/src/todo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int Todo::s_todo_id = 0; 4 | 5 | Todo::Todo(const char* title, Date date) : m_id(s_todo_id++), m_date(date), m_title(title) {} 6 | 7 | int Todo::todo_count() { 8 | return s_todo_id; 9 | } 10 | 11 | std::string_view Todo::title() const { return m_title; } 12 | int Todo::id() const { return m_id; } 13 | const Date& Todo::date() const { return m_date; } -------------------------------------------------------------------------------- /test/cppworkspace/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(test) 3 | set(CMAKE_CXX_STANDARD 20) 4 | 5 | add_executable(test ./src/main.cpp ./src/testcase_namespaces/enum.cpp ./src/testcase_namespaces/test_ptrs.cpp ./src/testcase_namespaces/baseclasses.cpp ./src/testcase_namespaces/longstack.cpp ./src/testcase_namespaces/statics.cpp ./src/testcase_namespaces/structrequests.cpp ./src/testcase_namespaces/derive.cpp ./src/todo.cpp ./src/testcase_namespaces/pp.cpp ./src/testcase_namespaces/test_freefloating_watch.cpp src/testcase_namespaces/exceptions.cpp) 6 | target_include_directories(test PUBLIC ../include) 7 | 8 | # target_compile_options(test PUBLIC $<$:${DEBUG_SETTINGS}>) 9 | # target_compile_options(test PUBLIC $<$:${DEBUG_SETTINGS}>) 10 | target_compile_options(test PUBLIC ${DEBUG_SETTINGS}) 11 | target_compile_features(test PUBLIC cxx_std_17) -------------------------------------------------------------------------------- /test/cppworkspace/test/README.md: -------------------------------------------------------------------------------- 1 | # For testing of GDB 2 | 3 | Building a debug build with cmake: 4 | 5 | ```bash 6 | # While in test folder 7 | mkdir build 8 | cd build 9 | cmake .. -DCMAKE_BUILD_TYPE=Debug 10 | cmake --build . 11 | ``` 12 | -------------------------------------------------------------------------------- /test/cppworkspace/test/src/date.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | std::ostream& operator<<(std::ostream& os, const Date& date) { 3 | } 4 | -------------------------------------------------------------------------------- /test/cppworkspace/test/src/testcase_namespaces/baseclasses.cpp: -------------------------------------------------------------------------------- 1 | #include "baseclasses.hpp" 2 | namespace baseclasses 3 | { 4 | void main() { 5 | Bar* a = new Bar{10}; 6 | Bar* someLongBar = new Bar{20}; 7 | Bar cSomeLongBarValue{10}; 8 | Bar d{20}; 9 | Zoo z{10}; 10 | Zoo* someLongerZooName = new Zoo{10}; 11 | Zoo* y = new Zoo{20}; 12 | Tricky t{1337}; 13 | Tricky* tp = new Tricky{42}; 14 | Tricky* nptr = nullptr; 15 | } 16 | } // namespace baseclasses 17 | -------------------------------------------------------------------------------- /test/cppworkspace/test/src/testcase_namespaces/baseclasses.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace baseclasses { 4 | struct Foo { 5 | Foo(int i) : foo_value(i) { } 6 | int foo_value; 7 | }; 8 | 9 | struct Bar : public Foo { 10 | Bar(int I): Foo(I*2), bar_value(I) { } 11 | int bar_value; 12 | }; 13 | 14 | struct Quux : public Bar { 15 | Quux(int I): Bar(I*2), quux_value(I) { } 16 | int quux_value; 17 | }; 18 | 19 | struct Baz { 20 | Baz(int k) : baz_value(k) { } 21 | int baz_value; 22 | }; 23 | 24 | struct Zoo : public Baz, public Quux { 25 | Zoo(int i) : Baz(i + 2), Quux(i * 2), zoo_value(i) { } 26 | int zoo_value; 27 | }; 28 | 29 | struct Tricky : public Bar { 30 | Tricky(int tricky) : Bar(tricky * 2), tricky(tricky), q{tricky * 42} {} 31 | Quux q; 32 | int tricky; 33 | }; 34 | 35 | void main(); 36 | } 37 | 38 | -------------------------------------------------------------------------------- /test/cppworkspace/test/src/testcase_namespaces/derive.cpp: -------------------------------------------------------------------------------- 1 | #include "derive.hpp" 2 | 3 | namespace derive 4 | { 5 | void take_interface(Base *b) 6 | { 7 | b->sayHello(); 8 | std::cout << "good bye" << std::endl; 9 | } 10 | 11 | void two_impls() 12 | { 13 | Base *ba = new Derived{"foo"}; 14 | IntDerived *bb = new IntDerived{42}; 15 | bb->foo(); 16 | take_interface(ba); 17 | take_interface(bb); 18 | } 19 | 20 | void testFinalDerived() 21 | { 22 | auto f = new Final{10, 1}; 23 | f->sayHello(); 24 | std::cout << "say hello, through interface" << std::endl; 25 | take_interface(f); 26 | } 27 | 28 | void main() 29 | { 30 | two_impls(); 31 | testFinalDerived(); 32 | } 33 | } // namespace derive 34 | -------------------------------------------------------------------------------- /test/cppworkspace/test/src/testcase_namespaces/derive.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace derive 6 | { 7 | static int ids = 0; 8 | struct Base 9 | { 10 | protected: 11 | int id; 12 | std::string name; 13 | 14 | public: 15 | Base(int id, std::string name) : id(id), name(std::move(name)) {} 16 | virtual ~Base() {} 17 | virtual void sayHello() = 0; 18 | }; 19 | 20 | struct Derived : Base 21 | { 22 | Derived(std::string sub_name) : Base(ids++, "Derived"), sub_name(std::move(sub_name)) {} 23 | ~Derived() = default; 24 | std::string sub_name; 25 | void sayHello() override 26 | { 27 | std::cout << "[ID: " << this->id << "]: Hello my name is: " << this->name << ", " << this->sub_name << std::endl; 28 | } 29 | }; 30 | 31 | struct IntDerived : Base 32 | { 33 | IntDerived(int sub_id) : Base(ids++, "Derived"), sub_id(sub_id) {} 34 | virtual ~IntDerived() = default; 35 | int sub_id; 36 | void sayHello() override 37 | { 38 | std::cout << "[ID: " << this->id << ":" << this->sub_id << "]: Hello my name is: " << this->name << std::endl; 39 | } 40 | 41 | void foo() 42 | { 43 | std::cout << "[ID: " << this->id << ":" << this->sub_id << "]: Hello my name is: " << this->name << std::endl; 44 | } 45 | }; 46 | 47 | struct Final : public IntDerived 48 | { 49 | int m_k; 50 | Final(int k, int sub) : IntDerived(sub), m_k(k) 51 | { 52 | } 53 | 54 | void sayHello() override 55 | { 56 | std::cout << "[ID: " << this->id << ":" << this->sub_id << "]: Hello my name is: " << this->name << " and I am derived of a derived. Value: " << m_k << std::endl; 57 | } 58 | }; 59 | 60 | void main(); 61 | } // namespace derive 62 | -------------------------------------------------------------------------------- /test/cppworkspace/test/src/testcase_namespaces/enum.cpp: -------------------------------------------------------------------------------- 1 | #include "enum.hpp" 2 | 3 | FooEnum enum_stuff() { 4 | FooEnum f = FooEnum::Bar; 5 | auto e = f; 6 | return e; 7 | } -------------------------------------------------------------------------------- /test/cppworkspace/test/src/testcase_namespaces/enum.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | enum class FooEnum { 3 | Bar, 4 | Baz, 5 | Quux 6 | }; 7 | 8 | FooEnum enum_stuff(); -------------------------------------------------------------------------------- /test/cppworkspace/test/src/testcase_namespaces/exceptions.cpp: -------------------------------------------------------------------------------- 1 | #include "exceptions.hpp" 2 | #include 3 | #include 4 | 5 | namespace exceptions { 6 | void main(int i) { 7 | try { 8 | if (i < 10) { 9 | throw std::runtime_error{"i is below 10"}; 10 | } 11 | } catch (std::exception &e) { 12 | std::cout << "exception caught: " << e.what() << std::endl; 13 | } 14 | 15 | // un caught exception 16 | if (i < 5) { 17 | throw std::runtime_error{"i below 5"}; 18 | } 19 | } 20 | } // namespace exceptions -------------------------------------------------------------------------------- /test/cppworkspace/test/src/testcase_namespaces/exceptions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace exceptions { 4 | void main(int i); 5 | } -------------------------------------------------------------------------------- /test/cppworkspace/test/src/testcase_namespaces/longstack.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace longstack 4 | { 5 | void doNothing3() { 6 | std::cout << "reached the peak" << std::endl; 7 | } 8 | 9 | void doNothing2() { 10 | doNothing3(); 11 | } 12 | 13 | void doNothing() { 14 | doNothing2(); 15 | } 16 | 17 | int chain25(int v) 18 | { 19 | int result = 24; 20 | result = v / -5; 21 | result += 5; 22 | doNothing(); 23 | return result; 24 | } 25 | 26 | int chain24(int v) 27 | { 28 | int result = 23; 29 | result = chain25(v * -5); 30 | result += 5; 31 | return result; 32 | } 33 | 34 | int chain23(int v) 35 | { 36 | int result = 22; 37 | result = chain24(v * -4); 38 | result += 5; 39 | return result; 40 | } 41 | 42 | int chain22(int v) 43 | { 44 | int result = 21; 45 | result = chain23(v * -3); 46 | result += 5; 47 | return result; 48 | } 49 | 50 | int chain21(int v) 51 | { 52 | int result = 20; 53 | result = chain22(v * -2); 54 | result += 5; 55 | return result; 56 | } 57 | 58 | int chain20(int v) 59 | { 60 | int result = 19; 61 | result = chain21(v * -1); 62 | result += 5; 63 | return result; 64 | } 65 | 66 | int chain19(int v) 67 | { 68 | int result = 18; 69 | result = chain20(v - 10); 70 | result += 5; 71 | return result; 72 | } 73 | 74 | int chain18(int v) 75 | { 76 | int result = 17; 77 | result = chain19(v - 9); 78 | result += 5; 79 | return result; 80 | } 81 | 82 | int chain17(int v) 83 | { 84 | int result = 16; 85 | result = chain18(v - 8); 86 | result += 5; 87 | return result; 88 | } 89 | 90 | int chain16(int v) 91 | { 92 | int result = 15; 93 | result = chain17(v - 7); 94 | result += 5; 95 | return result; 96 | } 97 | 98 | int chain15(int v) 99 | { 100 | int result = 14; 101 | result = chain16(v - 6); 102 | result += 5; 103 | return result; 104 | } 105 | 106 | int chain14(int v) 107 | { 108 | int result = 13; 109 | result = chain15(v - 5); 110 | result += 5; 111 | return result; 112 | } 113 | 114 | int chain13(int v) 115 | { 116 | int result = 12; 117 | result = chain14(v - 4); 118 | result += 5; 119 | return result; 120 | } 121 | 122 | int chain12(int v) 123 | { 124 | int result = 11; 125 | result = chain13(v - 3); 126 | result += 5; 127 | return result; 128 | } 129 | 130 | int chain11(int v) 131 | { 132 | int result = 10; 133 | result = chain12(v); 134 | result += 5; 135 | return result; 136 | } 137 | 138 | int chain10(int v) 139 | { 140 | int result = 9; 141 | result = chain11(v / 2); 142 | result += 5; 143 | return result; 144 | } 145 | 146 | int chain9(int v) 147 | { 148 | int result = 8; 149 | result = chain10(v / 3); 150 | result += 5; 151 | return result; 152 | } 153 | 154 | int chain8(int v) 155 | { 156 | int result = 7; 157 | result = chain9(v / 4); 158 | result += 5; 159 | return result; 160 | } 161 | 162 | int chain7(int v) 163 | { 164 | int result = 6; 165 | result = chain8(v / 5); 166 | result += 5; 167 | return result; 168 | } 169 | 170 | int chain6(int v) 171 | { 172 | int result = 5; 173 | result = chain7(v / 6); 174 | result += 5; 175 | return result; 176 | } 177 | 178 | int chain5(int v) 179 | { 180 | int result = 4; 181 | result = chain6(v * 6); 182 | result += 5; 183 | return result; 184 | } 185 | 186 | int chain4(int v) 187 | { 188 | int result = 3; 189 | result = chain5(v * 5); 190 | result += 4; 191 | return result; 192 | } 193 | 194 | int chain3(int v) 195 | { 196 | int result = 2; 197 | result = chain4(v * 4); 198 | result += 3; 199 | return result; 200 | } 201 | 202 | int chain2(int v) 203 | { 204 | int result = 1; 205 | result = chain3(v * 3); 206 | result += 2; 207 | return result; 208 | } 209 | 210 | int start_stackchain(int v) 211 | { 212 | int result = 0; 213 | result = chain2(v * 2); 214 | return result + 1; 215 | } 216 | 217 | void main() 218 | { 219 | const auto result = start_stackchain(10); 220 | std::cout << "result " << result << std::endl; 221 | } 222 | } -------------------------------------------------------------------------------- /test/cppworkspace/test/src/testcase_namespaces/longstack.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace longstack 4 | { 5 | int chain25(int v); 6 | int chain24(int v); 7 | int chain23(int v); 8 | int chain22(int v); 9 | int chain21(int v); 10 | int chain20(int v); 11 | int chain19(int v); 12 | int chain18(int v); 13 | int chain17(int v); 14 | int chain16(int v); 15 | int chain15(int v); 16 | int chain14(int v); 17 | int chain13(int v); 18 | int chain12(int v); 19 | int chain11(int v); 20 | int chain10(int v); 21 | int chain9(int v); 22 | int chain8(int v); 23 | int chain7(int v); 24 | int chain6(int v); 25 | int chain5(int v); 26 | int chain4(int v); 27 | int chain3(int v); 28 | int chain2(int v); 29 | int start_stackchain(int v); 30 | void main(); 31 | } -------------------------------------------------------------------------------- /test/cppworkspace/test/src/testcase_namespaces/pp.cpp: -------------------------------------------------------------------------------- 1 | #include "pp.hpp" 2 | #include 3 | 4 | namespace prettyprinting { 5 | bank_account::bank_account(int id, std::string name, float rate) : base_t(new base_t::base_tuple{id, name, rate}) { } 6 | 7 | void bank_account::print_values() const { 8 | const base_t::base_tuple& t = *base_t::ts; 9 | const auto& [id, name, rate] = t; 10 | std::cout << "id: " << id; 11 | std::cout.flush(); 12 | std::cout << " Account owner: " << name; 13 | std::cout.flush(); 14 | std::cout << " at rate: " << (100.0f * rate) - 100.0f << "%" << std::endl; 15 | } 16 | 17 | person_t::person_t(int id, std::string name, bank_account* acc) : id(id), name(name), account(acc) {} 18 | 19 | employee_t::employee_t(int id, std::string name, bank_account* acc, std::string position) : person_t(id, name, acc), position(position) {} 20 | 21 | struct Hidden { 22 | int i, j; 23 | }; 24 | 25 | struct Ref { 26 | int& intRef; 27 | }; 28 | 29 | void main() { 30 | std::tuple