Coverage Report

Created: 2025-11-16 09:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sal/osl/unx/backtraceapi.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 */
9
10
#include <sal/config.h>
11
12
#include <cassert>
13
#include <cstdlib>
14
#include <limits>
15
#include <memory>
16
#include <mutex>
17
18
#include <o3tl/runtimetooustring.hxx>
19
#include <rtl/ustrbuf.hxx>
20
#include <rtl/ustring.hxx>
21
#include <sal/types.h>
22
#include <sal/log.hxx>
23
#include <sal/backtrace.hxx>
24
25
#include "backtrace.h"
26
#include <backtraceasstring.hxx>
27
28
0
OUString osl::detail::backtraceAsString(sal_uInt32 maxDepth) {
29
0
    std::unique_ptr<sal::BacktraceState> backtrace = sal::backtrace_get( maxDepth );
30
0
    return sal::backtrace_to_string( backtrace.get());
31
0
}
32
33
std::unique_ptr<sal::BacktraceState> sal::backtrace_get(sal_uInt32 maxDepth)
34
0
{
35
0
    assert(maxDepth != 0);
36
0
    auto const maxInt = static_cast<unsigned int>(
37
0
        std::numeric_limits<int>::max());
38
0
    if (maxDepth > maxInt) {
39
0
        maxDepth = static_cast<sal_uInt32>(maxInt);
40
0
    }
41
0
    auto b1 = new void *[maxDepth];
42
0
    int n = backtrace(b1, static_cast<int>(maxDepth));
43
0
    return std::unique_ptr<BacktraceState>(new BacktraceState{ b1, n });
44
0
}
45
46
#if OSL_DEBUG_LEVEL > 0 && (defined LINUX || defined MACOSX || defined FREEBSD || defined NETBSD || defined OPENBSD || defined(DRAGONFLY))
47
// The backtrace_symbols() function is unreliable, it requires -rdynamic and even then it cannot resolve names
48
// of many functions, such as those with hidden ELF visibility. Libunwind doesn't resolve names for me either,
49
// boost::stacktrace doesn't work properly, the best result I've found is addr2line. Using addr2line is relatively
50
// slow, but I don't find that to be a big problem for printing of backtraces. Feel free to improve if needed
51
// (e.g. the calls could be grouped by the binary).
52
#include <dlfcn.h>
53
#include <unistd.h>
54
#include <vector>
55
#include <osl/process.h>
56
#include <rtl/strbuf.hxx>
57
#include <o3tl/lru_map.hxx>
58
#include "file_url.hxx"
59
60
namespace
61
{
62
struct FrameData
63
{
64
    const char* file = nullptr;
65
    void* addr;
66
    ptrdiff_t offset;
67
    OString info;
68
    bool handled = false;
69
};
70
71
typedef o3tl::lru_map<void*, OString> FrameCache;
72
std::mutex frameCacheMutex;
73
FrameCache frameCache( 256 );
74
75
void process_file_addr2line( const char* file, std::vector<FrameData>& frameData )
76
{
77
    if(access( file, R_OK ) != 0)
78
        return; // cannot read info from the binary file anyway
79
    OUString binary(u"addr2line"_ustr);
80
    OUString dummy;
81
#if defined __clang__
82
    // llvm-addr2line is faster than addr2line
83
    if(osl::detail::find_in_PATH(u"llvm-addr2line"_ustr, dummy))
84
        binary = "llvm-addr2line";
85
#endif
86
    if(!osl::detail::find_in_PATH(binary, dummy))
87
        return; // Will not work, avoid warnings from osl process code.
88
    OUString arg1(u"-Cfe"_ustr);
89
    OUString arg2 = OUString::fromUtf8(file);
90
    std::vector<OUString> addrs;
91
    std::vector<rtl_uString*> args;
92
    args.reserve(frameData.size() + 2);
93
    args.push_back( arg1.pData );
94
    args.push_back( arg2.pData );
95
    for( FrameData& frame : frameData )
96
    {
97
        if( frame.file != nullptr && strcmp( file, frame.file ) == 0 )
98
        {
99
            addrs.push_back("0x" + OUString::number(frame.offset, 16));
100
            args.push_back(addrs.back().pData);
101
            frame.handled = true;
102
        }
103
    }
104
105
    oslProcess aProcess;
106
    oslFileHandle pOut = nullptr;
107
    oslFileHandle pErr = nullptr;
108
    oslSecurity pSecurity = osl_getCurrentSecurity();
109
    oslProcessError eErr = osl_executeProcess_WithRedirectedIO(
110
        binary.pData, args.data(), args.size(), osl_Process_SEARCHPATH | osl_Process_HIDDEN, pSecurity, nullptr,
111
        nullptr, 0, &aProcess, nullptr, &pOut, &pErr);
112
    osl_freeSecurityHandle(pSecurity);
113
114
    if (eErr != osl_Process_E_None)
115
    {
116
        SAL_WARN("sal.osl", binary << " call to resolve " << file << " symbols failed");
117
        return;
118
    }
119
120
    OStringBuffer outputBuffer;
121
    if (pOut)
122
    {
123
        const sal_uInt64 BUF_SIZE = 1024;
124
        char buffer[BUF_SIZE];
125
        while (true)
126
        {
127
            sal_uInt64 bytesRead = 0;
128
            while(osl_readFile(pErr, buffer, BUF_SIZE, &bytesRead) == osl_File_E_None
129
                && bytesRead != 0)
130
                ; // discard possible stderr output
131
            oslFileError err = osl_readFile(pOut, buffer, BUF_SIZE, &bytesRead);
132
            if(bytesRead == 0 && err == osl_File_E_None)
133
                break;
134
            outputBuffer.append(buffer, bytesRead);
135
            if (err != osl_File_E_None && err != osl_File_E_AGAIN)
136
                break;
137
        }
138
        osl_closeFile(pOut);
139
    }
140
    if(pErr)
141
        osl_closeFile(pErr);
142
    eErr = osl_joinProcess(aProcess);
143
    osl_freeProcessHandle(aProcess);
144
145
    OString output = outputBuffer.makeStringAndClear();
146
    std::vector<OString> lines;
147
    sal_Int32 outputPos = 0;
148
    while(outputPos < output.getLength())
149
    {
150
        sal_Int32 end1 = output.indexOf('\n', outputPos);
151
        if(end1 < 0)
152
            break;
153
        sal_Int32 end2 = output.indexOf('\n', end1 + 1);
154
        if(end2 < 0)
155
            end2 = output.getLength();
156
        lines.push_back(output.copy( outputPos, end1 - outputPos ));
157
        lines.push_back(output.copy( end1 + 1, end2 - end1 - 1 ));
158
        outputPos = end2 + 1;
159
    }
160
    if(lines.size() != addrs.size() * 2)
161
    {
162
        SAL_WARN("sal.osl", "failed to parse " << binary << " call output to resolve " << file << " symbols ");
163
        return; // addr2line problem?
164
    }
165
    size_t linesPos = 0;
166
    for( FrameData& frame : frameData )
167
    {
168
        if( frame.file != nullptr && strcmp( file, frame.file ) == 0 )
169
        {
170
            // There should be two lines, first function name and second source file information.
171
            // If each of them starts with ??, it is invalid/unknown.
172
            const OString& function = lines[linesPos];
173
            const OString& source = lines[linesPos+1];
174
            linesPos += 2;
175
            if(function.isEmpty() || function.startsWith("??"))
176
            {
177
                // Cache that the address cannot be resolved.
178
                std::lock_guard guard(frameCacheMutex);
179
                frameCache.insert( { frame.addr, "" } );
180
            }
181
            else
182
            {
183
                if( source.startsWith("??"))
184
                    frame.info = function + " in " + file;
185
                else
186
                    frame.info = function + " at " + source;
187
                std::lock_guard guard(frameCacheMutex);
188
                frameCache.insert( { frame.addr, frame.info } );
189
            }
190
        }
191
    }
192
}
193
194
} // namespace
195
196
OUString sal::backtrace_to_string(BacktraceState* backtraceState)
197
{
198
    // Collect frames for each binary and process each binary in one addr2line
199
    // call for better performance.
200
    std::vector< FrameData > frameData;
201
    frameData.resize(backtraceState->nDepth);
202
    for (int i = 0; i != backtraceState->nDepth; ++i)
203
    {
204
        Dl_info dli;
205
        void* addr = backtraceState->buffer[i];
206
        std::unique_lock guard(frameCacheMutex);
207
        auto it = frameCache.find(addr);
208
        bool found = it != frameCache.end();
209
        guard.unlock();
210
        if( found )
211
        {
212
            frameData[ i ].info = it->second;
213
            frameData[ i ].handled = true;
214
        }
215
        else if (dladdr(addr, &dli) != 0)
216
        {
217
            if (dli.dli_fname && dli.dli_fbase)
218
            {
219
                frameData[ i ].file = dli.dli_fname;
220
                frameData[ i ].addr = addr;
221
                frameData[ i ].offset = reinterpret_cast<ptrdiff_t>(addr) - reinterpret_cast<ptrdiff_t>(dli.dli_fbase);
222
            }
223
        }
224
    }
225
    for (int i = 0; i != backtraceState->nDepth; ++i)
226
    {
227
        if(frameData[ i ].file != nullptr && !frameData[ i ].handled)
228
            process_file_addr2line( frameData[ i ].file, frameData );
229
    }
230
    OUStringBuffer b3;
231
    std::unique_ptr<char*, decltype(free)*> b2{ nullptr, free };
232
    bool fallbackInitDone = false;
233
    for (int i = 0; i != backtraceState->nDepth; ++i)
234
    {
235
        if (i != 0)
236
            b3.append("\n");
237
        b3.append( "#" + OUString::number( i ) + " " );
238
        if(!frameData[i].info.isEmpty())
239
            b3.append(o3tl::runtimeToOUString(frameData[i].info.getStr()));
240
        else
241
        {
242
            if(!fallbackInitDone)
243
            {
244
                b2 = std::unique_ptr<char*, decltype(free)*>
245
                    {backtrace_symbols(backtraceState->buffer, backtraceState->nDepth), free};
246
                fallbackInitDone = true;
247
            }
248
            if(b2)
249
                b3.append(o3tl::runtimeToOUString(b2.get()[i]));
250
            else
251
                b3.append("??");
252
        }
253
    }
254
    return b3.makeStringAndClear();
255
}
256
257
#else
258
259
OUString sal::backtrace_to_string(BacktraceState* backtraceState)
260
0
{
261
0
    std::unique_ptr<char*, decltype(free)*> b2{backtrace_symbols(backtraceState->buffer, backtraceState->nDepth), free};
262
0
    if (!b2) {
263
0
        return OUString();
264
0
    }
265
0
    OUStringBuffer b3;
266
0
    for (int i = 0; i != backtraceState->nDepth; ++i) {
267
0
        if (i != 0) {
268
0
            b3.append("\n");
269
0
        }
270
0
        b3.append( "#" + OUString::number( i ) + " " );
271
0
        b3.append(o3tl::runtimeToOUString(b2.get()[i]));
272
0
    }
273
0
    return b3.makeStringAndClear();
274
0
}
275
276
#endif
277
278
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */