diff --git a/README.md b/README.md index 78d0ea18..9fdde616 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,12 @@ The following subcommands are supported: * -n, --name name - all properties with the specified name * -s, --string string - all properties that refer to the specified JavaScript string value + getactivehandles -- Print all pending handles in the queue. Equivalent to running process._getActiveHandles() on + the living process. + + getactiverequests -- Print all pending handles in the queue. Equivalent to running process._getActiveHandles() on + the living process. + inspect -- Print detailed description and contents of the JavaScript value. Possible flags (all optional): diff --git a/binding.gyp b/binding.gyp index 78864d48..eb367270 100644 --- a/binding.gyp +++ b/binding.gyp @@ -16,6 +16,7 @@ "<(lldb_include_dir)", ], + "cflags" : [ "-std=c++11" ], "conditions": [ @@ -58,10 +59,14 @@ "target_name": "plugin", "type": "shared_library", "sources": [ + "src/constants.cc", + "src/error.cc", "src/llnode.cc", "src/llv8.cc", "src/llv8-constants.cc", "src/llscan.cc", + "src/node.cc", + "src/node-constants.cc", ] }], } diff --git a/src/constants.cc b/src/constants.cc new file mode 100644 index 00000000..782f0fd6 --- /dev/null +++ b/src/constants.cc @@ -0,0 +1,121 @@ +#include + +#include + +#include "src/constants.h" + +using lldb::SBAddress; +using lldb::SBError; +using lldb::SBSymbol; +using lldb::SBSymbolContext; +using lldb::SBSymbolContextList; + +namespace llnode { + +template +T ReadSymbolFromTarget(SBTarget& target, SBAddress& start, const char* name, + Error& err) { + SBError sberr; + T res = 0; + target.ReadMemory(start, &res, sizeof(T), sberr); + if (!sberr.Fail()) { + err = Error::Ok(); + } else { + err = Error::Failure("Failed to read symbol %s", name); + } + return res; +} + +int64_t Constants::LookupConstant(SBTarget target, const char* name, + int64_t def, Error& err) { + int64_t res = 0; + res = def; + + SBSymbolContextList context_list = target.FindSymbols(name); + + if (!context_list.IsValid() || context_list.GetSize() == 0) { + err = Error::Failure("Failed to find symbol %s", name); + return res; + } + + SBSymbolContext context = context_list.GetContextAtIndex(0); + SBSymbol symbol = context.GetSymbol(); + if (!symbol.IsValid()) { + err = Error::Failure("Failed to fetch symbol %s from context", name); + return res; + } + + SBAddress start = symbol.GetStartAddress(); + SBAddress end = symbol.GetEndAddress(); + uint32_t size = end.GetOffset() - start.GetOffset(); + + // NOTE: size could be bigger for at the end symbols + if (size >= 8) { + res = ReadSymbolFromTarget(target, start, name, err); + } else if (size == 4) { + int32_t tmp = ReadSymbolFromTarget(target, start, name, err); + res = static_cast(tmp); + } else if (size == 2) { + int16_t tmp = ReadSymbolFromTarget(target, start, name, err); + res = static_cast(tmp); + } else if (size == 1) { + int8_t tmp = ReadSymbolFromTarget(target, start, name, err); + res = static_cast(tmp); + } else { + err = Error::Failure("Unexpected symbol size %" PRIu32 " of symbol %s", + size, name); + } + + return res; +} + +void Constants::Assign(SBTarget target) { + loaded_ = false; + target_ = target; +} + + +int64_t Constants::LoadRawConstant(const char* name, int64_t def) { + Error err; + int64_t v = Constants::LookupConstant(target_, name, def, err); + if (err.Fail()) { + Error::PrintInDebugMode( + "Failed to load raw constant %s, default to %" PRId64, name, def); + } + + return v; +} + +int64_t Constants::LoadConstant(const char* name, Error& err, int64_t def) { + int64_t v = Constants::LookupConstant( + target_, (constant_prefix() + name).c_str(), def, err); + return v; +} + +int64_t Constants::LoadConstant(const char* name, int64_t def) { + Error err; + int64_t v = LoadConstant(name, err, def); + if (err.Fail()) { + Error::PrintInDebugMode("Failed to load constant %s, default to %" PRId64, + name, def); + } + + return v; +} + +int64_t Constants::LoadConstant(const char* name, const char* fallback, + int64_t def) { + Error err; + int64_t v = LoadConstant(name, err, def); + if (err.Fail()) v = LoadConstant(fallback, err, def); + if (err.Fail()) { + Error::PrintInDebugMode( + "Failed to load constant %s, fallback %s, default to %" PRId64, name, + fallback, def); + } + + return v; +} + + +} // namespace llnode diff --git a/src/constants.h b/src/constants.h new file mode 100644 index 00000000..aefec402 --- /dev/null +++ b/src/constants.h @@ -0,0 +1,47 @@ +#ifndef SRC_CONSTANTS_H_ +#define SRC_CONSTANTS_H_ + +#include +#include + +#include "src/error.h" + +using lldb::SBTarget; + +namespace llnode { + +#define CONSTANTS_DEFAULT_METHODS(NAME) \ + inline NAME* operator()() { \ + if (loaded_) return this; \ + loaded_ = true; \ + Load(); \ + return this; \ + } + +class Constants { + public: + Constants() : loaded_(false) {} + + inline bool is_loaded() const { return loaded_; } + + void Assign(lldb::SBTarget target); + + inline virtual std::string constant_prefix() { return ""; }; + + static int64_t LookupConstant(SBTarget target, const char* name, int64_t def, + Error& err); + + protected: + int64_t LoadRawConstant(const char* name, int64_t def = -1); + int64_t LoadConstant(const char* name, Error& err, int64_t def = -1); + int64_t LoadConstant(const char* name, int64_t def = -1); + int64_t LoadConstant(const char* name, const char* fallback, + int64_t def = -1); + + lldb::SBTarget target_; + bool loaded_; +}; + +} // namespace llnode + +#endif diff --git a/src/error.cc b/src/error.cc new file mode 100644 index 00000000..463b5cbc --- /dev/null +++ b/src/error.cc @@ -0,0 +1,46 @@ +#include + +#include "error.h" + +namespace llnode { +bool Error::is_debug_mode = false; + +Error::Error(bool failed, const char* format, ...) { + failed_ = failed; + char tmp[kMaxMessageLength]; + va_list arglist; + va_start(arglist, format); + vsnprintf(tmp, sizeof(tmp), format, arglist); + va_end(arglist); + msg_ = tmp; +} + + +void Error::PrintInDebugMode(const char* format, ...) { + if (!is_debug_mode) { + return; + } + char fmt[kMaxMessageLength]; + snprintf(fmt, sizeof(fmt), "[llv8] %s\n", format); + va_list arglist; + va_start(arglist, format); + vfprintf(stderr, fmt, arglist); + va_end(arglist); +} + + +Error Error::Failure(std::string msg) { + PrintInDebugMode("%s", msg.c_str()); + return Error(true, msg); +} + + +Error Error::Failure(const char* format, ...) { + char tmp[kMaxMessageLength]; + va_list arglist; + va_start(arglist, format); + vsnprintf(tmp, sizeof(tmp), format, arglist); + va_end(arglist); + return Error::Failure(std::string(tmp)); +} +} // namespace llnode diff --git a/src/error.h b/src/error.h new file mode 100644 index 00000000..73018c9b --- /dev/null +++ b/src/error.h @@ -0,0 +1,37 @@ +#ifndef SRC_ERROR_H_ +#define SRC_ERROR_H_ + +#include + +namespace llnode { + +class Error { + public: + Error() : failed_(false), msg_("") {} + Error(bool failed, std::string msg) : failed_(failed), msg_(msg) {} + Error(bool failed, const char* format, ...) + __attribute__((format(printf, 3, 4))); + + static inline Error Ok() { return Error(false, "ok"); } + static Error Failure(std::string msg); + static Error Failure(const char* format, ...) + __attribute__((format(printf, 1, 2))); + static void PrintInDebugMode(const char* format, ...) + __attribute__((format(printf, 1, 2))); + + inline bool Success() const { return !Fail(); } + inline bool Fail() const { return failed_; } + + inline const char* GetMessage() { return msg_.c_str(); } + + static void SetDebugMode(bool mode) { is_debug_mode = mode; } + + private: + bool failed_; + std::string msg_; + static const size_t kMaxMessageLength = 128; + static bool is_debug_mode; +}; +} // namespace llnode + +#endif diff --git a/src/llnode.cc b/src/llnode.cc index 4a9cc7d1..00aa15b2 100644 --- a/src/llnode.cc +++ b/src/llnode.cc @@ -4,12 +4,16 @@ #include #include +#include +#include #include +#include "src/error.h" #include "src/llnode.h" #include "src/llscan.h" #include "src/llv8.h" +#include "src/node-inl.h" namespace llnode { @@ -19,6 +23,7 @@ using lldb::SBDebugger; using lldb::SBError; using lldb::SBExpressionOptions; using lldb::SBFrame; +using lldb::SBProcess; using lldb::SBStream; using lldb::SBSymbol; using lldb::SBTarget; @@ -121,7 +126,7 @@ bool BacktraceCmd::DoExecute(SBDebugger d, char** cmd, const uint64_t pc = frame.GetPC(); if (!frame.GetSymbol().IsValid()) { - v8::Error err; + Error err; v8::JSFrame v8_frame(llv8_, static_cast(frame.GetFP())); std::string res = v8_frame.Inspect(true, err); if (err.Success()) { @@ -197,7 +202,7 @@ bool PrintCmd::DoExecute(SBDebugger d, char** cmd, llv8_->Load(target); v8::Value v8_value(llv8_, value.GetValueAsSigned()); - v8::Error err; + Error err; std::string res = v8_value.Inspect(&inspect_options, err); if (err.Fail()) { result.SetError(err.GetMessage()); @@ -276,7 +281,7 @@ bool ListCmd::DoExecute(SBDebugger d, char** cmd, } // V8 frame - v8::Error err; + Error err; v8::JSFrame v8_frame(llv8_, static_cast(frame.GetFP())); const static uint32_t kDisplayLines = 4; @@ -299,6 +304,112 @@ bool ListCmd::DoExecute(SBDebugger d, char** cmd, return true; } +bool WorkqueueCmd::DoExecute(SBDebugger d, char** cmd, + SBCommandReturnObject& result) { + SBTarget target = d.GetSelectedTarget(); + SBProcess process = target.GetProcess(); + SBThread thread = process.GetSelectedThread(); + std::string result_message; + Error err; + + llv8_->Load(target); + node_->Load(target); + + if (!thread.IsValid()) { + result.SetError("No valid process, please start something\n"); + return false; + } + + node::Environment env = node::Environment::GetCurrent(node_, err); + if (err.Fail()) { + result.SetError(err.GetMessage()); + return false; + } + + result_message = GetResultMessage(&env, err); + + if (err.Fail()) { + result.SetError(err.GetMessage()); + return false; + } + + result.Printf("%s", result_message.c_str()); + return true; +} + +std::string GetActiveHandlesCmd::GetResultMessage(node::Environment* env, + Error& err) { + int active_handles = 0; + v8::Value::InspectOptions inspect_options; + inspect_options.detailed = true; + std::ostringstream result_message; + + for (auto w : env->handle_wrap_queue()) { + addr_t persistent_addr = w.persistent_addr(err); + if (err.Fail()) { + break; + } + if (persistent_addr == 0) { + continue; + } + + addr_t v8_object_addr = w.v8_object_addr(err); + if (err.Fail()) { + break; + } + v8::JSObject v8_object(llv8(), v8_object_addr); + std::string res = v8_object.Inspect(&inspect_options, err); + if (err.Fail()) { + Error::PrintInDebugMode("Failed to load object at address %" PRIx64, + v8_object_addr); + break; + } + + active_handles++; + result_message << res.c_str() << std::endl; + } + + result_message << "Total: " << active_handles << std::endl; + return result_message.str(); +} + + +std::string GetActiveRequestsCmd::GetResultMessage(node::Environment* env, + Error& err) { + int active_handles = 0; + v8::Value::InspectOptions inspect_options; + inspect_options.detailed = true; + std::ostringstream result_message; + + for (auto w : env->req_wrap_queue()) { + addr_t persistent_addr = w.persistent_addr(err); + if (err.Fail()) { + break; + } + if (persistent_addr == 0) { + continue; + } + + addr_t v8_object_addr = w.v8_object_addr(err); + if (err.Fail()) { + break; + } + v8::JSObject v8_object(llv8(), v8_object_addr); + std::string res = v8_object.Inspect(&inspect_options, err); + if (err.Fail()) { + Error::PrintInDebugMode("Failed to load object at address %" PRIx64, + v8_object_addr); + break; + } + + active_handles++; + result_message << res.c_str() << std::endl; + } + + result_message << "Total: " << active_handles << std::endl; + return result_message.str(); +} + void InitDebugMode() { bool is_debug_mode = false; @@ -307,7 +418,7 @@ void InitDebugMode() { is_debug_mode = true; } - v8::Error::SetDebugMode(is_debug_mode); + Error::SetDebugMode(is_debug_mode); } } // namespace llnode @@ -318,6 +429,7 @@ bool PluginInitialize(SBDebugger d) { llnode::InitDebugMode(); static llnode::v8::LLV8 llv8; + static llnode::node::Node node(&llv8); static llnode::LLScan llscan = llnode::LLScan(&llv8); SBCommandInterpreter interpreter = d.GetCommandInterpreter(); @@ -403,6 +515,16 @@ bool PluginInitialize(SBDebugger d) { "JavaScript string value\n" "\n"); + v8.AddCommand("getactivehandles", + new llnode::GetActiveHandlesCmd(&llv8, &node), + "Print all pending handles in the queue. Equivalent to running " + "process._getActiveHandles() on the living process.\n"); + + v8.AddCommand( + "getactiverequests", new llnode::GetActiveRequestsCmd(&llv8, &node), + "Print all pending requests in the queue. Equivalent to " + "running process._getActiveRequests() on the living process.\n"); + return true; } diff --git a/src/llnode.h b/src/llnode.h index 5340571c..6880b7f5 100644 --- a/src/llnode.h +++ b/src/llnode.h @@ -6,6 +6,7 @@ #include #include "src/llv8.h" +#include "src/node.h" namespace llnode { @@ -52,6 +53,43 @@ class ListCmd : public CommandBase { v8::LLV8* llv8_; }; +class WorkqueueCmd : public CommandBase { + public: + WorkqueueCmd(v8::LLV8* llv8, node::Node* node) : llv8_(llv8), node_(node) {} + ~WorkqueueCmd() override {} + + inline v8::LLV8* llv8() { return llv8_; }; + inline node::Node* node() { return node_; }; + + bool DoExecute(lldb::SBDebugger d, char** cmd, + lldb::SBCommandReturnObject& result) override; + + virtual std::string GetResultMessage(node::Environment* env, Error& err) { + return std::string(); + }; + + private: + v8::LLV8* llv8_; + node::Node* node_; +}; + +class GetActiveHandlesCmd : public WorkqueueCmd { + public: + GetActiveHandlesCmd(v8::LLV8* llv8, node::Node* node) + : WorkqueueCmd(llv8, node) {} + + std::string GetResultMessage(node::Environment* env, Error& err) override; +}; + +class GetActiveRequestsCmd : public WorkqueueCmd { + public: + GetActiveRequestsCmd(v8::LLV8* llv8, node::Node* node) + : WorkqueueCmd(llv8, node) {} + + std::string GetResultMessage(node::Environment* env, Error& err) override; +}; + + } // namespace llnode #endif // SRC_LLNODE_H_ diff --git a/src/llscan.cc b/src/llscan.cc index be6182be..e9cfa404 100644 --- a/src/llscan.cc +++ b/src/llscan.cc @@ -10,6 +10,7 @@ #include +#include "src/error.h" #include "src/llnode.h" #include "src/llscan.h" #include "src/llv8-inl.h" @@ -177,7 +178,7 @@ bool FindInstancesCmd::DoExecute(SBDebugger d, char** cmd, TypeRecord* t = instance_it->second; for (std::set::iterator it = t->GetInstances().begin(); it != t->GetInstances().end(); ++it) { - v8::Error err; + Error err; v8::Value v8_value(llscan_->v8(), *it); std::string res = v8_value.Inspect(&inspect_options, err); result.Printf("%s\n", res.c_str()); @@ -219,7 +220,7 @@ bool NodeInfoCmd::DoExecute(SBDebugger d, char** cmd, TypeRecord* t = instance_it->second; for (std::set::iterator it = t->GetInstances().begin(); it != t->GetInstances().end(); ++it) { - v8::Error err; + Error err; // The properties object should be a JSObject v8::JSObject process_obj(llscan_->v8(), *it); @@ -487,7 +488,7 @@ void FindReferencesCmd::ScanForReferences(ObjectScanner* scanner) { for (auto const entry : mapstoinstances) { TypeRecord* typerecord = entry.second; for (uint64_t addr : typerecord->GetInstances()) { - v8::Error err; + Error err; v8::Value obj_value(llscan_->v8(), addr); v8::HeapObject heap_object(obj_value); int64_t type = heap_object.GetType(err); @@ -526,7 +527,7 @@ void FindReferencesCmd::PrintReferences(SBCommandReturnObject& result, // Walk all the object instances and handle them according to their type. TypeRecordMap mapstoinstances = llscan_->GetMapsToInstances(); for (uint64_t addr : *references) { - v8::Error err; + Error err; v8::Value obj_value(llscan_->v8(), addr); v8::HeapObject heap_object(obj_value); int64_t type = heap_object.GetType(err); @@ -613,7 +614,7 @@ char** FindReferencesCmd::ParseScanOptions(char** cmd, ScanType* type) { void FindReferencesCmd::ReferenceScanner::PrintRefs( - SBCommandReturnObject& result, v8::JSObject& js_obj, v8::Error& err) { + SBCommandReturnObject& result, v8::JSObject& js_obj, Error& err) { int64_t length = js_obj.GetArrayLength(err); for (int64_t i = 0; i < length; ++i) { v8::Value v = js_obj.GetArrayElement(i, err); @@ -648,7 +649,7 @@ void FindReferencesCmd::ReferenceScanner::PrintRefs( void FindReferencesCmd::ReferenceScanner::PrintRefs( - SBCommandReturnObject& result, v8::String& str, v8::Error& err) { + SBCommandReturnObject& result, v8::String& str, Error& err) { v8::LLV8* v8 = str.v8(); int64_t repr = str.Representation(err); @@ -693,7 +694,7 @@ void FindReferencesCmd::ReferenceScanner::PrintRefs( void FindReferencesCmd::ReferenceScanner::ScanRefs(v8::JSObject& js_obj, - v8::Error& err) { + Error& err) { ReferencesVector* references; std::set already_saved; @@ -730,7 +731,7 @@ void FindReferencesCmd::ReferenceScanner::ScanRefs(v8::JSObject& js_obj, void FindReferencesCmd::ReferenceScanner::ScanRefs(v8::String& str, - v8::Error& err) { + Error& err) { ReferencesVector* references; std::set already_saved; @@ -788,7 +789,7 @@ ReferencesVector* FindReferencesCmd::ReferenceScanner::GetReferences() { void FindReferencesCmd::PropertyScanner::PrintRefs( - SBCommandReturnObject& result, v8::JSObject& js_obj, v8::Error& err) { + SBCommandReturnObject& result, v8::JSObject& js_obj, Error& err) { // (Note: We skip array elements as they don't have names.) // Walk all the properties in this object. @@ -814,7 +815,7 @@ void FindReferencesCmd::PropertyScanner::PrintRefs( void FindReferencesCmd::PropertyScanner::ScanRefs(v8::JSObject& js_obj, - v8::Error& err) { + Error& err) { // (Note: We skip array elements as they don't have names.) // Walk all the properties in this object. @@ -849,7 +850,7 @@ ReferencesVector* FindReferencesCmd::PropertyScanner::GetReferences() { void FindReferencesCmd::StringScanner::PrintRefs(SBCommandReturnObject& result, v8::JSObject& js_obj, - v8::Error& err) { + Error& err) { v8::LLV8* v8 = js_obj.v8(); int64_t length = js_obj.GetArrayLength(err); @@ -914,8 +915,7 @@ void FindReferencesCmd::StringScanner::PrintRefs(SBCommandReturnObject& result, void FindReferencesCmd::StringScanner::PrintRefs(SBCommandReturnObject& result, - v8::String& str, - v8::Error& err) { + v8::String& str, Error& err) { v8::LLV8* v8 = str.v8(); // Concatenated and sliced strings refer to other strings so @@ -984,7 +984,7 @@ void FindReferencesCmd::StringScanner::PrintRefs(SBCommandReturnObject& result, void FindReferencesCmd::StringScanner::ScanRefs(v8::JSObject& js_obj, - v8::Error& err) { + Error& err) { v8::LLV8* v8 = js_obj.v8(); ReferencesVector* references; std::set already_saved; @@ -1044,8 +1044,7 @@ void FindReferencesCmd::StringScanner::ScanRefs(v8::JSObject& js_obj, } -void FindReferencesCmd::StringScanner::ScanRefs(v8::String& str, - v8::Error& err) { +void FindReferencesCmd::StringScanner::ScanRefs(v8::String& str, Error& err) { v8::LLV8* v8 = str.v8(); ReferencesVector* references; @@ -1129,7 +1128,7 @@ FindJSObjectsVisitor::FindJSObjectsVisitor(SBTarget& target, LLScan* llscan) uint64_t FindJSObjectsVisitor::Visit(uint64_t location, uint64_t word) { v8::Value v8_value(llscan_->v8(), word); - v8::Error err; + Error err; // Test if this is SMI // Skip inspecting things that look like Smi's, they aren't objects. v8::Smi smi(v8_value); @@ -1177,7 +1176,7 @@ uint64_t FindJSObjectsVisitor::Visit(uint64_t location, uint64_t word) { void FindJSObjectsVisitor::InsertOnMapsToInstances( uint64_t word, v8::Map map, FindJSObjectsVisitor::MapCacheEntry map_info, - v8::Error& err) { + Error& err) { TypeRecord* t; auto entry = std::make_pair(map_info.type_name, nullptr); @@ -1196,7 +1195,7 @@ void FindJSObjectsVisitor::InsertOnMapsToInstances( void FindJSObjectsVisitor::InsertOnDetailedMapsToInstances( uint64_t word, v8::Map map, FindJSObjectsVisitor::MapCacheEntry map_info, - v8::Error& err) { + Error& err) { DetailedTypeRecord* t; auto type_name_with_properties = map_info.GetTypeNameWithProperties(); @@ -1223,7 +1222,7 @@ void FindJSObjectsVisitor::InsertOnDetailedMapsToInstances( } -bool FindJSObjectsVisitor::IsAHistogramType(v8::Map& map, v8::Error& err) { +bool FindJSObjectsVisitor::IsAHistogramType(v8::Map& map, Error& err) { int64_t type = map.GetType(err); if (err.Fail()) return false; @@ -1317,7 +1316,7 @@ std::string FindJSObjectsVisitor::MapCacheEntry::GetTypeNameWithProperties( bool FindJSObjectsVisitor::MapCacheEntry::Load(v8::Map map, v8::HeapObject heap_object, - v8::LLV8* llv8, v8::Error& err) { + v8::LLV8* llv8, Error& err) { // Check type first is_histogram = FindJSObjectsVisitor::IsAHistogramType(map, err); diff --git a/src/llscan.h b/src/llscan.h index 83fe74ff..9e352984 100644 --- a/src/llscan.h +++ b/src/llscan.h @@ -4,6 +4,8 @@ #include #include #include + +#include "src/error.h" #include "src/llnode.h" namespace llnode { @@ -78,13 +80,13 @@ class FindReferencesCmd : public CommandBase { virtual ReferencesVector* GetReferences() { return nullptr; }; - virtual void ScanRefs(v8::JSObject& js_obj, v8::Error& err){}; - virtual void ScanRefs(v8::String& str, v8::Error& err){}; + virtual void ScanRefs(v8::JSObject& js_obj, Error& err){}; + virtual void ScanRefs(v8::String& str, Error& err){}; virtual void PrintRefs(lldb::SBCommandReturnObject& result, - v8::JSObject& js_obj, v8::Error& err) {} + v8::JSObject& js_obj, Error& err) {} virtual void PrintRefs(lldb::SBCommandReturnObject& result, v8::String& str, - v8::Error& err) {} + Error& err) {} static const char* const property_reference_template; static const char* const array_reference_template; @@ -104,13 +106,13 @@ class FindReferencesCmd : public CommandBase { ReferencesVector* GetReferences() override; - void ScanRefs(v8::JSObject& js_obj, v8::Error& err) override; - void ScanRefs(v8::String& str, v8::Error& err) override; + void ScanRefs(v8::JSObject& js_obj, Error& err) override; + void ScanRefs(v8::String& str, Error& err) override; void PrintRefs(lldb::SBCommandReturnObject& result, v8::JSObject& js_obj, - v8::Error& err) override; + Error& err) override; void PrintRefs(lldb::SBCommandReturnObject& result, v8::String& str, - v8::Error& err) override; + Error& err) override; private: LLScan* llscan_; @@ -126,12 +128,12 @@ class FindReferencesCmd : public CommandBase { ReferencesVector* GetReferences() override; - void ScanRefs(v8::JSObject& js_obj, v8::Error& err) override; + void ScanRefs(v8::JSObject& js_obj, Error& err) override; // We only scan properties on objects not Strings, use default no-op impl // of PrintRefs for Strings. void PrintRefs(lldb::SBCommandReturnObject& result, v8::JSObject& js_obj, - v8::Error& err) override; + Error& err) override; private: LLScan* llscan_; @@ -148,13 +150,13 @@ class FindReferencesCmd : public CommandBase { ReferencesVector* GetReferences() override; - void ScanRefs(v8::JSObject& js_obj, v8::Error& err) override; - void ScanRefs(v8::String& str, v8::Error& err) override; + void ScanRefs(v8::JSObject& js_obj, Error& err) override; + void ScanRefs(v8::String& str, Error& err) override; void PrintRefs(lldb::SBCommandReturnObject& result, v8::JSObject& js_obj, - v8::Error& err) override; + Error& err) override; void PrintRefs(lldb::SBCommandReturnObject& result, v8::String& str, - v8::Error& err) override; + Error& err) override; static const char* const property_reference_template; static const char* const array_reference_template; @@ -264,17 +266,17 @@ class FindJSObjectsVisitor : MemoryVisitor { size_t max_properties = 0); bool Load(v8::Map map, v8::HeapObject heap_object, v8::LLV8* llv8, - v8::Error& err); + Error& err); }; - static bool IsAHistogramType(v8::Map& map, v8::Error& err); + static bool IsAHistogramType(v8::Map& map, Error& err); void InsertOnMapsToInstances(uint64_t word, v8::Map map, FindJSObjectsVisitor::MapCacheEntry map_info, - v8::Error& err); + Error& err); void InsertOnDetailedMapsToInstances( uint64_t word, v8::Map map, FindJSObjectsVisitor::MapCacheEntry map_info, - v8::Error& err); + Error& err); lldb::SBTarget& target_; uint32_t address_byte_size_; diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc index 7238d3f5..db372ec5 100644 --- a/src/llv8-constants.cc +++ b/src/llv8-constants.cc @@ -22,8 +22,6 @@ using lldb::SBSymbolContextList; using lldb::SBTarget; using lldb::addr_t; -static std::string kConstantPrefix = "v8dbg_"; - void Module::Assign(SBTarget target, Common* common) { loaded_ = false; target_ = target; @@ -31,107 +29,6 @@ void Module::Assign(SBTarget target, Common* common) { } -template -T ReadSymbolFromTarget(SBTarget& target, SBAddress& start, const char* name, - Error& err) { - SBError sberr; - T res = 0; - target.ReadMemory(start, &res, sizeof(T), sberr); - if (!sberr.Fail()) { - err = Error::Ok(); - } else { - err = Error::Failure("Failed to read symbol %s", name); - } - return res; -} - -static int64_t LookupConstant(SBTarget target, const char* name, int64_t def, - Error& err) { - int64_t res = 0; - res = def; - - SBSymbolContextList context_list = target.FindSymbols(name); - - if (!context_list.IsValid() || context_list.GetSize() == 0) { - err = Error::Failure("Failed to find symbol %s", name); - return res; - } - - SBSymbolContext context = context_list.GetContextAtIndex(0); - SBSymbol symbol = context.GetSymbol(); - if (!symbol.IsValid()) { - err = Error::Failure("Failed to fetch symbol %s from context", name); - return res; - } - - SBAddress start = symbol.GetStartAddress(); - SBAddress end = symbol.GetEndAddress(); - uint32_t size = end.GetOffset() - start.GetOffset(); - - // NOTE: size could be bigger for at the end symbols - if (size >= 8) { - res = ReadSymbolFromTarget(target, start, name, err); - } else if (size == 4) { - int32_t tmp = ReadSymbolFromTarget(target, start, name, err); - res = static_cast(tmp); - } else if (size == 2) { - int16_t tmp = ReadSymbolFromTarget(target, start, name, err); - res = static_cast(tmp); - } else if (size == 1) { - int8_t tmp = ReadSymbolFromTarget(target, start, name, err); - res = static_cast(tmp); - } else { - err = Error::Failure("Unexpected symbol size %" PRIu32 " of symbol %s", - size, name); - } - - return res; -} - - -int64_t Module::LoadRawConstant(const char* name, int64_t def) { - Error err; - int64_t v = LookupConstant(target_, name, def, err); - if (err.Fail()) { - Error::PrintInDebugMode( - "Failed to load raw constant %s, default to %" PRId64, name, def); - } - - return v; -} - -int64_t Module::LoadConstant(const char* name, Error& err, int64_t def) { - int64_t v = - LookupConstant(target_, (kConstantPrefix + name).c_str(), def, err); - return v; -} - -int64_t Module::LoadConstant(const char* name, int64_t def) { - Error err; - int64_t v = LoadConstant(name, err, def); - if (err.Fail()) { - Error::PrintInDebugMode("Failed to load constant %s, default to %" PRId64, - name, def); - } - - return v; -} - -int64_t Module::LoadConstant(const char* name, const char* fallback, - int64_t def) { - Error err; - int64_t v = LoadConstant(name, err, def); - if (err.Fail()) v = LoadConstant(fallback, err, def); - if (err.Fail()) { - Error::PrintInDebugMode( - "Failed to load constant %s, fallback %s, default to %" PRId64, name, - fallback, def); - } - - return v; -} - - void Common::Load() { kPointerSize = 1 << LoadConstant("PointerSizeLog2"); kVersionMajor = LoadRawConstant("v8::internal::Version::major_"); @@ -348,6 +245,15 @@ void Context::Load() { LoadConstant("class_Context__closure_index__int", "context_idx_closure"); kPreviousIndex = LoadConstant("class_Context__previous_index__int", "context_idx_prev"); + // TODO (mmarchini) change LoadConstant to accept variable arguments, a list + // of constants or a fallback list). + kNativeIndex = + LoadConstant("class_Context__native_index__int", "context_idx_native"); + if (kNativeIndex == -1) { + kNativeIndex = LoadConstant("class_Context__native_context_index__int"); + } + kEmbedderDataIndex = LoadConstant("context_idx_embedder_data", (int)5); + kMinContextSlots = LoadConstant("class_Context__min_context_slots__int", "context_min_slots"); } diff --git a/src/llv8-constants.h b/src/llv8-constants.h index 9c237153..b6859ceb 100644 --- a/src/llv8-constants.h +++ b/src/llv8-constants.h @@ -3,49 +3,29 @@ #include +#include "constants.h" + namespace llnode { namespace v8 { - -class Error; - namespace constants { // Forward declarations class Common; -class Module { +class Module : public Constants { public: - Module() : loaded_(false) {} - - inline bool is_loaded() const { return loaded_; } - void Assign(lldb::SBTarget target, Common* common = nullptr); - protected: - int64_t LoadRawConstant(const char* name, int64_t def = -1); - int64_t LoadConstant(const char* name, Error& err, int64_t def = -1); - int64_t LoadConstant(const char* name, int64_t def = -1); - int64_t LoadConstant(const char* name, const char* fallback, - int64_t def = -1); + inline std::string constant_prefix() override { return "v8dbg_"; } - lldb::SBTarget target_; + protected: Common* common_; - bool loaded_; }; -#define MODULE_DEFAULT_METHODS(NAME) \ - NAME() {} \ - inline NAME* operator()() { \ - if (loaded_) return this; \ - loaded_ = true; \ - Load(); \ - return this; \ - } - class Common : public Module { public: - MODULE_DEFAULT_METHODS(Common); + CONSTANTS_DEFAULT_METHODS(Common); int64_t kPointerSize; int64_t kVersionMajor; @@ -61,7 +41,7 @@ class Common : public Module { class Smi : public Module { public: - MODULE_DEFAULT_METHODS(Smi); + CONSTANTS_DEFAULT_METHODS(Smi); int64_t kTag; int64_t kTagMask; @@ -73,7 +53,7 @@ class Smi : public Module { class HeapObject : public Module { public: - MODULE_DEFAULT_METHODS(HeapObject); + CONSTANTS_DEFAULT_METHODS(HeapObject); int64_t kTag; int64_t kTagMask; @@ -86,7 +66,7 @@ class HeapObject : public Module { class Map : public Module { public: - MODULE_DEFAULT_METHODS(Map); + CONSTANTS_DEFAULT_METHODS(Map); int64_t kMapTypeMask; int64_t kInstanceAttrsOffset; @@ -106,7 +86,7 @@ class Map : public Module { class JSObject : public Module { public: - MODULE_DEFAULT_METHODS(JSObject); + CONSTANTS_DEFAULT_METHODS(JSObject); int64_t kPropertiesOffset; int64_t kElementsOffset; @@ -118,7 +98,7 @@ class JSObject : public Module { class HeapNumber : public Module { public: - MODULE_DEFAULT_METHODS(HeapNumber); + CONSTANTS_DEFAULT_METHODS(HeapNumber); int64_t kValueOffset; @@ -128,7 +108,7 @@ class HeapNumber : public Module { class JSArray : public Module { public: - MODULE_DEFAULT_METHODS(JSArray); + CONSTANTS_DEFAULT_METHODS(JSArray); int64_t kLengthOffset; @@ -138,7 +118,7 @@ class JSArray : public Module { class JSFunction : public Module { public: - MODULE_DEFAULT_METHODS(JSFunction); + CONSTANTS_DEFAULT_METHODS(JSFunction); int64_t kSharedInfoOffset; int64_t kContextOffset; @@ -149,7 +129,7 @@ class JSFunction : public Module { class JSRegExp : public Module { public: - MODULE_DEFAULT_METHODS(JSRegExp); + CONSTANTS_DEFAULT_METHODS(JSRegExp); int64_t kSourceOffset; @@ -159,7 +139,7 @@ class JSRegExp : public Module { class JSDate : public Module { public: - MODULE_DEFAULT_METHODS(JSDate); + CONSTANTS_DEFAULT_METHODS(JSDate); int64_t kValueOffset; @@ -169,7 +149,7 @@ class JSDate : public Module { class SharedInfo : public Module { public: - MODULE_DEFAULT_METHODS(SharedInfo); + CONSTANTS_DEFAULT_METHODS(SharedInfo); int64_t kNameOffset; int64_t kInferredNameOffset; @@ -190,7 +170,7 @@ class SharedInfo : public Module { class Code : public Module { public: - MODULE_DEFAULT_METHODS(Code) + CONSTANTS_DEFAULT_METHODS(Code) int64_t kStartOffset; int64_t kSizeOffset; @@ -201,7 +181,7 @@ class Code : public Module { class ScopeInfo : public Module { public: - MODULE_DEFAULT_METHODS(ScopeInfo); + CONSTANTS_DEFAULT_METHODS(ScopeInfo); int64_t kParameterCountOffset; int64_t kStackLocalCountOffset; @@ -214,11 +194,13 @@ class ScopeInfo : public Module { class Context : public Module { public: - MODULE_DEFAULT_METHODS(Context); + CONSTANTS_DEFAULT_METHODS(Context); int64_t kClosureIndex; int64_t kGlobalObjectIndex; int64_t kPreviousIndex; + int64_t kNativeIndex; + int64_t kEmbedderDataIndex; int64_t kMinContextSlots; protected: @@ -227,7 +209,7 @@ class Context : public Module { class Script : public Module { public: - MODULE_DEFAULT_METHODS(Script); + CONSTANTS_DEFAULT_METHODS(Script); int64_t kNameOffset; int64_t kLineOffsetOffset; @@ -240,7 +222,7 @@ class Script : public Module { class String : public Module { public: - MODULE_DEFAULT_METHODS(String); + CONSTANTS_DEFAULT_METHODS(String); int64_t kEncodingMask; int64_t kRepresentationMask; @@ -264,7 +246,7 @@ class String : public Module { class OneByteString : public Module { public: - MODULE_DEFAULT_METHODS(OneByteString); + CONSTANTS_DEFAULT_METHODS(OneByteString); int64_t kCharsOffset; @@ -274,7 +256,7 @@ class OneByteString : public Module { class TwoByteString : public Module { public: - MODULE_DEFAULT_METHODS(TwoByteString); + CONSTANTS_DEFAULT_METHODS(TwoByteString); int64_t kCharsOffset; @@ -284,7 +266,7 @@ class TwoByteString : public Module { class ConsString : public Module { public: - MODULE_DEFAULT_METHODS(ConsString); + CONSTANTS_DEFAULT_METHODS(ConsString); int64_t kFirstOffset; int64_t kSecondOffset; @@ -295,7 +277,7 @@ class ConsString : public Module { class SlicedString : public Module { public: - MODULE_DEFAULT_METHODS(SlicedString); + CONSTANTS_DEFAULT_METHODS(SlicedString); int64_t kParentOffset; int64_t kOffsetOffset; @@ -306,7 +288,7 @@ class SlicedString : public Module { class ThinString : public Module { public: - MODULE_DEFAULT_METHODS(ThinString); + CONSTANTS_DEFAULT_METHODS(ThinString); int64_t kActualOffset; @@ -316,7 +298,7 @@ class ThinString : public Module { class FixedArrayBase : public Module { public: - MODULE_DEFAULT_METHODS(FixedArrayBase); + CONSTANTS_DEFAULT_METHODS(FixedArrayBase); int64_t kLengthOffset; @@ -326,7 +308,7 @@ class FixedArrayBase : public Module { class FixedArray : public Module { public: - MODULE_DEFAULT_METHODS(FixedArray); + CONSTANTS_DEFAULT_METHODS(FixedArray); int64_t kDataOffset; @@ -336,7 +318,7 @@ class FixedArray : public Module { class FixedTypedArrayBase : public Module { public: - MODULE_DEFAULT_METHODS(FixedTypedArrayBase); + CONSTANTS_DEFAULT_METHODS(FixedTypedArrayBase); int64_t kBasePointerOffset; int64_t kExternalPointerOffset; @@ -347,7 +329,7 @@ class FixedTypedArrayBase : public Module { class Oddball : public Module { public: - MODULE_DEFAULT_METHODS(Oddball); + CONSTANTS_DEFAULT_METHODS(Oddball); int64_t kKindOffset; @@ -365,7 +347,7 @@ class Oddball : public Module { class JSArrayBuffer : public Module { public: - MODULE_DEFAULT_METHODS(JSArrayBuffer); + CONSTANTS_DEFAULT_METHODS(JSArrayBuffer); int64_t kKindOffset; @@ -382,7 +364,7 @@ class JSArrayBuffer : public Module { class JSArrayBufferView : public Module { public: - MODULE_DEFAULT_METHODS(JSArrayBufferView); + CONSTANTS_DEFAULT_METHODS(JSArrayBufferView); int64_t kBufferOffset; int64_t kByteOffsetOffset; @@ -394,7 +376,7 @@ class JSArrayBufferView : public Module { class DescriptorArray : public Module { public: - MODULE_DEFAULT_METHODS(DescriptorArray); + CONSTANTS_DEFAULT_METHODS(DescriptorArray); int64_t kDetailsOffset; int64_t kKeyOffset; @@ -438,7 +420,7 @@ class DescriptorArray : public Module { class NameDictionary : public Module { public: - MODULE_DEFAULT_METHODS(NameDictionary); + CONSTANTS_DEFAULT_METHODS(NameDictionary); int64_t kKeyOffset; int64_t kValueOffset; @@ -453,7 +435,7 @@ class NameDictionary : public Module { class Frame : public Module { public: - MODULE_DEFAULT_METHODS(Frame); + CONSTANTS_DEFAULT_METHODS(Frame); int64_t kContextOffset; int64_t kFunctionOffset; @@ -476,7 +458,7 @@ class Frame : public Module { class Types : public Module { public: - MODULE_DEFAULT_METHODS(Types); + CONSTANTS_DEFAULT_METHODS(Types); int64_t kFirstNonstringType; @@ -503,8 +485,6 @@ class Types : public Module { void Load(); }; -#undef MODULE_DEFAULT_METHODS - } // namespace constants } // namespace v8 } // namespace llnode diff --git a/src/llv8-inl.h b/src/llv8-inl.h index 6d726222..48d0a213 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -458,6 +458,10 @@ inline Value Context::Previous(Error& err) { return FixedArray::Get(v8()->context()->kPreviousIndex, err); } +inline Value Context::Native(Error& err) { + return FixedArray::Get(v8()->context()->kNativeIndex, err); +} + inline Value Context::ContextSlot(int index, Error& err) { return FixedArray::Get(v8()->context()->kMinContextSlots + index, err); } diff --git a/src/llv8.cc b/src/llv8.cc index 0bc972dc..68174b7e 100644 --- a/src/llv8.cc +++ b/src/llv8.cc @@ -58,48 +58,6 @@ void LLV8::Load(SBTarget target) { types.Assign(target, &common); } -bool Error::is_debug_mode = false; - -Error::Error(bool failed, const char* format, ...) { - failed_ = failed; - char tmp[kMaxMessageLength]; - va_list arglist; - va_start(arglist, format); - vsnprintf(tmp, sizeof(tmp), format, arglist); - va_end(arglist); - msg_ = tmp; -} - - -void Error::PrintInDebugMode(const char* format, ...) { - if (!is_debug_mode) { - return; - } - char fmt[kMaxMessageLength]; - snprintf(fmt, sizeof(fmt), "[llv8] %s\n", format); - va_list arglist; - va_start(arglist, format); - vfprintf(stderr, fmt, arglist); - va_end(arglist); -} - - -Error Error::Failure(std::string msg) { - PrintInDebugMode("%s", msg.c_str()); - return Error(true, msg); -} - - -Error Error::Failure(const char* format, ...) { - char tmp[kMaxMessageLength]; - va_list arglist; - va_start(arglist, format); - vsnprintf(tmp, sizeof(tmp), format, arglist); - va_end(arglist); - return Error::Failure(std::string(tmp)); -} - - int64_t LLV8::LoadPtr(int64_t addr, Error& err) { SBError sberr; int64_t value = diff --git a/src/llv8.h b/src/llv8.h index f67eb28a..62af7095 100644 --- a/src/llv8.h +++ b/src/llv8.h @@ -6,10 +6,17 @@ #include +#include "src/error.h" #include "src/llv8-constants.h" namespace llnode { +namespace node { +namespace constants { +class Environment; +} +} // namespace node + class FindJSObjectsVisitor; class FindReferencesCmd; class FindObjectsCmd; @@ -20,33 +27,6 @@ namespace v8 { class LLV8; class CodeMap; -class Error { - public: - Error() : failed_(false), msg_("") {} - Error(bool failed, std::string msg) : failed_(failed), msg_(msg) {} - Error(bool failed, const char* format, ...) - __attribute__((format(printf, 3, 4))); - - static inline Error Ok() { return Error(false, "ok"); } - static Error Failure(std::string msg); - static Error Failure(const char* format, ...) - __attribute__((format(printf, 1, 2))); - static void PrintInDebugMode(const char* format, ...) - __attribute__((format(printf, 1, 2))); - - inline bool Success() const { return !Fail(); } - inline bool Fail() const { return failed_; } - - inline const char* GetMessage() { return msg_.c_str(); } - - static void SetDebugMode(bool mode) { is_debug_mode = mode; } - - private: - bool failed_; - std::string msg_; - static const size_t kMaxMessageLength = 128; - static bool is_debug_mode; -}; #define V8_VALUE_DEFAULT_METHODS(NAME, PARENT) \ NAME(const NAME& v) = default; \ @@ -394,6 +374,7 @@ class Context : public FixedArray { inline JSFunction Closure(Error& err); inline Value Previous(Error& err); + inline Value Native(Error& err); inline Value ContextSlot(int index, Error& err); std::string Inspect(Error& err); @@ -553,6 +534,7 @@ class LLV8 { friend class llnode::FindJSObjectsVisitor; friend class llnode::FindObjectsCmd; friend class llnode::FindReferencesCmd; + friend class llnode::node::constants::Environment; }; #undef V8_VALUE_DEFAULT_METHODS diff --git a/src/node-constants.cc b/src/node-constants.cc new file mode 100644 index 00000000..aea7c0f5 --- /dev/null +++ b/src/node-constants.cc @@ -0,0 +1,152 @@ +#include +#include + +#include "src/llv8-inl.h" +#include "src/node-constants.h" + +using lldb::SBFrame; +using lldb::SBProcess; +using lldb::SBStream; +using lldb::SBThread; + +namespace llnode { +namespace node { +namespace constants { + +void Environment::Load() { + kReqWrapQueueOffset = LoadConstant( + "offset_Environment__req_wrap_queue___Environment_ReqWrapQueue"); + kHandleWrapQueueOffset = LoadConstant( + "offset_Environment__handle_wrap_queue___Environment_HandleWrapQueue"); + kEnvContextEmbedderDataIndex = + LoadConstant("const_Environment__kContextEmbedderDataIndex__int"); + + Error err; + kCurrentEnvironment = LoadCurrentEnvironment(err); +} + +addr_t Environment::LoadCurrentEnvironment(Error& err) { + if (kEnvContextEmbedderDataIndex == -1) { + err = Error::Failure("Missing Node's embedder data index"); + return 0; + } + addr_t currentEnvironment = 0; + SBProcess process = target_.GetProcess(); + SBThread thread = process.GetSelectedThread(); + if (!thread.IsValid()) { + err = Error::Failure("Invalid thread"); + return 0; + } + + llv8()->Load(target_); + + SBStream desc; + if (!thread.GetDescription(desc)) { + err = Error::Failure("Couldn't get thread description"); + return 0; + } + SBFrame selected_frame = thread.GetSelectedFrame(); + + uint32_t num_frames = thread.GetNumFrames(); + + // Heuristically finds the native context and gets the Environment from its + // embedder data. + for (uint32_t i = 0; i < num_frames; i++) { + SBFrame frame = thread.GetFrameAtIndex(i); + + if (!frame.GetSymbol().IsValid()) { + Error v8_err; + v8::JSFrame v8_frame(llv8(), static_cast(frame.GetFP())); + v8::JSFunction v8_function = v8_frame.GetFunction(v8_err); + if (v8_err.Fail()) { + continue; + } + v8::Value val; + val = v8_function.GetContext(v8_err); + if (v8_err.Fail()) { + continue; + } + bool found = false; + std::set visited_contexts; + while (!found) { + // Necessary to avoid an infinite loop. + if (visited_contexts.count(val.raw())) { + break; + } + visited_contexts.insert(val.raw()); + v8::Context context(val); + v8::Value native = context.Native(v8_err); + if (v8_err.Success()) { + if (native.raw() == context.raw()) { + found = true; + currentEnvironment = CurrentEnvironmentFromContext(native, err); + break; + } + } + + val = context.Previous(v8_err); + if (v8_err.Fail()) { + break; + } + } + if (found) { + break; + } + } + } + + if (!currentEnvironment) { + err = + Error::Failure("Couldn't find the Environemnt from the native context"); + } + + return currentEnvironment; +} + +addr_t Environment::CurrentEnvironmentFromContext(v8::Value context, + Error& err) { + llv8()->Load(target_); + + v8::FixedArray contextArray = v8::FixedArray(context); + v8::FixedArray embed = contextArray.Get( + llv8()->context()->kEmbedderDataIndex, err); + if (err.Fail()) { + return 0; + } + v8::Smi encodedEnv = embed.Get(kEnvContextEmbedderDataIndex, err); + if (err.Fail()) { + return 0; + } + return encodedEnv.raw(); +} + + +void ReqWrapQueue::Load() { + kHeadOffset = LoadConstant( + "offset_Environment_ReqWrapQueue__head___ListNode_ReqWrapQueue"); + kNextOffset = LoadConstant("offset_ListNode_ReqWrap__next___uintptr_t"); +} + +void ReqWrap::Load() { + kListNodeOffset = + LoadConstant("offset_ReqWrap__req_wrap_queue___ListNode_ReqWrapQueue"); +} + +void HandleWrapQueue::Load() { + kHeadOffset = LoadConstant( + "offset_Environment_HandleWrapQueue__head___ListNode_HandleWrap"); + kNextOffset = LoadConstant("offset_ListNode_HandleWrap__next___uintptr_t"); +} + +void HandleWrap::Load() { + kListNodeOffset = LoadConstant( + "offset_HandleWrap__handle_wrap_queue___ListNode_HandleWrap"); +} + +void BaseObject::Load() { + kPersistentHandleOffset = LoadConstant( + "offset_BaseObject__persistent_handle___v8_Persistent_v8_Object"); +} +} // namespace constants +} // namespace node +} // namespace llnode diff --git a/src/node-constants.h b/src/node-constants.h new file mode 100644 index 00000000..ec89f3e6 --- /dev/null +++ b/src/node-constants.h @@ -0,0 +1,102 @@ +#ifndef SRC_NODE_CONSTANTS_H_ +#define SRC_NODE_CONSTANTS_H_ + +#include +#include "src/constants.h" +#include "src/llv8.h" + +using lldb::addr_t; + +namespace llnode { +namespace node { +namespace constants { + +#define NODE_CONSTANTS_DEFAULT_METHODS(Class) \ + Class(v8::LLV8* llv8) : Module(llv8) {} \ + CONSTANTS_DEFAULT_METHODS(Class) + + +class Module : public Constants { + public: + Module(v8::LLV8* llv8) : llv8_(llv8) {} + inline std::string constant_prefix() override { return "nodedbg_"; }; + + inline v8::LLV8* llv8() { return llv8_; } + + private: + v8::LLV8* llv8_; +}; + +class Environment : public Module { + public: + NODE_CONSTANTS_DEFAULT_METHODS(Environment); + + int64_t kReqWrapQueueOffset; + int64_t kHandleWrapQueueOffset; + int64_t kEnvContextEmbedderDataIndex; + addr_t kCurrentEnvironment; + + protected: + void Load(); + + private: + addr_t LoadCurrentEnvironment(Error& err); + addr_t CurrentEnvironmentFromContext(v8::Value context, Error& err); +}; + +class ReqWrapQueue : public Module { + public: + NODE_CONSTANTS_DEFAULT_METHODS(ReqWrapQueue); + + int64_t kHeadOffset; + int64_t kNextOffset; + + protected: + void Load(); +}; + +class ReqWrap : public Module { + public: + NODE_CONSTANTS_DEFAULT_METHODS(ReqWrap); + + int64_t kListNodeOffset; + + protected: + void Load(); +}; + +class HandleWrapQueue : public Module { + public: + NODE_CONSTANTS_DEFAULT_METHODS(HandleWrapQueue); + + int64_t kHeadOffset; + int64_t kNextOffset; + + protected: + void Load(); +}; + +class HandleWrap : public Module { + public: + NODE_CONSTANTS_DEFAULT_METHODS(HandleWrap); + + int64_t kListNodeOffset; + + protected: + void Load(); +}; + +class BaseObject : public Module { + public: + NODE_CONSTANTS_DEFAULT_METHODS(BaseObject); + + int64_t kPersistentHandleOffset; + + protected: + void Load(); +}; +} +} // namespace node +} // namespace llnode + +#endif diff --git a/src/node-inl.h b/src/node-inl.h new file mode 100644 index 00000000..b1bc47eb --- /dev/null +++ b/src/node-inl.h @@ -0,0 +1,42 @@ +#include "node.h" + +namespace llnode { +namespace node { + +template +T Queue::Iterator::operator*() const { + return T::FromListNode(node_, current_); +} + +template +const typename Queue::Iterator Queue::Iterator::operator++() { + lldb::SBError sberr; + + addr_t current = current_ + constants_->kNextOffset; + current = node_->process().ReadPointerFromMemory(current, sberr); + current_ = current; + return Iterator(node_, current, constants_); +} + +template +bool Queue::Iterator::operator!=(const Iterator& that) const { + return current_ != that.current_; +} + +template +typename Queue::Iterator Queue::begin() const { + lldb::SBError sberr; + addr_t currentNode = raw_ + constants_->kHeadOffset; + + currentNode = currentNode + constants_->kNextOffset; + currentNode = node_->process().ReadPointerFromMemory(currentNode, sberr); + + return Iterator(node_, currentNode, constants_); +} + +template +typename Queue::Iterator Queue::end() const { + return Iterator(node_, raw_ + constants_->kHeadOffset, constants_); +} +} // namespace node +} // namespace llnode diff --git a/src/node.cc b/src/node.cc new file mode 100644 index 00000000..2cef038e --- /dev/null +++ b/src/node.cc @@ -0,0 +1,77 @@ +#include "node.h" + +namespace llnode { +namespace node { + +addr_t BaseObject::persistent_addr(Error& err) { + lldb::SBError sberr; + + addr_t persistentHandlePtr = + raw_ + node_->base_object()->kPersistentHandleOffset; + addr_t persistentHandle = + node_->process().ReadPointerFromMemory(persistentHandlePtr, sberr); + if (sberr.Fail()) { + err = Error::Failure("Failed to load persistent handle"); + return 0; + } + return persistentHandle; +} + +addr_t BaseObject::v8_object_addr(Error& err) { + lldb::SBError sberr; + + addr_t persistentHandle = persistent_addr(err); + addr_t obj = node_->process().ReadPointerFromMemory(persistentHandle, sberr); + if (sberr.Fail()) { + err = Error::Failure("Failed to load object from persistent handle"); + return 0; + } + return obj; +} + +HandleWrap HandleWrap::FromListNode(Node* node, addr_t list_node_addr) { + return HandleWrap(node, + list_node_addr - node->handle_wrap()->kListNodeOffset); +} + +ReqWrap ReqWrap::FromListNode(Node* node, addr_t list_node_addr) { + return ReqWrap(node, list_node_addr - node->req_wrap()->kListNodeOffset); +} + +Environment Environment::GetCurrent(Node* node, Error& err) { + addr_t envAddr = node->env()->kCurrentEnvironment; + if (envAddr == 0) { + err = Error::Failure("Couldn't get node's Environment"); + } + + return Environment(node, envAddr); +} + +HandleWrapQueue Environment::handle_wrap_queue() const { + return HandleWrapQueue(node_, raw_ + node_->env()->kHandleWrapQueueOffset, + node_->handle_wrap_queue()); +} + +ReqWrapQueue Environment::req_wrap_queue() const { + return ReqWrapQueue(node_, raw_ + node_->env()->kReqWrapQueueOffset, + node_->req_wrap_queue()); +} + +void Node::Load(SBTarget target) { + // Reload process anyway + process_ = target.GetProcess(); + + // No need to reload + if (target_ == target) return; + + target_ = target; + + env.Assign(target); + req_wrap_queue.Assign(target); + req_wrap.Assign(target); + handle_wrap_queue.Assign(target); + handle_wrap.Assign(target); + base_object.Assign(target); +} +} // namespace node +} // namespace llnode diff --git a/src/node.h b/src/node.h new file mode 100644 index 00000000..887de58c --- /dev/null +++ b/src/node.h @@ -0,0 +1,134 @@ +#ifndef SRC_NODE_H_ +#define SRC_NODE_H_ + +#include +#include + +#include "node-constants.h" + +#define CONSTANTS_LIST(V) \ + V(Environment, env) \ + V(ReqWrapQueue, req_wrap_queue) \ + V(ReqWrap, req_wrap) \ + V(HandleWrapQueue, handle_wrap_queue) \ + V(HandleWrap, handle_wrap) \ + V(BaseObject, base_object) + +namespace llnode { +namespace node { + +class Node; +class HandleWrap; +class ReqWrap; +template +class Queue; + +class BaseNode { + public: + BaseNode(Node* node) : node_(node){}; + + protected: + Node* node_; +}; + +typedef Queue HandleWrapQueue; +typedef Queue ReqWrapQueue; + +class Environment : public BaseNode { + public: + Environment(Node* node, addr_t raw) : BaseNode(node), raw_(raw){}; + inline addr_t raw() { return raw_; }; + + static Environment GetCurrent(Node* node, Error& err); + + HandleWrapQueue handle_wrap_queue() const; + ReqWrapQueue req_wrap_queue() const; + + private: + addr_t raw_; +}; + +class BaseObject : public BaseNode { + public: + BaseObject(Node* node, addr_t raw) : BaseNode(node), raw_(raw){}; + inline addr_t raw() { return raw_; }; + + addr_t persistent_addr(Error& err); + + addr_t v8_object_addr(Error& err); + + + private: + addr_t raw_; +}; + +class AsyncWrap : public BaseObject { + public: + AsyncWrap(Node* node, addr_t raw) : BaseObject(node, raw){}; +}; + +class HandleWrap : public AsyncWrap { + public: + HandleWrap(Node* node, addr_t raw) : AsyncWrap(node, raw){}; + + static HandleWrap FromListNode(Node* node, addr_t list_node_addr); +}; + +class ReqWrap : public AsyncWrap { + public: + ReqWrap(Node* node, addr_t raw) : AsyncWrap(node, raw){}; + + static ReqWrap FromListNode(Node* node, addr_t list_node_addr); +}; + +class Node { + public: +#define V(Class, Attribute) Attribute(constants::Class(llv8)), + Node(v8::LLV8* llv8) : CONSTANTS_LIST(V) target_(lldb::SBTarget()) {} +#undef V + + inline lldb::SBProcess process() { return process_; }; + + void Load(lldb::SBTarget target); + +#define V(Class, Attribute) constants::Class Attribute; + CONSTANTS_LIST(V) +#undef V + + private: + lldb::SBTarget target_; + lldb::SBProcess process_; +}; + +template +class Queue : public BaseNode { + class Iterator : public BaseNode { + public: + inline T operator*() const; + inline const Iterator operator++(); + inline bool operator!=(const Iterator& that) const; + + + inline Iterator(Node* node, addr_t current, C* constants) + : BaseNode(node), current_(current), constants_(constants){}; + + public: + addr_t current_; + C* constants_; + }; + + public: + inline Queue(Node* node, addr_t raw, C* constants) + : BaseNode(node), raw_(raw), constants_(constants){}; + + inline Iterator begin() const; + inline Iterator end() const; + + private: + addr_t raw_; + C* constants_; +}; +} // namespace node +} // namespace llnode + +#endif diff --git a/test/common.js b/test/common.js index e0dbed91..6e861997 100644 --- a/test/common.js +++ b/test/common.js @@ -89,12 +89,14 @@ SessionOutput.prototype.timeoutAfter = function timeoutAfter(timeout) { this.timeout = timeout; }; -SessionOutput.prototype.wait = function wait(regexp, callback, allLines) { +SessionOutput.prototype.wait = function wait(regexp, callback, allLines, + outputOnTimeout) { if (!this._queueWait(() => { this.wait(regexp, callback, allLines); })) return; const self = this; const lines = []; + outputOnTimeout = outputOnTimeout == undefined ? true : outputOnTimeout; function onLine(line) { lines.push(line); @@ -117,9 +119,11 @@ SessionOutput.prototype.wait = function wait(regexp, callback, allLines) { self.removeListener('line', onLine); self._unqueueWait(); - console.error(`${'='.repeat(10)} lldb output ${'='.repeat(10)}`); - console.error(lines.join('\n')); - console.error('='.repeat(33)); + if (outputOnTimeout) { + console.error(`${'='.repeat(10)} lldb output ${'='.repeat(10)}`); + console.error(lines.join('\n')); + console.error('='.repeat(33)); + } const message = `Test timeout in ${this.timeout} ` + `waiting for ${regexp}`; callback(new Error(message)); @@ -193,6 +197,7 @@ function Session(options) { // Map these methods to stdout for compatibility with legacy tests. this.wait = SessionOutput.prototype.wait.bind(this.stdout); + this.waitError = SessionOutput.prototype.wait.bind(this.stderr); this.waitBreak = SessionOutput.prototype.waitBreak.bind(this.stdout); this.linesUntil = SessionOutput.prototype.linesUntil.bind(this.stdout); this.timeoutAfter = SessionOutput.prototype.timeoutAfter.bind(this.stdout); @@ -228,7 +233,7 @@ exports.saveCore = function saveCore(options, cb) { cb(null); } else { const ranges = core + '.ranges'; - exports.generateRanges(core, ranges, cb); + exports.generateRanges(core, ranges, cb); } }); } diff --git a/test/fixtures/workqueue-scenario.js b/test/fixtures/workqueue-scenario.js new file mode 100644 index 00000000..b4f68487 --- /dev/null +++ b/test/fixtures/workqueue-scenario.js @@ -0,0 +1,15 @@ +'use strict'; +const http = require('http') +var fs = require('fs'); + +// Creates a Timer to be inspected with getactivehandles +setInterval(()=>{}, 500); + +// Creates a TCP to be inspected with getactivehandles +const server = http.createServer((req, res) => { res.end('test'); }); +server.listen(4321, (err) => {}); + +// Creates a FSReqWrap to be inspected with getactivehandles +fs.readFile('invalidFile', (err, data) => {}); + +uncaughtException(); diff --git a/test/workqueue-test.js b/test/workqueue-test.js new file mode 100644 index 00000000..9d74d683 --- /dev/null +++ b/test/workqueue-test.js @@ -0,0 +1,58 @@ +'use strict'; + +const tape = require('tape'); + +const common = require('./common'); + +function testWorkqueueCommands(t, sess) { + sess.send('v8 getactivehandles'); + + sess.wait(/TCP/, (err, line) => { + t.error(err); + let match = line.match(/ { + t.error(err); + let match = line.match(/ { + t.error(err); + let match = line.match(/ { + t.timeoutAfter(15000); + + const sess = common.Session.create('workqueue-scenario.js'); + sess.timeoutAfter + + sess.waitBreak((err) => { + t.error(err); + sess.send('v8 getactivehandles'); + }); + + // Check if current node version support these commands. + sess.waitError(/error: Couldn't get node's Environment/, (err, line) => { + if (err) { + testWorkqueueCommands(t, sess); + } else { + // TODO (mmarchini): print node's version from core. + t.skip(`workqueue commands can't be tested with this version of node`); + sess.quit(); + t.end(); + } + }, false, false); +});