Skip to content

Commit

Permalink
deps: backport a8f6869 from upstream V8
Browse files Browse the repository at this point in the history
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

  R=yangguo@chromium.org,jgruber@chromium.org
  CC=info@bnoordhuis.nl

  Bug: v8:7230
  Change-Id: Id517c873eb81cd53f7216c7efd441b956cf7f943
  Reviewed-on: https://chromium-review.googlesource.com/833260
  Commit-Queue: Yang Guo <yangguo@chromium.org>
  Reviewed-by: Yang Guo <yangguo@chromium.org>
  Cr-Commit-Position: refs/heads/master@{nodejs#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: nodejs#22122
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
  • Loading branch information
Ben Newman authored and targos committed Sep 4, 2018
1 parent 8e91215 commit bb35752
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 9 deletions.
2 changes: 1 addition & 1 deletion common.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

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

# Enable disassembler for `--print-code` v8 options
'v8_enable_disassembler': 1,
Expand Down
2 changes: 2 additions & 0 deletions deps/v8/AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Facebook, Inc. <*@fb.com>
Facebook, Inc. <*@oculus.com>
Vewd Software AS <*@vewd.com>
Groupon <*@groupon.com>
Meteor Development Group <*@meteor.com>
Cloudflare, Inc. <*@cloudflare.com>

Aaron Bieber <deftly@gmail.com>
Expand All @@ -49,6 +50,7 @@ Andrei Kashcha <anvaka@gmail.com>
Anna Henningsen <anna@addaleax.net>
Bangfu Tao <bangfu.tao@samsung.com>
Ben Coe <ben@npmjs.com>
Ben Newman <ben@meteor.com>
Ben Noordhuis <info@bnoordhuis.nl>
Benjamin Tan <demoneaux@gmail.com>
Bert Belder <bertbelder@gmail.com>
Expand Down
29 changes: 23 additions & 6 deletions deps/v8/src/debug/debug.cc
Original file line number Diff line number Diff line change
Expand Up @@ -355,19 +355,36 @@ void Debug::ThreadInit() {


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


char* Debug::RestoreDebug(char* storage) {
// Simply reset state. Don't restore anything.
ThreadInit();
MemCopy(reinterpret_cast<char*>(&thread_local_), storage,
ArchiveSpacePerThread());

if (in_debug_scope()) {
// If this thread was in a DebugScope when we archived it, restore the
// previous debugging state now. Note that in_debug_scope() returns
// true when thread_local_.current_debug_scope_ (restored by MemCopy
// above) is non-null.

// Clear any one-shot breakpoints that may have been set by the other
// thread, and reapply breakpoints for this thread.
HandleScope scope(isolate_);
ClearOneShot();

if (thread_local_.last_step_action_ != StepNone) {
// Reset the previous step action for this thread.
PrepareStep(thread_local_.last_step_action_);
}
}

return storage + ArchiveSpacePerThread();
}

int Debug::ArchiveSpacePerThread() { return 0; }
int Debug::ArchiveSpacePerThread() { return sizeof(ThreadLocal); }

void Debug::Iterate(RootVisitor* v) {
v->VisitRootPointer(Root::kDebug, nullptr, &thread_local_.return_value_);
Expand Down
1 change: 1 addition & 0 deletions deps/v8/src/debug/debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ class Debug {
static int ArchiveSpacePerThread();
void FreeThreadResources() { }
void Iterate(RootVisitor* v);
void InitThread(const ExecutionAccess& lock) { ThreadInit(); }

bool CheckExecutionState(int id) {
return CheckExecutionState() && break_id() == id;
Expand Down
8 changes: 6 additions & 2 deletions deps/v8/src/v8threads.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ void Locker::Initialize(v8::Isolate* isolate) {
} else {
internal::ExecutionAccess access(isolate_);
isolate_->stack_guard()->ClearThread(access);
isolate_->stack_guard()->InitThread(access);
isolate_->thread_manager()->InitThread(access);
}
}
DCHECK(isolate_->thread_manager()->IsLockedByCurrentThread());
Expand Down Expand Up @@ -95,6 +95,10 @@ Unlocker::~Unlocker() {

namespace internal {

void ThreadManager::InitThread(const ExecutionAccess& lock) {
isolate_->stack_guard()->InitThread(lock);
isolate_->debug()->InitThread(lock);
}

bool ThreadManager::RestoreThread() {
DCHECK(IsLockedByCurrentThread());
Expand Down Expand Up @@ -127,7 +131,7 @@ bool ThreadManager::RestoreThread() {
isolate_->FindPerThreadDataForThisThread();
if (per_thread == nullptr || per_thread->thread_state() == nullptr) {
// This is a new thread.
isolate_->stack_guard()->InitThread(access);
InitThread(access);
return false;
}
ThreadState* state = per_thread->thread_state();
Expand Down
1 change: 1 addition & 0 deletions deps/v8/src/v8threads.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class ThreadManager {
void Lock();
void Unlock();

void InitThread(const ExecutionAccess&);
void ArchiveThread();
bool RestoreThread();
void FreeThreadResources();
Expand Down
129 changes: 129 additions & 0 deletions deps/v8/test/cctest/test-debug.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6221,6 +6221,135 @@ TEST(DebugBreakOffThreadTerminate) {
}


class ArchiveRestoreThread : public v8::base::Thread,
public v8::debug::DebugDelegate {
public:
ArchiveRestoreThread(v8::Isolate* isolate, int spawn_count)
: Thread(Options("ArchiveRestoreThread")),
isolate_(isolate),
debug_(reinterpret_cast<i::Isolate*>(isolate_)->debug()),
spawn_count_(spawn_count),
break_count_(0) {}

virtual void Run() {
v8::Locker locker(isolate_);
isolate_->Enter();

v8::HandleScope scope(isolate_);
v8::Local<v8::Context> context = v8::Context::New(isolate_);
v8::Context::Scope context_scope(context);

v8::Local<v8::Function> test = CompileFunction(isolate_,
"function test(n) {\n"
" debugger;\n"
" return n + 1;\n"
"}\n",
"test");

debug_->SetDebugDelegate(this, false);
v8::internal::DisableBreak enable_break(debug_, false);

v8::Local<v8::Value> args[1] = {v8::Integer::New(isolate_, spawn_count_)};

int result = test->Call(context, context->Global(), 1, args)
.ToLocalChecked()
->Int32Value(context)
.FromJust();

// Verify that test(spawn_count_) returned spawn_count_ + 1.
CHECK_EQ(spawn_count_ + 1, result);

isolate_->Exit();
}

void BreakProgramRequested(v8::Local<v8::Context> context,
v8::Local<v8::Object> exec_state,
const std::vector<v8::debug::BreakpointId>&) {
auto stack_traces = v8::debug::StackTraceIterator::Create(isolate_);
if (!stack_traces->Done()) {
v8::debug::Location location = stack_traces->GetSourceLocation();

i::PrintF("ArchiveRestoreThread #%d hit breakpoint at line %d\n",
spawn_count_, location.GetLineNumber());

switch (location.GetLineNumber()) {
case 1: // debugger;
CHECK_EQ(break_count_, 0);

// Attempt to stop on the next line after the first debugger
// statement. If debug->{Archive,Restore}Debug() improperly reset
// thread-local debug information, the debugger will fail to stop
// before the test function returns.
debug_->PrepareStep(StepNext);

// Spawning threads while handling the current breakpoint verifies
// that the parent thread correctly archived and restored the
// state necessary to stop on the next line. If not, then control
// will simply continue past the `return n + 1` statement.
MaybeSpawnChildThread();

break;

case 2: // return n + 1;
CHECK_EQ(break_count_, 1);
break;

default:
CHECK(false);
}
}

++break_count_;
}

void MaybeSpawnChildThread() {
if (spawn_count_ > 1) {
v8::Unlocker unlocker(isolate_);

// Spawn a thread that spawns a thread that spawns a thread (and so
// on) so that the ThreadManager is forced to archive and restore
// the current thread.
ArchiveRestoreThread child(isolate_, spawn_count_ - 1);
child.Start();
child.Join();

// The child thread sets itself as the debug delegate, so we need to
// usurp it after the child finishes, or else future breakpoints
// will be delegated to a destroyed ArchiveRestoreThread object.
debug_->SetDebugDelegate(this, false);

// This is the most important check in this test, since
// child.GetBreakCount() will return 1 if the debugger fails to stop
// on the `return n + 1` line after the grandchild thread returns.
CHECK_EQ(child.GetBreakCount(), 2);
}
}

int GetBreakCount() { return break_count_; }

private:
v8::Isolate* isolate_;
v8::internal::Debug* debug_;
const int spawn_count_;
int break_count_;
};

TEST(DebugArchiveRestore) {
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);

ArchiveRestoreThread thread(isolate, 5);
// Instead of calling thread.Start() and thread.Join() here, we call
// thread.Run() directly, to make sure we exercise archive/restore
// logic on the *current* thread as well as other threads.
thread.Run();
CHECK_EQ(thread.GetBreakCount(), 2);

isolate->Dispose();
}


static void DebugEventExpectNoException(
const v8::Debug::EventDetails& event_details) {
v8::DebugEvent event = event_details.GetEvent();
Expand Down

0 comments on commit bb35752

Please sign in to comment.