Skip to content

Commit f729549

Browse files
Ben Newmantargos
Ben Newman
authored andcommitted
deps: backport a8f6869 from upstream V8
Original commit message: [debug] Fully implement Debug::ArchiveDebug and Debug::RestoreDebug. I have a project that embeds V8 and uses a single `Isolate` from multiple threads. The program runs just fine, but sometimes the inspector doesn't stop on the correct line after stepping over a statement that switches threads behind the scenes, even though the original thread is restored by the time the next statement is executed. After some digging, I discovered that the `Debug::ArchiveDebug` and `Debug::RestoreDebug` methods, which should be responsible for saving/restoring this `ThreadLocal` information when switching threads, currently don't do anything. This commit implements those methods using MemCopy, in the style of other Archive/Restore methods in the V8 codebase. Related: https://groups.google.com/forum/#!topic/v8-users/_Qf2rwljRk8 [email protected],[email protected] [email protected] Bug: v8:7230 Change-Id: Id517c873eb81cd53f7216c7efd441b956cf7f943 Reviewed-on: https://chromium-review.googlesource.com/833260 Commit-Queue: Yang Guo <[email protected]> Reviewed-by: Yang Guo <[email protected]> Cr-Commit-Position: refs/heads/master@{#54902} Refs: v8/v8@a8f6869 Fix build errors by matching older V8 APIs used by Node. It looks like SetDebugDelegate(debug::DebugDelegate* delegate, bool pass_ownership) was simplified to just SetDebugDelegate(debug::DebugDelegate* delegate) in v8/v8@37dcd83, but the extra `pass_ownership` parameter is still there in the current version of `node/deps/v8`. I should be able to fix those tests by passing `false` for `pass_ownership`. Also, the `DebugDelegate::BreakProgramRequested` method lost a parameter in v8/v8@e404670, but it's not a parameter I was using in my test, so there shouldn't be any harm in adding the `exec_state` parameter back to `BreakProgramRequested` (and continuing to ignore it). Skip restoring debug state unless thread previously in DebugScope. A simpler version of the changes I proposed upstream in this V8 change request: https://chromium-review.googlesource.com/c/v8/v8/+/1168449 In this version, Debug::RestoreDebug never attempts to enter a new DebugScope, but merely reuses the previous one, if we're returning to a thread that was previously in a DebugScope. If the thread was not previously in a DebugScope, I believe it does not need to have any debugging state restored with ClearOneShot and PrepareStep. The tests from https://chromium-review.googlesource.com/c/v8/v8/+/833260 still pass, and the failing V8-CI tests are now passing locally for me. PR-URL: #22122 Reviewed-By: Franziska Hinkelmann <[email protected]> Reviewed-By: Michaël Zasso <[email protected]>
1 parent e5b732f commit f729549

File tree

7 files changed

+163
-9
lines changed

7 files changed

+163
-9
lines changed

common.gypi

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
# Reset this number to 0 on major V8 upgrades.
3131
# Increment by one for each non-official patch applied to deps/v8.
32-
'v8_embedder_string': '-node.24',
32+
'v8_embedder_string': '-node.25',
3333

3434
# Enable disassembler for `--print-code` v8 options
3535
'v8_enable_disassembler': 1,

deps/v8/AUTHORS

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Facebook, Inc. <*@fb.com>
3232
Facebook, Inc. <*@oculus.com>
3333
Vewd Software AS <*@vewd.com>
3434
Groupon <*@groupon.com>
35+
Meteor Development Group <*@meteor.com>
3536
Cloudflare, Inc. <*@cloudflare.com>
3637

3738
Aaron Bieber <[email protected]>
@@ -49,6 +50,7 @@ Andrei Kashcha <[email protected]>
4950
Anna Henningsen <[email protected]>
5051
Bangfu Tao <[email protected]>
5152
53+
Ben Newman <[email protected]>
5254
Ben Noordhuis <[email protected]>
5355
Benjamin Tan <[email protected]>
5456
Bert Belder <[email protected]>

deps/v8/src/debug/debug.cc

