Skip to content

Commit f1141d1

Browse files
committed
Refactor control socket and support win32 and linux.
1 parent 1087d8d commit f1141d1

File tree

6 files changed

+149
-41
lines changed

6 files changed

+149
-41
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
66

7+
8+
## [Unreleased]
9+
10+
- Xdebug control socket support for pause on Linux and Windows.
711
## [1.35.0]
812

913
- Support for DBGp stream command

package-lock.json

Lines changed: 15 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
"@vscode/debugadapter": "^1.57.0",
4747
"@vscode/debugprotocol": "^1.55.1",
4848
"@xmldom/xmldom": "^0.8.4",
49-
"abstract-socket": "^2.1.1",
5049
"buffer-crc32": "^0.2.13",
5150
"dotenv": "^16.0.3",
5251
"file-url": "^3.0.0",
@@ -60,6 +59,9 @@
6059
"urlencode": "^1.1.0",
6160
"which": "^2.0.2"
6261
},
62+
"optionalDependencies": {
63+
"abstract-socket": "^2.1.1"
64+
},
6365
"devDependencies": {
6466
"@commitlint/cli": "^17.3.0",
6567
"@commitlint/config-conventional": "^17.3.0",

src/abstract-socket.d.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
/// <reference types="node" />
2-
declare module "abstract-socket" {
3-
export function createServer(listener: any): AbstractSocketServer;
4-
export function connect(name: any, connectListener: any): net.Socket;
5-
export function createConnection(name: any, connectListener: any): net.Socket;
2+
declare module 'abstract-socket' {
3+
export function createServer(listener: any): AbstractSocketServer
4+
export function connect(name: any, connectListener: any): net.Socket
5+
export function createConnection(name: any, connectListener: any): net.Socket
66
class AbstractSocketServer extends net.Server {
77
// constructor(listener: any);
88
// listen(path: string, listener: () => void): net.Socket;
99
// listen(path: string, listeningListener?: () => void): this;
1010
}
11-
import net = require("net");
12-
export {};
11+
import net = require('net')
12+
export {}
1313
}

src/controlSocket.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import * as xdebug from './xdebugConnection'
2+
import * as semver from 'semver'
3+
import * as net from 'net'
4+
5+
export class ControlSocket {
6+
/**
7+
* @returns Returns true if the current platoform is supported for Xdebug control socket (win and linux)
8+
*/
9+
supportedPlatform(): boolean {
10+
return process.platform === 'linux' || process.platform === 'win32'
11+
}
12+
13+
/**
14+
*
15+
* @param initPacket
16+
* @returns Returns true if the current platform and xdebug version are supporting Xdebug control socket.
17+
*/
18+
supportedInitPacket(initPacket: xdebug.InitPacket): boolean {
19+
return (
20+
this.supportedPlatform() &&
21+
initPacket.engineName === 'Xdebug' &&
22+
semver.valid(initPacket.engineVersion, { loose: true }) !== null &&
23+
(semver.gte(initPacket.engineVersion, '3.4.0', { loose: true }) || initPacket.engineVersion === '3.4.0-dev')
24+
)
25+
}
26+
27+
/**
28+
*
29+
* @param pid appId returned in the init packet
30+
* @returns
31+
*/
32+
async requestPause(pid: string): Promise<void> {
33+
let retval
34+
if (process.platform === 'linux') {
35+
retval = await this.executeLinux(pid, 'pause')
36+
} else if (process.platform === 'win32') {
37+
retval = await this.executeWindows(pid, 'pause')
38+
} else {
39+
throw new Error('Invalid platform for Xdebug control socket')
40+
}
41+
retval
42+
return
43+
}
44+
45+
private async executeLinux(pid: string, cmd: string): Promise<string> {
46+
const abs = await import('abstract-socket')
47+
return new Promise<string>((resolve, reject) => {
48+
const cs = `\0xdebug-ctrl.${pid}y`.padEnd(108, 'x')
49+
try {
50+
const s = abs.connect(cs, () => {
51+
s.write(`${cmd}\0`)
52+
})
53+
s.setTimeout(3000)
54+
s.on('timeout', () => {
55+
reject(new Error('Timed out while reading from Xdebug control socket'))
56+
s.end()
57+
})
58+
s.on('data', data => {
59+
resolve(data.toString())
60+
})
61+
s.on('error', error => {
62+
reject(
63+
new Error(
64+
`Cannot connect to Xdebug control socket: ${String(
65+
error instanceof Error ? error.message : error
66+
)}`
67+
)
68+
)
69+
})
70+
return
71+
} catch (error) {
72+
reject(
73+
new Error(
74+
`Cannot connect to Xdebug control socket: ${String(
75+
error instanceof Error ? error.message : error
76+
)}`
77+
)
78+
)
79+
return
80+
}
81+
})
82+
}
83+
84+
private async executeWindows(pid: string, cmd: string): Promise<string> {
85+
return new Promise<string>((resolve, reject) => {
86+
const s = net.createConnection(`\\\\.\\pipe\\xdebug-ctrl.${pid}`, () => {
87+
s.end(`${cmd}\0`)
88+
})
89+
s.setTimeout(3000)
90+
s.on('timeout', () => {
91+
reject(new Error('Timed out while reading from Xdebug control socket'))
92+
s.end()
93+
})
94+
s.on('data', data => {
95+
resolve(data.toString())
96+
})
97+
s.on('error', error => {
98+
// sadly this happens all the time - even on normal server-side-close, but luckily the promise is already resolved
99+
reject(
100+
new Error(
101+
`Cannot connect to Xdebug control socket: ${String(
102+
error instanceof Error ? error.message : error
103+
)}`
104+
)
105+
)
106+
})
107+
})
108+
}
109+
}

src/phpDebug.ts

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { randomUUID } from 'crypto'
1919
import { getConfiguredEnvironment } from './envfile'
2020
import { XdebugCloudConnection } from './cloud'
2121
import { shouldIgnoreException } from './ignore'
22-
import * as abs from 'abstract-socket'
22+
import { ControlSocket } from './controlSocket'
2323

2424
if (process.env['VSCODE_NLS_CONFIG']) {
2525
try {
@@ -1387,43 +1387,26 @@ class PhpDebugSession extends vscode.DebugSession {
13871387
response: VSCodeDebugProtocol.PauseResponse,
13881388
args: VSCodeDebugProtocol.PauseArguments
13891389
): Promise<void> {
1390-
// test
1391-
let connection: xdebug.Connection | undefined
1392-
connection = this._connections.get(args.threadId)
1390+
const connection = this._connections.get(args.threadId)
13931391
if (!connection) {
13941392
return this.sendErrorResponse(response, new Error(`Unknown thread ID ${args.threadId}`))
13951393
}
13961394

1397-
let initPacket = await connection.waitForInitPacket()
1398-
const supportedControl =
1399-
process.platform === 'linux' &&
1400-
initPacket.engineName === 'Xdebug' &&
1401-
semver.valid(initPacket.engineVersion, { loose: true }) &&
1402-
(semver.gte(initPacket.engineVersion, '3.4.0', { loose: true }) || initPacket.engineVersion === '3.4.0-dev')
1403-
1404-
if (supportedControl) {
1405-
let cs = `\0xdebug-ctrl.${initPacket.appId}y`.padEnd(108, 'x')
1395+
const initPacket = await connection.waitForInitPacket()
1396+
const controlSocket = new ControlSocket()
1397+
if (controlSocket.supportedInitPacket(initPacket)) {
1398+
if (this._skippingFiles.has(args.threadId)) {
1399+
this._skippingFiles.set(args.threadId, false)
1400+
}
14061401
try {
1407-
const sock = abs.connect(cs, () => {
1408-
sock.write("pause\0")
1409-
if (this._skippingFiles.has(args.threadId)) {
1410-
this._skippingFiles.set(args.threadId, false)
1411-
}
1412-
this.sendResponse(response)
1413-
})
1414-
sock.on('data', (data) => {
1415-
// todo
1416-
})
1417-
sock.on('error', (error) => {
1418-
this.sendErrorResponse(response, new Error('Cannot connect to Xdebug control socket'))
1419-
})
1402+
await controlSocket.requestPause(initPacket.appId)
1403+
this.sendResponse(response)
14201404
return
14211405
} catch (error) {
1422-
this.sendErrorResponse(response, new Error('Cannot connect to Xdebug control socket'))
1423-
return
1406+
this.sendErrorResponse(response, error as Error)
14241407
}
14251408
}
1426-
// test
1409+
14271410
if (this._skippingFiles.has(args.threadId)) {
14281411
this._skippingFiles.set(args.threadId, false)
14291412
this.sendResponse(response)

0 commit comments

Comments
 (0)