Skip to content

Commit 41ce39f

Browse files
committed
Better terminal re-use method
1 parent 3777907 commit 41ce39f

File tree

7 files changed

+374
-338
lines changed

7 files changed

+374
-338
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
ChangeLog
2+
3+
# V0.3.14.pre1
4+
New Features:
5+
* Support for RTT (SEGGER RealTimeTrace) with OpenOCD. JLink is not supported yet
6+
See [Issue#456](https://github.com/Marus/cortex-debug/issues/456) for more info
7+
* `demangle` is by default true. You can turn it off in `launch.json`
8+
29
# V0.3.13
310

411
New Features:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1904,5 +1904,5 @@
19041904
"compile": "webpack --mode development",
19051905
"test-compile": "tsc -p ./"
19061906
},
1907-
"version": "0.3.13-pre1"
1907+
"version": "0.3.14-pre1"
19081908
}

src/frontend/extension.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { FileSWOSource } from './swo/sources/file';
1919
import { SerialSWOSource } from './swo/sources/serial';
2020
import { DisassemblyContentProvider } from './disassembly_content_provider';
2121
import { SymbolInformation, SymbolScope } from '../symbols';
22-
import { RTTTerminal } from './rtt_terminal';
22+
import { RTTTerminal, TerminalServer } from './rtt_terminal';
2323

2424
const commandExistsSync = require('command-exists').sync;
2525
interface SVDInfo {
@@ -36,6 +36,7 @@ export class CortexDebugExtension {
3636
private rttSources: SocketRTTSource[] = [];
3737
private rttTerminals: RTTTerminal[] = [];
3838
private rttPortMap: { [channel: number]: string} = {};
39+
private rttTermServer = new TerminalServer();
3940

4041

4142
private peripheralProvider: PeripheralTreeProvider;
@@ -546,10 +547,10 @@ export class CortexDebugExtension {
546547
});
547548
}
548549

549-
private rttCreateTerninal(decoder: RTTConsoleDecoderOpts) {
550+
private async rttCreateTerninal(decoder: RTTConsoleDecoderOpts) {
550551
for (const terminal of this.rttTerminals) {
551-
if (terminal.canReuse(decoder)) {
552-
terminal.inUse = true;
552+
const success = await terminal.tryReuse(decoder);
553+
if (success) {
553554
if (vscode.debug.activeDebugConsole) {
554555
vscode.debug.activeDebugConsole.appendLine(
555556
`Reusing RTT terminal for channel ${decoder.port} on tcp port ${decoder.tcpPort}`
@@ -565,7 +566,7 @@ export class CortexDebugExtension {
565566
if (!this.nodeExecExists) {
566567
vscode.window.showErrorMessage('RTT terminal needs "node" to be installed. Visit\nhttps://nodejs.org\nto doanload and install')
567568
} else {
568-
const newTerminal = new RTTTerminal(this.context, decoder);
569+
const newTerminal = new RTTTerminal(this.context, decoder, this.rttTermServer);
569570
if (newTerminal.startTerminal()) {
570571
this.rttTerminals.push(newTerminal);
571572
if (vscode.debug.activeDebugConsole) {

src/frontend/rtt_terminal.ts

Lines changed: 172 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,44 @@
11
import * as vscode from 'vscode';
22
import * as path from 'path';
33
import * as fs from 'fs';
4-
import { RTTConsoleDecoderOpts, TerminalInputMode } from '../common';
4+
import * as net from 'net';
5+
import { getAnyFreePort, RTTConsoleDecoderOpts, TerminalInputMode } from '../common';
56
import { SWORTTSource } from './swo/sources/common';
67
import EventEmitter = require('events');
8+
import { getNonce } from './swo/common';
9+
10+
export interface IRTTTerminalOptions
11+
{
12+
port : string;
13+
prompt : string;
14+
noprompt : boolean;
15+
clear : boolean;
16+
logfile : string;
17+
inputmode : TerminalInputMode;
18+
binary : boolean;
19+
scale : number;
20+
encoding : string;
21+
nonce : string;
22+
}
23+
24+
export class RTTTerminalOptions implements IRTTTerminalOptions {
25+
constructor(options: RTTConsoleDecoderOpts,
26+
public nonce,
27+
public port = options.tcpPort,
28+
public prompt = options.prompt || `RTT-${options.port}> `,
29+
public noprompt = !!options.noprompt,
30+
public clear = !!options.clear,
31+
public logfile = options.logfile || '',
32+
public inputmode = options.inputmode,
33+
public binary = options.type === 'binary',
34+
public scale = options.scale || 1,
35+
public encoding = options.encoding || (options.type === 'binary' ? 'unsigned' : 'utf8'),
36+
) {}
37+
}
38+
739
export class RTTTerminal extends EventEmitter implements SWORTTSource {
40+
protected termOptions: RTTTerminalOptions;
41+
protected nonce = getNonce();
842
connected: boolean;
943
protected _rttTerminal: vscode.Terminal = null;
1044
public get rttTerminal(): vscode.Terminal {
@@ -26,8 +60,10 @@ export class RTTTerminal extends EventEmitter implements SWORTTSource {
2660

2761
constructor(
2862
protected context: vscode.ExtensionContext,
29-
public options: RTTConsoleDecoderOpts) {
63+
public options: RTTConsoleDecoderOpts,
64+
protected rttTermServer: TerminalServer) {
3065
super();
66+
this.termOptions = new RTTTerminalOptions(options, this.nonce);
3167
}
3268
dispose() {
3369
// process.kill(this.rttTerminal.processId)
@@ -40,47 +76,26 @@ export class RTTTerminal extends EventEmitter implements SWORTTSource {
4076
}
4177

4278
public startTerminal(): boolean {
79+
if (this.connected) {
80+
return true;
81+
}
4382
const script = path.join(this.context.extensionPath, 'dist', 'tcp_cat.bundle.js');
44-
this._name = RTTTerminal.createTermName(this.options);
83+
this._name = RTTTerminal.createTermName(this.options, null);
4584
const args = {
4685
name: this.name,
4786
shellPath: 'node',
4887
shellArgs: [script,
49-
"--port", this.options.tcpPort, // Can be [host:]port
50-
"--encoding", this.options.encoding || 'utf8'
88+
"--useserver", this.rttTermServer.portNumber().toString(),
89+
"--nonce", this.termOptions.nonce
5190
]
5291
};
5392

54-
if (this.options.noprompt) {
55-
args.shellArgs.push('--noprompt');
56-
} else {
57-
args.shellArgs.push('--prompt', this.options.prompt || `RTT-${this.options.port}> `);
58-
}
59-
60-
if (this.options.clear) {
61-
args.shellArgs.push('--clear');
62-
}
63-
if (this.options.inputmode === TerminalInputMode.RAW) {
64-
args.shellArgs.push('--raw');
65-
} else if (this.options.inputmode === TerminalInputMode.RAWECHO) {
66-
args.shellArgs.push('--rawecho');
67-
}
68-
69-
if (this.options.type === 'binary') {
70-
args.shellArgs.push('--binary');
71-
if (this.options.scale) {
72-
args.shellArgs.push('--scale', `${this.options.scale}`);
73-
}
74-
}
75-
7693
if (this.options.logfile) {
7794
try {
7895
fs.writeFileSync(this.options.logfile, "");
79-
args.shellArgs.push('--logfile', this.options.logfile);
8096
}
8197
catch (e) {
82-
vscode.window.showErrorMessage(
83-
`RTT logging failed ${this.options.logfile}: ${e.toString()}`);
98+
vscode.window.showErrorMessage(`RTT logging failed ${this.options.logfile}: ${e.toString()}`);
8499
}
85100
}
86101

@@ -89,6 +104,8 @@ export class RTTTerminal extends EventEmitter implements SWORTTSource {
89104
setTimeout(() => {
90105
this._rttTerminal.show();
91106
}, 100);
107+
this.rttTermServer.addClient(this.nonce);
108+
this.sendOptions(this.termOptions);
92109
this.connected = true;
93110
this.inUse = true;
94111
return true;
@@ -99,24 +116,144 @@ export class RTTTerminal extends EventEmitter implements SWORTTSource {
99116
}
100117
}
101118

102-
static createTermName(options: RTTConsoleDecoderOpts): string {
119+
static createTermName(options: RTTConsoleDecoderOpts, existing: string | null): string {
103120
const channel = options.port || 0;
104121
const orig = options.label || `RTT Ch:${channel}`;
105122
let ret = orig;
106123
let count = 1;
107124
while (vscode.window.terminals.findIndex((t) => t.name === ret) >= 0) {
125+
if (existing === ret) {
126+
return existing;
127+
}
108128
ret = `${orig}-${count}`;
109129
count = count + 1;
110130
}
111131
return ret;
112132
}
113133

114-
public canReuse(options: RTTConsoleDecoderOpts) {
115-
for (const prop of ['type', 'tcpPort', 'port', 'label', 'prompt', 'noprompt', 'logfile', 'clear', 'scale']) {
116-
if (options[prop] !== this.options[prop]) {
134+
// If all goes well, this will reset the terminal options. The original port (meaning channel) name
135+
// has to match and the label for the VSCode terminal has to match. If successful, tt will reset the
136+
// Terminal options and mark it as used (inUse = true) as well.
137+
public async tryReuse(options: RTTConsoleDecoderOpts): Promise<boolean> {
138+
if (this.options.port === options.port) {
139+
// See if we are going to get the same label as before because that cannot be changed
140+
const newName = RTTTerminal.createTermName(options, this._rttTerminal.name);
141+
if (newName !== this._rttTerminal.name) {
117142
return false;
118143
}
144+
const termOptions = new RTTTerminalOptions(options, this.termOptions.nonce);
145+
const ret = await this.sendOptions(termOptions);
146+
return ret;
147+
}
148+
return false;
149+
}
150+
151+
private sendOptions(options: RTTTerminalOptions): boolean {
152+
const str = JSON.stringify(options) + '\n';
153+
if (this.rttTermServer.sendToClient(this.nonce, str)) {
154+
this.termOptions = options;
155+
this.inUse = true;
156+
return true;
119157
}
120-
return true;
158+
return false;
159+
}
160+
}
161+
162+
export class TerminalServer {
163+
protected server: net.Server = null;
164+
protected port: number;
165+
protected socketByNonce: Map<string, net.Socket> = new Map();
166+
protected nonceBySocket: Map<net.Socket, string> = new Map();
167+
168+
constructor() {
169+
this.createServer();
170+
}
171+
172+
public portNumber(): number { return this.port; }
173+
public isConnected(): boolean { return !!this.server; }
174+
private createServer() {
175+
getAnyFreePort(55000).then((x) => {
176+
this.port = x;
177+
const newServer = net.createServer(this.onNewClient.bind(this));
178+
newServer.listen(this.port, 'localhost', () => {
179+
this.server = newServer;
180+
});
181+
newServer.on(('error'), (e) => {
182+
console.log(e);
183+
});
184+
newServer.on('close', () => { this.server = null; });
185+
}).catch((e) => {
186+
});
187+
}
188+
189+
protected onNewClient(socket: net.Socket) {
190+
console.log('New client connected');
191+
socket.setKeepAlive(true);
192+
socket.on('close', () => {
193+
console.log('client closed');
194+
const nonce = this.nonceBySocket.get(socket);
195+
if (nonce) {
196+
this.nonceBySocket.delete(socket);
197+
this.socketByNonce.delete(nonce);
198+
}
199+
});
200+
socket.on('data', (data) => {
201+
const str = data.toString().trim();
202+
if (this.socketByNonce.has(str)) {
203+
// Client is alive and responded with proper nonce
204+
this.socketByNonce.set(str, socket);
205+
this.nonceBySocket.set(socket, str);
206+
} else {
207+
console.error(`Unknown message '${str}' from client`);
208+
}
209+
});
210+
socket.on('error', (e) => {
211+
console.error(`client error ${e}`)
212+
});
213+
}
214+
215+
public dispose() {
216+
if (this.server) {
217+
this.server.close();
218+
this.server = null;
219+
}
220+
}
221+
222+
public sendToClient(nonce: string, data: string | Buffer): Promise<boolean> {
223+
return new Promise<boolean>((resolve) => {
224+
if (!this.socketByNonce.has(nonce)) {
225+
resolve(false);
226+
return;
227+
}
228+
let socket: net.Socket = this.socketByNonce[nonce];
229+
function send() {
230+
try {
231+
socket.write(data);
232+
socket.uncork();
233+
resolve(true);
234+
}
235+
catch (e) {
236+
resolve(false);
237+
}
238+
}
239+
if (socket) {
240+
send();
241+
} else {
242+
// This can happen the very first time we are sending something. Technically, return
243+
// should be a promise
244+
const interval = setInterval(() => {
245+
socket = this.socketByNonce.get(nonce);
246+
if (socket) {
247+
clearInterval(interval);
248+
send();
249+
}
250+
}, 1);
251+
}
252+
});
253+
}
254+
255+
public addClient(nonce:string) {
256+
console.log(`adding client ${nonce}`);
257+
this.socketByNonce.set(nonce, null);
121258
}
122259
}

src/frontend/swo/common.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export interface Packet {
124124

125125
export function parseHostPort(hostPort: string) {
126126
let port: number;
127-
let host = 'localhost';
127+
let host = '127.0.0.1';
128128
const match = hostPort.match(/(.*)\:([0-9]+)/);
129129
if (match) {
130130
host = match[1] ? match[1] : host;
@@ -134,3 +134,12 @@ export function parseHostPort(hostPort: string) {
134134
}
135135
return { port: port, host: host };
136136
}
137+
138+
export function getNonce() {
139+
let text = '';
140+
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
141+
for (let i = 0; i < 32; i++) {
142+
text += possible.charAt(Math.floor(Math.random() * possible.length));
143+
}
144+
return text;
145+
}

src/frontend/swo/core.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import { SWORTTSource } from './sources/common';
1010
import { SWODecoderConfig, GraphConfiguration, SWOAdvancedDecoderConfig,
1111
SWOBinaryDecoderConfig, SWOConsoleDecoderConfig, SWOGraphDecoderConfig,
1212
SWOBasicDecoderConfig, GrapherMessage, GrapherStatusMessage,
13-
GrapherProgramCounterMessage } from './common';
13+
GrapherProgramCounterMessage,
14+
getNonce} from './common';
1415
import { SWORTTAdvancedProcessor } from './decoders/advanced';
1516
import { EventEmitter } from 'events';
1617
import { PacketType, Packet } from './common';
@@ -204,23 +205,14 @@ class SWOWebview {
204205

205206
private getHTML() {
206207
const scriptUri = vscode.Uri.file(path.join(this.extensionPath, 'dist', 'grapher.bundle.js')).with({ scheme: 'vscode-resource' });
207-
const nonce = this.getNonce();
208+
const nonce = getNonce();
208209

209210
let html = fs.readFileSync(path.join(this.extensionPath, 'resources', 'grapher.html'), { encoding: 'utf8', flag: 'r' });
210211
html = html.replace(/\$\{nonce\}/g, nonce).replace(/\$\{scriptUri\}/g, scriptUri.toString());
211212

212213
return html;
213214
}
214215

215-
private getNonce() {
216-
let text = '';
217-
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
218-
for (let i = 0; i < 32; i++) {
219-
text += possible.charAt(Math.floor(Math.random() * possible.length));
220-
}
221-
return text;
222-
}
223-
224216
private processors: Array<SWORTTGraphProcessor | SWORTTAdvancedProcessor> = [];
225217
public registerProcessors(processor: SWORTTGraphProcessor | SWORTTAdvancedProcessor): void {
226218
processor.on('message', this.sendMessage.bind(this));

0 commit comments

Comments
 (0)