Skip to content

Commit 2514622

Browse files
committed
implemented pipe communication
1 parent ec1a528 commit 2514622

File tree

9 files changed

+248
-26
lines changed

9 files changed

+248
-26
lines changed

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ Note that the path to `lldebugger` will automatically be appended to the `LUA_PA
6767

6868
---
6969
## Requirements & Limitations
70-
- The Lua environment must support communication via stdio.
71-
- Some enviroments may require command line options to support this (ex. Solar2D requires `/no-console` flag)
72-
- Use of `io.read` or other calls that require user input will cause problems
70+
- The Lua environment must support communication via either stdio or pipes (named pipes on Windows, fifos on Linux).
71+
- Some enviroments may require command line options to support stdio communication (ex. Solar2D requires `/no-console` flag)
72+
- Use of `io.read` or other calls that require user input will cause problems in stdio mode. Set [`program.communication`](#program.communication) to `pipe` to work around this.
7373
- The Lua environment must be built with the `debug` library, and no other code should attempt to set debug hooks.
7474
- You cannot manually pause debugging while the program is running.
7575
- In Lua 5.1 and LuaJIT, the main thread cannot be accessed while stopped inside of a coroutine.
@@ -135,6 +135,14 @@ List of arguments to pass to Lua script or custom environment when launching.
135135

136136
Specify environment variables to set when launching executable.
137137

138+
#### `program.communication`
139+
140+
Specifies how the extension communicates with the debugger.
141+
142+
Possible values:
143+
- `stdio` (default): Messages are embeded in stdin and stdout.
144+
- `pipe`: Pipes are created for passing messages (named pipes on Windows, fifos on Linux). Use this if your environment has issues with stdio communication.
145+
138146
#### `verbose`
139147

140148
Enable verbose output from debugger. Only useful when trying to identify problems with the debugger itself.

debugger/debugger.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,23 @@ export namespace Debugger {
5858
(this: void, ...args: unknown[]): LuaMultiReturn<unknown[]>;
5959
}
6060

61-
const prompt = "";
6261
const debuggerName = "lldebugger.lua";
6362
const builtinFunctionPrefix = "[builtin:";
6463

64+
const inputFileEnv: LuaDebug.InputFileEnv = "LOCAL_LUA_DEBUGGER_INPUT_FILE";
65+
const inputFilePath = os.getenv(inputFileEnv);
66+
let inputFile: LuaFile;
67+
if (inputFilePath && inputFilePath.length > 0) {
68+
const [file, err] = io.open(inputFilePath, "r+");
69+
if (!file) {
70+
luaError(`Failed to open input file "${inputFilePath}": ${err}\n`);
71+
}
72+
inputFile = file as LuaFile;
73+
} else {
74+
inputFile = io.stdin;
75+
}
76+
inputFile.setvbuf("no");
77+
6578
let skipNextBreak = false;
6679

6780
const enum HookType {
@@ -395,10 +408,7 @@ export namespace Debugger {
395408
}
396409

397410
function getInput(): string | undefined {
398-
if (prompt.length > 0) {
399-
io.write(prompt);
400-
}
401-
const inp = io.read("*l");
411+
const inp = inputFile.read("*l");
402412
return inp;
403413
}
404414

debugger/lldebugger.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ import {Debugger} from "./debugger";
2727
declare const ____exports: unknown;
2828
_G.lldebugger = _G.lldebugger || ____exports;
2929

30-
//Don't buffer io
31-
io.stdout.setvbuf("no");
32-
io.stderr.setvbuf("no");
33-
3430
//Start debugger globally
3531
export function start(breakImmediately?: boolean): void {
3632
if (breakImmediately === undefined) {

debugger/protocol.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,6 @@ declare namespace LuaDebug {
113113

114114
type ScriptRootsEnv = "LOCAL_LUA_DEBUGGER_SCRIPT_ROOTS";
115115
type BreakInCoroutinesEnv = "LOCAL_LUA_DEBUGGER_BREAK_IN_COROUTINES";
116+
type InputFileEnv = "LOCAL_LUA_DEBUGGER_INPUT_FILE";
117+
type OutputFileEnv = "LOCAL_LUA_DEBUGGER_OUTPUT_FILE";
116118
}

debugger/send.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
//SOFTWARE.
2222

23-
import {luaRawLen} from "./luafuncs";
23+
import {luaError, luaRawLen} from "./luafuncs";
2424
import {Format} from "./format";
2525
import {Vars} from "./debugger";
2626
import {Thread, mainThread, mainThreadName} from "./thread";
@@ -30,6 +30,20 @@ export namespace Send {
3030
const startToken: LuaDebug.StartToken = "@lldbg|";
3131
const endToken: LuaDebug.EndToken = "|lldbg@";
3232

33+
const outputFileEnv: LuaDebug.OutputFileEnv = "LOCAL_LUA_DEBUGGER_OUTPUT_FILE";
34+
const outputFilePath = os.getenv(outputFileEnv);
35+
let outputFile: LuaFile;
36+
if (outputFilePath && outputFilePath.length > 0) {
37+
const [file, err] = io.open(outputFilePath, "w+");
38+
if (!file) {
39+
luaError(`Failed to open output file "${outputFilePath}": ${err}\n`);
40+
}
41+
outputFile = file as LuaFile;
42+
} else {
43+
outputFile = io.stdout;
44+
}
45+
outputFile.setvbuf("no");
46+
3347
function getPrintableValue(value: unknown) {
3448
const valueType = type(value);
3549
if (valueType === "string") {
@@ -63,7 +77,7 @@ export namespace Send {
6377
}
6478

6579
function send(message: LuaDebug.MessageBase) {
66-
io.write(`${startToken}${Format.asJson(message)}${endToken}`);
80+
outputFile.write(`${startToken}${Format.asJson(message)}${endToken}`);
6781
}
6882

6983
export function error(err: string): void {
@@ -186,6 +200,6 @@ export namespace Send {
186200
const [name, desc] = unpack(nameAndDesc);
187201
table.insert(builtStrs, `${name}${string.rep(" ", nameLength - name.length + 1)}: ${desc}`);
188202
}
189-
io.write(`${table.concat(builtStrs, "\n")}\n`);
203+
outputFile.write(`${table.concat(builtStrs, "\n")}\n`);
190204
}
191205
}

extension/debugPipe.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import * as crypto from "crypto";
2+
import * as net from "net";
3+
import * as childProcess from "child_process";
4+
import * as fs from "fs";
5+
6+
export interface DebugPipe {
7+
open: (onData: (data: unknown) => void, onError: (err: unknown) => void) => void;
8+
close: () => void;
9+
write: (data: string) => void;
10+
getOutputPipePath: () => string;
11+
getInputPipePath: () => string;
12+
}
13+
14+
export function createNamedPipe(): DebugPipe {
15+
const pipeId = crypto.randomBytes(16).toString("hex");
16+
const outputPipePath = `\\\\.\\pipe\\lldbg_out_${pipeId}`;
17+
const inputPipePath = `\\\\.\\pipe\\lldbg_in_${pipeId}`;
18+
let outputPipe: net.Server | null = null;
19+
let inputPipe: net.Server | null = null;
20+
let inputStream: net.Socket | null;
21+
return {
22+
open: (onData, onError) => {
23+
outputPipe = net.createServer(
24+
stream => {
25+
stream.on("data", onData);
26+
stream.on("error", err => onError(`error on output pipe: ${err}`));
27+
}
28+
);
29+
outputPipe.listen(outputPipePath);
30+
inputPipe = net.createServer(
31+
stream => {
32+
stream.on("error", err => onError(`error on input pipe: ${err}`));
33+
inputStream = stream;
34+
}
35+
);
36+
inputPipe.listen(inputPipePath);
37+
},
38+
39+
close: () => {
40+
outputPipe?.close();
41+
outputPipe = null;
42+
inputPipe?.close();
43+
inputPipe = null;
44+
inputStream = null;
45+
},
46+
47+
write: data => {
48+
inputStream?.write(data);
49+
},
50+
51+
getOutputPipePath: () => outputPipePath,
52+
getInputPipePath: () => inputPipePath,
53+
};
54+
}
55+
56+
export function createFifoPipe(): DebugPipe {
57+
const pipeId = crypto.randomBytes(16).toString("hex");
58+
const outputPipePath = `/tmp/lldbg_out_${pipeId}`;
59+
const inputPipePath = `/tmp/lldbg_in_${pipeId}`;
60+
let outputFd: number | null;
61+
let inputFd: number | null;
62+
let inputStream: fs.WriteStream | null = null;
63+
let onErrorCallback: ((err: unknown) => void) | null = null;
64+
return {
65+
open: (onData, onError) => {
66+
onErrorCallback = onError;
67+
68+
childProcess.exec(
69+
`mkfifo ${outputPipePath}`,
70+
fifoErr => {
71+
if (fifoErr) {
72+
onError(`error executing mkfifo for output pipe: ${fifoErr}`);
73+
return;
74+
}
75+
76+
fs.open(
77+
outputPipePath,
78+
fs.constants.O_RDWR,
79+
(fdErr, fd) => {
80+
if (fdErr) {
81+
onError(`error opening fifo for output pipe: ${fdErr}`);
82+
return;
83+
}
84+
85+
outputFd = fd;
86+
const outputStream = fs.createReadStream(null as unknown as fs.PathLike, {fd});
87+
outputStream.on("data", onData);
88+
}
89+
);
90+
}
91+
);
92+
93+
childProcess.exec(
94+
`mkfifo ${inputPipePath}`,
95+
fifoErr => {
96+
if (fifoErr) {
97+
onError(`error executing mkfifo for input pipe: ${fifoErr}`);
98+
return;
99+
}
100+
101+
fs.open(
102+
inputPipePath,
103+
fs.constants.O_RDWR,
104+
(fdErr, fd) => {
105+
if (fdErr) {
106+
onError(`error opening fifo for input pipe: ${fdErr}`);
107+
return;
108+
}
109+
110+
inputFd = fd;
111+
inputStream = fs.createWriteStream(null as unknown as fs.PathLike, {fd});
112+
}
113+
);
114+
}
115+
);
116+
117+
},
118+
119+
close: () => {
120+
if (outputFd !== null) {
121+
fs.close(outputFd);
122+
outputFd = null;
123+
fs.rm(
124+
outputPipePath,
125+
err => {
126+
if (err) {
127+
onErrorCallback?.(`error removing fifo for output pipe: ${err}`);
128+
}
129+
}
130+
);
131+
}
132+
if (inputFd !== null) {
133+
fs.close(inputFd);
134+
inputFd = null;
135+
fs.rm(
136+
inputPipePath,
137+
err => {
138+
if (err) {
139+
onErrorCallback?.(`error removing fifo for input pipe: ${err}`);
140+
}
141+
}
142+
);
143+
}
144+
},
145+
146+
write: data => {
147+
inputStream?.write(data);
148+
},
149+
150+
getOutputPipePath: () => outputPipePath,
151+
getInputPipePath: () => inputPipePath,
152+
};
153+
}

extension/launchConfig.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@
2323
export interface LuaProgramConfig {
2424
lua: string;
2525
file: string;
26+
communication?: string;
2627
}
2728

2829
export interface CustomProgramConfig {
2930
command: string;
31+
communication?: string;
3032
}
3133

3234
export interface LaunchConfig {

0 commit comments

Comments
 (0)