Skip to content

Commit a692ed5

Browse files
authored
otel-thread-ctx: publish V8 + ObjectWrap layout constants for readers (#375)
* otel-thread-ctx: publish V8 + ObjectWrap layout constants for readers Extends the OTEP-4719 process-context attributes emitted by getProcessContextAttributes with three new entries an out-of-process eBPF reader needs to walk from our TLS discovery struct to the actual OTEP-4947 record: - threadlocal.native_wrap_fields_offset — sizeof(node::ObjectWrap). Given a pointer to a CtxWrap fetched via wrapped_object_offset, add this to reach CtxWrap::record_. - threadlocal.js_map_table_offset — offset within a V8 JSMap object of the tagged pointer to its backing OrderedHashMap table (JSCollection::kTableOffset in V8, 0x18). - threadlocal.ordered_hash_map_header_size — size of the header preceding the element_count / deleted_element_count / n_buckets fields inside an OrderedHashMap (0x10). Without these, a reader implementing the Node.js schema would have to hardcode them itself and re-verify them per V8 version. Keeping the constants in the addon puts one source of truth close to V8. The two V8 internal offsets are not exposed in V8's public headers (v8-internal.h); they live in Node's private V8 tree (deps/v8/src/objects/{js-collection,ordered-hash-table}.h) — we cite those paths in the code comments so a future V8 layout change has a clear pointer to what to re-check. New non-Linux fallback values match the Linux ones (24 / 0x18 / 0x10); the reader contract is Linux-only per the OTEP anyway, so this just keeps the exported surface consistent in shape across platforms.
1 parent b08a56c commit a692ed5

3 files changed

Lines changed: 59 additions & 11 deletions

File tree

bindings/otel-thread-ctx.cc

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,28 @@ constexpr int WRAPPED_OBJECT_OFFSET = 0;
679679
#endif
680680
constexpr int TAGGED_SIZE = v8::internal::kApiTaggedSize;
681681

682+
// sizeof(node::ObjectWrap). Given a pointer to a CtxWrap — or any other
683+
// ObjectWrap-derived C++ object attached to a JSObject via the V8
684+
// wrapped-object slot — add this offset to reach the derived class's own
685+
// fields. For CtxWrap, that's `record_` (see the static_assert on its
686+
// offset above).
687+
constexpr int NATIVE_WRAP_FIELDS_OFFSET =
688+
static_cast<int>(sizeof(node::ObjectWrap));
689+
690+
// V8 JSMap layout: kTableOffset within the JSMap object holds a tagged
691+
// pointer to the backing OrderedHashMap table. Not exposed in V8's
692+
// public headers; kept in sync with
693+
// deps/v8/src/objects/js-collection.h (JSCollection::kTableOffset)
694+
// and the torque-generated JSCollection layout.
695+
constexpr int JS_MAP_TABLE_OFFSET = 0x18;
696+
697+
// V8 OrderedHashMap layout: the on-heap table starts with a 16-byte
698+
// header before the element_count / deleted_element_count /
699+
// number_of_buckets fields. Not exposed in V8's public headers; kept in
700+
// sync with deps/v8/src/objects/ordered-hash-table.h
701+
// (OrderedHashTable base layout).
702+
constexpr int ORDERED_HASH_MAP_HEADER_SIZE = 0x10;
703+
682704
} // namespace
683705

684706
void OtelThreadCtx::Init(Local<Object> exports) {
@@ -688,17 +710,19 @@ void OtelThreadCtx::Init(Local<Object> exports) {
688710

689711
Isolate* isolate = Isolate::GetCurrent();
690712
Local<Context> ctx = isolate->GetCurrentContext();
691-
exports
692-
->Set(ctx,
693-
String::NewFromUtf8Literal(isolate,
694-
"otelThreadCtxWrappedObjectOffset"),
695-
Integer::New(isolate, WRAPPED_OBJECT_OFFSET))
696-
.FromJust();
697-
exports
698-
->Set(ctx,
699-
String::NewFromUtf8Literal(isolate, "otelThreadCtxTaggedSize"),
700-
Integer::New(isolate, TAGGED_SIZE))
701-
.FromJust();
713+
auto publish_int = [&](const char* name, int value) {
714+
exports
715+
->Set(ctx,
716+
String::NewFromUtf8(isolate, name).ToLocalChecked(),
717+
Integer::New(isolate, value))
718+
.FromJust();
719+
};
720+
publish_int("otelThreadCtxJsMapTableOffset", JS_MAP_TABLE_OFFSET);
721+
publish_int("otelThreadCtxNativeWrapFieldsOffset", NATIVE_WRAP_FIELDS_OFFSET);
722+
publish_int("otelThreadCtxOrderedHashMapHeaderSize",
723+
ORDERED_HASH_MAP_HEADER_SIZE);
724+
publish_int("otelThreadCtxTaggedSize", TAGGED_SIZE);
725+
publish_int("otelThreadCtxWrappedObjectOffset", WRAPPED_OBJECT_OFFSET);
702726
}
703727

704728
} // namespace dd

