| #include "debug_utils-inl.h" // NOLINT(build/include) |
| #include "env-inl.h" |
| #include "node_internals.h" |
| |
| #ifdef __POSIX__ |
| #if defined(__linux__) |
| #include <features.h> |
| #endif |
| |
| #ifdef __ANDROID__ |
| #include <android/log.h> |
| #endif |
| |
| #if defined(__linux__) && !defined(__GLIBC__) || \ |
| defined(__UCLIBC__) || \ |
| defined(_AIX) || \ |
| defined(__Fuchsia__) |
| #define HAVE_EXECINFO_H 0 |
| #else |
| #define HAVE_EXECINFO_H 1 |
| #endif |
| |
| #if HAVE_EXECINFO_H |
| #include <cxxabi.h> |
| #include <dlfcn.h> |
| #include <execinfo.h> |
| #include <unistd.h> |
| #include <sys/mman.h> |
| #include <cstdio> |
| #endif |
| |
| #endif // __POSIX__ |
| |
| #if defined(__linux__) || defined(__sun) || \ |
| defined(__FreeBSD__) || defined(__OpenBSD__) || \ |
| defined(__DragonFly__) |
| #include <link.h> |
| #endif |
| |
| #ifdef __APPLE__ |
| #include <mach-o/dyld.h> // _dyld_get_image_name() |
| #endif // __APPLE__ |
| |
| #ifdef _AIX |
| #include <sys/ldr.h> // ld_info structure |
| #endif // _AIX |
| |
| #ifdef _WIN32 |
| #include <Lm.h> |
| #include <Windows.h> |
| #include <dbghelp.h> |
| #include <process.h> |
| #include <psapi.h> |
| #include <tchar.h> |
| #endif // _WIN32 |
| |
| namespace node { |
| namespace per_process { |
| EnabledDebugList enabled_debug_list; |
| } |
| |
| void EnabledDebugList::Parse(Environment* env) { |
| std::string cats; |
| credentials::SafeGetenv("NODE_DEBUG_NATIVE", &cats, env); |
| Parse(cats, true); |
| } |
| |
| void EnabledDebugList::Parse(const std::string& cats, bool enabled) { |
| std::string debug_categories = cats; |
| while (!debug_categories.empty()) { |
| std::string::size_type comma_pos = debug_categories.find(','); |
| std::string wanted = ToLower(debug_categories.substr(0, comma_pos)); |
| |
| #define V(name) \ |
| { \ |
| static const std::string available_category = ToLower(#name); \ |
| if (available_category.find(wanted) != std::string::npos) \ |
| set_enabled(DebugCategory::name, enabled); \ |
| } |
| |
| DEBUG_CATEGORY_NAMES(V) |
| #undef V |
| |
| if (comma_pos == std::string::npos) break; |
| // Use everything after the `,` as the list for the next iteration. |
| debug_categories = debug_categories.substr(comma_pos + 1); |
| } |
| } |
| |
| #ifdef __POSIX__ |
| #if HAVE_EXECINFO_H |
| class PosixSymbolDebuggingContext final : public NativeSymbolDebuggingContext { |
| public: |
| PosixSymbolDebuggingContext() : pagesize_(getpagesize()) { } |
| |
| SymbolInfo LookupSymbol(void* address) override { |
| Dl_info info; |
| const bool have_info = dladdr(address, &info); |
| SymbolInfo ret; |
| if (!have_info) |
| return ret; |
| |
| if (info.dli_sname != nullptr) { |
| if (char* demangled = |
| abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, nullptr)) { |
| ret.name = demangled; |
| free(demangled); |
| } else { |
| ret.name = info.dli_sname; |
| } |
| } |
| |
| if (info.dli_fname != nullptr) { |
| ret.filename = info.dli_fname; |
| } |
| |
| return ret; |
| } |
| |
| bool IsMapped(void* address) override { |
| void* page_aligned = reinterpret_cast<void*>( |
| reinterpret_cast<uintptr_t>(address) & ~(pagesize_ - 1)); |
| return msync(page_aligned, pagesize_, MS_ASYNC) == 0; |
| } |
| |
| int GetStackTrace(void** frames, int count) override { |
| return backtrace(frames, count); |
| } |
| |
| private: |
| uintptr_t pagesize_; |
| }; |
| |
| std::unique_ptr<NativeSymbolDebuggingContext> |
| NativeSymbolDebuggingContext::New() { |
| return std::make_unique<PosixSymbolDebuggingContext>(); |
| } |
| |
| #else // HAVE_EXECINFO_H |
| |
| std::unique_ptr<NativeSymbolDebuggingContext> |
| NativeSymbolDebuggingContext::New() { |
| return std::make_unique<NativeSymbolDebuggingContext>(); |
| } |
| |
| #endif // HAVE_EXECINFO_H |
| |
| #else // __POSIX__ |
| |
| class Win32SymbolDebuggingContext final : public NativeSymbolDebuggingContext { |
| public: |
| Win32SymbolDebuggingContext() { |
| current_process_ = GetCurrentProcess(); |
| USE(SymInitialize(current_process_, nullptr, true)); |
| } |
| |
| ~Win32SymbolDebuggingContext() override { |
| USE(SymCleanup(current_process_)); |
| } |
| |
| using NameAndDisplacement = std::pair<std::string, DWORD64>; |
| NameAndDisplacement WrappedSymFromAddr(DWORD64 dwAddress) const { |
| // Refs: https://docs.microsoft.com/en-us/windows/desktop/Debug/retrieving-symbol-information-by-address |
| // Patches: |
| // Use `fprintf(stderr, ` instead of `printf` |
| // `sym.filename = pSymbol->Name` on success |
| // `current_process_` instead of `hProcess. |
| DWORD64 dwDisplacement = 0; |
| // Patch: made into arg - DWORD64 dwAddress = SOME_ADDRESS; |
| |
| char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; |
| const auto pSymbol = reinterpret_cast<PSYMBOL_INFO>(buffer); |
| |
| pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); |
| pSymbol->MaxNameLen = MAX_SYM_NAME; |
| |
| if (SymFromAddr(current_process_, dwAddress, &dwDisplacement, pSymbol)) { |
| // SymFromAddr returned success |
| return NameAndDisplacement(pSymbol->Name, dwDisplacement); |
| } else { |
| // SymFromAddr failed |
| const DWORD error = GetLastError(); // "eat" the error anyway |
| #ifdef DEBUG |
| fprintf(stderr, "SymFromAddr returned error : %lu\n", error); |
| #endif |
| } |
| // End MSDN code |
| |
| return NameAndDisplacement(); |
| } |
| |
| SymbolInfo WrappedGetLine(DWORD64 dwAddress) const { |
| SymbolInfo sym{}; |
| |
| // Refs: https://docs.microsoft.com/en-us/windows/desktop/Debug/retrieving-symbol-information-by-address |
| // Patches: |
| // Use `fprintf(stderr, ` instead of `printf`. |
| // Assign values to `sym` on success. |
| // `current_process_` instead of `hProcess. |
| |
| // Patch: made into arg - DWORD64 dwAddress; |
| DWORD dwDisplacement; |
| IMAGEHLP_LINE64 line; |
| |
| SymSetOptions(SYMOPT_LOAD_LINES); |
| |
| line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); |
| // Patch: made into arg - dwAddress = 0x1000000; |
| |
| if (SymGetLineFromAddr64(current_process_, dwAddress, |
| &dwDisplacement, &line)) { |
| // SymGetLineFromAddr64 returned success |
| sym.filename = line.FileName; |
| sym.line = line.LineNumber; |
| } else { |
| // SymGetLineFromAddr64 failed |
| const DWORD error = GetLastError(); // "eat" the error anyway |
| #ifdef DEBUG |
| fprintf(stderr, "SymGetLineFromAddr64 returned error : %lu\n", error); |
| #endif |
| } |
| // End MSDN code |
| |
| return sym; |
| } |
| |
| // Fills the SymbolInfo::name of the io/out argument `sym` |
| std::string WrappedUnDecorateSymbolName(const char* name) const { |
| // Refs: https://docs.microsoft.com/en-us/windows/desktop/Debug/retrieving-undecorated-symbol-names |
| // Patches: |
| // Use `fprintf(stderr, ` instead of `printf`. |
| // return `szUndName` instead of `printf` on success |
| char szUndName[MAX_SYM_NAME]; |
| if (UnDecorateSymbolName(name, szUndName, sizeof(szUndName), |
| UNDNAME_COMPLETE)) { |
| // UnDecorateSymbolName returned success |
| return szUndName; |
| } else { |
| // UnDecorateSymbolName failed |
| const DWORD error = GetLastError(); // "eat" the error anyway |
| #ifdef DEBUG |
| fprintf(stderr, "UnDecorateSymbolName returned error %lu\n", error); |
| #endif |
| } |
| return nullptr; |
| } |
| |
| SymbolInfo LookupSymbol(void* address) override { |
| const DWORD64 dw_address = reinterpret_cast<DWORD64>(address); |
| SymbolInfo ret = WrappedGetLine(dw_address); |
| std::tie(ret.name, ret.dis) = WrappedSymFromAddr(dw_address); |
| if (!ret.name.empty()) { |
| ret.name = WrappedUnDecorateSymbolName(ret.name.c_str()); |
| } |
| return ret; |
| } |
| |
| bool IsMapped(void* address) override { |
| MEMORY_BASIC_INFORMATION info; |
| |
| if (VirtualQuery(address, &info, sizeof(info)) != sizeof(info)) |
| return false; |
| |
| return info.State == MEM_COMMIT && info.Protect != 0; |
| } |
| |
| int GetStackTrace(void** frames, int count) override { |
| return CaptureStackBackTrace(0, count, frames, nullptr); |
| } |
| |
| Win32SymbolDebuggingContext(const Win32SymbolDebuggingContext&) = delete; |
| Win32SymbolDebuggingContext(Win32SymbolDebuggingContext&&) = delete; |
| Win32SymbolDebuggingContext operator=(const Win32SymbolDebuggingContext&) |
| = delete; |
| Win32SymbolDebuggingContext operator=(Win32SymbolDebuggingContext&&) |
| = delete; |
| |
| private: |
| HANDLE current_process_; |
| }; |
| |
| std::unique_ptr<NativeSymbolDebuggingContext> |
| NativeSymbolDebuggingContext::New() { |
| return std::unique_ptr<NativeSymbolDebuggingContext>( |
| new Win32SymbolDebuggingContext()); |
| } |
| |
| #endif // __POSIX__ |
| |
| std::string NativeSymbolDebuggingContext::SymbolInfo::Display() const { |
| std::ostringstream oss; |
| oss << name; |
| if (dis != 0) { |
| oss << "+" << dis; |
| } |
| if (!filename.empty()) { |
| oss << " [" << filename << ']'; |
| } |
| if (line != 0) { |
| oss << ":L" << line; |
| } |
| return oss.str(); |
| } |
| |
| void DumpBacktrace(FILE* fp) { |
| auto sym_ctx = NativeSymbolDebuggingContext::New(); |
| void* frames[256]; |
| const int size = sym_ctx->GetStackTrace(frames, arraysize(frames)); |
| for (int i = 1; i < size; i += 1) { |
| void* frame = frames[i]; |
| NativeSymbolDebuggingContext::SymbolInfo s = sym_ctx->LookupSymbol(frame); |
| fprintf(fp, "%2d: %p %s\n", i, frame, s.Display().c_str()); |
| } |
| } |
| |
| void CheckedUvLoopClose(uv_loop_t* loop) { |
| if (uv_loop_close(loop) == 0) return; |
| |
| PrintLibuvHandleInformation(loop, stderr); |
| |
| fflush(stderr); |
| // Finally, abort. |
| CHECK(0 && "uv_loop_close() while having open handles"); |
| } |
| |
| void PrintLibuvHandleInformation(uv_loop_t* loop, FILE* stream) { |
| struct Info { |
| std::unique_ptr<NativeSymbolDebuggingContext> ctx; |
| FILE* stream; |
| size_t num_handles; |
| }; |
| |
| Info info { NativeSymbolDebuggingContext::New(), stream, 0 }; |
| |
| fprintf(stream, "uv loop at [%p] has open handles:\n", loop); |
| |
| uv_walk(loop, [](uv_handle_t* handle, void* arg) { |
| Info* info = static_cast<Info*>(arg); |
| NativeSymbolDebuggingContext* sym_ctx = info->ctx.get(); |
| FILE* stream = info->stream; |
| info->num_handles++; |
| |
| fprintf(stream, "[%p] %s%s\n", handle, uv_handle_type_name(handle->type), |
| uv_is_active(handle) ? " (active)" : ""); |
| |
| void* close_cb = reinterpret_cast<void*>(handle->close_cb); |
| fprintf(stream, "\tClose callback: %p %s\n", |
| close_cb, sym_ctx->LookupSymbol(close_cb).Display().c_str()); |
| |
| fprintf(stream, "\tData: %p %s\n", |
| handle->data, sym_ctx->LookupSymbol(handle->data).Display().c_str()); |
| |
| // We are also interested in the first field of what `handle->data` |
| // points to, because for C++ code that is usually the virtual table pointer |
| // and gives us information about the exact kind of object we're looking at. |
| void* first_field = nullptr; |
| // `handle->data` might be any value, including `nullptr`, or something |
| // cast from a completely different type; therefore, check that it’s |
| // dereferencable first. |
| if (sym_ctx->IsMapped(handle->data)) |
| first_field = *reinterpret_cast<void**>(handle->data); |
| |
| if (first_field != nullptr) { |
| fprintf(stream, "\t(First field): %p %s\n", |
| first_field, sym_ctx->LookupSymbol(first_field).Display().c_str()); |
| } |
| }, &info); |
| |
| fprintf(stream, "uv loop at [%p] has %zu open handles in total\n", |
| loop, info.num_handles); |
| } |
| |
| std::vector<std::string> NativeSymbolDebuggingContext::GetLoadedLibraries() { |
| std::vector<std::string> list; |
| #if defined(__linux__) || defined(__FreeBSD__) || \ |
| defined(__OpenBSD__) || defined(__DragonFly__) |
| dl_iterate_phdr( |
| [](struct dl_phdr_info* info, size_t size, void* data) { |
| auto list = static_cast<std::vector<std::string>*>(data); |
| if (*info->dlpi_name != '\0') { |
| list->push_back(info->dlpi_name); |
| } |
| return 0; |
| }, |
| &list); |
| #elif __APPLE__ |
| uint32_t i = 0; |
| for (const char* name = _dyld_get_image_name(i); name != nullptr; |
| name = _dyld_get_image_name(++i)) { |
| list.push_back(name); |
| } |
| |
| #elif _AIX |
| // We can't tell in advance how large the buffer needs to be. |
| // Retry until we reach too large a size (1Mb). |
| const unsigned int kBufferGrowStep = 4096; |
| MallocedBuffer<char> buffer(kBufferGrowStep); |
| int rc = -1; |
| do { |
| rc = loadquery(L_GETINFO, buffer.data, buffer.size); |
| if (rc == 0) break; |
| buffer = MallocedBuffer<char>(buffer.size + kBufferGrowStep); |
| } while (buffer.size < 1024 * 1024); |
| |
| if (rc == 0) { |
| char* buf = buffer.data; |
| ld_info* cur_info = nullptr; |
| do { |
| std::ostringstream str; |
| cur_info = reinterpret_cast<ld_info*>(buf); |
| char* member_name = cur_info->ldinfo_filename + |
| strlen(cur_info->ldinfo_filename) + 1; |
| if (*member_name != '\0') { |
| str << cur_info->ldinfo_filename << "(" << member_name << ")"; |
| list.push_back(str.str()); |
| str.str(""); |
| } else { |
| list.push_back(cur_info->ldinfo_filename); |
| } |
| buf += cur_info->ldinfo_next; |
| } while (cur_info->ldinfo_next != 0); |
| } |
| #elif __sun |
| Link_map* p; |
| |
| if (dlinfo(RTLD_SELF, RTLD_DI_LINKMAP, &p) != -1) { |
| for (Link_map* l = p; l != nullptr; l = l->l_next) { |
| list.push_back(l->l_name); |
| } |
| } |
| |
| #elif _WIN32 |
| // Windows implementation - get a handle to the process. |
| HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, |
| FALSE, GetCurrentProcessId()); |
| if (process_handle == nullptr) { |
| // Cannot proceed, return an empty list. |
| return list; |
| } |
| // Get a list of all the modules in this process |
| DWORD size_1 = 0; |
| DWORD size_2 = 0; |
| // First call to get the size of module array needed |
| if (EnumProcessModules(process_handle, nullptr, 0, &size_1)) { |
| MallocedBuffer<HMODULE> modules(size_1); |
| |
| // Second call to populate the module array |
| if (EnumProcessModules(process_handle, modules.data, size_1, &size_2)) { |
| for (DWORD i = 0; |
| i < (size_1 / sizeof(HMODULE)) && i < (size_2 / sizeof(HMODULE)); |
| i++) { |
| WCHAR module_name[MAX_PATH]; |
| // Obtain and report the full pathname for each module |
| if (GetModuleFileNameExW(process_handle, |
| modules.data[i], |
| module_name, |
| arraysize(module_name) / sizeof(WCHAR))) { |
| DWORD size = WideCharToMultiByte( |
| CP_UTF8, 0, module_name, -1, nullptr, 0, nullptr, nullptr); |
| char* str = new char[size]; |
| WideCharToMultiByte( |
| CP_UTF8, 0, module_name, -1, str, size, nullptr, nullptr); |
| list.push_back(str); |
| } |
| } |
| } |
| } |
| |
| // Release the handle to the process. |
| CloseHandle(process_handle); |
| #endif |
| return list; |
| } |
| |
| void FWrite(FILE* file, const std::string& str) { |
| auto simple_fwrite = [&]() { |
| // The return value is ignored because there's no good way to handle it. |
| fwrite(str.data(), str.size(), 1, file); |
| }; |
| |
| if (file != stderr && file != stdout) { |
| simple_fwrite(); |
| return; |
| } |
| #ifdef _WIN32 |
| HANDLE handle = |
| GetStdHandle(file == stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); |
| |
| // Check if stderr is something other than a tty/console |
| if (handle == INVALID_HANDLE_VALUE || handle == nullptr || |
| uv_guess_handle(_fileno(file)) != UV_TTY) { |
| simple_fwrite(); |
| return; |
| } |
| |
| // Get required wide buffer size |
| int n = MultiByteToWideChar(CP_UTF8, 0, str.data(), str.size(), nullptr, 0); |
| |
| std::vector<wchar_t> wbuf(n); |
| MultiByteToWideChar(CP_UTF8, 0, str.data(), str.size(), wbuf.data(), n); |
| |
| WriteConsoleW(handle, wbuf.data(), n, nullptr, nullptr); |
| return; |
| #elif defined(__ANDROID__) |
| if (file == stderr) { |
| __android_log_print(ANDROID_LOG_ERROR, "nodejs", "%s", str.data()); |
| return; |
| } |
| #endif |
| simple_fwrite(); |
| } |
| |
| } // namespace node |
| |
| extern "C" void __DumpBacktrace(FILE* fp) { |
| node::DumpBacktrace(fp); |
| } |