blob: c9a47c2418d3fd8efe0268813c6e2e544d332988 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/service_isolate.h"
#include "vm/compiler.h"
#include "vm/dart_api_impl.h"
#include "vm/dart_entry.h"
#include "vm/isolate.h"
#include "vm/lockers.h"
#include "vm/message.h"
#include "vm/message_handler.h"
#include "vm/native_entry.h"
#include "vm/native_arguments.h"
#include "vm/object.h"
#include "vm/object_store.h"
#include "vm/port.h"
#include "vm/service.h"
#include "vm/symbols.h"
#include "vm/thread_pool.h"
namespace dart {
DEFINE_FLAG(bool, trace_service, false, "Trace VM service requests.");
DEFINE_FLAG(bool, trace_service_pause_events, false,
"Trace VM service isolate pause events.");
struct ResourcesEntry {
const char* path_;
const char* resource_;
int length_;
};
extern ResourcesEntry __service_resources_[];
class Resources {
public:
static const int kNoSuchInstance = -1;
static int ResourceLookup(const char* path, const char** resource) {
ResourcesEntry* table = ResourceTable();
for (int i = 0; table[i].path_ != NULL; i++) {
const ResourcesEntry& entry = table[i];
if (strcmp(path, entry.path_) == 0) {
*resource = entry.resource_;
ASSERT(entry.length_ > 0);
return entry.length_;
}
}
return kNoSuchInstance;
}
static const char* Path(int idx) {
ASSERT(idx >= 0);
ResourcesEntry* entry = At(idx);
if (entry == NULL) {
return NULL;
}
ASSERT(entry->path_ != NULL);
return entry->path_;
}
static int Length(int idx) {
ASSERT(idx >= 0);
ResourcesEntry* entry = At(idx);
if (entry == NULL) {
return kNoSuchInstance;
}
ASSERT(entry->path_ != NULL);
return entry->length_;
}
static const uint8_t* Resource(int idx) {
ASSERT(idx >= 0);
ResourcesEntry* entry = At(idx);
if (entry == NULL) {
return NULL;
}
return reinterpret_cast<const uint8_t*>(entry->resource_);
}
private:
static ResourcesEntry* At(int idx) {
ASSERT(idx >= 0);
ResourcesEntry* table = ResourceTable();
for (int i = 0; table[i].path_ != NULL; i++) {
if (idx == i) {
return &table[i];
}
}
return NULL;
}
static ResourcesEntry* ResourceTable() {
return &__service_resources_[0];
}
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(Resources);
};
static uint8_t* allocator(uint8_t* ptr, intptr_t old_size, intptr_t new_size) {
void* new_ptr = realloc(reinterpret_cast<void*>(ptr), new_size);
return reinterpret_cast<uint8_t*>(new_ptr);
}
static Dart_Port ExtractPort(Isolate* isolate, Dart_Handle receivePort) {
const ReceivePort& rp = Api::UnwrapReceivePortHandle(isolate, receivePort);
if (rp.IsNull()) {
return ILLEGAL_PORT;
}
return rp.Id();
}
// These must be kept in sync with service/constants.dart
#define VM_SERVICE_ISOLATE_EXIT_MESSAGE_ID 0
#define VM_SERVICE_ISOLATE_STARTUP_MESSAGE_ID 1
#define VM_SERVICE_ISOLATE_SHUTDOWN_MESSAGE_ID 2
static RawArray* MakeServiceControlMessage(Dart_Port port_id, intptr_t code,
const String& name) {
const Array& list = Array::Handle(Array::New(4));
ASSERT(!list.IsNull());
const Integer& code_int = Integer::Handle(Integer::New(code));
const Integer& port_int = Integer::Handle(Integer::New(port_id));
const SendPort& send_port = SendPort::Handle(SendPort::New(port_id));
list.SetAt(0, code_int);
list.SetAt(1, port_int);
list.SetAt(2, send_port);
list.SetAt(3, name);
return list.raw();
}
static RawArray* MakeServiceExitMessage() {
const Array& list = Array::Handle(Array::New(1));
ASSERT(!list.IsNull());
const intptr_t code = VM_SERVICE_ISOLATE_EXIT_MESSAGE_ID;
const Integer& code_int = Integer::Handle(Integer::New(code));
list.SetAt(0, code_int);
return list.raw();
}
const char* ServiceIsolate::kName = "vm-service";
Isolate* ServiceIsolate::isolate_ = NULL;
Dart_Port ServiceIsolate::port_ = ILLEGAL_PORT;
Dart_Port ServiceIsolate::load_port_ = ILLEGAL_PORT;
Dart_IsolateCreateCallback ServiceIsolate::create_callback_ = NULL;
uint8_t* ServiceIsolate::exit_message_ = NULL;
intptr_t ServiceIsolate::exit_message_length_ = 0;
Monitor* ServiceIsolate::monitor_ = NULL;
bool ServiceIsolate::initializing_ = true;
bool ServiceIsolate::shutting_down_ = false;
class RegisterRunningIsolatesVisitor : public IsolateVisitor {
public:
explicit RegisterRunningIsolatesVisitor(Isolate* service_isolate)
: IsolateVisitor(),
register_function_(Function::Handle(service_isolate)),
service_isolate_(service_isolate) {
ASSERT(ServiceIsolate::IsServiceIsolate(Isolate::Current()));
// Get library.
const String& library_url = Symbols::DartVMService();
ASSERT(!library_url.IsNull());
const Library& library =
Library::Handle(Library::LookupLibrary(library_url));
ASSERT(!library.IsNull());
// Get function.
const String& function_name =
String::Handle(String::New("_registerIsolate"));
ASSERT(!function_name.IsNull());
register_function_ = library.LookupFunctionAllowPrivate(function_name);
ASSERT(!register_function_.IsNull());
}
virtual void VisitIsolate(Isolate* isolate) {
ASSERT(ServiceIsolate::IsServiceIsolate(Isolate::Current()));
if (ServiceIsolate::IsServiceIsolate(isolate) ||
(isolate == Dart::vm_isolate())) {
// We do not register the service or vm isolate.
return;
}
// Setup arguments for call.
Dart_Port port_id = isolate->main_port();
const Integer& port_int = Integer::Handle(Integer::New(port_id));
ASSERT(!port_int.IsNull());
const SendPort& send_port = SendPort::Handle(SendPort::New(port_id));
const String& name = String::Handle(String::New(isolate->name()));
ASSERT(!name.IsNull());
const Array& args = Array::Handle(Array::New(3));
ASSERT(!args.IsNull());
args.SetAt(0, port_int);
args.SetAt(1, send_port);
args.SetAt(2, name);
Object& r = Object::Handle(service_isolate_);
r = DartEntry::InvokeFunction(register_function_, args);
if (FLAG_trace_service) {
OS::Print("vm-service: Isolate %s %" Pd64 " registered.\n",
name.ToCString(),
port_id);
}
ASSERT(!r.IsError());
}
private:
Function& register_function_;
Isolate* service_isolate_;
};
class ServiceIsolateNatives : public AllStatic {
public:
static void SendIsolateServiceMessage(Dart_NativeArguments args) {
NativeArguments* arguments = reinterpret_cast<NativeArguments*>(args);
Isolate* isolate = arguments->isolate();
StackZone stack_zone(isolate);
Zone* zone = stack_zone.GetZone(); // Used by GET_NON_NULL_NATIVE_ARGUMENT.
HANDLESCOPE(isolate);
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, sp, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Array, message, arguments->NativeArgAt(1));
// Set the type of the OOB message.
message.SetAt(0, Smi::Handle(isolate, Smi::New(Message::kServiceOOBMsg)));
// Serialize message.
uint8_t* data = NULL;
MessageWriter writer(&data, &allocator, false);
writer.WriteMessage(message);
// TODO(turnidge): Throw an exception when the return value is false?
bool result = PortMap::PostMessage(
new Message(sp.Id(), data, writer.BytesWritten(),
Message::kOOBPriority));
arguments->SetReturn(Bool::Get(result));
}
static void SendRootServiceMessage(Dart_NativeArguments args) {
NativeArguments* arguments = reinterpret_cast<NativeArguments*>(args);
Isolate* isolate = arguments->isolate();
StackZone stack_zone(isolate);
Zone* zone = stack_zone.GetZone(); // Used by GET_NON_NULL_NATIVE_ARGUMENT.
HANDLESCOPE(isolate);
GET_NON_NULL_NATIVE_ARGUMENT(Array, message, arguments->NativeArgAt(0));
Service::HandleRootMessage(message);
}
static void SetEventMask(Dart_NativeArguments args) {
NativeArguments* arguments = reinterpret_cast<NativeArguments*>(args);
Isolate* isolate = arguments->isolate();
StackZone stack_zone(isolate);
Zone* zone = stack_zone.GetZone(); // Used by GET_NON_NULL_NATIVE_ARGUMENT.
HANDLESCOPE(isolate);
GET_NON_NULL_NATIVE_ARGUMENT(Integer, mask, arguments->NativeArgAt(0));
Service::SetEventMask(mask.AsTruncatedUint32Value());
}
static void OnStart(Dart_NativeArguments args) {
NativeArguments* arguments = reinterpret_cast<NativeArguments*>(args);
Isolate* isolate = arguments->isolate();
StackZone zone(isolate);
HANDLESCOPE(isolate);
{
if (FLAG_trace_service) {
OS::Print("vm-service: Booting dart:vmservice library.\n");
}
// Boot the dart:vmservice library.
Dart_EnterScope();
Dart_Handle url_str =
Dart_NewStringFromCString(Symbols::Name(Symbols::kDartVMServiceId));
Dart_Handle library = Dart_LookupLibrary(url_str);
ASSERT(Dart_IsLibrary(library));
Dart_Handle result =
Dart_Invoke(library, Dart_NewStringFromCString("boot"), 0, NULL);
ASSERT(!Dart_IsError(result));
Dart_Port port = ExtractPort(isolate, result);
ASSERT(port != ILLEGAL_PORT);
ServiceIsolate::SetServicePort(port);
Dart_ExitScope();
}
{
if (FLAG_trace_service) {
OS::Print("vm-service: Registering running isolates.\n");
}
// Register running isolates with service.
RegisterRunningIsolatesVisitor register_isolates(isolate);
Isolate::VisitIsolates(&register_isolates);
}
}
static void OnExit(Dart_NativeArguments args) {
NativeArguments* arguments = reinterpret_cast<NativeArguments*>(args);
Isolate* isolate = arguments->isolate();
StackZone zone(isolate);
HANDLESCOPE(isolate);
{
if (FLAG_trace_service) {
OS::Print("vm-service: processed exit message.\n");
}
}
}
};
struct ServiceNativeEntry {
const char* name;
int num_arguments;
Dart_NativeFunction function;
};
static ServiceNativeEntry _ServiceNativeEntries[] = {
{"VMService_SendIsolateServiceMessage", 2,
ServiceIsolateNatives::SendIsolateServiceMessage},
{"VMService_SendRootServiceMessage", 1,
ServiceIsolateNatives::SendRootServiceMessage},
{"VMService_SetEventMask", 1,
ServiceIsolateNatives::SetEventMask},
{"VMService_OnStart", 0,
ServiceIsolateNatives::OnStart },
{"VMService_OnExit", 0,
ServiceIsolateNatives::OnExit },
};
static Dart_NativeFunction ServiceNativeResolver(Dart_Handle name,
int num_arguments,
bool* auto_setup_scope) {
const Object& obj = Object::Handle(Api::UnwrapHandle(name));
if (!obj.IsString()) {
return NULL;
}
const char* function_name = obj.ToCString();
ASSERT(function_name != NULL);
ASSERT(auto_setup_scope != NULL);
*auto_setup_scope = true;
intptr_t n = sizeof(_ServiceNativeEntries) /
sizeof(_ServiceNativeEntries[0]);
for (intptr_t i = 0; i < n; i++) {
ServiceNativeEntry entry = _ServiceNativeEntries[i];
if ((strcmp(function_name, entry.name) == 0) &&
(num_arguments == entry.num_arguments)) {
return entry.function;
}
}
return NULL;
}
bool ServiceIsolate::NameEquals(const char* name) {
ASSERT(name != NULL);
return strcmp(name, kName) == 0;
}
bool ServiceIsolate::Exists() {
MonitorLocker ml(monitor_);
return isolate_ != NULL;
}
bool ServiceIsolate::IsRunning() {
MonitorLocker ml(monitor_);
return (port_ != ILLEGAL_PORT) && (isolate_ != NULL);
}
bool ServiceIsolate::IsServiceIsolate(Isolate* isolate) {
MonitorLocker ml(monitor_);
return isolate == isolate_;
}
Dart_Port ServiceIsolate::Port() {
MonitorLocker ml(monitor_);
return port_;
}
Dart_Port ServiceIsolate::WaitForLoadPort() {
MonitorLocker ml(monitor_);
while (initializing_ && (load_port_ == ILLEGAL_PORT)) {
ml.Wait();
}
return load_port_;
}
Dart_Port ServiceIsolate::LoadPort() {
MonitorLocker ml(monitor_);
return load_port_;
}
bool ServiceIsolate::SendIsolateStartupMessage() {
if (!IsRunning()) {
return false;
}
Isolate* isolate = Isolate::Current();
if (IsServiceIsolate(isolate)) {
return false;
}
ASSERT(isolate != NULL);
HANDLESCOPE(isolate);
const String& name = String::Handle(String::New(isolate->name()));
ASSERT(!name.IsNull());
const Array& list = Array::Handle(
MakeServiceControlMessage(Dart_GetMainPortId(),
VM_SERVICE_ISOLATE_STARTUP_MESSAGE_ID,
name));
ASSERT(!list.IsNull());
uint8_t* data = NULL;
MessageWriter writer(&data, &allocator, false);
writer.WriteMessage(list);
intptr_t len = writer.BytesWritten();
if (FLAG_trace_service) {
OS::Print("vm-service: Isolate %s %" Pd64 " registered.\n",
name.ToCString(),
Dart_GetMainPortId());
}
return PortMap::PostMessage(
new Message(port_, data, len, Message::kNormalPriority));
}
bool ServiceIsolate::SendIsolateShutdownMessage() {
if (!IsRunning()) {
return false;
}
Isolate* isolate = Isolate::Current();
if (IsServiceIsolate(isolate)) {
return false;
}
ASSERT(isolate != NULL);
HANDLESCOPE(isolate);
const String& name = String::Handle(String::New(isolate->name()));
ASSERT(!name.IsNull());
const Array& list = Array::Handle(
MakeServiceControlMessage(Dart_GetMainPortId(),
VM_SERVICE_ISOLATE_SHUTDOWN_MESSAGE_ID,
name));
ASSERT(!list.IsNull());
uint8_t* data = NULL;
MessageWriter writer(&data, &allocator, false);
writer.WriteMessage(list);
intptr_t len = writer.BytesWritten();
if (FLAG_trace_service) {
OS::Print("vm-service: Isolate %s %" Pd64 " deregistered.\n",
name.ToCString(),
Dart_GetMainPortId());
}
return PortMap::PostMessage(
new Message(port_, data, len, Message::kNormalPriority));
}
void ServiceIsolate::SendServiceExitMessage() {
if (!IsRunning()) {
return;
}
if ((exit_message_ == NULL) || (exit_message_length_ == 0)) {
return;
}
if (FLAG_trace_service) {
OS::Print("vm-service: sending service exit message.\n");
}
PortMap::PostMessage(new Message(port_,
exit_message_,
exit_message_length_,
Message::kNormalPriority));
}
void ServiceIsolate::SetServicePort(Dart_Port port) {
MonitorLocker ml(monitor_);
port_ = port;
}
void ServiceIsolate::SetServiceIsolate(Isolate* isolate) {
MonitorLocker ml(monitor_);
isolate_ = isolate;
if (isolate_ != NULL) {
isolate_->is_service_isolate_ = true;
}
}
void ServiceIsolate::SetLoadPort(Dart_Port port) {
MonitorLocker ml(monitor_);
load_port_ = port;
}
void ServiceIsolate::MaybeInjectVMServiceLibrary(Isolate* isolate) {
ASSERT(isolate != NULL);
ASSERT(isolate->name() != NULL);
if (!ServiceIsolate::NameEquals(isolate->name())) {
// Not service isolate.
return;
}
if (Exists()) {
// Service isolate already exists.
return;
}
SetServiceIsolate(isolate);
StackZone zone(isolate);
HANDLESCOPE(isolate);
// Register dart:vmservice library.
const String& url_str = String::Handle(Symbols::DartVMService().raw());
const Library& library = Library::Handle(Library::New(url_str));
library.Register();
library.set_native_entry_resolver(ServiceNativeResolver);
// Temporarily install our library tag handler.
isolate->set_library_tag_handler(LibraryTagHandler);
// Get script source.
const char* resource = NULL;
const char* path = "/vmservice.dart";
intptr_t r = Resources::ResourceLookup(path, &resource);
ASSERT(r != Resources::kNoSuchInstance);
ASSERT(resource != NULL);
const String& source_str = String::Handle(
String::FromUTF8(reinterpret_cast<const uint8_t*>(resource), r));
ASSERT(!source_str.IsNull());
const Script& script = Script::Handle(
isolate, Script::New(url_str, source_str, RawScript::kLibraryTag));
// Compile script.
Dart_EnterScope(); // Need to enter scope for tag handler.
library.SetLoadInProgress();
const Error& error = Error::Handle(isolate,
Compiler::Compile(library, script));
ASSERT(error.IsNull());
Dart_Handle result = Dart_FinalizeLoading(false);
ASSERT(!Dart_IsError(result));
Dart_ExitScope();
// Uninstall our library tag handler.
isolate->set_library_tag_handler(NULL);
}
void ServiceIsolate::ConstructExitMessageAndCache(Isolate* isolate) {
// Construct and cache exit message here so we can send it without needing an
// isolate.
StartIsolateScope iso_scope(isolate);
StackZone zone(isolate);
HANDLESCOPE(isolate);
ASSERT(exit_message_ == NULL);
ASSERT(exit_message_length_ == 0);
const Array& list = Array::Handle(MakeServiceExitMessage());
ASSERT(!list.IsNull());
MessageWriter writer(&exit_message_, &allocator, false);
writer.WriteMessage(list);
exit_message_length_ = writer.BytesWritten();
ASSERT(exit_message_ != NULL);
ASSERT(exit_message_length_ != 0);
}
void ServiceIsolate::FinishedExiting() {
MonitorLocker ml(monitor_);
shutting_down_ = false;
ml.NotifyAll();
}
void ServiceIsolate::FinishedInitializing() {
MonitorLocker ml(monitor_);
initializing_ = false;
ml.NotifyAll();
}
class RunServiceTask : public ThreadPool::Task {
public:
virtual void Run() {
ASSERT(Isolate::Current() == NULL);
char* error = NULL;
Isolate* isolate = NULL;
Dart_IsolateCreateCallback create_callback =
ServiceIsolate::create_callback();
// TODO(johnmccutchan): Support starting up service isolate without embedder
// provided isolate creation callback.
if (create_callback == NULL) {
ServiceIsolate::FinishedInitializing();
return;
}
isolate =
reinterpret_cast<Isolate*>(create_callback(ServiceIsolate::kName,
NULL,
NULL,
NULL,
&error));
if (isolate == NULL) {
OS::PrintErr("vm-service: Isolate creation error: %s\n", error);
ServiceIsolate::FinishedInitializing();
return;
}
Isolate::SetCurrent(NULL);
ServiceIsolate::ConstructExitMessageAndCache(isolate);
RunMain(isolate);
ServiceIsolate::FinishedInitializing();
isolate->message_handler()->Run(Dart::thread_pool(),
NULL,
ShutdownIsolate,
reinterpret_cast<uword>(isolate));
}
protected:
static void ShutdownIsolate(uword parameter) {
Isolate* isolate = reinterpret_cast<Isolate*>(parameter);
ASSERT(ServiceIsolate::IsServiceIsolate(isolate));
{
// Print the error if there is one. This may execute dart code to
// print the exception object, so we need to use a StartIsolateScope.
StartIsolateScope start_scope(isolate);
StackZone zone(isolate);
HandleScope handle_scope(isolate);
Error& error = Error::Handle();
error = isolate->object_store()->sticky_error();
if (!error.IsNull()) {
OS::PrintErr("vm-service: Error: %s\n", error.ToErrorCString());
}
Dart::RunShutdownCallback();
}
{
// Shut the isolate down.
SwitchIsolateScope switch_scope(isolate);
Dart::ShutdownIsolate();
}
ServiceIsolate::SetServiceIsolate(NULL);
ServiceIsolate::SetServicePort(ILLEGAL_PORT);
if (FLAG_trace_service) {
OS::Print("vm-service: Shutdown.\n");
}
ServiceIsolate::FinishedExiting();
}
void RunMain(Isolate* isolate) {
StartIsolateScope iso_scope(isolate);
StackZone zone(isolate);
HANDLESCOPE(isolate);
// Invoke main which will return the loadScriptPort.
const Library& root_library =
Library::Handle(isolate, isolate->object_store()->root_library());
if (root_library.IsNull()) {
if (FLAG_trace_service) {
OS::Print("vm-service: Embedder did not install a script.");
}
// Service isolate is not supported by embedder.
return;
}
ASSERT(!root_library.IsNull());
const String& entry_name = String::Handle(isolate, String::New("main"));
ASSERT(!entry_name.IsNull());
const Function& entry =
Function::Handle(isolate,
root_library.LookupFunctionAllowPrivate(entry_name));
if (entry.IsNull()) {
// Service isolate is not supported by embedder.
if (FLAG_trace_service) {
OS::Print("vm-service: Embedder did not provide a main function.");
}
return;
}
ASSERT(!entry.IsNull());
const Object& result =
Object::Handle(isolate,
DartEntry::InvokeFunction(entry,
Object::empty_array()));
ASSERT(!result.IsNull());
if (result.IsError()) {
// Service isolate did not initialize properly.
if (FLAG_trace_service) {
const Error& error = Error::Cast(result);
OS::Print("vm-service: Calling main resulted in an error: %s",
error.ToErrorCString());
}
return;
}
ASSERT(result.IsReceivePort());
const ReceivePort& rp = ReceivePort::Cast(result);
ServiceIsolate::SetLoadPort(rp.Id());
}
};
void ServiceIsolate::Run() {
ASSERT(monitor_ == NULL);
monitor_ = new Monitor();
ASSERT(monitor_ != NULL);
// Grab the isolate create callback here to avoid race conditions with tests
// that change this after Dart_Initialize returns.
create_callback_ = Isolate::CreateCallback();
Dart::thread_pool()->Run(new RunServiceTask());
}
void ServiceIsolate::Shutdown() {
if (!IsRunning()) {
return;
}
{
MonitorLocker ml(monitor_);
shutting_down_ = true;
}
SendServiceExitMessage();
{
MonitorLocker ml(monitor_);
while (shutting_down_ && (port_ != ILLEGAL_PORT)) {
ml.Wait();
}
}
}
Dart_Handle ServiceIsolate::GetSource(const char* name) {
ASSERT(name != NULL);
int i = 0;
while (true) {
const char* path = Resources::Path(i);
if (path == NULL) {
break;
}
ASSERT(*path != '\0');
// Skip the '/'.
path++;
if (strcmp(name, path) == 0) {
const uint8_t* str = Resources::Resource(i);
intptr_t length = Resources::Length(i);
return Dart_NewStringFromUTF8(str, length);
}
i++;
}
FATAL1("vm-service: Could not find embedded source file: %s ", name);
return Dart_Null();
}
Dart_Handle ServiceIsolate::LibraryTagHandler(Dart_LibraryTag tag,
Dart_Handle library,
Dart_Handle url) {
if (tag == Dart_kCanonicalizeUrl) {
// url is already canonicalized.
return url;
}
if (tag != Dart_kSourceTag) {
FATAL("ServiceIsolate::LibraryTagHandler encountered an unexpected tag.");
}
ASSERT(tag == Dart_kSourceTag);
const char* url_string = NULL;
Dart_Handle result = Dart_StringToCString(url, &url_string);
if (Dart_IsError(result)) {
return result;
}
Dart_Handle source = GetSource(url_string);
if (Dart_IsError(source)) {
return source;
}
return Dart_LoadSource(library, url, source, 0, 0);
}
} // namespace dart