ts/src/otel-thread-ctx.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ export interface ProcessContextAttributes {
4040
readonly 'threadlocal.attribute_key_map': readonly string[];
4141
readonly 'threadlocal.wrapped_object_offset': number;
4242
readonly 'threadlocal.tagged_size': number;
43+
readonly 'threadlocal.native_wrap_fields_offset': number;
44+
readonly 'threadlocal.js_map_table_offset': number;
45+
readonly 'threadlocal.ordered_hash_map_header_size': number;
4346
}
4447

4548
/**
@@ -103,6 +106,9 @@ interface Addon {
103106
otelThreadCtxGetStoredAlsHash(): number;
104107
otelThreadCtxWrappedObjectOffset: number;
105108
otelThreadCtxTaggedSize: number;
109+
otelThreadCtxNativeWrapFieldsOffset: number;
110+
otelThreadCtxJsMapTableOffset: number;
111+
otelThreadCtxOrderedHashMapHeaderSize: number;
106112
}
107113

108114
const SCHEMA_VERSION = 'nodejs_v1_dev';
@@ -114,6 +120,9 @@ const SCHEMA_VERSION = 'nodejs_v1_dev';
114120
// consistent in shape.
115121
let WRAPPED_OBJECT_OFFSET = 24;
116122
let TAGGED_SIZE = 8;
123+
let NATIVE_WRAP_FIELDS_OFFSET = 24;
124+
let JS_MAP_TABLE_OFFSET = 0x18;
125+
let ORDERED_HASH_MAP_HEADER_SIZE = 0x10;
117126

118127
/** {@inheritDoc ThreadContextCtor} */
119128
export let ThreadContext: ThreadContextCtor;
@@ -140,6 +149,9 @@ if (process.platform === 'linux') {
140149
const addon: Addon = findBinding(join(__dirname, '..', '..'));
141150
WRAPPED_OBJECT_OFFSET = addon.otelThreadCtxWrappedObjectOffset;
142151
TAGGED_SIZE = addon.otelThreadCtxTaggedSize;
152+
NATIVE_WRAP_FIELDS_OFFSET = addon.otelThreadCtxNativeWrapFieldsOffset;
153+
JS_MAP_TABLE_OFFSET = addon.otelThreadCtxJsMapTableOffset;
154+
ORDERED_HASH_MAP_HEADER_SIZE = addon.otelThreadCtxOrderedHashMapHeaderSize;
143155

144156
ThreadContext = addon.threadContext;
145157

@@ -266,5 +278,8 @@ export function getProcessContextAttributes(
266278
'threadlocal.attribute_key_map': Object.freeze(keys.slice()),
267279
'threadlocal.wrapped_object_offset': WRAPPED_OBJECT_OFFSET,
268280
'threadlocal.tagged_size': TAGGED_SIZE,
281+
'threadlocal.native_wrap_fields_offset': NATIVE_WRAP_FIELDS_OFFSET,
282+
'threadlocal.js_map_table_offset': JS_MAP_TABLE_OFFSET,
283+
'threadlocal.ordered_hash_map_header_size': ORDERED_HASH_MAP_HEADER_SIZE,
269284
}) as ProcessContextAttributes;
270285
}

ts/test/test-otel-thread-ctx.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,8 +733,17 @@ function captureBytes(opts: {
733733
strictAssert.deepEqual(pca['threadlocal.attribute_key_map'], keys);
734734
strictAssert.equal(pca['threadlocal.wrapped_object_offset'], 24);
735735
strictAssert.equal(pca['threadlocal.tagged_size'], 8);
736+
strictAssert.equal(pca['threadlocal.native_wrap_fields_offset'], 24);
737+
strictAssert.equal(pca['threadlocal.js_map_table_offset'], 0x18);
738+
strictAssert.equal(
739+
pca['threadlocal.ordered_hash_map_header_size'],
740+
0x10,
741+
);
736742
strictAssert.deepEqual(Object.keys(pca).sort(), [
737743
'threadlocal.attribute_key_map',
744+
'threadlocal.js_map_table_offset',
745+
'threadlocal.native_wrap_fields_offset',
746+
'threadlocal.ordered_hash_map_header_size',
738747
'threadlocal.schema_version',
739748
'threadlocal.tagged_size',
740749
'threadlocal.wrapped_object_offset',

0 commit comments

Comments
 (0)