Reputation: 1730
I have C++ program that runs in terminal and uses libv8. I wanted to catch Ctrl+C and do some checks and cleanups before exit. To catch Ctrl+C I use signal(SIGINT, intHandler), it works but occasionally it segfaults. Here is minimal demo (just hold Ctrl+C and after about 100 catches it segfaults, more complex programs segfaults more often, this is simple demo so it crashes less often but it still does crash).
#include <stdio.h>
#include <signal.h>
#include <node/v8.h>
#include <v8/libplatform/libplatform.h>
const char* ToCString(const v8::String::Utf8Value& value) {//, char * place
return *value ? *value : "<string conversion failed>";
}
void echo(const v8::FunctionCallbackInfo < v8::Value > &args) {
// Print first argument
v8::HandleScope handle_scope(args.GetIsolate());
v8::String::Utf8Value str(args.GetIsolate(), args[0]);
const char *cstr = ToCString(str);
printf("%s\n", cstr);
fflush(stdout);
};
v8::Isolate* globalIsolate;
v8::Local<v8::Context> globalContext;
void intHandler(int dummy) {
// compile script
v8::TryCatch try_catch(globalIsolate);
v8::Local<v8::String> source = v8::String::NewFromUtf8(globalIsolate, "echo('ctrl+c was pressed')");
v8::Local<v8::Script> script;
if (!v8::Script::Compile(globalContext, source).ToLocal(&script)) {
fprintf(stderr, "error 1: compiled failed!\n");
}
// Run the script to get the result.
v8::Local<v8::Value> result;
if (!script->Run(globalContext).ToLocal(&result)) {
fprintf(stderr, "error 2: run failed!\n");
}
}
int main(int argc, char* argv[]) {
signal(SIGINT, intHandler);
// Initialize V8
v8::V8::InitializeICUDefaultLocation(argv[0]);
v8::V8::InitializeExternalStartupData(argv[0]);
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
v8::V8::InitializePlatform(platform.get());
v8::V8::Initialize();
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
v8::Isolate::Scope isolate_scope(isolate);
globalIsolate = isolate;
v8::HandleScope handle_scope(isolate);
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
global->Set(isolate, "echo", v8::FunctionTemplate::New(isolate, echo));
v8::Local<v8::Context> context = v8::Context::New(isolate, NULL, global);
v8::Context::Scope context_scope(context);
globalContext = context;
// compile script
v8::TryCatch try_catch(isolate);
v8::Local<v8::String> source = v8::String::NewFromUtf8(isolate, "var x = 1; while (true) { x++; if (x % 10000000 === 0) echo(x); };");
v8::Local<v8::Script> script;
if (!v8::Script::Compile(context, source).ToLocal(&script)) {
fprintf(stderr, "error 3: compiled failed!\n");
return 1;
}
// Run the script to get the result.
v8::Local<v8::Value> result;
if (!script->Run(context).ToLocal(&result)) {
fprintf(stderr, "error 4: run failed!\n");
return 2;
}
// Dispose the isolate and tear down V8.
//isolate->Dispose();
//v8::V8::Dispose();
//v8::V8::DisposePlatform();
//delete create_params.array_buffer_allocator;
printf("Program finished\n");
return 0;
}
I compile with:
g++ -o demo demo.cpp -lv8 -lpthread -lssl -lcrypto -Wall -ggdb -I/usr/include/v8
Run the program and keep pressing ctrl+c until it segfaults.
I assume I need to run JS code differently when I am inside that sigint handler but I don't know how. Thanks.
Notes:
I'm on Ubuntu 22.04.4
libv8/libnode 7.8.279.23-node.56
g++ 11.4.0
I don't know how to do gdb stack trace because if I use gdb it interferes with ctrl+c
I used -fsanitize=address,undefined and got stack trace that points somewhere deep inside v8:
=================================================================
==403170==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000027 (pc 0x73d794d71461 bp 0x7ffee62e53f0 sp 0x7ffee62e5200 T0)
==403170==The signal is caused by a READ memory access.
==403170==Hint: address points to the zero page.
#0 0x73d794d71461 in v8::internal::Compiler::GetSharedFunctionInfoForScript(v8::internal::Isolate*, v8::internal::Handle<v8::internal::String>, v8::internal::Compiler::ScriptDetails const&, v8::ScriptOriginOptions, v8::Extension*, v8::internal::ScriptData*, v8::ScriptCompiler::CompileOptions, v8::ScriptCompiler::NoCacheReason, v8::internal::NativesFlag) (/lib/x86_64-linux-gnu/libnode.so.72+0xd71461)
#1 0x73d794cc8ccf in v8::ScriptCompiler::CompileUnboundInternal(v8::Isolate*, v8::ScriptCompiler::Source*, v8::ScriptCompiler::CompileOptions, v8::ScriptCompiler::NoCacheReason) (/lib/x86_64-linux-gnu/libnode.so.72+0xcc8ccf)
#2 0x73d794cc91ad in v8::ScriptCompiler::Compile(v8::Local<v8::Context>, v8::ScriptCompiler::Source*, v8::ScriptCompiler::CompileOptions, v8::ScriptCompiler::NoCacheReason) (/lib/x86_64-linux-gnu/libnode.so.72+0xcc91ad)
#3 0x73d794cc9260 in v8::Script::Compile(v8::Local<v8::Context>, v8::Local<v8::String>, v8::ScriptOrigin*) (/lib/x86_64-linux-gnu/libnode.so.72+0xcc9260)
#4 0x5eaac0640466 in intHandler(int) /home/me/demo.cpp:27
#5 0x73d792a4251f (/lib/x86_64-linux-gnu/libc.so.6+0x4251f)
#6 0x17913f4c31c0 (<unknown module>)
however demo.cpp:27 is this line inside the intHandler:
if (!v8::Script::Compile(globalContext, source).ToLocal(&script)) {
Upvotes: 0
Views: 73
Reputation: 40691
(V8 developer here.)
I am, frankly, surprised that this works at all, even sometimes. It's definitely not a supported use case: V8 wasn't designed for this, and isn't tested for this, and there is no safe or proper way to do this. The signal could arrive at an arbitrary point in time, hence interrupting execution in an arbitrary state, and no part of V8 (in particular: not the stack) is built to support this.
In short: don't run JS code in signal handlers. Don't even call v8::Script::Compile
. Don't do anything that allocates on the managed heap, such as v8::String::NewFromUtf8
.
(Or do, and enjoy the crashes -- as the meme goes, I'm just a recommendation, not a cop.)
What you can do instead, especially for handling Ctrl+C
, is use the v8::Isolate::TerminateExecution()
feature. Internally it'll throw a special kind of exception that will unwind JS execution to the nearest script->Run(...)
in your code, i.e. you should get to the line fprintf(stderr, "error 4: run failed!\n");
. You can then run whatever checks you want to run and terminate the process gracefully.
Upvotes: 1