├── .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 |
--------------------------------------------------------------------------------
/assets/icons/run-to-event.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 | 
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 | 
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 |
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 tup{42, Hidden{1,2}};
31 | auto&[id, hidden] = tup;
32 | Ref intRef{id};
33 | id++;
34 | bank_account b{1, "john doe", 1.05f};
35 | b.print_values();
36 | employee_t janedoe{2, "jane doe", new bank_account{2, "jane doe", 1.08f}, "manager"};
37 | janedoe.account->print_values();
38 | std::cout << "closing for the day" << std::endl;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/cppworkspace/test/src/testcase_namespaces/pp.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // Types to check if pretty printing works properly for all scenarios
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | namespace prettyprinting {
10 | // These types are not meant to be good design.
11 | // In fact they're meant to be as convoluted as possible
12 | template
13 | struct base_t {
14 | using base_tuple = std::tuple;
15 | base_t(base_tuple* t) : ts(t) {}
16 | base_t(base_t&&) = default;
17 | virtual ~base_t() {
18 | std::cout << "destroy base_t" << std::endl;
19 | if(ts)
20 | delete ts;
21 | }
22 |
23 | virtual void print_values() const = 0;
24 | base_tuple* ts = nullptr;
25 | };
26 |
27 | struct bank_account : public base_t {
28 | bank_account(int, std::string, float);
29 | virtual ~bank_account() {}
30 | void print_values() const override;
31 | };
32 |
33 | struct person_t {
34 | person_t(int id, std::string name, bank_account* acc);
35 | virtual ~person_t() {
36 | delete account;
37 | }
38 |
39 | int id;
40 | std::string name;
41 | bank_account* account;
42 | };
43 |
44 | struct employee_t : public person_t {
45 | employee_t(int id, std::string name, bank_account* acc, std::string position);
46 | virtual ~employee_t() = default;
47 | std::string position;
48 | };
49 |
50 | void main();
51 | }
52 |
--------------------------------------------------------------------------------
/test/cppworkspace/test/src/testcase_namespaces/statics.cpp:
--------------------------------------------------------------------------------
1 | #include "statics.hpp"
2 | #include "todo.hpp"
3 | namespace statics
4 | {
5 | int Statics::sk = 42;
6 | int *Statics::p_sk = new int{142};
7 |
8 | Todo Statics::stodo = Todo{"Static Todo", Date{.day = 4, .month = 2, .year = 2022}};
9 | Todo *Statics::p_stodo = new Todo{"Static pointer to Todo", Date{.day = 4, .month = 2, .year = 2022}};
10 |
11 | void main() {
12 | static auto sStatic = new statics::Statics{1337,42, "Static static all the way statics::statics"};
13 | statics::Statics* sOne = new statics::Statics{1,2, "Statics one"};
14 | statics::Statics* sTwo = new statics::Statics{100,200, "Statics Two"};
15 | Date d{.day = 20, .month = 2, .year = 2022};
16 | Todo::post_pone(sTwo->p_stodo, d);
17 | }
18 | } // namespace statics
--------------------------------------------------------------------------------
/test/cppworkspace/test/src/testcase_namespaces/statics.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | class Todo;
4 | namespace statics
5 | {
6 | struct Statics
7 | {
8 | int i;
9 | int j;
10 |
11 | Statics(int i, int j, std::string name) : i(i), j(j), m_name{std::move(name)} {}
12 |
13 | static int sk;
14 | static int *p_sk;
15 | static Todo stodo;
16 | static Todo *p_stodo;
17 |
18 | const std::string &get_name() const
19 | {
20 | return this->m_name;
21 | }
22 |
23 | private:
24 | std::string m_name;
25 | };
26 |
27 | void main();
28 | } // namespace statics
29 |
--------------------------------------------------------------------------------
/test/cppworkspace/test/src/testcase_namespaces/structrequests.cpp:
--------------------------------------------------------------------------------
1 | #include "structrequests.hpp"
2 |
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 |
9 | namespace structsrequests
10 | {
11 | Struct variablesRequestTest(Struct s)
12 | {
13 | // set first breakpoint here
14 | const auto new_i = s.i + 10;
15 | const auto new_f = s.f + 10.10f;
16 | s.i = new_i;
17 | s.f = new_f;
18 | return s;
19 | }
20 |
21 | void variablesRequestTestReference(Struct &s)
22 | {
23 | const auto i = s.i + 10;
24 | const auto f = s.f + 10.10f;
25 | s.i = i;
26 | s.f = f;
27 | }
28 |
29 | int testSubChildUpdate(Bar *b)
30 | {
31 | b->j++;
32 | variablesRequestTestReference(*b->s);
33 | auto res = b->s->f;
34 | return res;
35 | }
36 |
37 | void variablesRequestTestPointer(Struct *s)
38 | {
39 | auto local_ptr = s;
40 | const auto i = local_ptr->i + 10;
41 | const auto f = local_ptr->f + 10.10f;
42 | variablesRequestTestReference(*s);
43 | local_ptr->i += i;
44 | local_ptr->f += f;
45 | variablesRequestTestReference(*s);
46 | local_ptr = nullptr;
47 | }
48 |
49 | void do_todo(Todo &t)
50 | {
51 | std::cout << t.title() << std::endl;
52 | }
53 |
54 | Todo move_todo(Todo &&t)
55 | {
56 | std::cout << t.title() << std::endl;
57 | return t;
58 | }
59 |
60 | void main()
61 | {
62 | Todo tmp{"Test local struct", Date{.day = 20, .month = 2, .year = 2022}};
63 | auto d = tmp.date();
64 | auto tmpptr = new Todo{"Pointer to Todo", Date{.day = 25, .month = 1, .year = 2022}};
65 | Foo f{.name = "hello world", .k = 10};
66 | int i = 0;
67 | Todo::post_pone(tmpptr, d);
68 | auto somestruct = new Struct{.i = 10, .f = 10.10f, .name = "somestruct"};
69 | auto copied_somestruct = variablesRequestTest(*somestruct);
70 | variablesRequestTestPointer(&copied_somestruct);
71 | auto barptr = new Bar{.j = 100, .s = new Struct{.i = 10, .f = 10.10f, .name = "somestruct_refByBar"}};
72 | i = testSubChildUpdate(barptr);
73 | do_todo(*tmpptr);
74 | auto a = move_todo(std::move(tmp));
75 |
76 | std::vector todos{};
77 | todos.push_back(Todo{"Make test app for debugger extension",
78 | Date{.day = 3, .month = 11, .year = 2021}});
79 | todos.push_back(Todo{"Read code-debug & look for useful stuff",
80 | Date{.day = 4, .month = 11, .year = 2021}});
81 | todos.push_back(Todo{"Read vscode-mock-debug & rip out things of use",
82 | Date{.day = 5, .month = 11, .year = 2021}});
83 |
84 | std::cout << "Things to do: " << Todo::todo_count() << std::endl;
85 | for (const auto &t : todos)
86 | {
87 | std::cout << "\tTodo id " << t.id() << ": " << t.title() << " @" << t.date()
88 | << std::endl;
89 | }
90 | } // namespace structsrequests
91 | }
--------------------------------------------------------------------------------
/test/cppworkspace/test/src/testcase_namespaces/structrequests.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | namespace structsrequests
3 | {
4 | struct Struct
5 | {
6 | int i;
7 | float f;
8 | const char *name;
9 | };
10 |
11 | struct Bar
12 | {
13 | int j = 0;
14 | Struct *s;
15 | };
16 |
17 | struct Foo
18 | {
19 | const char *name;
20 | int k;
21 | };
22 |
23 | Struct variablesRequestTest(Struct s);
24 | void variablesRequestTestReference(Struct &s);
25 | int testSubChildUpdate(Bar *b);
26 | void variablesRequestTestPointer(Struct *s);
27 | void main();
28 | } // namespace structsrequests
29 |
--------------------------------------------------------------------------------
/test/cppworkspace/test/src/testcase_namespaces/test_freefloating_watch.cpp:
--------------------------------------------------------------------------------
1 | #include "test_freefloating_watch.hpp"
2 | #include
3 | namespace freefloating_watch {
4 | Widget::Widget(int w, int h, int x, int y, AppState *app_state)
5 | : w(w), h(h), x(x), y(y), app_state(app_state) {}
6 |
7 | AppState::AppState(int win_width, int win_height, std::string title, int* children)
8 | : window{.w = win_width,
9 | .h = win_height,
10 | .x = 0,
11 | .y = 0,
12 | .title = {.title = title, .needs_update = false},
13 | .app_state = nullptr},
14 | p_child_identifiers(children) {
15 | this->window.app_state = this;
16 | this->widget = new Widget{10, 10, 0, 0, this};
17 | }
18 |
19 | void update_title(Title &title, std::string new_title) {
20 | title.title = new_title;
21 | title.needs_update = true;
22 | }
23 |
24 | void update_window(Window &window) {
25 | update_title(window.title, "Hello world");
26 | window.x += 10;
27 | window.y += 100;
28 | }
29 |
30 | void do_app_stuff(AppState *state) {
31 | update_window(state->window);
32 | state->p_child_identifiers[0] = 0;
33 | state->p_child_identifiers[5] = 5;
34 | state->p_child_identifiers[9] = 9;
35 | }
36 | void main() {
37 | // when in update title, we should be able to lock the watch
38 | // variable "app_state" to this scope and be able to watch it from `update_title`
39 | // `update_window` and `do_app_stuff`
40 | auto children = new int[10];
41 | auto idx = 0;
42 | std::span span{children, 10};
43 | for(auto& i : span) {
44 | i = idx * (2 + idx);
45 | idx++;
46 | }
47 | auto app_state = new AppState{100, 100, "Foo bar", children};
48 | do_app_stuff(app_state);
49 | }
50 | } // namespace freefloating_watch
--------------------------------------------------------------------------------
/test/cppworkspace/test/src/testcase_namespaces/test_freefloating_watch.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 |
4 |
5 | namespace freefloating_watch {
6 | struct AppState;
7 |
8 | struct Title {
9 | std::string title;
10 | bool needs_update;
11 | };
12 |
13 | struct Window {
14 | int w, h;
15 | int x, y;
16 | Title title;
17 | AppState *app_state;
18 | };
19 |
20 | struct Widget {
21 | Widget(int w, int h, int x, int y, AppState *app_state);
22 | int w, h, x, y;
23 | AppState *app_state;
24 | };
25 |
26 | struct AppState {
27 | AppState(int win_width, int win_height, std::string title, int* children);
28 | Window window;
29 | Widget *widget;
30 | int* p_child_identifiers = nullptr;
31 | };
32 |
33 | void update_title(Title &title, std::string new_title);
34 | void update_window(Window &window);
35 | void do_app_stuff(AppState *state);
36 |
37 | void main();
38 | } // namespace freefloating_watch
39 |
--------------------------------------------------------------------------------
/test/cppworkspace/test/src/testcase_namespaces/test_ptrs.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include