+23-6
Original file line numberDiff line numberDiff line change
@@ -355,19 +355,36 @@ void Debug::ThreadInit() {
355355

356356

357357
char* Debug::ArchiveDebug(char* storage) {
358-
// Simply reset state. Don't archive anything.
359-
ThreadInit();
358+
MemCopy(storage, reinterpret_cast<char*>(&thread_local_),
359+
ArchiveSpacePerThread());
360360
return storage + ArchiveSpacePerThread();
361361
}
362362

363-
364363
char* Debug::RestoreDebug(char* storage) {
365-
// Simply reset state. Don't restore anything.
366-
ThreadInit();
364+
MemCopy(reinterpret_cast<char*>(&thread_local_), storage,
365+
ArchiveSpacePerThread());
366+
367+
if (in_debug_scope()) {
368+
// If this thread was in a DebugScope when we archived it, restore the
369+
// previous debugging state now. Note that in_debug_scope() returns
370+
// true when thread_local_.current_debug_scope_ (restored by MemCopy
371+
// above) is non-null.
372+
373+
// Clear any one-shot breakpoints that may have been set by the other
374+
// thread, and reapply breakpoints for this thread.
375+
HandleScope scope(isolate_);
376+
ClearOneShot();
377+
378+
if (thread_local_.last_step_action_ != StepNone) {
379+
// Reset the previous step action for this thread.
380+
PrepareStep(thread_local_.last_step_action_);
381+
}
382+
}
383+
367384
return storage + ArchiveSpacePerThread();
368385
}
369386

370-
int Debug::ArchiveSpacePerThread() { return 0; }
387+
int Debug::ArchiveSpacePerThread() { return sizeof(ThreadLocal); }
371388

372389
void Debug::Iterate(RootVisitor* v) {
373390
v->VisitRootPointer(Root::kDebug, nullptr, &thread_local_.return_value_);

deps/v8/src/debug/debug.h

+1
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ class Debug {
327327
static int ArchiveSpacePerThread();
328328
void FreeThreadResources() { }
329329
void Iterate(RootVisitor* v);
330+
void InitThread(const ExecutionAccess& lock) { ThreadInit(); }
330331

331332
bool CheckExecutionState(int id) {
332333
return CheckExecutionState() && break_id() == id;

deps/v8/src/v8threads.cc

+6-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ void Locker::Initialize(v8::Isolate* isolate) {
4545
} else {
4646
internal::ExecutionAccess access(isolate_);
4747
isolate_->stack_guard()->ClearThread(access);
48-
isolate_->stack_guard()->InitThread(access);
48+
isolate_->thread_manager()->InitThread(access);
4949
}
5050
}
5151
DCHECK(isolate_->thread_manager()->IsLockedByCurrentThread());
@@ -95,6 +95,10 @@ Unlocker::~Unlocker() {
9595

9696
namespace internal {
9797

98+
void ThreadManager::InitThread(const ExecutionAccess& lock) {
99+
isolate_->stack_guard()->InitThread(lock);
100+
isolate_->debug()->InitThread(lock);
101+
}
98102

99103
bool ThreadManager::RestoreThread() {
100104
DCHECK(IsLockedByCurrentThread());
@@ -127,7 +131,7 @@ bool ThreadManager::RestoreThread() {
127131
isolate_->FindPerThreadDataForThisThread();
128132
if (per_thread == nullptr || per_thread->thread_state() == nullptr) {
129133
// This is a new thread.
130-
isolate_->stack_guard()->InitThread(access);
134+
InitThread(access);
131135
return false;
132136
}
133137
ThreadState* state = per_thread->thread_state();

deps/v8/src/v8threads.h

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class ThreadManager {
6767
void Lock();
6868
void Unlock();
6969

70+
void InitThread(const ExecutionAccess&);
7071
void ArchiveThread();
7172
bool RestoreThread();
7273
void FreeThreadResources();

deps/v8/test/cctest/test-debug.cc

+129
Original file line numberDiff line numberDiff line change
@@ -6221,6 +6221,135 @@ TEST(DebugBreakOffThreadTerminate) {
62216221
}
62226222

62236223

6224+
class ArchiveRestoreThread : public v8::base::Thread,
6225+
public v8::debug::DebugDelegate {
6226+
public:
6227+
ArchiveRestoreThread(v8::Isolate* isolate, int spawn_count)
6228+
: Thread(Options("ArchiveRestoreThread")),
6229+
isolate_(isolate),
6230+
debug_(reinterpret_cast<i::Isolate*>(isolate_)->debug()),
6231+
spawn_count_(spawn_count),
6232+
break_count_(0) {}
6233+
6234+
virtual void Run() {
6235+
v8::Locker locker(isolate_);
6236+
isolate_->Enter();
6237+
6238+
v8::HandleScope scope(isolate_);
6239+
v8::Local<v8::Context> context = v8::Context::New(isolate_);
6240+
v8::Context::Scope context_scope(context);
6241+
6242+
v8::Local<v8::Function> test = CompileFunction(isolate_,
6243+
"function test(n) {\n"
6244+
" debugger;\n"
6245+
" return n + 1;\n"
6246+
"}\n",
6247+
"test");
6248+
6249+
debug_->SetDebugDelegate(this, false);
6250+
v8::internal::DisableBreak enable_break(debug_, false);
6251+
6252+
v8::Local<v8::Value> args[1] = {v8::Integer::New(isolate_, spawn_count_)};
6253+
6254+
int result = test->Call(context, context->Global(), 1, args)
6255+
.ToLocalChecked()
6256+
->Int32Value(context)
6257+
.FromJust();
6258+
6259+
// Verify that test(spawn_count_) returned spawn_count_ + 1.
6260+
CHECK_EQ(spawn_count_ + 1, result);
6261+
6262+
isolate_->Exit();
6263+
}
6264+
6265+
void BreakProgramRequested(v8::Local<v8::Context> context,
6266+
v8::Local<v8::Object> exec_state,
6267+
const std::vector<v8::debug::BreakpointId>&) {
6268+
auto stack_traces = v8::debug::StackTraceIterator::Create(isolate_);
6269+
if (!stack_traces->Done()) {
6270+
v8::debug::Location location = stack_traces->GetSourceLocation();
6271+
6272+
i::PrintF("ArchiveRestoreThread #%d hit breakpoint at line %d\n",
6273+
spawn_count_, location.GetLineNumber());
6274+
6275+
switch (location.GetLineNumber()) {
6276+
case 1: // debugger;
6277+
CHECK_EQ(break_count_, 0);
6278+
6279+
// Attempt to stop on the next line after the first debugger
6280+
// statement. If debug->{Archive,Restore}Debug() improperly reset
6281+
// thread-local debug information, the debugger will fail to stop
6282+
// before the test function returns.
6283+
debug_->PrepareStep(StepNext);
6284+
6285+
// Spawning threads while handling the current breakpoint verifies
6286+
// that the parent thread correctly archived and restored the
6287+
// state necessary to stop on the next line. If not, then control
6288+
// will simply continue past the `return n + 1` statement.
6289+
MaybeSpawnChildThread();
6290+
6291+
break;
6292+
6293+
case 2: // return n + 1;
6294+
CHECK_EQ(break_count_, 1);
6295+
break;
6296+
6297+
default:
6298+
CHECK(false);
6299+
}
6300+
}
6301+
6302+
++break_count_;
6303+
}
6304+
6305+
void MaybeSpawnChildThread() {
6306+
if (spawn_count_ > 1) {
6307+
v8::Unlocker unlocker(isolate_);
6308+
6309+
// Spawn a thread that spawns a thread that spawns a thread (and so
6310+
// on) so that the ThreadManager is forced to archive and restore
6311+
// the current thread.
6312+
ArchiveRestoreThread child(isolate_, spawn_count_ - 1);
6313+
child.Start();
6314+
child.Join();
6315+
6316+
// The child thread sets itself as the debug delegate, so we need to
6317+
// usurp it after the child finishes, or else future breakpoints
6318+
// will be delegated to a destroyed ArchiveRestoreThread object.
6319+
debug_->SetDebugDelegate(this, false);
6320+
6321+
// This is the most important check in this test, since
6322+
// child.GetBreakCount() will return 1 if the debugger fails to stop
6323+
// on the `return n + 1` line after the grandchild thread returns.
6324+
CHECK_EQ(child.GetBreakCount(), 2);
6325+
}
6326+
}
6327+
6328+
int GetBreakCount() { return break_count_; }
6329+
6330+
private:
6331+
v8::Isolate* isolate_;
6332+
v8::internal::Debug* debug_;
6333+
const int spawn_count_;
6334+
int break_count_;
6335+
};
6336+
6337+
TEST(DebugArchiveRestore) {
6338+
v8::Isolate::CreateParams create_params;
6339+
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
6340+
v8::Isolate* isolate = v8::Isolate::New(create_params);
6341+
6342+
ArchiveRestoreThread thread(isolate, 5);
6343+
// Instead of calling thread.Start() and thread.Join() here, we call
6344+
// thread.Run() directly, to make sure we exercise archive/restore
6345+
// logic on the *current* thread as well as other threads.
6346+
thread.Run();
6347+
CHECK_EQ(thread.GetBreakCount(), 2);
6348+
6349+
isolate->Dispose();
6350+
}
6351+
6352+
62246353
static void DebugEventExpectNoException(
62256354
const v8::Debug::EventDetails& event_details) {
62266355
v8::DebugEvent event = event_details.GetEvent();

0 commit comments

Comments
 (0)