Skip to content
This repository was archived by the owner on Apr 22, 2023. It is now read-only.

Commit 66f64ae

Browse files
sblompiscisaureus
authored andcommitted
windows: generate ETW events to track v8 compiled code positions
Patch by Henry Rawas and Scott Blomquist.
1 parent 2f5fa08 commit 66f64ae

File tree

4 files changed

+306
-13
lines changed

4 files changed

+306
-13
lines changed

src/node_win32_etw_provider-inl.h

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ extern int events_enabled;
4242
#define ETW_WRITE_INT32_DATA(data_descriptor, data) \
4343
EventDataDescCreate(data_descriptor, data, sizeof(int32_t));
4444

45+
#define ETW_WRITE_INT64_DATA(data_descriptor, data) \
46+
EventDataDescCreate(data_descriptor, data, sizeof(int64_t));
47+
48+
#define ETW_WRITE_ADDRESS_DATA(data_descriptor, data) \
49+
EventDataDescCreate(data_descriptor, data, sizeof(intptr_t));
50+
51+
#define ETW_WRITE_INT16_DATA(data_descriptor, data) \
52+
EventDataDescCreate(data_descriptor, data, sizeof(int16_t));
53+
54+
#define ETW_WRITE_WSTRING_DATA_LENGTH(data_descriptor, data, data_len_bytes) \
55+
EventDataDescCreate(data_descriptor, \
56+
data, \
57+
data_len_bytes);
58+
4559
#define ETW_WRITE_NET_CONNECTION(descriptors, conn) \
4660
ETW_WRITE_INT32_DATA(descriptors, &conn->fd); \
4761
ETW_WRITE_INT32_DATA(descriptors + 1, &conn->port); \
@@ -61,6 +75,34 @@ extern int events_enabled;
6175
ETW_WRITE_INT32_DATA(descriptors, &type); \
6276
ETW_WRITE_INT32_DATA(descriptors + 1, &flags);
6377

