summaryrefslogtreecommitdiffstats
path: root/sal/osl/unx/backtraceapi.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sal/osl/unx/backtraceapi.cxx
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sal/osl/unx/backtraceapi.cxx')
-rw-r--r--sal/osl/unx/backtraceapi.cxx278
1 files changed, 278 insertions, 0 deletions
diff --git a/sal/osl/unx/backtraceapi.cxx b/sal/osl/unx/backtraceapi.cxx
new file mode 100644
index 0000000000..4c1f25b886
--- /dev/null
+++ b/sal/osl/unx/backtraceapi.cxx
@@ -0,0 +1,278 @@
+/* -*- 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 <sal/config.h>
+
+#include <cassert>
+#include <cstdlib>
+#include <limits>
+#include <memory>
+#include <mutex>
+
+#include <o3tl/runtimetooustring.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <sal/log.hxx>
+#include <sal/backtrace.hxx>
+
+#include "backtrace.h"
+#include <backtraceasstring.hxx>
+
+OUString osl::detail::backtraceAsString(sal_uInt32 maxDepth) {
+ std::unique_ptr<sal::BacktraceState> backtrace = sal::backtrace_get( maxDepth );
+ return sal::backtrace_to_string( backtrace.get());
+}
+
+std::unique_ptr<sal::BacktraceState> sal::backtrace_get(sal_uInt32 maxDepth)
+{
+ assert(maxDepth != 0);
+ auto const maxInt = static_cast<unsigned int>(
+ std::numeric_limits<int>::max());
+ if (maxDepth > maxInt) {
+ maxDepth = static_cast<sal_uInt32>(maxInt);
+ }
+ auto b1 = new void *[maxDepth];
+ int n = backtrace(b1, static_cast<int>(maxDepth));
+ return std::unique_ptr<BacktraceState>(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 <dlfcn.h>
+#include <unistd.h>
+#include <vector>
+#include <osl/process.h>
+#include <rtl/strbuf.hxx>
+#include <o3tl/lru_map.hxx>
+#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<void*, OString> FrameCache;
+std::mutex frameCacheMutex;
+FrameCache frameCache( 256 );
+
+void process_file_addr2line( const char* file, std::vector<FrameData>& 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<OUString> addrs;
+ std::vector<rtl_uString*> 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<OString> 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<ptrdiff_t>(addr) - reinterpret_cast<ptrdiff_t>(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<char*, decltype(free)*> 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<char*, decltype(free)*>
+ {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<char*, decltype(free)*> 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: */