我有一个在终端运行并使用 libv8 的 C++ 程序。我想抓住 Ctrl+C 并在退出之前进行一些检查和清理。为了捕获 Ctrl+C,我使用 signal(SIGINT, intHandler),它可以工作,但偶尔会出现段错误。这是最小的演示(只需按住 Ctrl+C,大约 100 次后捕获它的段错误,更复杂的程序更频繁地出现段错误,这是简单的演示,因此它崩溃的频率较低,但仍然会崩溃)。
#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;
}
我编译:
g++ -o demo demo.cpp -lv8 -lpthread -lssl -lcrypto -Wall -ggdb -I/usr/include/v8
运行程序并按住ctrl+c,直到出现段错误。
我认为当我在 sigint 处理程序中时,我需要以不同的方式运行 JS 代码,但我不知道如何操作。谢谢。
备注:
我使用的是 Ubuntu 22.04.4
libv8/libnode 7.8.279.23-node.56
g++ 11.4.0
我不知道如何进行 gdb 堆栈跟踪,因为如果我使用 gdb,它会干扰 ctrl+c
我使用了 -fsanitize=address,undefined 并获得了指向 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)) {
(这里是 V8 开发者。)
坦率地说,我很惊讶这竟然有效,甚至有时也是如此。这绝对不是受支持的用例:V8 不是为此设计的,也没有对此进行测试,并且没有安全或正确的方法来执行此操作。信号可能到达任意时间点,从而在任意状态下中断执行,并且 V8 的任何部分(特别是:不是堆栈)都没有构建支持这一点。
简而言之:不要在信号处理程序中运行 JS 代码。甚至不要打电话
v8::Script::Compile
。不要执行任何在托管堆上分配的操作,例如 v8::String::NewFromUtf8
。您可以使用
Ctrl+C
功能,尤其是处理 v8::Isolate::TerminateExecution()
时。在内部,它会抛出一种特殊的异常,将 JS 执行展开到代码中最近的 script->Run(...)
,即您应该到达 fprintf(stderr, "error 4: run failed!\n");
行。然后,您可以运行任何您想要运行的检查并正常终止该进程。