From 484629995cd67d6569c647d371dacb26bae51f33 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 20 Dec 2017 01:44:55 +0800 Subject: [PATCH] src: print more debug info when LLNODE_DEBUG=true PR-URL: https://github.com/nodejs/llnode/pull/151 Refs: https://github.com/nodejs/post-mortem/issues/50 Reviewed-By: Ben Noordhuis --- Makefile | 35 +++++++++++--- package.json | 1 + src/llnode.cc | 13 ++++++ src/llv8-constants.cc | 33 +++++++------- src/llv8-inl.h | 8 +++- src/llv8.cc | 103 +++++++++++++++++++++++++++++++++--------- src/llv8.h | 28 +++++++++--- 7 files changed, 167 insertions(+), 54 deletions(-) diff --git a/Makefile b/Makefile index 874bdc8c..14ebf7a1 100644 --- a/Makefile +++ b/Makefile @@ -1,32 +1,53 @@ +TEST_LLDB_BINARY ?= $(shell which lldb-3.9) + +.PHONY: all all: @echo "Please take a look at README.md" +.PHONY: install-osx install-osx: mkdir -p ~/Library/Application\ Support/LLDB/PlugIns/ cp -rf ./out/Release/llnode.dylib \ ~/Library/Application\ Support/LLDB/PlugIns/ +.PHONY: uninstall-osx uninstall-osx: rm ~/Library/Application\ Support/LLDB/PlugIns/llnode.dylib +.PHONY: install-linux install-linux: mkdir -p /usr/lib/lldb/plugins cp -rf ./out/Release/lib.target/llnode.so /usr/lib/lldb/plugins +.PHONY: uninstall-linux uninstall-linux: rm /usr/lib/lldb/plugins/llnode.so +.PHONY: format format: clang-format -i src/* -configure: scripts/configure.js +# This depends on the system setting e.g. $PATH so can't actually be skipped +.PHONY: configure +configure: node scripts/configure.js + ./gyp_llnode +.PHONY: plugin plugin: configure - ./gyp_llnode $(MAKE) -C out/ - -_travis: plugin - TEST_LLDB_BINARY=`which lldb-3.9` TEST_LLNODE_DEBUG=true npm test - -.PHONY: all + node scripts/cleanup.js + +.PHONY: _travis +_travis: + TEST_LLDB_BINARY="$(TEST_LLDB_BINARY)" \ + TEST_LLNODE_DEBUG=true \ + LLNODE_DEBUG=true \ + npm test + +.PHONY: clean +clean: + $(RM) -r out + $(RM) options.gypi + $(RM) lldb + $(RM) llnode.so llnode.dylib diff --git a/package.json b/package.json index afb8ea10..b690d1d3 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "url": "git+ssh://git@github.com/nodejs/llnode.git" }, "files": [ + "Makefile", "llnode.gyp.json", "gyp_llnode", "common.gypi", diff --git a/src/llnode.cc b/src/llnode.cc index 8570d15b..3a41c1b2 100644 --- a/src/llnode.cc +++ b/src/llnode.cc @@ -299,11 +299,24 @@ bool ListCmd::DoExecute(SBDebugger d, char** cmd, return true; } + +void InitDebugMode() { + bool is_debug_mode = false; + char* var = getenv("LLNODE_DEBUG"); + if (var != nullptr && strlen(var) != 0) { + is_debug_mode = true; + } + + v8::Error::SetDebugMode(is_debug_mode); +} + } // namespace llnode namespace lldb { bool PluginInitialize(SBDebugger d) { + llnode::InitDebugMode(); + SBCommandInterpreter interpreter = d.GetCommandInterpreter(); SBCommand v8 = interpreter.AddMultiwordCommand("v8", "Node.js helpers"); diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc index 99754efa..6d93b4b1 100644 --- a/src/llv8-constants.cc +++ b/src/llv8-constants.cc @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -23,14 +24,6 @@ using lldb::addr_t; static std::string kConstantPrefix = "v8dbg_"; -static bool IsDebugMode() { - char* var = getenv("LLNODE_DEBUG"); - if (var == nullptr) return false; - - return strlen(var) != 0; -} - - void Module::Assign(SBTarget target, Common* common) { loaded_ = false; target_ = target; @@ -46,20 +39,20 @@ static int64_t LookupConstant(SBTarget target, const char* name, int64_t def, SBSymbolContextList context_list = target.FindSymbols(name); if (!context_list.IsValid() || context_list.GetSize() == 0) { - err = Error::Failure("Failed to find symbol"); + 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"); + err = Error::Failure("Failed to fetch symbol %s", name); return res; } SBAddress start = symbol.GetStartAddress(); SBAddress end = symbol.GetEndAddress(); - size_t size = end.GetOffset() - start.GetOffset(); + uint32_t size = end.GetOffset() - start.GetOffset(); SBError sberr; @@ -79,12 +72,13 @@ static int64_t LookupConstant(SBTarget target, const char* name, int64_t def, int8_t tmp = process.ReadUnsignedFromMemory(addr, size, sberr); res = static_cast(tmp); } else { - err = Error::Failure("Unexpected symbol size"); + err = Error::Failure("Unexpected symbol size %" PRIu32 " of symbol %s", + size, name); return res; } if (sberr.Fail()) - err = Error::Failure("Failed to load symbol"); + err = Error::Failure("Failed to load symbol %s", name); else err = Error::Ok(); @@ -95,7 +89,9 @@ static int64_t LookupConstant(SBTarget target, const char* name, int64_t def, int64_t Module::LoadRawConstant(const char* name, int64_t def) { Error err; int64_t v = LookupConstant(target_, name, def, err); - if (err.Fail() && IsDebugMode()) fprintf(stderr, "Failed to load %s\n", name); + if (err.Fail()) { + Error::PrintInDebugMode("Failed to load raw constant %s", name); + } return v; } @@ -105,7 +101,9 @@ int64_t Module::LoadConstant(const char* name, int64_t def) { Error err; int64_t v = LookupConstant(target_, (kConstantPrefix + name).c_str(), def, err); - if (err.Fail() && IsDebugMode()) fprintf(stderr, "Failed to load %s\n", name); + if (err.Fail()) { + Error::PrintInDebugMode("Failed to load constant %s", name); + } return v; } @@ -118,7 +116,10 @@ int64_t Module::LoadConstant(const char* name, const char* fallback, LookupConstant(target_, (kConstantPrefix + name).c_str(), def, err); if (err.Fail()) v = LookupConstant(target_, (kConstantPrefix + fallback).c_str(), def, err); - if (err.Fail() && IsDebugMode()) fprintf(stderr, "Failed to load %s\n", name); + if (err.Fail()) { + Error::PrintInDebugMode("Failed to load constant %s, fallback %s", name, + fallback); + } return v; } diff --git a/src/llv8-inl.h b/src/llv8-inl.h index 3ec42fa2..8a3ce863 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -1,6 +1,7 @@ #ifndef SRC_LLV8_INL_H_ #define SRC_LLV8_INL_H_ +#include #include "llv8.h" namespace llnode { @@ -19,7 +20,9 @@ inline T LLV8::LoadValue(int64_t addr, Error& err) { T res = T(this, ptr); if (!res.Check()) { - err = Error::Failure("Invalid value"); + // TODO(joyeecheung): use Error::Failure() to report information when + // there is less noise from here. + err = Error(true, "Invalid value"); return T(); } @@ -63,7 +66,8 @@ inline T HeapObject::LoadFieldValue(int64_t off, Error& err) { T res = v8()->LoadValue(LeaField(off), err); if (err.Fail()) return T(); if (!res.Check()) { - err = Error::Failure("Invalid value"); + err = Error::Failure("Invalid field value %s at 0x%016" PRIx64, + T::ClassName(), off); return T(); } diff --git a/src/llv8.cc b/src/llv8.cc index 1792435d..0bc972dc 100644 --- a/src/llv8.cc +++ b/src/llv8.cc @@ -2,6 +2,7 @@ #include #include +#include #include "llv8-inl.h" #include "llv8.h" @@ -57,14 +58,56 @@ 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 = process_.ReadPointerFromMemory(static_cast(addr), sberr); if (sberr.Fail()) { - // TODO(indutny): add more information - err = Error::Failure("Failed to load V8 value"); + // TODO(joyeecheung): use Error::Failure() to report information when + // there is less noise from here. + err = Error(true, "Failed to load pointer from v8 memory"); return -1; } @@ -79,8 +122,9 @@ int64_t LLV8::LoadUnsigned(int64_t addr, uint32_t byte_size, Error& err) { byte_size, sberr); if (sberr.Fail()) { - // TODO(indutny): add more information - err = Error::Failure("Failed to load V8 value"); + // TODO(joyeecheung): use Error::Failure() to report information when + // there is less noise from here. + err = Error(true, "Failed to load unsigned from v8 memory"); return -1; } @@ -94,8 +138,10 @@ double LLV8::LoadDouble(int64_t addr, Error& err) { int64_t value = process_.ReadUnsignedFromMemory(static_cast(addr), sizeof(double), sberr); if (sberr.Fail()) { - // TODO(indutny): add more information - err = Error::Failure("Failed to load V8 double value"); + err = Error::Failure( + "Failed to load double from v8 memory, " + "addr=0x%016" PRIx64, + addr); return -1.0; } @@ -104,12 +150,15 @@ double LLV8::LoadDouble(int64_t addr, Error& err) { } -std::string LLV8::LoadBytes(int64_t length, int64_t addr, Error& err) { +std::string LLV8::LoadBytes(int64_t addr, int64_t length, Error& err) { uint8_t* buf = new uint8_t[length + 1]; SBError sberr; process_.ReadMemory(addr, buf, static_cast(length), sberr); if (sberr.Fail()) { - err = Error::Failure("Failed to load V8 raw buffer"); + err = Error::Failure( + "Failed to load v8 backing store memory, " + "addr=0x%016" PRIx64 ", length=%" PRId64, + addr, length); delete[] buf; return std::string(); } @@ -135,8 +184,10 @@ std::string LLV8::LoadString(int64_t addr, int64_t length, Error& err) { process_.ReadMemory(static_cast(addr), buf, static_cast(length), sberr); if (sberr.Fail()) { - // TODO(indutny): add more information - err = Error::Failure("Failed to load V8 one byte string"); + err = Error::Failure( + "Failed to load v8 one byte string memory, " + "addr=0x%016" PRIx64 ", length=%" PRId64, + addr, length); delete[] buf; return std::string(); } @@ -161,8 +212,10 @@ std::string LLV8::LoadTwoByteString(int64_t addr, int64_t length, Error& err) { process_.ReadMemory(static_cast(addr), buf, static_cast(length * 2), sberr); if (sberr.Fail()) { - // TODO(indutny): add more information - err = Error::Failure("Failed to load V8 two byte string"); + err = Error::Failure( + "Failed to load V8 two byte string memory, " + "addr=0x%016" PRIx64 ", length=%" PRId64, + addr, length); delete[] buf; return std::string(); } @@ -183,8 +236,10 @@ uint8_t* LLV8::LoadChunk(int64_t addr, int64_t length, Error& err) { process_.ReadMemory(static_cast(addr), buf, static_cast(length), sberr); if (sberr.Fail()) { - // TODO(indutny): add more information - err = Error::Failure("Failed to load V8 memory chunk"); + err = Error::Failure( + "Failed to load V8 chunk memory, " + "addr=0x%016" PRIx64 ", length=%" PRId64, + addr, length); delete[] buf; return nullptr; } @@ -235,7 +290,7 @@ uint32_t JSFrame::GetSourceForDisplay(bool reset_line, uint32_t line_start, if (err.Fail()) { const char* msg = err.GetMessage(); if (msg == nullptr) { - err = Error(true, "Failed to get Function Source"); + err = Error::Failure("Failed to get Function Source"); } return line_start; } @@ -288,7 +343,7 @@ std::string JSFrame::Inspect(bool with_args, Error& err) { return ""; } else if (value != v8()->frame()->kJSFrame && value != v8()->frame()->kOptimizedFrame) { - err = Error::Failure("Unknown frame marker"); + err = Error::Failure("Unknown frame marker %" PRId64, value); return std::string(); } } @@ -432,7 +487,7 @@ std::string JSFunction::GetSource(Error& err) { // No source if (source_type > v8()->types()->kFirstNonstringType) { - err = Error(true, "No source"); + err = Error::Failure("No source, source_type=%" PRId64, source_type); return std::string(); } @@ -591,7 +646,7 @@ void Script::GetLines(uint64_t start_line, std::string lines[], // No source if (type > v8()->types()->kFirstNonstringType) { - err = Error(true, "No source"); + err = Error::Failure("No source, source_type=%" PRId64, type); return; } @@ -828,6 +883,8 @@ std::string HeapObject::Inspect(InspectOptions* options, Error& err) { return pre + date.Inspect(err); } + Error::PrintInDebugMode( + "Unknown HeapObject Type %" PRId64 " at 0x%016" PRIx64 "", type, raw()); return pre + ""; } @@ -956,7 +1013,7 @@ std::string String::ToString(Error& err) { return two.ToString(err); } - err = Error::Failure("Unsupported seq string encoding"); + err = Error::Failure("Unsupported seq string encoding %" PRId64, encoding); return std::string(); } @@ -980,7 +1037,7 @@ std::string String::ToString(Error& err) { return thin.ToString(err); } - err = Error::Failure("Unsupported string representation"); + err = Error::Failure("Unsupported string representation %" PRId64, repr); return std::string(); } @@ -1143,7 +1200,7 @@ std::string JSArrayBuffer::Inspect(InspectOptions* options, Error& err) { res += ": [\n "; int display_length = std::min(byte_length, options->length); - res += v8()->LoadBytes(display_length, data, err); + res += v8()->LoadBytes(data, display_length, err); if (display_length < byte_length) { res += " ..."; @@ -1201,7 +1258,7 @@ std::string JSArrayBufferView::Inspect(InspectOptions* options, Error& err) { res += ": [\n "; int display_length = std::min(byte_length, options->length); - res += v8()->LoadBytes(display_length, data + byte_offset, err); + res += v8()->LoadBytes(data + byte_offset, display_length, err); if (display_length < byte_length) { res += " ..."; @@ -1502,6 +1559,8 @@ std::string JSObject::InspectDescriptors(Map map, Error& err) { // Skip non-fields for now if (!descriptors.IsFieldDetails(details)) { + Error::PrintInDebugMode("Unknown field Type %" PRId64, + details.GetValue()); res += ""; continue; } diff --git a/src/llv8.h b/src/llv8.h index 59f014f9..5aa0921e 100644 --- a/src/llv8.h +++ b/src/llv8.h @@ -1,6 +1,7 @@ #ifndef SRC_LLV8_H_ #define SRC_LLV8_H_ +#include #include #include @@ -20,20 +21,30 @@ class CodeMap; class Error { public: - Error() : failed_(false), msg_(nullptr) {} - Error(bool failed, const char* msg) : failed_(failed), msg_(msg) {} + 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 inline Error Failure(const char* msg) { return Error(true, msg); } + 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_; } + inline const char* GetMessage() { return msg_.c_str(); } + + static void SetDebugMode(bool mode) { is_debug_mode = mode; } private: bool failed_; - const char* msg_; + std::string msg_; + static const size_t kMaxMessageLength = 128; + static bool is_debug_mode; }; #define V8_VALUE_DEFAULT_METHODS(NAME, PARENT) \ @@ -41,7 +52,8 @@ class Error { NAME() : PARENT() {} \ NAME(LLV8* v8, int64_t raw) : PARENT(v8, raw) {} \ NAME(Value& v) : PARENT(v) {} \ - NAME(Value* v) : PARENT(v->v8(), v->raw()) {} + NAME(Value* v) : PARENT(v->v8(), v->raw()) {} \ + static inline const char* ClassName() { return #NAME; } class Value { public: @@ -78,6 +90,8 @@ class Value { std::string GetTypeName(Error& err); std::string ToString(Error& err); + static inline const char* ClassName() { return "Value"; } + protected: LLV8* v8_; int64_t raw_; @@ -464,7 +478,7 @@ class LLV8 { int64_t LoadPtr(int64_t addr, Error& err); int64_t LoadUnsigned(int64_t addr, uint32_t byte_size, Error& err); double LoadDouble(int64_t addr, Error& err); - std::string LoadBytes(int64_t length, int64_t addr, Error& err); + std::string LoadBytes(int64_t addr, int64_t length, Error& err); std::string LoadString(int64_t addr, int64_t length, Error& err); std::string LoadTwoByteString(int64_t addr, int64_t length, Error& err); uint8_t* LoadChunk(int64_t addr, int64_t length, Error& err);