Repository: cs01/gdbgui Branch: master Commit: 773b9161729e Files: 143 Total size: 526.7 KB Directory structure: gitextract_zcfu_6aw/ ├── .eslintrc.json ├── .flake8 ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── build_executable.yml │ └── tests.yml ├── .gitignore ├── .prettierrc.js ├── .vscode/ │ └── settings.json ├── .vulture_whitelist.py ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs/ │ ├── CNAME │ ├── api.md │ ├── contact.md │ ├── examples.md │ ├── faq.md │ ├── gettingstarted.md │ ├── guides.md │ ├── howitworks.md │ ├── index.md │ ├── installation.md │ └── screenshots.md ├── examples/ │ ├── .gitignore │ ├── README.md │ ├── c/ │ │ ├── debug_segfault.c │ │ ├── hello.c │ │ ├── input.c │ │ ├── makefile │ │ ├── sleeper.c │ │ ├── threads.c │ │ └── tree.c │ ├── cpp/ │ │ ├── hello.cpp │ │ ├── linked_list.cpp │ │ ├── makefile │ │ ├── sin.cpp │ │ └── smart_ptr_demo.cpp │ ├── fortran/ │ │ ├── fortran_array.f90 │ │ └── makefile │ ├── golang/ │ │ ├── hello.go │ │ └── makefile │ └── rust/ │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── compile_and_debug.sh │ └── src/ │ └── main.rs ├── gdbgui/ │ ├── SSLify.py │ ├── VERSION.txt │ ├── __init__.py │ ├── __main__.py │ ├── cli.py │ ├── htmllistformatter.py │ ├── py.typed │ ├── server/ │ │ ├── __init__.py │ │ ├── app.py │ │ ├── constants.py │ │ ├── http_routes.py │ │ ├── http_util.py │ │ ├── ptylib.py │ │ ├── server.py │ │ └── sessionmanager.py │ ├── src/ │ │ └── js/ │ │ ├── Actions.ts │ │ ├── BinaryLoader.tsx │ │ ├── Breakpoints.tsx │ │ ├── ControlButtons.tsx │ │ ├── CopyToClipboard.tsx │ │ ├── Expressions.tsx │ │ ├── FileOps.tsx │ │ ├── FileSystem.tsx │ │ ├── FoldersView.tsx │ │ ├── GdbApi.tsx │ │ ├── GdbMiOutput.tsx │ │ ├── GdbVariable.tsx │ │ ├── GdbguiModal.tsx │ │ ├── GlobalEvents.ts │ │ ├── HoverVar.tsx │ │ ├── InferiorProgramInfo.tsx │ │ ├── InitialStoreData.ts │ │ ├── Links.tsx │ │ ├── Locals.tsx │ │ ├── Memory.tsx │ │ ├── MemoryLink.tsx │ │ ├── MiddleLeft.tsx │ │ ├── ReactTable.tsx │ │ ├── Registers.tsx │ │ ├── RightSidebar.tsx │ │ ├── Settings.tsx │ │ ├── SourceCode.tsx │ │ ├── SourceCodeHeading.tsx │ │ ├── SourceFileAutocomplete.tsx │ │ ├── StatusBar.tsx │ │ ├── Terminals.tsx │ │ ├── Threads.tsx │ │ ├── ToolTip.tsx │ │ ├── ToolTipTourguide.tsx │ │ ├── TopBar.tsx │ │ ├── Tree.ts │ │ ├── Util.ts │ │ ├── constants.ts │ │ ├── dashboard.tsx │ │ ├── gdbgui.tsx │ │ ├── processFeatures.ts │ │ ├── process_gdb_response.tsx │ │ ├── register_descriptions.ts │ │ ├── tests/ │ │ │ └── Util.jest.ts │ │ └── types.d.ts │ ├── static/ │ │ ├── css/ │ │ │ ├── gdbgui.css │ │ │ ├── splitjs-gdbgui.css │ │ │ └── tailwind.css │ │ └── vendor/ │ │ ├── css/ │ │ │ ├── animate.css │ │ │ ├── gdbgui_awesomeplete.css │ │ │ └── pygments/ │ │ │ ├── emacs.css │ │ │ ├── light.css │ │ │ ├── monokai.css │ │ │ └── vim.css │ │ └── js/ │ │ └── splitjs.min-1.2.0.js │ └── templates/ │ ├── dashboard.html │ └── gdbgui.html ├── images/ │ ├── gdbgui.xcf │ ├── gdbgui_small.xcf │ └── gdbgui_square.xcf ├── jest.config.js ├── make_executable.py ├── mkdocs.yml ├── noxfile.py ├── package.json ├── postcss.config.js ├── requirements.in ├── requirements.txt ├── setup.py ├── tailwind.config.js ├── tests/ │ ├── __init__.py │ ├── test_backend.py │ ├── test_cli.py │ ├── test_ptylib.py │ └── test_sessionmanager.py ├── tsconfig.json ├── tslint.json └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.json ================================================ { "env": { "browser": true, "es6": true, "jquery": true }, "globals": { "initial_data": true, "module": true, "_": true, "moment": true }, "extends": "eslint:recommended", "parserOptions": { "ecmaFeatures": { "experimentalObjectRestSpread": true, "jsx": true }, "sourceType": "module" }, "plugins": ["react"], "rules": { "no-console": [0], "react/jsx-uses-vars": 1 }, "parser": "babel-eslint" } ================================================ FILE: .flake8 ================================================ [flake8] max-line-length = 88 ignore = E501, E203, W503, E402, E231 # line length, whitespace before ':', line break before binary operator, module level import not at top of file ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Please complete the following information:** * OS: * gdbgui version (`gdbgui -v`): * gdb version (`gdb -v`): * browser [e.g. chrome, safari]: * python packages (`pip freeze`): **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ - [] I have added an entry to `CHANGELOG.md`, or an entry is not needed for this change ## Summary of changes ## Test plan Tested by running ``` # command(s) to exercise these changes ``` ================================================ FILE: .github/workflows/build_executable.yml ================================================ name: Build native gdbgui executables with pyinstaller and pex on: pull_request: push: branches: - master release: jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest] python-version: ["3.13"] include: - os: ubuntu-latest buildname: linux # - os: windows-latest # buildname: windows - os: macos-latest buildname: mac steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install nox - name: Compile ${{ matrix.buildname }} gdbgui executable run: | nox --non-interactive --session build_executables_${{ matrix.buildname }} - name: Upload ${{ matrix.buildname }} executable uses: actions/upload-artifact@v4 with: name: gdbgui_${{ matrix.buildname }} path: ./build/executable if-no-files-found: error # Optional: warn, error, or ignore retention-days: 90 # Optional: 1-90 days, or repo default ================================================ FILE: .github/workflows/tests.yml ================================================ # https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions name: Tests on: pull_request: push: branches: - master release: jobs: run_tests: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest] python-version: ["3.13"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install nox - name: Install gdb ubuntu run: | sudo apt update sudo apt upgrade sudo apt install gdb - name: Execute Tests run: | nox --non-interactive --session tests-${{ matrix.python-version }} build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest] python-version: ["3.13"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install nox - name: Execute Tests run: | nox --non-interactive --session build lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: 3.13 - name: Install dependencies run: | python -m pip install --upgrade pip pip install nox - name: Lint run: | nox --non-interactive --session lint docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: "3.13" - name: Install dependencies run: | python -m pip install --upgrade pip pip install nox - name: Verify Docs run: | nox --non-interactive --session docs ================================================ FILE: .gitignore ================================================ dist *egg* node_modules build executable .DS_Store *.a.dSYM gdbgui_pyinstaller.spec *-link *-link.c *-link.dSYM *.pyc yarn-error.log venv site gdbgui/static/js/* __pycache__ .coverage* ================================================ FILE: .prettierrc.js ================================================ module.exports = { printWidth: 90, } ================================================ FILE: .vscode/settings.json ================================================ { "python.formatting.provider": "none", "[python]": { "editor.formatOnSave": true, "editor.defaultFormatter": "ms-python.black-formatter" }, "[json]": { "editor.formatOnSave": true }, "files.associations": { "*.spec": "python" } } ================================================ FILE: .vulture_whitelist.py ================================================ _.sessions # unused attribute (noxfile.py:7) _.reuse_existing_virtualenvs # unused attribute (noxfile.py:6) _.secret_key # unused attribute (gdbgui/backend.py:104) _.reuse_existing_virtualenvs # unused attribute (noxfile.py:6) _.sessions # unused attribute (noxfile.py:7) cover # unused function (noxfile.py:50) lint # unused function (noxfile.py:78) autoformat # unused function (noxfile.py:94) docs # unused function (noxfile.py:103) develop # unused function (noxfile.py:109) serve # unused function (noxfile.py:118) publish # unused function (noxfile.py:133) watch_docs # unused function (noxfile.py:142) build_executable_current_platform # unused function (noxfile.py:154) build_executable_mac # unused function (noxfile.py:162) build_executable_linux # unused function (noxfile.py:169) build_executable_windows # unused function (noxfile.py:176) on_connect # unused function (tests/test_backend.py:14) monkeypatch # unused variable (tests/test_cli.py:23) monkeypatch # unused variable (tests/test_cli.py:33) monkeypatch # unused variable (tests/test_cli.py:43) ================================================ FILE: CHANGELOG.md ================================================ # gdbgui release history ## 0.15.3.0 - Update default python version to 3.13 ## 0.15.2.0 - Update default python version to 3.12 - utf-8 decode error bugfix - fix registers cannot be displayed bug ## 0.15.1.0 - Compatibility with Werkzeug 2.1. Use the eventlet server instead of the Werkzeug development server. - Use pinned requirements instead of abstract requirements to ensure reproducability of pip installs ## 0.15.0.1 This release has no changes to features or usability. The only change is to include a file used by other package maintainers. - Include all files needed to rebuild from source (#403) ## 0.15.0.0 This release is focused mostly on Python 3.9 compatibility and updating dependencies - Support only Python 3.9 (though other Python versions may still work) - Build gdbgui as a [pex](https://pypi.org/project/pex/) executable. - These are executable Python environments that are self-contained with the exception of requiring a specific Python version installed in the environment running the executable. The pex executables should have better compatibility than PyInstaller executables, which sometimes have missing shared libraries depending on the operating system. - Use only the threading async model for flask-socketio. No longer support gevent or eventlet. - [bugfix] Catch exception if gdb used in tty window crashes instead of gdbgui crashing along with it - Disable pagination in gdb tty by default. It can be turned back on with `set pagination off`. - Upgrade various dependencies for both the backend and frontend (Python and JavaScript) - Display gdbgui version in "about" and "session information" ## 0.14.0.2 - Pinned python-socketio version - Pinned mypy version to unbreak linting - Fixed reverse debugging commands that were broken when `--gdb` flag was removed ## 0.14.0.1 - Fix import paths - Pin broken dependency to avoid segfault - Hide "No registers." message ## 0.14.0.0 **Breaking Changes** - Removed support for Windows - Replaced `--gdb` flag with `--gdb-cmd`. The `--gdb-cmd` argument specifies the gdb executable as well as all arguments you wish to pass to gdb at startup, for example `--gdb-cmd "gdb -nx"`. The existing `-g` argument is an alias for `--gdb-cmd`. - Removed `--rr` flag. Use `--gdb-cmd "rr replay --"` instead. - Removed deprecated and hidden `--hide-gdbgui-upgrades` argument. It will now raise an error. **Additional Changes** - Replaced single terminal on frontend with three terminals: an interactive xterm terminal running gdb, a gdbgui console for diagnostic messages, and a terminal connected to the inferior application being debugged. - Updates to the dashboard - Add ability to specify gdb command from the browser. This can now be accomplished from the dashboard. - Removed gdbgui binaries from source control. They can now be downloaded as artifacts of [releases](https://github.com/cs01/gdbgui/releases). - [documentation] Fix bug when generating md5 checksum for binary releases - Remove "shutdown" button in UI ## 0.13.2.1 - No end user changes. This release builds the gdbgui executables with GitHub actions. ## 0.13.2.0 - Print number of times a breakpoint was hit (@MatthiasKreileder). - Publish sdist to PyPI (this was overlooked in previous release). - Do not notify users of gdbgui upgrades (deprecate `--hide-gdbgui-upgrades` flag) - Drop support for Python 3.4 - [dev] Some infrastructure changes to gdbgui. End users should not be affected. - [dev] Fix build error due to webpack bug (https://github.com/webpack/webpack/issues/8082). ## 0.13.1.2 - Exclude "tests" directory from Python package - Remove analytics from documentation ## 0.13.1.1 - Add `__main__` entrypoint ## 0.13.1.0 - Remove automatic flushing of stdout and require newer version of pygdbmi - Add flake8 tests to CI build ## 0.13.0.0 - Add ability to re-map source file paths. Added flags `--remap-sources` and `-m` to replace compile-time source paths to local source paths. i.e. `gdbgui --remap-sources='{"/buildmachine": "/home/chad"}'` (#158) - Add shift keyboard shortcut to go in reverse when using rr (#201) - Pass arbitrary gdb arguments directly to gdb: added `--gdb-args` flag - Removed `-x` CLI option, which caused major version to change. New way to pass is `gdbgui --gdb-args='-x=FILE'` (#205) - Add "name" to Threads (new gdb 8.1 feature) (@P4Cu) - Fix crash/black screen from "Python Exception name long is not defined" #212 - Fix bug when debugging filenames with spaces (Fix Cannot create breakpoint: -break-insert: Garbage following #211") - Fix empty frame causes the ui to crash/black screen #216 - Update npm packages; update react to 16.4 - Update prettier rules - Update tour text + fix typo in tour (@nkirkby) ## 0.12.0.0 - Add pause button - Update command line parsing for cmd and --args, change arguments from underscore to hyphen, add option to specify browser (@fritzr) - Add tour - Run `set breakpoint pending on` on initial connection - Allow signal to be sent to arbitrary PIDs - Fix bug when sending signals in Python2 - Move signal component lower in side pane - Update Rust documentation - Make requirements.txt point to setup.py's dependencies ## 0.11.3.1 - Limit maximum Flask version to prevent `Session expired. Please refresh this webpage.` error - Rename "premium" to "ad-free" - Do smarter version checking - Fix bug when trying to view "about" ## 0.11.3.0 - ensure expressions with hex values are parsed and updated appropriately (#182) - improve command line arguments - use python logging module ## 0.11.2.1 - Small bugfix for specific platforms when reading version number ## 0.11.2.0 - add option to remove fflush command (#179) - remove react-treebeard and render filesystem w/ new component ## 0.11.1.1 - Bugfix displaying upgrade text ## 0.11.1.0 - Add csrf and cross origin protection - Convert backslashes to forward slashes when entering windows binary paths (#167) - Fix safari ui issue (#164) - Update text on reload file button, and disable when no file is loaded (#165) - When disassembly can't be fetched in mode 4, fetch in mode 3 and assume gdb version is 7.6.0 (#166) - Add copy to clipboard icon for files and variables - Allow SSL module import to fail and print warning (#170) - Cleanup menu, add license info, bugfixes, etc. (#169, #136, #163, #172) ## 0.11.0.0 - Replace `--auth` cli option with `--user` and `--password` ## 0.10.3.0 - Added resizer buttons to components on right pane ## 0.10.2.1 - Add link for fix for macOS users - Update version of React to 16.2 - Remove unused links ## 0.10.2.0 - Add folders view, rearrange layout (@martin-der) - Add settings cog button - Add message when sending signal to inferior process (#156) - Change default theme to monokai, rename 'default' theme to 'light' - Minor bugfixes ## 0.10.1.0 - Display descriptions of registers - Do not try to fetch Registers when they cannot be read ## 0.10.0.2 - Add support for rr (--rr flag) - Add dashboard to connect to/kill existing gdb processes - Add option to specify SSL key and certificate to enable https - Add option to connect to process - Add option to connect to gdbserver - Add infinite scrolling ## 0.9.4.1 - Remove `pypugjs` dependency ## 0.9.4.0 - Add native Windows support (no longer relies on Cygwin) ## 0.9.3.0 - Only display assembly flavor is assembly is displayed - Add new output type to console (gdbgui output) - Add dashboard link and dropdown for gdb server/pid attach - Handle invalid signal choice better - Print gdb mi log messages to console - Remove localStorage keys when they are invalid ## 0.9.2.0 - Add signals component and allow signals to be sent to gdb (issue ##141) - Fix bug when jumping to line of source file ## 0.9.1.1 - Fix bug when passing arguments to gdb - Require latest version of pygdbmi for faster parsing of large gdb output ## 0.9.1.0 - Lazily load files (issue #131) - Update setup.py to build wheels ## 0.9.0.1 - Reupload to fix setup.cfg PyPI bug ## 0.9.0.0 - Compress responses from server (massive bandwidth improvement) - Add button to toggle assembly flavors (issue #110) - Parse executable+args with spaces (issue #116) - Turn modals into components - Move everything into a single root React component - Refresh state when clicking "return" button - Add javascript unit tests ## 0.8.2.0 - Add optional authentication (@nickamon, issue #132) - Support the `--args` flag (issue #126) - Ensure code is correct and adheres to recommended Python style when running tests/building (flake8) - Display source when running `backtrace` (fix regression, #134) ## 0.8.1.0 - Add autocomplete functionality (@bobthekingofegypt, issue #129) - Rearranged and improved alignment of assembly - Fixed bug when fetching variable fails - Plot floating point values instead of casting to int ## 0.8.0.3 - modify component initialization order so that store updates are better sequenced ## 0.8.0.2 - display bracket instead of `<` when exploring gdb variables ## 0.8.0.1 - fix bug when restoring old settings ## 0.8.0.0 - Add ability to change radix of variables (issue #102) - Add component to send signals to inferior program (issues #31, #90) - Parse gdb version from arm-non-eabi-gdb (issue #83) - Rewrite most components to React (issue #17) - Improve CSS in various components ## 0.7.9.5 - re-fetch registers if name/value count does not match ## 0.7.9.4 - add inputs to resize Tree view - add menu in top right - css updates to preserve whitespace in terminal - add top-level html to wrap body+head elements in gdbgui.pug - add help file - add donate page ## 0.7.9.3 - Changes to layout - Fix character escaping in breakpoint line display ## 0.7.9.2 - Fix firefox css bug - Update examples - Update readme for windows (cygwin) users (thanks tgharib) ## 0.7.9.1 - Collapse simple fields to the parent node in tree explorer - Add button to re-enter program state when signals are received (i.e. SEGFAULT) ## 0.7.9.0 - Add interactive tree explorer of variables ## 0.7.8.3 - Remove optimization for fetching registers due to potential bug ## 0.7.8.2 - bugfix in logic when jumping to source code line - bugfix for when variable goes from`empty -> 1 element` - add CODE OF CONDUCT, CONTRIBUTING, and CHANGELOG files ## 0.7.8.1 - correctly display `<` and `>` in console widget ## 0.7.8.0 - show disassembly when file is unknown or missing - show new children in expressions widget when they are dynamically added by application (@wuyihao) - suppress nuisance errors when hover variable or fflush command is not found - improve logic when source code line should be jumped to - escape brackets in disassembly, and gracefully hide missing opcodes - update socketio version for more reliable websocket connection ## 0.7.7.0 - Show variable values when hovering in source code - gracefully handle hostname not being present in /etc/hosts when running with remote flag - Use external state management library (`stator.js`) for client ui ================================================ FILE: CONTRIBUTING.md ================================================ Thanks for your interest in contributing to gdbgui! If your change is small, go ahead and submit a pull request. If it is substantial, create a GitHub issue to discuss it before making the change. ## Dependencies 1.) [nox](https://github.com/theacodes/nox) is used to automate various tasks. You will need it installed on your system before continuing. You can install it with pipx (recommended): ``` > pipx install nox ``` or pip: ``` > pip install --user nox ``` 2.) [yarn](https://yarnpkg.com/) is used for managing JavaScript files ## Developing Development can be done with one simple step: ``` > nox -s develop ``` This will install all Python and JavaScript dependencies, and build and watch Python and JavaScript files for changes, automatically reloading as things are changed. Make sure you [turn your cache off](https://www.technipages.com/google-chrome-how-to-completely-disable-cache) so that changes made locally are reflected in the page. ## Running and Adding tests ```bash > nox ``` runs all applicable tests and linting. Python tests are in `gdbgui/tests`. They are run as part of the above command, but can be run with ``` > nox -s python_tests ``` JavaScript tests are in `gdbgui/src/js/tests`. They are run as part of the above command, but can be run with ``` > nox -s js_tests ``` ## Documentation ### Modifying Documentation Documentation is made with `mkdocs`. Then make changes to `mkdocs.yml` or md files in the `docs` directory. To build docs, run ``` nox -s docs ``` To see a live preview of current documentation, run ``` nox -s watch_docs ``` ### Publishing Documentation The generated documentation is published to the `gh-pages` branch. ``` nox -s publish_docs ``` ### Building Binary Executables These are automatically built on CI, but can be built locally with corresponding `nox` commands, such as: ``` nox -s build_executables_current_platform ``` ## Publishing a New Version 1. Make sure the version number is incremented in `VERSION.txt`. 1. The version to release must be on the master branch and have all CI tests pass and new binary executable artifacts attached to the GitHub action results 1. Publish the package to PyPI and update documentation. Both are done with this `nox -s publish`. 1. Create a "release" in GitHub and attach the gdbgui binary executable artifacts to it. ================================================ FILE: LICENSE ================================================ Copyright (C) Chad Smith (Grass Fed Code) GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: MANIFEST.in ================================================ include README.md include LICENSE include requirements.txt graft gdbgui # these files are built and must be included in distribution # but shouldn't be included in git repository since they # are generated graft gdbgui/static/js prune examples prune .vscode prune downloads prune screenshots prune tests prune docs prune docker prune images prune gdbgui/__pycache__ prune gdbgui/server/__pycache__ prune gdbgui/src exclude mypy.ini exclude .eslintrc.json exclude .coveragerc exclude .flake8 exclude .vulture_whitelist.py exclude .prettierrc.js exclude jest.config.js exclude make_executable.py exclude mkdocs.yml exclude package.json exclude requirements.in exclude tsconfig.json exclude tslint.json exclude webpack.config.js exclude yarn.lock exclude noxfile.py exclude CHANGELOG.md exclude CONTRIBUTING.md exclude postcss.config.js exclude tailwind.config.js ================================================ FILE: README.md ================================================

A browser-based frontend to gdb (gnu debugger)

image PyPI version image

--- **Documentation**: https://gdbgui.com **Source Code**: https://github.com/cs01/gdbgui/ ================================================ FILE: docs/CNAME ================================================ www.gdbgui.com ================================================ FILE: docs/api.md ================================================ This is the command line help output of gdbgui. ``` usage: gdbgui [-h] [-g GDB_CMD] [-p PORT] [--host HOST] [-r] [--auth-file AUTH_FILE] [--user USER] [--password PASSWORD] [--key KEY] [--cert CERT] [--remap-sources REMAP_SOURCES] [--project PROJECT] [-v] [-n] [-b BROWSER] [--debug] [--args ...] [debug_program] A server that provides a graphical user interface to the gnu debugger (gdb). https://github.com/cs01/gdbgui positional arguments: debug_program The executable file you wish to debug, and any arguments to pass to it. To pass flags to the binary, wrap in quotes, or use --args instead. Example: gdbgui ./mybinary [other-gdbgui-args...] Example: gdbgui './mybinary myarg -flag1 -flag2' [other gdbgui args...] (default: None) optional arguments: -h, --help show this help message and exit --args ... Specify the executable file you wish to debug and any arguments to pass to it. All arguments are taken literally, so if used, this must be the last argument. This can also be specified later in the frontend. passed to gdbgui. Example: gdbgui [...] --args ./mybinary myarg -flag1 -flag2 (default: []) gdb settings: -g GDB_CMD, --gdb-cmd GDB_CMD gdb binary and arguments to run. If passing arguments, enclose in quotes. If using rr, it should be specified here with 'rr replay'. Examples: gdb, /path/to/gdb, 'gdb --command=FILE -ix', 'rr replay' (default: gdb) gdbgui network settings: -p PORT, --port PORT The port on which gdbgui will be hosted (default: 5000) --host HOST The host ip address on which gdbgui serve (default: 127.0.0.1) -r, --remote Shortcut to set host to 0.0.0.0 and suppress browser from opening. This allows remote access to gdbgui and is useful when running on a remote machine that you want to view/debug from your local browser, or let someone else debug your application remotely. (default: False) security settings: --auth-file AUTH_FILE Require authentication before accessing gdbgui in the browser. Specify a file that contains the HTTP Basic auth username and password separate by newline. (default: None) --user USER Username when authenticating (default: None) --password PASSWORD Password when authenticating (default: None) --key KEY SSL private key. Generate with:openssl req -newkey rsa:2048 -nodes -keyout host.key -x509 -days 365 -out host.cert (default: None) --cert CERT SSL certificate. Generate with:openssl req -newkey rsa:2048 -nodes -keyout host.key -x509 -days 365 -out host.cert (default: None) other settings: --remap-sources REMAP_SOURCES, -m REMAP_SOURCES Replace compile-time source paths to local source paths. Pass valid JSON key/value pairs.i.e. --remap- sources='{"/buildmachine": "/current/machine"}' (default: None) --project PROJECT Set the project directory. When viewing the "folders" pane, paths are shown relative to this directory. (default: None) -v, --version Print version (default: False) -n, --no-browser By default, the browser will open with gdbgui. Pass this flag so the browser does not open. (default: False) -b BROWSER, --browser BROWSER Use the given browser executable instead of the system default. (default: None) --debug The debug flag of this Flask application. Pass this flag when debugging gdbgui itself to automatically reload the server when changes are detected (default: False) ``` ================================================ FILE: docs/contact.md ================================================ * Email: chadsmith.software@gmail.com ================================================ FILE: docs/examples.md ================================================ # Examples ## Code Examples View code examples on [GitHub](https://github.com/cs01/gdbgui/tree/master/examples). ## gdbgui Invocation Examples launch gdbgui ``` gdbgui ``` set the inferior program, pass argument, set a breakpoint at main ``` gdbgui --args ./myprogram myarg -myflag ``` ``` gdbgui "./myprogram myarg -myflag" ``` use gdb binary not on your $PATH ``` gdbgui --gdb-cmd build/mygdb ``` Pass arbitrary arguments directly to gdb when it is launched ``` gdbgui --gdb-cmd="gdb -x gdbcmds.txt" ``` run on port 8080 instead of the default port ``` gdbgui --port 8080 ``` run on a server and host on 0.0.0.0. Accessible to the outside world as long as port 80 is not blocked. ``` gdbgui -r ``` Same as previous but will prompt for a username and password ``` gdbgui -r --auth ``` Same as previous but with encrypted https connection. ``` openssl req -newkey rsa:2048 -nodes -keyout private.key -x509 -days 365 -out host.cert ``` ``` gdbgui -r --auth --key private.key --cert host.cert ``` Use Mozilla's [record and replay](https://rr-project.org) (rr) debugging supplement to gdb. rr lets your record a program (usually with a hard-to-reproduce bug in it), then deterministically replay it as many times as you want. You can even step forwards and backwards. ``` gdbgui --gdb-cmd "rr replay --" ``` Use recording other than the most recent one ``` gdbgui --gdb-cmd "rr replay RECORDED_DIRECTORY --" ``` Don't automatically open the browser when launching ``` gdbgui -n ``` ================================================ FILE: docs/faq.md ================================================ ## How can I see what commands are being sent to gdb? Go to Settings and check the box that says `Print all sent commands in console, including those sent automatically by gdbgui` ## How can I see gdb's raw output? Launch gdbgui with the debug flag, `gdbgui --debug`, then a new component will appear on the bottom right side of UI. ## Can I use a different gdb executable? Yes, use `gdbgui -g ` ## Does this work with LLDB? No, only gdb. ## Can this debug Python? No. It uses gdb on the backend which does not debug Python code. ## How do I make program output appear in a different terminal? On linux terminals are named. You can get a terminal's name by running `tty` which will print something like `/dev/ttys3`. Tell gdb to use the terminal gdbgui was launched from with ```bash gdbgui --gdb-args="--tty=$(tty)" ``` or if you want to set it from the UI after gdbgui has been opened, run ```bash set inferior-tty /dev/ttys3 # replace /dev/ttys3 with desired tty name ``` ## Help! There isn't a button for something I want to do. What should I do? The vast majority of common use cases are handled in the UI, and to keep the UI somewhat simple I do not intend on making UI support for every single gdb command. You can search gdb documentation and use any gdb command you want in the console at the bottom of the window. If you think there should be a UI element for a command or function, create an issue on GitHub and I will consider it. ================================================ FILE: docs/gettingstarted.md ================================================ Before running `gdbgui`, you should compile your program with debug symbols and a lower level of optimization, so code isn't optimized out before runtime. To include debug symbols with `gcc` use `-ggdb`, with `rustc` use `-g`. To disable most optimizations in `gcc` use the `-O0` flag, with `rustc` use `-O`. For more details, consult your compiler's documentation or a search engine. Now that you have `gdbgui` installed and your program compiled with debug symbols, all you need to do is run ``` gdbgui ``` This will start gdbgui's server and open a new tab in your browser. That tab contains a fully functional frontend running `gdb`! You can see gdbgui in action on [YouTube](https://www.youtube.com/channel/UCUCOSclB97r9nd54NpXMV5A). To see the full list of options gdbgui offers, you can view command line options by running ``` gdbgui --help ``` If you have a question about something * Read documentation on the [homepage](https://github.com/cs01/gdbgui/) * [Ask question in an issue on github](https://github.com/cs01/gdbgui/issues) ## Settings `gdbgui` settings can be accessed by clicking the gear icon in the top right of the frontend. Most of these settings persist between sessions for a given url and port. ## Keyboard Shortcuts The following keyboard shortcuts are available when the focus is not in an input field. They have the same effect as when the button is pressed. * Run: r * Continue: c * Next: n or right arrow * Step: s or down arrow * Up: u or up arrow * Next Instruction: m * Step Instruction: , ================================================ FILE: docs/guides.md ================================================ gdb can be used in a plethora of environments. These guides help you get gdb and gdbgui working in specific environments. Remember, these guides, like gdbgui, are **open source** and can be edited by you, the users! See [contributing](contributing) to modify these docs. ## Running Locally After downloading gdbgui, you can launch it like so: * `gdbgui` (or whatever the binary name is, i.e. `gdbgui_0.10.0.0`) * `gdbgui --args ./mybinary -myarg value -flag1 -flag2` Make sure the program you want to debug was compiled with debug symbols. See the getting started section for more details. A new tab in your browser will open with gdbgui in it. If a browser tab did not open, navigate to the ip/port that gdbgui is being served on (i.e. http://localhost:5000). Now that gdbgui is open, you can interactively run a program with it. * Type the path to the executable in the input at the top (next to "Load Binary"). The executable should already exist and have been compiled with the `-g` flag. * Click `Load Binary`. The program and symbols will load, but will not begin running. A breakpoint will be added to main automatically. This can be changed in settings if you prefer not to do this. * The line of source code corresponding to main will display if the program was compiled with the `-g` flag debug symbols. * Click the Run button, which is on the top right and looks like a circular arrow. * Step through the program by clicking the Next, Step, Continue, icons as desired. These are also on the top right. For a list of gdbgui arguments, run `gdbgui --help`. ## Running Remotely Because gdbgui is a server, it naturally allows you to debug programs running on other computers. * ssh into the computer with the program that needs to be debugged. * run `gdbgui -r` on the remote machine (this will serve publicly so beware of security here) * on your local machine, open your browser and access the remote machine's ip and port * debug the remote computer in your local browser Note that gnu also distrubutes a program called `gdbserver` which gdbgui is compatible with. See the relevant section in this doc. ## Debugging Rust Programs `gdbgui` can be used to debug programs written in Rust. Assuming you use [Cargo](https://doc.rust-lang.org/stable/cargo/) to create a new program and build it in Debug mode in the standard way: ``` cargo new myprog cd myprog cargo build ``` You can start debugging with ``` gdbgui --args target/debug/myprog ``` There are a couple of small difficulties. 1.) Instead of showing your `main` function the initial screen will be blank and `gdbgui` will print `File not found: main`. You need to help `gdbgui` out by typing `main` into the file browser box: ![](https://raw.githubusercontent.com/cs01/gdbgui/master/screenshots/rust_main.png) and selecting the `main.rs` file. The source code should then appear in the browser and you can click to set breakpoints and run the program. Of course, if you want to break in some other file, you can find that in the file browser instead. ### Rust on macOS When you load your rust binary on a mac, you may see many warnings like this > warning /Users/user/examples/rust/target/debug/deps/hello-486956f9dde465e5.9elsx31vb4it187.rcgu.o': can't open to read symbols: No such file or directory. Symbols are names of variables, functions and types defined in your program. You can define symbols for your program by loading symbol files. gdb usually does this automatically for you, but sometimes has trouble finding the right paths. In this case, you need to manually tell gdb where the symbol files is; it's usually the first part of the missing file. In the above example, it's `hello-486956f9dde465e5.9elsx31vb4it187.rcgu.o`. You can load this into gdb with the following command (changed as appropriate): ``` symbol-file /Users/user/git/gdbgui/examples/rust/target/debug/deps/hello-486956f9dde465e5 ``` 2.) The GDB pretty-printing macros that Rust ships with. GDB can't find these by default, which makes it print the message ``` warning: Missing auto-load script at offset 0 in section .debug_gdb_scripts of file /home/temp/myprog/target/debug/myprog. Use `info auto-load python-scripts [REGEXP]' to list them. ``` You can safely ignore this, but the [Rust issue](https://github.com/rust-lang/rust/issues/33159#issuecomment-384073290) describes the workarounds necessary (create a `.gdbinit` file and paste a few lines into the Python helper script). * On Windows Rust defaults to the MSVC toolchain, and `gdbgui` can't debug binaries compiled that way. If you want to use `gdbgui`, you'll have to [switch to the GNU toolchain](https://github.com/rust-lang-nursery/rustup.rs#working-with-rust-on-windows). * If you want to debug programs compiled in Release mode, you will need to create a `profile.release` section in your `Cargo.toml` and add `debug = true` to it. See the [Cargo manifest](https://doc.rust-lang.org/stable/cargo/reference/manifest.html) for details. and now gdb will be able to see which files were used to compile your binary, among other things. ## Connecting to gdbserver Like gdb, [`gdbserver`](https://sourceware.org/gdb/onlinedocs/gdb/Server.html) is also made by gnu, but with the following important differences: * it is much smaller than gdb * it is easier to port to other architectures than all of gdb gdbserver runs on a remote machine or embedded target, which, as the name suggests, runs a server. gdb communicates with gdbserver so you can debug on your local machine. To do this, the remote machine must run the server and program: `gdbserver :9000 mybinary.a` Then you can launch `gdb` or `gdbgui` and connect to it. In `gdbgui`, use the dropdown to select `Connect to gdbserver`, and enter `:9000` Read more at the [gdbserver homepage](https://sourceware.org/gdb/onlinedocs/gdb/Server.html). If the machine gdbgui is running on and the target being debugged have different architectures, make sure gdb is built properly (see `Remote Debugging Between Different Architectures`). ## Remote Debugging Between Different Architectures For example, this is useful if you are working from an x86_64 based PC gdb client with gdbgui, to ARM arch gdbserver. You need to build the `gdb` client with the `--host` and `--target` flags. You need to build the `gdbserver` for the correct architecture. Build the `gdb` client that `gdbgui` will use. This example applies to an x86_64 pc running gdbgui that connects to an arm device running gdbserver, so you will need to ensure the targets apply to the environments you are working in: 1. downloaded latest gdb source code 2. unzip it, go into folder 3. ```bash ./configure --host=x86_64-pc-linux-gnu --build=x86_64-pc-linux-gnu --target=arm-linux-gnuabi && make -j8 && sudo make install ``` 4. Now arm-linux-gnuabi-gdb is installed by default to `/usr/local/bin`, but you can instead provide `prefix=` to where you want it to install in the ./configure script above 5. The `arm-linux-gnuabi-gdb` binary can now be used by gdbgui to connect to the ARM device: ```bash gdbgui -g arm-linux-gnuabi-gdb ``` Links: * [Building GDB and GDBserver for cross debugging](https://sourceware.org/gdb/wiki/BuildingCrossGDBandGDBserver) * [http://www.brain-dump.org/blog/entry/138/Cross_Arch_Remote_Debugging_with_gdb_and_gdbserver](Cross Arch Remote Debugging with gdb and gdbserver) * [support remote debug from x86_64 based PC gdb client with gdbgui, to ARM arch gdbserver (multiarch)](https://github.com/cs01/gdbgui/issues/237) ================================================ FILE: docs/howitworks.md ================================================ gdbgui consists of two main parts: the frontend and the backend ## Backend The backend is written in Python and consists of a Flask server with websocket capability thanks to the `python-socketio` package. When a new websocket connection from a browser is established, the server starts a new gdb subprocess and associates it with this websocket. This gdb process is told to use gdb's [machine interface](https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI.html) interpreter, which enables gdb's input and output to be programatically parsed so you can write code to do further processing with it, such as build a user interface. The [pygdbmi library](https://github.com/cs01/pygdbmi) is used to manage the gdb subprocess and parse its output. It returns key/value pairs (dictionaries) that can be used to create a frontend. I wrote pygdbmi as a building block for gdbgui, but it is useful for any type of programmatic control over gdb. In summary, the backend is used to: - create endpoints for the browser, including http and websocket. - The server can access the operating system and do things like read source files or send signals to processes. - create a managed gdb subprocess and parse output with pygdbmi - spawn a separate thread to constantly check for output from the gdb subprocess - forward output to the client through a websocket as it is parsed in the reader thread ## Frontend The frontend is written in JavaScript and uses React. It establishes a websocket connection to the server, at which time the server starts a new gdb subprocess for that particular websocket connection as mentioned above. Commands can be sent from the browser through the websocket to the server which writes to gdb, and output from gdb is forwarded from the server through the websocket to the browser. As the browser receives websocket messages from the server, it maintains the state of gdb, such as whether it's running, paused, or exited, where breakpoints are, what the stack is, etc. As this state changes, React performs the necessary DOM updates. In summary, the frontend is used to: * Convert key/value pairs of gdb's machine interface output into a user interface * Maintain the state of gdb * Provide UI elements that can send gdb machine interface commands to gdb ================================================ FILE: docs/index.md ================================================

