├── .prettierrc.json ├── src ├── integration-tests │ ├── test-programs │ │ ├── disassemble.c │ │ ├── empty space.c │ │ ├── empty.c │ │ ├── bug275-测试.c │ │ ├── cwd.c │ │ ├── segv.c │ │ ├── evaluate.cpp │ │ ├── count_other.c │ │ ├── count.c │ │ ├── mem.c │ │ ├── stderr.c │ │ ├── .gitignore │ │ ├── Sleep.h │ │ ├── count space.c │ │ ├── ThreadPthreadTypes.h │ │ ├── ThreadWindowsTypes.h │ │ ├── functions_other.c │ │ ├── vars_env.c │ │ ├── vars.c │ │ ├── functions.c │ │ ├── loopforever.c │ │ ├── socketServer.js │ │ ├── stepping.c │ │ ├── vars_cpp.cpp │ │ ├── MultiThreadRunControl.cc │ │ ├── vars_globals.c │ │ ├── ThreadPthread.h │ │ ├── Thread.h │ │ ├── Makefile │ │ ├── MultiThread.cc │ │ └── ThreadWindows.h │ ├── test │ │ └── setup.ts │ ├── stop.spec.ts │ ├── GDBBackend.spec.ts │ ├── pause.spec.ts │ ├── stopGDBServer.spec.ts │ ├── stderr.spec.ts │ ├── stepout.spec.ts │ ├── terminated.spec.ts │ ├── logpoints.spec.ts │ ├── continues.spec.ts │ ├── mocks │ │ └── debugAdapters │ │ │ └── dynamicBreakpointOptions.ts │ ├── gdbCwd.spec.ts │ ├── lateAsyncErrorsRemote.spec.ts │ ├── mem-cdt-custom.spec.ts │ ├── custom-reset.spec.ts │ ├── dynamicBreakpointOptions.spec.ts │ └── config.spec.ts ├── util │ ├── isWindowsPath.ts │ ├── isHexString.ts │ ├── processes.ts │ ├── parseGdbVersionOutput.ts │ ├── standardEscape.ts │ ├── getGdbCwd.ts │ ├── getGdbVersion.ts │ ├── calculateMemoryOffset.ts │ ├── createEnvValues.ts │ ├── sendResponseWithTimeout.ts │ └── compareVersions.ts ├── constants │ ├── session.ts │ └── gdb.ts ├── web.ts ├── index.ts ├── mi │ ├── index.ts │ ├── target.ts │ ├── interpreter.ts │ ├── thread.ts │ ├── symbols.ts │ ├── base.ts │ ├── exec.ts │ ├── stack.ts │ └── data.ts ├── debugAdapter.ts ├── debugTargetAdapter.ts ├── events │ ├── continuedEvent.ts │ └── stoppedEvent.ts ├── web │ ├── processManagers │ │ ├── GDBServerWebProcessManager.ts │ │ └── GDBWebProcessManager.ts │ ├── factories │ │ ├── GDBServerFactory.ts │ │ └── GDBBackendFactory.ts │ └── GDBDebugSession.ts ├── desktop │ ├── factories │ │ ├── GDBServerFactory.ts │ │ └── GDBBackendFactory.ts │ ├── processManagers │ │ ├── GDBFileSystemProcessManagerBase.ts │ │ ├── GDBPTYProcessManager.ts │ │ ├── GDBFileSystemProcessManager.ts │ │ └── GDBServerFileSystemProcessManager.ts │ └── GDBDebugSession.ts ├── native │ ├── pty.ts │ ├── scoped_fd.h │ ├── file.ts │ ├── forked-file.ts │ ├── pty.spec.ts │ └── pty.cc ├── namedLogger.ts ├── gdb │ ├── errors.ts │ └── common.ts ├── varManager.ts └── types │ └── gdb.ts ├── .vscode ├── settings.json ├── extensions.json ├── tasks.json └── launch.json ├── .npmignore ├── .prettierignore ├── .gitignore ├── .mocharc.json ├── .mocharc-windows-ci.json ├── .devcontainer ├── devcontainer.json └── Dockerfile ├── .editorconfig ├── .nycrc.json ├── install.js ├── tsconfig.json ├── binding.gyp ├── .github └── workflows │ ├── report.yml │ ├── build-pr.yml │ └── build-push.yml ├── NOTICE ├── CONTRIBUTING.md └── eslint.config.mjs /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5" 4 | } 5 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/disassemble.c: -------------------------------------------------------------------------------- 1 | int main() { 2 | return 0; 3 | } 4 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/empty space.c: -------------------------------------------------------------------------------- 1 | int main() 2 | { 3 | return 0; 4 | } 5 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/empty.c: -------------------------------------------------------------------------------- 1 | int main(int argc, char *argv[]) 2 | { 3 | return 0; 4 | } 5 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/bug275-测试.c: -------------------------------------------------------------------------------- 1 | int main(int argc, char *argv[]) 2 | { 3 | return 0; 4 | } 5 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/cwd.c: -------------------------------------------------------------------------------- 1 | int main(int argc, char *argv[]) 2 | { 3 | return 0; // STOP HERE 4 | } 5 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/segv.c: -------------------------------------------------------------------------------- 1 | int foo(int *addr) { 2 | return *addr; 3 | } 4 | 5 | int main() { 6 | foo(0); 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false, 3 | "eslint.packageManager": "yarn" 4 | } 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | .editorconfig 3 | .vscode 4 | *.tgz 5 | test-reports 6 | tsconfig.json 7 | tsfmt.json 8 | tslint.json 9 | dist/integration-tests 10 | *.map 11 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/evaluate.cpp: -------------------------------------------------------------------------------- 1 | int main() { 2 | int monitor = 0; 3 | while(monitor < 1000){ 4 | monitor++; 5 | } 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | compile_commands.json 2 | node_modules 3 | /.vscode/ipch/ 4 | /build/ 5 | /dist/ 6 | /test-reports/ 7 | *.tgz 8 | yarn-error.log 9 | coverage/ 10 | .nyc_output/ 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | compile_commands.json 2 | node_modules 3 | /.vscode/ipch/ 4 | /build/ 5 | /dist/ 6 | /test-reports/ 7 | /test-logs/ 8 | *.tgz 9 | yarn-error.log 10 | coverage/ 11 | .nyc_output/ 12 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "//": "This timeout should match what is in CdtDebugClient constructor", 3 | "timeout": "5000", 4 | "require": ["ts-node/register", "src/integration-tests/test/setup.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /.mocharc-windows-ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "//": "This timeout should match what is in CdtDebugClient constructor", 3 | "timeout": "25000", 4 | "require": ["ts-node/register", "src/integration-tests/test/setup.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node", 3 | "build": { "dockerfile": "Dockerfile" }, 4 | 5 | "customizations": { 6 | "vscode": { 7 | "extensions": [] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | end_of_line = lf 6 | indent_style = space 7 | 8 | [*.{js,ts,md,c,cpp,h}] 9 | indent_size = 4 10 | 11 | [*.{json,yml}] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@istanbuljs/nyc-config-typescript", 3 | "check-coverage": true, 4 | "all": true, 5 | "include": ["src/**/!(*.spec.*).[tj]s?(x)"], 6 | "reporter": ["html", "lcov", "text", "text-summary"], 7 | "report-dir": "coverage" 8 | } 9 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/count_other.c: -------------------------------------------------------------------------------- 1 | static int staticfunc1(void) { 2 | return 2; 3 | } 4 | static int staticfunc2(void) { 5 | return 2; 6 | } 7 | 8 | int other(void) { 9 | staticfunc1(); 10 | staticfunc2(); 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/count.c: -------------------------------------------------------------------------------- 1 | int main() { 2 | int count = 0, another = 0; 3 | while (1) { 4 | count ++; 5 | // line with no code 6 | another ++; 7 | } 8 | return 0; 9 | } 10 | volatile int g_variable = 0xDEADBEEF; 11 | -------------------------------------------------------------------------------- /install.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const { spawnSync } = require('child_process'); 3 | 4 | if (os.platform() === 'linux') { 5 | const { status } = spawnSync('npm', ['run', 'nativebuild'], { 6 | stdio: 'inherit', 7 | }); 8 | process.exitCode = status; 9 | } 10 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/mem.c: -------------------------------------------------------------------------------- 1 | int main() { 2 | static unsigned char array[] = { 3 | 0xf1, 0xef, 0xd4, 0xfd, 4 | 0x72, 0x48, 0x45, 0x0c, 5 | 0x2d, 0x13, 0x74, 0xd6, 6 | 0xf6, 0x12, 0xdc, 0xe1, 7 | 0x89, 0x03, 0x9e, 0x42, 8 | }; 9 | 10 | static unsigned char *parray = array; 11 | 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/stderr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Sleep.h" 3 | int main() 4 | { 5 | fprintf(stderr, "STDERR Here I am\n"); 6 | fflush(stderr); 7 | 8 | // Sleep for a while so that there is no other 9 | // noise, such as process termination, while 10 | // looking for above output 11 | SLEEP(2); 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/.gitignore: -------------------------------------------------------------------------------- 1 | count 2 | functions 3 | disassemble 4 | empty 5 | empty space 6 | evaluate 7 | mem 8 | vars 9 | vars_env 10 | vars_globals 11 | segv 12 | loopforever 13 | MultiThread 14 | MultiThreadRunControl 15 | *.o 16 | *.exe 17 | .vscode 18 | .project 19 | .cproject 20 | .settings/ 21 | /vars_cpp 22 | /log 23 | stderr 24 | bug275-测试 25 | stepping 26 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/Sleep.h: -------------------------------------------------------------------------------- 1 | #ifndef Sleep_h 2 | #define Sleep_h 3 | 4 | #ifdef __MINGW32__ // MinGW has no POSIX support; use Win32 API 5 | #include 6 | #define SLEEP(s) Sleep((s)*1000) // Win32's Sleep takes milliseconds 7 | #else 8 | #include 9 | #define SLEEP(s) sleep(s) // POSIX sleep takes seconds 10 | #endif 11 | 12 | #endif // Sleep_h 13 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/count space.c: -------------------------------------------------------------------------------- 1 | static int staticfunc1(void) { 2 | return 2; 3 | } 4 | static int staticfunc2(void) { 5 | // line with no code 6 | return 2; 7 | } 8 | 9 | int other_space(void) 10 | { staticfunc1(); // make the line of code the same as opening brace to account for different gdb/gcc combinations 11 | staticfunc2(); 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/ThreadPthreadTypes.h: -------------------------------------------------------------------------------- 1 | #ifndef THREADPTHREADTYPES_H 2 | #define THREADPTHREADTYPES_H 3 | 4 | #include 5 | #include 6 | 7 | /* Type definitions */ 8 | 9 | typedef pthread_t ThreadHandle; 10 | typedef pthread_barrier_t ThreadBarrier; 11 | typedef sem_t ThreadSemaphore; 12 | typedef void *ThreadRet; 13 | static void *THREAD_DEFAULT_RET = NULL; 14 | #define THREAD_CALL_CONV 15 | 16 | #endif // THREADPTHREADTYPES_H 17 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": ["EditorConfig.EditorConfig"], 7 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 8 | "unwantedRecommendations": [] 9 | } 10 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/ThreadWindowsTypes.h: -------------------------------------------------------------------------------- 1 | #ifndef THREADWINDOWSTYPES_H 2 | #define THREADWINDOWSTYPES_H 3 | 4 | #include 5 | 6 | /* Type definitions */ 7 | 8 | struct WindowsThreadBarrier; 9 | 10 | typedef HANDLE ThreadHandle; 11 | typedef struct WindowsThreadBarrier ThreadBarrier; 12 | typedef HANDLE ThreadSemaphore; 13 | typedef unsigned ThreadRet; 14 | static ThreadRet THREAD_DEFAULT_RET = 0; 15 | #define THREAD_CALL_CONV __stdcall 16 | 17 | #endif // THREADWINDOWSTYPES_H 18 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/functions_other.c: -------------------------------------------------------------------------------- 1 | static int staticfunc1(void) 2 | { return 2; // make the line of code the same as opening brace to account for different gdb/gcc combinations 3 | } 4 | static int staticfunc2(void) 5 | { return 2; // make the line of code the same as opening brace to account for different gdb/gcc combinations 6 | } 7 | 8 | int other(void) 9 | { staticfunc1(); // make the line of code the same as opening brace to account for different gdb/gcc combinations 10 | staticfunc2(); 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /src/util/isWindowsPath.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2025 ABB Ltd. and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | export function isWindowsPath(p: string): boolean { 12 | return /^[a-zA-Z]:[\\/]/.test(p); 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Watch", 8 | "command": "yarn", 9 | "args": ["watch"], 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | }, 14 | "problemMatcher": ["$tsc-watch"], 15 | "isBackground": true 16 | }, 17 | { 18 | "label": "Tests", 19 | "command": "yarn", 20 | "args": ["test:integration"] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/integration-tests/test/setup.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2025 Arm Limited and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import * as chai from 'chai'; 12 | import * as chaiString from 'chai-string'; 13 | 14 | chai.use(chaiString); 15 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use a base image 2 | FROM node:20 3 | 4 | # Create app directory 5 | WORKDIR /work 6 | 7 | # Install GCC 8 | RUN apt-get update 9 | RUN apt-get -y install build-essential gcc g++ gdb gdbserver socat 10 | RUN gdb --version 11 | RUN gcc --version 12 | RUN gdbserver --version 13 | #RUN sysctl kernel.yama.ptrace_scope=0 14 | 15 | # Copy your app's source code 16 | #COPY . . 17 | 18 | ## Build the project 19 | #RUN yarn 20 | # 21 | ## Build test programs 22 | #RUN make -C src/integration-tests/test-programs 23 | # 24 | ## Run tests 25 | #RUN yarn test:integration 26 | # 27 | ## Expose a port 28 | #EXPOSE 3000 29 | 30 | CMD ["/bin/bash"] -------------------------------------------------------------------------------- /src/constants/session.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2025 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | /** 12 | * The default stepping response timeout. 13 | * Used if it is not defined in the launch configuration. 14 | */ 15 | export const DEFAULT_STEPPING_RESPONSE_TIMEOUT = 100; 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "target": "es2015", 6 | "lib": ["es2015"], 7 | "outDir": "dist", 8 | "sourceMap": true, 9 | "declaration": true, 10 | "strict": true, 11 | "noImplicitAny": true, 12 | "noImplicitReturns": true, 13 | "noUnusedLocals": true, 14 | "allowSyntheticDefaultImports": true, 15 | "experimentalDecorators": true, 16 | "plugins": [ 17 | { 18 | "name": "tslint-language-service" 19 | } 20 | ] 21 | }, 22 | "include": [ 23 | "src/**/*.ts", 24 | "src/integration-tests/test-programs/socketServer.js" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/vars_env.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | char *path, *test1, *test2, *test3, *test4, *envtest; 7 | path = getenv("PATH"); 8 | test1 = getenv("VARTEST1"); 9 | test2 = getenv("VARTEST2"); 10 | test3 = getenv("VARTEST3"); 11 | test4 = getenv("VARTEST4"); 12 | envtest = getenv("ENV_TEST_VAR"); 13 | printf("PATH: %s\n", path); 14 | printf("VARTEST1: %s\n", test1); 15 | printf("VARTEST2: %s\n", test2); 16 | printf("VARTEST3: %s\n", test3); 17 | printf("VARTEST4: %s\n", test4); 18 | printf("ENV_TEST_VAR: %s\n", envtest); 19 | return 0; // STOP HERE 20 | } 21 | -------------------------------------------------------------------------------- /src/web.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | export * from './mi'; 11 | export * from './types/gdb'; 12 | export * from './types/session'; 13 | export * from './gdb/GDBBackend'; 14 | export * from './gdb/GDBDebugSessionBase'; 15 | export * from './web/GDBDebugSession'; 16 | export * from './web/GDBTargetDebugSession'; 17 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 QNX Software Systems and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | export * from './mi'; 12 | export * from './types/gdb'; 13 | export * from './types/session'; 14 | export * from './gdb/GDBBackend'; 15 | export * from './gdb/GDBDebugSessionBase'; 16 | export * from './desktop/GDBDebugSession'; 17 | export * from './desktop/GDBTargetDebugSession'; 18 | -------------------------------------------------------------------------------- /src/mi/index.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 Ericsson and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | export * from './base'; 11 | export * from './breakpoint'; 12 | export * from './data'; 13 | export * from './exec'; 14 | export * from './stack'; 15 | export * from './target'; 16 | export * from './thread'; 17 | export * from './var'; 18 | export * from './interpreter'; 19 | export * from './symbols'; 20 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/vars.c: -------------------------------------------------------------------------------- 1 | 2 | struct bar 3 | { 4 | int a; 5 | int b; 6 | }; 7 | 8 | struct baz 9 | { 10 | float w; 11 | double v; 12 | }; 13 | 14 | struct foo 15 | { 16 | int x; 17 | int y; 18 | struct bar z; 19 | struct baz aa; 20 | }; 21 | 22 | int main() 23 | { 24 | int a = 1; 25 | int b = 2; 26 | int c = a + b; // STOP HERE 27 | struct foo r = {1, 2, {3, 4}, {3.1415, 1234.5678}}; 28 | int d = r.x + r.y; 29 | int e = r.z.a + r.z.b; 30 | int f[] = {1, 2, 3}; 31 | int g = f[0] + f[1] + f[2]; // After array init 32 | int rax = 1; 33 | const unsigned char h[] = {0x01, 0x10, 0x20}; 34 | const unsigned char k[] = "hello"; // char string setup 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/functions.c: -------------------------------------------------------------------------------- 1 | extern int other(void); 2 | static int staticfunc1(void) 3 | { return 2; // make the line of code the same as opening brace to account for different gdb/gcc combinations 4 | } 5 | static int staticfunc2(void) 6 | { return 2; // make the line of code the same as opening brace to account for different gdb/gcc combinations 7 | } 8 | 9 | int sub(void) 10 | { return 0; // make the line of code the same as opening brace to account for different gdb/gcc combinations 11 | } 12 | 13 | int main(void) 14 | { staticfunc1(); // make the line of code the same as opening brace to account for different gdb/gcc combinations 15 | staticfunc2(); 16 | sub(); 17 | other(); 18 | while (1) { 19 | sub(); 20 | } 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/loopforever.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | volatile int var1 = 0; 4 | volatile int var2 = 0; 5 | volatile int stop = 0; 6 | 7 | 8 | int inner1(void) { 9 | return var1++; 10 | } 11 | int inner2(void) { 12 | return var2++; 13 | } 14 | 15 | int main(int argc, char *argv[]) 16 | { 17 | time_t start_time = time(NULL); // main function 18 | while (stop == 0) { 19 | if (time(NULL) > start_time + 10) { 20 | // Don't actually loop forever as that can hang tests 21 | // run for about 10 seconds, about twice as long as the test timeout's worst case 22 | // especially on Windows where pause does not work (yet) 23 | return 1; 24 | } 25 | inner1(); // inner1 stop 26 | inner2(); 27 | } 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /src/util/isHexString.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2025 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | /** 12 | * Checks if the given value is an hex string starting with 0x 13 | * 14 | * @param value 15 | * Reference value to check. For example '0x0000FF00', 'main', 'main+200' 16 | * @return 17 | * Returns true if value is an hex string, otherwise returns false. 18 | */ 19 | 20 | export const isHexString = (value: string) => /^0x[\da-f]+$/i.test(value); 21 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/socketServer.js: -------------------------------------------------------------------------------- 1 | const net = require('net'); 2 | const os = require('os'); 3 | 4 | // Create socket echo server. 5 | const socketServer = new net.Server(); 6 | 7 | socketServer.on('connection', (connection) => { 8 | console.log('adapter connected'); 9 | 10 | connection.on('end', () => { 11 | console.log('adapter disconected'); 12 | }); 13 | 14 | // Echo "Hello World!" 15 | connection.write(`Hello World!${os.EOL}`); 16 | }); 17 | 18 | socketServer.on('close', () => { 19 | console.log('shutting down'); 20 | }); 21 | 22 | socketServer.on('error', (error) => { 23 | throw error; 24 | }); 25 | 26 | function serverListen() { 27 | socketServer.listen(0, 'localhost', 1, () => { 28 | const port = socketServer.address().port; 29 | console.log(`${port}`); 30 | }); 31 | } 32 | 33 | serverListen(); 34 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/stepping.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern int getFromElsewhere(int start); 4 | 5 | int main (int argc, char *argv[]) 6 | { char knownLocally = 10; 7 | int i; 8 | for (i = 0; i < 3; i++) { // main for 9 | knownLocally += 1; 10 | int gottenFromElsewhere = getFromElsewhere(knownLocally); // main getFromElsewhere call 11 | printf("Saw it here first: %d", knownLocally); // main printf call 12 | } 13 | return 0; 14 | } 15 | 16 | // make the line of code the same as opening brace to account for different gdb/gcc combinations 17 | int getFromElsewhere(int start) 18 | { int result = start; int i; // getFromElsewhere entry 19 | for (i = 1; i <= 5; i++) { // getFromElsewhere for 20 | result += i; 21 | printf("Eventually, I'll return something like... %d", result); 22 | } 23 | return result; 24 | } 25 | -------------------------------------------------------------------------------- /src/util/processes.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2025 Arm Ltd. and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { IStdioProcess } from '../types/gdb'; 11 | 12 | /** 13 | * Check if the process is active 14 | * 15 | * @param proc 16 | * Process to check 17 | * @return 18 | * Returns true if process is active, false otherwise 19 | */ 20 | export const isProcessActive = (proc?: IStdioProcess): boolean => { 21 | if (!proc) { 22 | return false; 23 | } 24 | return !proc.exitCode && proc.exitCode !== 0 && !proc.signalCode; 25 | }; 26 | -------------------------------------------------------------------------------- /src/debugAdapter.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /********************************************************************* 4 | * Copyright (c) 2018 QNX Software Systems and others 5 | * 6 | * This program and the accompanying materials are made 7 | * available under the terms of the Eclipse Public License 2.0 8 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 9 | * 10 | * SPDX-License-Identifier: EPL-2.0 11 | *********************************************************************/ 12 | import { logger } from '@vscode/debugadapter/lib/logger'; 13 | import { GDBDebugSession } from './desktop/GDBDebugSession'; 14 | 15 | process.on('uncaughtException', (err: any) => { 16 | logger.error(JSON.stringify(err)); 17 | }); 18 | 19 | class GDBDebugSessionToRun extends GDBDebugSession { 20 | constructor() { 21 | super(); 22 | } 23 | } 24 | 25 | GDBDebugSessionToRun.run(GDBDebugSessionToRun); 26 | -------------------------------------------------------------------------------- /src/debugTargetAdapter.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /********************************************************************* 4 | * Copyright (c) 2018 QNX Software Systems and others 5 | * 6 | * This program and the accompanying materials are made 7 | * available under the terms of the Eclipse Public License 2.0 8 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 9 | * 10 | * SPDX-License-Identifier: EPL-2.0 11 | *********************************************************************/ 12 | import { logger } from '@vscode/debugadapter/lib/logger'; 13 | import { GDBTargetDebugSession } from './desktop/GDBTargetDebugSession'; 14 | 15 | process.on('uncaughtException', (err: any) => { 16 | logger.error(JSON.stringify(err)); 17 | }); 18 | 19 | class GDBTargetDebugSessionToRun extends GDBTargetDebugSession { 20 | constructor() { 21 | super(); 22 | } 23 | } 24 | 25 | GDBTargetDebugSessionToRun.run(GDBTargetDebugSessionToRun); 26 | -------------------------------------------------------------------------------- /src/util/parseGdbVersionOutput.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2022 Kichwa Coders Canada, Inc. and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | /** 12 | * Find gdb version info from a string object which is supposed to 13 | * contain output text of "gdb --version" command. 14 | * 15 | * @param stdout 16 | * output text from "gdb --version" command . 17 | * @return 18 | * String representation of version of gdb such as "10.1" on success 19 | */ 20 | export function parseGdbVersionOutput(stdout: string): string | undefined { 21 | return stdout.split(/ gdb( \(.*?\))? (\D* )*\(?(\d*(\.\d*)*)/g)[3]; 22 | } 23 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/vars_cpp.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================ 2 | // Name : hello_cpp.cpp 3 | // Author : 4 | // Version : 5 | // Copyright : Your copyright notice 6 | // Description : Hello World in C++, Ansi-style 7 | //============================================================================ 8 | 9 | #include 10 | using namespace std; 11 | 12 | class Foo 13 | { 14 | public: 15 | int a; 16 | Foo(int, int, char); 17 | 18 | protected: 19 | int b; 20 | 21 | private: 22 | char c; 23 | }; 24 | 25 | Foo::Foo(int a, int b, char c) 26 | { 27 | this->a = a; 28 | this->b = b; 29 | this->c = c; 30 | } 31 | 32 | int main() 33 | { 34 | Foo *fooA = new Foo(1, 2, 'a'); 35 | Foo *fooB = new Foo(3, 4, 'b'); 36 | Foo *fooarr[] = {fooA, fooB}; 37 | cout << "!!!Hello World!!!" << endl; // STOP HERE 38 | cout << "!!!Hello World Again!!!" << endl; 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /src/util/standardEscape.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2022 Kichwa Coders Canada, Inc. and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | // Rewrite the argument escaping whitespace, quotes and backslash 12 | export function standardEscape(arg: string, needQuotes = true): string { 13 | let result = ''; 14 | for (const char of arg) { 15 | if (char === '\\' || char === '"') { 16 | result += '\\'; 17 | } 18 | if (char == ' ') { 19 | needQuotes = true; 20 | } 21 | result += char; 22 | } 23 | if (needQuotes) { 24 | result = `"${result}"`; 25 | } 26 | return result; 27 | } 28 | -------------------------------------------------------------------------------- /src/events/continuedEvent.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { Event } from '@vscode/debugadapter'; 11 | import { DebugProtocol } from '@vscode/debugprotocol'; 12 | 13 | export class ContinuedEvent 14 | extends Event 15 | implements DebugProtocol.ContinuedEvent 16 | { 17 | public body: { 18 | threadId: number; 19 | allThreadsContinued?: boolean; 20 | }; 21 | 22 | constructor(threadId: number, allThreadsContinued = false) { 23 | super('continued'); 24 | 25 | this.body = { 26 | threadId, 27 | allThreadsContinued, 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/web/processManagers/GDBServerWebProcessManager.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { TargetLaunchRequestArguments } from '../../types/session'; 11 | import { IGDBServerProcessManager, IStdioProcess } from '../../types/gdb'; 12 | 13 | export class GDBServerWebProcessManager implements IGDBServerProcessManager { 14 | public async start( 15 | _requestArgs: TargetLaunchRequestArguments 16 | ): Promise { 17 | throw new Error('Method not implemented yet!'); 18 | } 19 | public async stop(): Promise { 20 | throw new Error('Method not implemented yet!'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/web/factories/GDBServerFactory.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { 11 | TargetAttachRequestArguments, 12 | TargetLaunchRequestArguments, 13 | } from '../../types/session'; 14 | import { IGDBServerFactory, IGDBServerProcessManager } from '../../types/gdb'; 15 | import { GDBServerWebProcessManager } from '../processManagers/GDBServerWebProcessManager'; 16 | 17 | export class GDBServerFactory implements IGDBServerFactory { 18 | async createGDBServerManager( 19 | _args: TargetLaunchRequestArguments | TargetAttachRequestArguments 20 | ): Promise { 21 | return new GDBServerWebProcessManager(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/mi/target.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 QNX Software Systems and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { IGDBBackend } from '../types/gdb'; 11 | import { MIResponse } from './base'; 12 | 13 | export function sendTargetAttachRequest( 14 | gdb: IGDBBackend, 15 | params: { 16 | pid: string; 17 | } 18 | ): Promise { 19 | return gdb.sendCommand(`-target-attach ${params.pid}`); 20 | } 21 | 22 | export function sendTargetSelectRequest( 23 | gdb: IGDBBackend, 24 | params: { 25 | type: string; 26 | parameters: string[]; 27 | } 28 | ): Promise { 29 | return gdb.sendCommand( 30 | `-target-select ${params.type} ${params.parameters.join(' ')}` 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/desktop/factories/GDBServerFactory.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { GDBServerFileSystemProcessManager } from '../processManagers/GDBServerFileSystemProcessManager'; 11 | import { 12 | TargetAttachRequestArguments, 13 | TargetLaunchRequestArguments, 14 | } from '../../types/session'; 15 | import { IGDBServerFactory, IGDBServerProcessManager } from '../../types/gdb'; 16 | 17 | export class GDBServerFactory implements IGDBServerFactory { 18 | async createGDBServerManager( 19 | _args: TargetLaunchRequestArguments | TargetAttachRequestArguments 20 | ): Promise { 21 | return new GDBServerFileSystemProcessManager(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/native/pty.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 Ericsson and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import { File } from './file'; 12 | export { File }; 13 | 14 | interface PtyHandles { 15 | master_fd: number; 16 | slave_name: string; 17 | } 18 | 19 | /** 20 | * Represents the master-side of a pseudo-terminal master/slave pair. 21 | */ 22 | export class Pty extends File { 23 | public readonly slave_name: string; 24 | 25 | constructor() { 26 | // eslint-disable-next-line @typescript-eslint/no-require-imports 27 | const pty = require('../../build/Release/pty.node'); 28 | const handles = pty.create_pty() as PtyHandles; 29 | super(handles.master_fd); 30 | this.slave_name = handles.slave_name; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/events/stoppedEvent.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2019 Arm Ltd. and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { Event } from '@vscode/debugadapter'; 11 | import { DebugProtocol } from '@vscode/debugprotocol'; 12 | 13 | export class StoppedEvent extends Event implements DebugProtocol.StoppedEvent { 14 | public body: { 15 | reason: string; 16 | threadId?: number; 17 | allThreadsStopped?: boolean; 18 | }; 19 | 20 | constructor(reason: string, threadId: number, allThreadsStopped = false) { 21 | super('stopped'); 22 | 23 | this.body = { 24 | reason, 25 | allThreadsStopped, 26 | }; 27 | 28 | if (typeof threadId === 'number') { 29 | this.body.threadId = threadId; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/web/GDBDebugSession.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { DebugSession, logger } from '@vscode/debugadapter'; 11 | import { GDBDebugSessionBase } from '../gdb/GDBDebugSessionBase'; 12 | import { GDBBackendFactory } from './factories/GDBBackendFactory'; 13 | import { IGDBBackendFactory } from '../types/gdb'; 14 | 15 | export class GDBDebugSession extends GDBDebugSessionBase { 16 | constructor(backendFactory?: IGDBBackendFactory) { 17 | super(backendFactory || new GDBBackendFactory()); 18 | this.logger = logger; 19 | } 20 | 21 | /** 22 | * Main entry point 23 | */ 24 | public static run(debugSession: typeof DebugSession) { 25 | DebugSession.run(debugSession); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/native/scoped_fd.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 Ericsson and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | #include 11 | 12 | /** 13 | * Own a file descriptor, close it on scope exit. 14 | */ 15 | class scoped_fd { 16 | public: 17 | explicit scoped_fd(int fd) : m_fd(fd) {} 18 | 19 | scoped_fd(const scoped_fd &other) = delete; 20 | scoped_fd &operator=(const scoped_fd &other) = delete; 21 | 22 | bool operator==(int other) { return m_fd == other; } 23 | 24 | ~scoped_fd() { close(); } 25 | 26 | void close() { 27 | if (m_fd >= 0) { 28 | ::close(m_fd); 29 | m_fd = -1; 30 | } 31 | } 32 | 33 | int release() { 34 | int fd(m_fd); 35 | m_fd = -1; 36 | return fd; 37 | } 38 | 39 | int get() const { return m_fd; } 40 | 41 | private: 42 | int m_fd; 43 | }; 44 | -------------------------------------------------------------------------------- /src/util/getGdbCwd.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2022 Kichwa Coders Canada, Inc. and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { existsSync } from 'fs'; 11 | import { dirname } from 'path'; 12 | 13 | /** 14 | * Calculate the CWD that should be used to launch gdb based on the program 15 | * being debugged or the explicitly set cwd in the launch arguments. 16 | * 17 | * Note that launchArgs.program is optional here in preparation for 18 | * debugging where no program is specified. See #262 19 | * 20 | * @param launchArgs Launch Arguments to compute GDB cwd from 21 | * @returns effective cwd to use 22 | */ 23 | export function getGdbCwd(launchArgs: { 24 | program?: string; 25 | cwd?: string; 26 | }): string { 27 | const cwd = 28 | launchArgs.cwd || 29 | (launchArgs.program && existsSync(launchArgs.program) 30 | ? dirname(launchArgs.program) 31 | : process.cwd()); 32 | return existsSync(cwd) ? cwd : process.cwd(); 33 | } 34 | -------------------------------------------------------------------------------- /src/integration-tests/stop.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 QNX Software Systems and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { CdtDebugClient } from './debugClient'; 11 | import { fillDefaults, standardBeforeEach, testProgramsDir } from './utils'; 12 | import { expect } from 'chai'; 13 | import * as path from 'path'; 14 | 15 | describe('stop', async () => { 16 | let dc: CdtDebugClient; 17 | 18 | beforeEach(async () => { 19 | dc = await standardBeforeEach(); 20 | }); 21 | 22 | afterEach(async () => { 23 | await dc.stop(); 24 | }); 25 | 26 | it('handles segv', async function () { 27 | await dc.launchRequest( 28 | fillDefaults(this.test, { 29 | program: path.join(testProgramsDir, 'segv'), 30 | }) 31 | ); 32 | await dc.configurationDoneRequest(); 33 | const stoppedEvent = await dc.waitForEvent('stopped'); 34 | expect(stoppedEvent.body.reason).to.eq('SIGSEGV'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/web/processManagers/GDBWebProcessManager.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { 11 | AttachRequestArguments, 12 | LaunchRequestArguments, 13 | TargetLaunchRequestArguments, 14 | } from '../../types/session'; 15 | import { IGDBProcessManager, IStdioProcess } from '../../types/gdb'; 16 | 17 | export class GDBWebProcessManager implements IGDBProcessManager { 18 | public async getVersion( 19 | _requestArgs?: 20 | | LaunchRequestArguments 21 | | AttachRequestArguments 22 | | undefined 23 | ): Promise { 24 | throw new Error('Method not implemented yet!'); 25 | } 26 | public async start( 27 | _requestArgs: TargetLaunchRequestArguments 28 | ): Promise { 29 | throw new Error('Method not implemented yet!'); 30 | } 31 | public async stop(): Promise { 32 | throw new Error('Method not implemented yet!'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'pty', 5 | 'sources': ['src/native/pty.cc'], 6 | 'conditions': [ 7 | ['OS=="win"', { 'defines': ['WINDOWS', 'NAPI_CPP_EXCEPTIONS'] }], 8 | ['OS=="mac"', { 'defines': ['MAC'] }], 9 | ['OS=="linux"', { 'defines': ['LINUX'] }], 10 | ], 11 | # https://github.com/nodejs/node/blob/master/doc/api/n-api.md#n-api-version-matrix 12 | 'defines': ['NAPI_VERSION=4'], 13 | }, 14 | ], 15 | 'target_defaults': { 16 | # https://github.com/nodejs/node-addon-api/blob/master/doc/setup.md#installation-and-usage 17 | # Setup N-API C++ wrappers: 18 | 'include_dirs': ["= 0. 22 | let cmd = '-interpreter-exec'; 23 | if ( 24 | params.frameRef?.threadId !== undefined && 25 | params.frameRef.threadId >= 0 26 | ) { 27 | cmd += ` --thread ${params.frameRef.threadId}`; 28 | } 29 | if ( 30 | params.frameRef?.frameId !== undefined && 31 | params.frameRef.frameId >= 0 32 | ) { 33 | cmd += ` --frame ${params.frameRef.frameId}`; 34 | } 35 | cmd += ` console "${params.command}"`; 36 | return gdb.sendCommand(cmd); 37 | } 38 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/MultiThreadRunControl.cc: -------------------------------------------------------------------------------- 1 | #include "Thread.h" 2 | #include 3 | #include "Sleep.h" 4 | 5 | // Set a breakpoint here so that both threads stop. 6 | void firstBreakpoint(long id) 7 | { 8 | printf("First breakpoint method from thread %ld\n", id); 9 | } 10 | 11 | struct PrintHelloArgs { 12 | int thread_id; 13 | }; 14 | 15 | static ThreadRet THREAD_CALL_CONV PrintHello(void *void_arg) { 16 | struct PrintHelloArgs *args = (struct PrintHelloArgs *) void_arg; 17 | int thread_id = args->thread_id; 18 | 19 | firstBreakpoint(thread_id); // Stop a first time 20 | 21 | SLEEP(1); // Keep state running a little 22 | 23 | firstBreakpoint(thread_id); // Stop a second time 24 | 25 | SLEEP(3); // Resuming past this will give us a running thread 26 | 27 | return THREAD_DEFAULT_RET; 28 | } 29 | 30 | int main(int argc, char *argv[]) 31 | { 32 | ThreadHandle thread; 33 | struct PrintHelloArgs args; 34 | args.thread_id = 1; // Break at main will stop here: we have a single thread stopped 35 | 36 | SLEEP(1); // When resuming past here, we have a single thread running 37 | 38 | int ret = StartThread(PrintHello, &args, &thread); 39 | if (!ret) { 40 | printf("Error: failed to start thread.\n"); 41 | return 1; 42 | } 43 | 44 | firstBreakpoint(0); 45 | 46 | SLEEP(1); // Resuming past this will make this thread run, while we stop the second thread 47 | 48 | SLEEP(3); // Resuming past this will make this thread run, while we also run the second thread 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/vars_globals.c: -------------------------------------------------------------------------------- 1 | /*** Data Types for testing ***/ 2 | typedef struct structWithArray { 3 | int a; 4 | int b; 5 | char char_array[sizeof("char_array")]; 6 | } STRUCT_WITH_ARRAY; 7 | 8 | typedef struct childStruct { 9 | int x; 10 | int y; 11 | } CHILD_STRUCT; 12 | 13 | typedef struct parentStruct { 14 | int m; 15 | float n; 16 | CHILD_STRUCT child; 17 | CHILD_STRUCT children[2]; 18 | } PARENT_STRUCT; 19 | 20 | /*** Global variables for testing, volatile to avoid optimizing them out ***/ 21 | 22 | volatile STRUCT_WITH_ARRAY s0 = { 23 | 1, 24 | 2, 25 | "char_array" 26 | }; 27 | 28 | volatile STRUCT_WITH_ARRAY *p_s0 = &s0; 29 | 30 | volatile PARENT_STRUCT s1 = { 31 | 10, 32 | 3.14f, 33 | { 4, 5 }, 34 | { { 6, 7 }, { 8, 9 } } 35 | }; 36 | 37 | volatile PARENT_STRUCT *p_s1 = &s1; 38 | 39 | int main() 40 | { 41 | // Struct with array 42 | volatile STRUCT_WITH_ARRAY *p_s0_local = &s0; 43 | unsigned long long s0_address = (unsigned long long)&s0; 44 | s0.a *= 10; // INITIAL_STOP 45 | s0.b *= 2; 46 | p_s0_local->a += 12; 47 | p_s0_local->b--; 48 | // Parent-child struct 49 | volatile PARENT_STRUCT *p_s1_local = &s1; 50 | unsigned long long s1_address = (unsigned long long)&s1; 51 | s1.m += 5; 52 | s1.n *= 2.0f; 53 | s1.child.x += 10; 54 | s1.child.y += 20; 55 | s1.children[0].x += 30; 56 | s1.children[0].y += 40; 57 | s1.children[1].x += 50; 58 | s1.children[1].y += 60; 59 | return 0; // RETURN 60 | } 61 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/ThreadPthread.h: -------------------------------------------------------------------------------- 1 | #ifndef THREADPTHREAD_H 2 | #define THREADPTHREAD_H 3 | 4 | #include 5 | #include 6 | 7 | /* Thread functions */ 8 | 9 | static int StartThread(ThreadFunc func, void *arg, ThreadHandle *handle) { 10 | return pthread_create(handle, NULL, func, arg) == 0; 11 | } 12 | 13 | static int JoinThread(ThreadHandle handle, ThreadRet *ret) 14 | { 15 | return pthread_join(handle, ret) == 0; 16 | } 17 | 18 | 19 | /* Barrier functions */ 20 | 21 | static int ThreadBarrierInit(ThreadBarrier *barrier, unsigned int count) 22 | { 23 | return pthread_barrier_init(barrier, NULL, count) == 0; 24 | } 25 | 26 | static int ThreadBarrierDestroy(ThreadBarrier *barrier) 27 | { 28 | return pthread_barrier_destroy(barrier) == 0; 29 | } 30 | 31 | static int ThreadBarrierWait(ThreadBarrier *barrier) 32 | { 33 | int ret = pthread_barrier_wait(barrier); 34 | return ret == 0 || ret == PTHREAD_BARRIER_SERIAL_THREAD; 35 | } 36 | 37 | static int ThreadSemaphoreInit(ThreadSemaphore *sem, unsigned int count) 38 | { 39 | return sem_init(sem, 0, count) == 0; 40 | } 41 | 42 | static int ThreadSemaphoreTake(ThreadSemaphore *sem) 43 | { 44 | return sem_wait(sem) == 0; 45 | } 46 | 47 | static int ThreadSemaphorePut(ThreadSemaphore *sem) 48 | { 49 | return sem_post(sem) == 0; 50 | } 51 | 52 | static int ThreadSemaphoreDestroy(ThreadSemaphore *sem) 53 | { 54 | return sem_destroy(sem) == 0; 55 | } 56 | 57 | static int ThreadSetName(const char *name) 58 | { 59 | return prctl(PR_SET_NAME, name, 0, 0, 0) == 0; 60 | } 61 | 62 | #endif // THREADPTHREAD_H 63 | -------------------------------------------------------------------------------- /src/mi/thread.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 QNX Software Systems and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { IGDBBackend } from '../types/gdb'; 11 | import { MIFrameInfo, MIResponse } from './base'; 12 | 13 | export interface MIThreadInfo { 14 | id: string; 15 | targetId: string; 16 | details?: string; 17 | name?: string; 18 | state: string; 19 | frame?: MIFrameInfo; 20 | core?: string; 21 | } 22 | 23 | export interface MIThreadInfoResponse extends MIResponse { 24 | threads: MIThreadInfo[]; 25 | 'current-thread-id': string; 26 | } 27 | 28 | export function sendThreadInfoRequest( 29 | gdb: IGDBBackend, 30 | params: { 31 | threadId?: string; 32 | } 33 | ): Promise { 34 | let command = '-thread-info'; 35 | if (params.threadId) { 36 | command += ` ${params.threadId}`; 37 | } 38 | return gdb.sendCommand(command); 39 | } 40 | 41 | export interface MIThreadSelectResponse extends MIResponse { 42 | 'new-thread-id': string; 43 | frame: MIFrameInfo; 44 | } 45 | 46 | export function sendThreadSelectRequest( 47 | gdb: IGDBBackend, 48 | params: { 49 | threadId: number; 50 | } 51 | ): Promise { 52 | return gdb.sendCommand(`-thread-select ${params.threadId}`); 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/report.yml: -------------------------------------------------------------------------------- 1 | # To make sure we don't give unnecessary permissions to the workflow that runs when PRs 2 | # are built, we split the build and report back of test results into two workflows. 3 | # This is based on documention of https://github.com/marketplace/actions/junit-report-action 4 | # with the slight complication that we need to collect both Windows and Ubuntu results 5 | # and combine them together. 6 | name: Report PR test results 7 | on: 8 | workflow_run: 9 | workflows: [build-pr] 10 | types: [completed] 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | permissions: 17 | checks: write 18 | 19 | jobs: 20 | checks: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Download Test Report 24 | uses: dawidd6/action-download-artifact@v7 25 | with: 26 | name: test-results-ubuntu 27 | path: ubuntu 28 | workflow: ${{ github.event.workflow.id }} 29 | run_id: ${{ github.event.workflow_run.id }} 30 | - name: Download Test Report 31 | uses: dawidd6/action-download-artifact@v7 32 | with: 33 | name: test-results-windows 34 | path: windows 35 | workflow: ${{ github.event.workflow.id }} 36 | run_id: ${{ github.event.workflow_run.id }} 37 | - name: Publish Test Report 38 | uses: mikepenz/action-junit-report@v5 39 | with: 40 | commit: ${{github.event.workflow_run.head_sha}} 41 | report_paths: '**/*.xml' 42 | fail_on_failure: true 43 | require_tests: true 44 | check_name: PR Test Results (Windows and Ubuntu) 45 | -------------------------------------------------------------------------------- /src/util/getGdbVersion.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2022 Kichwa Coders Canada, Inc. and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { execFile } from 'child_process'; 11 | import { promisify } from 'util'; 12 | import { createEnvValues } from './createEnvValues'; 13 | import { parseGdbVersionOutput } from './parseGdbVersionOutput'; 14 | 15 | /** 16 | * This method actually launches 'gdb --version' to determine the version of 17 | * the GDB that is being used. 18 | * 19 | * @param gdbPath the path to the GDB executable to be called 20 | * @return the detected version of GDB at gdbPath 21 | */ 22 | export async function getGdbVersion( 23 | gdbPath: string, 24 | gdbCwd?: string, 25 | environment?: Record 26 | ): Promise { 27 | const gdbEnvironment = environment 28 | ? createEnvValues(process.env, environment) 29 | : process.env; 30 | const { stdout, stderr } = await promisify(execFile)( 31 | gdbPath, 32 | ['--version'], 33 | { cwd: gdbCwd, env: gdbEnvironment } 34 | ); 35 | 36 | const gdbVersion = parseGdbVersionOutput(stdout); 37 | if (!gdbVersion) { 38 | throw new Error( 39 | `Failed to get version number from GDB. GDB returned:\nstdout:\n${stdout}\nstderr:\n${stderr}` 40 | ); 41 | } 42 | return gdbVersion; 43 | } 44 | -------------------------------------------------------------------------------- /src/web/factories/GDBBackendFactory.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { Logger, logger } from '@vscode/debugadapter/lib/logger'; 11 | import { 12 | IGDBBackend, 13 | IGDBBackendFactory, 14 | IGDBProcessManager, 15 | } from '../../types/gdb'; 16 | import { 17 | AttachRequestArguments, 18 | LaunchRequestArguments, 19 | } from '../../types/session'; 20 | import { GDBBackend } from '../../gdb/GDBBackend'; 21 | import { GDBDebugSessionBase } from '../../gdb/GDBDebugSessionBase'; 22 | import { GDBWebProcessManager } from '../processManagers/GDBWebProcessManager'; 23 | 24 | export class GDBBackendFactory implements IGDBBackendFactory { 25 | logger: Logger; 26 | constructor() { 27 | this.logger = logger; 28 | } 29 | 30 | async createGDBManager( 31 | _session: GDBDebugSessionBase, 32 | _args: LaunchRequestArguments | AttachRequestArguments 33 | ): Promise { 34 | return new GDBWebProcessManager(); 35 | } 36 | 37 | async createBackend( 38 | _session: GDBDebugSessionBase, 39 | manager: IGDBProcessManager, 40 | _args: LaunchRequestArguments | AttachRequestArguments, 41 | name?: string 42 | ): Promise { 43 | return new GDBBackend(manager, name); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | # Notices for Eclipse CDT.cloud 2 | 3 | This content is produced and maintained by the Eclipse CDT.cloud project. 4 | 5 | * Project home: https://projects.eclipse.org/projects/ecd.cdt.cloud 6 | 7 | ## Trademarks 8 | 9 | Eclipse CDT.cloud is a trademark of the Eclipse Foundation. 10 | 11 | ## Copyright 12 | 13 | All content is the property of the respective authors or their employers. For 14 | more information regarding authorship of content, please consult the listed 15 | source code repository logs. 16 | 17 | ## Declared Project Licenses 18 | 19 | This program and the accompanying materials are made available under the terms 20 | of the Eclipse Public License v. 2.0 which is available at 21 | https://www.eclipse.org/legal/epl-2.0. 22 | 23 | SPDX-License-Identifier: EPL-2.0 24 | 25 | ## Source Code 26 | 27 | The project maintains the following source code repositories: 28 | 29 | * https://github.com/eclipse-cdt-cloud/cdt-cloud 30 | * https://github.com/eclipse-cdt-cloud/cdt-cloud-blueprint 31 | * https://github.com/eclipse-cdt-cloud/cdt-amalgamator 32 | * https://github.com/eclipse-cdt-cloud/cdt-gdb-vscode 33 | * https://github.com/eclipse-cdt-cloud/cdt-gdb-adapter 34 | 35 | ## Third-party Content 36 | 37 | This project leverages the following third party content. 38 | 39 | None 40 | 41 | ## Cryptography 42 | 43 | Content may contain encryption software. The country in which you are currently 44 | may have restrictions on the import, possession, and use, and/or re-export to 45 | another country, of encryption software. BEFORE using any encryption software, 46 | please check the country's laws, regulations and policies concerning the import, 47 | possession, or use, and re-export of encryption software, to see if this is 48 | permitted. 49 | -------------------------------------------------------------------------------- /src/integration-tests/GDBBackend.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2019 QNX Software Systems and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import { expect } from 'chai'; 12 | import { LaunchRequestArguments } from '../types/session'; 13 | import { GDBBackend } from '../gdb/GDBBackend'; 14 | import { GDBFileSystemProcessManager } from '../desktop/processManagers/GDBFileSystemProcessManager'; 15 | 16 | describe('GDB Backend Test Suite', function () { 17 | let gdb: GDBBackend; 18 | 19 | beforeEach(async function () { 20 | gdb = new GDBBackend(new GDBFileSystemProcessManager()); 21 | const args: LaunchRequestArguments = { 22 | program: 'foo', 23 | }; 24 | await gdb.spawn(args); 25 | }); 26 | 27 | afterEach(function () { 28 | gdb.sendGDBExit(); 29 | }); 30 | 31 | it('can read a value from -gdb-show', async function () { 32 | const response = await gdb.sendGDBShow('width'); 33 | expect(response.value).to.be.a('string'); 34 | expect(Number(response.value)).to.be.not.equal(NaN); 35 | expect(Number(response.value)).to.be.greaterThan(0); 36 | }); 37 | 38 | it('can set a value using -gdb-set', async function () { 39 | await gdb.sendGDBSet('width 88'); 40 | const response = await gdb.sendGDBShow('width'); 41 | expect(response.value).to.equal('88'); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/integration-tests/pause.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2022 Kichwa Coders Canada Inc. and others. 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { CdtDebugClient } from './debugClient'; 11 | import { 12 | standardBeforeEach, 13 | testProgramsDir, 14 | gdbAsync, 15 | isRemoteTest, 16 | fillDefaults, 17 | } from './utils'; 18 | import * as path from 'path'; 19 | import * as os from 'os'; 20 | 21 | describe('pause', async () => { 22 | let dc: CdtDebugClient; 23 | 24 | beforeEach(async () => { 25 | dc = await standardBeforeEach(); 26 | }); 27 | 28 | afterEach(async () => { 29 | await dc.stop(); 30 | }); 31 | 32 | it('can be paused', async function () { 33 | if (os.platform() === 'win32' && (!isRemoteTest || !gdbAsync)) { 34 | // win32 host can only pause remote + mi-async targets 35 | this.skip(); 36 | } 37 | await dc.launchRequest( 38 | fillDefaults(this.test, { 39 | program: path.join(testProgramsDir, 'loopforever'), 40 | }) 41 | ); 42 | await dc.configurationDoneRequest(); 43 | const waitForStopped = dc.waitForEvent('stopped'); 44 | const threads = await dc.threadsRequest(); 45 | const pr = dc.pauseRequest({ threadId: threads.body.threads[0].id }); 46 | await Promise.all([pr, waitForStopped]); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to CDT GDB Debug Adapter 2 | 3 | Thanks for your interest in this project, part of the [Eclipse C/C++ Development Tools project](https://projects.eclipse.org/projects/ecd.cdt.cloud) 4 | 5 | The source code can be found in the following repository: https://github.com/eclipse-cdt-cloud/cdt-gdb-adapter 6 | 7 | ## Project description 8 | 9 | This is an implementation of the Debug Adapter Protocol for gdb. 10 | It is loosely based on the Eclipse CDT MI layer. 11 | We are at least learning from it. 12 | 13 | ## Build Instructions 14 | 15 | See build instructions in the [Readme](README.md#building) and details on testing in 16 | the [test readme](src/integration-tests/README.md). Further information on building, 17 | including CI information, download locations, etc can be found in the 18 | [Wiki](https://github.com/eclipse-cdt/cdt-infra/wiki) 19 | 20 | ## Eclipse Contributor Agreement 21 | 22 | Before your contribution can be accepted by the project team contributors must 23 | electronically sign the Eclipse Contributor Agreement (ECA). 24 | 25 | - http://www.eclipse.org/legal/ECA.php 26 | 27 | Commits that are provided by non-committers must have a Signed-off-by field in 28 | the footer indicating that the author is aware of the terms by which the 29 | contribution has been provided to the project. The non-committer must 30 | additionally have an Eclipse Foundation account and must have a signed Eclipse 31 | Contributor Agreement (ECA) on file. 32 | 33 | For more information, please see the Eclipse Committer Handbook: 34 | https://www.eclipse.org/projects/handbook/#resources-commit 35 | 36 | ## Contact 37 | 38 | Contact the project developers via the project's "dev" list. 39 | 40 | - https://dev.eclipse.org/mailman/listinfo/cdt-cloud-dev 41 | -------------------------------------------------------------------------------- /src/integration-tests/stopGDBServer.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2019 Kichwa Coders and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import * as path from 'path'; 12 | import { 13 | TargetLaunchRequestArguments, 14 | TargetLaunchArguments, 15 | } from '../types/session'; 16 | import { CdtDebugClient } from './debugClient'; 17 | import { fillDefaults, standardBeforeEach, testProgramsDir } from './utils'; 18 | 19 | describe('stop gdbserver', function () { 20 | let dc: CdtDebugClient; 21 | const emptyProgram = path.join(testProgramsDir, 'empty'); 22 | const emptySrc = path.join(testProgramsDir, 'empty.c'); 23 | 24 | beforeEach(async function () { 25 | dc = await standardBeforeEach('debugTargetAdapter.js'); 26 | }); 27 | 28 | afterEach(async function () { 29 | const e = dc.waitForOutputEvent('server', 'gdbserver stopped\n'); 30 | await dc.stop(); 31 | await e; 32 | }); 33 | 34 | it('do something', async function () { 35 | await dc.hitBreakpoint( 36 | fillDefaults(this.test, { 37 | program: emptyProgram, 38 | target: { 39 | type: 'remote', 40 | serverParameters: [':0', emptyProgram], 41 | } as TargetLaunchArguments, 42 | } as TargetLaunchRequestArguments), 43 | { 44 | path: emptySrc, 45 | line: 3, 46 | } 47 | ); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /src/constants/gdb.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2025 Arm Ltd. and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | /** 12 | * GDB MI and GDB CLI commands considered to cause 13 | * a thread/target resume. 14 | */ 15 | export const RESUME_COMMANDS = [ 16 | // GDB MI 17 | '-exec-continue', 18 | '-exec-finish', 19 | '-exec-jump', 20 | '-exec-next', 21 | '-exec-next-instruction', 22 | '-exec-return', 23 | '-exec-run', 24 | '-exec-step', 25 | '-exec-step-instruction', 26 | '-exec-until', 27 | // GDB CLI (initCommands, customResetCommands) 28 | 'advance', 29 | 'continue', 30 | 'fg', 31 | 'c', 32 | 'finish', 33 | 'fin', 34 | 'jump', 35 | 'j', 36 | 'next', 37 | 'n', 38 | 'nexti', 39 | 'ni', 40 | 'run', 41 | 'r', 42 | 'start', 43 | 'starti', 44 | 'step', 45 | 's', 46 | 'stepi', 47 | 'si', 48 | 'until', 49 | 'u', 50 | ]; 51 | 52 | export const SET_HOSTCHARSET_REGEXPS = [ 53 | /-gdb-set\s+(host-charset|charset)\s+.*/, 54 | /-interpreter-exec\s+console\s+"set\s+(host-charset|charset)\s+.*/, 55 | /set\s+(host-charset|charset)\s+.*/, 56 | ]; 57 | 58 | export const SET_ALL_CHARSET_REGEXPS = [ 59 | /-gdb-set\s+(charset|host-charset|target-charset|target-wide-charset)\s+.*/, 60 | /-interpreter-exec\s+console\s+"set\s+(charset|host-charset|target-charset|target-wide-charset)\s+.*/, 61 | /set\s+(charset|host-charset|target-charset|target-wide-charset)\s+.*/, 62 | ]; 63 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/Thread.h: -------------------------------------------------------------------------------- 1 | #ifndef Thread_h 2 | #define Thread_h 3 | 4 | /* 5 | * This file provides a simple and incomplete platform compatibility 6 | * layer for thread-related operations. This should help avoiding 7 | * platform dependent code in tests. 8 | */ 9 | 10 | 11 | /* First, include type defintions necessary to define the public API. */ 12 | #ifndef __MINGW32__ 13 | #include "ThreadPthreadTypes.h" 14 | #else // __MINGW32__ 15 | #include "ThreadWindowsTypes.h" 16 | #endif 17 | 18 | 19 | /* 20 | * Type of callback functions for threads 21 | * 22 | * Note: if you don't care about the return value of the thread, you can 23 | * return THREAD_DEFAULT_RET instead, which is defined according to the 24 | * platform type. 25 | * 26 | * Otherwise, you'll need to have some #ifdefs in your test code in order 27 | * to return different value types depending on the platform. 28 | */ 29 | typedef ThreadRet (THREAD_CALL_CONV *ThreadFunc)(void *); 30 | 31 | 32 | /* Public API */ 33 | static int StartThread(ThreadFunc func, void *arg, ThreadHandle *handle); 34 | static int JoinThread(ThreadHandle handle, ThreadRet *ret); 35 | static int ThreadBarrierInit(ThreadBarrier *barrier, unsigned int count); 36 | static int ThreadBarrierDestroy(ThreadBarrier *barrier); 37 | static int ThreadBarrierWait(ThreadBarrier *barrier); 38 | static int ThreadSemaphoreInit(ThreadSemaphore *sem, unsigned int count); 39 | static int ThreadSemaphoreTake(ThreadSemaphore *sem); 40 | static int ThreadSemaphorePut(ThreadSemaphore *sem); 41 | static int ThreadSemaphoreDestroy(ThreadSemaphore *sem); 42 | static int ThreadSetName(const char *name); 43 | 44 | 45 | /* Then, include the implemention of the API. */ 46 | #ifndef __MINGW32__ 47 | #include "ThreadPthread.h" 48 | #else // __MINGW32__ 49 | #include "ThreadWindows.h" 50 | #endif 51 | 52 | #endif // Thread_h 53 | -------------------------------------------------------------------------------- /src/util/calculateMemoryOffset.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import { isHexString } from './isHexString'; 12 | 13 | /** 14 | * This method calculates the memory offset arithmetics on string hexadecimal address value 15 | * 16 | * @param address 17 | * Reference address to perform the operation for example '0x0000FF00', 'main', 'main+200' 18 | * @param offset 19 | * Offset (in bytes) to be applied to the reference location before disassembling. Can be negative. 20 | * @return 21 | * Returns the calculated address. Keeping the address length same. 22 | */ 23 | export const calculateMemoryOffset = ( 24 | address: string, 25 | offset: string | number | bigint 26 | ): string => { 27 | if (isHexString(address)) { 28 | const addressLength = address.length - 2; 29 | const newAddress = BigInt(address) + BigInt(offset); 30 | if (newAddress < 0) { 31 | return `(0x${'0'.padStart(addressLength, '0')})${newAddress}`; 32 | } 33 | return `0x${newAddress.toString(16).padStart(addressLength, '0')}`; 34 | } else { 35 | const addrParts = /^([^+-]*)([+-]\d+)?$/g.exec(address); 36 | const addrReference = addrParts?.[1]; 37 | const addrOffset = BigInt(addrParts?.[2] ?? 0); 38 | const calcOffset = BigInt(offset) + addrOffset; 39 | return `${addrReference}${calcOffset < 0 ? '-' : '+'}${ 40 | calcOffset < 0 ? -calcOffset : calcOffset 41 | }`; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/util/createEnvValues.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2022 Kichwa Coders Canada, Inc. and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { platform } from 'os'; 11 | 12 | /** 13 | * This method is providing an automatic operation to including new variables to process.env. 14 | * Method is not injecting the new variables to current thread, rather it is returning a new 15 | * object with included parameters. 16 | * 17 | * @param source 18 | * Source environment variables to include. 19 | * @param valuesToMerge 20 | * Key-Value dictionary to include. 21 | * @return 22 | * New environment variables dictionary. 23 | */ 24 | export function createEnvValues( 25 | source: NodeJS.ProcessEnv, 26 | valuesToMerge: Record 27 | ): NodeJS.ProcessEnv { 28 | const findTarget = (obj: any, key: string) => { 29 | if (platform() === 'win32') { 30 | return ( 31 | Object.keys(obj).find( 32 | (i) => 33 | i.localeCompare(key, undefined, { 34 | sensitivity: 'accent', 35 | }) === 0 36 | ) || key 37 | ); 38 | } 39 | return key; 40 | }; 41 | const result = { ...source }; 42 | for (const [key, value] of Object.entries(valuesToMerge)) { 43 | const target = findTarget(result, key); 44 | if (value === null) { 45 | delete result[target]; 46 | } else { 47 | result[target] = value; 48 | } 49 | } 50 | return result; 51 | } 52 | -------------------------------------------------------------------------------- /src/gdb/errors.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2025 Arm Ltd. and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | class GDBBackendError extends Error { 12 | constructor( 13 | error: Error, 14 | name: string = 'GDBBackendError', 15 | public backend = '' 16 | ) { 17 | super(error.message); 18 | this.name = name; 19 | this.stack = error.stack; 20 | } 21 | } 22 | 23 | export class GDBError extends GDBBackendError { 24 | constructor(error: Error, backend = '') { 25 | super(error, 'GDBError', backend); 26 | Object.setPrototypeOf(this, new.target.prototype); 27 | } 28 | } 29 | 30 | export class GDBThreadRunning extends GDBBackendError { 31 | constructor(error: Error, backend = '') { 32 | super(error, 'GDBThreadRunning', backend); 33 | Object.setPrototypeOf(this, new.target.prototype); 34 | } 35 | } 36 | 37 | export class GDBUnknownResponse extends GDBBackendError { 38 | constructor(error: Error, backend = '') { 39 | super(error, 'GDBUnknownResponse', backend); 40 | Object.setPrototypeOf(this, new.target.prototype); 41 | } 42 | } 43 | 44 | export class GDBCommandCancelled extends GDBBackendError { 45 | constructor(error: Error, backend = '') { 46 | super(error, 'GDBCommandCancelled', backend); 47 | Object.setPrototypeOf(this, new.target.prototype); 48 | } 49 | } 50 | 51 | export class GDBPipeError extends GDBBackendError { 52 | constructor(error: Error, backend = '') { 53 | super(error, 'GDBPipeError', backend); 54 | Object.setPrototypeOf(this, new.target.prototype); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/gdb/common.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { DebugProtocol } from '@vscode/debugprotocol'; 11 | 12 | export class ThreadWithStatus implements DebugProtocol.Thread { 13 | id: number; 14 | name: string; 15 | running: boolean; 16 | lastRunToken: string | undefined; 17 | constructor(id: number, name: string, running: boolean) { 18 | this.id = id; 19 | this.name = name; 20 | this.running = running; 21 | } 22 | } 23 | 24 | export function hexToBase64(hex: string): string { 25 | // The buffer will ignore incomplete bytes (unpaired digits), so we need to catch that early 26 | if (hex.length % 2 !== 0) { 27 | throw new Error('Received memory with incomplete bytes.'); 28 | } 29 | const base64 = Buffer.from(hex, 'hex').toString('base64'); 30 | // If the hex input includes characters that are not hex digits, Buffer.from() will return an empty buffer, and the base64 string will be empty. 31 | if (base64.length === 0 && hex.length !== 0) { 32 | throw new Error('Received ill-formed hex input: ' + hex); 33 | } 34 | return base64; 35 | } 36 | 37 | export function base64ToHex(base64: string): string { 38 | const buffer = Buffer.from(base64, 'base64'); 39 | // The caller likely passed in a value that left dangling bits that couldn't be assigned to a full byte and so 40 | // were ignored by Buffer. We can't be sure what the client thought they wanted to do with those extra bits, so fail here. 41 | if (buffer.length === 0 || !buffer.toString('base64').startsWith(base64)) { 42 | throw new Error('Received ill-formed base64 input: ' + base64); 43 | } 44 | return buffer.toString('hex'); 45 | } 46 | -------------------------------------------------------------------------------- /src/integration-tests/stderr.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2023 Kichwa Coders Canada Inc. 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import * as path from 'path'; 12 | import * as os from 'os'; 13 | import { LaunchRequestArguments } from '../types/session'; 14 | import { CdtDebugClient } from './debugClient'; 15 | import { 16 | fillDefaults, 17 | getScopes, 18 | isRemoteTest, 19 | standardBeforeEach, 20 | testProgramsDir, 21 | } from './utils'; 22 | 23 | describe('stderr', function () { 24 | let dc: CdtDebugClient; 25 | const program = path.join(testProgramsDir, 'stderr'); 26 | const source = path.join(testProgramsDir, 'stderr.c'); 27 | 28 | beforeEach(async function () { 29 | dc = await standardBeforeEach(); 30 | }); 31 | 32 | afterEach(async function () { 33 | await dc.stop(); 34 | }); 35 | 36 | it('receives stderr from inferior as output events', async function () { 37 | if (isRemoteTest) { 38 | // remote tests the inferior stdout/err comes out the remote end, so 39 | // no output events from the adapter 40 | this.skip(); 41 | } 42 | 43 | await dc.hitBreakpoint( 44 | fillDefaults(this.test, { 45 | program: program, 46 | } as LaunchRequestArguments), 47 | { 48 | path: source, 49 | line: 5, 50 | } 51 | ); 52 | 53 | const stderr = dc.waitForOutputEvent( 54 | 'stderr', 55 | `STDERR Here I am${os.platform() === 'win32' ? '\r\n' : '\n'}` 56 | ); 57 | 58 | const scope = await getScopes(dc); 59 | await Promise.all([ 60 | dc.continueRequest({ threadId: scope.thread.id }), 61 | stderr, 62 | ]); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/native/file.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2020 Ericsson and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import * as fs from 'fs'; 12 | import { Duplex, Readable, Writable } from 'stream'; 13 | 14 | export class File { 15 | protected _duplex: Duplex; 16 | 17 | get reader(): Readable { 18 | return this._duplex; 19 | } 20 | 21 | get writer(): Writable { 22 | return this._duplex; 23 | } 24 | 25 | constructor(public fd: number) { 26 | // eslint-disable-next-line @typescript-eslint/no-this-alias 27 | const _this = this; 28 | this._duplex = new Duplex({ 29 | read(size) { 30 | fs.read( 31 | fd, 32 | Buffer.alloc(size), 33 | 0, 34 | size, 35 | null, 36 | (err, bytesRead, buffer) => { 37 | if (err) { 38 | console.error(fd, err.message); 39 | this.push(null); 40 | } else { 41 | this.push(buffer.slice(0, bytesRead)); 42 | } 43 | } 44 | ); 45 | }, 46 | write(chunk, encoding, callback) { 47 | const buffer = Buffer.isBuffer(chunk) 48 | ? chunk 49 | : Buffer.from(chunk, encoding); 50 | fs.write(fd, buffer, (err, _written, _buffer) => { 51 | callback(err); 52 | }); 53 | }, 54 | destroy(err, callback) { 55 | fs.close(fd, callback); 56 | _this.fd = -1; 57 | }, 58 | }); 59 | } 60 | 61 | destroy() { 62 | this._duplex.destroy(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/integration-tests/stepout.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2019 Arm and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import * as path from 'path'; 12 | import { expect } from 'chai'; 13 | import { CdtDebugClient } from './debugClient'; 14 | import { 15 | standardBeforeEach, 16 | testProgramsDir, 17 | getScopes, 18 | fillDefaults, 19 | } from './utils'; 20 | 21 | describe('stepout', async function () { 22 | let dc: CdtDebugClient; 23 | 24 | beforeEach(async function () { 25 | dc = await standardBeforeEach(); 26 | await dc.launchRequest( 27 | fillDefaults(this.currentTest, { 28 | program: path.join(testProgramsDir, 'functions'), 29 | }) 30 | ); 31 | }); 32 | 33 | afterEach(async () => { 34 | await dc.stop(); 35 | }); 36 | 37 | it('should step out from staticfunc1 to main', async () => { 38 | await dc.setBreakpointsRequest({ 39 | source: { 40 | name: 'functions.c', 41 | path: path.join(testProgramsDir, 'functions.c'), 42 | }, 43 | breakpoints: [ 44 | { 45 | column: 1, 46 | line: 3, 47 | }, 48 | ], 49 | }); 50 | await Promise.all([ 51 | dc.waitForEvent('stopped'), 52 | dc.configurationDoneRequest(), 53 | ]); 54 | const scope = await getScopes(dc); 55 | const [stepOutEvent] = await Promise.all([ 56 | dc.waitForEvent('stopped'), 57 | dc.stepOutRequest({ 58 | threadId: scope.thread.id, 59 | }), 60 | ]); 61 | expect(stepOutEvent.body.reason).eq('step'); 62 | const stackTrace = await dc.stackTraceRequest({ 63 | threadId: stepOutEvent.body.threadId, 64 | }); 65 | expect(stackTrace.body.stackFrames[0].name.includes('main')); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/integration-tests/terminated.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2023 Kichwa Coders Canada Inc. and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | // import { expect } from 'chai'; 12 | import * as path from 'path'; 13 | import { LaunchRequestArguments } from '../types/session'; 14 | import { CdtDebugClient } from './debugClient'; 15 | import { 16 | fillDefaults, 17 | getScopes, 18 | standardBeforeEach, 19 | testProgramsDir, 20 | } from './utils'; 21 | 22 | describe('terminated', function () { 23 | let dc: CdtDebugClient; 24 | const emptyProgram = path.join(testProgramsDir, 'empty'); 25 | const emptySrc = path.join(testProgramsDir, 'empty.c'); 26 | 27 | beforeEach(async function () { 28 | dc = await standardBeforeEach(); 29 | }); 30 | 31 | afterEach(async function () { 32 | await dc.stop(); 33 | }); 34 | 35 | it('terminated event arrives after continuing after a breakpoint', async function () { 36 | await dc.hitBreakpoint( 37 | fillDefaults(this.test, { 38 | program: emptyProgram, 39 | } as LaunchRequestArguments), 40 | { 41 | path: emptySrc, 42 | line: 3, 43 | } 44 | ); 45 | 46 | await Promise.all([ 47 | dc.waitForEvent('terminated'), 48 | dc.continueRequest({ threadId: (await getScopes(dc)).thread.id }), 49 | ]); 50 | }); 51 | 52 | it('terminated event arrives on a short run', async function () { 53 | await Promise.all([ 54 | dc.waitForEvent('terminated'), 55 | 56 | dc 57 | .waitForEvent('initialized') 58 | .then((_event) => dc.configurationDoneRequest()), 59 | 60 | dc.launch( 61 | fillDefaults(this.test, { 62 | program: emptyProgram, 63 | } as LaunchRequestArguments) 64 | ), 65 | ]); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/native/forked-file.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2020 Ericsson and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import * as assert from 'assert'; 12 | import { ChildProcess, spawn } from 'child_process'; 13 | import { openSync } from 'fs'; 14 | import { Readable, Writable } from 'stream'; 15 | import { File } from './file'; 16 | 17 | /** 18 | * Open and read a file from a subprocess (mode `r+` only). 19 | * 20 | * This is useful when opening a ptmx/pts pair at the same time. 21 | * When both files are opened by the same process, closing does not correctly release 22 | * the read callbacks, leaving node hanging at exit. 23 | * 24 | * Instead, we open one of the two files in a subprocess in order to kill it once done, 25 | * which will properly release read callbacks for some reason. 26 | */ 27 | export class ForkedFile { 28 | protected _fork: ChildProcess; 29 | 30 | get reader(): Readable { 31 | if (!this._fork.stdout) { 32 | throw new Error('Forked process missing stdout'); 33 | } 34 | return this._fork.stdout; 35 | } 36 | 37 | get writer(): Writable { 38 | if (!this._fork.stdin) { 39 | throw new Error('Forked process missing stdin'); 40 | } 41 | return this._fork.stdin; 42 | } 43 | 44 | constructor(readonly path: string) { 45 | // To write to the file, we'll write to stdin. 46 | // To read from the file, we'll read from stdout. 47 | this._fork = spawn( 48 | process.execPath, 49 | [...process.execArgv, __filename, path], 50 | { 51 | stdio: ['pipe', 'pipe', 'inherit'], 52 | } 53 | ); 54 | } 55 | 56 | destroy(): void { 57 | if (this._fork.exitCode === null && this._fork.signalCode === null) { 58 | this._fork.kill(); 59 | } 60 | } 61 | } 62 | 63 | const [, script, path] = process.argv; 64 | // Check if we are forked: 65 | if (script === __filename) { 66 | assert(typeof path === 'string', 'argv[2] must be a string'); 67 | const file = new File(openSync(path, 'r+')); 68 | process.stdin.pipe(file.writer); 69 | file.reader.pipe(process.stdout); 70 | } 71 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // eslint.config.mjs 2 | import chaiFriendly from 'eslint-plugin-chai-friendly'; 3 | import js from '@eslint/js'; 4 | import tseslint from 'typescript-eslint'; 5 | import prettier from 'eslint-config-prettier'; 6 | import globals from 'globals'; 7 | 8 | const jsLanguageOptions = { 9 | globals: { 10 | // Add any specific globals you need 11 | ...globals.node, 12 | }, 13 | }; 14 | 15 | const tsLanguageOptions = { 16 | parser: tseslint.parser, 17 | parserOptions: { 18 | project: './tsconfig.json', 19 | }, 20 | ecmaVersion: 2015, 21 | sourceType: 'module', 22 | globals: { 23 | // Add any specific globals you need 24 | ...globals.node, 25 | }, 26 | }; 27 | 28 | export default [ 29 | { 30 | ignores: [ 31 | // don't ever lint node_modules 32 | '**/node_modules/**', 33 | // don't lint build output (make sure 34 | // it's set to your correct build folder name) 35 | 'dist/**', 36 | ], 37 | }, 38 | { 39 | // Javascript files like build and config scripts 40 | files: ['**/*.js'], 41 | ...js.configs.recommended, 42 | languageOptions: jsLanguageOptions, 43 | }, 44 | // Recommended rules config array must only apply 45 | // to Typescript files, not plain Javascript, and 46 | // should use same language options as 47 | ...tseslint.configs.recommended.map((config) => ({ 48 | files: ['**/*.ts', '**/*.tsx'], 49 | ...config, 50 | languageOptions: tsLanguageOptions, 51 | })), 52 | { 53 | files: ['**/*.ts', '**/*.tsx'], 54 | languageOptions: tsLanguageOptions, 55 | plugins: { 56 | '@typescript-eslint': tseslint.plugin, 57 | 'chai-friendly': chaiFriendly, 58 | }, 59 | rules: { 60 | ...prettier.rules, 61 | '@typescript-eslint/no-explicit-any': 'off', 62 | 'no-unused-vars': 'off', 63 | '@typescript-eslint/no-unused-vars': [ 64 | 'warn', 65 | { 66 | argsIgnorePattern: '^_', 67 | varsIgnorePattern: '^_', 68 | caughtErrorsIgnorePattern: '^_', 69 | }, 70 | ], 71 | // For backwards compatibility with older recommended rules 72 | // in eslint/typescript-eslint, based on existing "disable" 73 | // directives 74 | '@typescript-eslint/no-non-null-assertion': 'error', 75 | // Turn off the "no-unused-expressions" default rules... 76 | 'no-unused-expressions': 'off', 77 | '@typescript-eslint/no-unused-expressions': 'off', 78 | // ... and turn on the chai-friendly version 79 | 'chai-friendly/no-unused-expressions': 'error', 80 | }, 81 | }, 82 | ]; 83 | -------------------------------------------------------------------------------- /src/mi/symbols.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2025 QNX Software Systems, Arm Limited and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { IGDBBackend } from '../types/gdb'; 11 | 12 | export interface MIDebugSymbol { 13 | line: string; 14 | name: string; 15 | type: string; 16 | description: string; 17 | } 18 | 19 | export interface MINonDebugSymbol { 20 | address: string; 21 | name: string; 22 | } 23 | 24 | export interface MISymbolInfoDebug { 25 | filename: string; 26 | fullname: string; 27 | symbols: MIDebugSymbol[]; 28 | } 29 | 30 | export interface MISymbolInfoResponse { 31 | symbols: { 32 | debug: MISymbolInfoDebug[]; 33 | nondebug: MINonDebugSymbol[]; 34 | }; 35 | } 36 | 37 | export function sendSymbolInfoVars( 38 | gdb: IGDBBackend, 39 | params?: { 40 | name?: string; 41 | type?: string; 42 | max_result?: string; 43 | non_debug?: boolean; 44 | } 45 | ): Promise { 46 | let command = '-symbol-info-variables'; 47 | if (params) { 48 | if (params.name) { 49 | command += ` --name ${params.name}`; 50 | } 51 | if (params.type) { 52 | command += ` --type ${params.type}`; 53 | } 54 | if (params.max_result) { 55 | command += ` --max-result ${params.max_result}`; 56 | } 57 | if (params.non_debug) { 58 | command += ' --include-nondebug'; 59 | } 60 | } 61 | return gdb.sendCommand(command); 62 | } 63 | 64 | export function sendSymbolInfoFunctions( 65 | gdb: IGDBBackend, 66 | params?: { 67 | name?: string; 68 | type?: string; 69 | max_result?: string; 70 | non_debug?: boolean; 71 | } 72 | ): Promise { 73 | let command = '-symbol-info-functions'; 74 | if (params) { 75 | if (params.name) { 76 | command += ` --name ${params.name}`; 77 | } 78 | if (params.type) { 79 | command += ` --type ${params.type}`; 80 | } 81 | if (params.max_result) { 82 | command += ` --max-result ${params.max_result}`; 83 | } 84 | if (params.non_debug) { 85 | command += ' --include-nondebug'; 86 | } 87 | } 88 | return gdb.sendCommand(command); 89 | } 90 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/Makefile: -------------------------------------------------------------------------------- 1 | BINS = empty empty\ space evaluate vars vars_cpp vars_env vars_globals mem segv count disassemble functions loopforever MultiThread MultiThreadRunControl stderr bug275-测试 cwd.exe stepping 2 | 3 | .PHONY: all 4 | all: $(BINS) 5 | 6 | CC = gcc 7 | CXX = g++ 8 | LINK = $(CC) -o $@ $^ 9 | LINK_CXX = $(CXX) -o $@ $^ 10 | 11 | # Don't try to use pthread on Windows 12 | # The OS environment variable exists on Windows 13 | ifneq ($(OS),Windows_NT) 14 | CC += -pthread 15 | CXX += -pthread 16 | LINK += -pthread 17 | LINK_CXX += -pthread 18 | endif 19 | 20 | functions: functions.o functions_other.o 21 | $(LINK) 22 | 23 | count: count.o count_other.o count\ space.o 24 | $(CC) -o "count" count.o count_other.o "count space.o" 25 | 26 | count\ space.o: count\ space.c 27 | $(CC) -c "count space.c" -g3 -O0 28 | 29 | # the cwd tests need to move around source and binary 30 | # in ways that mean gdb cannot find the source automatically 31 | # in the normal $cdir:$cwd and needs additional information 32 | # to be provided 33 | # debug-prefix-map used like this is to put an "incorrect" 34 | # DW_AT_comp_dir in the debug info 35 | cwd.o: cwd.c 36 | $(CC) -fdebug-prefix-map="$(CURDIR)"=. -c $< -g3 -O0 37 | 38 | cwd.exe: cwd.o 39 | $(LINK) -fdebug-prefix-map="$(CURDIR)"=. 40 | mkdir -p Debug 41 | cp cwd.exe Debug 42 | mkdir -p EmptyDir 43 | 44 | empty: empty.o 45 | $(LINK) 46 | 47 | # This is a workaround because make on Windows (on GitHub actions?) doesn't like 48 | # it otherwise 49 | bug275-测试.o: empty.c 50 | cp empty.c bug275-测试.c 51 | $(CC) -c bug275-测试.c -g3 -O0 52 | 53 | bug275-测试: bug275-测试.o 54 | $(LINK) 55 | 56 | evaluate: evaluate.o 57 | $(LINK) 58 | 59 | mem: mem.o 60 | $(LINK) 61 | 62 | disassemble: disassemble.o 63 | $(LINK) 64 | 65 | vars: vars.o 66 | $(LINK) 67 | 68 | vars_env: vars_env.o 69 | $(LINK) 70 | 71 | vars_cpp: vars_cpp.o 72 | $(LINK_CXX) 73 | 74 | vars_globals: vars_globals.o 75 | $(LINK) 76 | 77 | segv: segv.o 78 | $(LINK) 79 | 80 | loopforever: loopforever.o 81 | $(LINK) 82 | 83 | MultiThread: MultiThread.o 84 | $(LINK_CXX) 85 | 86 | MultiThreadRunControl: MultiThreadRunControl.o 87 | $(LINK_CXX) 88 | 89 | stderr: stderr.o 90 | $(LINK) 91 | 92 | stepping: stepping.o 93 | $(LINK) 94 | 95 | %.o: %.c 96 | $(CC) -c $< -g3 -O0 97 | 98 | %.o: %.cpp 99 | $(CXX) -c $< -g3 -O0 100 | 101 | %.o: %.cc 102 | $(CXX) -c $< -g3 -O0 103 | 104 | empty\ space: empty\ space.o 105 | $(CC) -o "empty space" "empty space.o" 106 | 107 | empty\ space.o: empty\ space.c 108 | $(CC) -c "empty space.c" -g3 -O0 109 | 110 | .PHONY: clean 111 | clean: 112 | rm -f $(BINS) *.o 113 | -------------------------------------------------------------------------------- /src/util/sendResponseWithTimeout.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2025 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | export interface SendResponseWithTimeoutParameters { 12 | /** The main function to be called during the execution */ 13 | execute: () => Promise | void; 14 | /** The independent response which should be sent after the operation or the timeout */ 15 | onResponse: () => Promise | void; 16 | /** Error handling block. It is optional, if it is not implemented the error will not be catch be thrown */ 17 | onError?: ( 18 | error: unknown, 19 | args: { hasResponseSent: boolean } 20 | ) => Promise | void; 21 | /** Timeout to send the response in milliseconds, timer disabled if timeout is negative (e.g. `-1`) */ 22 | timeout: number; 23 | } 24 | 25 | /** 26 | * `sendResponseWithTimeout` method is handling an early response invocation for independent response and execution 27 | * logics after the defined timeout duration, in order to provide the response to the IDE user interface. 28 | * Designed to be used in the debug session operations such as step-in, step-out, step-over. 29 | * 30 | * @param parameters Should be specified in `SendResponseWithTimeoutParameters` structure. Provide the 31 | * `execute`, `onResponse`, `onError` and `timeout` values. 32 | */ 33 | export const sendResponseWithTimeout = async ( 34 | parameters: SendResponseWithTimeoutParameters 35 | ) => { 36 | const { execute, onResponse, onError, timeout } = parameters; 37 | let responseSent = false; 38 | const timer = 39 | timeout >= 0 40 | ? setTimeout(async () => { 41 | responseSent = true; 42 | await onResponse(); 43 | }, timeout) 44 | : undefined; 45 | try { 46 | await execute(); 47 | clearTimeout(timer); 48 | if (!responseSent) { 49 | await onResponse(); 50 | } 51 | } catch (err) { 52 | // Important to clear timeout if error catched from the `execute` logic. 53 | // But it is also safe to call clearTimeout twice if error thrown in the `onResponse` logic. 54 | clearTimeout(timer); 55 | 56 | if (!onError) { 57 | throw err; 58 | } 59 | await onError(err, { 60 | hasResponseSent: responseSent, 61 | }); 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /src/desktop/factories/GDBBackendFactory.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { Logger, logger } from '@vscode/debugadapter/lib/logger'; 11 | import { GDBFileSystemProcessManager } from '../processManagers/GDBFileSystemProcessManager'; 12 | import { GDBPTYProcessManager } from '../processManagers/GDBPTYProcessManager'; 13 | import { compareVersions } from '../../util/compareVersions'; 14 | import { 15 | IGDBBackend, 16 | IGDBBackendFactory, 17 | IGDBProcessManager, 18 | } from '../../types/gdb'; 19 | import { 20 | AttachRequestArguments, 21 | LaunchRequestArguments, 22 | } from '../../types/session'; 23 | import { GDBBackend } from '../../gdb/GDBBackend'; 24 | import { GDBDebugSessionBase } from '../../gdb/GDBDebugSessionBase'; 25 | 26 | export class GDBBackendFactory implements IGDBBackendFactory { 27 | logger: Logger; 28 | constructor() { 29 | this.logger = logger; 30 | } 31 | 32 | async createGDBManager( 33 | session: GDBDebugSessionBase, 34 | args: LaunchRequestArguments | AttachRequestArguments 35 | ): Promise { 36 | const defaultProcessManager = new GDBFileSystemProcessManager(); 37 | if (args.openGdbConsole) { 38 | const version = await defaultProcessManager.getVersion(args); 39 | if (!session.supportsGdbConsole) { 40 | logger.warn( 41 | 'cdt-gdb-adapter: openGdbConsole is not supported on this platform' 42 | ); 43 | } else if (compareVersions(version, '7.12') < 0) { 44 | logger.warn( 45 | `cdt-gdb-adapter: new-ui command not detected (${ 46 | args.gdb || 'gdb' 47 | })` 48 | ); 49 | } else { 50 | logger.verbose( 51 | 'cdt-gdb-adapter: spawning gdb console in client terminal' 52 | ); 53 | return new GDBPTYProcessManager(session); 54 | } 55 | } 56 | return defaultProcessManager; 57 | } 58 | 59 | async createBackend( 60 | session: GDBDebugSessionBase, 61 | manager: IGDBProcessManager, 62 | _args: LaunchRequestArguments | AttachRequestArguments, 63 | name?: string 64 | ): Promise { 65 | return new GDBBackend(manager, name); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/util/compareVersions.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2022 Kichwa Coders Canada, Inc. and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | /** 12 | * Compares two version numbers. 13 | * Returns -1, 0, or 1 if v1 is less than, equal to, or greater than v2, respectively. 14 | * @param v1 The first version 15 | * @param v2 The second version 16 | * @return -1, 0, or 1 if v1 is less than, equal to, or greater than v2, respectively. 17 | */ 18 | export function compareVersions(v1: string, v2: string): number { 19 | const v1Parts = v1.split(/\./); 20 | const v2Parts = v2.split(/\./); 21 | for (let i = 0; i < v1Parts.length && i < v2Parts.length; i++) { 22 | const v1PartValue = parseInt(v1Parts[i], 10); 23 | const v2PartValue = parseInt(v2Parts[i], 10); 24 | 25 | if (isNaN(v1PartValue) || isNaN(v2PartValue)) { 26 | // Non-integer part, ignore it 27 | continue; 28 | } 29 | if (v1PartValue > v2PartValue) { 30 | return 1; 31 | } else if (v1PartValue < v2PartValue) { 32 | return -1; 33 | } 34 | } 35 | 36 | // If we get here is means the versions are still equal 37 | // but there could be extra parts to examine 38 | 39 | if (v1Parts.length < v2Parts.length) { 40 | // v2 has extra parts, which implies v1 is a lower version (e.g., v1 = 7.9 v2 = 7.9.1) 41 | // unless each extra part is 0, in which case the two versions are equal (e.g., v1 = 7.9 v2 = 7.9.0) 42 | for (let i = v1Parts.length; i < v2Parts.length; i++) { 43 | const v2PartValue = parseInt(v2Parts[i], 10); 44 | 45 | if (isNaN(v2PartValue)) { 46 | // Non-integer part, ignore it 47 | continue; 48 | } 49 | if (v2PartValue != 0) { 50 | return -1; 51 | } 52 | } 53 | } 54 | if (v1Parts.length > v2Parts.length) { 55 | // v1 has extra parts, which implies v1 is a higher version (e.g., v1 = 7.9.1 v2 = 7.9) 56 | // unless each extra part is 0, in which case the two versions are equal (e.g., v1 = 7.9.0 v2 = 7.9) 57 | for (let i = v2Parts.length; i < v1Parts.length; i++) { 58 | const v1PartValue = parseInt(v1Parts[i], 10); 59 | 60 | if (isNaN(v1PartValue)) { 61 | // Non-integer part, ignore it 62 | continue; 63 | } 64 | if (v1PartValue != 0) { 65 | return 1; 66 | } 67 | } 68 | } 69 | 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /src/mi/base.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 QNX Software Systems and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { IGDBBackend } from '../types/gdb'; 11 | 12 | export interface MIResponse { 13 | _class: string; 14 | } 15 | 16 | export interface MIShowResponse extends MIResponse { 17 | value?: string; 18 | } 19 | 20 | /** Response to -list-features and -list-target-features */ 21 | export interface MIFeaturesResponse extends MIResponse { 22 | features?: string[]; 23 | } 24 | 25 | export abstract class MIRequest { 26 | public abstract send(backend: IGDBBackend): Promise; 27 | } 28 | 29 | // Shared types 30 | /** See {@link https://sourceware.org/gdb/current/onlinedocs/gdb.html/GDB_002fMI-Breakpoint-Information.html this documentation} for additional details. */ 31 | export interface MIBreakpointInfo { 32 | disp: string; 33 | enabled: 'y' | 'n'; 34 | number: string; 35 | type: string; 36 | addr?: string; 37 | addr_flags?: string; 38 | at?: string; 39 | 'catch-type'?: string; 40 | cond?: string; 41 | enable?: string; 42 | 'evaluated-by'?: 'host' | 'target'; 43 | file?: string; // docs say filname, but that is wrong 44 | frame?: string; 45 | fullname?: string; 46 | func?: string; 47 | ignore?: string; 48 | inferior?: string; 49 | installed?: 'y' | 'n'; 50 | line?: string; 51 | locations?: MILocation[]; 52 | mask?: string; 53 | 'original-location'?: string; 54 | pass?: string; 55 | pending?: string; 56 | script?: string; 57 | task?: string; 58 | thread?: string; 59 | 'thread-groups'?: string[]; 60 | times: string; 61 | what?: string; 62 | // TODO there are a few more fields here 63 | } 64 | 65 | /** See {@link https://sourceware.org/gdb/current/onlinedocs/gdb.html/GDB_002fMI-Breakpoint-Information.html this documentation} for additional details. */ 66 | export interface MILocation { 67 | number: string; 68 | enabled: 'y' | 'n' | 'N'; 69 | addr: string; 70 | addr_flags?: string; 71 | func?: string; 72 | file?: string; 73 | fullname?: string; 74 | line?: string; 75 | 'thread-groups': string[]; 76 | } 77 | 78 | export interface MIFrameInfo { 79 | level: string; 80 | func?: string; 81 | addr?: string; 82 | file?: string; 83 | fullname?: string; 84 | line?: string; 85 | from?: string; 86 | } 87 | 88 | export interface MIVariableInfo { 89 | name: string; 90 | value?: string; 91 | type?: string; 92 | } 93 | 94 | export interface MIRegisterValueInfo { 95 | number: string; 96 | value: string; 97 | } 98 | -------------------------------------------------------------------------------- /src/integration-tests/logpoints.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2019 Arm and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import { join } from 'path'; 12 | import { expect } from 'chai'; 13 | import { CdtDebugClient } from './debugClient'; 14 | import { fillDefaults, standardBeforeEach, testProgramsDir } from './utils'; 15 | 16 | describe('logpoints', async () => { 17 | let dc: CdtDebugClient; 18 | 19 | beforeEach(async function () { 20 | dc = await standardBeforeEach(); 21 | 22 | await dc.launchRequest( 23 | fillDefaults(this.currentTest, { 24 | program: join(testProgramsDir, 'count'), 25 | }) 26 | ); 27 | }); 28 | 29 | afterEach(async () => { 30 | await dc.stop(); 31 | }); 32 | 33 | it('hits a logpoint', async () => { 34 | const logMessage = 'log message'; 35 | 36 | await dc.setBreakpointsRequest({ 37 | source: { 38 | name: 'count.c', 39 | path: join(testProgramsDir, 'count.c'), 40 | }, 41 | breakpoints: [ 42 | { 43 | column: 1, 44 | line: 4, 45 | logMessage, 46 | }, 47 | ], 48 | }); 49 | await dc.configurationDoneRequest(); 50 | const logEvent = await dc.waitForOutputEvent('console'); 51 | expect(logEvent.body.output).to.eq(logMessage); 52 | }); 53 | 54 | it('supports changing log messages', async () => { 55 | const logMessage = 'log message'; 56 | 57 | await dc.setBreakpointsRequest({ 58 | source: { 59 | name: 'count.c', 60 | path: join(testProgramsDir, 'count.c'), 61 | }, 62 | breakpoints: [ 63 | { 64 | column: 1, 65 | line: 4, 66 | logMessage: 'something uninteresting', 67 | }, 68 | ], 69 | }); 70 | await dc.setBreakpointsRequest({ 71 | source: { 72 | name: 'count.c', 73 | path: join(testProgramsDir, 'count.c'), 74 | }, 75 | breakpoints: [ 76 | { 77 | column: 1, 78 | line: 4, 79 | logMessage, 80 | }, 81 | ], 82 | }); 83 | await dc.configurationDoneRequest(); 84 | const logEvent = await dc.waitForOutputEvent('console'); 85 | expect(logEvent.body.output).to.eq(logMessage); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /src/mi/exec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 QNX Software Systems and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { IGDBBackend } from '../types/gdb'; 11 | import { FrameReference } from '../types/session'; 12 | import { MIResponse } from './base'; 13 | 14 | export function sendExecArguments( 15 | gdb: IGDBBackend, 16 | params: { 17 | arguments: string; 18 | } 19 | ): Promise { 20 | return gdb.sendCommand(`-exec-arguments ${params.arguments}`); 21 | } 22 | 23 | export function sendExecRun(gdb: IGDBBackend) { 24 | return gdb.sendCommand('-exec-run'); 25 | } 26 | 27 | export function sendExecContinue(gdb: IGDBBackend, threadId?: number) { 28 | let command = '-exec-continue'; 29 | if (threadId !== undefined) { 30 | command += ` --thread ${threadId}`; 31 | } 32 | return gdb.sendCommand(command); 33 | } 34 | 35 | export function sendExecNext(gdb: IGDBBackend, threadId?: number) { 36 | let command = '-exec-next'; 37 | if (threadId !== undefined) { 38 | command += ` --thread ${threadId}`; 39 | } 40 | return gdb.sendCommand(command); 41 | } 42 | 43 | export function sendExecNextInstruction(gdb: IGDBBackend, threadId?: number) { 44 | let command = '-exec-next-instruction'; 45 | if (threadId !== undefined) { 46 | command += ` --thread ${threadId}`; 47 | } 48 | return gdb.sendCommand(command); 49 | } 50 | 51 | export function sendExecStep(gdb: IGDBBackend, threadId?: number) { 52 | let command = '-exec-step'; 53 | if (threadId !== undefined) { 54 | command += ` --thread ${threadId}`; 55 | } 56 | return gdb.sendCommand(command); 57 | } 58 | 59 | export function sendExecStepInstruction(gdb: IGDBBackend, threadId?: number) { 60 | let command = '-exec-step-instruction'; 61 | if (threadId !== undefined) { 62 | command += ` --thread ${threadId}`; 63 | } 64 | return gdb.sendCommand(command); 65 | } 66 | 67 | export function sendExecFinish(gdb: IGDBBackend, frameRef: FrameReference) { 68 | let command = '-exec-finish'; 69 | if (frameRef.threadId !== undefined) { 70 | command += ` --thread ${frameRef.threadId}`; 71 | } 72 | if (frameRef.frameId !== undefined) { 73 | command += ` --frame ${frameRef.frameId}`; 74 | } 75 | return gdb.sendCommand(command); 76 | } 77 | 78 | export function sendExecInterrupt(gdb: IGDBBackend, threadId?: number) { 79 | let command = '-exec-interrupt'; 80 | 81 | if (threadId !== undefined) { 82 | command += ` --thread ${threadId}`; 83 | } else { 84 | command += ' --all'; 85 | } 86 | 87 | return gdb.sendCommand(command); 88 | } 89 | -------------------------------------------------------------------------------- /src/desktop/processManagers/GDBFileSystemProcessManagerBase.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { 11 | AttachRequestArguments, 12 | LaunchRequestArguments, 13 | } from '../../types/session'; 14 | import { ChildProcess, spawn } from 'child_process'; 15 | import { createEnvValues } from '../../util/createEnvValues'; 16 | import { existsSync } from 'fs'; 17 | import { dirname } from 'path'; 18 | import { GetPIDType, IStdioProcess } from '../../types/gdb'; 19 | 20 | type ConvertChildProcess = ChildProcess & GetPIDType; 21 | 22 | export class GDBFileSystemProcessManagerBase { 23 | protected proc?: ChildProcess; 24 | protected token = 0; 25 | protected requestArgs?: LaunchRequestArguments | AttachRequestArguments; 26 | 27 | constructor() {} 28 | 29 | protected getEnvironment( 30 | additionalEnvironment?: Record 31 | ): NodeJS.ProcessEnv { 32 | return additionalEnvironment 33 | ? createEnvValues(process.env, additionalEnvironment) 34 | : process.env; 35 | } 36 | 37 | protected getCwd( 38 | requestArgs: LaunchRequestArguments | AttachRequestArguments 39 | ): string { 40 | const cwd = 41 | requestArgs.cwd || 42 | (requestArgs.program && existsSync(requestArgs.program) 43 | ? dirname(requestArgs.program) 44 | : process.cwd()); 45 | return existsSync(cwd) ? cwd : process.cwd(); 46 | } 47 | 48 | public async spawn( 49 | executable: string, 50 | args: string[] | undefined, 51 | options: { 52 | cwd?: string; 53 | additionalEnvironment?: Record; 54 | } 55 | ): Promise { 56 | const env = this.getEnvironment(options.additionalEnvironment); 57 | 58 | this.proc = spawn(executable, args, { env, cwd: options.cwd }); 59 | (this.proc as ConvertChildProcess).getPID = () => this.proc?.pid; 60 | return this.proc as ConvertChildProcess; 61 | } 62 | 63 | public async kill() { 64 | if (!this.proc) { 65 | throw new Error('GDB is not running, nothing to interrupt'); 66 | } 67 | // logger.verbose(`GDB signal: SIGINT to pid ${this.proc.pid}`); 68 | this.proc.kill('SIGINT'); 69 | } 70 | 71 | public onStop( 72 | callback: (code: number | null, signal: NodeJS.Signals | null) => void 73 | ): void { 74 | this.proc?.on('exit', callback); 75 | } 76 | public onError(callback: (err: Error) => void): void { 77 | this.proc?.on('error', callback); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/integration-tests/continues.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 QNX Software Systems and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { CdtDebugClient } from './debugClient'; 11 | import { 12 | standardBeforeEach, 13 | fillDefaults, 14 | testProgramsDir, 15 | getScopes, 16 | gdbNonStop, 17 | } from './utils'; 18 | import { expect, assert } from 'chai'; 19 | import * as path from 'path'; 20 | 21 | describe('continues', async function () { 22 | let dc: CdtDebugClient; 23 | 24 | beforeEach(async function () { 25 | dc = await standardBeforeEach(); 26 | await dc.launchRequest( 27 | fillDefaults(this.currentTest, { 28 | program: path.join(testProgramsDir, 'count'), 29 | }) 30 | ); 31 | }); 32 | 33 | afterEach(async function () { 34 | await dc.stop(); 35 | }); 36 | 37 | it('handles continues single-thread', async function () { 38 | await dc.setBreakpointsRequest({ 39 | source: { 40 | name: 'count.c', 41 | path: path.join(testProgramsDir, 'count.c'), 42 | }, 43 | breakpoints: [ 44 | { 45 | column: 1, 46 | line: 4, 47 | }, 48 | ], 49 | }); 50 | await dc.configurationDoneRequest(); 51 | await dc.waitForEvent('stopped'); 52 | const scope = await getScopes(dc); 53 | const continueResponse = await dc.continueRequest({ 54 | threadId: scope.thread.id, 55 | }); 56 | expect(continueResponse.body.allThreadsContinued).to.eq(!gdbNonStop); 57 | }); 58 | 59 | it('handles async continued events', async function () { 60 | await dc.setBreakpointsRequest({ 61 | source: { 62 | name: 'count.c', 63 | path: path.join(testProgramsDir, 'count.c'), 64 | }, 65 | breakpoints: [ 66 | { 67 | column: 1, 68 | line: 4, 69 | }, 70 | ], 71 | }); 72 | await dc.configurationDoneRequest(); 73 | await dc.waitForEvent('stopped'); 74 | const scope = await getScopes(dc); 75 | dc.send('cdt-gdb-tests/executeCommand', { 76 | command: `-exec-continue --thread ${scope.thread.id}`, 77 | }); 78 | const event = await dc.waitForEvent('continued'); 79 | 80 | assert.deepEqual( 81 | event.body, 82 | gdbNonStop 83 | ? { 84 | threadId: scope.thread.id, 85 | allThreadsContinued: false, 86 | } 87 | : { threadId: 1, allThreadsContinued: true } 88 | ); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /src/native/pty.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2019 Ericsson and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import { expect } from 'chai'; 12 | import { Readable, Writable } from 'stream'; 13 | import { Pty } from '../native/pty'; 14 | import { ForkedFile } from '../native/forked-file'; 15 | 16 | if (process.platform !== 'win32') { 17 | describe('pty creation', function () { 18 | let master: Pty; 19 | let slave: ForkedFile; 20 | 21 | afterEach(function () { 22 | if (slave) { 23 | slave.destroy(); 24 | } 25 | if (master) { 26 | master.destroy(); 27 | } 28 | }); 29 | 30 | it( 31 | 'should be able to open a ptmx/pts pair', 32 | failFast(async function (fail) { 33 | master = new Pty(); 34 | slave = new ForkedFile(master.slave_name); 35 | 36 | let masterBuffer = ''; 37 | let slaveBuffer = ''; 38 | 39 | master.reader.on('error', fail); 40 | slave.reader.on('error', fail); 41 | 42 | master.reader.on( 43 | 'data', 44 | (data) => (masterBuffer += data.toString('utf8')) 45 | ); 46 | slave.reader.on( 47 | 'data', 48 | (data) => (slaveBuffer += data.toString('utf8')) 49 | ); 50 | 51 | await sendAndAwait('master2slave', master.writer, slave.reader); 52 | 53 | expect(masterBuffer).eq(''); 54 | expect(slaveBuffer).eq('master2slave'); 55 | 56 | await sendAndAwait('slave2master', slave.writer, master.reader); 57 | 58 | expect(masterBuffer).eq('slave2master'); 59 | expect(slaveBuffer).eq('master2slave'); 60 | }) 61 | ); 62 | }); 63 | } 64 | 65 | /** 66 | * What goes in should come out. Useful to test PTYs since what we write on `master` should come out of `slave` and vice-versa. 67 | * 68 | * @param str payload 69 | * @param writeTo where to write into 70 | * @param readFrom where to wait for it to come out 71 | */ 72 | function sendAndAwait( 73 | str: string, 74 | writeTo: Writable, 75 | readFrom: Readable 76 | ): Promise { 77 | return new Promise((resolve) => { 78 | readFrom.once('data', () => resolve()); 79 | writeTo.write(str); 80 | }); 81 | } 82 | 83 | /** 84 | * Allows an async function to reject early. 85 | */ 86 | function failFast( 87 | callback: (this: T, fail: (error: Error) => void) => Promise 88 | ): (this: T) => Promise { 89 | let fail!: (error: Error) => void; 90 | const abortPromise = new Promise((_, reject) => { 91 | fail = reject; 92 | }); 93 | return function (this: T) { 94 | return Promise.race([abortPromise, callback.call(this, fail)]); 95 | }; 96 | } 97 | -------------------------------------------------------------------------------- /src/desktop/processManagers/GDBPTYProcessManager.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { IStdioProcess } from '../../types/gdb'; 11 | import { ChildProcess } from 'child_process'; 12 | import { GDBFileSystemProcessManager } from './GDBFileSystemProcessManager'; 13 | import { DebugProtocol } from '@vscode/debugprotocol'; 14 | import { DebugSession } from '@vscode/debugadapter'; 15 | import { 16 | AttachRequestArguments, 17 | LaunchRequestArguments, 18 | } from '../../types/session'; 19 | 20 | export class GDBPTYProcessManager extends GDBFileSystemProcessManager { 21 | protected proc?: ChildProcess; 22 | 23 | constructor(protected session: DebugSession) { 24 | super(); 25 | } 26 | 27 | public async start( 28 | requestArgs: LaunchRequestArguments | AttachRequestArguments 29 | ): Promise { 30 | this.requestArgs = requestArgs; 31 | await this.getVersion(requestArgs); 32 | const gdbPath = requestArgs.gdb || 'gdb'; 33 | const gdbEnvironment = this.getEnvironment(requestArgs.environment); 34 | const gdbCwd = this.getGdbCwd(requestArgs); 35 | 36 | const { Pty } = await import('../../native/pty'); 37 | const pty = new Pty(); 38 | let gdbArgs = [gdbPath, '-ex', `new-ui mi2 ${pty.slave_name}`]; 39 | if (requestArgs.gdbArguments) { 40 | gdbArgs = gdbArgs.concat(requestArgs.gdbArguments); 41 | } 42 | 43 | const response = await new Promise((resolve) => 44 | this.session.sendRequest( 45 | 'runInTerminal', 46 | { 47 | kind: 'integrated', 48 | cwd: gdbCwd, 49 | env: gdbEnvironment, 50 | args: gdbArgs, 51 | } as DebugProtocol.RunInTerminalRequestArguments, 52 | 5000, 53 | resolve 54 | ) 55 | ); 56 | 57 | if (!response.success) { 58 | const message = `could not start the terminal on the client: ${response.message}`; 59 | // logger.error(message); 60 | throw new Error(message); 61 | } 62 | 63 | const item = { 64 | stdout: pty.reader, 65 | stdin: pty.writer, 66 | stderr: null, 67 | getPID: () => undefined, 68 | exitCode: null, 69 | signalCode: null, 70 | kill: () => true, 71 | on: (_event: 'error' | 'exit', _fn: any) => { 72 | return item; 73 | }, 74 | }; 75 | return item; 76 | } 77 | public async stop() { 78 | if (!this.proc) { 79 | throw new Error('GDB is not running, nothing to interrupt'); 80 | } 81 | // logger.verbose(`GDB signal: SIGINT to pid ${this.proc.pid}`); 82 | this.proc.kill('SIGINT'); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/mi/stack.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 QNX Software Systems and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { IGDBBackend } from '../types/gdb'; 11 | import { FrameReference } from '../types/session'; 12 | import { MIFrameInfo, MIResponse, MIVariableInfo } from './base'; 13 | 14 | export interface MIStackInfoDepthResponse extends MIResponse { 15 | depth: string; 16 | } 17 | 18 | export interface MIStackListVariablesResponse extends MIResponse { 19 | variables: MIVariableInfo[]; 20 | } 21 | 22 | export function sendStackInfoDepth( 23 | gdb: IGDBBackend, 24 | params: { 25 | maxDepth: number; 26 | threadId?: number; 27 | } 28 | ): Promise { 29 | let command = '-stack-info-depth'; 30 | if (params.threadId !== undefined) { 31 | command += ` --thread ${params.threadId}`; 32 | } 33 | if (params.maxDepth) { 34 | command += ` ${params.maxDepth}`; 35 | } 36 | return gdb.sendCommand(command); 37 | } 38 | 39 | export function sendStackListFramesRequest( 40 | gdb: IGDBBackend, 41 | params: { 42 | noFrameFilters?: boolean; 43 | lowFrame?: number; 44 | highFrame?: number; 45 | threadId?: number; 46 | } 47 | ): Promise<{ 48 | stack: MIFrameInfo[]; 49 | }> { 50 | let command = '-stack-list-frames'; 51 | if (params.threadId !== undefined) { 52 | command += ` --thread ${params.threadId}`; 53 | } 54 | if (params.noFrameFilters) { 55 | command += ' -no-frame-filters'; 56 | } 57 | if (params.lowFrame !== undefined) { 58 | command += ` ${params.lowFrame}`; 59 | } 60 | if (params.highFrame !== undefined) { 61 | command += ` ${params.highFrame}`; 62 | } 63 | return gdb.sendCommand(command); 64 | } 65 | 66 | export function sendStackSelectFrame( 67 | gdb: IGDBBackend, 68 | params: { 69 | frameNum: number; 70 | } 71 | ): Promise { 72 | return gdb.sendCommand(`-stack-select-frame ${params.frameNum}`); 73 | } 74 | 75 | export function sendStackListVariables( 76 | gdb: IGDBBackend, 77 | params: { 78 | frameRef: FrameReference | undefined; 79 | printValues: 'no-values' | 'all-values' | 'simple-values'; 80 | noFrameFilters?: boolean; 81 | skipUnavailable?: boolean; 82 | } 83 | ): Promise { 84 | let command = '-stack-list-variables'; 85 | if (params.noFrameFilters) { 86 | command += ' --no-frame-filters'; 87 | } 88 | if (params.skipUnavailable) { 89 | command += ' --skip-unavailable'; 90 | } 91 | if (params.frameRef?.threadId !== undefined) { 92 | command += ` --thread ${params.frameRef.threadId}`; 93 | } 94 | if (params.frameRef?.frameId !== undefined) { 95 | command += ` --frame ${params.frameRef.frameId}`; 96 | } 97 | command += ` --${params.printValues}`; 98 | 99 | return gdb.sendCommand(command); 100 | } 101 | -------------------------------------------------------------------------------- /.github/workflows/build-pr.yml: -------------------------------------------------------------------------------- 1 | # This workflow runs on pull requests - it is similar to what happens 2 | # in push workflow, but needs to be split across multiple workflows 3 | # see report.yml for more details 4 | name: build-pr 5 | on: [pull_request] 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | jobs: 10 | Build-on-Ubuntu: 11 | name: Build & Test on Ubuntu 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: '20' 18 | - name: Install GCC & GDB & other build essentials 19 | run: | 20 | sudo apt-get update 21 | sudo apt-get -y install build-essential gcc g++ gdb gdbserver socat 22 | gdb --version 23 | gcc --version 24 | gdbserver --version 25 | - name: Enable ptrace so tests can attach to running processes, see attach.spec.ts 26 | run: | 27 | sudo sysctl kernel.yama.ptrace_scope=0 28 | - name: Build 29 | run: yarn 30 | - name: Verify code formatting is valid 31 | run: | 32 | yarn format-check 33 | yarn lint 34 | - name: Build Test Programs 35 | run: make -C src/integration-tests/test-programs 36 | - name: Test 37 | run: yarn test-ci 38 | - name: Log file artifacts 39 | uses: actions/upload-artifact@v4 40 | if: success() || failure() 41 | with: 42 | name: test-logs-ubuntu 43 | path: test-logs/ 44 | - name: Upload Test Report 45 | uses: actions/upload-artifact@v4 46 | if: success() || failure() 47 | with: 48 | name: test-results-ubuntu 49 | path: 'test-reports/*.xml' 50 | retention-days: 1 51 | - name: Verify no unexpected changes to source tree 52 | run: git diff --exit-code 53 | Build-on-Windows: 54 | name: Build & Test on Windows 55 | runs-on: windows-latest 56 | steps: 57 | - uses: actions/checkout@v4 58 | - uses: actions/setup-node@v4 59 | with: 60 | node-version: '20' 61 | # Install build tools and update PATH through GITHUB_PATH for next steps 62 | - name: Install GCC & GDB & other build essentials 63 | run: | 64 | choco install mingw --version=11.2.0.07112021 65 | $chocoMingwBin = Join-Path $env:ChocolateyInstall 'lib\mingw\tools\install\mingw64\bin' 66 | echo $chocoMingwBin >> $env:GITHUB_PATH 67 | - name: Echo Tool Versions 68 | run: | 69 | gdb --version 70 | gcc --version 71 | gdbserver --version 72 | make --version 73 | - name: Build 74 | run: yarn 75 | - name: Build Test Programs 76 | run: make -C src/integration-tests/test-programs 77 | - name: Use special Mocha settings on Windows tests 78 | run: | 79 | Copy -path .mocharc-windows-ci.json -destination .mocharc.json -verbose 80 | - name: Test 81 | run: yarn test-ci 82 | - name: Log file artifacts 83 | uses: actions/upload-artifact@v4 84 | if: success() || failure() 85 | with: 86 | name: test-logs-windows 87 | path: test-logs/ 88 | - name: Upload Test Report 89 | uses: actions/upload-artifact@v4 90 | if: success() || failure() 91 | with: 92 | name: test-results-windows 93 | path: 'test-reports/*.xml' 94 | retention-days: 1 95 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Server", 8 | "cwd": "${workspaceFolder}", 9 | "program": "${workspaceFolder}/dist/debugAdapter.js", 10 | "args": ["--server=4711"], 11 | "outFiles": ["${workspaceFolder}/dist/**/*.js"] 12 | }, 13 | { 14 | "type": "node", 15 | "request": "launch", 16 | "name": "Server (Target adapter)", 17 | "cwd": "${workspaceFolder}", 18 | "program": "${workspaceFolder}/dist/debugTargetAdapter.js", 19 | "args": ["--server=4711"], 20 | "outFiles": ["${workspaceFolder}/dist/**/*.js"] 21 | }, 22 | { 23 | "type": "node", 24 | "request": "launch", 25 | "name": "Server (Auxiliary GDB mock adapter)", 26 | "cwd": "${workspaceFolder}", 27 | "program": "${workspaceFolder}/dist/integration-tests/mocks/debugAdapters/auxiliaryGdb.js", 28 | "args": ["--server=4711"], 29 | "outFiles": ["${workspaceFolder}/dist/**/*.js"] 30 | }, 31 | { 32 | "type": "node", 33 | "request": "launch", 34 | "name": "Mocha All", 35 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 36 | "args": [ 37 | "--timeout", 38 | "999999", 39 | "--colors", 40 | "${workspaceFolder}/dist/integration-tests/*.spec.js" 41 | ], 42 | "console": "integratedTerminal", 43 | "internalConsoleOptions": "neverOpen" 44 | }, 45 | { 46 | "type": "node", 47 | "request": "launch", 48 | "name": "Mocha Current File", 49 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 50 | "args": [ 51 | "--timeout", 52 | "999999", 53 | "--colors", 54 | "-r", 55 | "ts-node/register", 56 | "${file}" 57 | ], 58 | "console": "integratedTerminal", 59 | "internalConsoleOptions": "neverOpen" 60 | }, 61 | { 62 | "type": "node", 63 | "request": "launch", 64 | "name": "Mocha Current File (non-stop)", 65 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 66 | "args": [ 67 | "--timeout", 68 | "999999", 69 | "--colors", 70 | "-r", 71 | "ts-node/register", 72 | "${file}", 73 | "--test-gdb-non-stop" 74 | ], 75 | "console": "integratedTerminal", 76 | "internalConsoleOptions": "neverOpen" 77 | }, 78 | { 79 | "type": "node", 80 | "request": "launch", 81 | "name": "Mocha Current File (Attach to Server)", 82 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 83 | "args": [ 84 | "--debugserverport", 85 | "4711", 86 | "--timeout", 87 | "999999", 88 | "--colors", 89 | "-r", 90 | "ts-node/register", 91 | "${file}" 92 | ], 93 | "console": "integratedTerminal", 94 | "internalConsoleOptions": "neverOpen" 95 | } 96 | ], 97 | "compounds": [ 98 | { 99 | "name": "Mocha Current File & launch Server", 100 | "configurations": ["Server", "Mocha Current File (Attach to Server)"] 101 | }, 102 | { 103 | "name": "Mocha Current File & launch Target Server", 104 | "configurations": [ 105 | "Server (Target adapter)", 106 | "Mocha Current File (Attach to Server)" 107 | ] 108 | } 109 | ] 110 | } 111 | -------------------------------------------------------------------------------- /src/desktop/processManagers/GDBFileSystemProcessManager.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { 11 | AttachRequestArguments, 12 | LaunchRequestArguments, 13 | } from '../../types/session'; 14 | import { ChildProcess, execFile } from 'child_process'; 15 | import { parseGdbVersionOutput } from '../../util/parseGdbVersionOutput'; 16 | import { promisify } from 'util'; 17 | import { existsSync } from 'fs'; 18 | import { dirname } from 'path'; 19 | import { GDBFileSystemProcessManagerBase } from './GDBFileSystemProcessManagerBase'; 20 | import { IGDBProcessManager, IStdioProcess } from '../../types/gdb'; 21 | 22 | export class GDBFileSystemProcessManager 23 | extends GDBFileSystemProcessManagerBase 24 | implements IGDBProcessManager 25 | { 26 | protected proc?: ChildProcess; 27 | public gdbVersion?: string; 28 | 29 | protected token = 0; 30 | protected requestArgs?: LaunchRequestArguments | AttachRequestArguments; 31 | constructor() { 32 | super(); 33 | } 34 | 35 | protected getGdbCwd( 36 | requestArgs: LaunchRequestArguments | AttachRequestArguments 37 | ): string { 38 | const cwd = 39 | requestArgs.cwd || 40 | (requestArgs.program && existsSync(requestArgs.program) 41 | ? dirname(requestArgs.program) 42 | : process.cwd()); 43 | return existsSync(cwd) ? cwd : process.cwd(); 44 | } 45 | 46 | public async getVersion( 47 | requestArgs?: LaunchRequestArguments | AttachRequestArguments 48 | ): Promise { 49 | if (this.gdbVersion) { 50 | return this.gdbVersion; 51 | } 52 | requestArgs = requestArgs || this.requestArgs; 53 | if (!requestArgs) { 54 | throw new Error(`You need to initialize first!`); 55 | } 56 | const gdbPath = requestArgs.gdb || 'gdb'; 57 | const gdbEnvironment = this.getEnvironment(requestArgs.environment); 58 | const gdbCwd = this.getGdbCwd(requestArgs); 59 | 60 | const { stdout, stderr } = await promisify(execFile)( 61 | gdbPath, 62 | ['--version'], 63 | { cwd: gdbCwd, env: gdbEnvironment } 64 | ); 65 | 66 | const gdbVersion = parseGdbVersionOutput(stdout); 67 | if (!gdbVersion) { 68 | throw new Error( 69 | `Failed to get version number from GDB. GDB returned:\nstdout:\n${stdout}\nstderr:\n${stderr}` 70 | ); 71 | } 72 | this.gdbVersion = gdbVersion; 73 | return gdbVersion; 74 | } 75 | 76 | public async start( 77 | requestArgs: LaunchRequestArguments | AttachRequestArguments 78 | ): Promise { 79 | this.requestArgs = requestArgs; 80 | await this.getVersion(requestArgs); 81 | const gdbPath = this.requestArgs.gdb || 'gdb'; 82 | let gdbArgs = ['--interpreter=mi2']; 83 | if (requestArgs.gdbArguments) { 84 | gdbArgs = gdbArgs.concat(requestArgs.gdbArguments); 85 | } 86 | 87 | const gdbCwd = this.getGdbCwd(requestArgs); 88 | 89 | return this.spawn(gdbPath, gdbArgs, { 90 | cwd: gdbCwd, 91 | additionalEnvironment: requestArgs.environment, 92 | }); 93 | } 94 | public async stop() { 95 | this.kill(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/desktop/processManagers/GDBServerFileSystemProcessManager.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import { ChildProcess, spawn } from 'child_process'; 11 | import { existsSync } from 'fs'; 12 | import { dirname } from 'path'; 13 | import { GDBFileSystemProcessManagerBase } from './GDBFileSystemProcessManagerBase'; 14 | import { TargetLaunchRequestArguments } from '../../types/session'; 15 | import { createEnvValues } from '../../util/createEnvValues'; 16 | import { isProcessActive } from '../../util/processes'; 17 | import { 18 | GetPIDType, 19 | IGDBServerProcessManager, 20 | IStdioProcess, 21 | } from '../../types/gdb'; 22 | 23 | type ConvertedChildProcess = ChildProcess & GetPIDType; 24 | 25 | export class GDBServerFileSystemProcessManager 26 | extends GDBFileSystemProcessManagerBase 27 | implements IGDBServerProcessManager 28 | { 29 | protected proc?: ConvertedChildProcess; 30 | public gdbVersion?: string; 31 | 32 | protected token = 0; 33 | 34 | protected getCwd(requestArgs: TargetLaunchRequestArguments): string { 35 | const cwd = 36 | requestArgs.target?.cwd || 37 | requestArgs.cwd || 38 | (requestArgs.program && existsSync(requestArgs.program) 39 | ? dirname(requestArgs.program) 40 | : process.cwd()); 41 | return existsSync(cwd) ? cwd : process.cwd(); 42 | } 43 | 44 | public async start( 45 | requestArgs: TargetLaunchRequestArguments 46 | ): Promise { 47 | if (requestArgs.target === undefined) { 48 | requestArgs.target = {}; 49 | } 50 | const target = requestArgs.target; 51 | const serverExe = 52 | target.server !== undefined ? target.server : 'gdbserver'; 53 | const serverCwd = this.getCwd(requestArgs); 54 | const serverParams = 55 | target.serverParameters !== undefined 56 | ? target.serverParameters 57 | : requestArgs.program 58 | ? ['--once', ':0', requestArgs.program] 59 | : ['--once', ':0']; 60 | 61 | // this.killGdbServer = target.automaticallyKillServer !== false; 62 | 63 | const gdbEnvironment = requestArgs.environment 64 | ? createEnvValues(process.env, requestArgs.environment) 65 | : process.env; 66 | const serverEnvironment = target.environment 67 | ? createEnvValues(gdbEnvironment, target.environment) 68 | : gdbEnvironment; 69 | 70 | this.proc = spawn(serverExe, serverParams, { 71 | cwd: serverCwd, 72 | env: serverEnvironment, 73 | }) as ConvertedChildProcess; 74 | if (this.proc) { 75 | this.proc.getPID = () => this.proc?.pid; 76 | } 77 | return this.proc; 78 | } 79 | 80 | public async stop(): Promise { 81 | return new Promise((resolve, reject) => { 82 | if (!this.proc || !isProcessActive(this.proc)) { 83 | resolve(); 84 | } else { 85 | this.proc.on('exit', () => { 86 | resolve(); 87 | }); 88 | this.proc?.kill(); 89 | } 90 | setTimeout(() => { 91 | reject(); 92 | }, 1000); 93 | }); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/native/pty.cc: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 Ericsson and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | #include "napi.h" 11 | 12 | #ifdef LINUX 13 | #include "scoped_fd.h" 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | /** 21 | * Takes an error code and throws a pretty JS error such as: 22 | * "function_name: errormsg". 23 | */ 24 | static void _throw_exc_format(const Napi::Env env, int error, 25 | const char *function_name) { 26 | const int ERRMSG_MAX_SIZE = 128; 27 | char errmsg_buffer[ERRMSG_MAX_SIZE]; 28 | char message[ERRMSG_MAX_SIZE]; 29 | #ifdef _GNU_SOURCE 30 | char *errmsg = strerror_r(error, errmsg_buffer, ERRMSG_MAX_SIZE); 31 | snprintf(message, ERRMSG_MAX_SIZE, "%s: %s", function_name, errmsg); 32 | #else 33 | int rc = strerror_r(error, errmsg_buffer, ERRMSG_MAX_SIZE); 34 | if (rc) { 35 | snprintf(message, ERRMSG_MAX_SIZE, "%s", function_name); 36 | } else { 37 | snprintf(message, ERRMSG_MAX_SIZE, "%s: %s", function_name, errmsg_buffer); 38 | } 39 | #endif // _GNU_SOURCE 40 | throw Napi::Error::New(env, message); 41 | } 42 | #endif // LINUX 43 | 44 | static Napi::Value create_pty(const Napi::CallbackInfo &info) { 45 | Napi::Env env = info.Env(); 46 | #ifndef LINUX 47 | // Windows does not supports TTYs. 48 | throw Napi::Error::New(env, ".create_pty() is not supported on this platform"); 49 | #else 50 | // master_fd will be closed on scope exit if an error is thrown. 51 | scoped_fd master_fd(posix_openpt(O_RDWR | O_NOCTTY)); 52 | if (master_fd == -1) { 53 | throw Napi::Error::New(env, "posix_openpt(O_RDWR | O_NOCTTY) failed"); 54 | } 55 | const int SLAVE_NAME_MAX_SIZE = 128; 56 | char slave_name[SLAVE_NAME_MAX_SIZE]; 57 | termios configuration; 58 | int error; 59 | 60 | error = tcgetattr(master_fd.get(), &configuration); 61 | if (error) 62 | _throw_exc_format(env, error, "tcgetattr"); 63 | 64 | // By default, the master tty will be in echo mode, which means that we will 65 | // get what we write back when we read from it. The stream is also line 66 | // buffered by default. Making it raw prevents all this. 67 | // see: man cfmakeraw 68 | cfmakeraw(&configuration); 69 | 70 | error = tcsetattr(master_fd.get(), 0, &configuration); 71 | if (error) 72 | _throw_exc_format(env, error, "tcsetattr"); 73 | 74 | // see: man ptmx 75 | error = ptsname_r(master_fd.get(), slave_name, SLAVE_NAME_MAX_SIZE); 76 | if (error) 77 | _throw_exc_format(env, error, "ptsname_r"); 78 | error = grantpt(master_fd.get()); 79 | if (error) 80 | _throw_exc_format(env, error, "grantpt"); 81 | error = unlockpt(master_fd.get()); 82 | if (error) 83 | _throw_exc_format(env, error, "unlockpt"); 84 | 85 | // We release master_fd for the scoped_fd wrapper to not actually close it, 86 | // as we want to send it to the running JS scripts. 87 | Napi::Object terminal = Napi::Object::New(env); 88 | terminal.Set("master_fd", master_fd.release()); 89 | terminal.Set("slave_name", slave_name); 90 | return terminal; 91 | #endif 92 | } 93 | 94 | static Napi::Object initialize(Napi::Env env, Napi::Object exports) { 95 | exports.Set("create_pty", Napi::Function::New(env, create_pty)); 96 | return exports; 97 | } 98 | 99 | NODE_API_MODULE(NODE_GYP_MODULE_NAME, initialize); 100 | -------------------------------------------------------------------------------- /src/integration-tests/mocks/debugAdapters/dynamicBreakpointOptions.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /********************************************************************* 3 | * Copyright (c) 2023 Renesas Electronics Corporation and others 4 | * 5 | * This program and the accompanying materials are made 6 | * available under the terms of the Eclipse Public License 2.0 7 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 8 | * 9 | * SPDX-License-Identifier: EPL-2.0 10 | *********************************************************************/ 11 | import { logger } from '@vscode/debugadapter/lib/logger'; 12 | import { GDBBackend } from '../../../gdb/GDBBackend'; 13 | import { GDBTargetDebugSession } from '../../../desktop/GDBTargetDebugSession'; 14 | import { 15 | MIBreakpointLocation, 16 | MIBreakpointInsertOptions, 17 | MIBreakpointMode, 18 | } from '../../../mi'; 19 | import { GDBFileSystemProcessManager } from '../../../desktop/processManagers/GDBFileSystemProcessManager'; 20 | import { 21 | AttachRequestArguments, 22 | LaunchRequestArguments, 23 | } from '../../../types/session'; 24 | import { 25 | IGDBBackend, 26 | IGDBBackendFactory, 27 | IGDBProcessManager, 28 | } from '../../../types/gdb'; 29 | import { GDBDebugSessionBase } from '../../../gdb/GDBDebugSessionBase'; 30 | 31 | process.on('uncaughtException', (err: any) => { 32 | logger.error(JSON.stringify(err)); 33 | }); 34 | 35 | // Breakpoint options to override 36 | const hardwareBreakpointTrue = process.argv.includes( 37 | '--hardware-breakpoint-true' 38 | ); 39 | const hardwareBreakpointFalse = process.argv.includes( 40 | '--hardware-breakpoint-false' 41 | ); 42 | 43 | // Breakpoint mode to override 44 | const breakpointModeIndex = process.argv.indexOf('--breakpoint-mode'); 45 | const breakpointMode = 46 | breakpointModeIndex < 0 47 | ? undefined 48 | : (process.argv[breakpointModeIndex + 1] as 49 | | MIBreakpointMode 50 | | undefined); 51 | 52 | const throwError = process.argv.includes('--throw-error'); 53 | 54 | class DynamicBreakpointOptionsGDBBackend extends GDBBackend { 55 | public async getBreakpointOptions( 56 | _: MIBreakpointLocation, 57 | initialOptions: MIBreakpointInsertOptions 58 | ): Promise { 59 | if (throwError) { 60 | throw new Error( 61 | 'Some error message providing information that the breakpoint is not valid!' 62 | ); 63 | } 64 | const hardware = hardwareBreakpointTrue 65 | ? true 66 | : hardwareBreakpointFalse 67 | ? false 68 | : initialOptions.hardware; 69 | const mode = breakpointMode ?? initialOptions.mode; 70 | return { ...initialOptions, mode, hardware }; 71 | } 72 | } 73 | 74 | class DynamicBreakpointBackendFactory implements IGDBBackendFactory { 75 | async createGDBManager( 76 | _session: GDBDebugSessionBase, 77 | _args: LaunchRequestArguments | AttachRequestArguments 78 | ): Promise { 79 | return new GDBFileSystemProcessManager(); 80 | } 81 | 82 | async createBackend( 83 | _session: GDBDebugSessionBase, 84 | manager: IGDBProcessManager, 85 | _args: LaunchRequestArguments | AttachRequestArguments, 86 | _name?: string 87 | ): Promise { 88 | return new DynamicBreakpointOptionsGDBBackend(manager); 89 | } 90 | } 91 | 92 | class DynamicBreakpointOptionsGDBDebugSession extends GDBTargetDebugSession { 93 | constructor() { 94 | super(new DynamicBreakpointBackendFactory()); 95 | } 96 | } 97 | 98 | DynamicBreakpointOptionsGDBDebugSession.run( 99 | DynamicBreakpointOptionsGDBDebugSession 100 | ); 101 | -------------------------------------------------------------------------------- /src/desktop/GDBDebugSession.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 QNX Software Systems and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import * as fs from 'fs'; 11 | import { DebugSession, logger } from '@vscode/debugadapter'; 12 | import { 13 | LaunchRequestArguments, 14 | AttachRequestArguments, 15 | } from '../types/session'; 16 | import { GDBDebugSessionBase } from '../gdb/GDBDebugSessionBase'; 17 | import { GDBBackendFactory } from './factories/GDBBackendFactory'; 18 | import { IGDBBackendFactory } from '../types/gdb'; 19 | 20 | export class GDBDebugSession extends GDBDebugSessionBase { 21 | /** 22 | * Initial (aka default) configuration for launch/attach request 23 | * typically supplied with the --config command line argument. 24 | */ 25 | protected static defaultRequestArguments?: any; 26 | 27 | /** 28 | * Frozen configuration for launch/attach request 29 | * typically supplied with the --config-frozen command line argument. 30 | */ 31 | protected static frozenRequestArguments?: { request?: string }; 32 | constructor(backendFactory?: IGDBBackendFactory) { 33 | super(backendFactory || new GDBBackendFactory()); 34 | this.logger = logger; 35 | } 36 | 37 | /** 38 | * Main entry point 39 | */ 40 | public static run(debugSession: typeof DebugSession) { 41 | GDBDebugSession.processArgv(process.argv.slice(2)); 42 | DebugSession.run(debugSession); 43 | } 44 | 45 | /** 46 | * Parse an optional config file which is a JSON string of launch/attach request arguments. 47 | * The config can be a response file by starting with an @. 48 | */ 49 | public static processArgv(args: string[]) { 50 | args.forEach(function (val, _index, _array) { 51 | const configMatch = /^--config(-frozen)?=(.*)$/.exec(val); 52 | if (configMatch) { 53 | let configJson; 54 | const configStr = configMatch[2]; 55 | if (configStr.startsWith('@')) { 56 | const configFile = configStr.slice(1); 57 | configJson = JSON.parse( 58 | fs.readFileSync(configFile).toString('utf8') 59 | ); 60 | } else { 61 | configJson = JSON.parse(configStr); 62 | } 63 | if (configMatch[1]) { 64 | GDBDebugSession.frozenRequestArguments = configJson; 65 | } else { 66 | GDBDebugSession.defaultRequestArguments = configJson; 67 | } 68 | } 69 | }); 70 | } 71 | 72 | /** 73 | * Apply the initial and frozen launch/attach request arguments. 74 | * @param request the default request type to return if request type is not frozen 75 | * @param args the arguments from the user to apply initial and frozen arguments to. 76 | * @returns resolved request type and the resolved arguments 77 | */ 78 | protected applyRequestArguments( 79 | request: 'launch' | 'attach', 80 | args: LaunchRequestArguments | AttachRequestArguments 81 | ): ['launch' | 'attach', LaunchRequestArguments | AttachRequestArguments] { 82 | const frozenRequest = GDBDebugSession.frozenRequestArguments?.request; 83 | if (frozenRequest === 'launch' || frozenRequest === 'attach') { 84 | request = frozenRequest; 85 | } 86 | 87 | return [ 88 | request, 89 | { 90 | ...GDBDebugSession.defaultRequestArguments, 91 | ...args, 92 | ...GDBDebugSession.frozenRequestArguments, 93 | }, 94 | ]; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/MultiThread.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "Sleep.h" 5 | #include "Thread.h" 6 | 7 | static const int NUM_THREADS = 5; 8 | 9 | struct PrintHelloArgs { 10 | int thread_id; 11 | ThreadBarrier *barrier_start; 12 | ThreadBarrier *barrier_finish; 13 | ThreadSemaphore *sem_start; 14 | const char *name; 15 | }; 16 | 17 | static void inner_method(int thread_id) 18 | { 19 | int thread_id_plus_1 = thread_id + 1; 20 | int thread_id_plus_2 = thread_id + 2; 21 | printf("Thread %d in inner_method %d, %d\n", thread_id, thread_id_plus_1, thread_id_plus_2); /* LINE_THREAD_INNER */ 22 | } 23 | static void recursive(int thread_id, int depth) 24 | { 25 | if (depth == 0) { 26 | inner_method(thread_id); 27 | } else { 28 | printf("Recursing thread %d\n", thread_id); 29 | recursive(thread_id, depth - 1); 30 | } 31 | } 32 | 33 | static ThreadRet THREAD_CALL_CONV PrintHello(void *void_arg) 34 | { 35 | struct PrintHelloArgs *args = (struct PrintHelloArgs *) void_arg; 36 | int thread_id = args->thread_id; 37 | ThreadBarrier *barrier_start = args->barrier_start; 38 | ThreadBarrier *barrier_finish = args->barrier_finish; 39 | ThreadSemaphore *sem_start = args->sem_start; 40 | const char *name = args->name; 41 | 42 | /* Indicate to main thread that the thread is started. */ 43 | ThreadSemaphorePut(sem_start); 44 | 45 | printf("Hello World! It's me, thread #%d!\n", thread_id); 46 | 47 | ThreadSetName(name); 48 | 49 | /* Make sure that all threads are started before the breakpoint in main hits. */ 50 | ThreadBarrierWait(barrier_start); 51 | 52 | printf("Thread %d in the middle\n", thread_id); /* LINE_THREAD_IN_HELLO */ 53 | recursive(thread_id, thread_id); 54 | 55 | /* Make sure that the thread does not finish before the breakpoint in main hits. */ 56 | ThreadBarrierWait(barrier_finish); 57 | 58 | printf("Goodbye World! From thread #%d\n", thread_id); 59 | 60 | return THREAD_DEFAULT_RET; 61 | } 62 | 63 | int main(int argc, char *argv[]) 64 | { 65 | ThreadHandle threads[NUM_THREADS]; 66 | struct PrintHelloArgs args[NUM_THREADS]; 67 | const char *thread_names[NUM_THREADS] = {"monday", "tuesday", "wednesday", "thursday", "friday"}; 68 | 69 | /* Used to make rendez-vous points between all threads. */ 70 | ThreadBarrier barrier_start; 71 | ThreadBarrier barrier_finish; 72 | 73 | /* Used to tell when a thread is started for real. */ 74 | ThreadSemaphore sem_start; 75 | 76 | /* + 1 for main thread */ 77 | ThreadBarrierInit(&barrier_start, NUM_THREADS + 1); 78 | ThreadBarrierInit(&barrier_finish, NUM_THREADS + 1); 79 | 80 | ThreadSemaphoreInit(&sem_start, 0); 81 | 82 | for (int t = 0; t < NUM_THREADS; t++) 83 | { 84 | printf("In main: creating thread #%d\n", t); 85 | 86 | args[t].thread_id = t; 87 | args[t].barrier_start = &barrier_start; 88 | args[t].barrier_finish = &barrier_finish; 89 | args[t].sem_start = &sem_start; 90 | args[t].name = thread_names[t]; 91 | 92 | int ret = StartThread(PrintHello, &args[t], &threads[t]); /* Breakpoint LINE_MAIN_BEFORE_THREAD_START */ 93 | 94 | if (!ret) 95 | { 96 | printf("Error: StartThread failed.\n"); 97 | exit(-1); 98 | } 99 | 100 | /* Wait until the thread has really started. */ 101 | ThreadSemaphoreTake(&sem_start); 102 | 103 | printf("In main: thread #%d has started\n", t); /* Breakpoint LINE_MAIN_AFTER_THREAD_START */ 104 | } 105 | 106 | /* Let the threads continue to the 'critical' section> */ 107 | ThreadBarrierWait(&barrier_start); 108 | 109 | printf("In main thread, all threads created.\n"); /* Breakpoint LINE_MAIN_ALL_THREADS_STARTED */ 110 | 111 | SLEEP(30); 112 | 113 | /* Unlock the threads and let the program finish. */ 114 | ThreadBarrierWait(&barrier_finish); 115 | 116 | for (int t = 0; t < NUM_THREADS; t++) 117 | { 118 | printf("In main, joining thread #%d\n", t); 119 | JoinThread(threads[t], NULL); 120 | } 121 | 122 | ThreadBarrierDestroy(&barrier_start); 123 | ThreadBarrierDestroy(&barrier_finish); 124 | ThreadSemaphoreDestroy(&sem_start); 125 | 126 | return 0; 127 | } 128 | -------------------------------------------------------------------------------- /src/integration-tests/test-programs/ThreadWindows.h: -------------------------------------------------------------------------------- 1 | #ifndef THREADWINDOWS_H 2 | #define THREADWINDOWS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /* Thread functions */ 9 | 10 | static int StartThread(ThreadFunc func, void *arg, ThreadHandle *handle) { 11 | *handle = (HANDLE) _beginthreadex(NULL, 0, func, arg, 0, NULL); 12 | 13 | return *handle != 0; 14 | } 15 | 16 | static int JoinThread(ThreadHandle handle, ThreadRet *ret) 17 | { 18 | DWORD result = WaitForSingleObject(handle, INFINITE); 19 | if (result != WAIT_OBJECT_0) 20 | return 0; 21 | 22 | BOOL result_b = GetExitCodeThread(handle, (DWORD *) ret); 23 | if (!result) 24 | return 0; 25 | 26 | return 1; 27 | } 28 | 29 | 30 | /* Barrier functions */ 31 | 32 | struct WindowsThreadBarrier 33 | { 34 | LONG num_threads_to_wait; 35 | // InterlockedIncrement requires the LONG variable to be aligned on a 4-bytes boundary. 36 | LONG num_threads_waiting __attribute__ ((aligned (4))); 37 | HANDLE semaphore; 38 | }; 39 | 40 | static int ThreadBarrierInit(ThreadBarrier *barrier, unsigned int count) 41 | { 42 | const LONG max_threads = LONG_MAX; 43 | 44 | barrier->semaphore = CreateSemaphore(NULL, 0, max_threads, NULL); 45 | if (!barrier->semaphore) { 46 | return 0; 47 | } 48 | 49 | barrier->num_threads_to_wait = count; 50 | barrier->num_threads_waiting = 0; 51 | 52 | /* Make sure that the 4-bytes alignment directive works properly. */ 53 | assert(((intptr_t) &barrier->num_threads_waiting & 0x3) == 0); 54 | 55 | return 1; 56 | } 57 | 58 | static int ThreadBarrierDestroy(ThreadBarrier *barrier) 59 | { 60 | CloseHandle(barrier->semaphore); 61 | 62 | return 1; 63 | } 64 | 65 | static int ThreadBarrierWait(ThreadBarrier *barrier) 66 | { 67 | LONG new_value = InterlockedIncrement(&barrier->num_threads_waiting); 68 | 69 | if (new_value == barrier->num_threads_to_wait) { 70 | // We are the last thread to hit the barrier, release everybody else (count - 1 threads). 71 | BOOL ret = ReleaseSemaphore(barrier->semaphore, barrier->num_threads_to_wait - 1, NULL); 72 | if (!ret) 73 | return 0; 74 | } else { 75 | // We are not the last thread to hit the barrier, wait to get released. 76 | DWORD ret = WaitForSingleObject(barrier->semaphore, INFINITE); 77 | if (ret != WAIT_OBJECT_0) 78 | return 0; 79 | } 80 | 81 | return 1; 82 | } 83 | 84 | static int ThreadSemaphoreInit(ThreadSemaphore *sem, unsigned int initial_count) 85 | { 86 | *sem = CreateSemaphore(NULL, initial_count, LONG_MAX, NULL); 87 | return *sem != NULL; 88 | } 89 | 90 | static int ThreadSemaphoreTake(ThreadSemaphore *sem) 91 | { 92 | DWORD result = WaitForSingleObject(*sem, INFINITE); 93 | 94 | return result == WAIT_OBJECT_0; 95 | } 96 | 97 | static int ThreadSemaphorePut(ThreadSemaphore *sem) 98 | { 99 | return ReleaseSemaphore(*sem, 1, NULL) != 0; 100 | } 101 | 102 | static int ThreadSemaphoreDestroy(ThreadSemaphore *sem) 103 | { 104 | return CloseHandle(*sem) != 0; 105 | } 106 | 107 | static int ThreadSetName(const char *name) 108 | { 109 | // This code sends a special exception that GDB traps to add a name to the 110 | // thread. It is mostly undocumented, but can be referenced in GDB 111 | // code here: https://github.com/bminor/binutils-gdb/blob/a2e7f81e382d641780ce5ae0fe72a309c8a4964d/gdb/nat/windows-nat.h#L255-L261 112 | // Note: when running under gdbserver nothing catches this exception 113 | #define MS_VC_EXCEPTION 0x406d1388 114 | ULONG_PTR args[3]; // number of entries in the exception information (https://github.com/bminor/binutils-gdb/blob/a2e7f81e382d641780ce5ae0fe72a309c8a4964d/gdb/nat/windows-nat.c#L312) 115 | args[0] = 0x1000; // magic number that matches what GDB checks (https://github.com/bminor/binutils-gdb/blob/a2e7f81e382d641780ce5ae0fe72a309c8a4964d/gdb/nat/windows-nat.c#L313) 116 | args[1] = (ULONG_PTR)name; // thread name (https://github.com/bminor/binutils-gdb/blob/a2e7f81e382d641780ce5ae0fe72a309c8a4964d/gdb/nat/windows-nat.c#L319) 117 | args[2] = -1; // thread id, or -1 for current thread (https://github.com/bminor/binutils-gdb/blob/a2e7f81e382d641780ce5ae0fe72a309c8a4964d/gdb/nat/windows-nat.c#L322) 118 | 119 | RaiseException(MS_VC_EXCEPTION, 0, sizeof(args) / sizeof(ULONG_PTR), args); 120 | 121 | return 0; 122 | } 123 | 124 | #endif // THREADWINDOWS_H 125 | -------------------------------------------------------------------------------- /.github/workflows/build-push.yml: -------------------------------------------------------------------------------- 1 | # This workflow runs on pushes - it is similar to what happens in PR 2 | # workflow, but doesn't need to be split across multiple workflows 3 | # as this one runs where there is sufficient permission to report 4 | # results back 5 | name: build-push 6 | on: [push] 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: true 10 | jobs: 11 | Build-on-Ubuntu: 12 | name: Build & Test on Ubuntu 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version: '20' 19 | - name: Install GCC & GDB & other build essentials 20 | run: | 21 | sudo apt-get update 22 | sudo apt-get -y install build-essential gcc g++ gdb gdbserver socat 23 | gdb --version 24 | gcc --version 25 | gdbserver --version 26 | - name: Enable ptrace so tests can attach to running processes, see attach.spec.ts 27 | run: | 28 | sudo sysctl kernel.yama.ptrace_scope=0 29 | - name: Build 30 | run: yarn 31 | - name: Build Test Programs 32 | run: make -C src/integration-tests/test-programs 33 | - name: Test 34 | run: yarn test-ci 35 | - name: Log file artifacts 36 | uses: actions/upload-artifact@v4 37 | if: success() || failure() 38 | with: 39 | name: test-logs-ubuntu 40 | path: test-logs/ 41 | - name: Publish Test Report 42 | uses: mikepenz/action-junit-report@v5 43 | if: success() || failure() 44 | with: 45 | commit: ${{github.event.workflow_run.head_sha}} 46 | report_paths: 'test-reports/*.xml' 47 | fail_on_failure: true 48 | require_tests: true 49 | check_name: Test Results on Ubuntu 50 | - name: Verify no unexpected changes to source tree 51 | run: git diff --exit-code 52 | Build-on-Windows: 53 | name: Build & Test on Windows 54 | runs-on: windows-latest 55 | steps: 56 | - uses: actions/checkout@v4 57 | - uses: actions/setup-node@v4 58 | with: 59 | node-version: '20' 60 | # Install build tools and update PATH through GITHUB_PATH for next steps 61 | - name: Install GCC & GDB & other build essentials 62 | run: | 63 | choco install mingw --version=11.2.0.07112021 64 | $chocoMingwBin = Join-Path $env:ChocolateyInstall 'lib\mingw\tools\install\mingw64\bin' 65 | echo $chocoMingwBin >> $env:GITHUB_PATH 66 | - name: Echo Tool Versions 67 | run: | 68 | gdb --version 69 | gcc --version 70 | gdbserver --version 71 | make --version 72 | - name: Build 73 | run: yarn 74 | - name: Build Test Programs 75 | run: make -C src/integration-tests/test-programs 76 | - name: Use special Mocha settings on Windows tests 77 | run: | 78 | Copy -path .mocharc-windows-ci.json -destination .mocharc.json -verbose 79 | - name: Test 80 | run: yarn test-ci 81 | - name: Log file artifacts 82 | uses: actions/upload-artifact@v4 83 | if: success() || failure() 84 | with: 85 | name: test-logs-windows 86 | path: test-logs/ 87 | - name: Publish Test Report 88 | uses: mikepenz/action-junit-report@v5 89 | with: 90 | commit: ${{github.event.workflow_run.head_sha}} 91 | report_paths: 'test-reports/*.xml' 92 | fail_on_failure: true 93 | require_tests: true 94 | check_name: Test Results on Windows 95 | # Publish to NPM if tag is pushed and build and test succeeds 96 | # on both Linux and Windows. Note this is not restricted to 'main'. 97 | Publish-to-NPM: 98 | name: Publish Package to NPM 99 | runs-on: ubuntu-latest 100 | needs: [Build-on-Ubuntu, Build-on-Windows] 101 | if: startsWith(github.ref, 'refs/tags/') 102 | steps: 103 | - uses: actions/checkout@v4 104 | - uses: actions/setup-node@v4 105 | with: 106 | node-version: '20' 107 | registry-url: 'https://registry.npmjs.org' 108 | - name: Build 109 | run: yarn 110 | - name: Publish 111 | run: yarn publish 112 | env: 113 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 114 | -------------------------------------------------------------------------------- /src/integration-tests/gdbCwd.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2023 Kichwa Coders Canada Inc. and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import * as path from 'path'; 12 | import { CdtDebugClient } from './debugClient'; 13 | import { 14 | fillDefaults, 15 | resolveLineTagLocations, 16 | standardBeforeEach, 17 | testProgramsDir, 18 | } from './utils'; 19 | import { expect } from 'chai'; 20 | 21 | /** 22 | * To test that cwd is set properly we remove the compilation directory from the executable, 23 | * see the makefile for that part, and then launch with a variety of executable/cwd locations 24 | * to make sure that we can insert breakpoints when we expect to, and cannot insert breakpoints 25 | * when we force gdb not to be able to find the source 26 | */ 27 | describe('gdb cwd', function () { 28 | let dc: CdtDebugClient; 29 | const program = path.join(testProgramsDir, 'cwd.exe'); 30 | const programRelocated = path.join(testProgramsDir, 'Debug', 'cwd.exe'); 31 | const src = path.join(testProgramsDir, 'cwd.c'); 32 | const lineTags = { 33 | 'STOP HERE': 0, 34 | }; 35 | 36 | before(function () { 37 | resolveLineTagLocations(src, lineTags); 38 | }); 39 | 40 | beforeEach(async function () { 41 | dc = await standardBeforeEach(); 42 | }); 43 | 44 | afterEach(async function () { 45 | await dc.stop(); 46 | }); 47 | 48 | it('default cwd finds source in program directory', async function () { 49 | await dc.launchRequest( 50 | fillDefaults(this.test, { 51 | program: program, 52 | }) 53 | ); 54 | 55 | const bps = await dc.setBreakpointsRequest({ 56 | lines: [lineTags['STOP HERE']], 57 | breakpoints: [{ line: lineTags['STOP HERE'], column: 1 }], 58 | source: { path: src }, 59 | }); 60 | expect(bps.body.breakpoints[0].verified).to.eq(true); 61 | }); 62 | 63 | it('explicit cwd finds source in program directory', async function () { 64 | await dc.launchRequest( 65 | fillDefaults(this.test, { 66 | program: program, 67 | cwd: testProgramsDir, 68 | }) 69 | ); 70 | 71 | const bps = await dc.setBreakpointsRequest({ 72 | lines: [lineTags['STOP HERE']], 73 | breakpoints: [{ line: lineTags['STOP HERE'], column: 1 }], 74 | source: { path: src }, 75 | }); 76 | expect(bps.body.breakpoints[0].verified).to.eq(true); 77 | }); 78 | 79 | it('default cwd does not find source with relocated program', async function () { 80 | await dc.launchRequest( 81 | fillDefaults(this.test, { 82 | program: programRelocated, 83 | }) 84 | ); 85 | 86 | const bps = await dc.setBreakpointsRequest({ 87 | lines: [lineTags['STOP HERE']], 88 | breakpoints: [{ line: lineTags['STOP HERE'], column: 1 }], 89 | source: { path: src }, 90 | }); 91 | expect(bps.body.breakpoints[0].verified).to.eq(false); 92 | }); 93 | 94 | it('explicitly incorrect cwd does not finds source with relocated program', async function () { 95 | await dc.launchRequest( 96 | fillDefaults(this.test, { 97 | program: programRelocated, 98 | cwd: path.join(testProgramsDir, 'EmptyDir'), 99 | }) 100 | ); 101 | 102 | const bps = await dc.setBreakpointsRequest({ 103 | lines: [lineTags['STOP HERE']], 104 | breakpoints: [{ line: lineTags['STOP HERE'], column: 1 }], 105 | source: { path: src }, 106 | }); 107 | expect(bps.body.breakpoints[0].verified).to.eq(false); 108 | }); 109 | 110 | it('explicitly correct cwd does find source with relocated program', async function () { 111 | await dc.launchRequest( 112 | fillDefaults(this.test, { 113 | program: programRelocated, 114 | cwd: testProgramsDir, 115 | }) 116 | ); 117 | 118 | const bps = await dc.setBreakpointsRequest({ 119 | lines: [lineTags['STOP HERE']], 120 | breakpoints: [{ line: lineTags['STOP HERE'], column: 1 }], 121 | source: { path: src }, 122 | }); 123 | expect(bps.body.breakpoints[0].verified).to.eq(true); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /src/mi/data.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018 Ericsson and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import { IGDBBackend } from '../types/gdb'; 12 | import { FrameReference } from '../types/session'; 13 | import { MIResponse, MIRegisterValueInfo } from './base'; 14 | 15 | export interface MIDataReadMemoryBytesResponse { 16 | memory: Array<{ 17 | begin: string; 18 | end: string; 19 | offset: string; 20 | contents: string; 21 | }>; 22 | } 23 | export interface MIDataDisassembleAsmInsn { 24 | address: string; 25 | // func-name in MI 26 | 'func-name': string; 27 | offset: string; 28 | opcodes: string; 29 | inst: string; 30 | } 31 | 32 | export interface MIDataDisassembleSrcAndAsmLine { 33 | line: string; 34 | file: string; 35 | fullname: string; 36 | line_asm_insn: MIDataDisassembleAsmInsn[]; 37 | } 38 | export interface MIDataDisassembleResponse { 39 | asm_insns: MIDataDisassembleSrcAndAsmLine[]; 40 | } 41 | 42 | export interface MIListRegisterNamesResponse extends MIResponse { 43 | 'register-names': string[]; 44 | } 45 | 46 | export interface MIListRegisterValuesResponse extends MIResponse { 47 | 'register-values': MIRegisterValueInfo[]; 48 | } 49 | 50 | export interface MIGDBDataEvaluateExpressionResponse extends MIResponse { 51 | value?: string; 52 | } 53 | 54 | export function sendDataReadMemoryBytes( 55 | gdb: IGDBBackend, 56 | address: string, 57 | size: number, 58 | offset = 0 59 | ): Promise { 60 | return gdb.sendCommand( 61 | `-data-read-memory-bytes -o ${offset} "${address}" ${size}` 62 | ); 63 | } 64 | 65 | export function sendDataWriteMemoryBytes( 66 | gdb: IGDBBackend, 67 | memoryReference: string, 68 | data: string 69 | ): Promise { 70 | return gdb.sendCommand( 71 | `-data-write-memory-bytes "${memoryReference}" "${data}"` 72 | ); 73 | } 74 | 75 | export function sendDataEvaluateExpression( 76 | gdb: IGDBBackend, 77 | expr: string 78 | ): Promise { 79 | return gdb.sendCommand(`-data-evaluate-expression "${expr}"`); 80 | } 81 | 82 | // https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Data-Manipulation.html#The-_002ddata_002ddisassemble-Command 83 | export async function sendDataDisassemble( 84 | gdb: IGDBBackend, 85 | startAddress: string, 86 | endAddress: string 87 | ): Promise { 88 | // -- 5 == mixed source and disassembly with raw opcodes 89 | // needs to be deprecated mode 3 for GDB < 7.11 90 | const mode = gdb.gdbVersionAtLeast('7.11') ? '5' : '3'; 91 | const result: MIDataDisassembleResponse = await gdb.sendCommand( 92 | `-data-disassemble -s "${startAddress}" -e "${endAddress}" -- ${mode}` 93 | ); 94 | 95 | // cleanup the result data 96 | if (result.asm_insns.length > 0) { 97 | if ( 98 | !Object.prototype.hasOwnProperty.call( 99 | result.asm_insns[0], 100 | 'line_asm_insn' 101 | ) 102 | ) { 103 | // In this case there is no source info available for any instruction, 104 | // so GDB treats as if we had done -- 2 instead of -- 5 105 | // This bit of code remaps the data to look like it should 106 | const e: MIDataDisassembleSrcAndAsmLine = { 107 | line_asm_insn: 108 | result.asm_insns as unknown as MIDataDisassembleAsmInsn[], 109 | } as MIDataDisassembleSrcAndAsmLine; 110 | result.asm_insns = [e]; 111 | } 112 | for (const asmInsn of result.asm_insns) { 113 | if ( 114 | !Object.prototype.hasOwnProperty.call(asmInsn, 'line_asm_insn') 115 | ) { 116 | asmInsn.line_asm_insn = []; 117 | } 118 | } 119 | } 120 | return Promise.resolve(result); 121 | } 122 | 123 | export function sendDataListRegisterNames( 124 | gdb: IGDBBackend, 125 | params: { 126 | regno?: number[]; 127 | frameRef: FrameReference; 128 | } 129 | ): Promise { 130 | let command = `-data-list-register-names --frame ${params.frameRef.frameId} --thread ${params.frameRef.threadId}`; 131 | 132 | if (params.regno) { 133 | command += params.regno.join(' '); 134 | } 135 | 136 | return gdb.sendCommand(command); 137 | } 138 | 139 | export function sendDataListRegisterValues( 140 | gdb: IGDBBackend, 141 | params: { 142 | fmt: string; 143 | regno?: number[]; 144 | frameRef: FrameReference; 145 | } 146 | ): Promise { 147 | let command = `-data-list-register-values --frame ${params.frameRef.frameId} --thread ${params.frameRef.threadId} ${params.fmt}`; 148 | 149 | if (params.regno) { 150 | command += params.regno.join(' '); 151 | } 152 | 153 | return gdb.sendCommand(command); 154 | } 155 | -------------------------------------------------------------------------------- /src/integration-tests/lateAsyncErrorsRemote.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2025 Arm and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import * as path from 'path'; 12 | import { CdtDebugClient } from './debugClient'; 13 | import { 14 | fillDefaults, 15 | gdbAsync, 16 | isRemoteTest, 17 | resolveLineTagLocations, 18 | standardBeforeEach, 19 | testProgramsDir, 20 | } from './utils'; 21 | import { TargetLaunchRequestArguments } from '../types/session'; 22 | import { DebugProtocol } from '@vscode/debugprotocol'; 23 | import { expect } from 'chai'; 24 | 25 | describe('lateAsyncErrorsRemote', function () { 26 | let dc: CdtDebugClient; 27 | const program = path.join(testProgramsDir, 'loopforever'); 28 | const src = path.join(testProgramsDir, 'loopforever.c'); 29 | const lineTags = { 30 | 'main function': 0, 31 | 'inner1 stop': 0, 32 | }; 33 | 34 | this.beforeAll(function () { 35 | resolveLineTagLocations(src, lineTags); 36 | }); 37 | 38 | beforeEach(async function () { 39 | dc = await standardBeforeEach('debugTargetAdapter.js'); 40 | await dc.launchRequest( 41 | fillDefaults(this.currentTest, { 42 | program, 43 | } as TargetLaunchRequestArguments) 44 | ); 45 | }); 46 | 47 | afterEach(async function () { 48 | await dc.stop(); 49 | }); 50 | 51 | it('should provoke an error and not continue with too many watchpoints, but continue after reducing the number', async function () { 52 | if (!isRemoteTest || !gdbAsync) { 53 | this.skip(); 54 | } 55 | 56 | await dc.setBreakpointsRequest({ 57 | source: { path: src }, 58 | breakpoints: [{ line: lineTags['main function'] }], 59 | }); 60 | 61 | // Stopped by default 62 | let [stoppedEvent] = await Promise.all([ 63 | dc.waitForEvent('stopped'), 64 | dc.configurationDoneRequest(), 65 | ]); 66 | 67 | expect(stoppedEvent).to.not.be.undefined; 68 | if (!stoppedEvent) { 69 | // Pointless to continue test 70 | return; 71 | } 72 | 73 | // Set too many watchpoints, was not able to set HW breaks (on Windows) 74 | const watchExpressions = [ 75 | '>watch var1', 76 | '>watch var2', 77 | '>watch stop', 78 | '>awatch var1', 79 | '>awatch var2', 80 | '>awatch stop', 81 | '>rwatch var1', 82 | '>rwatch var2', 83 | '>rwatch stop', 84 | ]; 85 | watchExpressions.forEach(async (expr) => { 86 | await dc.evaluateRequest({ 87 | expression: expr, 88 | context: 'repl', 89 | }); 90 | }); 91 | 92 | // Set breakpoint to hit next 93 | await dc.setBreakpointsRequest({ 94 | source: { path: src }, 95 | breakpoints: [{ line: lineTags['inner1 stop'] }], 96 | }); 97 | 98 | const threadId = 99 | (stoppedEvent as DebugProtocol.StoppedEvent).body.threadId ?? 1; 100 | [, stoppedEvent] = await Promise.all([ 101 | dc.waitForEvent('continued'), // Continue will cause continued event, error noticed late 102 | dc.waitForEvent('stopped'), // Stop due to late error after attempt to install watchpoints 103 | dc.waitForOutputEvent( 104 | // Proof we had too many breakpoints 105 | 'log', 106 | 'You may have requested too many hardware breakpoints/watchpoints.\n' 107 | ), 108 | dc.continueRequest({ threadId }), 109 | ]); 110 | expect(stoppedEvent).to.not.be.undefined; 111 | if (!stoppedEvent) { 112 | // Pointless to continue test 113 | return; 114 | } 115 | // Check expected stop reason 116 | expect( 117 | (stoppedEvent as DebugProtocol.StoppedEvent).body.reason 118 | ).to.equal('error'); 119 | 120 | // Remove all breakpoints/watchpoints 121 | await dc.evaluateRequest({ 122 | expression: '>delete breakpoints', 123 | context: 'repl', 124 | }); 125 | 126 | // Check command queue recovered and executes next commands 127 | // Reinstall breakpoint to hit next 128 | await dc.setBreakpointsRequest({ 129 | source: { path: src }, 130 | breakpoints: [{ line: lineTags['inner1 stop'] }], 131 | }); 132 | const threadIdAfterError = 133 | (stoppedEvent as DebugProtocol.StoppedEvent).body.threadId ?? 1; 134 | // Set next breakpoint to hit, run and hit it 135 | await Promise.all([ 136 | dc.assertStoppedLocation('breakpoint', { 137 | line: lineTags['inner1 stop'], 138 | }), 139 | dc.continueRequest({ 140 | threadId: threadIdAfterError, 141 | }), 142 | ]); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /src/integration-tests/mem-cdt-custom.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2018, 2022 Ericsson and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import { expect } from 'chai'; 12 | import * as path from 'path'; 13 | import { DebugProtocol } from '@vscode/debugprotocol/lib/debugProtocol'; 14 | import { MemoryResponse } from '../types/session'; 15 | import { CdtDebugClient } from './debugClient'; 16 | import { 17 | expectRejection, 18 | fillDefaults, 19 | standardBeforeEach, 20 | testProgramsDir, 21 | } from './utils'; 22 | 23 | describe('Memory Test Suite for cdt-gdb-adapter/Memory custom request', function () { 24 | let dc: CdtDebugClient; 25 | let frame: DebugProtocol.StackFrame; 26 | const memProgram = path.join(testProgramsDir, 'mem'); 27 | const memSrc = path.join(testProgramsDir, 'mem.c'); 28 | 29 | beforeEach(async function () { 30 | dc = await standardBeforeEach(); 31 | 32 | await dc.hitBreakpoint( 33 | fillDefaults(this.currentTest, { 34 | program: memProgram, 35 | }), 36 | { 37 | path: memSrc, 38 | line: 12, 39 | } 40 | ); 41 | const threads = await dc.threadsRequest(); 42 | // On windows additional threads can exist to handle signals, therefore find 43 | // the real thread & frame running the user code. The other thread will 44 | // normally be running code from ntdll or similar. 45 | loop_threads: for (const thread of threads.body.threads) { 46 | const stack = await dc.stackTraceRequest({ threadId: thread.id }); 47 | if (stack.body.stackFrames.length >= 1) { 48 | for (const f of stack.body.stackFrames) { 49 | if (f.source && f.source.name === 'mem.c') { 50 | frame = f; 51 | break loop_threads; 52 | } 53 | } 54 | } 55 | } 56 | // Make sure we found the expected frame 57 | expect(frame).not.eq(undefined); 58 | }); 59 | 60 | afterEach(async function () { 61 | await dc.stop(); 62 | }); 63 | 64 | /** 65 | * Verify that `resp` contains the bytes `expectedBytes` and the 66 | * `expectedAddress` start address. 67 | * 68 | * `expectedAddress` should be an hexadecimal string, with the leading 0x. 69 | */ 70 | function verifyMemoryReadResult( 71 | resp: MemoryResponse, 72 | expectedBytes: string, 73 | expectedAddress: number 74 | ) { 75 | expect(resp.body.data).eq(expectedBytes); 76 | expect(resp.body.address).match(/^0x[0-9a-fA-F]+$/); 77 | 78 | const actualAddress = parseInt(resp.body.address, 16); 79 | expect(actualAddress).eq(expectedAddress); 80 | } 81 | 82 | // Test reading memory using cdt-gdb-adapter's extension request. 83 | it('can read memory', async function () { 84 | // Get the address of the array. 85 | const addrOfArrayResp = await dc.evaluateRequest({ 86 | expression: '&array', 87 | frameId: frame.id, 88 | }); 89 | const addrOfArray = parseInt(addrOfArrayResp.body.result, 16); 90 | 91 | let mem = (await dc.send('cdt-gdb-adapter/Memory', { 92 | address: '0x' + addrOfArray.toString(16), 93 | length: 10, 94 | })) as MemoryResponse; 95 | 96 | verifyMemoryReadResult(mem, 'f1efd4fd7248450c2d13', addrOfArray); 97 | 98 | mem = (await dc.send('cdt-gdb-adapter/Memory', { 99 | address: '&array[3 + 2]', 100 | length: 10, 101 | })) as MemoryResponse; 102 | 103 | verifyMemoryReadResult(mem, '48450c2d1374d6f612dc', addrOfArray + 5); 104 | 105 | mem = (await dc.send('cdt-gdb-adapter/Memory', { 106 | address: 'parray', 107 | length: 10, 108 | })) as MemoryResponse; 109 | 110 | verifyMemoryReadResult(mem, 'f1efd4fd7248450c2d13', addrOfArray); 111 | }); 112 | 113 | it('handles unable to read memory', async function () { 114 | // This test will only work for targets for which address 0 is not readable, which is good enough for now. 115 | const err = await expectRejection( 116 | dc.send('cdt-gdb-adapter/Memory', { 117 | address: '0', 118 | length: 10, 119 | }) 120 | ); 121 | expect(err.message).contains('Unable to read memory'); 122 | }); 123 | 124 | it('can read memory with offset', async function () { 125 | const addrOfArrayResp = await dc.evaluateRequest({ 126 | expression: '&array', 127 | frameId: frame.id, 128 | }); 129 | const addrOfArray = parseInt(addrOfArrayResp.body.result, 16); 130 | 131 | // Test positive offset 132 | let offset = 5; 133 | let mem = (await dc.send('cdt-gdb-adapter/Memory', { 134 | address: '&array', 135 | length: 5, 136 | offset, 137 | })) as MemoryResponse; 138 | 139 | verifyMemoryReadResult(mem, '48450c2d13', addrOfArray + offset); 140 | 141 | // Test negative offset 142 | offset = -5; 143 | mem = (await dc.send('cdt-gdb-adapter/Memory', { 144 | address: `array + ${-offset}`, 145 | length: 10, 146 | offset, 147 | })) as MemoryResponse; 148 | 149 | verifyMemoryReadResult(mem, 'f1efd4fd7248450c2d13', addrOfArray); 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /src/varManager.ts: -------------------------------------------------------------------------------- 1 | import { IGDBBackend } from './types/gdb'; 2 | import { MIVarCreateResponse } from './mi/var'; 3 | import { sendVarCreate, sendVarDelete, sendVarUpdate } from './mi/var'; 4 | import { FrameReference } from './types/session'; 5 | 6 | export interface VarObjType { 7 | varname: string; 8 | expression: string; 9 | numchild: string; 10 | children: VarObjType[]; 11 | value: string; 12 | type: string; 13 | isVar: boolean; 14 | isChild: boolean; 15 | varType: string; 16 | } 17 | 18 | export class VarManager { 19 | protected readonly variableMap: Map = new Map< 20 | string, 21 | VarObjType[] 22 | >(); 23 | 24 | constructor(protected gdb: IGDBBackend) { 25 | this.gdb = gdb; 26 | } 27 | 28 | public getKey(frameRef: FrameReference | undefined, depth: number): string { 29 | if (!frameRef) { 30 | return `global`; 31 | } 32 | return `frame${frameRef.frameId}_thread${frameRef.threadId}_depth${depth}`; 33 | } 34 | 35 | public getVars( 36 | frameRef: FrameReference | undefined, 37 | depth: number 38 | ): VarObjType[] | undefined { 39 | return this.variableMap.get(this.getKey(frameRef, depth)); 40 | } 41 | 42 | public getVar( 43 | frameRef: FrameReference | undefined, 44 | depth: number, 45 | expression: string, 46 | type?: string 47 | ): VarObjType | undefined { 48 | const vars = this.getVars(frameRef, depth); 49 | if (vars) { 50 | for (const varobj of vars) { 51 | if (varobj.expression === expression) { 52 | if (type !== 'registers') { 53 | type = 'local'; 54 | } 55 | if (type === varobj.varType) { 56 | return varobj; 57 | } 58 | } 59 | } 60 | } 61 | return; 62 | } 63 | 64 | public getVarByName( 65 | frameRef: FrameReference | undefined, 66 | depth: number, 67 | varname: string 68 | ): VarObjType | undefined { 69 | const vars = this.getVars(frameRef, depth); 70 | if (vars) { 71 | for (const varobj of vars) { 72 | if (varobj.varname === varname) { 73 | return varobj; 74 | } 75 | } 76 | } 77 | return; 78 | } 79 | 80 | public addVar( 81 | frameRef: FrameReference | undefined, 82 | depth: number, 83 | expression: string, 84 | isVar: boolean, 85 | isChild: boolean, 86 | varCreateResponse: MIVarCreateResponse, 87 | type?: string 88 | ): VarObjType { 89 | let vars = this.variableMap.get(this.getKey(frameRef, depth)); 90 | if (!vars) { 91 | vars = []; 92 | this.variableMap.set(this.getKey(frameRef, depth), vars); 93 | } 94 | const varobj: VarObjType = { 95 | varname: varCreateResponse.name, 96 | expression, 97 | numchild: varCreateResponse.numchild, 98 | children: [], 99 | value: varCreateResponse.value, 100 | type: varCreateResponse.type, 101 | isVar, 102 | isChild, 103 | varType: type ? type : 'local', 104 | }; 105 | vars.push(varobj); 106 | return varobj; 107 | } 108 | 109 | public async removeVar( 110 | frameRef: FrameReference | undefined, 111 | depth: number, 112 | varname: string 113 | ): Promise { 114 | let deleteme: VarObjType | undefined; 115 | const vars = frameRef 116 | ? this.variableMap.get(this.getKey(frameRef, depth)) 117 | : undefined; 118 | if (vars) { 119 | for (const varobj of vars) { 120 | if (varobj.varname === varname) { 121 | deleteme = varobj; 122 | break; 123 | } 124 | } 125 | if (deleteme) { 126 | await sendVarDelete(this.gdb, { varname: deleteme.varname }); 127 | vars.splice(vars.indexOf(deleteme), 1); 128 | for (const child of deleteme.children) { 129 | await this.removeVar(frameRef, depth, child.varname); 130 | } 131 | } 132 | } 133 | } 134 | 135 | public async updateVar( 136 | frameRef: FrameReference | undefined, 137 | depth: number, 138 | varobj: VarObjType 139 | ): Promise { 140 | let returnVar = varobj; 141 | const vup = await sendVarUpdate(this.gdb, { name: varobj.varname }); 142 | const update = vup.changelist[0]; 143 | if (update) { 144 | if (update.in_scope === 'true') { 145 | if (update.name === varobj.varname) { 146 | // don't update the parent value to a child's value 147 | varobj.value = update.value; 148 | } 149 | } else { 150 | this.removeVar(frameRef, depth, varobj.varname); 151 | await sendVarDelete(this.gdb, { varname: varobj.varname }); 152 | const createResponse = await sendVarCreate(this.gdb, { 153 | frame: 'current', 154 | expression: varobj.expression, 155 | frameRef, 156 | }); 157 | returnVar = this.addVar( 158 | frameRef, 159 | depth, 160 | varobj.expression, 161 | varobj.isVar, 162 | varobj.isChild, 163 | createResponse 164 | ); 165 | } 166 | } 167 | return Promise.resolve(returnVar); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/integration-tests/custom-reset.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2025 Arm Ltd 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import * as path from 'path'; 12 | import { TargetLaunchRequestArguments } from '../types/session'; 13 | import { CdtDebugClient } from './debugClient'; 14 | import { 15 | expectRejection, 16 | fillDefaults, 17 | gdbAsync, 18 | isRemoteTest, 19 | standardBeforeEach, 20 | testProgramsDir, 21 | } from './utils'; 22 | import { DebugProtocol } from '@vscode/debugprotocol'; 23 | import { Runnable } from 'mocha'; 24 | import { expect, use } from 'chai'; 25 | import * as chaistring from 'chai-string'; 26 | use(chaistring); 27 | 28 | const gdbtargetAdapter = 'debugTargetAdapter.js'; 29 | const loopForeverProgram = path.join(testProgramsDir, 'loopforever'); 30 | const commands = ['print 42']; 31 | const expectedResult = '$1 = 42\n'; 32 | const customResetCommandsUnsupported = gdbAsync === false || !isRemoteTest; 33 | 34 | describe('custom reset configuration', function () { 35 | let dc: CdtDebugClient; 36 | 37 | beforeEach(async function () { 38 | dc = await standardBeforeEach(gdbtargetAdapter); 39 | }); 40 | 41 | afterEach(async function () { 42 | if (dc) { 43 | await dc.stop(); 44 | } 45 | }); 46 | 47 | const testConnect = async ( 48 | launchArgs: TargetLaunchRequestArguments, 49 | expectToFail: boolean 50 | ) => { 51 | if (expectToFail) { 52 | // Expecting launch to fail, check for correct error message 53 | const expectedErrorMessage = 54 | "Setting 'customResetCommands' requires 'gdbAsync' to be active"; 55 | const rejectError = await expectRejection( 56 | dc.launchRequest(launchArgs) 57 | ); 58 | expect(rejectError.message).to.startWith(expectedErrorMessage); 59 | } else { 60 | // Expecting launch to succeed 61 | const launchResponse = (await dc.launchRequest( 62 | launchArgs 63 | )) as DebugProtocol.LaunchResponse; 64 | expect(launchResponse.success).to.be.true; 65 | } 66 | }; 67 | 68 | it('correctly validates if auxiliary gdb mode can work with other settings', async function () { 69 | if (!isRemoteTest) { 70 | // Only skip remote tests, gdbAsync validation tested here 71 | this.skip(); 72 | } 73 | 74 | const launchArgs = fillDefaults(this.test, { 75 | program: loopForeverProgram, 76 | customResetCommands: commands, 77 | } as TargetLaunchRequestArguments); 78 | 79 | await testConnect(launchArgs, customResetCommandsUnsupported); 80 | }); 81 | }); 82 | 83 | describe('custom reset', function () { 84 | let dc: CdtDebugClient; 85 | 86 | beforeEach(async function () { 87 | dc = await standardBeforeEach(gdbtargetAdapter); 88 | }); 89 | 90 | afterEach(async function () { 91 | await dc.stop(); 92 | }); 93 | 94 | const completeStartup = async function ( 95 | testContext?: Runnable 96 | ): Promise { 97 | // Call here instead of beforeEach so that test can be skipped without 98 | // failing due to argument validation. 99 | await dc.launchRequest( 100 | fillDefaults(testContext, { 101 | program: loopForeverProgram, 102 | customResetCommands: commands, 103 | } as TargetLaunchRequestArguments) 104 | ); 105 | }; 106 | 107 | it('tests sending custom reset commands', async function () { 108 | if (customResetCommandsUnsupported) { 109 | // Command is implemented in GDBDebugSessionBase but deliberately documented 110 | // for gdbtarget (remote) adapter only. So skip this test if not running remote 111 | this.skip(); 112 | } 113 | 114 | await completeStartup(this.test); 115 | 116 | await Promise.all([ 117 | dc.waitForOutputEvent('stdout', expectedResult), 118 | dc.customRequest('cdt-gdb-adapter/customReset'), 119 | ]); 120 | }); 121 | 122 | it('stops the target if necessary before sending custom reset commands', async function () { 123 | if (customResetCommandsUnsupported) { 124 | // Command is implemented in GDBDebugSessionBase but deliberately documented 125 | // for gdbtarget (remote) adapter only. So skip this test if not running remote. 126 | // Skip if not gdbAsync, pauseIfNeeded will otherwise hang in when fetching `$_gthread`. 127 | this.skip(); 128 | } 129 | 130 | await completeStartup(this.test); 131 | 132 | await dc.setFunctionBreakpointsRequest({ 133 | breakpoints: [{ name: 'main' }], 134 | }); 135 | const [stoppedEvent] = await Promise.all([ 136 | dc.waitForEvent('stopped'), 137 | dc.configurationDoneRequest(), 138 | ]); 139 | await dc.setFunctionBreakpointsRequest({ breakpoints: [] }); // remove function breakpoints 140 | 141 | // Let the program run 142 | await dc.continueRequest({ threadId: stoppedEvent.body.threadId }); 143 | 144 | await Promise.all([ 145 | dc.waitForOutputEvent('stdout', expectedResult), // wait stdout event 146 | dc.customRequest('cdt-gdb-adapter/customReset'), 147 | ]); 148 | 149 | // Would throw if it wasn't stopped 150 | await dc.stepInRequest({ threadId: stoppedEvent.body.threadId }); 151 | }); 152 | }); 153 | -------------------------------------------------------------------------------- /src/types/gdb.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2024 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import EventEmitter from 'events'; 11 | import { 12 | MIBreakpointInsertOptions, 13 | MIBreakpointLocation, 14 | MIShowResponse, 15 | } from '../mi'; 16 | import { VarManager } from '../varManager'; 17 | import { Readable, Writable } from 'stream'; 18 | import { 19 | AttachRequestArguments, 20 | LaunchRequestArguments, 21 | TargetAttachRequestArguments, 22 | TargetLaunchRequestArguments, 23 | } from './session'; 24 | import { GDBDebugSessionBase } from '../gdb/GDBDebugSessionBase'; 25 | 26 | export type GetPIDType = { getPID: () => number | undefined }; 27 | 28 | export interface IStdioProcess { 29 | get stdin(): Writable | null; 30 | get stdout(): Readable | null; 31 | get stderr(): Readable | null; 32 | getPID: () => number | undefined; 33 | get exitCode(): number | null; 34 | get signalCode(): NodeJS.Signals | null; 35 | kill: (signal?: NodeJS.Signals) => boolean; 36 | on( 37 | event: 'exit', 38 | listener: (code: number | null, signal: NodeJS.Signals | null) => void 39 | ): this; 40 | on(event: 'error', listener: (err: Error) => void): this; 41 | } 42 | 43 | export interface IGDBProcessManager { 44 | getVersion( 45 | requestArgs?: LaunchRequestArguments | AttachRequestArguments 46 | ): Promise; 47 | start: ( 48 | requestArgs: LaunchRequestArguments | AttachRequestArguments 49 | ) => Promise; 50 | stop: () => Promise; 51 | } 52 | 53 | export interface IGDBServerProcessManager { 54 | start: ( 55 | requestArgs: TargetLaunchRequestArguments 56 | ) => Promise; 57 | stop: () => Promise; 58 | } 59 | 60 | export interface IGDBBackendFactory { 61 | createGDBManager: ( 62 | session: GDBDebugSessionBase, 63 | args: LaunchRequestArguments | AttachRequestArguments 64 | ) => Promise; 65 | createBackend: ( 66 | session: GDBDebugSessionBase, 67 | manager: IGDBProcessManager, 68 | args: LaunchRequestArguments | AttachRequestArguments, 69 | name?: string 70 | ) => Promise; 71 | } 72 | 73 | export interface IGDBServerFactory { 74 | createGDBServerManager: ( 75 | args: TargetLaunchRequestArguments | TargetAttachRequestArguments 76 | ) => Promise; 77 | } 78 | 79 | export interface IGDBBackend extends EventEmitter { 80 | get varManager(): VarManager; 81 | 82 | spawn( 83 | requestArgs: LaunchRequestArguments | AttachRequestArguments 84 | ): Promise; 85 | 86 | setAsyncMode: (isSet?: boolean) => Promise; 87 | 88 | getAsyncMode: () => boolean; 89 | 90 | confirmAsyncMode: () => Promise; 91 | 92 | setNonStopMode: (isSet?: boolean) => Promise; 93 | 94 | isNonStopMode: () => boolean; 95 | 96 | isUseHWBreakpoint: () => boolean; 97 | 98 | // getBreakpointOptions called before inserting the breakpoint and this 99 | // method could overridden in derived classes to dynamically control the 100 | // breakpoint insert options. If an error thrown from this method, then 101 | // the breakpoint will not be inserted. 102 | getBreakpointOptions: ( 103 | _: MIBreakpointLocation, 104 | initialOptions: MIBreakpointInsertOptions 105 | ) => Promise; 106 | 107 | pause: (threadId?: number) => Promise | void; 108 | 109 | sendEnablePrettyPrint: () => Promise; 110 | 111 | sendFileExecAndSymbols: (program: string) => Promise; 112 | 113 | sendFileSymbolFile: (symbols: string) => Promise; 114 | 115 | sendAddSymbolFile: (symbols: string, offset: string) => Promise; 116 | 117 | sendLoad: ( 118 | imageFileName: string, 119 | imageOffset: string | undefined 120 | ) => Promise; 121 | 122 | sendGDBSet: (params: string) => Promise; 123 | 124 | sendGDBShow: (params: string) => Promise; 125 | 126 | sendGDBExit: () => Promise; 127 | 128 | isActive: () => boolean; 129 | 130 | sendCommand(command: string): Promise; 131 | sendCommands(commands?: string[]): Promise; 132 | gdbVersionAtLeast(targetVersion: string): boolean; 133 | 134 | /** Ask GDB for its current thread ID. 135 | * 136 | * Can return 0 when GDB has no threads, or -1 in case of unexpected errors 137 | * (which will in some cases make things work anyway because "--thread -1" 138 | * is equivalent to omitting "--thread" - that is probably an implementation 139 | * detail though). */ 140 | queryCurrentThreadId(): Promise; 141 | 142 | on( 143 | event: 'consoleStreamOutput', 144 | listener: (output: string, category: string) => void 145 | ): this; 146 | on( 147 | event: 'execAsync' | 'notifyAsync' | 'statusAsync' | 'resultAsync', 148 | listener: (asyncClass: string, data: any) => void 149 | ): this; 150 | on( 151 | event: 'exit', 152 | listener: (code: number | null, signal: NodeJS.Signals | null) => void 153 | ): this; 154 | 155 | emit( 156 | event: 'consoleStreamOutput', 157 | output: string, 158 | category: string 159 | ): boolean; 160 | emit( 161 | event: 'execAsync' | 'notifyAsync' | 'statusAsync' | 'resultAsync', 162 | asyncClass: string, 163 | data: any 164 | ): boolean; 165 | emit( 166 | event: 'exit', 167 | code: number | null, 168 | signal: NodeJS.Signals | null 169 | ): boolean; 170 | } 171 | -------------------------------------------------------------------------------- /src/integration-tests/dynamicBreakpointOptions.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2023 Renesas Electronics Corporation and others 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | import * as path from 'path'; 11 | import * as os from 'os'; 12 | import { expect } from 'chai'; 13 | import { CdtDebugClient } from './debugClient'; 14 | import { standardBeforeEach, testProgramsDir, fillDefaults } from './utils'; 15 | 16 | // This mock adapter is overriding the getBreakpointOptions method. 17 | const adapter = 18 | 'integration-tests/mocks/debugAdapters/dynamicBreakpointOptions.js'; 19 | const argHardwareBreakpointTrue = '--hardware-breakpoint-true'; 20 | const argHardwareBreakpointFalse = '--hardware-breakpoint-false'; 21 | const argThrowError = '--throw-error'; 22 | 23 | describe('dynamic breakpoint options with hardware set to false', async () => { 24 | let dc: CdtDebugClient; 25 | 26 | beforeEach(async function () { 27 | // Overriding breakpoint option hardware to false 28 | dc = await standardBeforeEach(adapter, [argHardwareBreakpointFalse]); 29 | await dc.launchRequest( 30 | fillDefaults(this.currentTest, { 31 | program: path.join(testProgramsDir, 'count'), 32 | hardwareBreakpoint: true, 33 | }) 34 | ); 35 | }); 36 | 37 | afterEach(async () => { 38 | await dc.stop(); 39 | }); 40 | 41 | it('insert breakpoint as software breakpoint', async () => { 42 | const bpResp = await dc.setBreakpointsRequest({ 43 | source: { 44 | name: 'count.c', 45 | path: path.join(testProgramsDir, 'count.c'), 46 | }, 47 | breakpoints: [ 48 | { 49 | column: 1, 50 | line: 4, 51 | }, 52 | ], 53 | }); 54 | expect(bpResp.body.breakpoints.length).eq(1); 55 | expect(bpResp.body.breakpoints[0].verified).eq(true); 56 | expect(bpResp.body.breakpoints[0].message).eq(undefined); 57 | await dc.configurationDoneRequest(); 58 | let isCorrect; 59 | let outputs; 60 | while (!isCorrect) { 61 | // Cover the case of getting event in Linux environment. 62 | // If cannot get correct event, program timeout and test case failed. 63 | outputs = await dc.waitForEvent('output'); 64 | isCorrect = outputs.body.output.includes('breakpoint-modified'); 65 | } 66 | expect(outputs?.body.output).includes('type="breakpoint"'); 67 | }); 68 | }); 69 | 70 | describe('dynamic breakpoint options with hardware set to true', async () => { 71 | let dc: CdtDebugClient; 72 | 73 | beforeEach(async function () { 74 | // Overriding breakpoint option hardware to true 75 | dc = await standardBeforeEach(adapter, [argHardwareBreakpointTrue]); 76 | await dc.launchRequest( 77 | fillDefaults(this.currentTest, { 78 | program: path.join(testProgramsDir, 'count'), 79 | hardwareBreakpoint: false, 80 | }) 81 | ); 82 | }); 83 | 84 | afterEach(async () => { 85 | await dc.stop(); 86 | }); 87 | 88 | it('insert breakpoint as hardware breakpoint', async function () { 89 | // Hardware breakpoints are not supported for Windows 90 | if (os.platform() === 'win32') { 91 | this.skip(); 92 | } 93 | const bpResp = await dc.setBreakpointsRequest({ 94 | source: { 95 | name: 'count.c', 96 | path: path.join(testProgramsDir, 'count.c'), 97 | }, 98 | breakpoints: [ 99 | { 100 | column: 1, 101 | line: 4, 102 | }, 103 | ], 104 | }); 105 | expect(bpResp.body.breakpoints.length).eq(1); 106 | expect(bpResp.body.breakpoints[0].verified).eq(true); 107 | expect(bpResp.body.breakpoints[0].message).eq(undefined); 108 | await dc.configurationDoneRequest(); 109 | let isCorrect; 110 | let outputs; 111 | while (!isCorrect) { 112 | // Cover the case of getting event in Linux environment. 113 | // If cannot get correct event, program timeout and test case failed. 114 | outputs = await dc.waitForEvent('output'); 115 | isCorrect = outputs.body.output.includes('breakpoint-modified'); 116 | } 117 | expect(outputs?.body.output).includes('type="hw breakpoint"'); 118 | }); 119 | }); 120 | 121 | describe('dynamic breakpoint options with throwing error', async () => { 122 | let dc: CdtDebugClient; 123 | 124 | beforeEach(async function () { 125 | // Overriding breakpoint options and throwing error when getBreakpointOptions invoked 126 | dc = await standardBeforeEach(adapter, [argThrowError]); 127 | await dc.launchRequest( 128 | fillDefaults(this.currentTest, { 129 | program: path.join(testProgramsDir, 'count'), 130 | hardwareBreakpoint: false, 131 | }) 132 | ); 133 | }); 134 | 135 | afterEach(async () => { 136 | await dc.stop(); 137 | }); 138 | 139 | it('insert breakpoint is not performed', async () => { 140 | const bpResp = await dc.setBreakpointsRequest({ 141 | source: { 142 | name: 'count.c', 143 | path: path.join(testProgramsDir, 'count.c'), 144 | }, 145 | breakpoints: [ 146 | { 147 | column: 1, 148 | line: 4, 149 | }, 150 | ], 151 | }); 152 | expect(bpResp.body.breakpoints.length).eq(1); 153 | expect(bpResp.body.breakpoints[0].verified).eq(false); 154 | expect(bpResp.body.breakpoints[0].message).not.eq(undefined); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /src/integration-tests/config.spec.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Copyright (c) 2023 Kichwa Coders Canada Inc. and others. 3 | * 4 | * This program and the accompanying materials are made 5 | * available under the terms of the Eclipse Public License 2.0 6 | * which is available at https://www.eclipse.org/legal/epl-2.0/ 7 | * 8 | * SPDX-License-Identifier: EPL-2.0 9 | *********************************************************************/ 10 | 11 | import * as path from 'path'; 12 | import * as tmp from 'tmp'; 13 | import * as fs from 'fs'; 14 | import { 15 | LaunchRequestArguments, 16 | AttachRequestArguments, 17 | } from '../types/session'; 18 | import { 19 | debugServerPort, 20 | defaultAdapter, 21 | fillDefaults, 22 | standardBeforeEach, 23 | testProgramsDir, 24 | } from './utils'; 25 | 26 | describe('config', function () { 27 | const emptyProgram = path.join(testProgramsDir, 'empty'); 28 | const emptySrc = path.join(testProgramsDir, 'empty.c'); 29 | 30 | async function verifyLaunchWorks( 31 | test: Mocha.Context, 32 | commandLine: string[], 33 | requestArgs: LaunchRequestArguments 34 | ) { 35 | if (debugServerPort) { 36 | // This test requires launching the adapter to work 37 | test.skip(); 38 | } 39 | 40 | const dc = await standardBeforeEach(defaultAdapter, commandLine); 41 | 42 | try { 43 | await dc.hitBreakpoint(fillDefaults(test.test, requestArgs), { 44 | path: emptySrc, 45 | line: 3, 46 | }); 47 | } finally { 48 | await dc.stop(); 49 | } 50 | } 51 | 52 | it('can specify program via --config=', async function () { 53 | const config = { program: emptyProgram }; 54 | await verifyLaunchWorks( 55 | this, 56 | [`--config=${JSON.stringify(config)}`], 57 | {} as LaunchRequestArguments 58 | ); 59 | }); 60 | 61 | it('program via --config= can be overridden', async function () { 62 | const config = { program: '/program/that/does/not/exist' }; 63 | await verifyLaunchWorks(this, [`--config=${JSON.stringify(config)}`], { 64 | program: emptyProgram, 65 | } as LaunchRequestArguments); 66 | }); 67 | 68 | it('can specify program via --config-frozen=', async function () { 69 | const config = { program: emptyProgram }; 70 | await verifyLaunchWorks( 71 | this, 72 | [`--config-frozen=${JSON.stringify(config)}`], 73 | {} as LaunchRequestArguments 74 | ); 75 | }); 76 | 77 | it('program via --config-frozen= can not be overridden', async function () { 78 | const config = { program: emptyProgram }; 79 | await verifyLaunchWorks( 80 | this, 81 | [`--config-frozen=${JSON.stringify(config)}`], 82 | { 83 | program: '/program/that/does/not/exist', 84 | } as LaunchRequestArguments 85 | ); 86 | }); 87 | 88 | it('can specify program via --config= using response file', async function () { 89 | const config = { program: emptyProgram }; 90 | const json = JSON.stringify(config); 91 | const jsonFile = tmp.fileSync(); 92 | fs.writeFileSync(jsonFile.fd, json); 93 | fs.closeSync(jsonFile.fd); 94 | 95 | await verifyLaunchWorks( 96 | this, 97 | [`--config=@${jsonFile.name}`], 98 | {} as LaunchRequestArguments 99 | ); 100 | }); 101 | 102 | it('can specify program via --config-frozen= using response file', async function () { 103 | const config = { program: emptyProgram }; 104 | const json = JSON.stringify(config); 105 | const jsonFile = tmp.fileSync(); 106 | fs.writeFileSync(jsonFile.fd, json); 107 | fs.closeSync(jsonFile.fd); 108 | 109 | await verifyLaunchWorks( 110 | this, 111 | [`--config-frozen=@${jsonFile.name}`], 112 | {} as LaunchRequestArguments 113 | ); 114 | }); 115 | 116 | // This test most closely models the original design goal 117 | // for the change that added --config and --config-frozen 118 | // as discussed in #227 and #228 119 | // In summary we force a launch request for the given program, 120 | // but the user does not specify the program and specifies 121 | // an attach request 122 | it('config frozen forces specific launch type', async function () { 123 | if (debugServerPort) { 124 | // This test requires launching the adapter to work 125 | this.skip(); 126 | } 127 | 128 | const config = { request: 'launch', program: emptyProgram }; 129 | 130 | // Launch the adapter with the frozen config 131 | const dc = await standardBeforeEach(defaultAdapter, [ 132 | `--config-frozen=${JSON.stringify(config)}`, 133 | ]); 134 | 135 | try { 136 | await Promise.all([ 137 | // Do an attach request omitting the program that we want 138 | // the adapter to force into a launch request 139 | dc.attachRequest( 140 | fillDefaults(this.test, {} as AttachRequestArguments) 141 | ), 142 | 143 | // The rest of this code is to ensure we launcher properly by verifying 144 | // we can run to a breakpoint 145 | dc.waitForEvent('initialized').then((_event) => { 146 | return dc 147 | .setBreakpointsRequest({ 148 | lines: [3], 149 | breakpoints: [{ line: 3 }], 150 | source: { path: emptySrc }, 151 | }) 152 | .then((_response) => { 153 | return dc.configurationDoneRequest(); 154 | }); 155 | }), 156 | dc.assertStoppedLocation('breakpoint', { 157 | path: emptySrc, 158 | line: 3, 159 | }), 160 | ]); 161 | } finally { 162 | await dc.stop(); 163 | } 164 | }); 165 | }); 166 | --------------------------------------------------------------------------------