From 7d9d1e1a3d2a8c4a72aa48ac1d9002e44e75b404 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Thu, 17 Aug 2017 16:51:43 -0300 Subject: [PATCH] src: add commands to inspect the workqueue Added two new commands (getactivehandles and getactiverequests) which prints all pending handles and requests (same return given by process._getActiveHandles() and process._getActiveRequests()). Those changes were built upon the symbols added on nodejs/node#14901, which means it's currently not working with node's latest build. Fixes: https://github.com/nodejs/llnode/issues/100 Ref: https://github.com/nodejs/node/pull/14901 --- README.md | 74 ++++++++-------- src/llnode.cc | 191 ++++++++++++++++++++++++++++++++++++++++++ src/llnode.h | 16 ++++ src/llv8-constants.cc | 2 +- src/llv8-constants.h | 6 ++ 5 files changed, 252 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 2cc4374d..1552d8a7 100644 --- a/README.md +++ b/README.md @@ -219,42 +219,44 @@ Syntax: v8 The following subcommands are supported: - bt -- Show a backtrace with node.js JavaScript functions and their args. An optional argument is accepted; if - that argument is a number, it specifies the number of frames to display. Otherwise all frames will be - dumped. - - Syntax: v8 bt [number] - findjsinstances -- List all objects which share the specified map. - Accepts the same options as `v8 inspect` - findjsobjects -- List all object types and instance counts grouped by map and sorted by instance count. - Requires `LLNODE_RANGESFILE` environment variable to be set to a file containing memory ranges for the - core file being debugged. - There are scripts for generating this file on Linux and Mac in the scripts directory of the llnode - repository. - findrefs -- Finds all the object properties which meet the search criteria. - The default is to list all the object properties that reference the specified value. - Flags: - - * -v, --value expr - all properties that refer to the specified JavaScript object (default) - * -n, --name name - all properties with the specified name - * -s, --string string - all properties that refer to the specified JavaScript string value - * --array-length num - print maximum of `num` elements in array - - inspect -- Print detailed description and contents of the JavaScript value. - - Possible flags (all optional): - - * -F, --full-string - print whole string without adding ellipsis - * -m, --print-map - print object's map address - * -s, --print-source - print source code for function objects - * --string-length num - print maximum of `num` characters in string - - Syntax: v8 inspect [flags] expr - nodeinfo -- Print information about Node.js - print -- Print short description of the JavaScript value. - - Syntax: v8 print expr - source -- Source code information + bt -- Show a backtrace with node.js JavaScript functions and their args. An optional argument is accepted; if + that argument is a number, it specifies the number of frames to display. Otherwise all frames will be + dumped. + + Syntax: v8 bt [number] + findjsinstances -- List all objects which share the specified map. + Accepts the same options as `v8 inspect` + findjsobjects -- List all object types and instance counts grouped by map and sorted by instance count. + Requires `LLNODE_RANGESFILE` environment variable to be set to a file containing memory ranges for the + core file being debugged. + There are scripts for generating this file on Linux and Mac in the scripts directory of the llnode + repository. + findrefs -- Finds all the object properties which meet the search criteria. + The default is to list all the object properties that reference the specified value. + Flags: + + * -v, --value expr - all properties that refer to the specified JavaScript object (default) + * -n, --name name - all properties with the specified name + * -s, --string string - all properties that refer to the specified JavaScript string value + * --array-length num - print maximum of `num` elements in array + + getactivehandles -- *EXPERIMENTAL* Equivalent to running process._getActiveHandles. This command is still being developed and for now it only works building node from source. + getactiverequests -- *EXPERIMENTAL* Equivalent to running process._getActiveRequests. This command is still being developed and for now it only works building node from source. + inspect -- Print detailed description and contents of the JavaScript value. + + Possible flags (all optional): + + * -F, --full-string - print whole string without adding ellipsis + * -m, --print-map - print object's map address + * -s, --print-source - print source code for function objects + * --string-length num - print maximum of `num` characters in string + + Syntax: v8 inspect [flags] expr + nodeinfo -- Print information about Node.js + print -- Print short description of the JavaScript value. + + Syntax: v8 print expr + source -- Source code information For more help on any particular subcommand, type 'help '. ``` diff --git a/src/llnode.cc b/src/llnode.cc index d24fdc4c..0b0536f5 100644 --- a/src/llnode.cc +++ b/src/llnode.cc @@ -4,12 +4,15 @@ #include #include +#include +#include #include #include "src/llnode.h" #include "src/llscan.h" #include "src/llv8.h" +#include "src/llv8-constants.h" namespace llnode { @@ -26,6 +29,12 @@ using lldb::SBThread; using lldb::SBValue; using lldb::eReturnStatusFailed; using lldb::eReturnStatusSuccessFinishResult; +using lldb::SBProcess; +using lldb::SBAddress; +using lldb::SBSymbolContext; +using lldb::SBSymbolContextList; +using lldb::addr_t; +using v8::constants::LookupConstant; v8::LLV8 llv8; @@ -296,6 +305,176 @@ bool ListCmd::DoExecute(SBDebugger d, char** cmd, return true; } +bool GetActiveHandlesCmd::DoExecute(SBDebugger d, char** cmd, + SBCommandReturnObject& result) { + SBTarget target = d.GetSelectedTarget(); + SBProcess process = target.GetProcess(); + SBThread thread = process.GetSelectedThread(); + SBError sberr; + std::ostringstream resultMsg; + v8::Value::InspectOptions inspect_options; + inspect_options.detailed = true; + + llv8.Load(target); + + int size = 8; // TODO size is arch-dependent + int64_t envPtr = 0; + uint64_t env = 0; + int64_t queue = 0; + int64_t head = 0; + int64_t next = 0; + int64_t node = 0; + int64_t persistant_handle = 0; + v8::Error err2; + + envPtr = LookupConstant(target, "nodedbg_currentEnvironment", envPtr, err2); + process.ReadMemory(envPtr, &env, size, sberr); + + queue = LookupConstant(target, "nodedbg_class__Environment__handleWrapQueue", queue, err2); + head = LookupConstant(target, "nodedbg_class__HandleWrapQueue__headOffset", head, err2); + next = LookupConstant(target, "nodedbg_class__HandleWrapQueue__nextOffset", next, err2); + node = LookupConstant(target, "nodedbg_class__HandleWrap__node", node, err2); + persistant_handle = LookupConstant(target, "nodedbg_class__BaseObject__persistant_handle", persistant_handle, err2); + + // uint8_t *buffer = new uint8_t[size]; + // XXX Ozadia time + uint64_t buffer = 0; + bool go=true; + if (!thread.IsValid()) { + result.SetError("No valid process, please start something\n"); + return false; + } + + int activeHandles = 0; + uint64_t currentNode = env; + currentNode += queue; // env.handle_wrap_queue_ + currentNode += head; // env.handle_wrap_queue_.head_ + currentNode += next; // env.handle_wrap_queue_.head_.next_ + process.ReadMemory(currentNode, &buffer, size, sberr); + currentNode = buffer; + // TODO needs a stop condition, currently it's being stopped by a break + while(go) { + addr_t myMemory = currentNode; + myMemory = myMemory - node; // wrap + myMemory += persistant_handle; + // w->persistent().IsEmpty() + if(myMemory == 0) { + continue; + } + + process.ReadMemory(myMemory, &buffer, size, sberr); + myMemory = buffer; + process.ReadMemory(myMemory, &buffer, size, sberr); + // TODO needs a better check + if(sberr.Fail()) { + break; + } + + v8::JSObject v8_object(&llv8, buffer); + v8::Error err; + std::string res = v8_object.Inspect(&inspect_options, err); + if (err.Fail()) { + // result.SetError("Failed to evaluate expression"); + break; + } + + activeHandles++; + resultMsg << res.c_str() << std::endl; + + currentNode += next; // env.handle_wrap_queue_.head_.next_->next_->(...)->next_ + process.ReadMemory(currentNode, &buffer, size, sberr); + currentNode = buffer; + } + result.Printf("Active handles: %d\n\n", activeHandles); + result.Printf("%s", resultMsg.str().c_str()); + return true; +} + +bool GetActiveRequestsCmd::DoExecute(SBDebugger d, char** cmd, + SBCommandReturnObject& result) { + SBTarget target = d.GetSelectedTarget(); + SBProcess process = target.GetProcess(); + SBThread thread = process.GetSelectedThread(); + SBError sberr; + std::ostringstream resultMsg; + v8::Value::InspectOptions inspect_options; + inspect_options.detailed = true; + + llv8.Load(target); + + int size = 8; // TODO size is arch-dependent + int64_t envPtr = 0; + uint64_t env = 0; + int64_t queue = 0; + int64_t head = 0; + int64_t next = 0; + int64_t node = 0; + int64_t persistant_handle = 0; + v8::Error err2; + + envPtr = LookupConstant(target, "nodedbg_currentEnvironment", envPtr, err2); + process.ReadMemory(envPtr, &env, size, sberr); + + queue = LookupConstant(target, "nodedbg_class__Environment__reqWrapQueue", queue, err2); + head = LookupConstant(target, "nodedbg_class__ReqWrapQueue__headOffset", head, err2); + next = LookupConstant(target, "nodedbg_class__ReqWrapQueue__nextOffset", next, err2); + node = LookupConstant(target, "nodedbg_class__ReqWrap__node", node, err2); + persistant_handle = LookupConstant(target, "nodedbg_class__BaseObject__persistant_handle", persistant_handle, err2); + + // uint8_t *buffer = new uint8_t[size]; + // XXX Ozadia time + uint64_t buffer = 0; + bool go=true; + if (!thread.IsValid()) { + result.SetError("No valid process, please start something\n"); + return false; + } + + int activeHandles = 0; + uint64_t currentNode = env; + currentNode += queue; // env.handle_wrap_queue_ + currentNode += head; // env.handle_wrap_queue_.head_ + currentNode += next; // env.handle_wrap_queue_.head_.next_ + process.ReadMemory(currentNode, &buffer, size, sberr); + currentNode = buffer; + // TODO needs a stop condition + while(go) { + addr_t myMemory = currentNode; + myMemory = myMemory - node; + myMemory += persistant_handle; + // w->persistent().IsEmpty() + if(myMemory == 0) { + continue; + } + + process.ReadMemory(myMemory, &buffer, size, sberr); + myMemory = buffer; + process.ReadMemory(myMemory, &buffer, size, sberr); + // TODO needs a better check + if(sberr.Fail()) { + break; + } + + v8::JSObject v8_object(&llv8, buffer); + v8::Error err; + std::string res = v8_object.Inspect(&inspect_options, err); + if (err.Fail()) { + // result.SetError("Failed to evaluate expression"); + break; + } + + activeHandles++; + resultMsg << res.c_str() << std::endl; + + currentNode += next; // env.handle_wrap_queue_.head_.next_->next_->(...)->next_ + process.ReadMemory(currentNode, &buffer, size, sberr); + currentNode = buffer; + } + result.Printf("Active handles: %d\n\n", activeHandles); + result.Printf("%s", resultMsg.str().c_str()); + return true; +} + } // namespace llnode namespace lldb { @@ -379,6 +558,18 @@ bool PluginInitialize(SBDebugger d) { "JavaScript string value\n" "\n"); + v8.AddCommand( + "getactivehandles", new llnode::GetActiveHandlesCmd(), + "*EXPERIMENTAL* Equivalent to running process._getActiveHandles. " + "This command is still being developed and for now it only works " + "building node from source.\n"); + + v8.AddCommand( + "getactiverequests", new llnode::GetActiveRequestsCmd(), + "*EXPERIMENTAL* Equivalent to running process._getActiveRequests. " + "This command is still being developed and for now it only works " + "building node from source.\n"); + return true; } diff --git a/src/llnode.h b/src/llnode.h index b46bc7ef..755a342d 100644 --- a/src/llnode.h +++ b/src/llnode.h @@ -35,6 +35,22 @@ class PrintCmd : public CommandBase { bool detailed_; }; +class GetActiveHandlesCmd : public CommandBase { + public: + ~GetActiveHandlesCmd() override {} + + bool DoExecute(lldb::SBDebugger d, char** cmd, + lldb::SBCommandReturnObject& result) override; +}; + +class GetActiveRequestsCmd : public CommandBase { + public: + ~GetActiveRequestsCmd() override {} + + bool DoExecute(lldb::SBDebugger d, char** cmd, + lldb::SBCommandReturnObject& result) override; +}; + class ListCmd : public CommandBase { public: ~ListCmd() override {} diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc index d36cec53..1abf60b7 100644 --- a/src/llv8-constants.cc +++ b/src/llv8-constants.cc @@ -38,7 +38,7 @@ void Module::Assign(SBTarget target, Common* common) { } -static int64_t LookupConstant(SBTarget target, const char* name, int64_t def, +int64_t LookupConstant(SBTarget target, const char* name, int64_t def, Error& err) { int64_t res; diff --git a/src/llv8-constants.h b/src/llv8-constants.h index 04a46d23..10888362 100644 --- a/src/llv8-constants.h +++ b/src/llv8-constants.h @@ -3,10 +3,16 @@ #include +using lldb::SBTarget; + namespace llnode { namespace v8 { +class Error; namespace constants { +int64_t LookupConstant(SBTarget target, const char* name, int64_t def, + Error& err); + // Forward declarations class Common;