78+
#define ETW_WRITE_V8ADDRESSCHANGE(descriptors, addr1, addr2) \
79+
ETW_WRITE_ADDRESS_DATA(descriptors, &addr1); \
80+
ETW_WRITE_ADDRESS_DATA(descriptors + 1, &addr2);
81+
82+
#define ETW_WRITE_JSMETHOD_LOADUNLOAD(descriptors, \
83+
context, \
84+
startAddr, \
85+
size, \
86+
id, \
87+
flags, \
88+
rangeId, \
89+
sourceId, \
90+
line, \
91+
col, \
92+
name, \
93+
name_len_bytes) \
94+
ETW_WRITE_ADDRESS_DATA(descriptors, &context); \
95+
ETW_WRITE_ADDRESS_DATA(descriptors + 1, &startAddr); \
96+
ETW_WRITE_INT64_DATA(descriptors + 2, &size); \
97+
ETW_WRITE_INT32_DATA(descriptors + 3, &id); \
98+
ETW_WRITE_INT16_DATA(descriptors + 4, &flags); \
99+
ETW_WRITE_INT16_DATA(descriptors + 5, &rangeId); \
100+
ETW_WRITE_INT64_DATA(descriptors + 6, &sourceId); \
101+
ETW_WRITE_INT32_DATA(descriptors + 7, &line); \
102+
ETW_WRITE_INT32_DATA(descriptors + 8, &col); \
103+
ETW_WRITE_WSTRING_DATA_LENGTH(descriptors + 9, name, name_len_bytes);
104+
105+
64106
#define ETW_WRITE_EVENT(eventDescriptor, dataDescriptors) \
65107
DWORD status = event_write(node_provider, \
66108
&eventDescriptor, \
@@ -133,6 +175,83 @@ void NODE_GC_DONE(GCType type, GCCallbackFlags flags) {
133175
}
134176

135177

178+
void NODE_V8SYMBOL_REMOVE(const void* addr1, const void* addr2) {
179+
if (events_enabled > 0) {
180+
EVENT_DATA_DESCRIPTOR descriptors[2];
181+
ETW_WRITE_V8ADDRESSCHANGE(descriptors, addr1, addr2);
182+
ETW_WRITE_EVENT(NODE_V8SYMBOL_REMOVE_EVENT, descriptors);
183+
}
184+
}
185+
186+
187+
void NODE_V8SYMBOL_MOVE(const void* addr1, const void* addr2) {
188+
if (events_enabled > 0) {
189+
EVENT_DATA_DESCRIPTOR descriptors[2];
190+
ETW_WRITE_V8ADDRESSCHANGE(descriptors, addr1, addr2);
191+
ETW_WRITE_EVENT(NODE_V8SYMBOL_MOVE_EVENT, descriptors);
192+
}
193+
}
194+
195+
196+
void NODE_V8SYMBOL_RESET() {
197+
if (events_enabled > 0) {
198+
int val = 0;
199+
EVENT_DATA_DESCRIPTOR descriptors[1];
200+
ETW_WRITE_INT32_DATA(descriptors, &val);
201+
ETW_WRITE_EVENT(NODE_V8SYMBOL_RESET_EVENT, descriptors);
202+
}
203+
}
204+
205+
#define SETSYMBUF(s) \
206+
wcscpy(symbuf, s); \
207+
symbol_len = ARRAY_SIZE(s) - 1;
208+
209+
void NODE_V8SYMBOL_ADD(LPCSTR symbol,
210+
int symbol_len,
211+
const void* addr1,
212+
int len) {
213+
if (events_enabled > 0) {
214+
wchar_t symbuf[128];
215+
if (symbol == NULL) {
216+
SETSYMBUF(L"NULL");
217+
} else {
218+
symbol_len = MultiByteToWideChar(CP_ACP, 0, symbol, symbol_len, symbuf, 128);
219+
if (symbol_len == 0) {
220+
SETSYMBUF(L"Invalid");
221+
} else {
222+
if (symbol_len > 127) {
223+
symbol_len = 127;
224+
}
225+
symbuf[symbol_len] = L'\0';
226+
}
227+
}
228+
void* context = NULL;
229+
INT64 size = (INT64)len;
230+
INT32 id = (INT32)addr1;
231+
INT16 flags = 0;
232+
INT16 rangeid = 1;
233+
INT32 col = 1;
234+
INT32 line = 1;
235+
INT64 sourceid = 0;
236+
EVENT_DATA_DESCRIPTOR descriptors[10];
237+
ETW_WRITE_JSMETHOD_LOADUNLOAD(descriptors,
238+
context,
239+
addr1,
240+
size,
241+
id,
242+
flags,
243+
rangeid,
244+
sourceid,
245+
line,
246+
col,
247+
symbuf,
248+
symbol_len * sizeof(symbuf[0]));
249+
ETW_WRITE_EVENT(MethodLoad, descriptors);
250+
}
251+
}
252+
#undef SETSYMBUF
253+
254+
136255
bool NODE_HTTP_SERVER_REQUEST_ENABLED() { return events_enabled > 0; }
137256
bool NODE_HTTP_SERVER_RESPONSE_ENABLED() { return events_enabled > 0; }
138257
bool NODE_HTTP_CLIENT_REQUEST_ENABLED() { return events_enabled > 0; }
@@ -141,5 +260,6 @@ bool NODE_NET_SERVER_CONNECTION_ENABLED() { return events_enabled > 0; }
141260
bool NODE_NET_STREAM_END_ENABLED() { return events_enabled > 0; }
142261
bool NODE_NET_SOCKET_READ_ENABLED() { return events_enabled > 0; }
143262
bool NODE_NET_SOCKET_WRITE_ENABLED() { return events_enabled > 0; }
263+
bool NODE_V8SYMBOL_ENABLED() { return events_enabled > 0; }
144264
}
145265
#endif // SRC_ETW_INL_H_

