Repository: node
Issue: Taking snapshot crashes Worker thread when taken during bootstrap
Labels: worker

* **Version**: v14.15.4, v15.6.0, 13ac5fbc5 (master)
* **Platform**: Linux 5.8.0-7630-generic x86_64
* **Subsystem**: worker_threads

### What steps will reproduce the bug?

Run this code with a debug build of node:
```js
const { Worker, isMainThread } = require('worker_threads');

if (!isMainThread)
  require('v8').getHeapSnapshot();
else
  new Worker(__filename);
```

### How often does it reproduce? Is there a required condition?

Every time with a debug build.

### What is the expected behavior?

To not crash.

### What do you see instead?

The process aborts. Here is the backtrace from v14.15.4 using `gdb`:
```cpp
#0  v8::internal::ScopeInfo::FunctionContextSlotIndex (this=0x7ffff5235998, name=...) at ../deps/v8/src/objects/scope-info.cc:938
#1  0x0000000001af258d in v8::internal::V8HeapExplorer::ExtractContextReferences (this=0x7ffff5235b98, entry=0x7fffe82c4cf8, context=...) at ../deps/v8/src/profiler/heap-snapshot-generator.cc:993
#2  0x0000000001af6041 in v8::internal::V8HeapExplorer::IterateAndExtractReferences (this=0x7ffff5235b98, generator=<optimized out>) at ../deps/v8/src/profiler/heap-snapshot-generator.cc:1533
#3  0x0000000001af834e in v8::internal::HeapSnapshotGenerator::FillReferences (this=0x7ffff5235b80) at ../deps/v8/src/profiler/heap-snapshot-generator.cc:2147
#4  v8::internal::HeapSnapshotGenerator::GenerateSnapshot (this=0x7ffff5235b80) at ../deps/v8/src/profiler/heap-snapshot-generator.cc:2111
#5  0x0000000001ae80c6 in v8::internal::HeapProfiler::TakeSnapshot (this=0x7fffe812f200, control=<optimized out>, resolver=<optimized out>, treat_global_objects_as_roots=<optimized out>) at ../deps/v8/src/profiler/heap-profiler.cc:75
#6  0x0000000000fdbbde in node::heap::CreateHeapSnapshotStream (args=...) at ../src/heap_utils.cc:360
#7  0x00000000014369f5 in v8::internal::FunctionCallbackArguments::Call (this=0x7ffff5235eb0, handler=...) at ../deps/v8/src/api/api-arguments-inl.h:158
#8  0x000000000143545e in v8::internal::(anonymous namespace)::HandleApiCallHelper<false> (isolate=<optimized out>, function=..., new_target=..., fun_data=..., receiver=..., args=...) at ../deps/v8/src/builtins/builtins-api.cc:111
#9  0x0000000001433c0a in v8::internal::Builtin_Impl_HandleApiCall (args=..., isolate=0x7fffe8000d40) at ../deps/v8/src/builtins/builtins-api.cc:141
#10 0x0000000001433758 in v8::internal::Builtin_HandleApiCall (args_length=5, args_object=0x7ffff5236050, isolate=0x7fffe8000d40) at ../deps/v8/src/builtins/builtins-api.cc:129
#11 0x000000000229e000 in Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_BuiltinExit ()
#12 0x00000000020497a4 in Builtins_InterpreterEntryTrampoline ()
```