A browser-based frontend to gdb (gnu debugger)

CI Tests PyPI version Download Count

---

`gdbgui` is a browser-based frontend to `gdb`, the [gnu debugger](https://www.gnu.org/software/gdb/). You can add breakpoints, view stack traces, and more in C, C++, Go, and Rust! It's perfect for beginners and experts. Simply run `gdbgui` from the terminal to start the gdbgui server, and a new tab will open in your browser. **Sound Good? Get started with [installation](installation)**. ## Testimonials "*Definitely worth checking out.*"
Jason Turner, host of C++ weekly on YouTube
"_Seriously, great front-end to gdb for those of us who are not always using a full IDE. Great project._"
—Jefferson on Twitter
"_Where were you all my life? And why did I use DDD?_"
Mario Zechner, author, game engine developer on Twitter
gdbgui is used by thousands of developers around the world including engineers at Google and college computer science course instructions. It even made its way into the Rust programming language's [source code](https://github.com/rust-lang/rust/blob/master/src/etc/rust-gdbgui) and appeared on episode [110 of C++ Weekly](https://youtu.be/em842geJhfk). ## License gdbgui's license is GNU GPLv3. To summarize it, you - can use it for free at work or for personal use - can modify its source code - must disclose your source code if you redistribute any part of gdbgui ## Distribution gdbgui is distributed through - github ([https://github.com/cs01/gdbgui](https://github.com/cs01/gdbgui)) - [PyPI](https://pypi.python.org/pypi/gdbgui/) ## Authors - Chad Smith, creator/maintainer - @bobthekingofegypt, contibutor - [Community contributions](https://github.com/cs01/gdbgui/graphs/contributors) ## Donate [Paypal](https://www.paypal.me/grassfedcode/20) ## Contact https://chadsmith.dev chadsmith.software@gmail.com ================================================ FILE: docs/installation.md ================================================ # gdbgui installation There are a few ways to install gdbgui on your machine. There is even a way to run gdbgui without installing it. Read on to to find the one that's right for you. ## Method 1: Using `pipx` (recommended) gdbgui recommends using [pipx](https://github.com/pipxproject/pipx), a program to run Python CLI binaries in isolated environments. You can install pipx like this: ``` python3 -m pip install --user pipx python3 -m userpath append ~/.local/bin ``` Restart/re-source your console to make sure the userpath is up to date. Then, install gdbgui with pipx: ``` pipx install gdbgui ``` To upgrade run ``` pipx upgrade gdbgui ``` When installation is finished, type `gdbgui` from the command line to run it, or `gdbgui -h` for help. To uninstall, run ``` pipx uninstall gdbgui ``` ### Try Without Installing By using [pipx](https://github.com/pipxproject/pipx), you can run Python CLI programs in ephemeral one-time virtual environments. ``` pipx run gdbgui ``` A new tab running the latest version of gdbgui will open in your browser. Press CTRL+C to end the process, and your system will remain untouched. ## Method 2: Using `pip` `pip` is a popular installer for Python packages. gdbgui is a Python package and as such can be installed with pip, though we recommend using `pipx` rather than `pip` if possible. If you prefer to use Virtual Environments, you can activate one and then run ``` pip install gdbgui ``` You can get upgrades with ``` pip install --upgrade gdbgui ``` To uninstall, run ``` pip uninstall gdbgui ``` ## Method 3: Download and Run Binary Executable Download and run the binary executable for your system from [GitHub Releases](https://github.com/cs01/gdbgui/releases). ## System Dependencies for Python Package Note that this only applies if you are installing the Python package, and not using the binary executable. - gdb (gnu debugger) - Python 3.4+ (recommended) or 2.7 - pip version 8 or higher ### Linux Dependencies sudo apt install gdb python3 ### macOS Dependencies brew install python3 brew install gdb --with-python --with-all-targets macOS users must also codesign gdb: follow [these instructions](http://andresabino.com/2015/04/14/codesign-gdb-on-mac-os-x-yosemite-10-10-2/). This will fix the error `please check gdb is codesigned - see taskgated(8)`. ### Windows Dependencies Note that windows is only supported for gdbgui versions less than 0.14. - [Python 3](https://www.python.org/downloads/windows/) - gdb, make, gcc If you do not have already have gdb/make/gcc installed, there are two options to install them on Windows: `MinGW` and `cygwin`. ##### MinGW (recommended) Minimal GNU for Windows ([`MinGW`]([http://mingw.org/)) is the recommended Windows option. [Install MinGW](https://sourceforge.net/projects/mingw/files/Installer/mingw-get-setup.exe/download) with the "MinGW Base System" package. This is the default package which contains `make`, `gcc`, and `gdb`. It will install to somewhere like `C:\MinGW\bin\...`. For example `C:\MinGW\bin\gdb.exe`, `C:\MinGW\bin\mingw32-make.exe`, etc. Ensure this MinGW binary directory (i.e. `C:\MinGW\bin\`) is on your "Path" environment variable: Go to `Control Panel > System Properties > Environment Variables > System Variables > Path` and make sure `C:\MinGW\bin\` is added to that list. If it is not added to your "Path", you will have to run gdbgui with the path explicitly called out, such as `gdbgui -g C:\MinGW\bin\gdb.exe`. ##### Cygwin Cygwin is a more UNIX-like compatibility layer on Windows, and `gdbgui` works with it as well. - Install [cygwin](https://cygwin.com/install.html) When installing cygwin packages, add the following: - python3 - python3-pip - python3-devel - gdb - gcc-core - gcc-g++ ### Running from Source See the [contributing](/contributing) section. ================================================ FILE: docs/screenshots.md ================================================ ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/gdbgui.png) ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/gdbgui2.png) Enter the binary and args just as you'd call them on the command line. The binary is restored when gdbgui is opened at a later time. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/load_binary_and_args.png) Intuitive control of your program. From left to right: Run, Continue, Next, Step, Return, Next Instruction, Step Instruction. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/controls.png) If the environment supports reverse debugging, such as when using an Intel CPU and running Linux and debugging with [rr](http://rr-project.org/), gdbgui allows you to debug in reverse. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/reverse_debugging.png) ## Stack/Threads View all threads, the full stack on the active thread, the current frame on inactive threads. Switch between frames on the stack, or threads by pointing and clicking. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/stack_and_threads.png) ## Send Signal to Inferior (debugged) Process Choose from any signal your OS supports to send to the inferior. For example, to mock `CTRL+C` in plain gdb, you can send `SIGINT` to interrupt the inferior process. If the inferior process is hung for some reason, you can send `SIGKILL`, etc. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/send_signal.png) ## Source Code View source, assembly, add breakpoints. All symbols used to compile the target are listed in a dropdown above the source code viewer, and have autocompletion capabilities. There are two different color schemes: dark (monokai), and a light theme (default). ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/source.png) With assembly. Note the bold line is the current instruction that gdb is stopped on. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/source_with_assembly.png) If the source file is not found, it will display assembly, and allow you to step through it as desired. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/assembly.png) ## Variables and Expressions All local variables are automatically displayed, and are clickable to explore their fields. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/locals.png) Hover over a variable and explore it, just like in the Chrome debugger. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/hover.png) Arbitrary expressions can be evaluated as well. These expressions persist as the program is stepped through. The base/radix can be modified as desired. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/radix.gif) ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/expressions.png) Expressions record their previous values, and can be displayed in an x/y plot. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/plots.png) Expressions can be interactively explored in a tree view. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/tree_explorer.png) ## Memory Viewer All hex addresses are automatically converted to clickable links to explore memory. Length of memory is configurable. In this case 10 bytes are displayed per row. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/memory.png) ## Registers View all registers. If a register was updated it is highlighted in yellow. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/registers.png) ## gdb console * Prints gdb output * Allows you to write directly to the underlying gdb subprocess as if you were using it in the terminal * Tab completion works, and displays a button to view help on gdb commands * Can be used to ease into learning gdb * Can be used as a fallback for commands that don't have a UI widget * History can be accessed using up/down arrows ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/console.png) ## authentication Authentication can be enabled when serving on a publicly accessible IP address. See `gdbgui --help` for instructions on how to enable authentication. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/authentication.png) ## Dashboard A dashboard is available to let you look at all gdb instances managed by gdbgui. You can kill them, or attach to them. More than one person can attach to a managed gdb subprocess and participate in the debugging session simultaneously. i.e. if one person steps forward, all connected users see the program step forward in real time. ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/dashboard.png) ## gdbgui at launch ![image](https://github.com/cs01/gdbgui/raw/master/screenshots/ready.png) ================================================ FILE: examples/.gitignore ================================================ *.a ================================================ FILE: examples/README.md ================================================ # Examples ## Overview `gdbgui` can debug executables generated from various languages. This folder contains example source code and makefiles to build and automatically launch `gdbgui`. ## Clone To get started, first clone this repository: ``` git clone https://github.com/cs01/gdbgui.git ``` ## Install Dependencies If you already installed `gdbgui` with `pip`, you have all dependencies installed. If not, you need to install them manually: ```bash pip install -r gdbgui/requirements.txt # run as sudo if this fails ``` ## Build Executables and Debug with gdbgui Enter the directory with the language of your choice in `gdbgui/examples/*` (`c`, `cpp`, `rust`, `golang`, `fortran`), then type `make` and hit the `tab` to see the make targets. For example, in `gdbgui/examples/c`, running `make hello` will: * build the binary (assuming you have the right compilers and libraries installed) * open a new tab in your browser * load the executable for the make target you just built * insert a breakpoint at main (Rust and Go users may see machine code displayed rather than source code. This is a `gdb` limitation.) * **Note: Although the program has loaded, you still must click the run icon to actually begin running the program.** ================================================ FILE: examples/c/debug_segfault.c ================================================ #include #include #include int main(void) { char* badstring = 0; const char* s = "gdbgui"; int myvar = 100; unsigned int myvar2 = 200; printf("The next function call will cause a segfault. With gdbgui, the state of the program\n" "can be debugged at the time the program exited by running the command\n" "\"backtrace\" or \"bt\" in the gdb console.\n\n" "It will re-enter the state the\n" "program was in, including the stack trace: you'll end up in `main -> _IO_puts -> strlen`.\n" "If you click on the `main` function in the call stack, gdbgui will put you in the main function\n" "where you can inspect your local\n" "variables and determine how the segfault occured.\n\n" ); printf("%s\n", badstring); printf("This line is never reached because the above line causes a segfault\n"); return 0; } ================================================ FILE: examples/c/hello.c ================================================ #include #include void say_something(const char *str) { printf("%s\n", str); } struct mystruct_t { int value; char letter; char *string; struct { double dbl; } substruct; /* named sub-struct */ struct { float fp; }; /* anonymous struct */ void *ptr; size_t struct_size; union { int unionint; double uniondouble; }; }; int main(int argc, char **argv) { printf("Hello World\n"); int retval = 1; /* bytes are allocated for s, but still contain garbage */ struct mystruct_t s; s.value = 100; s.string = "pass"; s.substruct.dbl = 567.8; s.letter = 'P'; s.fp = 123.4; s.ptr = say_something; /* address of function */ s.ptr = &say_something; /* also address of function */ s.unionint = 0; s.uniondouble = 1.0; for (int i = 0; i < 2; i++) { printf("i is %d\n", i); } if (!strcmp(s.string, "pass")) { retval = 0; } printf("returning %d\n", retval); say_something("Goodbye"); return retval; } ================================================ FILE: examples/c/input.c ================================================ #include #include int main(int argc, char **argv) { char name[20]; printf("Hello. What's your name?\n"); fgets(name, 20, stdin); printf("Hi there, %s", name); return 0; } ================================================ FILE: examples/c/makefile ================================================ ROOT:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) hello: hello.c gcc hello.c -o hello_c.a -std=c99 -g @echo Run with gdbgui: gdbgui --args $(ROOT)/hello_c.a input: input.c gcc input.c -o input.a -std=c99 -g @echo Run with gdbgui: gdbgui --args $(ROOT)/input.a debug_segfault: debug_segfault.c gcc debug_segfault.c -g -o debug_segfault.a -std=c99 @echo Run with gdbgui: gdbgui --args $(ROOT)/debug_segfault.a threads: threads.c gcc threads.c -o threads.a -std=c99 -lpthread -g @echo Run with gdbgui: gdbgui --args $(ROOT)/threads.a sleeper: sleeper.c gcc sleeper.c -o sleeper.a -std=c99 -g @echo Run with gdbgui: gdbgui --args $(ROOT)/sleeper.a ================================================ FILE: examples/c/sleeper.c ================================================ #include #include #include int main(int argc, char **argv) { printf("entering\n"); while(1){ // while this loop is running, you cannot interact with // gdb until you interrupt (send signal SIGINT) to gdb // or the inferior process printf("sleeping...\n"); sleep(2); printf("Finished sleeping. Repeating.\n"); } printf("exiting\n"); return 0; } ================================================ FILE: examples/c/threads.c ================================================ #include #include static const int num_increments = 2; /* this function is run by the second thread */ void *thread_callback(void *arg) { int *val = (int*)arg; while((*val) < num_increments){ printf("incrementing\n"); (*val)++; } printf("increment finished\n"); } int main() { int x = 0, y = 0; printf("x: %d, y: %d\n", x, y); pthread_t thread_to_increment_x, thread_to_increment_y; /* create and run threads */ if(pthread_create(&thread_to_increment_x, NULL, thread_callback, &x)) { printf("error: pthread_create returned non-zero value\n"); return 1; } if(pthread_create(&thread_to_increment_y, NULL, thread_callback, &y)) { printf("error: pthread_create returned non-zero value\n"); return 1; } /* wait for threads to finish */ if(pthread_join(thread_to_increment_x, NULL)) { printf("error: pthread_join returned non-zero value\n"); return 1; } if(pthread_join(thread_to_increment_y, NULL)) { printf("error: pthread_join returned non-zero value\n"); return 1; } printf("x: %d, y: %d\n", x, y); return 0; } ================================================ FILE: examples/c/tree.c ================================================ #include #include #include struct Node { struct Node* left; struct Node* right; char* name; }; void visit(struct Node* node) { printf("visiting node '%s'\n", node->name); } void dfs(struct Node *node) { if (node == NULL) { return; } visit(node); dfs(node->left); dfs(node->right); } int main(void) { printf("gdbgui has a widget that allows interactive tree exploration. " "Enter 'root' in 'Expressions' widget, then hover over root and click the tree icon next to 'root' to draw the tree. " "The tree is automatically updated as the program's state changes changed.\n\n"); /* initialize nodes so that left/right are NULL and each node has a name */ struct Node root = {.name = "root"}, a = {.name = "a"}, b = {.name = "b"}, c = {.name = "c"}, d = {.name = "d"}, e = {.name = "e"}, f = {.name = "f"}; /* connect nodes */ printf("As you step through the following code, you can see the graph grow and change as assignments are made\n"); root.left = &a; root.right = &b; a.left = &c; a.right = &d; d.left = &e; b.right = &f; printf("beginning depth first search. We can verify the dfs algorithm is accurate by comparing it to gdbgui's graph view.\n"); dfs(&root); printf("finished depth first search\n"); return 0; } ================================================ FILE: examples/cpp/hello.cpp ================================================ #include #include #include int main(void) { std::cout << "Hello World" << std::endl; std::cout << "Example vector" << std::endl; std::vector myvector {}; myvector.push_back(1.1); myvector.push_back(2.2); myvector.push_back(3.3); myvector.push_back(4.4); for (auto i : myvector){ std::cout << i << " is an element in a vector" << std::endl; } std::cout << "Example map" << std::endl; std::map mymap; mymap['a'] = 10; mymap['b'] = 30; mymap['c'] = 50; mymap['d'] = 70; for (auto i : mymap){ std::cout << i.first << " is a key in a map with a value of " << i.second << std::endl; } return 0; } ================================================ FILE: examples/cpp/linked_list.cpp ================================================ #include class Node{ private: Node* next = 0; Node* prev = 0; int value; public: Node(int v){ value = v; } int get_value() const{ return value; } void print_values() const{ std::cout << this->get_value() << std::endl; if(this->next){ this->next->print_values(); } } void append(int v){ Node* new_node = new Node(v); Node* iter = this; while(iter->next){ iter = iter->next; } iter->next = new_node; new_node->prev = iter; } }; int main(){ Node* linked_list = new Node(0); linked_list->print_values(); linked_list->append(1); linked_list->append(2); linked_list->append(3); linked_list->append(4); linked_list->print_values(); return 0; } ================================================ FILE: examples/cpp/makefile ================================================ GDBGUI=../../gdbgui/backend.py hello: hello.cpp g++ hello.cpp -o hello_cpp.a -std=c++11 -g @echo Run with gdbgui: gdbgui --args hello_cpp.a linked_list: linked_list.cpp g++ linked_list.cpp -o linked_list_cpp.a -std=c++11 -g @echo Run with gdbgui: gdbgui --args linked_list_cpp.a smart_ptr_demo: smart_ptr_demo.cpp g++ smart_ptr_demo.cpp -o smart_ptr_demo_cpp.a -std=c++11 -g @echo Run with gdbgui: gdbgui --args smart_ptr_demo_cpp.a sin: sin.cpp g++ sin.cpp -o sin_cpp.a -std=c++11 -g @echo Run with gdbgui: gdbgui --args sin_cpp.a ================================================ FILE: examples/cpp/sin.cpp ================================================ #include /* sin */ int main () { double angle = 0, result = 0; static const double RAD_TO_DEG = 3.14159265 / 180; while (angle <= 360){ result = sin(angle * RAD_TO_DEG); angle += 20; } return 0; } ================================================ FILE: examples/cpp/smart_ptr_demo.cpp ================================================ // A demonstration of unique, smart, weak, and raw pointers in C++11. // compile with: // g++ smart_ptr_demo.cpp -std=c++11 -o smart_ptr_cpp_demo.a -g // // running yields: // >> ./smart_ptr_demo_cpp.a // constructed raw pointer, at address 0x169fc20 // entering local scope // entered local scope // constructed unique (only one reference) pointer, at address 0x16a0090 // constructed shared (local and global reference) pointer, at address 0x16a00f0 // constructed shared (one shared reference, two weak references) pointer, at address 0x16a0060 // local weak pointer has valid reference // global weak pointer has valid reference // smart pointers can be accessed like regular pointers // This is my type: unique (only one reference) // This is my type: shared (local and global reference) // leaving local scope // destroyed shared (one shared reference, two weak references) pointer, at address 0x16a0060 // destroyed unique (only one reference) pointer, at address 0x16a0090 // left local scope // global weak pointer has no reference // destroyed raw pointer, at address 0x169fc20 // leaving main // destroyed shared (local and global reference) pointer, at address 0x16a00f0 #include #include #include // A class that prints metadata when constructed and destroyed class SimpleType { std::string m_ptr_type; public: SimpleType(const std::string& ptr_type){ m_ptr_type = ptr_type; std::cout << "constructed " << m_ptr_type << " pointer, at address " << this << std::endl; } ~SimpleType(){ std::cout << "destroyed " << m_ptr_type << " pointer, at address " << this << std::endl; } void identify(){ std::cout << "This is my type: " << m_ptr_type << std::endl; } }; int main() { std::unique_ptr globalunique; std::shared_ptr globalshared; std::weak_ptr globalweak; SimpleType* raw_ptr = new SimpleType("raw"); // locally scoped operations will cause smart pointers to automatically // be deleted (garbage collected) if no owners remain at the end of the scope std::cout << "entering local scope" << std::endl; { std::cout << "entered local scope" << std::endl; // unique (deleted upon exit of this local scope) std::unique_ptr localunique = std::unique_ptr(new SimpleType("unique (only one reference)")); // shared with > 1 owner (not deleted upon exit of this local scope) std::shared_ptr localshared = std::shared_ptr(new SimpleType("shared (local and global reference)")); globalshared = localshared; // assign global reference // shared with exactly 1 owner (deleted upon exit of this local scope) std::shared_ptr localshared2 = std::shared_ptr(new SimpleType("shared (one shared reference, two weak references)")); std::weak_ptr localweak = localshared2; // shared_ptr reference count does not increment here, because weak pointers don't "own" the pointer globalweak = localweak; // again, the shared_ptr reference count does not increment // prove that the weak pointer references the shared pointer (but it does not own it!) std::cout << (localweak.lock() ? "local weak pointer has valid reference" : "local weak pointer has no reference") << std::endl; std::cout << (globalweak.lock() ? "global weak pointer has valid reference" : "global weak pointer has no reference") << std::endl; std::cout << "smart pointers can be accessed like regular pointers" << std::endl; localunique->identify(); (*globalshared).identify(); std::cout << "leaving local scope" << std::endl; } // localshared is not deleted here because the globalshared reference still exists and shares ownership of it. // localshared2/globalweak's object is deleted here. Even though globalweak still references it, it doesn't own it, so it's deleted. std::cout << "left local scope" << std::endl; std::cout << (globalweak.lock() ? "global weak pointer has valid reference" : "global weak pointer has no reference") << std::endl; delete raw_ptr; // this needs to be done manually std::cout << "leaving main" << std::endl; } ================================================ FILE: examples/fortran/fortran_array.f90 ================================================ program array integer, parameter :: n=3 integer :: ii real, dimension(n) :: a, b real, dimension(n,n) :: c a = [( real(ii), ii=1, n )] do ii = 1,n print *, a(ii) enddo b = 0. do ii = 1, n ! You could just write b = a ** 2., but I want to see the loop progress b(ii) = a(ii) ** 2. enddo do ii = 1, n print *, b(ii) enddo c = reshape( [( real(ii), ii=1,n**2 )], [n,n] ) do ii = 1, n print *, c(ii,:) enddo end program ================================================ FILE: examples/fortran/makefile ================================================ GDBGUI=../../gdbgui/backend.py array_demo: fortran_array.f90 gfortran fortran_array.f90 -o array_f90.a -g @echo Run with gdbgui: gdbgui --args array_f90.a ================================================ FILE: examples/golang/hello.go ================================================ package main import "fmt" func main() { fmt.Println("hello world") // Create an array of three ints. array := [...]int{10, 20, 30} // Loop over three ints and print them. for i := 0; i < len(array); i++ { fmt.Println(array[i]) } } ================================================ FILE: examples/golang/makefile ================================================ GDBGUI=../../gdbgui/backend.py hello: hello.go go build -o hello_go.a -gccgoflags "-w" hello.go @echo Run with gdbgui: gdbgui --args hello_go.a ================================================ FILE: examples/rust/.gitignore ================================================ /target/ **/*.rs.bk # User-specific stuff: .idea/**/workspace.xml .idea/**/tasks.xml .idea/dictionaries ================================================ FILE: examples/rust/Cargo.toml ================================================ [package] name = "hello" version = "0.1.0" authors = ["Your Name "] [dependencies] ================================================ FILE: examples/rust/README.md ================================================ This directory contains a very simple Rust program to demonstrate how to run gdbgui. Run the shell script `compile_and_debug.sh` which will use Cargo to build the program in debug mode and then launch `gdbgui` to debug the program. ================================================ FILE: examples/rust/compile_and_debug.sh ================================================ #!/usr/bin/env bash GDBGUI=../../gdbgui/backend.py cargo build && $GDBGUI ./target/debug/hello ================================================ FILE: examples/rust/src/main.rs ================================================ use std::mem; // This function borrows a slice fn analyze_slice(slice: &[i32]) { println!("first element of the slice: {}", slice[0]); println!("the slice has {} elements", slice.len()); } fn main() { println!("Hello World!"); // Fixed-size array (type signature is superfluous) let xs: [i32; 5] = [1, 2, 3, 4, 5]; // All elements can be initialized to the same value let ys: [i32; 9] = [0; 9]; // Indexing starts at 0 println!("first element of the array: {}", xs[0]); println!("second element of the array: {}", xs[1]); // `len` returns the size of the array println!("array size: {}", xs.len()); // Arrays are stack allocated println!("array occupies {} bytes", mem::size_of_val(&xs)); // Arrays can be automatically borrowed as slices println!("borrow the whole array as a slice"); analyze_slice(&xs); // Slices can point to a section of an array println!("borrow a section of the array as a slice"); analyze_slice(&ys[1 .. 4]); } ================================================ FILE: gdbgui/SSLify.py ================================================ """Module to enable SSL for Flask application. Adapted from https://github.com/kennethreitz/flask-sslify """ import os import ssl from flask import redirect, request YEAR_IN_SECS = 31536000 class SSLify(object): """Secures your Flask App.""" def __init__( self, app=None, age=YEAR_IN_SECS, subdomains=False, permanent=False, skips=None ): self.app = app self.hsts_age = age self.hsts_include_subdomains = subdomains self.permanent = permanent self.skip_list = skips if app is not None: self.init_app(app) def init_app(self, app): """Configures the specified Flask app to enforce SSL.""" app.config.setdefault("SSLIFY_SUBDOMAINS", False) app.config.setdefault("SSLIFY_PERMANENT", False) app.config.setdefault("SSLIFY_SKIPS", None) self.hsts_include_subdomains = ( self.hsts_include_subdomains or app.config["SSLIFY_SUBDOMAINS"] ) self.permanent = self.permanent or self.app.config["SSLIFY_PERMANENT"] self.skip_list = self.skip_list or self.app.config["SSLIFY_SKIPS"] app.before_request(self.redirect_to_ssl) app.after_request(self.set_hsts_header) @property def hsts_header(self): """Returns the proper HSTS policy.""" hsts_policy = "max-age={0}".format(self.hsts_age) if self.hsts_include_subdomains: hsts_policy += "; includeSubDomains" return hsts_policy @property def skip(self): """Checks the skip list.""" # Should we skip? if self.skip_list and isinstance(self.skip_list, list): for skip in self.skip_list: if request.path.startswith("/{0}".format(skip)): return True return False def redirect_to_ssl(self): """Redirect incoming requests to HTTPS.""" # Should we redirect? criteria = [ request.is_secure, self.app.debug, self.app.testing, request.headers.get("X-Forwarded-Proto", "http") == "https", ] if not any(criteria) and not self.skip: if request.url.startswith("http://"): url = request.url.replace("http://", "https://", 1) code = 302 if self.permanent: code = 301 r = redirect(url, code=code) return r def set_hsts_header(self, response): """Adds HSTS header to each response.""" # Should we add STS header? if request.is_secure and not self.skip: response.headers.setdefault("Strict-Transport-Security", self.hsts_header) return response def get_ssl_context(private_key, certificate): """Get ssl context from private key and certificate paths. The return value is used when calling Flask. i.e. app.run(ssl_context=get_ssl_context(,,,)) """ if ( certificate and os.path.isfile(certificate) and private_key and os.path.isfile(private_key) ): context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) context.load_cert_chain(certificate, private_key) return context return None ================================================ FILE: gdbgui/VERSION.txt ================================================ 0.15.3.0 ================================================ FILE: gdbgui/__init__.py ================================================ import io import os import sys _base_dir = getattr(sys, "_MEIPASS", os.path.dirname(os.path.realpath(__file__))) _version = ( io.open(os.path.join(_base_dir, "VERSION.txt"), "r", encoding="utf-8") .read() .strip() ) __title__ = "gdbgui" __version__ = _version __author__ = "Chad Smith" __copyright__ = "Copyright Chad Smith" ================================================ FILE: gdbgui/__main__.py ================================================ from . import cli cli.main() ================================================ FILE: gdbgui/cli.py ================================================ #!/usr/bin/env python """ A server that provides a graphical user interface to the gnu debugger (gdb). https://github.com/cs01/gdbgui """ import argparse import json import logging import os import platform import re import shlex from typing import List, Optional from gdbgui import __version__ from gdbgui.server.app import app, socketio from gdbgui.server.constants import DEFAULT_GDB_EXECUTABLE, DEFAULT_HOST, DEFAULT_PORT from gdbgui.server.server import run_server logger = logging.getLogger(__name__) logging.getLogger("werkzeug").setLevel(logging.ERROR) def get_gdbgui_auth_user_credentials(auth_file, user, password): if auth_file and (user or password): print("Cannot supply auth file and username/password") exit(1) if auth_file: if os.path.isfile(auth_file): with open(auth_file, "r") as authFile: data = authFile.read() split_file_contents = data.split("\n") if len(split_file_contents) < 2: print( 'Auth file "%s" requires username on first line and password on second line' % auth_file ) exit(1) return split_file_contents else: print('Auth file "%s" for HTTP Basic auth not found' % auth_file) exit(1) elif user and password: return [user, password] else: return None def warn_startup_with_shell_off(platform: str, gdb_args: str): """return True if user may need to turn shell off if mac OS version is 16 (sierra) or higher, may need to set shell off due to os's security requirements http://stackoverflow.com/questions/39702871/gdb-kind-of-doesnt-work-on-macos-sierra """ darwin_match = re.match(r"darwin-(\d+)\..*", platform) on_darwin = darwin_match is not None and int(darwin_match.groups()[0]) >= 16 if on_darwin: shell_is_off = "startup-with-shell off" in gdb_args return not shell_is_off return False def get_parser(): parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter ) gdb_group = parser.add_argument_group(title="gdb settings") args_group = parser.add_mutually_exclusive_group() network = parser.add_argument_group(title="gdbgui network settings") security = parser.add_argument_group(title="security settings") other = parser.add_argument_group(title="other settings") gdb_group.add_argument( "-g", "--gdb-cmd", help=""" gdb binary and arguments to run. If passing arguments, enclose in quotes. If using rr, it should be specified here with 'rr replay'. Examples: gdb, /path/to/gdb, 'gdb --command=FILE -ix', 'rr replay' """, default=DEFAULT_GDB_EXECUTABLE, ) network.add_argument( "-p", "--port", help="The port on which gdbgui will be hosted", default=DEFAULT_PORT, ) network.add_argument( "--host", help="The host ip address on which gdbgui serve", default=DEFAULT_HOST ) network.add_argument( "-r", "--remote", help="Shortcut to set host to 0.0.0.0 and suppress browser from opening. This allows remote access " "to gdbgui and is useful when running on a remote machine that you want to view/debug from your local " "browser, or let someone else debug your application remotely.", action="store_true", ) security.add_argument( "--auth-file", help="Require authentication before accessing gdbgui in the browser. " "Specify a file that contains the HTTP Basic auth username and password separate by newline. ", ) security.add_argument("--user", help="Username when authenticating") security.add_argument("--password", help="Password when authenticating") security.add_argument( "--key", default=None, help="SSL private key. " "Generate with:" "openssl req -newkey rsa:2048 -nodes -keyout host.key -x509 -days 365 -out host.cert", ) # https://www.digitalocean.com/community/tutorials/openssl-essentials-working-with-ssl-certificates-private-keys-and-csrs security.add_argument( "--cert", default=None, help="SSL certificate. " "Generate with:" "openssl req -newkey rsa:2048 -nodes -keyout host.key -x509 -days 365 -out host.cert", ) # https://www.digitalocean.com/community/tutorials/openssl-essentials-working-with-ssl-certificates-private-keys-and-csrs other.add_argument( "--remap-sources", "-m", help=( "Replace compile-time source paths to local source paths. " "Pass valid JSON key/value pairs." 'i.e. --remap-sources=\'{"/buildmachine": "/current/machine"}\'' ), ) other.add_argument( "--project", help='Set the project directory. When viewing the "folders" pane, paths are shown relative to this directory.', ) other.add_argument("-v", "--version", help="Print version", action="store_true") other.add_argument( "-n", "--no-browser", help="By default, the browser will open with gdbgui. Pass this flag so the browser does not open.", action="store_true", ) other.add_argument( "-b", "--browser", help="Use the given browser executable instead of the system default.", default=None, ) other.add_argument( "--debug", help="The debug flag of this Flask application. " "Pass this flag when debugging gdbgui itself to automatically reload the server when changes are detected", action="store_true", ) args_group.add_argument( "debug_program", nargs="?", help="The executable file you wish to debug, and any arguments to pass to it." " To pass flags to the binary, wrap in quotes, or use --args instead." " Example: gdbgui ./mybinary [other-gdbgui-args...]" " Example: gdbgui './mybinary myarg -flag1 -flag2' [other gdbgui args...]", default=None, ) args_group.add_argument( "--args", nargs=argparse.REMAINDER, help="Specify the executable file you wish to debug and any arguments to pass to it. All arguments are" " taken literally, so if used, this must be the last argument. This can also be specified later in the frontend." " passed to gdbgui." " Example: gdbgui [...] --args ./mybinary myarg -flag1 -flag2", default=[], ) return parser def get_initial_binary_and_args( user_supplied_args: List[str], debug_program_and_args: Optional[str] ) -> List[str]: if debug_program_and_args: # passed via positional return shlex.split(debug_program_and_args) else: # passed via --args return user_supplied_args def main(): """Entry point from command line""" parser = get_parser() args = parser.parse_args() if args.version: print(__version__) return if args.no_browser and args.browser: print("Cannot specify no-browser and browser. Must specify one or the other.") exit(1) app.config["gdb_command"] = args.gdb_cmd app.config["initial_binary_and_args"] = get_initial_binary_and_args( args.args, args.debug_program ) app.config["gdbgui_auth_user_credentials"] = get_gdbgui_auth_user_credentials( args.auth_file, args.user, args.password ) app.config["project_home"] = args.project if args.remap_sources: try: app.config["remap_sources"] = json.loads(args.remap_sources) except json.decoder.JSONDecodeError as e: print( "The '--remap-sources' argument must be valid JSON. See gdbgui --help." ) print(e) exit(1) if args.remote: args.host = "0.0.0.0" args.no_browser = True if app.config["gdbgui_auth_user_credentials"] is None: print( "Warning: authentication is recommended when serving on a publicly " "accessible IP address. See gdbgui --help." ) if warn_startup_with_shell_off(platform.platform().lower(), args.gdb_cmd): logger.warning( "You may need to set startup-with-shell off when running on a mac. i.e.\n" " gdbgui --gdb-cmd='gdb --init-eval-command=\"set startup-with-shell off\"'\n" "see http://stackoverflow.com/questions/39702871/gdb-kind-of-doesnt-work-on-macos-sierra\n" "and https://sourceware.org/gdb/onlinedocs/gdb/Starting.html" ) logger.setLevel(logging.DEBUG if args.debug else logging.INFO) run_server( app=app, socketio=socketio, host=args.host, port=int(args.port), debug=bool(args.debug), open_browser=(not args.no_browser), browsername=args.browser, private_key=args.key, certificate=args.cert, ) if __name__ == "__main__": main() ================================================ FILE: gdbgui/htmllistformatter.py ================================================ from pygments.formatters import HtmlFormatter # type: ignore class HtmlListFormatter(HtmlFormatter): """A custom pygments class to format html. Returns a list of source code. Each element of the list corresponds to a line of (marked up) source code. """ def get_marked_up_list(self, tokensource): """an updated version of pygments.formatter.format_unencoded""" source = self._format_lines(tokensource) if self.hl_lines: source = self._highlight_lines(source) if not self.nowrap: if self.linenos == 2: source = self._wrap_inlinelinenos(source) if self.lineanchors: source = self._wrap_lineanchors(source) if self.linespans: source = self._wrap_linespans(source) if self.linenos == 1: source = self._wrap_tablelinenos(source) # instead of this: # for t, piece in source: # outfile.write(piece) # evaluate the generator to a list of just source code: IS_CODE_INDEX = 0 HTML_VALUE_INDEX = 1 IS_CODE_VAL = 1 source_list = [ html_line[HTML_VALUE_INDEX] for html_line in self._wrap_div(self._wrap_pre(source)) if html_line[IS_CODE_INDEX] == IS_CODE_VAL ] return source_list ================================================ FILE: gdbgui/py.typed ================================================ # Marker file for PEP 561. This package uses inline types. # https://mypy.readthedocs.io/en/latest/installed_packages.html#making-pep-561-compatible-packages ================================================ FILE: gdbgui/server/__init__.py ================================================ ================================================ FILE: gdbgui/server/app.py ================================================ import binascii import logging import os from typing import Dict, List import traceback from flask import Flask, abort, request, session from flask_compress import Compress # type: ignore from flask_socketio import SocketIO, emit # type: ignore from .constants import DEFAULT_GDB_EXECUTABLE, STATIC_DIR, TEMPLATE_DIR from .http_routes import blueprint from .http_util import is_cross_origin from .sessionmanager import SessionManager, DebugSession logger = logging.getLogger(__file__) # Create flask application and add some configuration keys to be used in various callbacks app = Flask(__name__, template_folder=str(TEMPLATE_DIR), static_folder=str(STATIC_DIR)) Compress( app ) # add gzip compression to Flask. see https://github.com/libwilliam/flask-compress app.register_blueprint(blueprint) app.config["initial_binary_and_args"] = [] app.config["gdb_path"] = DEFAULT_GDB_EXECUTABLE app.config["gdb_command"] = None app.config["TEMPLATES_AUTO_RELOAD"] = True app.config["project_home"] = None app.config["remap_sources"] = {} manager = SessionManager() app.config["_manager"] = manager app.secret_key = binascii.hexlify(os.urandom(24)).decode("utf-8") socketio = SocketIO(manage_session=False) @app.before_request def csrf_protect_all_post_and_cross_origin_requests(): """returns None upon success""" success = None if is_cross_origin(request): logger.warning("Received cross origin request. Aborting") abort(403) if request.method in ["POST", "PUT"]: server_token = session.get("csrf_token") if server_token == request.form.get("csrf_token"): return success elif server_token == request.environ.get("HTTP_X_CSRFTOKEN"): return success elif request.json and server_token == request.json.get("csrf_token"): return success else: logger.warning("Received invalid csrf token. Aborting") abort(403) @socketio.on("connect", namespace="/gdb_listener") def client_connected(): """Connect a websocket client to a debug session This is the main intial connection. Depending on the arguments passed, the client will connect to an existing debug session, or create a new one. A message is a emitted back to the client with details on the debug session that was created or connected to. """ if is_cross_origin(request): logger.warning("Received cross origin request. Aborting") abort(403) csrf_token = request.args.get("csrf_token") if csrf_token is None: logger.warning("Recieved invalid csrf token") emit("server_error", {"message": "Recieved invalid csrf token"}) return elif csrf_token != session.get("csrf_token"): # this can happen fairly often, so log debug message, not warning logger.debug( "Recieved invalid csrf token %s (expected %s)" % (csrf_token, str(session.get("csrf_token"))) ) emit( "server_error", {"message": "Session expired. Please refresh this webpage."} ) return desired_gdbpid = int(request.args.get("gdbpid", 0)) try: if desired_gdbpid: # connect to exiting debug session debug_session = manager.connect_client_to_debug_session( desired_gdbpid=desired_gdbpid, client_id=request.sid ) emit( "debug_session_connection_event", { "ok": True, "started_new_gdb_process": False, "pid": debug_session.pid, "message": f"Connected to existing gdb process {desired_gdbpid}", }, ) else: # start new debug session gdb_command = request.args.get("gdb_command", app.config["gdb_command"]) mi_version = request.args.get("mi_version", "mi2") debug_session = manager.add_new_debug_session( gdb_command=gdb_command, mi_version=mi_version, client_id=request.sid ) emit( "debug_session_connection_event", { "ok": True, "started_new_gdb_process": True, "message": f"Started new gdb process, pid {debug_session.pid}", "pid": debug_session.pid, }, ) except Exception as e: emit( "debug_session_connection_event", {"message": f"Failed to establish gdb session: {e}", "ok": False}, ) # Make sure there is a reader thread reading. One thread reads all instances. if manager.gdb_reader_thread is None: manager.gdb_reader_thread = socketio.start_background_task( target=read_and_forward_gdb_and_pty_output ) logger.info("Created background thread to read gdb responses") @socketio.on("pty_interaction", namespace="/gdb_listener") def pty_interaction(message): """Write a character to the user facing pty""" debug_session = manager.debug_session_from_client_id(request.sid) if not debug_session: emit( "error_running_gdb_command", {"message": f"no gdb session available for client id {request.sid}"}, ) return try: data = message.get("data") pty_name = data.get("pty_name") if pty_name == "user_pty": pty = debug_session.pty_for_gdb elif pty_name == "program_pty": pty = debug_session.pty_for_debugged_program else: raise ValueError(f"Unknown pty: {pty_name}") action = data.get("action") if action == "write": key = data["key"] pty.write(key) elif action == "set_winsize": pty.set_winsize(data["rows"], data["cols"]) else: raise ValueError(f"Unknown action {action}") except Exception: err = traceback.format_exc() logger.error(err) emit("error_running_gdb_command", {"message": err}) @socketio.on("run_gdb_command", namespace="/gdb_listener") def run_gdb_command(message: Dict[str, str]): """Write commands to gdbgui's gdb mi pty""" client_id = request.sid # type: ignore debug_session = manager.debug_session_from_client_id(client_id) if not debug_session: emit("error_running_gdb_command", {"message": "no session"}) return pty_mi = debug_session.pygdbmi_controller if pty_mi is not None: try: # the command (string) or commands (list) to run cmds = message["cmd"] for cmd in cmds: pty_mi.write( cmd + "\n", timeout_sec=0, raise_error_on_timeout=False, read_response=False, ) except Exception: err = traceback.format_exc() logger.error(err) emit("error_running_gdb_command", {"message": err}) else: emit("error_running_gdb_command", {"message": "gdb is not running"}) def send_msg_to_clients(client_ids, msg, error=False): """Send message to all clients""" if error: stream = "stderr" else: stream = "stdout" response = [{"message": None, "type": "console", "payload": msg, "stream": stream}] for client_id in client_ids: logger.info("emiting message to websocket client id " + client_id) socketio.emit( "gdb_response", response, namespace="/gdb_listener", room=client_id ) @socketio.on("disconnect", namespace="/gdb_listener") def client_disconnected(): """do nothing if client disconnects""" manager.disconnect_client(request.sid) logger.info("Client websocket disconnected, id %s" % (request.sid)) @socketio.on("Client disconnected") def test_disconnect(): print("Client websocket disconnected", request.sid) def read_and_forward_gdb_and_pty_output(): """A task that runs on a different thread, and emits websocket messages of gdb responses""" while True: socketio.sleep(0.05) debug_sessions_to_remove = [] for debug_session, client_ids in manager.debug_session_to_client_ids.items(): try: try: response = debug_session.pygdbmi_controller.get_gdb_response( timeout_sec=0, raise_error_on_timeout=False ) except Exception: response = None send_msg_to_clients( client_ids, "The underlying gdb process has been killed. This tab will no longer function as expected.", error=True, ) debug_sessions_to_remove.append(debug_session) if response: for client_id in client_ids: logger.info( "emiting message to websocket client id " + client_id ) socketio.emit( "gdb_response", response, namespace="/gdb_listener", room=client_id, ) else: # there was no queued response from gdb, not a problem pass except Exception: logger.error("caught exception, continuing:" + traceback.format_exc()) debug_sessions_to_remove += check_and_forward_pty_output() for debug_session in set(debug_sessions_to_remove): manager.remove_debug_session(debug_session) def check_and_forward_pty_output() -> List[DebugSession]: debug_sessions_to_remove = [] for debug_session, client_ids in manager.debug_session_to_client_ids.items(): try: response = debug_session.pty_for_gdb.read() if response is not None: for client_id in client_ids: socketio.emit( "user_pty_response", response, namespace="/gdb_listener", room=client_id, ) response = debug_session.pty_for_debugged_program.read() if response is not None: for client_id in client_ids: socketio.emit( "program_pty_response", response, namespace="/gdb_listener", room=client_id, ) except Exception as e: debug_sessions_to_remove.append(debug_session) for client_id in client_ids: socketio.emit( "fatal_server_error", {"message": str(e)}, namespace="/gdb_listener", room=client_id, ) logger.error(e, exc_info=True) return debug_sessions_to_remove ================================================ FILE: gdbgui/server/constants.py ================================================ import os import signal import sys from pathlib import Path DEFAULT_GDB_EXECUTABLE = "gdb" DEFAULT_HOST = "127.0.0.1" DEFAULT_PORT = 5000 USING_WINDOWS = os.name == "nt" IS_A_TTY = sys.stdout.isatty() pyinstaller_base_dir = getattr(sys, "_MEIPASS", None) using_pyinstaller = pyinstaller_base_dir is not None if using_pyinstaller: BASE_PATH = Path(pyinstaller_base_dir or "") else: BASE_PATH = Path(os.path.realpath(__file__)).parent.parent PARENTDIR = BASE_PATH.parent sys.path.append(str(PARENTDIR)) TEMPLATE_DIR = BASE_PATH / "templates" STATIC_DIR = BASE_PATH / "static" def colorize(text): if IS_A_TTY and not USING_WINDOWS: return "\033[1;32m" + text + "\x1b[0m" else: return text # create dictionary of signal names SIGNAL_NAME_TO_OBJ = {} for n in dir(signal): if n.startswith("SIG") and "_" not in n: SIGNAL_NAME_TO_OBJ[n.upper()] = getattr(signal, n) ================================================ FILE: gdbgui/server/http_routes.py ================================================ import json import logging import os from flask import ( Blueprint, current_app, jsonify, redirect, render_template, request, session, Response, ) from pygments.lexers import get_lexer_for_filename # type: ignore from gdbgui import htmllistformatter, __version__ from .constants import TEMPLATE_DIR, USING_WINDOWS, SIGNAL_NAME_TO_OBJ from .http_util import ( add_csrf_token_to_session, authenticate, client_error, csrf_protect, ) logger = logging.getLogger(__file__) blueprint = Blueprint("http_routes", __name__, template_folder=str(TEMPLATE_DIR)) @blueprint.route("/read_file", methods=["GET"]) @csrf_protect def read_file(): """Read a file and return its contents as an array""" def should_highlight(): try: return json.loads(request.args.get("highlight", "true")) except Exception as e: if current_app.debug: print("Raising exception since debug is on") raise e else: return True # highlight argument was invalid for some reason, default to true path = request.args.get("path") start_line = int(request.args.get("start_line")) start_line = max(1, start_line) # make sure it's not negative end_line = int(request.args.get("end_line")) if path and os.path.isfile(path): try: last_modified = os.path.getmtime(path) with open(path, "r") as f: raw_source_code_list = f.read().split("\n") num_lines_in_file = len(raw_source_code_list) end_line = min( num_lines_in_file, end_line ) # make sure we don't try to go too far # if leading lines are '', then the lexer will strip them out, but we want # to preserve blank lines. Insert a space whenever we find a blank line. for i in range((start_line - 1), (end_line)): if raw_source_code_list[i] == "": raw_source_code_list[i] = " " raw_source_code_lines_of_interest = raw_source_code_list[ (start_line - 1) : (end_line) ] try: lexer = get_lexer_for_filename(path) except Exception: lexer = None if lexer and should_highlight(): highlighted = True # convert string into tokens tokens = lexer.get_tokens("\n".join(raw_source_code_lines_of_interest)) # format tokens into nice, marked up list of html formatter = ( htmllistformatter.HtmlListFormatter() ) # Don't add newlines after each line source_code = formatter.get_marked_up_list(tokens) else: highlighted = False source_code = raw_source_code_lines_of_interest return jsonify( { "source_code_array": source_code, "path": path, "last_modified_unix_sec": last_modified, "highlighted": highlighted, "start_line": start_line, "end_line": end_line, "num_lines_in_file": num_lines_in_file, } ) except Exception as e: return client_error({"message": "%s" % e}) else: return client_error({"message": "File not found: %s" % path}) @blueprint.route("/get_last_modified_unix_sec", methods=["GET"]) @csrf_protect def get_last_modified_unix_sec(): """Get last modified unix time for a given file""" path = request.args.get("path") if path and os.path.isfile(path): try: last_modified = os.path.getmtime(path) return jsonify({"path": path, "last_modified_unix_sec": last_modified}) except Exception as e: return client_error({"message": "%s" % e, "path": path}) else: return client_error({"message": "File not found: %s" % path, "path": path}) @blueprint.route("/help") def help_route(): return redirect("https://github.com/cs01/gdbgui/blob/master/HELP.md") @blueprint.route("/dashboard", methods=["GET"]) @authenticate def dashboard(): manager = current_app.config.get("_manager") add_csrf_token_to_session() """display a dashboard with a list of all running gdb processes and ability to kill them, or open a new tab to work with that GdbController instance""" return render_template( "dashboard.html", gdbgui_sessions=manager.get_dashboard_data(), csrf_token=session["csrf_token"], default_command=current_app.config["gdb_command"], ) @blueprint.route("/", methods=["GET"]) @authenticate def gdbgui(): """Render the main gdbgui interface""" gdbpid = request.args.get("gdbpid", 0) gdb_command = request.args.get("gdb_command", current_app.config["gdb_command"]) add_csrf_token_to_session() THEMES = ["monokai", "light"] initial_data = { "csrf_token": session["csrf_token"], "gdbgui_version": __version__, "gdbpid": gdbpid, "gdb_command": gdb_command, "initial_binary_and_args": current_app.config["initial_binary_and_args"], "project_home": current_app.config["project_home"], "remap_sources": current_app.config["remap_sources"], "themes": THEMES, "signals": SIGNAL_NAME_TO_OBJ, "using_windows": USING_WINDOWS, } return render_template( "gdbgui.html", version=__version__, debug=current_app.debug, initial_data=initial_data, themes=THEMES, ) @blueprint.route("/dashboard_data", methods=["GET"]) @authenticate def dashboard_data(): manager = current_app.config.get("_manager") return jsonify(manager.get_dashboard_data()) @blueprint.route("/kill_session", methods=["PUT"]) @authenticate def kill_session(): from .app import manager pid = request.json.get("gdbpid") if pid: manager.remove_debug_session_by_pid(pid) return jsonify({"success": True}) else: return Response( "Missing required parameter: gdbpid", 401, ) @blueprint.route("/send_signal_to_pid", methods=["POST"]) def send_signal_to_pid(): signal_name = request.form.get("signal_name", "").upper() pid_str = str(request.form.get("pid")) try: pid_int = int(pid_str) except ValueError: return ( jsonify( { "message": "The pid %s cannot be converted to an integer. Signal %s was not sent." % (pid_str, signal_name) } ), 400, ) if signal_name not in SIGNAL_NAME_TO_OBJ: raise ValueError("no such signal %s" % signal_name) signal_value = int(SIGNAL_NAME_TO_OBJ[signal_name]) try: os.kill(pid_int, signal_value) except Exception: return ( jsonify( { "message": "Process could not be killed. Is %s an active PID?" % pid_int } ), 400, ) return jsonify( { "message": "sent signal %s (%s) to process id %s" % (signal_name, signal_value, pid_str) } ) ================================================ FILE: gdbgui/server/http_util.py ================================================ import binascii import logging import os from functools import wraps from flask import Response, abort, current_app, jsonify, request, session logger = logging.getLogger(__file__) def add_csrf_token_to_session(): if "csrf_token" not in session: session["csrf_token"] = binascii.hexlify(os.urandom(20)).decode("utf-8") def is_cross_origin(request): """Compare headers HOST and ORIGIN. Remove protocol prefix from ORIGIN, then compare. Return true if they are not equal example HTTP_HOST: '127.0.0.1:5000' example HTTP_ORIGIN: 'http://127.0.0.1:5000' """ origin = request.environ.get("HTTP_ORIGIN") host = request.environ.get("HTTP_HOST") if origin is None: # origin is sometimes omitted by the browser when origin and host are equal return False if origin.startswith("http://"): origin = origin.replace("http://", "") elif origin.startswith("https://"): origin = origin.replace("https://", "") return host != origin def csrf_protect(f): """A decorator to add csrf protection by validing the X_CSRFTOKEN field in request header""" @wraps(f) def wrapper(*args, **kwargs): token = session.get("csrf_token", None) if token is None or token != request.environ.get("HTTP_X_CSRFTOKEN"): logger.warning("Received invalid csrf token. Aborting") abort(403) # call original request handler return f(*args, **kwargs) return wrapper def client_error(obj): return jsonify(obj), 400 def authenticate(f): @wraps(f) def wrapper(*args, **kwargs): if current_app.config.get("gdbgui_auth_user_credentials") is not None: auth = request.authorization if ( not auth or not auth.username or not auth.password or not credentials_are_valid(auth.username, auth.password) ): return Response( "You must log in to continue.", 401, {"WWW-Authenticate": 'Basic realm="gdbgui_login"'}, ) return f(*args, **kwargs) return wrapper def credentials_are_valid(username, password): user_credentials = current_app.config.get("gdbgui_auth_user_credentials") if user_credentials is None: return False elif len(user_credentials) < 2: return False return user_credentials[0] == username and user_credentials[1] == password ================================================ FILE: gdbgui/server/ptylib.py ================================================ import os USING_WINDOWS = os.name == "nt" if USING_WINDOWS: raise RuntimeError( "Windows is not supported at this time. " + "Versions lower than 0.14.x. are Windows compatible." ) import fcntl import pty import select import shlex import signal import struct import termios from typing import Optional class Pty: max_read_bytes = 1024 * 20 def __init__(self, *, cmd: Optional[str] = None, echo: bool = True): if cmd: (child_pid, fd) = pty.fork() if child_pid == 0: # this is the child process fork. # anything printed here will show up in the pty, including the output # of this subprocess def sigint_handler(_sig, _frame): # prevent SIGINT (ctrl+c) from exiting # the whole program pass signal.signal(signal.SIGINT, sigint_handler) args = shlex.split(cmd) os.execvp(args[0], args) else: # this is the parent process fork. # store child fd and pid self.stdin = fd self.stdout = fd self.pid = child_pid else: (master, slave) = pty.openpty() self.stdin = master self.stdout = master self.name = os.ttyname(slave) self.set_echo(echo) def set_echo(self, echo_on: bool) -> None: (iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(self.stdin) if echo_on: lflag = lflag & termios.ECHO # type: ignore else: lflag = lflag & ~termios.ECHO # type: ignore termios.tcsetattr( self.stdin, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc], ) def set_winsize(self, rows: int, cols: int): xpix = 0 ypix = 0 winsize = struct.pack("HHHH", rows, cols, xpix, ypix) if self.stdin is None: raise RuntimeError("fd stdin not assigned") fcntl.ioctl(self.stdin, termios.TIOCSWINSZ, winsize) def read(self) -> Optional[str]: if self.stdout is None: return "done" timeout_sec = 0 (data_to_read, _, _) = select.select([self.stdout], [], [], timeout_sec) if data_to_read: try: response = os.read(self.stdout, self.max_read_bytes).decode() except (OSError, UnicodeDecodeError): return None return response return None def write(self, data: str): edata = data.encode() os.write(self.stdin, edata) ================================================ FILE: gdbgui/server/server.py ================================================ import os import socket import webbrowser from .constants import DEFAULT_HOST, DEFAULT_PORT, colorize try: from gdbgui.SSLify import SSLify, get_ssl_context # noqa except ImportError: print("Warning: Optional SSL support is not available") def get_ssl_context(private_key, certificate): # noqa return None def get_extra_files(): """returns a list of files that should be watched by the Flask server when in debug mode to trigger a reload of the server """ FILES_TO_SKIP = ["src/gdbgui.js"] THIS_DIR = os.path.dirname(os.path.abspath(__file__)) extra_dirs = [THIS_DIR] extra_files = [] for extra_dir in extra_dirs: for dirname, _, files in os.walk(extra_dir): for filename in files: filepath = os.path.join(dirname, filename) if os.path.isfile(filepath) and filepath not in extra_files: for skipfile in FILES_TO_SKIP: if skipfile not in filepath: extra_files.append(filepath) return extra_files def run_server( *, app=None, socketio=None, host=DEFAULT_HOST, port=DEFAULT_PORT, debug=False, open_browser=True, browsername=None, testing=False, private_key=None, certificate=None, ): """Run the server of the gdb gui""" kwargs = {} ssl_context = get_ssl_context(private_key, certificate) if ssl_context: # got valid ssl context # force everything through https SSLify(app) # pass ssl_context to flask kwargs["ssl_context"] = ssl_context url = "%s:%s" % (host, port) if kwargs.get("ssl_context"): protocol = "https://" url_with_prefix = "https://" + url else: protocol = "http://" url_with_prefix = "http://" + url socketio.server_options["allow_upgrades"] = False socketio.init_app(app) if testing is False: if host == DEFAULT_HOST: url = (DEFAULT_HOST, port) else: try: url = (socket.gethostbyname(socket.gethostname()), port) except Exception: url = (host, port) if open_browser is True and debug is False: browsertext = repr(browsername) if browsername else "default browser" args = (browsertext,) + url text = ("Opening gdbgui with %s at " + protocol + "%s:%d") % args print(colorize(text)) b = webbrowser.get(browsername) if browsername else webbrowser b.open(url_with_prefix) else: print(colorize(f"View gdbgui at {protocol}{url[0]}:{url[1]}")) print( colorize(f"View gdbgui dashboard at {protocol}{url[0]}:{url[1]}/dashboard") ) print("exit gdbgui by pressing CTRL+C") try: socketio.run( app, debug=debug, port=int(port), host=host, extra_files=get_extra_files(), **kwargs, ) except KeyboardInterrupt: # Process was interrupted by ctrl+c on keyboard, show message pass ================================================ FILE: gdbgui/server/sessionmanager.py ================================================ import datetime import logging import os import signal import traceback from collections import defaultdict from typing import Dict, List, Optional, Set from pygdbmi.IoManager import IoManager from .ptylib import Pty logger = logging.getLogger(__name__) class DebugSession: def __init__( self, *, pygdbmi_controller: IoManager, pty_for_gdbgui: Pty, pty_for_gdb: Pty, pty_for_debugged_program: Pty, command: str, mi_version: str, pid: int, ): self.command = command self.pygdbmi_controller = pygdbmi_controller self.pty_for_gdbgui = pty_for_gdbgui self.pty_for_gdb = pty_for_gdb self.pty_for_debugged_program = pty_for_debugged_program self.mi_version = mi_version self.pid = pid self.start_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.client_ids: Set[str] = set() def terminate(self): if self.pid: try: os.kill(self.pid, signal.SIGKILL) except Exception as e: logger.error(f"Failed to kill pid {self.pid}: {str(e)}") self.pygdbmi_controller = None def to_dict(self): return { "pid": self.pid, "start_time": self.start_time, "command": self.command, "c2": "hi", "client_ids": list(self.client_ids), } def add_client(self, client_id: str): self.client_ids.add(client_id) def remove_client(self, client_id: str): self.client_ids.discard(client_id) if len(self.client_ids) == 0: self.terminate() class SessionManager(object): def __init__(self): self.debug_session_to_client_ids: Dict[DebugSession, List[str]] = defaultdict( list ) # key is controller, val is list of client ids self.gdb_reader_thread = None def connect_client_to_debug_session( self, *, desired_gdbpid: int, client_id: str ) -> DebugSession: debug_session = self.debug_session_from_pid(desired_gdbpid) if not debug_session: raise ValueError(f"No existing gdb process with pid {desired_gdbpid}") debug_session.add_client(client_id) self.debug_session_to_client_ids[debug_session].append(client_id) return debug_session def add_new_debug_session( self, *, gdb_command: str, mi_version: str, client_id: str ) -> DebugSession: pty_for_debugged_program = Pty() pty_for_gdbgui = Pty(echo=False) gdbgui_startup_cmds = [ f"new-ui {mi_version} {pty_for_gdbgui.name}", f"set inferior-tty {pty_for_debugged_program.name}", "set pagination off", ] # instead of writing to the pty after it starts, add startup # commands to gdb. This allows gdb to be run as sudo and prompt for a # password, for example. gdbgui_startup_cmds_str = " ".join([f"-iex='{c}'" for c in gdbgui_startup_cmds]) pty_for_gdb = Pty(cmd=f"{gdb_command} {gdbgui_startup_cmds_str}") pid = pty_for_gdb.pid debug_session = DebugSession( pygdbmi_controller=IoManager( os.fdopen(pty_for_gdbgui.stdin, mode="wb", buffering=0), # type: ignore os.fdopen(pty_for_gdbgui.stdout, mode="rb", buffering=0), # type: ignore None, ), pty_for_gdbgui=pty_for_gdbgui, pty_for_gdb=pty_for_gdb, pty_for_debugged_program=pty_for_debugged_program, command=gdb_command, mi_version=mi_version, pid=pid, ) debug_session.add_client(client_id) self.debug_session_to_client_ids[debug_session] = [client_id] return debug_session def remove_debug_session_by_pid(self, gdbpid: int) -> List[str]: debug_session = self.debug_session_from_pid(gdbpid) if debug_session: orphaned_client_ids = self.remove_debug_session(debug_session) else: logger.info(f"could not find debug session with gdb pid {gdbpid}") orphaned_client_ids = [] return orphaned_client_ids def remove_debug_session(self, debug_session: DebugSession) -> List[str]: logger.info(f"Removing debug session for pid {debug_session.pid}") try: debug_session.terminate() except Exception: logger.error(traceback.format_exc()) orphaned_client_ids = self.debug_session_to_client_ids.pop(debug_session, []) return orphaned_client_ids def remove_debug_sessions_with_no_clients(self) -> None: to_remove = [] for debug_session, _ in self.debug_session_to_client_ids.items(): if len(debug_session.client_ids) == 0: to_remove.append(debug_session) for debug_session in to_remove: self.remove_debug_session(debug_session) def get_pid_from_debug_session(self, debug_session: DebugSession) -> Optional[int]: if debug_session and debug_session.pid: return debug_session.pid return None def debug_session_from_pid(self, pid: int) -> Optional[DebugSession]: for debug_session in self.debug_session_to_client_ids: this_pid = self.get_pid_from_debug_session(debug_session) if this_pid == pid: return debug_session return None def debug_session_from_client_id(self, client_id: str) -> Optional[DebugSession]: for debug_session, client_ids in self.debug_session_to_client_ids.items(): if client_id in client_ids: return debug_session return None def get_dashboard_data(self) -> List[DebugSession]: return [ debug_session.to_dict() for debug_session in self.debug_session_to_client_ids.keys() ] def disconnect_client(self, client_id: str): for debug_session, client_ids in self.debug_session_to_client_ids.items(): if client_id in client_ids: client_ids.remove(client_id) debug_session.remove_client(client_id) self.remove_debug_sessions_with_no_clients() ================================================ FILE: gdbgui/src/js/Actions.ts ================================================ import { store } from "statorgfc"; import GdbApi from "./GdbApi"; import SourceCode from "./SourceCode"; import Locals from "./Locals"; import Memory from "./Memory"; import constants from "./constants"; import React from "react"; void React; // using jsx implicity uses React const Actions = { clear_program_state: function() { store.set("line_of_source_to_flash", undefined); store.set("paused_on_frame", undefined); store.set("selected_frame_num", 0); store.set("current_thread_id", undefined); store.set("stack", []); store.set("threads", []); Memory.clear_cache(); Locals.clear(); }, inferior_program_starting: function() { store.set("inferior_program", constants.inferior_states.running); Actions.clear_program_state(); }, inferior_program_resuming: function() { store.set("inferior_program", constants.inferior_states.running); }, inferior_program_paused: function(frame = {}) { store.set("inferior_program", constants.inferior_states.paused); store.set( "source_code_selection_state", constants.source_code_selection_states.PAUSED_FRAME ); store.set("paused_on_frame", frame); // @ts-expect-error ts-migrate(2339) FIXME: Property 'fullname' does not exist on type '{}'. store.set("fullname_to_render", frame.fullname); // @ts-expect-error ts-migrate(2339) FIXME: Property 'line' does not exist on type '{}'. store.set("line_of_source_to_flash", parseInt(frame.line)); // @ts-expect-error ts-migrate(2339) FIXME: Property 'addr' does not exist on type '{}'. store.set("current_assembly_address", frame.addr); store.set("source_code_infinite_scrolling", false); SourceCode.make_current_line_visible(); Actions.refresh_state_for_gdb_pause(); }, inferior_program_exited: function() { store.set("inferior_program", constants.inferior_states.exited); store.set("disassembly_for_missing_file", []); store.set("root_gdb_tree_var", null); store.set("previous_register_values", {}); store.set("current_register_values", {}); store.set("inferior_pid", null); Actions.clear_program_state(); }, /** * Request relevant store information from gdb to refresh UI */ refresh_state_for_gdb_pause: function() { GdbApi.run_gdb_command(GdbApi._get_refresh_state_for_pause_cmds()); }, execute_console_command: function(command: any) { if (store.get("refresh_state_after_sending_console_command")) { GdbApi.run_command_and_refresh_state(command); } else { GdbApi.run_gdb_command(command); } }, onConsoleCommandRun: function() { if (store.get("refresh_state_after_sending_console_command")) { GdbApi.run_gdb_command(GdbApi._get_refresh_state_for_pause_cmds()); } }, clear_console: function() { store.set("gdb_console_entries", []); }, add_console_entries: function(entries: any, type: any) { if (type === constants.console_entry_type.STD_OUT) { // ignore return; } if (!Array.isArray(entries)) { entries = [entries]; } const pty = store.get("gdbguiPty"); if (pty) { entries.forEach((data: string) => { const entriesToIgnore = [ // No registers. appears when refresh commands are run when program hasn't started. // TODO The real fix for this is to not refresh commands when the program is not running. "No registers." ]; if (entriesToIgnore.indexOf(data) > -1) { return; } // @ts-expect-error ts-migrate(2339) FIXME: Property 'colorTypeMap' does not exist on type 'Re... Remove this comment to see the full error message pty.write(constants.colorTypeMap[type] ?? constants.xtermColors["reset"]); pty.writeln(data); pty.write(constants.xtermColors["reset"]); }); } else { console.error("Pty not available. New entries are:", entries); } }, add_gdb_response_to_console(mi_obj: any) { if (!mi_obj) { return; } // Update status let entries = [], error = false; if (mi_obj.message) { if (mi_obj.message === "error") { error = true; } else { entries.push(mi_obj.message); } } if (mi_obj.payload) { const interesting_keys = ["msg", "reason", "signal-name", "signal-meaning"]; for (let k of interesting_keys) { if (mi_obj.payload[k]) { entries.push(mi_obj.payload[k]); } } if (mi_obj.payload.frame) { for (let i of ["file", "func", "line", "addr"]) { if (i in mi_obj.payload.frame) { entries.push(`${i}: ${mi_obj.payload.frame[i]}`); } } } } let type = error ? constants.console_entry_type.STD_ERR : constants.console_entry_type.STD_OUT; Actions.add_console_entries(entries, type); }, toggle_modal_visibility() { store.set("show_modal", !store.get("show_modal")); }, show_modal(header: any, body: any) { store.set("modal_header", header); store.set("modal_body", body); store.set("show_modal", true); }, set_gdb_binary_and_arguments(binary: any, args: any) { // remove list of source files associated with the loaded binary since we're loading a new one store.set("source_file_paths", []); store.set("language", "c_family"); store.set("inferior_binary_path", null); Actions.inferior_program_exited(); let cmds = GdbApi.get_load_binary_and_arguments_cmds(binary, args); GdbApi.run_gdb_command(cmds); GdbApi.get_inferior_binary_last_modified_unix_sec(binary); }, connect_to_gdbserver(user_input: any) { // https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Target-Manipulation.html#GDB_002fMI-Target-Manipulation store.set("source_file_paths", []); store.set("language", "c_family"); store.set("inferior_binary_path", null); Actions.inferior_program_exited(); GdbApi.run_gdb_command([`-target-select remote ${user_input}`]); }, remote_connected() { Actions.inferior_program_paused(); let cmds = []; if (store.get("auto_add_breakpoint_to_main")) { Actions.add_console_entries( "Connected to remote target! Adding breakpoint to main, then continuing target execution.", constants.console_entry_type.GDBGUI_OUTPUT ); cmds.push("-break-insert main"); cmds.push("-exec-continue"); cmds.push(GdbApi.get_break_list_cmd()); } else { Actions.add_console_entries( 'Connected to remote target! Add breakpoint(s), then press "continue" button (do not press "run").', constants.console_entry_type.GDBGUI_OUTPUT ); } GdbApi.run_gdb_command(cmds); }, attach_to_process(user_input: any) { // https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Target-Manipulation.html#GDB_002fMI-Target-Manipulation GdbApi.run_gdb_command(`-target-attach ${user_input}`); }, fetch_source_files() { store.set("source_file_paths", []); GdbApi.run_gdb_command("-file-list-exec-source-files"); }, view_file(fullname: any, line: any) { store.set("fullname_to_render", fullname); store.set("source_code_infinite_scrolling", false); Actions.set_line_state(line); }, set_line_state(line: any) { store.set("source_code_infinite_scrolling", false); store.set( "source_code_selection_state", constants.source_code_selection_states.USER_SELECTION ); store.set("line_of_source_to_flash", parseInt(line)); store.set("make_current_line_visible", true); }, clear_cached_assembly() { store.set("disassembly_for_missing_file", []); let cached_source_files = store.get("cached_source_files"); for (let file of cached_source_files) { file.assembly = {}; } store.set("cached_source_files", cached_source_files); }, update_max_lines_of_code_to_fetch(new_value: any) { if (new_value <= 0) { new_value = constants.default_max_lines_of_code_to_fetch; } store.set("max_lines_of_code_to_fetch", new_value); localStorage.setItem("max_lines_of_code_to_fetch", JSON.stringify(new_value)); }, send_signal(signal_name: any, pid: any) { $.ajax({ beforeSend: function(xhr) { xhr.setRequestHeader( "x-csrftoken", // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. initial_data.csrf_token ); /* global initial_data */ }, url: "/send_signal_to_pid", cache: false, type: "POST", data: { signal_name: signal_name, pid: pid }, success: function(response) { Actions.add_console_entries( response.message, constants.console_entry_type.GDBGUI_OUTPUT ); }, error: function(response) { if (response.responseJSON && response.responseJSON.message) { Actions.add_console_entries( // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. _.escape(response.responseJSON.message), constants.console_entry_type.STD_ERR ); } else { Actions.add_console_entries( `${response.statusText} (${response.status} error)`, constants.console_entry_type.STD_ERR ); } console.error(response); }, complete: function() {} }); } }; export default Actions; ================================================ FILE: gdbgui/src/js/BinaryLoader.tsx ================================================ import React from "react"; import constants from "./constants"; import Actions from "./Actions"; import Util from "./Util"; import ToolTipTourguide from "./ToolTipTourguide"; const TARGET_TYPES = { file: "file", server: "server", process: "process", target_download: "target_download" }; type State = any; /** * The BinaryLoader component allows the user to select their binary * and specify inputs */ class BinaryLoader extends React.Component<{}, State> { constructor(props: {}) { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); this.state = { past_binaries: [], // @ts-expect-error ts-migrate(2339) FIXME: Property 'initial_user_input' does not exist on ty... Remove this comment to see the full error message user_input: props.initial_user_input.join(" "), // @ts-expect-error ts-migrate(2339) FIXME: Property 'initial_user_input' does not exist on ty... Remove this comment to see the full error message initial_set_target_app: props.initial_user_input.length, // if user supplied initial binary, load it immediately target_type: TARGET_TYPES.file }; try { // @ts-expect-error ts-migrate(2542) FIXME: Index signature in type 'Readonly' only permi... Remove this comment to see the full error message this.state.past_binaries = _.uniq( // @ts-expect-error ts-migrate(2345) FIXME: Type 'null' is not assignable to type 'string'. JSON.parse(localStorage.getItem("past_binaries")) ); if (!this.state.user_input) { let most_recent_binary = this.state.past_binaries[0]; // @ts-expect-error ts-migrate(2542) FIXME: Index signature in type 'Readonly' only permi... Remove this comment to see the full error message this.state.user_input = most_recent_binary; } } catch (err) { // @ts-expect-error ts-migrate(2542) FIXME: Index signature in type 'Readonly' only permi... Remove this comment to see the full error message this.state.past_binaries = []; } } render() { let button_text, title, placeholder; if (this.state.target_type === TARGET_TYPES.file) { button_text = "Load Binary"; title = "Loads the binary and any arguments present in the input to the right. Backslashes are treated as escape characters. Windows users can either use two backslashes in paths, or forward slashes."; placeholder = "/path/to/target/executable -and -flags"; } else if (this.state.target_type === TARGET_TYPES.server) { // https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Target-Manipulation.html#GDB_002fMI-Target-Manipulation // -target-select button_text = "Connect to gdbserver"; title = "Connect GDB to the remote target."; placeholder = "examples: 127.0.0.1:9999 | /dev/ttya"; } else if (this.state.target_type === TARGET_TYPES.process) { // -target-attach button_text = "Attach to Process"; title = "Attach to a process pid or a file file outside of GDB, or a thread group gid. If attaching to a thread group, the id previously returned by ‘-list-thread-groups --available’ must be used. Note: to do this, you usually need to run gdbgui as sudo."; placeholder = "pid | gid | file"; } return (
Enter the path to the binary you wish to debug here.

This is the first thing you should do.

The path can be absolute, or relative to where gdbgui was launched from.

} />
Press this button to load the executable specified in the input.

