/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "backtrace.h" #include OUString osl::detail::backtraceAsString(sal_uInt32 maxDepth) { std::unique_ptr backtrace = sal::backtrace_get( maxDepth ); return sal::backtrace_to_string( backtrace.get()); } std::unique_ptr sal::backtrace_get(sal_uInt32 maxDepth) { assert(maxDepth != 0); auto const maxInt = static_cast( std::numeric_limits::max()); if (maxDepth > maxInt) { maxDepth = static_cast(maxInt); } auto b1 = new void *[maxDepth]; int n = backtrace(b1, static_cast(maxDepth)); return std::unique_ptr(new BacktraceState{ b1, n }); } #if OSL_DEBUG_LEVEL > 0 && (defined LINUX || defined MACOSX || defined FREEBSD || defined NETBSD || defined OPENBSD || defined(DRAGONFLY)) // The backtrace_symbols() function is unreliable, it requires -rdynamic and even then it cannot resolve names // of many functions, such as those with hidden ELF visibility. Libunwind doesn't resolve names for me either, // boost::stacktrace doesn't work properly, the best result I've found is addr2line. Using addr2line is relatively // slow, but I don't find that to be a big problem for printing of backtraces. Feel free to improve if needed // (e.g. the calls could be grouped by the binary). #include #include #include #include #include #include #include #include "file_url.hxx" namespace { struct FrameData { const char* file = nullptr; void* addr; ptrdiff_t offset; OString info; bool handled = false; }; typedef o3tl::lru_map FrameCache; std::mutex frameCacheMutex; FrameCache frameCache( 256 ); void process_file_addr2line( const char* file, std::vector& frameData ) { if(access( file, R_OK ) != 0) return; // cannot read info from the binary file anyway OUString binary("addr2line"); OUString dummy; #if defined __clang__ // llvm-addr2line is faster than addr2line if(osl::detail::find_in_PATH("llvm-addr2line", dummy)) binary = "llvm-addr2line"; #endif if(!osl::detail::find_in_PATH(binary, dummy)) return; // Will not work, avoid warnings from osl process code. OUString arg1("-Cfe"); OUString arg2 = OUString::fromUtf8(file); std::vector addrs; std::vector args; args.reserve(frameData.size() + 2); args.push_back( arg1.pData ); args.push_back( arg2.pData ); for( FrameData& frame : frameData ) { if( frame.file != nullptr && strcmp( file, frame.file ) == 0 ) { addrs.push_back("0x" + OUString::number(frame.offset, 16)); args.push_back(addrs.back().pData); frame.handled = true; } } oslProcess aProcess; oslFileHandle pOut = nullptr; oslFileHandle pErr = nullptr; oslSecurity pSecurity = osl_getCurrentSecurity(); oslProcessError eErr = osl_executeProcess_WithRedirectedIO( binary.pData, args.data(), args.size(), osl_Process_SEARCHPATH | osl_Process_HIDDEN, pSecurity, nullptr, nullptr, 0, &aProcess, nullptr, &pOut, &pErr); osl_freeSecurityHandle(pSecurity); if (eErr != osl_Process_E_None) { SAL_WARN("sal.osl", binary << " call to resolve " << file << " symbols failed"); return; } OStringBuffer outputBuffer; if (pOut) { const sal_uInt64 BUF_SIZE = 1024; char buffer[BUF_SIZE]; while (true) { sal_uInt64 bytesRead = 0; while(osl_readFile(pErr, buffer, BUF_SIZE, &bytesRead) == osl_File_E_None && bytesRead != 0) ; // discard possible stderr output oslFileError err = osl_readFile(pOut, buffer, BUF_SIZE, &bytesRead); if(bytesRead == 0 && err == osl_File_E_None) break; outputBuffer.append(buffer, bytesRead); if (err != osl_File_E_None && err != osl_File_E_AGAIN) break; } osl_closeFile(pOut); } if(pErr) osl_closeFile(pErr); eErr = osl_joinProcess(aProcess); osl_freeProcessHandle(aProcess); OString output = outputBuffer.makeStringAndClear(); std::vector lines; sal_Int32 outputPos = 0; while(outputPos < output.getLength()) { sal_Int32 end1 = output.indexOf('\n', outputPos); if(end1 < 0) break; sal_Int32 end2 = output.indexOf('\n', end1 + 1); if(end2 < 0) end2 = output.getLength(); lines.push_back(output.copy( outputPos, end1 - outputPos )); lines.push_back(output.copy( end1 + 1, end2 - end1 - 1 )); outputPos = end2 + 1; } if(lines.size() != addrs.size() * 2) { SAL_WARN("sal.osl", "failed to parse " << binary << " call output to resolve " << file << " symbols "); return; // addr2line problem? } size_t linesPos = 0; for( FrameData& frame : frameData ) { if( frame.file != nullptr && strcmp( file, frame.file ) == 0 ) { // There should be two lines, first function name and second source file information. // If each of them starts with ??, it is invalid/unknown. OString function = lines[linesPos]; OString source = lines[linesPos+1]; linesPos += 2; if(function.isEmpty() || function.startsWith("??")) { // Cache that the address cannot be resolved. std::lock_guard guard(frameCacheMutex); frameCache.insert( { frame.addr, "" } ); } else { if( source.startsWith("??")) frame.info = function + " in " + file; else frame.info = function + " at " + source; std::lock_guard guard(frameCacheMutex); frameCache.insert( { frame.addr, frame.info } ); } } } } } // namespace OUString sal::backtrace_to_string(BacktraceState* backtraceState) { // Collect frames for each binary and process each binary in one addr2line // call for better performance. std::vector< FrameData > frameData; frameData.resize(backtraceState->nDepth); for (int i = 0; i != backtraceState->nDepth; ++i) { Dl_info dli; void* addr = backtraceState->buffer[i]; std::unique_lock guard(frameCacheMutex); auto it = frameCache.find(addr); bool found = it != frameCache.end(); guard.unlock(); if( found ) { frameData[ i ].info = it->second; frameData[ i ].handled = true; } else if (dladdr(addr, &dli) != 0) { if (dli.dli_fname && dli.dli_fbase) { frameData[ i ].file = dli.dli_fname; frameData[ i ].addr = addr; frameData[ i ].offset = reinterpret_cast(addr) - reinterpret_cast(dli.dli_fbase); } } } for (int i = 0; i != backtraceState->nDepth; ++i) { if(frameData[ i ].file != nullptr && !frameData[ i ].handled) process_file_addr2line( frameData[ i ].file, frameData ); } OUStringBuffer b3; std::unique_ptr b2{ nullptr, free }; bool fallbackInitDone = false; for (int i = 0; i != backtraceState->nDepth; ++i) { if (i != 0) b3.append("\n"); b3.append( "#" + OUString::number( i ) + " " ); if(!frameData[i].info.isEmpty()) b3.append(o3tl::runtimeToOUString(frameData[i].info.getStr())); else { if(!fallbackInitDone) { b2 = std::unique_ptr {backtrace_symbols(backtraceState->buffer, backtraceState->nDepth), free}; fallbackInitDone = true; } if(b2) b3.append(o3tl::runtimeToOUString(b2.get()[i])); else b3.append("??"); } } return b3.makeStringAndClear(); } #else OUString sal::backtrace_to_string(BacktraceState* backtraceState) { std::unique_ptr b2{backtrace_symbols(backtraceState->buffer, backtraceState->nDepth), free}; if (!b2) { return OUString(); } OUStringBuffer b3; for (int i = 0; i != backtraceState->nDepth; ++i) { if (i != 0) { b3.append("\n"); } b3.append( "#" + OUString::number( i ) + " " ); b3.append(o3tl::runtimeToOUString(b2.get()[i])); } return b3.makeStringAndClear(); } #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */