Skip to content

Commit 6a610a0

Browse files
committed
deps: re-implement debugger-agent
Reviewed-By: Trevor Norris <[email protected]> PR-URL: nodejs/node-v0.x-archive#8476
1 parent d71dd63 commit 6a610a0

File tree

17 files changed

+1009
-93
lines changed

17 files changed

+1009
-93
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ CPPLINT_EXCLUDE += src/queue.h
406406
CPPLINT_EXCLUDE += src/tree.h
407407
CPPLINT_EXCLUDE += src/v8abbr.h
408408

409-
CPPLINT_FILES = $(filter-out $(CPPLINT_EXCLUDE), $(wildcard src/*.cc src/*.h src/*.c tools/icu/*.h tools/icu/*.cc))
409+
CPPLINT_FILES = $(filter-out $(CPPLINT_EXCLUDE), $(wildcard src/*.cc src/*.h src/*.c tools/icu/*.h tools/icu/*.cc deps/debugger-agent/include/* deps/debugger-agent/src/*))
410410

411411
cpplint:
412412
@$(PYTHON) tools/cpplint.py $(CPPLINT_FILES)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"targets": [{
3+
"target_name": "debugger-agent",
4+
"type": "<(library)",
5+
"include_dirs": [
6+
"src",
7+
"include",
8+
"../v8/include",
9+
"../uv/include",
10+
11+
# Private node.js folder and stuff needed to include from it
12+
"../../src",
13+
"../cares/include",
14+
],
15+
"direct_dependent_settings": {
16+
"include_dirs": [
17+
"include",
18+
],
19+
},
20+
"sources": [
21+
"src/agent.cc",
22+
],
23+
}],
24+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright Fedor Indutny and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
#ifndef DEPS_DEBUGGER_AGENT_INCLUDE_DEBUGGER_AGENT_H_
23+
#define DEPS_DEBUGGER_AGENT_INCLUDE_DEBUGGER_AGENT_H_
24+
25+
#include "uv.h"
26+
#include "v8.h"
27+
#include "v8-debug.h"
28+
29+
namespace node {
30+
31+
// Forward declaration
32+
class Environment;
33+
34+
namespace debugger {
35+
36+
// Forward declaration
37+
class AgentMessage;
38+
39+
class Agent {
40+
public:
41+
explicit Agent(node::Environment* env);
42+
~Agent();
43+
44+
typedef void (*DispatchHandler)(node::Environment* env);
45+
46+
// Start the debugger agent thread
47+
bool Start(int port, bool wait);
48+
// Listen for debug events
49+
void Enable();
50+
// Stop the debugger agent
51+
void Stop();
52+
53+
inline void set_dispatch_handler(DispatchHandler handler) {
54+
dispatch_handler_ = handler;
55+
}
56+
57+
inline node::Environment* parent_env() const { return parent_env_; }
58+
inline node::Environment* child_env() const { return child_env_; }
59+
60+
protected:
61+
void InitAdaptor(Environment* env);
62+
63+
// Worker body
64+
void WorkerRun();
65+
66+
static void ThreadCb(Agent* agent);
67+
static void ParentSignalCb(uv_async_t* signal);
68+
static void ChildSignalCb(uv_async_t* signal);
69+
static void MessageHandler(const v8::Debug::Message& message);
70+
71+
// V8 API
72+
static Agent* Unwrap(const v8::FunctionCallbackInfo<v8::Value>& args);
73+
static void NotifyListen(const v8::FunctionCallbackInfo<v8::Value>& args);
74+
static void NotifyWait(const v8::FunctionCallbackInfo<v8::Value>& args);
75+
static void SendCommand(const v8::FunctionCallbackInfo<v8::Value>& args);
76+
77+
void EnqueueMessage(AgentMessage* message);
78+
79+
enum State {
80+
kNone,
81+
kRunning
82+
};
83+
84+
// TODO(indutny): Verify that there are no races
85+
State state_;
86+
87+
int port_;
88+
bool wait_;
89+
90+
uv_sem_t start_sem_;
91+
uv_mutex_t message_mutex_;
92+
uv_async_t child_signal_;
93+
94+
uv_thread_t thread_;
95+
node::Environment* parent_env_;
96+
node::Environment* child_env_;
97+
uv_loop_t child_loop_;
98+
v8::Persistent<v8::Object> api_;
99+
100+
// QUEUE
101+
void* messages_[2];
102+
103+
DispatchHandler dispatch_handler_;
104+
};
105+
106+
} // namespace debugger
107+
} // namespace node
108+
109+
#endif // DEPS_DEBUGGER_AGENT_INCLUDE_DEBUGGER_AGENT_H_
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
var assert = require('assert');
2+
var net = require('net');
3+
var util = require('util');
4+
var Buffer = require('buffer').Buffer;
5+
6+
var Transform = require('stream').Transform;
7+
8+
exports.start = function start() {
9+
var agent = new Agent();
10+
11+
// Do not let `agent.listen()` request listening from cluster master
12+
var cluster = require('cluster');
13+
cluster.isWorker = false;
14+
cluster.isMaster = true;
15+
16+
agent.on('error', function(err) {
17+
process._rawDebug(err.stack || err);
18+
});
19+
20+
agent.listen(process._debugAPI.port, function() {
21+
var addr = this.address();
22+
process._rawDebug('Debugger listening on port %d', addr.port);
23+
process._debugAPI.notifyListen();
24+
});
25+
26+
// Just to spin-off events
27+
// TODO(indutny): Figure out why node.cc isn't doing this
28+
setImmediate(function() {
29+
});
30+
31+
process._debugAPI.onclose = function() {
32+
// We don't care about it, but it prevents loop from cleaning up gently
33+
// NOTE: removeAllListeners won't work, as it doesn't call `removeListener`
34+
process.listeners('SIGWINCH').forEach(function(fn) {
35+
process.removeListener('SIGWINCH', fn);
36+
});
37+
38+
agent.close();
39+
};
40+
41+
// Not used now, but anyway
42+
return agent;
43+
};
44+
45+
function Agent() {
46+
net.Server.call(this, this.onConnection);
47+
48+
this.first = true;
49+
this.binding = process._debugAPI;
50+
51+
var self = this;
52+
this.binding.onmessage = function(msg) {
53+
self.clients.forEach(function(client) {
54+
client.send({}, msg);
55+
});
56+
};
57+
58+
this.clients = [];
59+
assert(this.binding, 'Debugger agent running without bindings!');
60+
}
61+
util.inherits(Agent, net.Server);
62+
63+
Agent.prototype.onConnection = function onConnection(socket) {
64+
var c = new Client(this, socket);
65+
66+
c.start();
67+
this.clients.push(c);
68+
69+
var self = this;
70+
c.once('close', function() {
71+
var index = self.clients.indexOf(c);
72+
assert(index !== -1);
73+
self.clients.splice(index, 1);
74+
});
75+
};
76+
77+
Agent.prototype.notifyWait = function notifyWait() {
78+
if (this.first)
79+
this.binding.notifyWait();
80+
this.first = false;
81+
};
82+
83+
function Client(agent, socket) {
84+
Transform.call(this);
85+
this._readableState.objectMode = true;
86+
87+
this.agent = agent;
88+
this.binding = this.agent.binding;
89+
this.socket = socket;
90+
91+
// Parse incoming data
92+
this.state = 'headers';
93+
this.headers = {};
94+
this.buffer = '';
95+
socket.pipe(this);
96+
97+
this.on('data', this.onCommand);
98+
99+
var self = this;
100+
this.socket.on('close', function() {
101+
self.destroy();
102+
});
103+
}
104+
util.inherits(Client, Transform);
105+
106+
Client.prototype.destroy = function destroy(msg) {
107+
this.socket.destroy();
108+
109+
this.emit('close');
110+
};
111+
112+
Client.prototype._transform = function _transform(data, enc, cb) {
113+
cb();
114+
115+
this.buffer += data;
116+
117+
while (true) {
118+
if (this.state === 'headers') {
119+
// Not enough data
120+
if (!/\r\n/.test(this.buffer))
121+
break;
122+
123+
if (/^\r\n/.test(this.buffer)) {
124+
this.buffer = this.buffer.slice(2);
125+
this.state = 'body';
126+
continue;
127+
}
128+
129+
// Match:
130+
// Header-name: header-value\r\n
131+
var match = this.buffer.match(/^([^:\s\r\n]+)\s*:\s*([^\s\r\n]+)\r\n/);
132+
if (!match)
133+
return this.destroy('Expected header, but failed to parse it');
134+
135+
this.headers[match[1].toLowerCase()] = match[2];
136+
137+
this.buffer = this.buffer.slice(match[0].length);
138+
} else {
139+
var len = this.headers['content-length'];
140+
if (len === undefined)
141+
return this.destroy('Expected content-length');
142+
143+
len = len | 0;
144+
if (Buffer.byteLength(this.buffer) < len)
145+
break;
146+
147+
this.push(new Command(this.headers, this.buffer.slice(0, len)));
148+
this.state = 'headers';
149+
this.buffer = this.buffer.slice(len);
150+
this.headers = {};
151+
}
152+
}
153+
};
154+
155+
Client.prototype.send = function send(headers, data) {
156+
if (!data)
157+
data = '';
158+
159+
var out = [];
160+
Object.keys(headers).forEach(function(key) {
161+
out.push(key + ': ' + headers[key]);
162+
});
163+
out.push('Content-Length: ' + Buffer.byteLength(data), '');
164+
165+
this.socket.cork();
166+
this.socket.write(out.join('\r\n') + '\r\n');
167+
168+
if (data.length > 0)
169+
this.socket.write(data);
170+
this.socket.uncork();
171+
};
172+
173+
Client.prototype.start = function start() {
174+
this.send({
175+
Type: 'connect',
176+
'V8-Version': process.versions.v8,
177+
'Protocol-Version': 1,
178+
'Embedding-Host': 'node ' + process.version
179+
});
180+
};
181+
182+
Client.prototype.onCommand = function onCommand(cmd) {
183+
this.binding.sendCommand(cmd.body);
184+
185+
this.agent.notifyWait();
186+
};
187+
188+
function Command(headers, body) {
189+
this.headers = headers;
190+
this.body = body;
191+
}

0 commit comments

Comments
 (0)