This is the second thing you should do.

Debugging won't start, but you will be able to set breakpoints. If present,{" "} debugging symbols{" "} in the binary are also loaded.

If you don't want to debug a binary, click the dropdown to choose a different target type.

} /> {this.state.past_binaries.map((b: any, i: any) => ( ))} ); } componentDidMount() { if (this.state.initial_set_target_app) { this.setState({ initial_set_target_app: false }); this.set_target_app(); } } onkeyup_user_input(e: any) { if (e.keyCode === constants.ENTER_BUTTON_NUM) { this.set_target_app(); } } onchange_user_inpu(e: any) { // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. if (initial_data.using_windows) { // replace backslashes with forward slashes when using windows this.setState({ user_input: e.target.value.replace(/\\/g, "/") }); } else { this.setState({ user_input: e.target.value }); } } click_set_target_app() { this.set_target_app(); } // save to list of binaries used that autopopulates the input dropdown _add_user_input_to_history(binary_and_args: any) { // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. _.remove(this.state.past_binaries, (i: any) => i === binary_and_args); this.state.past_binaries.unshift(binary_and_args); // add to beginning this.setState({ past_binaries: this.state.past_binaries }); // @ts-expect-error ts-migrate(2345) FIXME: Type 'never[]' is not assignable to type 'string'. localStorage.setItem("past_binaries", JSON.stringify(this.state.past_binaries) || []); // @ts-expect-error ts-migrate(2345) FIXME: Type 'null' is not assignable to type 'string'. let num_gdbgui_sessions = parseInt(localStorage.getItem("num_gdbgui_sessions")); if (isNaN(num_gdbgui_sessions)) { num_gdbgui_sessions = 0; } } /** * parse tokens with awareness of double quotes * * @param {string} user_input raw input from user * @return {Object} { the binary (string) and arguments (array) parsed from user input } */ _parse_binary_and_args_from_user_input(user_input: any) { let list_of_params = Util.string_to_array_safe_quotes(user_input), binary = "", args: any = [], len = list_of_params.length; if (len === 1) { binary = list_of_params[0]; } else if (len > 1) { binary = list_of_params[0]; args = list_of_params.slice(1, len); } return { binary: binary, args: args.join(" ") }; } set_target_app() { let user_input = this.state.user_input.trim(); if (user_input === "") { Actions.add_console_entries( "input cannot be empty", constants.console_entry_type.GDBGUI_OUTPUT ); return; } this._add_user_input_to_history(user_input); if (this.state.target_type === TARGET_TYPES.file) { const { binary, args } = this._parse_binary_and_args_from_user_input(user_input); Actions.set_gdb_binary_and_arguments(binary, args); } else if (this.state.target_type === TARGET_TYPES.server) { Actions.connect_to_gdbserver(user_input); } else if (this.state.target_type === TARGET_TYPES.process) { Actions.attach_to_process(user_input); } } } export default BinaryLoader; ================================================ FILE: gdbgui/src/js/Breakpoints.tsx ================================================ import React from "react"; import { store } from "statorgfc"; import GdbApi from "./GdbApi"; import Actions from "./Actions"; import Util from "./Util"; import FileOps from "./FileOps"; import { FileLink } from "./Links"; import constants from "./constants"; const BreakpointSourceLineCache = { _cache: {}, get_line: function(fullname: any, linenum: any) { if ( // @ts-expect-error ts-migrate(7053) FIXME: Property 'fullname' does not exist on type '{}'. BreakpointSourceLineCache._cache["fullname"] !== undefined && // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. _.isString(BreakpointSourceLineCache._cache["fullname"][linenum]) ) { // @ts-expect-error ts-migrate(7053) FIXME: Property 'fullname' does not exist on type '{}'. return BreakpointSourceLineCache._cache["fullname"][linenum]; } return null; }, add_line: function(fullname: any, linenum: any, escaped_text: any) { // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (!_.isObject(BreakpointSourceLineCache._cache["fullname"])) { // @ts-expect-error ts-migrate(7053) FIXME: Property 'fullname' does not exist on type '{}'. BreakpointSourceLineCache._cache["fullname"] = {}; } // @ts-expect-error ts-migrate(7053) FIXME: Property 'fullname' does not exist on type '{}'. BreakpointSourceLineCache._cache["fullname"][linenum] = escaped_text; } }; type BreakpointState = any; class Breakpoint extends React.Component<{}, BreakpointState> { constructor(props: {}) { super(props); this.state = { breakpoint_condition: "", editing_breakpoint_condition: false }; } get_source_line(fullname: any, linenum: any) { // if we have the source file cached, we can display the line of text const MAX_CHARS_TO_SHOW_FROM_SOURCE = 40; let line = null; if (BreakpointSourceLineCache.get_line(fullname, linenum)) { line = BreakpointSourceLineCache.get_line(fullname, linenum); // @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 2. } else if (FileOps.line_is_cached(fullname, linenum)) { let syntax_highlighted_line = FileOps.get_line_from_file(fullname, linenum); // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. line = _.trim(Util.get_text_from_html(syntax_highlighted_line)); if (line.length > MAX_CHARS_TO_SHOW_FROM_SOURCE) { line = line.slice(0, MAX_CHARS_TO_SHOW_FROM_SOURCE) + "..."; } BreakpointSourceLineCache.add_line(fullname, linenum, line); } if (line) { return ( {line ||
}
); } return "(file not cached)"; } get_delete_jsx(bkpt_num_to_delete: any) { return (
{ e.stopPropagation(); Breakpoints.delete_breakpoint(bkpt_num_to_delete); }} title={`Delete breakpoint ${bkpt_num_to_delete}`} >
); } get_num_times_hit(bkpt: any) { if ( bkpt.times === undefined || // E.g. 'bkpt' is a child breakpoint bkpt.times == 0 ) { return ""; } else if (bkpt.times == 1) { return "1 hit"; } else { return `${bkpt.times} hits`; } } on_change_bkpt_cond(e: any) { this.setState({ breakpoint_condition: e.target.value, editing_breakpoint_condition: true }); } on_key_up_bktp_cond(number: any, e: any) { if (e.keyCode === constants.ENTER_BUTTON_NUM) { this.setState({ editing_breakpoint_condition: false }); Breakpoints.set_breakpoint_condition(e.target.value, number); } } on_break_cond_click(e: any) { this.setState({ editing_breakpoint_condition: true }); } render() { // @ts-expect-error ts-migrate(2339) FIXME: Property 'bkpt' does not exist on type 'Readonly<{... Remove this comment to see the full error message let b = this.props.bkpt, checked = b.enabled === "y" ? "checked" : "", source_line = this.get_source_line(b.fullname_to_display, b.line); let info_glyph, function_jsx, bkpt_num_to_delete; if (b.is_child_breakpoint) { bkpt_num_to_delete = b.parent_breakpoint_number; info_glyph = ( ); } else if (b.is_parent_breakpoint) { info_glyph = ( ); bkpt_num_to_delete = b.number; } else { bkpt_num_to_delete = b.number; info_glyph = ""; } const delete_jsx = this.get_delete_jsx(bkpt_num_to_delete); let location_jsx = ( ); if (b.is_parent_breakpoint) { function_jsx = ( {info_glyph} parent breakpoint on inline, template, or ambiguous location ); } else { let func = b.func === undefined ? "(unknown function)" : b.func; let break_condition = (
condition
); if (this.state.editing_breakpoint_condition) { break_condition = ( ); } const times_hit = this.get_num_times_hit(b); function_jsx = (
{info_glyph} {func} thread groups: {b["thread-groups"]} {break_condition} {times_hit}
); } return (
Actions.view_file(b.fullname_to_display, b.line)} >
Breakpoints.enable_or_disable_bkpt(checked, b.number)} /> {function_jsx} {delete_jsx}
{location_jsx}
{source_line}
); } // render function } class Breakpoints extends React.Component { constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["breakpoints"]); } render() { let breakpoints_jsx = []; for (let b of store.get("breakpoints")) { // @ts-expect-error ts-migrate(2322) FIXME: Property 'bkpt' does not exist on type 'IntrinsicA... Remove this comment to see the full error message breakpoints_jsx.push(); } if (breakpoints_jsx.length) { return breakpoints_jsx; } else { return no breakpoints; } } static enable_or_disable_bkpt(checked: any, bkpt_num: any) { if (checked) { GdbApi.run_gdb_command([`-break-disable ${bkpt_num}`, GdbApi.get_break_list_cmd()]); } else { GdbApi.run_gdb_command([`-break-enable ${bkpt_num}`, GdbApi.get_break_list_cmd()]); } } static set_breakpoint_condition(condition: any, bkpt_num: any) { GdbApi.run_gdb_command([ `-break-condition ${bkpt_num} ${condition}`, GdbApi.get_break_list_cmd() ]); } static remove_breakpoint_if_present(fullname: any, line: any) { if (Breakpoints.has_breakpoint(fullname, line)) { let number = Breakpoints.get_breakpoint_number(fullname, line); let cmd = [GdbApi.get_delete_break_cmd(number), GdbApi.get_break_list_cmd()]; GdbApi.run_gdb_command(cmd); } } static add_or_remove_breakpoint(fullname: any, line: any) { if (Breakpoints.has_breakpoint(fullname, line)) { Breakpoints.remove_breakpoint_if_present(fullname, line); } else { Breakpoints.add_breakpoint(fullname, line); } } static add_breakpoint(fullname: any, line: any) { GdbApi.run_gdb_command(GdbApi.get_insert_break_cmd(fullname, line)); } static has_breakpoint(fullname: any, line: any) { let bkpts = store.get("breakpoints"); for (let b of bkpts) { if (b.fullname === fullname && b.line == line) { return true; } } return false; } static get_breakpoint_number(fullname: any, line: any) { let bkpts = store.get("breakpoints"); for (let b of bkpts) { if (b.fullname === fullname && b.line == line) { return b.number; } } console.error(`could not find breakpoint for ${fullname}:${line}`); } static delete_breakpoint(breakpoint_number: any) { GdbApi.run_gdb_command([ GdbApi.get_delete_break_cmd(breakpoint_number), GdbApi.get_break_list_cmd() ]); } static get_breakpoint_lines_for_file(fullname: any) { return store .get("breakpoints") .filter((b: any) => b.fullname_to_display === fullname && b.enabled === "y") .map((b: any) => parseInt(b.line)); } static get_disabled_breakpoint_lines_for_file(fullname: any) { return store .get("breakpoints") .filter((b: any) => b.fullname_to_display === fullname && b.enabled !== "y") .map((b: any) => parseInt(b.line)); } static get_conditional_breakpoint_lines_for_file(fullname: any) { return store .get("breakpoints") .filter((b: any) => b.fullname_to_display === fullname && b.cond !== undefined) .map((b: any) => parseInt(b.line)); } static save_breakpoints(payload: any) { store.set("breakpoints", []); if (payload && payload.BreakpointTable && payload.BreakpointTable.body) { for (let breakpoint of payload.BreakpointTable.body) { Breakpoints.save_breakpoint(breakpoint); } } } static save_breakpoint(breakpoint: any) { let bkpt = Object.assign({}, breakpoint); bkpt.is_parent_breakpoint = bkpt.addr === "(MULTIPLE)"; // parent breakpoints have numbers like "5.6", whereas normal breakpoints and parent breakpoints have numbers like "5" bkpt.is_child_breakpoint = parseInt(bkpt.number) !== parseFloat(bkpt.number); bkpt.is_normal_breakpoint = !bkpt.is_parent_breakpoint && !bkpt.is_child_breakpoint; if (bkpt.is_child_breakpoint) { bkpt.parent_breakpoint_number = parseInt(bkpt.number); } if ("fullname" in breakpoint && breakpoint.fullname) { // this is a normal/child breakpoint; gdb gives it the fullname bkpt.fullname_to_display = breakpoint.fullname; } else if ("original-location" in breakpoint && breakpoint["original-location"]) { // this breakpoint is the parent breakpoint of multiple other breakpoints. gdb does not give it // the fullname field, but rather the "original-location" field. // example breakpoint['original-location']: /home/file.h:19 // so we need to parse out the line number, and store it [bkpt.fullname_to_display, bkpt.line] = Util.parse_fullname_and_line( breakpoint["original-location"] ); } else { bkpt.fullname_to_display = null; } // add the breakpoint if it's not stored already let bkpts = store.get("breakpoints"); if (bkpts.indexOf(bkpt) === -1) { bkpts.push(bkpt); store.set("breakpoints", bkpts); } return bkpt; } } export default Breakpoints; ================================================ FILE: gdbgui/src/js/ControlButtons.tsx ================================================ import React from "react"; import Actions from "./Actions"; import GdbApi from "./GdbApi"; import { store } from "statorgfc"; type State = any; class ControlButtons extends React.Component<{}, State> { constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["gdb_pid", "reverse_supported"]); } render() { let btn_class = "btn btn-default btn-sm"; return (
); } } export default ControlButtons; ================================================ FILE: gdbgui/src/js/CopyToClipboard.tsx ================================================ import * as React from "react"; import ToolTip from "./ToolTip"; import { store } from "statorgfc"; type Props = { content: string | null; }; class CopyToClipboard extends React.Component { node: HTMLSpanElement | null = null; render() { if (!this.props.content) { return null; } return ( (this.node = node)} onMouseOver={() => { ToolTip.show_tooltip_on_node("copy to clipboard", this.node); }} onMouseLeave={ToolTip.hide_tooltip} onClick={() => { try { let textarea = store.get("textarea_to_copy_to_clipboard"); textarea.value = this.props.content; textarea.select(); if (document.execCommand("copy") === true) { ToolTip.show_copied_tooltip_on_node(this.node); } else { ToolTip.show_tooltip_on_node("unable to copy", this.node); } } catch (err) { ToolTip.show_tooltip_on_node("unable to copy", this.node); } }} /> ); } } export default CopyToClipboard; ================================================ FILE: gdbgui/src/js/Expressions.tsx ================================================ import React from "react"; import { store } from "statorgfc"; import GdbVariable from "./GdbVariable"; import constants from "./constants"; class Expressions extends React.Component { objs_to_delete: any; objs_to_render: any; constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["expressions"]); } render() { // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. let sorted_expression_objs = _.sortBy( store.get("expressions"), (unsorted_obj: any) => unsorted_obj.expression ); // only render variables in scope that were not created for the Locals component this.objs_to_render = sorted_expression_objs.filter( (obj: any) => obj.in_scope === "true" && obj.expr_type === "expr" ); this.objs_to_delete = sorted_expression_objs.filter( (obj: any) => obj.in_scope === "invalid" ); // delete invalid objects this.objs_to_delete.map((obj: any) => GdbVariable.delete_gdb_variable(obj.name)); let content = this.objs_to_render.map((obj: any) => ( )); if (content.length === 0) { content.push( no expressions in this context ); } content.push(
); return (

{content}

); } componentDidUpdate() { for (let obj of this.objs_to_render) { GdbVariable.plot_var_and_children(obj); } } static keydown_on_input(e: any) { if (e.keyCode === constants.ENTER_BUTTON_NUM) { let expr = e.currentTarget.value, // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. trimmed_expr = _.trim(expr); if (trimmed_expr !== "") { GdbVariable.create_variable(trimmed_expr, "expr"); } } } } export default Expressions; ================================================ FILE: gdbgui/src/js/FileOps.tsx ================================================ import { store } from "statorgfc"; import GdbApi from "./GdbApi"; import constants from "./constants"; import Actions from "./Actions"; import React from "react"; // needed for jsx void React; let debug_print: any; // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'debug'. if (debug) { /* global debug */ debug_print = console.info; } else { debug_print = function() { // stubbed out }; } let FileFetcher = { _is_fetching: false, _queue: [], _fetch: function(fullname: any, start_line: any, end_line: any) { if (FileOps.is_missing_file(fullname)) { // file doesn't exist and we already know about it // don't keep trying to fetch disassembly console.warn(`tried to fetch a file known to be missing ${fullname}`); FileFetcher._is_fetching = false; FileFetcher._fetch_next(); return; } // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (!_.isString(fullname)) { console.warn(`trying to fetch filename that is not a string`, fullname); FileOps.add_missing_file(fullname); FileFetcher._is_fetching = false; FileFetcher._fetch_next(); } FileFetcher._is_fetching = true; const data = { start_line: start_line, end_line: end_line, path: fullname, highlight: store.get("highlight_source_code") }; $.ajax({ beforeSend: function(xhr) { xhr.setRequestHeader( "x-csrftoken", // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. initial_data.csrf_token ); /* global initial_data */ }, url: "/read_file", cache: false, type: "GET", data: data, success: function(response) { response.source_code; let source_code_obj = {}; let linenum = response.start_line; for (let line of response.source_code_array) { // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message source_code_obj[linenum] = line; linenum++; } FileOps.add_source_file_to_cache( fullname, source_code_obj, response.last_modified_unix_sec, response.num_lines_in_file ); }, error: function(response) { if (response.responseJSON && response.responseJSON.message) { Actions.add_console_entries( // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. _.escape(response.responseJSON.message), constants.console_entry_type.STD_ERR ); } else { Actions.add_console_entries( `${response.statusText} (${response.status} error)`, constants.console_entry_type.STD_ERR ); } FileOps.add_missing_file(fullname); }, complete: function() { FileFetcher._is_fetching = false; // @ts-expect-error ts-migrate(2339) FIXME: Property 'fullname' does not exist on type 'never'... Remove this comment to see the full error message FileFetcher._queue = FileFetcher._queue.filter(o => o.fullname !== fullname); FileFetcher._fetch_next(); } }); }, _fetch_next: function() { if (FileFetcher._is_fetching) { return; } if (FileFetcher._queue.length) { let obj = FileFetcher._queue.shift(); // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'. FileFetcher._fetch(obj.fullname, obj.start_line, obj.end_line); } }, fetch_complete() { FileFetcher._is_fetching = false; FileFetcher._fetch_next(); }, fetch: function(fullname: any, start_line: any, end_line: any) { if (!start_line) { start_line = 1; console.warn("expected start line"); } if (!end_line) { end_line = start_line; console.warn("expected end line"); } if (FileOps.lines_are_cached(fullname, start_line, end_line)) { debug_print( `not fetching ${fullname}:${start_line}:${end_line} because it's cached` ); return; } // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'. FileFetcher._queue.push({ fullname, start_line, end_line }); FileFetcher._fetch_next(); } }; const FileOps = { warning_shown_for_old_binary: false, unfetchable_disassembly_addresses: {}, disassembly_addr_being_fetched: null, init: function() { // @ts-expect-error ts-migrate(2339) FIXME: Property 'subscribeToKeys' does not exist on type ... Remove this comment to see the full error message store.subscribeToKeys( [ "inferior_program", "source_code_selection_state", "paused_on_frame", "current_assembly_address", "disassembly_for_missing_file", "highlight_source_code", "missing_files", "files_being_fetched", "gdb_version_array", "fullname_to_render", "line_of_source_to_flash", "cached_source_files", "max_lines_of_code_to_fetch" ], FileOps._store_change_callback ); }, user_select_file_to_view: function(fullname: any, line: any) { store.set( "source_code_selection_state", constants.source_code_selection_states.USER_SELECTION ); store.set("fullname_to_render", fullname); store.set("line_of_source_to_flash", line); store.set("make_current_line_visible", true); store.set("source_code_infinite_scrolling", false); }, _store_change_callback: function() { if (store.get("inferior_program") === constants.inferior_states.running) { return; } let source_code_selection_state = store.get("source_code_selection_state"), fullname = null, is_paused = false, paused_addr = null, paused_frame_fullname = null, paused_frame = store.get("paused_on_frame"); if (paused_frame) { paused_frame_fullname = paused_frame.fullname; } let require_cached_line_num; if ( source_code_selection_state === constants.source_code_selection_states.USER_SELECTION ) { fullname = store.get("fullname_to_render"); is_paused = false; paused_addr = null; require_cached_line_num = parseInt(store.get("line_of_source_to_flash")); } else if ( source_code_selection_state === constants.source_code_selection_states.PAUSED_FRAME ) { is_paused = store.get("inferior_program") === constants.inferior_states.paused; paused_addr = store.get("current_assembly_address"); fullname = paused_frame_fullname; require_cached_line_num = parseInt(store.get("line_of_source_to_flash")); } let source_code_infinite_scrolling = store.get("source_code_infinite_scrolling"), assembly_is_cached = FileOps.assembly_is_cached(fullname), file_is_missing = FileOps.is_missing_file(fullname), start_line, end_line; ({ start_line, end_line, require_cached_line_num } = FileOps.get_start_and_end_lines( fullname, require_cached_line_num, source_code_infinite_scrolling )); FileOps.update_source_code_state( fullname, start_line, require_cached_line_num, end_line, assembly_is_cached, file_is_missing, is_paused, paused_addr ); }, get_start_and_end_lines( fullname: any, require_cached_line_num: any, source_code_infinite_scrolling: any ) { let start_line, end_line; if (source_code_infinite_scrolling) { start_line = store.get("source_linenum_to_display_start"); end_line = store.get("source_linenum_to_display_end"); require_cached_line_num = start_line; } else { let source_file_obj = FileOps.get_source_file_obj_from_cache(fullname); if (!require_cached_line_num) { require_cached_line_num = 1; } start_line = Math.max( Math.floor(require_cached_line_num - store.get("max_lines_of_code_to_fetch") / 2), 1 ); end_line = Math.ceil(start_line + store.get("max_lines_of_code_to_fetch")); if (source_file_obj) { // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. end_line = Math.ceil(Math.min(end_line, FileOps.get_num_lines_in_file(fullname))); // don't go past the end of the line } if (start_line > end_line) { start_line = Math.floor( Math.max(1, end_line - store.get("max_lines_of_code_to_fetch")) ); } require_cached_line_num = Math.min(require_cached_line_num, end_line); } return { start_line, end_line, require_cached_line_num }; }, update_source_code_state( fullname: any, start_line: any, require_cached_line_num: any, end_line: any, assembly_is_cached: any, file_is_missing: any, is_paused: any, paused_addr: any ) { const states = constants.source_code_states, // @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 2. line_is_cached = FileOps.line_is_cached(fullname, require_cached_line_num); if (fullname && line_is_cached) { // we have file cached. We may have assembly cached too. store.set( "source_code_state", assembly_is_cached ? states.ASSM_AND_SOURCE_CACHED : states.SOURCE_CACHED ); store.set("source_linenum_to_display_start", start_line); // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. end_line = Math.min(end_line, FileOps.get_num_lines_in_file(fullname)); store.set("source_linenum_to_display_end", end_line); } else if (fullname && !file_is_missing) { // we don't have file cached, and it is not known to be missing on the file system, so try to get it store.set("source_code_state", states.FETCHING_SOURCE); FileFetcher.fetch(fullname, start_line, end_line); } else if ( is_paused && paused_addr && store .get("disassembly_for_missing_file") .some((obj: any) => parseInt(obj.address, 16) === parseInt(paused_addr, 16)) ) { store.set("source_code_state", states.ASSM_CACHED); } else if (is_paused && paused_addr) { if (paused_addr in FileOps.unfetchable_disassembly_addresses) { store.set("source_code_state", states.ASSM_UNAVAILABLE); } else { // get disassembly store.set("source_code_state", states.FETCHING_ASSM); FileOps.fetch_disassembly_for_missing_file(paused_addr); } } else if (file_is_missing) { store.set("source_code_state", states.FILE_MISSING); } else { store.set("source_code_state", states.NONE_AVAILABLE); } }, get_num_lines_in_file: function(fullname: any, source_file_obj: any) { if (!source_file_obj) { source_file_obj = FileOps.get_source_file_obj_from_cache(fullname); } if (!source_file_obj) { console.error("Developer error: expected to find file object for " + fullname); return; } if (!source_file_obj.num_lines_in_file) { console.error('Developer error: expected key "num_lines_in_file"'); return Infinity; } return source_file_obj.num_lines_in_file; }, lines_are_cached: function(fullname: any, start_line: any, end_line: any) { let source_file_obj = FileOps.get_source_file_obj_from_cache(fullname), linenum = start_line; if (!source_file_obj) { return false; } const num_lines_in_file = FileOps.get_num_lines_in_file(fullname, source_file_obj); if (start_line > num_lines_in_file) { return false; } let safe_end_line = Math.min(end_line, num_lines_in_file); while (linenum <= safe_end_line) { if (!FileOps.line_is_cached(fullname, linenum, source_file_obj)) { return false; } linenum++; } return true; }, line_is_cached: function(fullname: any, linenum: any, source_file_obj: any) { if (!source_file_obj) { source_file_obj = FileOps.get_source_file_obj_from_cache(fullname); } return ( source_file_obj && source_file_obj.source_code_obj && source_file_obj.source_code_obj[linenum] !== undefined ); }, get_line_from_file: function(fullname: any, linenum: any) { let source_file_obj = FileOps.get_source_file_obj_from_cache(fullname); if (!source_file_obj) { return null; } return source_file_obj.source_code_obj[linenum]; }, assembly_is_cached: function(fullname: any) { let source_file_obj = FileOps.get_source_file_obj_from_cache(fullname); return ( source_file_obj && source_file_obj.assembly && Object.keys(source_file_obj.assembly).length ); }, get_source_file_obj_from_cache: function(fullname: any) { let cached_files = store.get("cached_source_files"); for (let sf of cached_files) { if (sf.fullname === fullname) { return sf; } } return null; }, add_source_file_to_cache: function( fullname: any, source_code_obj: any, last_modified_unix_sec: any, num_lines_in_file: any ) { let cached_file_obj = FileOps.get_source_file_obj_from_cache(fullname); if (cached_file_obj === null) { // nothing cached in the front end, add a new entry let new_source_file = { fullname: fullname, source_code_obj: source_code_obj, assembly: {}, last_modified_unix_sec: last_modified_unix_sec, num_lines_in_file: num_lines_in_file, exists: true }, cached_source_files = store.get("cached_source_files"); cached_source_files.push(new_source_file); store.set("cached_source_files", cached_source_files); FileOps.warning_shown_for_old_binary = false; FileOps.show_modal_if_file_modified_after_binary( fullname, new_source_file.last_modified_unix_sec ); } else { // mutate existing source code object by adding keys (lines) of the new source code object Object.assign(cached_file_obj.source_code_obj, source_code_obj); store.set("cached_source_files", store.get("cached_source_files")); } }, /** * Show modal warning if user is trying to show a file that was modified after the binary was compiled */ show_modal_if_file_modified_after_binary( fullname: any, src_last_modified_unix_sec: any ) { if (store.get("inferior_binary_path")) { if ( src_last_modified_unix_sec > store.get("inferior_binary_path_last_modified_unix_sec") && FileOps.warning_shown_for_old_binary === false ) { Actions.show_modal( "Warning",
This source file was modified after the binary was compiled. Recompile the binary, then try again. Otherwise the source code may not match the binary.

{/* @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'moment'. */}

{`Source file: ${fullname}, modified ${moment( src_last_modified_unix_sec * 1000 ).format(constants.DATE_FORMAT)}`}

{/* @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'moment'. */} {`Binary: ${store.get("inferior_binary_path")}, modified ${moment( store.get("inferior_binary_path_last_modified_unix_sec") * 1000 ).format(constants.DATE_FORMAT)}`} )

); FileOps.warning_shown_for_old_binary = true; } } }, get_cached_assembly_for_file: function(fullname: any) { for (let file of store.get("cached_source_files")) { if (file.fullname === fullname) { return file.assembly; } } return []; }, refresh_cached_source_files: function() { FileOps.clear_cached_source_files(); }, clear_cached_source_files: function() { store.set("cached_source_files", []); }, fetch_more_source_at_beginning() { let fullname = store.get("fullname_to_render"); let center_on_line = store.get("source_linenum_to_display_start") - 1; // store.set('source_code_infinite_scrolling', true) store.set( "source_linenum_to_display_start", Math.max( store.get("source_linenum_to_display_start") - Math.floor(store.get("max_lines_of_code_to_fetch") / 2), 1 ) ); store.set( "source_linenum_to_display_end", Math.ceil( store.get("source_linenum_to_display_start") + store.get("max_lines_of_code_to_fetch") ) ); Actions.view_file(fullname, center_on_line); FileFetcher.fetch( fullname, store.get("source_linenum_to_display_start"), store.get("source_linenum_to_display_end") ); }, fetch_more_source_at_end() { store.set("source_code_infinite_scrolling", true); let fullname = store.get("fullname_to_render"); let end_line = store.get("source_linenum_to_display_end") + Math.ceil(store.get("max_lines_of_code_to_fetch") / 2); let source_file_obj = FileOps.get_source_file_obj_from_cache(fullname); if (source_file_obj) { // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. end_line = Math.min(end_line, FileOps.get_num_lines_in_file(fullname)); // don't go past the end of the line } let start_line = end_line - store.get("max_lines_of_code_to_fetch"); start_line = Math.max(1, start_line); store.set("source_linenum_to_display_end", end_line); store.set("source_linenum_to_display_start", start_line); FileFetcher.fetch( fullname, store.get("source_linenum_to_display_start"), store.get("source_linenum_to_display_end") ); }, is_missing_file: function(fullname: any) { return store.get("missing_files").indexOf(fullname) !== -1; }, add_missing_file: function(fullname: any) { let missing_files = store.get("missing_files"); missing_files.push(fullname); store.set("missing_files", missing_files); }, /** * gdb changed its api for the data-disassemble command * see https://www.sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Data-Manipulation.html * TODO not sure which version this change occured in. I know in 7.7 it needs the '3' option, * and in 7.11 it needs the '4' option. I should test the various version at some point. */ get_dissasembly_format_num: function(gdb_version_array: any) { if (gdb_version_array.length === 0) { // assuming new version, but we shouldn't ever not know the version... return 4; } else if ( gdb_version_array[0] < 7 || (parseInt(gdb_version_array[0]) === 7 && gdb_version_array[1] <= 7) ) { // this option has been deprecated in newer versions, but is required in older ones return 3; } else { return 4; } }, get_fetch_disassembly_command: function( fullname: any, start_line: any, mi_response_format: any ) { // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (_.isString(fullname)) { return ( constants.INLINE_DISASSEMBLY_STR + `-data-disassemble -f ${fullname} -l ${start_line} -n 1000 -- ${mi_response_format}` ); } else { console.warn("not fetching undefined file"); } }, /** * Fetch disassembly for current file/line. */ fetch_assembly_cur_line: function(mi_response_format = null) { // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (mi_response_format === null || !_.isNumber(mi_response_format)) { // try to determine response format based on our guess of the gdb version being used // @ts-expect-error ts-migrate(2322) FIXME: Type '4' is not assignable to type 'null'. mi_response_format = FileOps.get_dissasembly_format_num( store.get("gdb_version_array") ); } let fullname = store.get("fullname_to_render"), line = parseInt(store.get("line_of_source_to_flash")); if (!line) { line = 1; } FileOps.fetch_disassembly(fullname, line, mi_response_format); }, fetch_disassembly: function(fullname: any, start_line: any, mi_response_format: any) { let cmd = FileOps.get_fetch_disassembly_command( fullname, start_line, mi_response_format ); if (cmd) { GdbApi.run_gdb_command(cmd); } }, fetch_disassembly_for_missing_file: function(hex_addr: any) { // https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Data-Manipulation.html if (window.isNaN(hex_addr)) { return; } Actions.add_console_entries( "Fetching assembly since file is missing", constants.console_entry_type.GDBGUI_OUTPUT ); let start = parseInt(hex_addr, 16), end = start + 100; FileOps.disassembly_addr_being_fetched = hex_addr; GdbApi.run_gdb_command( constants.DISASSEMBLY_FOR_MISSING_FILE_STR + `-data-disassemble -s 0x${start.toString(16)} -e 0x${end.toString(16)} -- 0` ); }, fetch_disassembly_for_missing_file_failed: function() { let addr_being_fetched = FileOps.disassembly_addr_being_fetched; // @ts-expect-error ts-migrate(2538) FIXME: Type 'null' cannot be used as an index type. FileOps.unfetchable_disassembly_addresses[addr_being_fetched] = true; FileOps.disassembly_addr_being_fetched = null; Actions.add_console_entries( "Failed to retrieve assembly for missing file", constants.console_entry_type.GDBGUI_OUTPUT ); }, /** * Save assembly and render source code if desired * @param mi_assembly: array of assembly instructions * @param mi_token (int): corresponds to either null (when src file is known and exists), * constants.DISASSEMBLY_FOR_MISSING_FILE_INT when source file is undefined or does not exist on filesystem */ save_new_assembly: function(mi_assembly: any, mi_token: any) { FileOps.disassembly_addr_being_fetched = null; if (!Array.isArray(mi_assembly) || mi_assembly.length === 0) { console.error("Attempted to save unexpected assembly", mi_assembly); } let fullname = mi_assembly[0].fullname; // @ts-expect-error ts-migrate(2551) FIXME: Property 'DISASSEMBLY_FOR_MISSING_FILE_INT' does n... Remove this comment to see the full error message if (mi_token === constants.DISASSEMBLY_FOR_MISSING_FILE_INT) { store.set("disassembly_for_missing_file", mi_assembly); return; } // convert assembly to an object, with key corresponding to line numbers // and values corresponding to asm instructions for that line let assembly_to_save = {}; for (let obj of mi_assembly) { // @ts-expect-error ts-migrate(7053) FIXME: No index signature with a parameter of type 'numbe... Remove this comment to see the full error message assembly_to_save[parseInt(obj.line)] = obj.line_asm_insn; } let cached_source_files = store.get("cached_source_files"); for (let cached_file of cached_source_files) { if (cached_file.fullname === fullname) { cached_file.assembly = Object.assign(cached_file.assembly, assembly_to_save); // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message let max_assm_line = Math.max(Object.keys(cached_file.assembly)), // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message max_source_line = Math.max(Object.keys(cached_file.source_code_obj)); if (max_assm_line > max_source_line) { cached_file.source_code_obj[max_assm_line] = ""; for (let i = 0; i < max_assm_line; i++) { if (!cached_file.source_code_obj[i]) { cached_file.source_code_obj[i] = ""; } } } store.set("cached_source_files", cached_source_files); break; } } } }; export default FileOps; ================================================ FILE: gdbgui/src/js/FileSystem.tsx ================================================ import React from "react"; class FileSystem extends React.Component { nodecount: any; get_node_jsx(node: any, depth = 0) { if (!node) { return null; } this.nodecount++; let get_child_jsx_for_node = (node: any) => { if (!(node.children && node.toggled)) { return null; } return (
    {node.children.map((child: any) => this.get_node_jsx(child, depth + 1))}
); }; let indent = "\u00A0\u00A0\u00A0".repeat(depth), glyph = null; let is_file = !node.children, is_dir = !is_file; if (is_dir) { glyph = node.toggled ? "glyphicon-chevron-down" : "glyphicon-chevron-right"; } let onClickName = null; if (is_file) { onClickName = () => { // @ts-expect-error ts-migrate(2339) FIXME: Property 'onClickName' does not exist on type 'Rea... Remove this comment to see the full error message this.props.onClickName(node); }; } return (
  • {indent} { // @ts-expect-error ts-migrate(2339) FIXME: Property 'onToggle' does not exist on type 'Readon... Remove this comment to see the full error message this.props.onToggle(node); }} /> {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'null' is not assignable to type '((event: Mo... Remove this comment to see the full error message */} {node.name}
  • {get_child_jsx_for_node(node)}
    ); } render() { this.nodecount = -1; return (
    {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'rootnode' does not exist on type 'Readon... Remove this comment to see the full error message */}
      {this.get_node_jsx(this.props.rootnode)}
    ); } } export default FileSystem; ================================================ FILE: gdbgui/src/js/FoldersView.tsx ================================================ import React from "react"; import { store } from "statorgfc"; import FileOps from "./FileOps"; import constants from "./constants"; import SourceFileAutocomplete from "./SourceFileAutocomplete"; import FileSystem from "./FileSystem"; import Actions from "./Actions"; const default_rootnode = { name: 'Load inferior program, then click "Fetch source files" to populate this window', children: [], toggled: false }; function get_child_node_with_name(name: any, curnode: any) { if (!curnode.children) { return null; } for (let node of curnode.children) { if (node.name === name) { return node; } } return null; } type State = any; class FoldersView extends React.Component<{}, State> { max_filesystem_entries: any; project_home: any; constructor(props: {}) { super(props); this.state = { rootnode: default_rootnode }; // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState( this, ["source_code_state", "source_file_paths"], this.update_filesystem_data.bind(this) ); this.max_filesystem_entries = 300; // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. this.project_home = initial_data.project_home; /* global initial_data */ this.onToggle = this.onToggle.bind(this); this.onClickName = this.onClickName.bind(this); this.reveal_path = this.reveal_path.bind(this); this.expand_all = this.expand_all.bind(this); this.collapse_all = this.collapse_all.bind(this); } render() { let source_code_state = this.state.source_code_state, file_is_rendered = source_code_state === constants.source_code_states.SOURCE_CACHED || source_code_state === constants.source_code_states.ASSM_AND_SOURCE_CACHED, can_reveal = file_is_rendered && this.state.source_file_paths.length, hiding_entries = this.state.source_file_paths.length > this.max_filesystem_entries; return (
    {store.get("source_file_paths").length ? (

    {store.get("source_file_paths").length} known files used to compile the inferior program

    ) : ( "" )} {hiding_entries ? (

    Maximum entries in tree below is {this.max_filesystem_entries} (hiding{" "} {store.get("source_file_paths").length - this.max_filesystem_entries}). All files can still be searched for in the input above.

    ) : ( "" )}
    ); } onClickName(node: any) { let curnode = node, path = []; while (curnode) { if (curnode.name === "root") { path.unshift(""); break; } // prepend this file/directory to the path path.unshift(curnode.name); // try to prepend the parent curnode = curnode.parent; } if (path.length) { FileOps.user_select_file_to_view(path.join("/"), 1); } } reveal_path(path: any) { if (!path) { return; } if (this.state.cursor) { this.state.cursor.active = false; } if (this.project_home) { path = path.replace(this.project_home, ""); } let names = path.split("/").filter((n: any) => n !== ""), curnode = this.state.rootnode; curnode.toggled = true; // expand the root for (let name of names) { curnode = get_child_node_with_name(name, curnode); if (curnode) { curnode.toggled = true; } else { break; } } if (curnode) { curnode.active = true; } this.setState({ rootnode: this.state.rootnode, cursor: curnode }); } update_filesystem_data(keys: any) { if (keys.indexOf("source_file_paths") === -1) { return; } let source_paths = this.state.source_file_paths; if (!Array.isArray(source_paths) || !source_paths.length) { this.setState({ rootnode: default_rootnode }); return; } let rootnode = { name: this.project_home || "root", toggled: true, children: [] }; let relative_source_paths = source_paths; if (this.project_home) { let project_home = this.project_home; relative_source_paths = source_paths .filter(p => p.startsWith(project_home)) .map(p => { p = p.replace(project_home, ""); return p; }); } for (let path of relative_source_paths) { let new_node, names = path.split("/").filter((n: any) => n !== ""), curnode = rootnode, // @ts-expect-error ts-migrate(2448) FIXME: Block-scoped variable 'depth' used before its decl... Remove this comment to see the full error message toggled = depth === 0; let depth = 0; for (let name of names) { let child = get_child_node_with_name(name, curnode); if (child) { // found an existing child node, use it curnode = child; } else { // add child and set it to cur node // @ts-expect-error ts-migrate(2322) FIXME: Object literal may only specify known properties, ... Remove this comment to see the full error message new_node = { name: name, toggled: toggled, parent: curnode }; if (curnode.children) { // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: any; toggled: boolean; p... Remove this comment to see the full error message curnode.children.push(new_node); } else { // @ts-expect-error ts-migrate(2322) FIXME: Type '{ name: any; toggled: boolean; parent: { nam... Remove this comment to see the full error message curnode.children = [new_node]; } curnode = new_node; } depth++; } } this.setState({ rootnode: rootnode }); } onToggle(node: any) { node.toggled = !node.toggled; this.setState({ rootnode: this.state.rootnode }); } expand_all() { let callback = (node: any) => { node.toggled = true; }; for (let top_level_child of this.state.rootnode.children) { this._dfs(top_level_child, callback); } this.setState({ rootnode: this.state.rootnode }); } collapse_all() { let callback = (node: any) => { node.toggled = false; }; for (let top_level_child of this.state.rootnode.children) { this._dfs(top_level_child, callback); } this.setState({ rootnode: this.state.rootnode }); } _dfs(node: any, callback: any) { callback(node); if (node.children) { for (let child of node.children) { this._dfs(child, callback); } } } } export default FoldersView; ================================================ FILE: gdbgui/src/js/GdbApi.tsx ================================================ /** * An object to manage the websocket connection to the python server that manages gdb, * to send various commands to gdb, to and to dispatch gdb responses to gdbgui. */ import { store } from "statorgfc"; import Registers from "./Registers"; import Memory from "./Memory"; import Actions from "./Actions"; import GdbVariable from "./GdbVariable"; import constants from "./constants"; import process_gdb_response from "./process_gdb_response"; import React from "react"; import io from "socket.io-client"; void React; // needed when using JSX, but not marked as used /* global debug */ // print to console if debug is true let log: { (arg0: string): void; (...data: any[]): void; (message?: any, ...optionalParams: any[]): void; (): void; }; // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'debug'. if (debug) { log = console.info; } else { log = function() { // stubbed out }; } /** * This object contains methods to interact with * gdb, but does not directly render anything in the DOM. */ // @ts-expect-error ts-migrate(2339) FIXME: Property 'initial_data' does not exist on type 'Wi... Remove this comment to see the full error message const initial_data = window.initial_data; let socket: SocketIOClient.Socket; const GdbApi = { getSocket: function() { return socket; }, init: function() { const TIMEOUT_MIN = 5; socket = io.connect(`/gdb_listener`, { timeout: TIMEOUT_MIN * 60 * 1000, query: { csrf_token: initial_data.csrf_token, gdbpid: initial_data.gdbpid, gdb_command: initial_data.gdb_command } }); socket.on("connect", function() { log("connected"); const queuedGdbCommands = store.get("queuedGdbCommands"); if (queuedGdbCommands) { GdbApi.run_gdb_command(queuedGdbCommands); store.set("queuedGdbCommands", []); } }); socket.on("gdb_response", function(response_array: any) { // @ts-expect-error ts-migrate(2769) FIXME: Argument of type 'null' is not assignable to param... Remove this comment to see the full error message clearTimeout(GdbApi._waiting_for_response_timeout); store.set("waiting_for_response", false); process_gdb_response(response_array); }); socket.on("fatal_server_error", function(data: { message: null | string }) { Actions.add_console_entries( `Message from server: ${data.message}`, constants.console_entry_type.STD_ERR ); socket.close(); }); socket.on("error_running_gdb_command", function(data: { message: any }) { Actions.add_console_entries( `Error occurred on server when running gdb command: ${data.message}`, constants.console_entry_type.STD_ERR ); socket.close(); }); socket.on("server_error", function(data: { message: any }) { Actions.add_console_entries( `Server message: ${data.message}`, constants.console_entry_type.STD_ERR ); }); socket.on("debug_session_connection_event", function(gdb_pid_obj: { pid: number; message: string | void; ok: boolean; started_new_gdb_process: boolean; }) { const gdb_pid = gdb_pid_obj.pid; const message = gdb_pid_obj.message; const error = !gdb_pid_obj.ok; const started_new_gdb_process = gdb_pid_obj.started_new_gdb_process; if (message) { Actions.add_console_entries( message, error ? constants.console_entry_type.STD_ERR : constants.console_entry_type.GDBGUI_OUTPUT ); } if (error) { socket.close(); return; } store.set("gdb_pid", gdb_pid); if (started_new_gdb_process) { GdbApi.run_initial_commands(); } else { Actions.refresh_state_for_gdb_pause(); } }); socket.on("disconnect", function() { // we no longer need to warn the user before they exit the page since the gdb process // on the server is already gone window.onbeforeunload = () => null; Actions.show_modal( "", <>

    The connection to the gdb session has been closed. This tab will no longer function as expected.

    To start a new session or connect to a different session, go to the{" "} dashboard.

    ); Actions.add_console_entries( `The connection to the gdb session has been closed. To start a new session, go to ${window.location.origin}/dashboard`, constants.console_entry_type.STD_ERR ); // if (debug) { // window.location.reload(true); // } }); }, _waiting_for_response_timeout: null, click_run_button: function() { Actions.inferior_program_starting(); GdbApi.run_gdb_command("-exec-run"); }, run_initial_commands: function() { const cmds = ["-list-features", "-list-target-features"]; for (const src in initial_data.remap_sources) { const dst = initial_data.remap_sources[src]; cmds.push(`set substitute-path "${src}" "${dst}"`); } GdbApi.run_gdb_command(cmds); }, inferior_is_paused: function() { return ( [constants.inferior_states.unknown, constants.inferior_states.paused].indexOf( store.get("inferior_program") ) !== -1 ); }, click_continue_button: function(reverse = false) { Actions.inferior_program_resuming(); GdbApi.run_gdb_command( "-exec-continue" + (store.get("debug_in_reverse") || reverse ? " --reverse" : "") ); }, click_next_button: function(reverse = false) { Actions.inferior_program_resuming(); GdbApi.run_gdb_command( "-exec-next" + (store.get("debug_in_reverse") || reverse ? " --reverse" : "") ); }, click_step_button: function(reverse = false) { Actions.inferior_program_resuming(); GdbApi.run_gdb_command( "-exec-step" + (store.get("debug_in_reverse") || reverse ? " --reverse" : "") ); }, click_return_button: function() { // From gdb mi docs (https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Program-Execution.html#GDB_002fMI-Program-Execution): // `-exec-return` Makes current function return immediately. Doesn't execute the inferior. // That means we do NOT dispatch the event `event_inferior_program_resuming`, because it's not, in fact, running. // The return also doesn't even indicate that it's paused, so we need to manually trigger the event here. GdbApi.run_gdb_command("-exec-return"); Actions.inferior_program_paused(); }, click_next_instruction_button: function(reverse = false) { Actions.inferior_program_resuming(); GdbApi.run_gdb_command( "-exec-next-instruction" + (store.get("debug_in_reverse") || reverse ? " --reverse" : "") ); }, click_step_instruction_button: function(reverse = false) { Actions.inferior_program_resuming(); GdbApi.run_gdb_command( "-exec-step-instruction" + (store.get("debug_in_reverse") || reverse ? " --reverse" : "") ); }, click_send_interrupt_button: function() { Actions.inferior_program_resuming(); GdbApi.run_gdb_command("-exec-interrupt"); }, send_autocomplete_command: function(command: string) { Actions.inferior_program_resuming(); GdbApi.run_gdb_command("complete " + command); }, click_gdb_cmd_button: function(e: { currentTarget: { dataset: { [x: string]: any; cmd: undefined; cmd0: undefined } }; }) { if (e.currentTarget.dataset.cmd !== undefined) { // run single command // i.e. GdbApi.run_gdb_command(e.currentTarget.dataset.cmd); } else if (e.currentTarget.dataset.cmd0 !== undefined) { // run multiple commands // i.e. let cmds = []; let i = 0; let cmd = e.currentTarget.dataset[`cmd${i}`]; // extract all commands into an array, then run them // (max of 100 commands) while (cmd !== undefined && i < 100) { cmds.push(cmd); i++; cmd = e.currentTarget.dataset[`cmd${i}`]; } GdbApi.run_gdb_command(cmds); } else { console.error( "expected cmd or cmd0 [cmd1, cmd2, ...] data attribute(s) on element" ); } }, select_frame: function(framenum: any) { // TODO this command is deprecated (https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Stack-Manipulation.html) // This command in deprecated in favor of passing the ‘--frame’ option to every command. GdbApi.run_command_and_refresh_state(`-stack-select-frame ${framenum}`); }, select_thread_id: function(thread_id: any) { // TODO this command is deprecated (http://www.sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI-Thread-Commands.html) // This command is deprecated in favor of explicitly using the ‘--thread’ option to each command. GdbApi.run_command_and_refresh_state(`-thread-select ${thread_id}`); }, /** * Before sending a command, set a timeout to notify the user that something might be wrong * if a response from gdb is not received */ waiting_for_response: function() { store.set("waiting_for_response", true); const WAIT_TIME_SEC = 10; // @ts-expect-error ts-migrate(2769) FIXME: Argument of type 'null' is not assignable to param... Remove this comment to see the full error message clearTimeout(GdbApi._waiting_for_response_timeout); // @ts-expect-error ts-migrate(2322) FIXME: Type 'Timeout' is not assignable to type 'null'. GdbApi._waiting_for_response_timeout = setTimeout(() => { Actions.clear_program_state(); store.set("waiting_for_response", false); if (GdbApi.getSocket().disconnected) { return; } Actions.add_console_entries( `No gdb response received after ${WAIT_TIME_SEC} seconds.`, constants.console_entry_type.GDBGUI_OUTPUT ); Actions.add_console_entries( "Possible reasons include:", constants.console_entry_type.GDBGUI_OUTPUT ); Actions.add_console_entries( "1) gdbgui, gdb, or the debugged process is not running.", constants.console_entry_type.GDBGUI_OUTPUT ); Actions.add_console_entries( "2) gdb or the inferior process is busy running and needs to be " + "interrupted (press the pause button up top).", constants.console_entry_type.GDBGUI_OUTPUT ); Actions.add_console_entries( "3) Something is just taking a long time to finish and respond back to " + "this browser window, in which case you can just keep waiting.", constants.console_entry_type.GDBGUI_OUTPUT ); }, WAIT_TIME_SEC * 1000); }, /** * runs a gdb cmd (or commands) directly in gdb on the backend * validates command before sending, and updates the gdb console and status bar * @param cmd: a string or array of strings, that are directly evaluated by gdb * @return nothing */ run_gdb_command: function(cmd: any) { // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (_.trim(cmd) === "") { return; } let cmds = cmd; // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (_.isString(cmds)) { cmds = [cmds]; } if (socket.connected) { socket.emit("run_gdb_command", { cmd: cmds }); GdbApi.waiting_for_response(); // add the send command to the console to show commands that are // automatically run by gdb if (store.get("show_all_sent_commands_in_console")) { Actions.add_console_entries(cmds, constants.console_entry_type.SENT_COMMAND); } } else { log("queuing commands"); const queuedGdbCommands = store.get("queuedGdbCommands").concat(cmds); store.set("queuedGdbCommands", queuedGdbCommands); } }, run_command_and_refresh_state: function(user_cmd: string | any[]) { let cmds: any[] = []; if (Array.isArray(user_cmd)) { cmds = cmds.concat(user_cmd); // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. } else if (_.isString(user_cmd) && user_cmd.length > 0) { cmds.push(user_cmd); } cmds = cmds.concat(GdbApi._get_refresh_state_for_pause_cmds()); GdbApi.run_gdb_command(cmds); }, backtrace: function() { let cmds = ["backtrace"]; cmds = cmds.concat(GdbApi._get_refresh_state_for_pause_cmds()); store.set("inferior_program", constants.inferior_states.paused); GdbApi.run_gdb_command(cmds); }, /** * Get array of commands to send to gdb that refreshes everything in the * frontend */ _get_refresh_state_for_pause_cmds: function() { let cmds = [ // get info on current thread // TODO run -thread-list-ids to store list of thread id's and know // which thread is the current thread constants.IGNORE_ERRORS_TOKEN_STR + "-thread-info", // print the name, type and value for simple data types, // and the name and type for arrays, structures and unions. constants.IGNORE_ERRORS_TOKEN_STR + "-stack-list-variables --simple-values" ]; // update all user-defined variables in gdb cmds.push(constants.IGNORE_ERRORS_TOKEN_STR + "-var-update --all-values *"); // update registers cmds = cmds.concat(Registers.get_update_cmds()); // re-fetch memory over desired range as specified by DOM inputs cmds = cmds.concat(Memory.get_gdb_commands_from_state()); // refresh breakpoints cmds.push(GdbApi.get_break_list_cmd()); // List the frames currently on the stack. // avoid the "no registers" error cmds.push(constants.IGNORE_ERRORS_TOKEN_STR + "-stack-list-frames"); return cmds; }, refresh_breakpoints: function() { GdbApi.run_gdb_command([GdbApi.get_break_list_cmd()]); }, get_inferior_binary_last_modified_unix_sec(path: any) { $.ajax({ beforeSend: function(xhr: { setRequestHeader: (arg0: string, arg1: any) => void }) { xhr.setRequestHeader("x-csrftoken", initial_data.csrf_token); }, url: "/get_last_modified_unix_sec", cache: false, method: "GET", data: { path: path }, success: GdbApi._recieve_last_modified_unix_sec, error: GdbApi._error_getting_last_modified_unix_sec }); }, get_insert_break_cmd: function(fullname: any, line: any) { return [`-break-insert "${fullname}:${line}"`]; }, get_delete_break_cmd: function(bkpt_num: any) { return `-break-delete ${bkpt_num}`; }, get_break_list_cmd: function() { return "-break-list"; }, get_load_binary_and_arguments_cmds(binary: any, args: any) { // tell gdb which arguments to use when calling the binary, before loading the binary let cmds = [ `-exec-arguments ${args}`, // Set the inferior program arguments, to be used in the next `-exec-run` `-file-exec-and-symbols ${binary}` // Specify the executable file to be debugged. This file is the one from which the symbol table is also read. ]; // add breakpoint if we don't already have one if (store.get("auto_add_breakpoint_to_main")) { cmds.push("-break-insert main"); } cmds.push(GdbApi.get_break_list_cmd()); return cmds; }, set_assembly_flavor(flavor: string) { GdbApi.run_gdb_command(`set disassembly-flavor ${flavor}`); }, _recieve_last_modified_unix_sec(data: { path: any; last_modified_unix_sec: any }) { if (data.path === store.get("inferior_binary_path")) { store.set( "inferior_binary_path_last_modified_unix_sec", data.last_modified_unix_sec ); } }, _error_getting_last_modified_unix_sec(data: any) { void data; store.set("inferior_binary_path", null); } }; // @ts-expect-error ts-migrate(2339) FIXME: Property 'socket' does not exist on type '{ getSoc... Remove this comment to see the full error message GdbApi.socket = socket; export default GdbApi; ================================================ FILE: gdbgui/src/js/GdbMiOutput.tsx ================================================ /** * A component to display, in gory detail, what is * returned from gdb's machine interface. This displays the * data source that is fed to all components and UI elements * in gdb gui, and is useful when debugging gdbgui, or * a command that failed but didn't have a useful failure * message in gdbgui. */ import React from "react"; import { store } from "statorgfc"; type State = any; class GdbMiOutput extends React.Component<{}, State> { static MAX_OUTPUT_ENTRIES = 500; _debounced_scroll_to_bottom: any; el: any; constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["gdb_mi_output"]); // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. this._debounced_scroll_to_bottom = _.debounce( this._scroll_to_bottom.bind(this), 300, { leading: true } ); } render() { return (
    {this.state.gdb_mi_output}
    ); } componentDidMount() { this.el = document.getElementById("gdb_mi_output"); } componentDidUpdate() { this._debounced_scroll_to_bottom(); } _scroll_to_bottom() { this.el.scrollTop = this.el.scrollHeight; } static add_mi_output(mi_obj: any) { let new_str = JSON.stringify(mi_obj, null, 4) // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. .replace(/[^(\\)]\\n/g) .replace("<", "<") .replace(">", ">"), gdb_mi_output = store.get("gdb_mi_output"); while (gdb_mi_output.length > GdbMiOutput.MAX_OUTPUT_ENTRIES) { gdb_mi_output.shift(); } gdb_mi_output.push(new_str); store.set("gdb_mi_output", gdb_mi_output); } } export default GdbMiOutput; ================================================ FILE: gdbgui/src/js/GdbVariable.tsx ================================================ /** * A component to render gdb user variables, and * some library functions to interact with gdb. The library * functions create gdb variable objects locally, update them, * remove them, etc. */ import React from "react"; import Memory from "./Memory"; import constants from "./constants"; import { store } from "statorgfc"; import GdbApi from "./GdbApi"; import CopyToClipboard from "./CopyToClipboard"; import Actions from "./Actions"; /** * Simple object to manage fetching of child variables. Maintains a queue of parent expressions * to fetch children for, and fetches them in serial. */ let ChildVarFetcher = { expr_gdb_parent_var_currently_fetching_children: null, // parent gdb variable name (i.e. var7) _is_fetching: false, _queue: [], // objects with keys 'expr_gdb_parent_var_currently_fetching_children' and 'expr_type' _fetch_next_in_queue: function() { if (ChildVarFetcher._is_fetching) { return; } if (ChildVarFetcher._queue.length) { let obj = ChildVarFetcher._queue.shift(); ChildVarFetcher.expr_gdb_parent_var_currently_fetching_children = // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'. obj.expr_of_parent; ChildVarFetcher._is_fetching = true; // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'. GdbApi.run_gdb_command(`-var-list-children --all-values "${obj.expr_of_parent}"`); } else { ChildVarFetcher.expr_gdb_parent_var_currently_fetching_children = null; } }, fetch_children(expr_of_parent: any, expr_type: any) { // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'. ChildVarFetcher._queue.push({ expr_of_parent: expr_of_parent, expr_type: expr_type }); ChildVarFetcher._fetch_next_in_queue(); }, fetch_complete() { ChildVarFetcher._is_fetching = false; ChildVarFetcher.expr_gdb_parent_var_currently_fetching_children = null; ChildVarFetcher._fetch_next_in_queue(); } }; /** * Simple object to manage fetching of variables. Maintains a queue of expressions * to fetch, and fetches them in serial. */ let VarCreator = { _queue: [], // list of objs with keys expr_being_created, expr_type _is_fetching: false, expr_being_created: null, expr_type: null, _fetch_next_in_queue: function() { if (VarCreator._is_fetching) { return; } if (VarCreator._queue.length) { let obj = VarCreator._queue.shift(), // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'. expression = obj.expression, // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'. expr_type = obj.expr_type; VarCreator._is_fetching = true; VarCreator.expr_being_created = expression; VarCreator.expr_type = expr_type; // surround in quotes if we found a quote if (expression.length > 0 && expression.indexOf('"') !== 0) { expression = '"' + expression + '"'; } let cmds = []; if (store.get("pretty_print")) { cmds.push("-enable-pretty-printing"); } // - means auto assign variable name in gdb // * means evaluate it at the current frame let var_create_cmd = constants.CREATE_VAR_STR + `-var-create - * ${expression}`; cmds.push(var_create_cmd); GdbApi.run_gdb_command(cmds); } else { VarCreator._clear_state(); } }, /** * Create a new variable in gdb. gdb automatically chooses and assigns * a unique variable name. */ create_variable: function(expression: any, expr_type: any) { // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'. VarCreator._queue.push({ expression: expression, expr_type: expr_type }); VarCreator._fetch_next_in_queue(); }, /** * After a variable is created, we need to link the gdb * variable name (which is automatically created by gdb), * and the expression the user wanted to evailuate. The * new variable is saved locally. The variable UI element is then re-rendered * @param r (object): gdb mi object */ created_variable(r: any) { let expr = VarCreator.expr_being_created; if (expr) { // example payload: // "payload": { // "has_more": "0", // "name": "var2", // "numchild": "0", // "thread-id": "1", // "type": "int", // "value": "0" // }, GdbVariable.save_new_expression(expr, VarCreator.expr_type, r.payload); VarCreator.expr_being_created = null; // automatically fetch first level of children for root variables GdbVariable.fetch_and_show_children_for_var(r.payload.name); } else { // gdbgui did not expect a new variable to be created here // it's likely this tab is viewing an instance of gdb that multiple users // are interacting with } VarCreator._fetch_complete(); }, fetch_failed(r: any) { if (VarCreator.expr_type === "hover") { // do nothing } else { Actions.add_gdb_response_to_console(r); } VarCreator._fetch_complete(); }, _fetch_complete() { VarCreator._is_fetching = false; VarCreator._clear_state(); VarCreator._fetch_next_in_queue(); }, _clear_state: function() { VarCreator._is_fetching = false; } }; class GdbVariable extends React.Component { render() { const is_root = true; // @ts-expect-error ts-migrate(2339) FIXME: Property 'expr_type' does not exist on type 'Reado... Remove this comment to see the full error message if (this.props.expr_type === "local") { // @ts-expect-error ts-migrate(2339) FIXME: Property 'obj' does not exist on type 'Readonly<{}... Remove this comment to see the full error message return this.get_ul_for_local(this.props.obj); } else { // @ts-expect-error ts-migrate(2339) FIXME: Property 'obj' does not exist on type 'Readonly<{}... Remove this comment to see the full error message if (this.props.obj.numchild > 0) { return this.get_ul_for_var_with_children( // @ts-expect-error ts-migrate(2339) FIXME: Property 'expression' does not exist on type 'Read... Remove this comment to see the full error message this.props.expression, // @ts-expect-error ts-migrate(2339) FIXME: Property 'obj' does not exist on type 'Readonly<{}... Remove this comment to see the full error message this.props.obj, // @ts-expect-error ts-migrate(2339) FIXME: Property 'expr_type' does not exist on type 'Reado... Remove this comment to see the full error message this.props.expr_type, is_root ); } else { return this.get_ul_for_var_without_children( // @ts-expect-error ts-migrate(2339) FIXME: Property 'expression' does not exist on type 'Read... Remove this comment to see the full error message this.props.expression, // @ts-expect-error ts-migrate(2339) FIXME: Property 'obj' does not exist on type 'Readonly<{}... Remove this comment to see the full error message this.props.obj, // @ts-expect-error ts-migrate(2339) FIXME: Property 'expr_type' does not exist on type 'Reado... Remove this comment to see the full error message this.props.expr_type, is_root ); } } } /** * get unordered list for a "local" returned by gdb * these are special snowflakes; gdb returns a small subset of information for * locals. The list is useful to browse, but oftentimes needs to be expanded. * If the user clicks on a local that can be expanded, gdbgui will ask gdb * to create a full-fledged variable for the user to explore. gdbgui will then * render that instead of the "local". */ get_ul_for_local(local: any) { let can_be_expanded = local.can_be_expanded, // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. value = _.isString(local.value) ? Memory.make_addrs_into_links_react(local.value) : local.value, onclick = can_be_expanded ? () => GdbVariable.create_variable(local.name, "local") : () => {}; return (
    {can_be_expanded ? "+" : ""} {local.name}  {value} {/* @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. */} {_.trim(local.type)}
    ); } /** * get unordered list for a variable that has children * @return unordered list, expanded or collapsed based on the key "show_children_in_ui" */ get_ul_for_var_with_children( expression: any, mi_obj: any, expr_type: any, is_root = false ) { let child_tree; if (mi_obj.show_children_in_ui) { let content = []; if (mi_obj.children.length > 0) { for (let child of mi_obj.children) { if (child.numchild > 0) { content.push(
  • {this.get_ul_for_var_with_children(child.exp, child, expr_type)}
  • ); } else { content.push(
  • {this.get_ul_for_var_without_children(child.exp, child, expr_type)}
  • ); } } } child_tree =
      {content}
    ; } else { child_tree = ""; } let plus_or_minus = mi_obj.show_children_in_ui ? "-" : "+"; return this._get_ul_for_var( expression, mi_obj, expr_type, is_root, plus_or_minus, // @ts-expect-error ts-migrate(2345) FIXME: Type 'Element' is not assignable to type 'string'. child_tree, mi_obj.numchild ); } get_ul_for_var_without_children( expression: any, mi_obj: any, expr_type: any, is_root = false ) { return this._get_ul_for_var(expression, mi_obj, expr_type, is_root); } static _get_value_jsx(obj: any) { let val; if (obj.is_int) { val = (
    {Memory.make_addrs_into_links_react(obj._int_value_to_str_in_radix)}
    ); } else { // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. val = _.isString(obj.value) ? Memory.make_addrs_into_links_react(obj.value) : obj.value; } return val; } static change_radix(obj: any) { if (obj._radix === 16) { obj._radix = 2; } else { obj._radix += 2; } GdbVariable._update_radix_values(obj); store.set("expressions", store.get("expressions")); } /** * Get ul for a variable with or without children */ _get_ul_for_var( expression: any, mi_obj: any, expr_type: any, is_root: any, plus_or_minus = "", child_tree = "", numchild = 0 ) { let glyph_style = { fontSize: "0.8em", paddingLeft: "5px" }, delete_button = is_root && expr_type === "expr" ? ( GdbVariable.delete_gdb_variable(mi_obj.name)} /> ) : ( "" ), has_children = numchild > 0, can_draw_tree = has_children && (expr_type === "expr" || expr_type === "local"), // hover var can't draw tree tree = can_draw_tree ? ( GdbVariable.click_draw_tree_gdb_variable(mi_obj.name)} /> ) : ( "" ), toggle_classes = has_children ? "pointer" : "", plot_content = "", plot_button = "", plusminus_click_callback = has_children ? () => GdbVariable.click_toggle_children_visibility(mi_obj.name) : () => {}; if (mi_obj.can_plot && mi_obj.show_plot) { // dots are not allowed in the dom as id's. replace with '-'. let id = mi_obj.dom_id_for_plot; // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. plot_button = ( GdbVariable.click_toggle_plot(mi_obj.name)} title="remove x/y plot" /> ); // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. plot_content =
    ; } else if (mi_obj.can_plot && !mi_obj.show_plot) { // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. plot_button = ( GdbVariable.click_toggle_plot(mi_obj.name)} title="show x/y plot" /> ); } return (
    • {plus_or_minus} {expression}  {GdbVariable._get_value_jsx(mi_obj)} {/* @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. */} {_.trim(mi_obj.type) || ""}
      :{tree} {plot_button} {delete_button}
      {plot_content}
    • {child_tree}
    ); } static _get_full_path(obj: any) { if (!obj) { return ""; } function update_path(path: any, obj: any) { let potential_addition = obj.expression || obj.exp; if ( potential_addition === "public" || potential_addition === "private" || potential_addition === "protected" ) { // these are inserted by gdb, and arent actually field names! return path; } else if (path) { return potential_addition + "." + path; } else { return potential_addition; } } let path = update_path("", obj); let cur_obj = obj.parent; let depth = 0; while (cur_obj) { path = update_path(path, cur_obj); cur_obj = cur_obj.parent; depth += 1; if (depth > 100) { console.warn("exceeded maximum depth, breaking while loop"); break; } } return path; } static create_variable(expression: any, expr_type: any) { VarCreator.create_variable(expression, expr_type); } static gdb_created_root_variable(r: any) { VarCreator.created_variable(r); } static gdb_variable_fetch_failed(r: any) { VarCreator.fetch_failed(r); } /** * Got data regarding children of a gdb variable. It could be an immediate child, or grandchild, etc. * This method stores this child array data to the appropriate locally stored * object * @param r (object): gdb mi object */ static gdb_created_children_variables(r: any) { // example reponse payload: // "payload": { // "has_more": "0", // "numchild": "2", // "children": [ // { // "name": "var9.a", // "thread-id": "1", // "numchild": "0", // "value": "4195840", // "exp": "a", // "type": "int" // } // { // "name": "var9.b", // "thread-id": "1", // "numchild": "0", // "value": "0", // "exp": "b", // "type": "float" // } // ] // } let parent_name = ChildVarFetcher.expr_gdb_parent_var_currently_fetching_children; if (!parent_name) { // gdb created child variable, but the parent variable is unknown // it's likely another tab interacting w/ the same gdb instance created this } ChildVarFetcher.fetch_complete(); // get the parent object of these children let expressions = store.get("expressions"); let parent_obj = GdbVariable.get_obj_from_gdb_var_name(expressions, parent_name); if (parent_obj) { // prepare all the child objects we received for local storage let children = r.payload.children.map((child_obj: any) => GdbVariable.prepare_gdb_obj_for_storage(child_obj, parent_obj) ); // save these children as a field to their parent parent_obj.children = children; parent_obj.numchild = children.length; store.set("expressions", expressions); // if this field is an anonymous struct, the user will want to // see this expanded by default for (let child of parent_obj.children) { if (child.exp.includes(" 36) { // defensive programming console.warn("Got invalid radix. Setting to 10."); obj._radix = 10; } obj._int_value_to_str_in_radix = obj._int_value_decimal.toString(obj._radix); if (obj._radix === 16) { obj._int_value_to_str_in_radix = "0x" + obj._int_value_to_str_in_radix; } } } /** * function render a plot on an existing element * @param obj: object to make a plot for */ static _make_plot(obj: any) { let id = "#" + obj.dom_id_for_plot, // this div should have been created already jq = $(id), data = [], i = 0; // collect data for (let val of obj.values) { data.push([i, val]); i++; } // make the plot // @ts-expect-error ts-migrate(2339) FIXME: Property 'plot' does not exist on type 'JQueryStat... Remove this comment to see the full error message $.plot( jq, [ { data: data, shadowSize: 0, color: "#33cdff" } ], { series: { lines: { show: true }, points: { show: true } }, grid: { hoverable: true, clickable: false } } ); // add hover event to show tooltip jq.bind("plothover", function(event, pos, item) { if (item) { let x = item.datapoint[0], y = item.datapoint[1]; $("#plot_coordinate_tooltip") .html(`(${x}, ${y})`) .css({ top: item.pageY + 5, left: item.pageX + 5 }) .show(); } else { $("#plot_coordinate_tooltip").hide(); } }); } /** * look through all expression objects and see if they are supposed to show their plot. * If so, update the dom accordingly * @param obj: expression object to plot (may have children to plot too) */ static plot_var_and_children(obj: any) { if (obj.show_plot) { GdbVariable._make_plot(obj); } for (let child of obj.children) { GdbVariable.plot_var_and_children(child); } } static fetch_and_show_children_for_var(gdb_var_name: any) { let expressions = store.get("expressions"); let obj = GdbVariable.get_obj_from_gdb_var_name(expressions, gdb_var_name); // mutate object by reference obj.show_children_in_ui = true; // update store store.set("expressions", expressions); if (obj.numchild && obj.children.length === 0) { // need to fetch child data ChildVarFetcher.fetch_children(gdb_var_name, obj.expr_type); } else { // already have child data, re-render will occur from event dispatch } } static hide_children_in_ui(gdb_var_name: any) { let expressions = store.get("expressions"), obj = GdbVariable.get_obj_from_gdb_var_name(expressions, gdb_var_name); if (obj) { obj.show_children_in_ui = false; store.set("expressions", expressions); } } static click_toggle_children_visibility(gdb_variable_name: any) { GdbVariable._toggle_children_visibility(gdb_variable_name); } static _toggle_children_visibility(gdb_var_name: any) { // get data object, which has field that says whether its expanded or not let obj = GdbVariable.get_obj_from_gdb_var_name( store.get("expressions"), gdb_var_name ); if (obj) { let showing_children_in_ui = obj.show_children_in_ui; if (showing_children_in_ui) { // collapse GdbVariable.hide_children_in_ui(gdb_var_name); } else { // expand GdbVariable.fetch_and_show_children_for_var(gdb_var_name); } } else { console.error("developer error - expected to find gdb variable object"); } } static click_toggle_plot(gdb_var_name: any) { let expressions = store.get("expressions"), // get data object, which has field that says whether its expanded or not obj = GdbVariable.get_obj_from_gdb_var_name(expressions, gdb_var_name); if (obj) { obj.show_plot = !obj.show_plot; store.set("expressions", expressions); } } static get_update_cmds() { function _get_cmds_for_obj(obj: any) { let cmds = [`-var-update --all-values ${obj.name}`]; for (let child of obj.children) { cmds = cmds.concat(_get_cmds_for_obj(child)); } return cmds; } let cmds: any = []; for (let obj of store.get("expressions")) { cmds = cmds.concat(_get_cmds_for_obj(obj)); } return cmds; } static handle_changelist(changelist_array: any) { for (let changelist of changelist_array) { let expressions = store.get("expressions"), obj = GdbVariable.get_obj_from_gdb_var_name(expressions, changelist.name); if (obj) { if (parseInt(changelist["has_more"]) === 1 && "name" in changelist) { // already retrieved children of obj, but more fields were added. // Re-fetch the object from gdb ChildVarFetcher.fetch_children(changelist["name"], obj.expr_type); } if ("new_children" in changelist) { let new_children = changelist.new_children.map((child_obj: any) => GdbVariable.prepare_gdb_obj_for_storage(child_obj, obj) ); obj.children = obj.children.concat(new_children); } // overwrite fields of obj with fields from changelist obj = Object.assign(obj, changelist); GdbVariable._update_numeric_properties(obj); GdbVariable._update_radix_values(obj); if (obj.can_plot) { obj.values.push(obj._float_value); } store.set("expressions", expressions); } else { // error } } } static click_draw_tree_gdb_variable(gdb_variable: any) { store.set("root_gdb_tree_var", gdb_variable); } static delete_gdb_variable(gdbvar: any) { // delete locally GdbVariable._delete_local_gdb_var_data(gdbvar); // delete in gdb too GdbApi.run_gdb_command(`-var-delete ${gdbvar}`); } /** * Delete local copy of gdb variable (all its children are deleted too * since they are stored as fields in the object) */ static _delete_local_gdb_var_data(gdb_var_name: any) { let expressions = store.get("expressions"); // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. _.remove(expressions, (v: any) => v.name === gdb_var_name); store.set("expressions", expressions); } /** * Locally save the variable to our cached variables */ static save_new_expression(expression: any, expr_type: any, obj: any) { let new_obj = GdbVariable.prepare_gdb_obj_for_storage(obj, null); new_obj.expression = expression; let expressions = store.get("expressions"); expressions.push(new_obj); store.set("expressions", expressions); } /** * Get child variable with a particular name */ static get_child_with_name(children: any, name: any) { for (let child of children) { if (child.name === name) { return child; } } return undefined; } static get_root_name_from_gdbvar_name(gdb_var_name: any) { // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (_.isString(gdb_var_name)) { return gdb_var_name.split(".")[0]; } else { return ""; } } static get_child_names_from_gdbvar_name(gdb_var_name: any) { // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (_.isString(gdb_var_name)) { return gdb_var_name.split(".").slice(1, gdb_var_name.length); } else { return ""; } } /** * Get object from gdb variable name. gdb variable names are unique, and don't match * the expression being evaluated. If drilling down into fields of structures, the * gdb variable name has dot notation, such as 'var.field1.field2'. * @param gdb_var_name: gdb variable name to find corresponding cached object. Can have dot notation * @return: object if found, or undefined if not found */ static get_obj_from_gdb_var_name(expressions: any, gdb_var_name: any) { // gdb provides names in dot notation // let gdb_var_names = gdb_var_name.split('.'), let top_level_var_name = GdbVariable.get_root_name_from_gdbvar_name(gdb_var_name), children_names = GdbVariable.get_child_names_from_gdbvar_name(gdb_var_name); let objs = expressions.filter((v: any) => v.name === top_level_var_name); if (objs.length === 1) { // we found our top level object let obj = objs[0]; let name_to_find = top_level_var_name; for (let i = 0; i < children_names.length; i++) { // append the '.' and field name to find as a child of the object we're looking at name_to_find += `.${children_names[i]}`; let child_obj = GdbVariable.get_child_with_name(obj.children, name_to_find); if (child_obj) { // our new object to search is this child obj = child_obj; } else { console.error(`could not find ${name_to_find}`); return undefined; } } return obj; } else if (objs.length === 0) { return undefined; } else { console.error( `Somehow found multiple local gdb variables with the name ${top_level_var_name}. Not using any of them. File a bug report with the developer.` ); return undefined; } } } export default GdbVariable; ================================================ FILE: gdbgui/src/js/GdbguiModal.tsx ================================================ import React from "react"; import Actions from "./Actions"; import { store } from "statorgfc"; type State = any; class Modal extends React.Component<{}, State> { fullscreen_node: any; constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["show_modal", "modal_body", "modal_header"]); } render() { return (
    (this.fullscreen_node = el)} onClick={e => { if (e.target === this.fullscreen_node) { Actions.toggle_modal_visibility(); } }} >

    {this.state.modal_header}

    {this.state.modal_body}
    ); } } export default Modal; ================================================ FILE: gdbgui/src/js/GlobalEvents.ts ================================================ /** * Setup global DOM events */ import constants from "./constants"; import GdbApi from "./GdbApi"; import { store } from "statorgfc"; const GlobalEvents = { init: function() { window.onkeydown = function(e: any) { if (e.keyCode === constants.ENTER_BUTTON_NUM) { // when pressing enter in an input, don't redirect entire page! e.preventDefault(); } }; $("body").on("keydown", GlobalEvents.body_keydown); // @ts-expect-error ts-migrate(2339) FIXME: Property 'tooltip' does not exist on type 'JQuery<... Remove this comment to see the full error message $('[data-toggle="tooltip"]').tooltip(); window.onbeforeunload = () => "text here makes dialog appear when exiting. Set function to back to null for nomal behavior."; }, /** * keyboard shortcuts to interact with gdb. * enabled only when key is depressed on a target that is NOT an input. */ body_keydown: function(e: any) { let modifier = e.altKey || e.ctrlKey || e.metaKey; if (e.target.nodeName !== "INPUT" && !modifier) { let char = String.fromCharCode(e.keyCode).toLowerCase(); if (e.keyCode === constants.DOWN_BUTTON_NUM || char === "s") { GdbApi.click_step_button(); } else if (e.keyCode === constants.RIGHT_BUTTON_NUM) { GdbApi.click_next_button(); } else if (char === "n") { GdbApi.click_next_button(e.shiftKey); } else if (char === "c") { GdbApi.click_continue_button(e.shiftKey); } else if (e.keyCode === constants.UP_BUTTON_NUM || char === "u") { GdbApi.click_return_button(); } else if (char === "r") { GdbApi.click_run_button(); } else if (char === "m") { GdbApi.click_next_instruction_button(e.shiftKey); } else if (e.keyCode === constants.COMMA_BUTTON_NUM) { GdbApi.click_step_instruction_button(e.shiftKey); } else if ( e.keyCode === constants.LEFT_BUTTON_NUM && store.get("reverse_supported") ) { GdbApi.click_next_button(true); } } } }; export default GlobalEvents; ================================================ FILE: gdbgui/src/js/HoverVar.tsx ================================================ /** * A component to show/hide variable exploration when hovering over a variable * in the source code */ import React from "react"; import { store } from "statorgfc"; import constants from "./constants"; import GdbVariable from "./GdbVariable"; class HoverVar extends React.Component { static enter_timeout = undefined; // debounce fetching the expression static exit_timeout = undefined; // debounce removing the box static left = 0; static top = 0; obj: any; constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // when hovering over a potential variable $("body").on("mouseover", "#code_table span.n", HoverVar.mouseover_variable); $("body").on("mouseleave", "#code_table span.n", HoverVar.mouseout_variable); $("body").on("mouseover", "#code_table span.nx", HoverVar.mouseover_variable); $("body").on("mouseleave", "#code_table span.nx", HoverVar.mouseout_variable); // when hovering over the hover var "tooltip"-like window $("body").on("mouseenter", "#hovervar", HoverVar.mouseover_hover_window); $("body").on("mouseleave", "#hovervar", HoverVar.mouseout_hover_window); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["expressions"]); } render() { let hover_objs = store.get("expressions").filter((o: any) => o.expr_type === "hover"), obj; if (Array.isArray(hover_objs) && hover_objs.length === 1) { obj = hover_objs[0]; } this.obj = obj; if (obj) { let style = { position: "absolute", left: HoverVar.left + "px", top: HoverVar.top + "px", backgroundColor: "white" }; return ( // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type '"absolute... Remove this comment to see the full error message
    ); } else { return
    no variable hovered
    ; } } static mouseover_variable(e: any) { HoverVar.clear_hover_state(); let rect = e.target.getBoundingClientRect(), var_name = e.target.textContent; // store coordinates of where the box should be displayed HoverVar.left = rect.left; HoverVar.top = rect.bottom; const WAIT_TIME_SEC = 0.5; // @ts-expect-error ts-migrate(2322) FIXME: Type 'Timeout' is not assignable to type 'undefine... Remove this comment to see the full error message HoverVar.enter_timeout = setTimeout(() => { if (store.get("inferior_program") === constants.inferior_states.paused) { let ignore_errors = true; // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 3. GdbVariable.create_variable(var_name, "hover", ignore_errors); } }, WAIT_TIME_SEC * 1000); } static mouseout_variable(e: any) { void e; const WAIT_TIME_SEC = 0.1; // @ts-expect-error ts-migrate(2322) FIXME: Type 'Timeout' is not assignable to type 'undefine... Remove this comment to see the full error message HoverVar.exit_timeout = setTimeout(() => { HoverVar.clear_hover_state(); }, WAIT_TIME_SEC * 1000); } static mouseover_hover_window(e: any) { void e; // Mouse went from hovering over variable name in source code to // hovering over the window showing the contents of the variable. // Don't remove the window in this case. clearTimeout(HoverVar.exit_timeout); } static mouseout_hover_window(e: any) { void e; HoverVar.clear_hover_state(); } static clear_hover_state() { clearTimeout(HoverVar.enter_timeout); clearTimeout(HoverVar.exit_timeout); let exprs_objs_to_remove = store .get("expressions") .filter((obj: any) => obj.expr_type === "hover"); exprs_objs_to_remove.map((obj: any) => GdbVariable.delete_gdb_variable(obj.name)); } } export default HoverVar; ================================================ FILE: gdbgui/src/js/InferiorProgramInfo.tsx ================================================ import React from "react"; import Actions from "./Actions"; import { store } from "statorgfc"; type State = any; class InferiorProgramInfo extends React.Component<{}, State> { constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); this.get_li_for_signal = this.get_li_for_signal.bind(this); this.get_dropdown = this.get_dropdown.bind(this); this.state = { selected_signal: "SIGINT", other_pid: "" }; // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["inferior_pid", "gdb_pid"]); } get_li_for_signal(s: any, signal_key: any) { let onclick = function() { let obj = {}; // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message obj[signal_key] = s; // @ts-expect-error ts-migrate(2683) FIXME: 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message this.setState(obj); }.bind(this); return (
  • {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'signals' does not exist on type 'Readonl... Remove this comment to see the full error message */} {`${s} (${this.props.signals[s]})`}
  • ); } get_signal_choices(signal_key: any) { let signals = []; // push SIGINT and SIGKILL to top // @ts-expect-error ts-migrate(2339) FIXME: Property 'signals' does not exist on type 'Readonl... Remove this comment to see the full error message for (let s in this.props.signals) { if (s === "SIGKILL" || s === "SIGINT") { signals.push(this.get_li_for_signal(s, signal_key)); } } // @ts-expect-error ts-migrate(2339) FIXME: Property 'signals' does not exist on type 'Readonl... Remove this comment to see the full error message for (let s in this.props.signals) { if (s !== "SIGKILL" && s !== "SIGINT") { signals.push(this.get_li_for_signal(s, signal_key)); } } return signals; } get_dropdown() { return (
      {this.get_signal_choices("selected_signal")}
    ); } render() { let gdb_button = ( ); let inferior_button = null; if (this.state.inferior_pid) { inferior_button = ( ); } let other_input_and_button = ( ); return (
    send  {this.get_dropdown()}  to 
    {gdb_button} {inferior_button}

    {other_input_and_button} { this.setState({ other_pid: e.currentTarget.value }); }} value={this.state.other_pid} />

    ); // return } // render } // component export default InferiorProgramInfo; ================================================ FILE: gdbgui/src/js/InitialStoreData.ts ================================================ /* global initial_data */ /* global debug */ import constants from "./constants"; /** * The initial store data. Keys cannot be added after initialization. * All fields in here should be shared by > 1 component, otherwise they should * exist as local state for that component. */ const initial_store_data = { // environment // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'debug'. debug: debug, // if gdbgui is run in debug mode // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. gdbgui_version: initial_data.gdbgui_version, latest_gdbgui_version: "(not fetched)", gdb_version: "unknown", // this is parsed from gdb's output gdb_version_array: [], // this is parsed from gdb's output gdb_pid: undefined, // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. gdb_command: initial_data.gdb_command, can_fetch_register_values: true, // set to false if using Rust and gdb v7.12.x (see https://github.com/cs01/gdbgui/issues/64) show_settings: false, debug_in_reverse: false, reverse_supported: false, show_modal: false, modal_header: null, modal_body: null, show_tour_guide: true, tour_guide_step: 0, num_tour_guide_steps: 0, tooltip: { hidden: false, content: "placeholder", node: null, show_for_n_sec: null }, textarea_to_copy_to_clipboard: {}, // will be replaced with textarea dom node // preferences // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. themes: initial_data.themes, // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'initial_data'. current_theme: localStorage.getItem("theme") || initial_data.themes[0], highlight_source_code: true, // get saved boolean to highlight source code max_lines_of_code_to_fetch: constants.default_max_lines_of_code_to_fetch, auto_add_breakpoint_to_main: true, pretty_print: true, // whether gdb should "pretty print" variables. There is an option for this in Settings refresh_state_after_sending_console_command: true, // If true, send commands to refresh GUI store after each command is sent from console // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'debug'. show_all_sent_commands_in_console: debug, // show all sent commands if in debug mode inferior_program: constants.inferior_states.unknown, inferior_pid: null, paused_on_frame: undefined, selected_frame_num: 0, current_thread_id: undefined, stack: [], locals: [], threads: [], // source files source_file_paths: [], // all the paths gdb says were used to compile the target binary language: "c_family", // assume langage of program is c or c++. Language is determined by source file paths. Used to turn on/off certain features/warnings. files_being_fetched: [], fullname_to_render: null, line_of_source_to_flash: null, current_assembly_address: null, // rendered_source: {}, make_current_line_visible: false, // set to true when source code window should jump to current line cached_source_files: [], // list with keys fullname, source_code disassembly_for_missing_file: [], // mi response object. Only fetched when there currently paused frame refers to a file that doesn't exist or is undefined missing_files: [], // files that were attempted to be fetched but did not exist on the local filesystem source_code_state: constants.source_code_states.NONE_AVAILABLE, source_code_selection_state: constants.source_code_selection_states.PAUSED_FRAME, source_code_infinite_scrolling: false, source_linenum_to_display_start: 0, source_linenum_to_display_end: 0, // binary selection inferior_binary_path: null, inferior_binary_path_last_modified_unix_sec: null, // registers register_names: [], previous_register_values: {}, current_register_values: {}, // memory memory_cache: {}, start_addr: "", end_addr: "", bytes_per_line: "8", // breakpoints breakpoints: [], // expressions expressions: [], // array of dicts. Key is expression, value has various keys. See Expressions component. root_gdb_tree_var: null, // draw tree for this variable waiting_for_response: false, gdb_mi_output: [], gdb_autocomplete_options: [], gdb_console_entries: [], // if we try to write something before the websocket is connected, store it here queuedGdbCommands: [], show_filesystem: false, middle_panes_split_obj: {}, gdbguiPty: null }; function get_stored(key: any, default_val: any) { try { if (localStorage.hasOwnProperty(key)) { // @ts-expect-error ts-migrate(2345) FIXME: Type 'null' is not assignable to type 'string'. let cached = JSON.parse(localStorage.getItem(key)); if (typeof cached === typeof default_val) { return cached; } return default_val; } } catch (err) { console.error(err); } localStorage.removeItem(key); return default_val; } // restore saved localStorage data for (let key in initial_store_data) { // @ts-expect-error ts-migrate(7053) FIXME: No index signature with a parameter of type 'strin... Remove this comment to see the full error message let default_val = initial_store_data[key]; // @ts-expect-error ts-migrate(7053) FIXME: No index signature with a parameter of type 'strin... Remove this comment to see the full error message initial_store_data[key] = get_stored(key, default_val); } if (localStorage.hasOwnProperty("max_lines_of_code_to_fetch")) { // @ts-expect-error ts-migrate(2345) FIXME: Type 'null' is not assignable to type 'string'. let savedval = JSON.parse(localStorage.getItem("max_lines_of_code_to_fetch")); // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (_.isInteger(savedval) && savedval > 0) { initial_store_data["max_lines_of_code_to_fetch"] = savedval; } } export default initial_store_data; ================================================ FILE: gdbgui/src/js/Links.tsx ================================================ import Actions from "./Actions"; import * as React from "react"; import CopyToClipboard from "./CopyToClipboard"; import MemoryLink from "./MemoryLink"; type Props = { file?: string; fullname?: string; line: string; num_lines?: number; }; export class FileLink extends React.Component { render() { let line = parseInt(this.props.line); let onclick = () => {}, cls = ""; if (!this.props.file || !line) { line = 0; } let sep = ""; if (line && line !== 0) { sep = ":"; } if (this.props.fullname) { onclick = () => Actions.view_file(this.props.fullname, line); cls = "pointer"; } let clipboard_content = null; if (this.props.fullname || this.props.file) { clipboard_content = (this.props.fullname || this.props.file) + sep + line; } return (
    {this.props.file} {sep} {line > 0 ? line : ""} {this.props.num_lines ? `(${this.props.num_lines} lines total)` : ""}
    ); } } type FrameLinkProps = { addr: string; file?: string; fullname?: string; line: string; }; export class FrameLink extends React.Component { render() { return (
    ); } } ================================================ FILE: gdbgui/src/js/Locals.tsx ================================================ /** * A component to render "local" variables, as well as a few static methods to * assist in their creation and deletion. */ import React from "react"; import { store } from "statorgfc"; import GdbVariable from "./GdbVariable"; class Locals extends React.Component { constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["expressions", "locals"]); } render() { let content = []; // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. let sorted_local_objs = _.sortBy( store.get("locals"), (unsorted_obj: any) => unsorted_obj.name ); for (let local of sorted_local_objs) { let obj = this.get_autocreated_obj_from_expr(local.name); if (obj) { content.push( ); } else { content.push( ); } } if (content.length === 0) { return ( no locals in this context ); } else { return content; } } get_autocreated_obj_from_expr(expr: any) { for (let obj of store.get("expressions")) { if (obj.expression === expr && obj.expr_type === "local") { return obj; } } return null; } static clear_autocreated_exprs() { let exprs_objs_to_remove = store .get("expressions") .filter((obj: any) => obj.expr_type === "local"); exprs_objs_to_remove.map((obj: any) => GdbVariable.delete_gdb_variable(obj.name)); } static clear() { store.set("locals", []); Locals.clear_autocreated_exprs(); } static save_locals(locals: any) { let locals_with_meta = locals.map((local: any) => { // add field to local local.can_be_expanded = Locals.can_local_be_expanded(local) ? true : false; return local; }); store.set("locals", locals_with_meta); } static can_local_be_expanded(local: any) { // gdb returns list of locals. We may want to turn that local into a GdbVariable // to explore its children if ("value" in local) { // local has a value associated with it. It's either a native // type or a pointer. It's not a complex type like a struct. if (local.type.indexOf("*") !== -1) { // make plus if value is a pointer (has asterisk) // and can therefore be evaluated further by gdb return true; } else { return false; } } else { // is a struct or object that can be evaluated further by gdb return true; } } } export default Locals; ================================================ FILE: gdbgui/src/js/Memory.tsx ================================================ /** * The Memory component allows the user to view * data stored at memory locations. It has some * static methods used by other objects to turn text into a clickable * address. It also has methods to manage the global store of memory data. */ import { store } from "statorgfc"; import GdbApi from "./GdbApi"; import constants from "./constants"; import ReactTable from "./ReactTable"; // @ts-expect-error ts-migrate(2691) FIXME: An import path cannot end with a '.tsx' extension.... Remove this comment to see the full error message import MemoryLink from "./MemoryLink.tsx"; import Actions from "./Actions"; import React from "react"; type State = any; class Memory extends React.Component<{}, State> { static MAX_ADDRESS_DELTA_BYTES = 1000; static DEFAULT_ADDRESS_DELTA_BYTES = 31; static DEFAULT_BYTES_PER_LINE = 8; constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "memory_cache", "start_addr", "end_addr", "bytes_per_line" ]); } get_memory_component_jsx_content() { if (Object.keys(store.get("memory_cache")).length === 0) { return ( no memory to display ); } let data = [], hex_vals_for_this_addr = [], char_vals_for_this_addr = [], i = 0, hex_addr_to_display = null; let bytes_per_line = parseInt(store.get("bytes_per_line")) || Memory.DEFAULT_BYTES_PER_LINE; bytes_per_line = Math.max(bytes_per_line, 1); data.push([ more , "", "" ]); for (let hex_addr in store.get("memory_cache")) { if (!hex_addr_to_display) { hex_addr_to_display = hex_addr; } if (i % bytes_per_line === 0 && hex_vals_for_this_addr.length > 0) { // begin new row data.push([ Memory.make_addrs_into_links_react(hex_addr_to_display), hex_vals_for_this_addr.join(" "), char_vals_for_this_addr ]); // update which address we're collecting values for i = 0; hex_addr_to_display = hex_addr; hex_vals_for_this_addr = []; char_vals_for_this_addr = []; } let hex_value = store.get("memory_cache")[hex_addr]; hex_vals_for_this_addr.push(hex_value); let char = String.fromCharCode(parseInt(hex_value, 16)).replace(/\W/g, "."); char_vals_for_this_addr.push( {char} ); i++; } if (hex_vals_for_this_addr.length > 0) { // memory range requested wasn't divisible by bytes per line // add the remaining memory data.push([ Memory.make_addrs_into_links_react(hex_addr_to_display), hex_vals_for_this_addr.join(" "), char_vals_for_this_addr ]); } if (Object.keys(store.get("memory_cache")).length > 0) { data.push([ more , "", "" ]); } // @ts-expect-error ts-migrate(2769) FIXME: Type 'string' is not assignable to type 'never'. return ; } render() { let input_style = { display: "inline", width: "100px", padding: "6px 6px", height: "25px", fontSize: "1em" }, content = this.get_memory_component_jsx_content(); return (
    { store.set("start_addr", e.target.value); }} /> { store.set("end_addr", e.target.value); }} /> { store.set("bytes_per_line", e.target.value); }} /> {content}
    ); } static keypress_on_input(e: any) { if (e.keyCode === constants.ENTER_BUTTON_NUM) { Memory.fetch_memory_from_state(); } } static set_inputs_from_address(addr: any) { // set inputs in DOM store.set("start_addr", "0x" + parseInt(addr, 16).toString(16)); store.set( "end_addr", "0x" + (parseInt(addr, 16) + Memory.DEFAULT_ADDRESS_DELTA_BYTES).toString(16) ); Memory.fetch_memory_from_state(); } static get_gdb_commands_from_state() { // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. let start_addr = parseInt(_.trim(store.get("start_addr")), 16), // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. end_addr = parseInt(_.trim(store.get("end_addr")), 16); if (!window.isNaN(start_addr) && window.isNaN(end_addr)) { end_addr = start_addr + Memory.DEFAULT_ADDRESS_DELTA_BYTES; } let cmds = []; // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (_.isInteger(start_addr) && end_addr) { if (start_addr > end_addr) { end_addr = start_addr + Memory.DEFAULT_ADDRESS_DELTA_BYTES; store.set("end_addr", "0x" + end_addr.toString(16)); } else if (end_addr - start_addr > Memory.MAX_ADDRESS_DELTA_BYTES) { let orig_end_addr = end_addr; end_addr = start_addr + Memory.MAX_ADDRESS_DELTA_BYTES; store.set("end_addr", "0x" + end_addr.toString(16)); Actions.add_console_entries( `Cannot fetch ${orig_end_addr - start_addr} bytes. Changed end address to ${store.get( "end_addr" )} since maximum bytes gdbgui allows is ${Memory.MAX_ADDRESS_DELTA_BYTES}.`, constants.console_entry_type.STD_ERR ); } let cur_addr = start_addr; while (cur_addr <= end_addr) { // TODO read more than 1 byte at a time? cmds.push(`-data-read-memory-bytes ${"0x" + cur_addr.toString(16)} 1`); cur_addr = cur_addr + 1; } } if (!window.isNaN(start_addr)) { store.set("start_addr", "0x" + start_addr.toString(16)); } if (!window.isNaN(end_addr)) { store.set("end_addr", "0x" + end_addr.toString(16)); } return cmds; } static fetch_memory_from_state() { let cmds = Memory.get_gdb_commands_from_state(); Memory.clear_cache(); GdbApi.run_gdb_command(cmds); } static click_read_preceding_memory() { // update starting value, then re-fetch let NUM_ROWS = 3; // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. let start_addr = parseInt(_.trim(store.get("start_addr")), 16), byte_offset = store.get("bytes_per_line") * NUM_ROWS; store.set("start_addr", "0x" + (start_addr - byte_offset).toString(16)); Memory.fetch_memory_from_state(); } static click_read_more_memory() { // update ending value, then re-fetch let NUM_ROWS = 3; // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. let end_addr = parseInt(_.trim(store.get("end_addr")), 16), byte_offset = store.get("bytes_per_line") * NUM_ROWS; store.set("end_addr", "0x" + (end_addr + byte_offset).toString(16)); Memory.fetch_memory_from_state(); } /** * @param text: string to convert address-like text into clickable components * return react component */ static make_addrs_into_links_react(text: any) { let matches = text.match(/(0x[\d\w]+)/g); if (text && matches && matches.length) { let addr = matches[0]; let leading_text = text.slice(0, text.indexOf(addr)); let trailing_text = text.slice(text.indexOf(addr) + addr.length, text.length); let suffix_component = trailing_text; if (trailing_text) { // recursive call to turn additional addressed after the first suffix_component = Memory.make_addrs_into_links_react(trailing_text); } return ( {leading_text} {suffix_component} ); } else { return text; } } static add_value_to_cache(hex_str: any, hex_val: any) { // strip leading zeros off address provided by gdb // i.e. 0x000123 turns to // 0x123 let hex_str_truncated = "0x" + parseInt(hex_str, 16).toString(16); let cache = store.get("memory_cache"); cache[hex_str_truncated] = hex_val; store.set("memory_cache", cache); } static clear_cache() { store.set("memory_cache", {}); } } export default Memory; ================================================ FILE: gdbgui/src/js/MemoryLink.tsx ================================================ import * as React from "react"; import Memory from "./Memory"; type OwnProps = { addr: string; style?: React.CSSProperties; }; type Props = OwnProps & typeof MemoryLink.defaultProps; class MemoryLink extends React.Component { render() { // turn 0x00000000000000 into 0x0 const address_no_leading_zeros = "0x" + parseInt(this.props.addr, 16).toString(16); return ( Memory.set_inputs_from_address(address_no_leading_zeros)} title={`click to explore memory at ${address_no_leading_zeros}`} style={this.props.style} > {address_no_leading_zeros} ); } static defaultProps = { style: { fontFamily: "monospace" } }; } export default MemoryLink; ================================================ FILE: gdbgui/src/js/MiddleLeft.tsx ================================================ /** * The middle left div will be rendered with this content */ import React from "react"; import SourceCode from "./SourceCode"; import FileOps from "./FileOps"; class MiddleLeft extends React.Component { fetch_more_at_top_timeout: any; onscroll_timeout: any; source_code_container_node: any; constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); this.onscroll_container = this.onscroll_container.bind(this); this.onscroll_timeout = null; this.fetch_more_at_top_timeout = null; } render() { return (
    (this.source_code_container_node = el)} >
    ); } componentDidMount() { // @ts-expect-error ts-migrate(2322) FIXME: Type 'JQuery' is not assignable to ty... Remove this comment to see the full error message SourceCode.el_code_container = $("#code_container"); // todo: no jquery if (this.source_code_container_node) { this.source_code_container_node.onscroll = this.onscroll_container.bind(this); } } onscroll_container() { clearTimeout(this.onscroll_timeout); this.onscroll_timeout = setTimeout(this.check_to_autofetch_more_source, 100); } check_to_autofetch_more_source() { // test if "view more" buttons are visible, and if so, fetch more source let fetching_for_top = false; // don't fetch for more at bottom and top at same time if (SourceCode.view_more_top_node) { let { is_visible } = SourceCode.is_source_line_visible( // @ts-expect-error ts-migrate(2769) FIXME: Argument of type 'null' is not assignable to param... Remove this comment to see the full error message $(SourceCode.view_more_top_node) ); if (is_visible) { fetching_for_top = true; FileOps.fetch_more_source_at_beginning(); } } if (!fetching_for_top && SourceCode.view_more_bottom_node) { let { is_visible } = SourceCode.is_source_line_visible( // @ts-expect-error ts-migrate(2769) FIXME: Argument of type 'null' is not assignable to param... Remove this comment to see the full error message $(SourceCode.view_more_bottom_node) ); if (is_visible) { FileOps.fetch_more_source_at_end(); } } } } export default MiddleLeft; ================================================ FILE: gdbgui/src/js/ReactTable.tsx ================================================ import React from "react"; class TableRow extends React.Component { className: any; get_tds() { let tds = []; // @ts-expect-error ts-migrate(2339) FIXME: Property 'data' does not exist on type 'Readonly<{... Remove this comment to see the full error message for (let i in this.props.data) { // @ts-expect-error ts-migrate(2339) FIXME: Property 'data' does not exist on type 'Readonly<{... Remove this comment to see the full error message tds.push({this.props.data[i]}); } return tds; } render() { return {this.get_tds()}; } } class ReactTable extends React.Component { static defaultProps = { header: [] }; render_row(row_data: any, i: any) { // @ts-expect-error ts-migrate(2769) FIXME: Property 'data' does not exist on type 'IntrinsicA... Remove this comment to see the full error message return ; } render_head() { let ths = [], i = 0; // @ts-expect-error ts-migrate(2339) FIXME: Property 'header' does not exist on type 'Readonly... Remove this comment to see the full error message for (let th_data of this.props.header) { ths.push({th_data}); i++; } return ths; } render() { // @ts-expect-error ts-migrate(2339) FIXME: Property 'classes' does not exist on type 'Readonl... Remove this comment to see the full error message let classes = ["table", "table-condensed"].concat(this.props.classes); return ( // @ts-expect-error ts-migrate(2339) FIXME: Property 'style' does not exist on type 'Readonly<... Remove this comment to see the full error message {this.render_head()} {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'data' does not exist on type 'Readonly<{... Remove this comment to see the full error message */} {this.props.data.map(this.render_row)}
    ); } } export default ReactTable; ================================================ FILE: gdbgui/src/js/Registers.tsx ================================================ /** * A component to display, fetch, and store register */ import React from "react"; import { store } from "statorgfc"; import constants from "./constants"; import ReactTable from "./ReactTable"; import Memory from "./Memory"; import GdbApi from "./GdbApi"; import register_descriptions from "./register_descriptions"; const MAX_REGISTER_NAME_FETCH_COUNT = 5; let register_name_fetch_count = 0, register_name_fetch_timeout: any = null; type State = any; class Registers extends React.Component<{}, State> { constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "inferior_program", "previous_register_values", "current_register_values", "register_names", "can_fetch_register_values" ]); } static get_update_cmds() { let cmds: any = []; if ( [constants.inferior_states.paused, constants.inferior_states.running].indexOf( store.get("inferior_program") ) == -1 ) { return cmds; } if (store.get("can_fetch_register_values") === true) { if (store.get("register_names").length === 0) { if (register_name_fetch_count <= MAX_REGISTER_NAME_FETCH_COUNT) { clearTimeout(register_name_fetch_timeout); register_name_fetch_count++; // only fetch register names when we don't have them // assumption is that the names don't change over time cmds.push(constants.IGNORE_ERRORS_TOKEN_STR + "-data-list-register-names"); } else { register_name_fetch_timeout = setTimeout(() => { register_name_fetch_count--; }, 5000); } } // update all registers values cmds.push(constants.IGNORE_ERRORS_TOKEN_STR + "-data-list-register-values x"); } else { Registers.clear_cached_values(); } return cmds; } static cache_register_names(names: any) { // filter out non-empty names store.set( "register_names", names.filter((name: any) => name) ); } static clear_register_name_cache() { store.set("register_names", []); } static clear_cached_values() { store.set("previous_register_values", {}); store.set("current_register_values", {}); } static inferior_program_exited() { Registers.clear_cached_values(); } render() { let num_register_names = store.get("register_names").length, num_register_values = Object.keys(store.get("current_register_values")).length; if (this.state.inferior_program !== constants.inferior_states.paused) { return no data to display; } if ( (num_register_names > 0 && num_register_values > 0 && num_register_names !== num_register_values) || (num_register_names === 0 && register_name_fetch_count <= MAX_REGISTER_NAME_FETCH_COUNT) ) { // Somehow register names and values do not match. Clear cached values, then refetch both. Registers.clear_register_name_cache(); Registers.clear_cached_values(); GdbApi.run_gdb_command(Registers.get_update_cmds()); } else if (num_register_names === num_register_values) { let columns = ["name", "value (hex)", "value (decimal)", "description"], register_table_data = [], register_names = store.get("register_names"), register_values = store.get("current_register_values"), prev_register_values = store.get("previous_register_values"); for (let i in register_names) { let name = register_names[i], // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. obj = _.find(register_values, (v: any) => v["number"] === i), hex_val_raw = "", disp_hex_val = "", disp_dec_val = "", // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message register_description = register_descriptions[name] || ""; if (obj && obj.value) { hex_val_raw = obj["value"]; // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. let old_obj = _.find(prev_register_values, (v: any) => v["number"] === i), old_hex_val_raw, changed = false; if (old_obj) { old_hex_val_raw = old_obj["value"]; } // if the value changed, highlight it if (old_hex_val_raw !== undefined && hex_val_raw !== old_hex_val_raw) { changed = true; } // if hex value is a valid value, convert it to a link // and display decimal format too if (obj["value"].indexOf("0x") === 0) { disp_hex_val = Memory.make_addrs_into_links_react(hex_val_raw); disp_dec_val = parseInt(obj["value"], 16).toString(10); } if (changed) { name = {name}; // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. disp_hex_val = {disp_hex_val}; // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. disp_dec_val = {disp_dec_val}; } } register_table_data.push([ name, disp_hex_val, disp_dec_val, register_description ]); } return ( ); } return no data to display; } } export default Registers; ================================================ FILE: gdbgui/src/js/RightSidebar.tsx ================================================ /** * A component to show/hide variable exploration when hovering over a variable * in the source code */ import React from "react"; import Breakpoints from "./Breakpoints"; import constants from "./constants"; import Expressions from "./Expressions"; import GdbMiOutput from "./GdbMiOutput"; import InferiorProgramInfo from "./InferiorProgramInfo"; import Locals from "./Locals"; import Memory from "./Memory"; import Registers from "./Registers"; import Tree from "./Tree"; import Threads from "./Threads"; import ToolTipTourguide from "./ToolTipTourguide"; let onmouseup_in_parent_callbacks: any = [], onmousemove_in_parent_callbacks: any = []; let onmouseup_in_parent_callback = function() { // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'fn' implicitly has an 'any' type. onmouseup_in_parent_callbacks.map(fn => fn()); }; let onmousemove_in_parent_callback = function(e: any) { // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'fn' implicitly has an 'any' type. onmousemove_in_parent_callbacks.map(fn => { fn(e); }); }; type OwnCollapserState = any; type CollapserState = OwnCollapserState & typeof Collapser.defaultProps; class Collapser extends React.Component<{}, CollapserState> { static defaultProps = { collapsed: false, id: "" }; _height_when_clicked: any; _page_y_orig: any; _resizing: any; collapser_box_node: any; constructor(props: {}) { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); this.state = { // @ts-expect-error ts-migrate(2339) FIXME: Property 'collapsed' does not exist on type '{}'. collapsed: props.collapsed, autosize: true, height_px: null, // if an integer, force height to this value _mouse_y_click_pos_px: null, _height_when_clicked: null }; this.onmousedown_resizer = this.onmousedown_resizer.bind(this); this.onmouseup_resizer = this.onmouseup_resizer.bind(this); this.onmousemove_resizer = this.onmousemove_resizer.bind(this); this.onclick_restore_autosize = this.onclick_restore_autosize.bind(this); onmouseup_in_parent_callbacks.push(this.onmouseup_resizer.bind(this)); onmousemove_in_parent_callbacks.push(this.onmousemove_resizer.bind(this)); } toggle_visibility() { this.setState({ collapsed: !this.state.collapsed }); } onmousedown_resizer(e: any) { this._resizing = true; this._page_y_orig = e.pageY; this._height_when_clicked = this.collapser_box_node.clientHeight; } onmouseup_resizer() { this._resizing = false; } onmousemove_resizer(e: any) { if (this._resizing) { let dh = e.pageY - this._page_y_orig; this.setState({ height_px: this._height_when_clicked + dh, autosize: false }); } } onclick_restore_autosize() { this.setState({ autosize: true }); } render() { let style = { height: this.state.autosize ? "auto" : this.state.height_px + "px", overflow: this.state.autosize ? "visible" : "auto" }; let reset_size_button = ""; if (!this.state.autosize) { // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. reset_size_button = ( reset height ); } let resizer = ""; if (!this.state.collapsed) { // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. resizer = (
    {" "} {reset_size_button}
    ); } return (
    {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'title' does not exist on type 'Readonly<... Remove this comment to see the full error message */} {this.props.title}
    ... Remove this comment to see the full error message id={this.props.id} style={style} ref={n => (this.collapser_box_node = n)} > {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'content' does not exist on type 'Readonl... Remove this comment to see the full error message */} {this.props.content}
    {resizer}
    ); } } class RightSidebar extends React.Component { render() { let input_style = { display: "inline", width: "100px", padding: "6px 6px", height: "25px", fontSize: "1em" }, mi_output = ""; // @ts-expect-error ts-migrate(2339) FIXME: Property 'debug' does not exist on type 'Readonly<... Remove this comment to see the full error message if (this.props.debug) { // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. mi_output = ( // @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message } /> ); } return (
    This sidebar contains a visual, interactive representation of the state of your program

    You can see which function the process is stopped in, explore variables, and much more.

    There is more to discover, but this should be enough to get you started.

    Something missing? Found a bug?{" "} Create an issue on github.

    Happy debugging!

    } step_num={5} /> {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message */} } /> {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message */} } /> {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message */} } />
    } /> {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message */} } /> {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message */} } /> } /> {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'title' does not exist on type 'Intrinsic... Remove this comment to see the full error message */} } /> {mi_output}
    ); } componentDidMount() { Tree.init(); } } export default RightSidebar; ================================================ FILE: gdbgui/src/js/Settings.tsx ================================================ import { store } from "statorgfc"; import Actions from "./Actions"; import ToolTip from "./ToolTip"; import React from "react"; /** * Settings modal when clicking the gear icon */ class Settings extends React.Component { max_source_file_lines_input: any; save_button: any; settings_node: any; constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "debug", "current_theme", "themes", "gdb_version", "gdb_pid", "show_settings", "auto_add_breakpoint_to_main", "pretty_print", "refresh_state_after_sending_console_command", "show_all_sent_commands_in_console", "highlight_source_code" ]); this.get_update_max_lines_of_code_to_fetch = this.get_update_max_lines_of_code_to_fetch.bind( this ); } static toggle_key(key: any) { store.set(key, !store.get(key)); localStorage.setItem(key, JSON.stringify(store.get(key))); } static get_checkbox_row(store_key: any, text: any) { return (
    ); } get_update_max_lines_of_code_to_fetch() { return ( Maximum number of source file lines to display: (this.max_source_file_lines_input = el)} /> ); } get_table() { return ( {Settings.get_checkbox_row( "auto_add_breakpoint_to_main", "Add breakpoint to main after loading executable" )} {this.get_update_max_lines_of_code_to_fetch()} {Settings.get_checkbox_row( "pretty_print", "Pretty print dynamic variables (requires restart)" )} {Settings.get_checkbox_row( "refresh_state_after_sending_console_command", "Refresh all components when a command is sent from the console" )} {Settings.get_checkbox_row( "show_all_sent_commands_in_console", "Print all sent commands in console, including those sent automatically by gdbgui" )} {Settings.get_checkbox_row( "highlight_source_code", "Add syntax highlighting to source files" )}
    Theme:{" "}
    ); } render() { return (
    (this.settings_node = el)} onClick={e => { if (e.target === this.settings_node) { Settings.toggle_key("show_settings"); } }} >

    Settings

    {this.get_table()}
    ); } } export default Settings; ================================================ FILE: gdbgui/src/js/SourceCode.tsx ================================================ /** * A component to render source code, assembly, and break points */ import { store } from "statorgfc"; import React from "react"; import FileOps from "./FileOps"; import Breakpoints from "./Breakpoints"; import Memory from "./Memory"; import MemoryLink from "./MemoryLink"; import constants from "./constants"; import Actions from "./Actions"; type State = any; class SourceCode extends React.Component<{}, State> { static el_code_container = null; // todo: no jquery static el_code_container_node = null; static code_container_node = null; static view_more_top_node = null; static view_more_bottom_node = null; constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "fullname_to_render", "cached_source_files", "missing_files", "disassembly_for_missing_file", "line_of_source_to_flash", "paused_on_frame", "breakpoints", "source_code_state", "make_current_line_visible", "source_code_selection_state", "current_theme", "inferior_binary_path", "source_linenum_to_display_start", "source_linenum_to_display_end", "max_lines_of_code_to_fetch", "source_code_infinite_scrolling" ]); // bind methods this.get_body_assembly_only = this.get_body_assembly_only.bind(this); this._get_source_line = this._get_source_line.bind(this); this._get_assm_row = this._get_assm_row.bind(this); this.click_gutter = this.click_gutter.bind(this); this.is_gdb_paused_on_this_line = this.is_gdb_paused_on_this_line.bind(this); } render() { return (
    {this.get_body()}
    ); } componentDidUpdate() { let source_is_displayed = this.state.source_code_state === constants.source_code_states.SOURCE_CACHED || this.state.source_code_state === constants.source_code_states.ASSM_AND_SOURCE_CACHED; if (source_is_displayed) { if (this.state.make_current_line_visible) { let success = SourceCode.make_current_line_visible(); if (success) { store.set("make_current_line_visible", false); } } } } get_body() { const states = constants.source_code_states; switch (this.state.source_code_state) { case states.ASSM_AND_SOURCE_CACHED: // fallthrough case states.SOURCE_CACHED: { let obj = FileOps.get_source_file_obj_from_cache(this.state.fullname_to_render); if (!obj) { console.error("expected to find source file"); return this.get_body_empty(); } let paused_addr = this.state.paused_on_frame ? this.state.paused_on_frame.addr : null, start_linenum = store.get("source_linenum_to_display_start"), end_linenum = store.get("source_linenum_to_display_end"); return this.get_body_source_and_assm( obj.fullname, obj.source_code_obj, obj.assembly, paused_addr, start_linenum, end_linenum, obj.num_lines_in_file ); } case states.FETCHING_SOURCE: { return ( fetching source, please wait ); } case states.ASSM_CACHED: { let paused_addr = this.state.paused_on_frame ? this.state.paused_on_frame.addr : null, assm_array = this.state.disassembly_for_missing_file; return this.get_body_assembly_only(assm_array, paused_addr); } case states.FETCHING_ASSM: { return ( fetching assembly, please wait ); } case states.ASSM_UNAVAILABLE: { let paused_addr = this.state.paused_on_frame ? this.state.paused_on_frame.addr : null; return ( cannot access address {paused_addr} ); } case states.FILE_MISSING: { return ( file not found: {this.state.fullname_to_render} ); } case states.NONE_AVAILABLE: { return this.get_body_empty(); } default: { console.error("developer error: unhandled state"); return this.get_body_empty(); } } } click_gutter(line_num: any) { Breakpoints.add_or_remove_breakpoint(this.state.fullname_to_render, line_num); } _get_source_line( source: any, line_should_flash: any, is_gdb_paused_on_this_line: any, line_num_being_rendered: any, has_bkpt: any, has_disabled_bkpt: any, has_conditional_bkpt: any, assembly_for_line: any, paused_addr: any ) { let row_class = ["srccode"]; if (is_gdb_paused_on_this_line) { row_class.push("paused_on_line"); } else if (line_should_flash) { row_class.push("flash"); } let id = ""; if ( this.state.source_code_selection_state === constants.source_code_selection_states.PAUSED_FRAME ) { if (is_gdb_paused_on_this_line) { id = "scroll_to_line"; } } else if ( this.state.source_code_selection_state === constants.source_code_selection_states.USER_SELECTION ) { if (line_should_flash) { id = "scroll_to_line"; } } let gutter_cls = ""; if (has_disabled_bkpt) { gutter_cls = "disabled_breakpoint"; } else if (has_conditional_bkpt) { gutter_cls = "conditional_breakpoint"; } else if (has_bkpt) { gutter_cls = "breakpoint"; } let assembly_content = []; if (assembly_for_line) { let i = 0; for (let assm of assembly_for_line) { assembly_content.push(SourceCode._get_assm_content(i, assm, paused_addr)); assembly_content.push(
    ); i++; } } return ( {this.get_linenum_td(line_num_being_rendered, gutter_cls)} {assembly_content} ); } get_linenum_td(linenum: any, gutter_cls = "") { return ( { this.click_gutter(linenum); }} >
    {linenum}
    ); } /** * example return value: mov $0x400684,%edi(00) main+8 0x0000000000400585 */ static _get_assm_content(key: any, assm: any, paused_addr: any) { let opcodes = assm.opcodes ? ( {`(${assm.opcodes})`} ) : ( "" ), instruction = Memory.make_addrs_into_links_react(assm.inst), func_name = assm["func-name"], offset = assm.offset, addr = assm.address, on_current_instruction = paused_addr === assm.address, cls = on_current_instruction ? "current_assembly_command" : "", asterisk = on_current_instruction ? ( ) : ( ); return ( {/* @ts-expect-error ts-migrate(2769) FIXME: Property 'fontFamily' is missing in type '{ paddin... Remove this comment to see the full error message */} {asterisk} {opcodes /* i.e. mov */} {instruction} {func_name ? ( {func_name}+{offset} ) : ( "" )} ); } _get_assm_row(key: any, assm: any, paused_addr: any) { return ( {SourceCode._get_assm_content(key, assm, paused_addr)} ); } is_gdb_paused_on_this_line(line_num_being_rendered: any, line_gdb_is_paused_on: any) { if (this.state.paused_on_frame) { return ( line_num_being_rendered === line_gdb_is_paused_on && this.state.paused_on_frame.fullname === this.state.fullname_to_render ); } else { return false; } } get_view_more_tr(fullname: any, linenum: any, node_key: any) { return ( // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message (SourceCode[node_key] = el)}> { Actions.view_file(fullname, linenum); }} style={{ fontStyle: "italic", paddingLeft: "10px" }} className="pointer" > view more ); } get_end_of_file_tr(linenum: any) { return ( (end of file) ); } get_line_nums_to_render( source_code_obj: any, start_linenum: any, line_to_flash: any, end_linenum: any ) { let start_linenum_to_render = start_linenum; let end_linenum_to_render = end_linenum; let linenum = start_linenum; // go backwards from center until missing element is found // linenum >= start_linenum && while (linenum < end_linenum) { if (source_code_obj.hasOwnProperty(linenum)) { start_linenum_to_render = linenum; break; } else { linenum++; } } linenum = end_linenum; while (linenum > start_linenum) { if (source_code_obj.hasOwnProperty(linenum)) { end_linenum_to_render = linenum; break; } else { linenum--; } } return { start_linenum_to_render, end_linenum_to_render }; } get_body_source_and_assm( fullname: any, source_code_obj: any, assembly: any, paused_addr: any, start_linenum: any, end_linenum: any, num_lines_in_file: any ) { let body = []; let bkpt_lines = Breakpoints.get_breakpoint_lines_for_file( this.state.fullname_to_render ), disabled_breakpoint_lines = Breakpoints.get_disabled_breakpoint_lines_for_file( this.state.fullname_to_render ), conditional_breakpoint_lines = Breakpoints.get_conditional_breakpoint_lines_for_file( this.state.fullname_to_render ), line_gdb_is_paused_on = this.state.paused_on_frame ? parseInt(this.state.paused_on_frame.line) : 0; const line_of_source_to_flash = this.state.line_of_source_to_flash; const { start_linenum_to_render, end_linenum_to_render } = this.get_line_nums_to_render( source_code_obj, start_linenum, line_of_source_to_flash, end_linenum ); let line_num_being_rendered = start_linenum_to_render; while (line_num_being_rendered <= end_linenum_to_render) { let cur_line_of_code = source_code_obj[line_num_being_rendered]; let has_bkpt = bkpt_lines.indexOf(line_num_being_rendered) !== -1, has_disabled_bkpt = disabled_breakpoint_lines.indexOf(line_num_being_rendered) !== -1, has_conditional_bkpt = conditional_breakpoint_lines.indexOf(line_num_being_rendered) !== -1, is_gdb_paused_on_this_line = this.is_gdb_paused_on_this_line( line_num_being_rendered, line_gdb_is_paused_on ), assembly_for_line = assembly[line_num_being_rendered]; body.push( this._get_source_line( cur_line_of_code, line_of_source_to_flash === line_num_being_rendered, is_gdb_paused_on_this_line, line_num_being_rendered, has_bkpt, has_disabled_bkpt, has_conditional_bkpt, assembly_for_line, paused_addr ) ); line_num_being_rendered++; } SourceCode.view_more_top_node = null; SourceCode.view_more_bottom_node = null; // add "view more" buttons if necessary if (start_linenum_to_render > start_linenum) { body.unshift( this.get_view_more_tr(fullname, start_linenum_to_render - 1, "view_more_top_node") ); } else if (start_linenum !== 1) { body.unshift( this.get_view_more_tr(fullname, start_linenum - 1, "view_more_top_node") ); } if (end_linenum_to_render < end_linenum) { body.push( this.get_view_more_tr( fullname, end_linenum_to_render + 1, "view_more_bottom_node" ) ); } else if (end_linenum < num_lines_in_file) { body.push( this.get_view_more_tr(fullname, line_num_being_rendered, "view_more_bottom_node") ); } if (end_linenum_to_render === num_lines_in_file) { body.push(this.get_end_of_file_tr(num_lines_in_file + 1)); } return body; } get_body_assembly_only(assm_array: any, paused_addr: any) { let body = [], i = 0; for (let assm of assm_array) { body.push(this._get_assm_row(i, assm, paused_addr)); i++; } return body; } get_body_empty() { return ( no source code or assembly to display ); } static make_current_line_visible() { return SourceCode._make_jq_selector_visible($("#scroll_to_line")); } static is_source_line_visible(jq_selector: any) { if (jq_selector.length !== 1) { // make sure something is selected before trying to scroll to it throw "Unexpected jquery selector"; } // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. let top_of_container = SourceCode.el_code_container.position().top, // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. height_of_container = SourceCode.el_code_container.height(), bottom_of_container = top_of_container + height_of_container, top_of_line = jq_selector.position().top, bottom_of_line = top_of_line + jq_selector.height(), top_of_table = jq_selector.closest("table").position().top, is_visible = top_of_line >= top_of_container && bottom_of_line <= bottom_of_container; if (is_visible) { return { is_visible: true, top_of_line, top_of_table, height_of_container }; } else { return { is_visible: false, top_of_line, top_of_table, height_of_container }; } } /** * Scroll to a jQuery selection in the source code table * Used to jump around to various lines * returns true on success */ static _make_jq_selector_visible(jq_selector: any) { if (jq_selector.length === 1) { // make sure something is selected before trying to scroll to it const { is_visible, top_of_line, top_of_table, height_of_container } = SourceCode.is_source_line_visible(jq_selector); if (!is_visible) { // line is out of view, scroll so it's in the middle of the table const time_to_scroll = 0; let scroll_top = top_of_line - (top_of_table + height_of_container / 2); // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. SourceCode.el_code_container.animate({ scrollTop: scroll_top }, time_to_scroll); } return true; } else { return false; } } } export default SourceCode; ================================================ FILE: gdbgui/src/js/SourceCodeHeading.tsx ================================================ import React from "react"; import constants from "./constants"; import { store } from "statorgfc"; import { FileLink } from "./Links"; import FileOps from "./FileOps"; type State = any; class SourceCodeHeading extends React.Component<{}, State> { constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "fullname_to_render", "paused_on_frame", "line_of_source_to_flash", "source_code_selection_state" ]); } render() { let line; if ( this.state.source_code_selection_state === constants.source_code_selection_states.PAUSED_FRAME && this.state.paused_on_frame ) { line = this.state.paused_on_frame.line; } else { line = this.state.line_of_source_to_flash; } let num_lines = 0; if ( this.state.fullname_to_render && FileOps.get_source_file_obj_from_cache(this.state.fullname_to_render) ) { // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. num_lines = FileOps.get_num_lines_in_file(this.state.fullname_to_render); } return ( ); } } export default SourceCodeHeading; ================================================ FILE: gdbgui/src/js/SourceFileAutocomplete.tsx ================================================ import { store } from "statorgfc"; import constants from "./constants"; import Actions from "./Actions"; import Util from "./Util"; import FileOps from "./FileOps"; import React from "react"; /** * The autocomplete dropdown of source files is complicated enough * to have its own component. It uses the awesomeplete library, * which is really nice: https://leaverou.github.io/awesomplete/ */ const help_text = "Enter file path to view, press enter"; /* global Awesomplete */ class SourceFileAutocomplete extends React.Component { awesomeplete_input: any; html_input: any; constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'subscribeToKeys' does not exist on type ... Remove this comment to see the full error message store.subscribeToKeys(["source_file_paths"], this.store_change_callback.bind(this)); } store_change_callback() { // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (!_.isEqual(this.awesomeplete_input._list, store.get("source_file_paths"))) { this.awesomeplete_input.list = store.get("source_file_paths"); } } render() { return (
    (this.html_input = el)} style={{ width: "100%" }} />
    ); } keyup_source_file_input(e: any) { if (e.keyCode === constants.ENTER_BUTTON_NUM) { // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. let user_input = _.trim(e.currentTarget.value); if (user_input.length === 0) { return; } let fullname, default_line = 0, line; // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number' is not assignable to par... Remove this comment to see the full error message [fullname, line] = Util.parse_fullname_and_line(user_input, default_line); FileOps.user_select_file_to_view(fullname, line); } else if (store.get("source_file_paths").length === 0) { // source file list has not been fetched yet, so fetch it Actions.fetch_source_files(); } } onclick_dropdown() { if (store.get("source_file_paths").length === 0) { // we have not asked gdb to get the list of source paths yet, or it just doesn't have any. // request that gdb populate this list. Actions.fetch_source_files(); return; } if (this.awesomeplete_input.ul.childNodes.length === 0) { this.awesomeplete_input.evaluate(); } else if (this.awesomeplete_input.ul.hasAttribute("hidden")) { this.awesomeplete_input.open(); } else { this.awesomeplete_input.close(); } } componentDidMount() { // initialize list of source files // TODO maybe use a pre-built React component for this // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'Awesomplete'. this.awesomeplete_input = new Awesomplete("#source_file_input", { minChars: 0, maxItems: 10000, list: [], // standard sort algorithm (the default Awesomeplete sort is weird) sort: (a: any, b: any) => { return a < b ? -1 : 1; } }); // perform action when an item is selected this.html_input.addEventListener("awesomplete-selectcomplete", function(e: any) { let fullname = e.currentTarget.value; FileOps.user_select_file_to_view(fullname, 1); }); } } export default SourceFileAutocomplete; ================================================ FILE: gdbgui/src/js/StatusBar.tsx ================================================ import React from "react"; import Util from "./Util"; import { store } from "statorgfc"; type State = any; /** * Component to render a status message with optional error/warning label */ class StatusBar extends React.Component<{}, State> { render() { if (this.state.waiting_for_response) { return ; } else { return ""; } } } export default StatusBar; ================================================ FILE: gdbgui/src/js/Terminals.tsx ================================================ import React from "react"; import GdbApi from "./GdbApi"; import { Terminal } from "xterm"; import { FitAddon } from "xterm-addon-fit"; import { store } from "statorgfc"; import "xterm/css/xterm.css"; import constants from "./constants"; import Actions from "./Actions"; function customKeyEventHandler(config: { pty_name: string; pty: Terminal; canPaste: boolean; pidStoreKey: string; }) { return async (e: KeyboardEvent): Promise => { if (!(e.type === "keydown")) { return true; } if (e.shiftKey && e.ctrlKey) { const key = e.key.toLowerCase(); if (key === "c") { const toCopy = config.pty.getSelection(); navigator.clipboard.writeText(toCopy); config.pty.focus(); return false; } else if (key === "v") { if (!config.canPaste) { return false; } const toPaste = await navigator.clipboard.readText(); GdbApi.getSocket().emit("pty_interaction", { data: { pty_name: config.pty_name, key: toPaste, action: "write" } }); return false; } } return true; }; } export class Terminals extends React.Component { userPtyRef: React.RefObject; programPtyRef: React.RefObject; gdbguiPtyRef: React.RefObject; constructor(props: any) { super(props); this.userPtyRef = React.createRef(); this.programPtyRef = React.createRef(); this.gdbguiPtyRef = React.createRef(); this.terminal = this.terminal.bind(this); } terminal(ref: React.RefObject) { let className = " bg-black p-0 m-0 h-full align-baseline "; return (
    ); } render() { let terminalsClass = "w-full h-full relative grid grid-cols-3 "; return (
    {this.terminal(this.userPtyRef)} {/* */} {this.terminal(this.gdbguiPtyRef)} {this.terminal(this.programPtyRef)}
    ); } componentDidMount() { const fitAddon = new FitAddon(); const programFitAddon = new FitAddon(); const gdbguiFitAddon = new FitAddon(); const userPty = new Terminal({ cursorBlink: true, macOptionIsMeta: true, scrollback: 9999 }); userPty.loadAddon(fitAddon); userPty.open(this.userPtyRef.current); userPty.writeln(`running command: ${store.get("gdb_command")}`); userPty.writeln(""); userPty.attachCustomKeyEventHandler( // @ts-expect-error customKeyEventHandler({ pty_name: "user_pty", pty: userPty, canPaste: true, pidStoreKey: "gdb_pid" }) ); GdbApi.getSocket().on("user_pty_response", function(data: string) { userPty.write(data); }); userPty.onKey((data, ev) => { GdbApi.getSocket().emit("pty_interaction", { data: { pty_name: "user_pty", key: data.key, action: "write" } }); if (data.domEvent.code === "Enter") { Actions.onConsoleCommandRun(); } }); const programPty = new Terminal({ cursorBlink: true, macOptionIsMeta: true, scrollback: 9999 }); programPty.loadAddon(programFitAddon); programPty.open(this.programPtyRef.current); programPty.attachCustomKeyEventHandler( // @ts-expect-error customKeyEventHandler({ pty_name: "program_pty", pty: programPty, canPaste: true, pidStoreKey: "inferior_pid" }) ); programPty.write(constants.xtermColors.grey); programPty.write( "Program output -- Programs being debugged are connected to this terminal. " + "You can read output and send input to the program from here." ); programPty.writeln(constants.xtermColors.reset); GdbApi.getSocket().on("program_pty_response", function(pty_response: string) { programPty.write(pty_response); }); programPty.onKey((data, ev) => { GdbApi.getSocket().emit("pty_interaction", { data: { pty_name: "program_pty", key: data.key, action: "write" } }); }); const gdbguiPty = new Terminal({ cursorBlink: false, macOptionIsMeta: true, scrollback: 9999, disableStdin: true // theme: { background: "#888" } }); gdbguiPty.write(constants.xtermColors.grey); gdbguiPty.writeln("gdbgui output (read-only)"); gdbguiPty.writeln( "Copy/Paste available in all terminals with ctrl+shift+c, ctrl+shift+v" ); gdbguiPty.write(constants.xtermColors.reset); gdbguiPty.attachCustomKeyEventHandler( // @ts-expect-error customKeyEventHandler({ pty_name: "unused", pty: gdbguiPty, canPaste: false }) ); gdbguiPty.loadAddon(gdbguiFitAddon); gdbguiPty.open(this.gdbguiPtyRef.current); // gdbguiPty is written to elsewhere store.set("gdbguiPty", gdbguiPty); const interval = setInterval(() => { fitAddon.fit(); programFitAddon.fit(); gdbguiFitAddon.fit(); const socket = GdbApi.getSocket(); if (socket.disconnected) { return; } socket.emit("pty_interaction", { data: { pty_name: "user_pty", rows: userPty.rows, cols: userPty.cols, action: "set_winsize" } }); socket.emit("pty_interaction", { data: { pty_name: "program_pty", rows: programPty.rows, cols: programPty.cols, action: "set_winsize" } }); }, 2000); setTimeout(() => { fitAddon.fit(); programFitAddon.fit(); gdbguiFitAddon.fit(); }, 0); } } ================================================ FILE: gdbgui/src/js/Threads.tsx ================================================ import React from "react"; import ReactTable from "./ReactTable"; import { store } from "statorgfc"; import GdbApi from "./GdbApi"; import Memory from "./Memory"; import { FileLink } from "./Links"; import MemoryLink from "./MemoryLink"; class FrameArguments extends React.Component { render_frame_arg(frame_arg: any) { return [frame_arg.name, frame_arg.value]; } render() { // @ts-expect-error ts-migrate(2339) FIXME: Property 'args' does not exist on type 'Readonly<{... Remove this comment to see the full error message let frame_args = this.props.args; // @ts-expect-error ts-migrate(2339) FIXME: Property 'args' does not exist on type 'Readonly<{... Remove this comment to see the full error message if (!this.props.args) { frame_args = []; } return ( ); } } type ThreadsState = any; class Threads extends React.Component<{}, ThreadsState> { constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "threads", "current_thread_id", "stack", "selected_frame_num" ]); } static select_thread_id(thread_id: any) { GdbApi.select_thread_id(thread_id); } static select_frame(framenum: any) { store.set("selected_frame_num", framenum); store.set("line_of_source_to_flash", null); store.set("make_current_line_visible", true); GdbApi.select_frame(framenum); } render() { if (this.state.threads.length <= 0) { return ; } let content = []; for (let thread of this.state.threads) { let is_current_thread_being_rendered = parseInt(thread.id) === this.state.current_thread_id; let stack = Threads.get_stack_for_thread( thread.frame, this.state.stack, is_current_thread_being_rendered ); let row_data; try { row_data = Threads.get_row_data_for_stack( stack, this.state.selected_frame_num, thread.id, is_current_thread_being_rendered ); } catch (err) { row_data = ["unknown", "unknown", "unknown"]; console.log(err); } content.push(Threads.get_thread_header(thread, is_current_thread_being_rendered)); content.push( // @ts-expect-error ts-migrate(2769) FIXME: Type 'string' is not assignable to type 'never'. ); content.push(
    ); } return
    {content}
    ; } static get_stack_for_thread( cur_frame: any, stack_data: any, is_current_thread_being_rendered: any ) { // each thread provides only the frame that it's paused on (cur_frame). // we also have the output of `-stack-list-frames` (stack_data), which // is the full stack of the selected thread if (is_current_thread_being_rendered) { for (let frame of stack_data) { if (frame && cur_frame && frame.addr === cur_frame.addr) { return stack_data; } } } return [cur_frame]; } static get_thread_header(thread: any, is_current_thread_being_rendered: any) { let selected, cls = ""; if (is_current_thread_being_rendered) { cls = "bold"; selected = ( selected ); } else { selected = ( ); } const details = Memory.make_addrs_into_links_react(thread["target-id"]); const core = thread.core ? `, core ${thread.core}` : ""; const state = ", " + thread.state; const id = ", id " + thread.id; const name = thread.name ? `, ${thread.name}` : ""; return ( {selected} {details} {id} {core} {state} {name} ); } static get_frame_row( frame: any, is_selected_frame: any, thread_id: any, is_current_thread_being_rendered: any, frame_num: any ) { let onclick; let classes = []; let title; if (is_selected_frame) { // current frame, current thread onclick = () => {}; classes.push("bold"); title = `this is the active frame of the selected thread (frame id ${frame_num})`; } else if (is_current_thread_being_rendered) { onclick = () => { Threads.select_frame(frame_num); }; classes.push("pointer"); title = `click to select this frame (frame id ${frame_num})`; } else { // different thread, allow user to switch threads onclick = () => { Threads.select_thread_id(thread_id); }; classes.push("pointer"); title = `click to select this thead (thread id ${thread_id})`; } let key = thread_id + frame_num; return [ {frame.func} , , , // @ts-expect-error ts-migrate(2769) FIXME: Property 'args' does not exist on type 'IntrinsicA... Remove this comment to see the full error message ]; } static get_row_data_for_stack( stack: any, selected_frame_num: any, thread_id: any, is_current_thread_being_rendered: any ) { let row_data = []; let frame_num = 0; for (let frame of stack) { let is_selected_frame = selected_frame_num === frame_num && is_current_thread_being_rendered; row_data.push( Threads.get_frame_row( frame || {}, is_selected_frame, thread_id, is_current_thread_being_rendered, frame_num ) ); frame_num++; } if (stack.length === 0) { row_data.push(["unknown", "unknown", "unknown"]); } return row_data; } static update_stack(stack: any) { store.set("stack", stack); store.set("paused_on_frame", stack[store.get("selected_frame_num") || 0]); store.set( "fullname_to_render", store.get("paused_on_frame") ? store.get("paused_on_frame").fullname : {} ); store.set("line_of_source_to_flash", parseInt(store.get("paused_on_frame").line)); store.set("current_assembly_address", store.get("paused_on_frame").addr); store.set("make_current_line_visible", true); } set_thread_id(id: any) { store.set("current_thread_id", parseInt(id)); } } export default Threads; ================================================ FILE: gdbgui/src/js/ToolTip.tsx ================================================ import React from "react"; import { store } from "statorgfc"; class ToolTip extends React.Component { timeout: any; constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, ["tooltip"]); this.timeout = null; } static hide_tooltip() { store.set("tooltip", { hidden: true, show_for_n_sec: null, node: null, content: null }); } static show_tooltip_on_node(content: any, node: any, show_for_n_sec = null) { store.set("tooltip", { hidden: false, show_for_n_sec: show_for_n_sec, node: node, content: content }); } static show_copied_tooltip_on_node(node: any) { // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '1' is not assignable to paramete... Remove this comment to see the full error message ToolTip.show_tooltip_on_node("copied!", node, 1); } render() { clearTimeout(this.timeout); const tooltip = store.get("tooltip"); if (!tooltip.node || tooltip.hidden) { return null; } let rect = tooltip.node.getBoundingClientRect(), assumed_width_px = 200, distance_to_right_edge = window.innerWidth - rect.x, horizontal_buffer = distance_to_right_edge < assumed_width_px ? assumed_width_px - distance_to_right_edge : 0, left = rect.x - horizontal_buffer + "px", top = rect.y + tooltip.node.offsetHeight + "px"; // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name '_'. if (_.isInteger(tooltip.show_for_n_sec)) { this.timeout = setTimeout(ToolTip.hide_tooltip, tooltip.show_for_n_sec * 1000); } return (
    {tooltip.content}
    ); } } export default ToolTip; ================================================ FILE: gdbgui/src/js/ToolTipTourguide.tsx ================================================ import React from "react"; import Util from "./Util"; import { store } from "statorgfc"; type State = any; class ToolTipTourguide extends React.Component<{}, State> { constructor(props: {}) { super(props); // @ts-expect-error ts-migrate(2339) FIXME: Property 'position' does not exist on type '{}'. if (!props.position && !(props.top && props.left)) { console.warn("did not receive position"); } // @ts-expect-error ts-migrate(2551) FIXME: Property 'ref' does not exist on type 'ToolTipTour... Remove this comment to see the full error message this.ref = React.createRef(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState(this, [ "tour_guide_step", "num_tour_guide_steps", "show_tour_guide" ]); } componentWillMount() { store.set("num_tour_guide_steps", store.get("num_tour_guide_steps") + 1); } static dismiss() { store.set("show_tour_guide", false); store.set("tour_guide_step", 0); Util.persist_value_for_key("show_tour_guide"); } static next() { store.set("tour_guide_step", store.get("tour_guide_step") + 1); } guide_finshed() { store.set("tour_guide_step", 0); } static start_guide() { store.set("tour_guide_step", 0); store.set("show_tour_guide", true); Util.persist_value_for_key("show_tour_guide"); } componentDidUpdate() { // @ts-expect-error ts-migrate(2551) FIXME: Property 'ref' does not exist on type 'ToolTipTour... Remove this comment to see the full error message if (this.state.show_tour_guide && this.ref.current) { // need to ensure absolute position is respected by setting parent to // relative // @ts-expect-error ts-migrate(2551) FIXME: Property 'ref' does not exist on type 'ToolTipTour... Remove this comment to see the full error message this.ref.current.parentNode.style.position = "relative"; } } get_position(position_name: any) { let top, left; switch (position_name) { case "left": top = "100%"; left = "-50%"; break; case "right": top = "50%"; left = "0px"; break; case "bottom": case "bottomcenter": top = "100%"; left = "50%"; break; case "bottomleft": top = "100%"; left = "0"; break; case "topleft": top = "0"; left = "0"; break; case "overlay": top = "50%"; left = "50%"; break; default: // @ts-expect-error ts-migrate(2339) FIXME: Property 'position' does not exist on type 'Readon... Remove this comment to see the full error message console.warn("invalid position " + this.props.position); top = "100%"; left = "50%"; break; } return [top, left]; } render() { if (!this.state.show_tour_guide) { return null; // @ts-expect-error ts-migrate(2339) FIXME: Property 'step_num' does not exist on type 'Readon... Remove this comment to see the full error message } else if (this.props.step_num !== this.state.tour_guide_step) { return null; } let top, left; // @ts-expect-error ts-migrate(2339) FIXME: Property 'top' does not exist on type 'Readonly<{}... Remove this comment to see the full error message if (this.props.top && this.props.left) { // @ts-expect-error ts-migrate(2339) FIXME: Property 'top' does not exist on type 'Readonly<{}... Remove this comment to see the full error message top = this.props.top; // @ts-expect-error ts-migrate(2339) FIXME: Property 'left' does not exist on type 'Readonly<{... Remove this comment to see the full error message left = this.props.left; } else { // @ts-expect-error ts-migrate(2339) FIXME: Property 'position' does not exist on type 'Readon... Remove this comment to see the full error message [top, left] = this.get_position(this.props.position); } // @ts-expect-error ts-migrate(2339) FIXME: Property 'step_num' does not exist on type 'Readon... Remove this comment to see the full error message let is_last_step = this.props.step_num + 1 === this.state.num_tour_guide_steps, dismiss = is_last_step ? null : ( Dismiss ); return (
    {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'content' does not exist on type 'Readonl... Remove this comment to see the full error message */} {this.props.content}

    {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'step_num' does not exist on type 'Readon... Remove this comment to see the full error message */} {this.props.step_num + 1} of {this.state.num_tour_guide_steps}

    {dismiss} {is_last_step ? "Finish" : "Next"}

    ); } } export default ToolTipTourguide; ================================================ FILE: gdbgui/src/js/TopBar.tsx ================================================ import React from "react"; import { store } from "statorgfc"; import BinaryLoader from "./BinaryLoader"; import ControlButtons from "./ControlButtons"; import Settings from "./Settings"; import SourceCodeHeading from "./SourceCodeHeading"; import ToolTipTourguide from "./ToolTipTourguide"; import FileOps from "./FileOps"; import GdbApi from "./GdbApi"; import Actions from "./Actions"; import constants from "./constants"; import Util from "./Util"; let onkeyup_jump_to_line = (e: any) => { if (e.keyCode === constants.ENTER_BUTTON_NUM) { Actions.set_line_state(e.currentTarget.value); } }; let show_license = function() { Actions.show_modal( "gdbgui license", GNU General Public License v3.0

    Copyright © Chad Smith

    This software can be used personally or commercially for free.

    Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights.

    If you wish to redistribute gdbgui as part of a closed source product, you can do so for a fee. Contact chadsmith.software@gmail.com for details.

    ); }; let About = { show_about: function() { Actions.show_modal( "About gdbgui",
    gdbgui, v{store.get("gdbgui_version")}
    Copyright © Chad Smith
    ); } }; let show_session_info = function() { Actions.show_modal( "session information",
    gdb version: {store.get("gdb_version")}
    gdb pid for this tab: {store.get("gdb_pid")}
    gdbgui v{store.get("gdbgui_version")}
    ); }; const menu = (
    } /> ); type State = any; class TopBar extends React.Component<{}, State> { spinner_timeout: any; spinner_timeout_msec: any; constructor() { // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-2 arguments, but got 0. super(); // state local to the component this.state = { assembly_flavor: "intel", // default to intel (choices are 'att' or 'intel') show_spinner: false }; // global state attached to this component // @ts-expect-error ts-migrate(2339) FIXME: Property 'connectComponentState' does not exist on... Remove this comment to see the full error message store.connectComponentState( this, [ "debug_in_reverse", "reverse_supported", "source_code_state", "waiting_for_response", "show_filesystem", "latest_gdbgui_version", "gdbgui_version" ], this.store_update_callback.bind(this) ); this.spinner_timeout = null; this.spinner_timeout_msec = 5000; } store_update_callback(keys: any) { if (keys.indexOf("waiting_for_response") !== -1) { this._clear_spinner_timeout(); this.setState({ show_spinner: false }); if (this.state.waiting_for_response === true) { // false to true this._set_spinner_timeout(); } } } _set_spinner_timeout() { this.spinner_timeout = setTimeout(() => { if (this.state.waiting_for_response) { this.setState({ show_spinner: true }); } }, this.spinner_timeout_msec); } _clear_spinner_timeout() { clearTimeout(this.spinner_timeout); } toggle_assembly_flavor() { const flavor = this.state.assembly_flavor === "att" ? "intel" : "att"; this.setState({ assembly_flavor: flavor }); GdbApi.set_assembly_flavor(flavor); Actions.clear_cached_assembly(); FileOps.fetch_assembly_cur_line(); } get_controls() { return (
    e.stopPropagation()} content={
    These buttons allow you to control execution of the target you are debugging.

    Hover over these buttons to see a description of their action. For example, the button starts (or restarts) a program from the beginning.

    Each button has a keyboard shortcut. For example, you can press "r" to start running.

    } />
    ); } render() { let toggle_assm_button = ""; if ( this.state.source_code_state === constants.source_code_states.ASSM_AND_SOURCE_CACHED || this.state.source_code_state === constants.source_code_states.ASSM_CACHED ) { // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'. toggle_assm_button = ( ); } let reload_button_disabled = "disabled"; if ( this.state.source_code_state === constants.source_code_states.ASSM_AND_SOURCE_CACHED || this.state.source_code_state === constants.source_code_states.SOURCE_CACHED ) { reload_button_disabled = ""; } let reload_button = ( ); let spinner = ( ); if (this.state.show_spinner) { spinner = ( ); } let reverse_checkbox = ( ); return (
    {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'initial_user_input' does not exist on ty... Remove this comment to see the full error message */} {spinner} {reverse_checkbox} {this.get_controls()} Settings.toggle_key("show_settings")} title="settings" className="pointer glyphicon glyphicon-cog" style={{ marginRight: "10px", fontSize: "1.3em" }} /> {menu}
    {/* @ts-expect-error ts-migrate(2322) FIXME: Object literal may only specify known properties, ... Remove this comment to see the full error message */}
    {reload_button} {toggle_assm_button}
    ); } static needs_to_update_gdbgui_version() { // to actually check each value: try { return Util.is_newer( store.get("latest_gdbgui_version"), store.get("gdbgui_version") ); } catch (err) { console.error(err); return true; } } } export default TopBar; ================================================ FILE: gdbgui/src/js/Tree.ts ================================================ // a widget to visualize a tree view of a variable with children // utilizes the amazing http://visjs.org library /* global vis */ import { store } from "statorgfc"; import GdbVariable from "./GdbVariable"; import constants from "./constants"; const Tree = { el: null, // tree id must be available in DOM before calling `init` width_input: null, height_input: null, init: function() { // @ts-expect-error ts-migrate(2339) FIXME: Property 'subscribeToKeys' does not exist on type ... Remove this comment to see the full error message store.subscribeToKeys( ["root_gdb_tree_var", "expressions", "root_gdb_tree_var"], Tree._render ); let render_on_enter = (e: any) => { if (e.keyCode === 13) { Tree._render(); } }; // @ts-expect-error ts-migrate(2322) FIXME: Type 'HTMLElement' is not assignable to type 'null... Remove this comment to see the full error message Tree.el = document.getElementById(constants.tree_component_id); // @ts-expect-error ts-migrate(2322) FIXME: Type 'HTMLElement' is not assignable to type 'null... Remove this comment to see the full error message Tree.width_input = document.getElementById("tree_width"); // @ts-expect-error ts-migrate(2322) FIXME: Type 'HTMLElement' is not assignable to type 'null... Remove this comment to see the full error message Tree.height_input = document.getElementById("tree_height"); // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. Tree.width_input.onkeyup = render_on_enter; // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. Tree.height_input.onkeyup = render_on_enter; }, network: null, // initialize to null rendered_gdb_var_tree_root: null, gdb_var_being_updated: null, // if user clicks deep in a tree, only rerender that subtree, don't start from root again _render: function() { let gdbvar = store.get("root_gdb_tree_var"); if (!gdbvar) { // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. Tree.el.innerHTML = ` create an Expression, then click when viewing a variable with children to interactively explore a tree view. You can click nodes to expand/collapse them. `; return; } let expressions = store.get("expressions"), gdb_root_var_to_update = Tree.gdb_var_being_updated ? Tree.gdb_var_being_updated : gdbvar, gdb_var_obj = GdbVariable.get_obj_from_gdb_var_name( expressions, gdb_root_var_to_update ); if (!gdb_var_obj) { // couldn't find this variable name in our list of variables. Probably was a local variable the // user graphed, then hit continue, and the variable was erased by gdb. This is expected. // "GdbVariable" that users enter persist between stepping through the program though, // so it's not expected that this line will be executed for an expression store.set("root_gdb_tree_var", ""); return; } if (gdbvar === Tree.rendered_gdb_var_tree_root) { // nodes is an Object with keys corresponding to node id's (which are gdb_var_names) Tree._add_nodes_and_edges( gdb_var_obj, undefined, // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. Tree.network.body.nodes, // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. Tree.network.body.edges ); } else { Tree.render_new_network(gdb_var_obj); } Tree._update_canvas_size(); Tree.rendered_gdb_var_tree_root = gdbvar; Tree.gdb_var_being_updated = null; }, _update_canvas_size: function() { // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. if (Tree.network && Tree.network.canvas && Tree.network.canvas.options) { // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. if (parseInt(Tree.width_input.value)) { // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. Tree.network.canvas.options["width"] = parseInt(Tree.width_input.value) + "px"; } else { // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. Tree.network.canvas.options["width"] = "100%"; } // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. if (Tree.height_input.value) { // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. Tree.network.canvas.options["height"] = parseInt(Tree.height_input.value) + "px"; } else { // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. Tree.network.canvas.options["height"] = "100%"; } } }, // @param node: gdb variable object // @return string for node label in the tree _get_node_label: function(node: any) { let label = []; if (node.value) { label.push(node.value); } if (node.type) { label.push(node.type); } if (node.children.some((c: any) => c.numchild === 0)) { label.push("field(s):"); } // children field is only populated when user expands a data structure // numchild is always present // if children have been fetched and are simple values (i.e. don't have children of their own), // show them in the same node. If the child has children of its own, show it as a "hidden child" of this node let hidden_children = 0; for (let child of node.children) { if (child.numchild === 0) { label.push(`${child.exp}: ${child.value} (${child.type})`); } else { hidden_children++; } } if (node.show_children_in_ui === false && hidden_children > 0) { // children have previously been fetched but are now hidden since user toggled visibility let child_text = hidden_children === 1 ? "child" : "children"; label.push(`+ ${hidden_children} ${child_text}`); } else if (node.numchild !== node.children.length) { // children have not yet been fetched, but gdb told us this node has children. We don't know if they // are "simple" values, or complex with children of their own. We just know they exist. let child_text = node.numchild === 1 ? "child" : "children"; label.push(`+ ${node.numchild} ${child_text}`); } return label.join("\n"); }, // mutates Tree.nodes and Tree.edges to (recursively) reflect node and its children // by either adding new nodes, modifying existing nodes, or deleting nodes that should be hidden // depending on the store of the existing nodes. // If updating a node, the background is highlighted yellow if the value changed // @param node: gdb variable object that should be added to Tree.nodes // @param parent: parent node of node. undefined when node is root. // @return nothing _add_nodes_and_edges: function(node: any, parent: any) { // add/update this node let node_label = Tree._get_node_label(node); // @ts-expect-error ts-migrate(2339) FIXME: Property 'nodes' does not exist on type '{ el: nul... Remove this comment to see the full error message if (node.name in Tree.nodes._data) { // compare old value and new value // if value changed, make it yellow! // @ts-expect-error ts-migrate(2339) FIXME: Property 'nodes' does not exist on type '{ el: nul... Remove this comment to see the full error message let old_label = Tree.nodes._data[node.name].label, bgcolor = node_label === old_label ? "white" : "yellow"; // @ts-expect-error ts-migrate(2339) FIXME: Property 'nodes' does not exist on type '{ el: nul... Remove this comment to see the full error message Tree.nodes.update({ id: node.name, label: Tree._get_node_label(node), color: { background: bgcolor } }); } else { // @ts-expect-error ts-migrate(2339) FIXME: Property 'nodes' does not exist on type '{ el: nul... Remove this comment to see the full error message Tree.nodes.add({ id: node.name, label: Tree._get_node_label(node) }); } // add edge from this node to parent if it's not there // @ts-expect-error ts-migrate(2339) FIXME: Property 'edges' does not exist on type '{ el: nul... Remove this comment to see the full error message if (parent && !(node.name in Tree.edges._data)) { // @ts-expect-error ts-migrate(2339) FIXME: Property 'edges' does not exist on type '{ el: nul... Remove this comment to see the full error message Tree.edges.add({ id: node.name, from: parent.name, to: node.name, label: node.exp }); } // add/update/delete child nodes if (node.show_children_in_ui) { // add/update child nodes for (let child of node.children) { if (child.numchild > 0) { Tree._add_nodes_and_edges(child, node); } } } else { // recursively delete to make invisible for (let child of node.children) { Tree._dfs(child, function(node: any) { // @ts-expect-error ts-migrate(2339) FIXME: Property 'nodes' does not exist on type '{ el: nul... Remove this comment to see the full error message Tree.nodes.remove({ id: node.name }); // @ts-expect-error ts-migrate(2339) FIXME: Property 'edges' does not exist on type '{ el: nul... Remove this comment to see the full error message Tree.edges.remove({ id: node.name }); }); } } }, // depth-first search of node and its children. `callback` is run on each node as it is visited by // this function _dfs: function(node: any, callback: any) { callback(node); for (let child of node.children) { Tree._dfs(child, callback); } }, // sets Tree.network to be a visjs network consisting of root_gdb_var_obj and all its children // @param root_gdb_var_obj root gdb variable object for which a network should be rendered // @return nothing render_new_network: function(root_gdb_var_obj: any) { // @ts-expect-error ts-migrate(2339) FIXME: Property 'nodes' does not exist on type '{ el: nul... Remove this comment to see the full error message Tree.nodes = new vis.DataSet(); // @ts-expect-error ts-migrate(2339) FIXME: Property 'edges' does not exist on type '{ el: nul... Remove this comment to see the full error message Tree.edges = new vis.DataSet(); // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. Tree._add_nodes_and_edges(root_gdb_var_obj); // create the network var data = { // @ts-expect-error ts-migrate(2339) FIXME: Property 'nodes' does not exist on type '{ el: nul... Remove this comment to see the full error message nodes: Tree.nodes, // @ts-expect-error ts-migrate(2339) FIXME: Property 'edges' does not exist on type '{ el: nul... Remove this comment to see the full error message edges: Tree.edges }; // options found by browsing through examples here: // http://visjs.org/network_examples.html const options = { nodes: { shape: "box", color: { background: "white" } }, layout: { randomSeed: 0, hierarchical: { direction: "UD", sortMethod: "directed" } }, interaction: { dragNodes: true }, physics: { enabled: false } }; // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'vis'. Tree.network = new vis.Network(Tree.el, data, options); // http://visjs.org/examples/network/events/interactionEvents.html // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. Tree.network.on("click", function(params: any) { // left click toggles child visibility // @ts-expect-error ts-migrate(2683) FIXME: 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message let gdb_var_name = this.getNodeAt(params.pointer.DOM); Tree.gdb_var_being_updated = gdb_var_name; if (!gdb_var_name) { return; } if (gdb_var_name) { GdbVariable._toggle_children_visibility(gdb_var_name); } }); } }; export default Tree; ================================================ FILE: gdbgui/src/js/Util.ts ================================================ import { store } from "statorgfc"; /** * Some general utility methods */ const Util = { persist_value_for_key: function(key: any) { try { let value = store.get(key); localStorage.setItem(key, JSON.stringify(value)); } catch (err) { console.error(err); } }, /** * Get html table * @param columns: array of strings * @param data: array of arrays of data */ get_table: function(columns: any, data: any, style = "") { var result = [ `` ]; if (columns) { result.push(""); result.push(""); for (let h of columns) { result.push(``); } result.push(""); result.push(""); } if (data) { result.push(""); for (let row of data) { result.push(""); for (let cell of row) { result.push(``); } result.push(""); } } result.push(""); result.push("
    ${h}
    ${cell}
    "); return result.join("\n"); }, /** * Escape gdb's output to be browser compatible * @param s: string to mutate */ escape: function(s: any) { return s .replace(/>/g, ">") .replace(/") .replace(/\\r/g, "") .replace(/\\"/g, '"') .replace(/\\t/g, " "); }, /** * take a string of html in JavaScript and strip out the html * http://stackoverflow.com/a/822486/2893090 */ get_text_from_html: function(html: any) { var tmp = document.createElement("DIV"); tmp.innerHTML = html; return tmp.textContent || tmp.innerText || ""; }, /** * @param fullname_and_line: i.e. /path/to/file.c:78 * @param default_line_if_not_found: i.e. 0 * @return: Array, with 0'th element == path, 1st element == line */ parse_fullname_and_line: function( fullname_and_line: any, default_line_if_not_found = undefined ) { let user_input_array = fullname_and_line.split(":"), fullname = user_input_array[0], line = default_line_if_not_found; if (user_input_array.length === 2) { line = user_input_array[1]; } // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'undefined' is not assignable to ... Remove this comment to see the full error message return [fullname, parseInt(line)]; }, string_to_array_safe_quotes(str: any) { let output = [], cur_str = "", in_quotes = false; for (let i = 0; i < str.length; i++) { let char = str[i]; if (char === '"') { in_quotes = !in_quotes; cur_str += char; } else if (char !== " " || (char === " " && in_quotes)) { cur_str += char; } else if (char === " ") { // got a space outside of quotes if (cur_str === "") { // a consecutive space. do nothing. } else { // save this argument, and reset cur_str output.push(cur_str); cur_str = ""; } } } if (cur_str !== "") { output.push(cur_str); } return output; }, /* Return true is latest is > current 1.0.0, 0.9.9 -> true 0.1.0, 0.0.9 -> true 0.0.9, 0.0.8 -> false */ is_newer(latest: any, current: any) { latest = latest.split("."); current = current.split("."); if (latest.length !== current.length) { return true; } for (let i in latest) { if (latest[i] > current[i]) { return true; } } return false; } }; export default Util; ================================================ FILE: gdbgui/src/js/constants.ts ================================================ let constants = { ENTER_BUTTON_NUM: 13, TAB_BUTTON_NUM: 9, LEFT_BUTTON_NUM: 37, UP_BUTTON_NUM: 38, RIGHT_BUTTON_NUM: 39, DOWN_BUTTON_NUM: 40, Y_BUTTON_NUM: 89, N_BUTTON_NUM: 78, COMMA_BUTTON_NUM: 188, DATE_FORMAT: "dddd, MMMM Do YYYY, h:mm:ss a", IGNORE_ERRORS_TOKEN_STR: "1", DISASSEMBLY_FOR_MISSING_FILE_STR: "2", CREATE_VAR_STR: "3", INLINE_DISASSEMBLY_STR: "4", console_entry_type: { SENT_COMMAND: "SENT_COMMAND", STD_ERR: "STD_ERR", STD_OUT: "STD_OUT", GDBGUI_OUTPUT: "GDBGUI_OUTPUT", GDBGUI_OUTPUT_RAW: "GDBGUI_OUTPUT_RAW", AUTOCOMPLETE_OPTION: "AUTOCOMPLETE_OPTION" }, source_code_selection_states: { USER_SELECTION: "USER_SELECTION", PAUSED_FRAME: "PAUSED_FRAME" }, source_code_states: { ASSM_AND_SOURCE_CACHED: "ASSM_AND_SOURCE_CACHED", SOURCE_CACHED: "SOURCE_CACHED", FETCHING_SOURCE: "FETCHING_SOURCE", ASSM_CACHED: "ASSM_CACHED", FETCHING_ASSM: "FETCHING_ASSM", ASSM_UNAVAILABLE: "ASSM_UNAVAILABLE", FILE_MISSING: "FILE_MISSING", NONE_AVAILABLE: "NONE_AVAILABLE" }, inferior_states: { unknown: "unknown", running: "running", paused: "paused", exited: "exited" }, tree_component_id: "tree", default_max_lines_of_code_to_fetch: 500, keys_to_not_log_changes_in_console: ["gdb_mi_output"], xtermColors: { reset: "\x1B[0m", red: "\x1B[31m", grey: "\x1b[1;30m", green: "\x1B[0;32m", lgreen: "\x1B[1;32m", blue: "\x1B[0;34m", lblue: "\x1B[1;34m", yellow: "\x1B[0;33m" } }; const colorTypeMap = {}; // @ts-expect-error ts-migrate(7053) FIXME: No index signature with a parameter of type 'strin... Remove this comment to see the full error message colorTypeMap[constants.console_entry_type.STD_OUT] = constants.xtermColors["reset"]; // @ts-expect-error ts-migrate(7053) FIXME: No index signature with a parameter of type 'strin... Remove this comment to see the full error message colorTypeMap[constants.console_entry_type.STD_ERR] = constants.xtermColors["red"]; // @ts-expect-error ts-migrate(7053) FIXME: No index signature with a parameter of type 'strin... Remove this comment to see the full error message colorTypeMap[constants.console_entry_type.SENT_COMMAND] = constants.xtermColors["lblue"]; // @ts-expect-error ts-migrate(7053) FIXME: No index signature with a parameter of type 'strin... Remove this comment to see the full error message colorTypeMap[constants.console_entry_type.GDBGUI_OUTPUT] = constants.xtermColors["yellow"]; // @ts-expect-error ts-migrate(7053) FIXME: No index signature with a parameter of type 'strin... Remove this comment to see the full error message colorTypeMap[constants.console_entry_type.GDBGUI_OUTPUT_RAW] = constants.xtermColors["green"]; // @ts-expect-error ts-migrate(7053) FIXME: Property 'colorTypeMap' does not exist on type '{ ... Remove this comment to see the full error message constants["colorTypeMap"] = colorTypeMap; // @ts-expect-error ts-migrate(2551) FIXME: Property 'IGNORE_ERRORS_TOKEN_INT' does not exist ... Remove this comment to see the full error message constants["IGNORE_ERRORS_TOKEN_INT"] = parseInt(constants.IGNORE_ERRORS_TOKEN_STR); // @ts-expect-error ts-migrate(2551) FIXME: Property 'DISASSEMBLY_FOR_MISSING_FILE_INT' does n... Remove this comment to see the full error message constants["DISASSEMBLY_FOR_MISSING_FILE_INT"] = parseInt( constants.DISASSEMBLY_FOR_MISSING_FILE_STR ); // @ts-expect-error ts-migrate(2551) FIXME: Property 'CREATE_VAR_INT' does not exist on type '... Remove this comment to see the full error message constants["CREATE_VAR_INT"] = parseInt(constants.CREATE_VAR_STR); // @ts-expect-error ts-migrate(2551) FIXME: Property 'INLINE_DISASSEMBLY_INT' does not exist o... Remove this comment to see the full error message constants["INLINE_DISASSEMBLY_INT"] = parseInt(constants.INLINE_DISASSEMBLY_STR); export default Object.freeze(constants); ================================================ FILE: gdbgui/src/js/dashboard.tsx ================================================ import ReactDOM from "react-dom"; import React, { useState } from "react"; import "../../static/css/tailwind.css"; type GdbguiSession = { pid: number; start_time: string; command: string; client_ids: string[]; }; const copyIcon = ( ); // @ts-expect-error ts-migrate(2339) FIXME: Property 'gdbgui_sessions' does not exist on type ... Remove this comment to see the full error message const data: GdbguiSession[] = window.gdbgui_sessions; // @ts-expect-error ts-migrate(2339) FIXME: Property 'csrf_token' does not exist on type 'Wind... Remove this comment to see the full error message const csrf_token: string = window.csrf_token; // @ts-expect-error ts-migrate(2339) FIXME: Property 'default_command' does not exist on type ... Remove this comment to see the full error message const default_command: string = window.default_command; function GdbguiSession(props: { session: GdbguiSession; updateData: Function }) { const session = props.session; const params = new URLSearchParams({ gdbpid: session.pid.toString() }).toString(); const url = `${window.location.origin}/?${params}`; const [shareButtonText, setShareButtonText] = useState(copyIcon); const [clickedKill, setClickedKill] = useState(false); let timeout: NodeJS.Timeout; return ( {session.command} {session.pid} {session.client_ids.length} {session.start_time} Connect to Session ); } function redirect(url: string) { window.open(url, "_blank"); setTimeout(() => window.location.reload(), 500); } class StartCommand extends React.Component { constructor(props: any) { super(props); // @ts-expect-error this.state = { value: window.default_command }; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event: any) { this.setState({ value: event.target.value }); } handleSubmit() { const params = new URLSearchParams({ gdb_command: this.state.value }).toString(); redirect(`/?${params}`); } render() { return ( <>
    Enter the gdb command to run in the session.
    { if (event.key.toLowerCase() === "enter") { this.handleSubmit(); } }} placeholder="gdb --flag args" />
    ); } } function Nav() { return ( ); } class Dashboard extends React.PureComponent { interval: NodeJS.Timeout | undefined; constructor(props: any) { super(props); this.state = { sessions: data }; this.updateData = this.updateData.bind(this); } async updateData() { const response = await fetch("/dashboard_data"); const sessions = await response.json(); this.setState({ sessions }); } componentDidMount() { this.interval = setInterval(this.updateData, 5000); } componentWillUnmount() { if (this.interval) { clearInterval(this.interval); } } render() { const sessions = this.state.sessions.map((d, index) => ( )); return (
    ); } } ReactDOM.render(, document.getElementById("dashboard")); ================================================ FILE: gdbgui/src/js/gdbgui.tsx ================================================ /** * This is the entrypoint to the frontend applicaiton. * * store (global state) is managed in a single location, and each time the store * changes, components are notified and update accordingly. * */ /* global Split */ /* global initial_data */ /* global debug */ import ReactDOM from "react-dom"; import React from "react"; // @ts-expect-error ts-migrate(2305) FIXME: Module '"statorgfc"' has no exported member 'middl... Remove this comment to see the full error message import { store, middleware } from "statorgfc"; import constants from "./constants"; import GdbApi from "./GdbApi"; import FileOps from "./FileOps"; import FoldersView from "./FoldersView"; import GlobalEvents from "./GlobalEvents"; import HoverVar from "./HoverVar"; import initial_store_data from "./InitialStoreData"; import MiddleLeft from "./MiddleLeft"; import Modal from "./GdbguiModal"; import RightSidebar from "./RightSidebar"; import Settings from "./Settings"; import ToolTip from "./ToolTip"; import TopBar from "./TopBar"; import ToolTipTourguide from "./ToolTipTourguide"; import "../../static/css/gdbgui.css"; import "../../static/css/splitjs-gdbgui.css"; import { Terminals } from "./Terminals"; const store_options = { immutable: false, debounce_ms: 10 }; // @ts-expect-error ts-migrate(2339) FIXME: Property 'initialize' does not exist on type '{ ge... Remove this comment to see the full error message store.initialize(initial_store_data, store_options); // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'debug'. if (debug) { // log call store changes in console except if changed key was in // constants.keys_to_not_log_changes_in_console // @ts-expect-error ts-migrate(2339) FIXME: Property 'use' does not exist on type '{ get(key: ... Remove this comment to see the full error message store.use(function(key: any, oldval: any, newval: any) { if (constants.keys_to_not_log_changes_in_console.indexOf(key) === -1) { middleware.logChanges(key, oldval, newval); } return true; }); } // make this visible in the console // @ts-expect-error ts-migrate(2339) FIXME: Property 'store' does not exist on type 'Window & ... Remove this comment to see the full error message window.store = store; class Gdbgui extends React.PureComponent { componentWillMount() { GdbApi.init(); GlobalEvents.init(); FileOps.init(); // this should be initialized before components that use store key 'source_code_state' } render() { return (
    {/* @ts-expect-error ts-migrate(2322) FIXME: Property 'initial_user_input' does not exist on ty... Remove this comment to see the full error message */}
    {/* @ts-expect-error ts-migrate(2769) FIXME: Property 'signals' does not exist on type 'Intrins... Remove this comment to see the full error message */}
    You can view gdb's output here.
    You usually don't need to enter commands here, but you have the option to if there is something you can't do in the UI.
    } />
    {/* below are elements that are only displayed under certain conditions */}