src/node_win32_etw_provider.cc

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "node_dtrace.h"
2323
#include "node_win32_etw_provider.h"
2424
#include "node_etw_provider.h"
25+
#include "node_win32_etw_provider-inl.h"
2526

2627
namespace node {
2728

@@ -33,10 +34,110 @@ EventRegisterFunc event_register;
3334
EventUnregisterFunc event_unregister;
3435
EventWriteFunc event_write;
3536
int events_enabled;
37+
static uv_async_t dispatch_etw_events_change_async;
38+
39+
struct v8tags {
40+
char prefix[32 - sizeof(size_t)];
41+
size_t prelen;
42+
};
43+
44+
// The v8 CODE_ADDED event name has a prefix indicating the type of event.
45+
// Many of these are internal to v8.
46+
// The trace_codes array specifies which types are written.
47+
struct v8tags trace_codes[] = {
48+
#define MAKE_V8TAG(s) { s, sizeof(s) - 1 }
49+
MAKE_V8TAG("LazyCompile:"),
50+
MAKE_V8TAG("Script:"),
51+
MAKE_V8TAG("Function:"),
52+
MAKE_V8TAG("RegExp:"),
53+
MAKE_V8TAG("Eval:")
54+
#undef MAKE_V8TAG
55+
};
56+
57+
/* Below are some code prefixes which are not being written.
58+
* "Builtin:"
59+
* "Stub:"
60+
* "CallIC:"
61+
* "LoadIC:"
62+
* "KeyedLoadIC:"
63+
* "StoreIC:"
64+
* "KeyedStoreIC:"
65+
* "CallPreMonomorphic:"
66+
* "CallInitialize:"
67+
* "CallMiss:"
68+
* "CallMegamorphic:"
69+
*/
70+
71+
// v8 sometimes puts a '*' or '~' in front of the name.
72+
#define V8_MARKER1 '*'
73+
#define V8_MARKER2 '~'
74+
75+
76+
// If prefix is not in filtered list return -1,
77+
// else return length of prefix and marker.
78+
int FilterCodeEvents(const char* name, size_t len) {
79+
for (int i = 0; i < ARRAY_SIZE(trace_codes); i++) {
80+
size_t prelen = trace_codes[i].prelen;
81+
if (prelen < len) {
82+
if (strncmp(name, trace_codes[i].prefix, prelen) == 0) {
83+
if (name[prelen] == V8_MARKER1 || name[prelen] == V8_MARKER2)
84+
prelen++;
85+
return prelen;
86+
}
87+
}
88+
}
89+
return -1;
90+
}
91+
92+
93+
// callback from V8 module passes symbol and address info for stack walk
94+
void CodeAddressNotification(const JitCodeEvent* jevent) {
95+
int pre_offset = 0;
96+
if (NODE_V8SYMBOL_ENABLED()) {
97+
switch (jevent->type) {
98+
case JitCodeEvent::CODE_ADDED:
99+
pre_offset = FilterCodeEvents(jevent->name.str, jevent->name.len);
100+
if (pre_offset >= 0) {
101+
// skip over prefix and marker
102+
NODE_V8SYMBOL_ADD(jevent->name.str + pre_offset,
103+
jevent->name.len - pre_offset,
104+
jevent->code_start,
105+
jevent->code_len);
106+
}
107+
break;
108+
case JitCodeEvent::CODE_REMOVED:
109+
NODE_V8SYMBOL_REMOVE(jevent->code_start, 0);
110+
break;
111+
case JitCodeEvent::CODE_MOVED:
112+
NODE_V8SYMBOL_MOVE(jevent->code_start, jevent->new_code_start);
113+
break;
114+
default:
115+
break;
116+
}
117+
}
118+
}
119+
120+
121+
// Call v8 to enable or disable code event callbacks.
122+
// Must be on default thread to do this.
123+
// Note: It is possible to call v8 from ETW thread, but then
124+
// event callbacks are received in the same thread. Attempts
125+
// to write ETW events in this thread will fail.
126+
void etw_events_change_async(uv_async_t* handle, int status) {
127+
if (events_enabled > 0) {
128+
NODE_V8SYMBOL_RESET();
129+
V8::SetJitCodeEventHandler(v8::kJitCodeEventEnumExisting,
130+
CodeAddressNotification);
131+
} else {
132+
V8::SetJitCodeEventHandler(v8::kJitCodeEventDefault, NULL);
133+
}
134+
}
135+
36136

37137
// This callback is called by ETW when consumers of our provider
38138
// are enabled or disabled.
39139
// The callback is dispatched on ETW thread.
140+
// Before calling into V8 to enable code events, switch to default thread.
40141
void NTAPI etw_events_enable_callback(
41142
LPCGUID SourceId,
42143
ULONG IsEnabled,
@@ -47,8 +148,14 @@ void NTAPI etw_events_enable_callback(
47148
PVOID CallbackContext) {
48149
if (IsEnabled) {
49150
events_enabled++;
151+
if (events_enabled == 1) {
152+
uv_async_send(&dispatch_etw_events_change_async);
153+
}
50154
} else {
51155
events_enabled--;
156+
if (events_enabled == 0) {
157+
uv_async_send(&dispatch_etw_events_change_async);
158+
}
52159
}
53160
}
54161

@@ -64,6 +171,12 @@ void init_etw() {
64171
GetProcAddress(advapi, "EventUnregister");
65172
event_write = (EventWriteFunc)GetProcAddress(advapi, "EventWrite");
66173

174+
// create async object used to invoke main thread from callback
175+
uv_async_init(uv_default_loop(),
176+
&dispatch_etw_events_change_async,
177+
etw_events_change_async);
178+
uv_unref((uv_handle_t*) &dispatch_etw_events_change_async);
179+
67180
if (event_register) {
68181
DWORD status = event_register(&NODE_ETW_PROVIDER,
69182
etw_events_enable_callback,
@@ -82,6 +195,7 @@ void shutdown_etw() {
82195
}
83196

84197
events_enabled = 0;
198+
V8::SetJitCodeEventHandler(v8::kJitCodeEventDefault, NULL);
85199

86200
if (advapi) {
87201
FreeLibrary(advapi);

src/node_win32_etw_provider.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ INLINE void NODE_NET_SERVER_CONNECTION(node_dtrace_connection_t* conn);
6666
INLINE void NODE_NET_STREAM_END(node_dtrace_connection_t* conn);
6767
INLINE void NODE_GC_START(GCType type, GCCallbackFlags flags);
6868
INLINE void NODE_GC_DONE(GCType type, GCCallbackFlags flags);
69+
INLINE void NODE_V8SYMBOL_REMOVE(const void* addr1, const void* addr2);
70+
INLINE void NODE_V8SYMBOL_MOVE(const void* addr1, const void* addr2);
71+
INLINE void NODE_V8SYMBOL_RESET();
72+
INLINE void NODE_V8SYMBOL_ADD(LPCSTR symbol,
73+
int symbol_len,
74+
const void* addr1,
75+
int len);
6976

7077
INLINE bool NODE_HTTP_SERVER_REQUEST_ENABLED();
7178
INLINE bool NODE_HTTP_SERVER_RESPONSE_ENABLED();
@@ -75,6 +82,7 @@ INLINE bool NODE_NET_SERVER_CONNECTION_ENABLED();
7582
INLINE bool NODE_NET_STREAM_END_ENABLED();
7683
INLINE bool NODE_NET_SOCKET_READ_ENABLED();
7784
INLINE bool NODE_NET_SOCKET_WRITE_ENABLED();
85+
INLINE bool NODE_V8SYMBOL_ENABLED();
7886

7987
#define NODE_NET_SOCKET_READ(arg0, arg1)
8088
#define NODE_NET_SOCKET_WRITE(arg0, arg1)

0 commit comments

Comments
 (0)