For some reason `lldb` gives a drastically different backtrace:
```cpp
 * thread #8: tid = 1762687, 0x00000000018d70be node`v8::internal::ScopeInfo::FunctionContextSlotIndex(v8::internal::String) const [inlined] v8::base::Relaxed_Load(ptr=0xffffffffffffffff) at atomicops_internals_portable.h:189:10, name = 'node', stop reason = signal SIGSEGV: invalid address (fault address: 0xffffffffffffffff)
  * frame #0: 0x00000000018d70be node`v8::internal::ScopeInfo::FunctionContextSlotIndex(v8::internal::String) const [inlined] v8::base::Relaxed_Load(ptr=0xffffffffffffffff) at atomicops_internals_portable.h:189:10
    ...
    frame #12: 0x0000000001970ef6 node`v8::internal::HeapProfiler::TakeSnapshot(this=0x00007fffe40b6510, control=<unavailable>, resolver=<unavailable>, treat_global_objects_as_roots=<unavailable>) at heap-profiler.cc:75:20
    ...
    frame #19: 0x0000000001e8abe2 getHeapSnapshot(this=0x1ca3665d56c1:<Object: Object>) at v8.js:1:10 fn=0x00001ca3665d15a1
    ...
    frame #22: 0x0000000001e8abe2 Module._extensions..js(this=0x1ca3665cbe59:<Object: Object>, 0x1ca3665cfe41:<Object: Module>, 0x5d2937ff151:<String: "/tmp/snapshot-cr...">) at internal/modules/cjs/loader.js:1:10 fn=0x000005d2937f59b1
    ...
    frame #36: 0x0000000000e77e61 node`node::AsyncWrap::MakeCallback(this=0x00007fffe4122800, cb=(val_ = 0x00007fffe401e1f0), argc=2, argv=0x00007ffff5237f40) at async_wrap.cc:954:27
    frame #37: 0x0000000001005f80 node`node::worker::MessagePort::OnMessage(this=0x00007fffe4122800) at node_messaging.cc:775:9
    frame #38: 0x00000000010097f8 node`node::worker::MessagePort::MessagePort(this=0x00007fffe4122868, handle=0x00007fffe4122868)::$_1::operator()(uv_async_s*) const at node_messaging.cc:589:14
    frame #39: 0x00000000010097b5 node`node::worker::MessagePort::MessagePort(handle=0x00007fffe4122868)::$_1::__invoke(uv_async_s*) at node_messaging.cc:586:20
    frame #40: 0x0000000001e50ebc node`uv__async_io(loop=0x00007ffff523b900, w=0x00007ffff523bac8, events=1) at async.c:163:5
    frame #41: 0x0000000001e6c418 node`uv__io_poll(loop=0x00007ffff523b900, timeout=0) at linux-core.c:462:11
    frame #42: 0x0000000001e514dd node`uv_run(loop=0x00007ffff523b900, mode=UV_RUN_DEFAULT) at core.c:385:5
    frame #43: 0x00000000010bf289 node`node::worker::Worker::Run(this=0x00000000058da570) at node_worker.cc:347:11
    frame #44: 0x00000000010c1cfd node`node::worker::Worker::StartThread(this=0x00000000058da570, arg=0x00000000058da570)::$_3::operator()(void*) const at node_worker.cc:623:8
```

### Additional information

This does not crash in v12.20.1.

I've also written a native module and called the C++ API directly from both the same thread and using an interrupt from a different thread. As long as the call is made during `node::worker::Worker::StartThread()` the process will crash.

It's also possible to crash the process when taking the snapshot from a different thread. Here's an example:
```js
const { Worker, isMainThread, parentPort } = require('worker_threads');

if (!isMainThread) {
  parentPort.postMessage('hi');
  while (true);
} else {
  const worker = new Worker(__filename);
  worker.once('message', () => {
    require('v8').getHeapSnapshot();
    process.exit();
  });
}
```

It also crashes if the `inspector` API is used:
```js
const { Worker, isMainThread, threadId, parentPort } =
  require('worker_threads');

if (!isMainThread) {
  parentPort.postMessage('hi');
  const t = Date.now();
  while (Date.now() - t < 100);
  return;
}

const inspector = require('inspector');
const session = new inspector.Session();
session.on('HeapProfiler.addHeapSnapshotChunk', (m) => { });
session.connect();

const worker = new Worker(__filename);
worker.once('message', (msg) => {
  session.post('HeapProfiler.takeHeapSnapshot', null, (err, r) => { });
});
```