Commit 37d7dcd2 authored by sgjesse@chromium.org's avatar sgjesse@chromium.org

Add thread information to the debugger.

Each thread running V8 code is assigned an id in thread local storage the first time V8 code is run in it. The thread information returned to the debugger contains the number of threads, the id of each of these threads and which one is the current thread.

Added a threads command to the developer shell debugger for showing information on threads.
Review URL: http://codereview.chromium.org/48009

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1523 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 223ea84e
...@@ -324,6 +324,10 @@ function DebugRequest(cmd_line) { ...@@ -324,6 +324,10 @@ function DebugRequest(cmd_line) {
this.request_ = this.clearCommandToJSONRequest_(args); this.request_ = this.clearCommandToJSONRequest_(args);
break; break;
case 'threads':
this.request_ = this.threadsCommandToJSONRequest_(args);
break;
case 'trace': case 'trace':
// Return undefined to indicate command handled internally (no JSON). // Return undefined to indicate command handled internally (no JSON).
this.request_ = void 0; this.request_ = void 0;
...@@ -686,6 +690,14 @@ DebugRequest.prototype.clearCommandToJSONRequest_ = function(args) { ...@@ -686,6 +690,14 @@ DebugRequest.prototype.clearCommandToJSONRequest_ = function(args) {
}; };
// Create a JSON request for the threads command.
DebugRequest.prototype.threadsCommandToJSONRequest_ = function(args) {
// Build a threads request from the text command.
var request = this.createRequest('threads');
return request.toJSONProtocol();
};
// Handle the trace command. // Handle the trace command.
DebugRequest.prototype.traceCommand_ = function(args) { DebugRequest.prototype.traceCommand_ = function(args) {
// Process arguments. // Process arguments.
...@@ -919,6 +931,18 @@ function DebugResponseDetails(response) { ...@@ -919,6 +931,18 @@ function DebugResponseDetails(response) {
details.text = result; details.text = result;
break; break;
case 'threads':
var result = 'Active V8 threads: ' + body.totalThreads + '\n';
body.threads.sort(function(a, b) { return a.id - b.id; });
for (i = 0; i < body.threads.length; i++) {
result += body.threads[i].current ? '*' : ' ';
result += ' ';
result += body.threads[i].id;
result += '\n';
}
details.text = result;
break;
case 'continue': case 'continue':
details.text = "(running)"; details.text = "(running)";
break; break;
......
...@@ -752,6 +752,10 @@ ExecutionState.prototype.frameCount = function() { ...@@ -752,6 +752,10 @@ ExecutionState.prototype.frameCount = function() {
return %GetFrameCount(this.break_id); return %GetFrameCount(this.break_id);
}; };
ExecutionState.prototype.threadCount = function() {
return %GetThreadCount(this.break_id);
};
ExecutionState.prototype.frame = function(opt_index) { ExecutionState.prototype.frame = function(opt_index) {
// If no index supplied return the selected frame. // If no index supplied return the selected frame.
if (opt_index == null) opt_index = this.selected_frame; if (opt_index == null) opt_index = this.selected_frame;
...@@ -1167,6 +1171,8 @@ DebugCommandProcessor.prototype.processDebugJSONRequest = function(json_request) ...@@ -1167,6 +1171,8 @@ DebugCommandProcessor.prototype.processDebugJSONRequest = function(json_request)
this.sourceRequest_(request, response); this.sourceRequest_(request, response);
} else if (request.command == 'scripts') { } else if (request.command == 'scripts') {
this.scriptsRequest_(request, response); this.scriptsRequest_(request, response);
} else if (request.command == 'threads') {
this.threadsRequest_(request, response);
} else { } else {
throw new Error('Unknown command "' + request.command + '" in request'); throw new Error('Unknown command "' + request.command + '" in request');
} }
...@@ -1673,6 +1679,28 @@ DebugCommandProcessor.prototype.scriptsRequest_ = function(request, response) { ...@@ -1673,6 +1679,28 @@ DebugCommandProcessor.prototype.scriptsRequest_ = function(request, response) {
}; };
DebugCommandProcessor.prototype.threadsRequest_ = function(request, response) {
// Get the number of threads.
var total_threads = this.exec_state_.threadCount();
// Get information for all threads.
var threads = [];
for (var i = 0; i < total_threads; i++) {
var details = %GetThreadDetails(this.exec_state_.break_id, i);
var thread_info = { current: details[0],
id: details[1]
}
threads.push(thread_info);
}
// Create the response body.
response.body = {
totalThreads: total_threads,
threads: threads
}
};
// Check whether the previously processed command caused the VM to become // Check whether the previously processed command caused the VM to become
// running. // running.
DebugCommandProcessor.prototype.isRunning = function() { DebugCommandProcessor.prototype.isRunning = function() {
......
...@@ -5738,6 +5738,78 @@ static Object* Runtime_GetCFrames(Arguments args) { ...@@ -5738,6 +5738,78 @@ static Object* Runtime_GetCFrames(Arguments args) {
} }
static Object* Runtime_GetThreadCount(Arguments args) {
HandleScope scope;
ASSERT(args.length() == 1);
// Check arguments.
Object* result = Runtime_CheckExecutionState(args);
if (result->IsFailure()) return result;
// Count all archived V8 threads.
int n = 0;
for (ThreadState* thread = ThreadState::FirstInUse();
thread != NULL;
thread = thread->Next()) {
n++;
}
// Total number of threads is current thread and archived threads.
return Smi::FromInt(n + 1);
}
static const int kThreadDetailsCurrentThreadIndex = 0;
static const int kThreadDetailsThreadIdIndex = 1;
static const int kThreadDetailsSize = 2;
// Return an array with thread details
// args[0]: number: break id
// args[1]: number: thread index
//
// The array returned contains the following information:
// 0: Is current thread?
// 1: Thread id
static Object* Runtime_GetThreadDetails(Arguments args) {
HandleScope scope;
ASSERT(args.length() == 2);
// Check arguments.
Object* check = Runtime_CheckExecutionState(args);
if (check->IsFailure()) return check;
CONVERT_NUMBER_CHECKED(int, index, Int32, args[1]);
// Allocate array for result.
Handle<FixedArray> details = Factory::NewFixedArray(kThreadDetailsSize);
// Thread index 0 is current thread.
if (index == 0) {
// Fill the details.
details->set(kThreadDetailsCurrentThreadIndex, Heap::true_value());
details->set(kThreadDetailsThreadIdIndex,
Smi::FromInt(ThreadManager::CurrentId()));
} else {
// Find the thread with the requested index.
int n = 1;
ThreadState* thread = ThreadState::FirstInUse();
while (index != n && thread != NULL) {
thread = thread->Next();
n++;
}
if (thread == NULL) {
return Heap::undefined_value();
}
// Fill the details.
details->set(kThreadDetailsCurrentThreadIndex, Heap::false_value());
details->set(kThreadDetailsThreadIdIndex, Smi::FromInt(thread->id()));
}
// Convert to JS array and return.
return *Factory::NewJSArrayWithElements(details);
}
static Object* Runtime_GetBreakLocations(Arguments args) { static Object* Runtime_GetBreakLocations(Arguments args) {
HandleScope scope; HandleScope scope;
ASSERT(args.length() == 1); ASSERT(args.length() == 1);
......
...@@ -234,6 +234,8 @@ namespace v8 { namespace internal { ...@@ -234,6 +234,8 @@ namespace v8 { namespace internal {
F(GetFrameCount, 1) \ F(GetFrameCount, 1) \
F(GetFrameDetails, 2) \ F(GetFrameDetails, 2) \
F(GetCFrames, 1) \ F(GetCFrames, 1) \
F(GetThreadCount, 1) \
F(GetThreadDetails, 2) \
F(GetBreakLocations, 1) \ F(GetBreakLocations, 1) \
F(SetFunctionBreakPoint, 3) \ F(SetFunctionBreakPoint, 3) \
F(SetScriptBreakPoint, 3) \ F(SetScriptBreakPoint, 3) \
......
...@@ -38,6 +38,8 @@ namespace v8 { ...@@ -38,6 +38,8 @@ namespace v8 {
static internal::Thread::LocalStorageKey thread_state_key = static internal::Thread::LocalStorageKey thread_state_key =
internal::Thread::CreateThreadLocalKey(); internal::Thread::CreateThreadLocalKey();
static internal::Thread::LocalStorageKey thread_id_key =
internal::Thread::CreateThreadLocalKey();
// Track whether this V8 instance has ever called v8::Locker. This allows the // Track whether this V8 instance has ever called v8::Locker. This allows the
...@@ -61,6 +63,9 @@ Locker::Locker() : has_lock_(false), top_level_(true) { ...@@ -61,6 +63,9 @@ Locker::Locker() : has_lock_(false), top_level_(true) {
} }
} }
ASSERT(internal::ThreadManager::IsLockedByCurrentThread()); ASSERT(internal::ThreadManager::IsLockedByCurrentThread());
// Make sure this thread is assigned a thread id.
internal::ThreadManager::AssignId();
} }
...@@ -115,6 +120,7 @@ bool ThreadManager::RestoreThread() { ...@@ -115,6 +120,7 @@ bool ThreadManager::RestoreThread() {
lazily_archived_thread_.Initialize(ThreadHandle::INVALID); lazily_archived_thread_.Initialize(ThreadHandle::INVALID);
ASSERT(Thread::GetThreadLocal(thread_state_key) == ASSERT(Thread::GetThreadLocal(thread_state_key) ==
lazily_archived_thread_state_); lazily_archived_thread_state_);
lazily_archived_thread_state_->set_id(kInvalidId);
lazily_archived_thread_state_->LinkInto(ThreadState::FREE_LIST); lazily_archived_thread_state_->LinkInto(ThreadState::FREE_LIST);
lazily_archived_thread_state_ = NULL; lazily_archived_thread_state_ = NULL;
Thread::SetThreadLocal(thread_state_key, NULL); Thread::SetThreadLocal(thread_state_key, NULL);
...@@ -143,6 +149,7 @@ bool ThreadManager::RestoreThread() { ...@@ -143,6 +149,7 @@ bool ThreadManager::RestoreThread() {
from = RegExpStack::RestoreStack(from); from = RegExpStack::RestoreStack(from);
from = Bootstrapper::RestoreState(from); from = Bootstrapper::RestoreState(from);
Thread::SetThreadLocal(thread_state_key, NULL); Thread::SetThreadLocal(thread_state_key, NULL);
state->set_id(kInvalidId);
state->Unlink(); state->Unlink();
state->LinkInto(ThreadState::FREE_LIST); state->LinkInto(ThreadState::FREE_LIST);
return true; return true;
...@@ -176,7 +183,8 @@ ThreadState* ThreadState::free_anchor_ = new ThreadState(); ...@@ -176,7 +183,8 @@ ThreadState* ThreadState::free_anchor_ = new ThreadState();
ThreadState* ThreadState::in_use_anchor_ = new ThreadState(); ThreadState* ThreadState::in_use_anchor_ = new ThreadState();
ThreadState::ThreadState() : next_(this), previous_(this) { ThreadState::ThreadState() : id_(ThreadManager::kInvalidId),
next_(this), previous_(this) {
} }
...@@ -224,6 +232,7 @@ ThreadState* ThreadState::Next() { ...@@ -224,6 +232,7 @@ ThreadState* ThreadState::Next() {
} }
int ThreadManager::next_id_ = 0;
Mutex* ThreadManager::mutex_ = OS::CreateMutex(); Mutex* ThreadManager::mutex_ = OS::CreateMutex();
ThreadHandle ThreadManager::mutex_owner_(ThreadHandle::INVALID); ThreadHandle ThreadManager::mutex_owner_(ThreadHandle::INVALID);
ThreadHandle ThreadManager::lazily_archived_thread_(ThreadHandle::INVALID); ThreadHandle ThreadManager::lazily_archived_thread_(ThreadHandle::INVALID);
...@@ -238,6 +247,9 @@ void ThreadManager::ArchiveThread() { ...@@ -238,6 +247,9 @@ void ThreadManager::ArchiveThread() {
Thread::SetThreadLocal(thread_state_key, reinterpret_cast<void*>(state)); Thread::SetThreadLocal(thread_state_key, reinterpret_cast<void*>(state));
lazily_archived_thread_.Initialize(ThreadHandle::SELF); lazily_archived_thread_.Initialize(ThreadHandle::SELF);
lazily_archived_thread_state_ = state; lazily_archived_thread_state_ = state;
ASSERT(state->id() == kInvalidId);
state->set_id(CurrentId());
ASSERT(state->id() != kInvalidId);
} }
...@@ -290,6 +302,18 @@ void ThreadManager::MarkCompactEpilogue(bool is_compacting) { ...@@ -290,6 +302,18 @@ void ThreadManager::MarkCompactEpilogue(bool is_compacting) {
} }
int ThreadManager::CurrentId() {
return bit_cast<int, void*>(Thread::GetThreadLocal(thread_id_key));
}
void ThreadManager::AssignId() {
if (Thread::GetThreadLocal(thread_id_key) == NULL) {
Thread::SetThreadLocal(thread_id_key, bit_cast<void*, int>(next_id_++));
}
}
// This is the ContextSwitcher singleton. There is at most a single thread // This is the ContextSwitcher singleton. There is at most a single thread
// running which delivers preemption events to V8 threads. // running which delivers preemption events to V8 threads.
ContextSwitcher* ContextSwitcher::singleton_ = NULL; ContextSwitcher* ContextSwitcher::singleton_ = NULL;
......
...@@ -45,6 +45,10 @@ class ThreadState { ...@@ -45,6 +45,10 @@ class ThreadState {
static ThreadState* GetFree(); static ThreadState* GetFree();
// Id of thread.
void set_id(int id) { id_ = id; }
int id() { return id_; }
// Get data area for archiving a thread. // Get data area for archiving a thread.
char* data() { return data_; } char* data() { return data_; }
private: private:
...@@ -52,9 +56,11 @@ class ThreadState { ...@@ -52,9 +56,11 @@ class ThreadState {
void AllocateSpace(); void AllocateSpace();
int id_;
char* data_; char* data_;
ThreadState* next_; ThreadState* next_;
ThreadState* previous_; ThreadState* previous_;
// In the following two lists there is always at least one object on the list. // In the following two lists there is always at least one object on the list.
// The first object is a flying anchor that is only there to simplify linking // The first object is a flying anchor that is only there to simplify linking
// and unlinking. // and unlinking.
...@@ -77,9 +83,15 @@ class ThreadManager : public AllStatic { ...@@ -77,9 +83,15 @@ class ThreadManager : public AllStatic {
static void MarkCompactPrologue(bool is_compacting); static void MarkCompactPrologue(bool is_compacting);
static void MarkCompactEpilogue(bool is_compacting); static void MarkCompactEpilogue(bool is_compacting);
static bool IsLockedByCurrentThread() { return mutex_owner_.IsSelf(); } static bool IsLockedByCurrentThread() { return mutex_owner_.IsSelf(); }
static int CurrentId();
static void AssignId();
static const int kInvalidId = -1;
private: private:
static void EagerlyArchiveThread(); static void EagerlyArchiveThread();
static int next_id_; // V8 threads are identified through an integer.
static Mutex* mutex_; static Mutex* mutex_;
static ThreadHandle mutex_owner_; static ThreadHandle mutex_owner_;
static ThreadHandle lazily_archived_thread_; static ThreadHandle lazily_archived_thread_;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment