/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: */ |