Arcane  4.1.12.0
Developer documentation
Loading...
Searching...
No Matches
LibUnwindStackTraceService.cc
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2026 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
4// See the top-level COPYRIGHT file for details.
5// SPDX-License-Identifier: Apache-2.0
6//-----------------------------------------------------------------------------
7/*---------------------------------------------------------------------------*/
8/* LibUnwindStackTraceService.cc (C) 2000-2025 */
9/* */
10/* Function call trace service using 'libunwind'. */
11/*---------------------------------------------------------------------------*/
12/*---------------------------------------------------------------------------*/
13
14#include "arcane/utils/PlatformUtils.h"
15#include "arcane/utils/IStackTraceService.h"
16#include "arcane/utils/StackTrace.h"
17#include "arcane/utils/StringBuilder.h"
18#include "arcane/utils/Array.h"
19#include "arcane/utils/ISymbolizerService.h"
20
21#include "arccore/base/internal/DependencyInjection.h"
22#include "arccore/common/internal/Process.h"
23
24#include "arcane/core/ServiceBuilder.h"
25#include "arcane/core/Directory.h"
26
28#include "arcane/core/AbstractService.h"
29
30#include "arcane_packages.h"
31
32#define UNW_LOCAL_ONLY
33#include <libunwind.h>
34#include <cxxabi.h>
35
36#include <map>
37#include <mutex>
38
39#include <execinfo.h>
40#include <cstdio>
41#include <sys/types.h>
42#include <unistd.h>
43//#include <cstdlib>
44#include <dlfcn.h>
45
46#if defined(ARCANE_HAS_PACKAGE_DW)
47#include <elfutils/libdwfl.h>
48#endif
49
50/*---------------------------------------------------------------------------*/
51/*---------------------------------------------------------------------------*/
52
53namespace Arcane
54{
55
57{
58 int m_line = 0;
59 int m_column = 0;
60 const char* m_source_file = nullptr;
61};
62
63#if defined(ARCANE_HAS_PACKAGE_DW)
76class DWHandler
77{
78 public:
79
80 DWHandler()
81 {
82 m_callback.find_elf = &dwfl_linux_proc_find_elf;
83 m_callback.find_debuginfo = &dwfl_standard_find_debuginfo;
84 m_callback.section_address = nullptr;
85 m_callback.debuginfo_path = nullptr;
86 }
87
88 ~DWHandler()
89 {
90 if (m_session)
91 dwfl_end(m_session);
92 }
93
99 DebugSourceInfo getInfo(unw_word_t func_address)
100 {
101 std::scoped_lock<std::mutex> lock(m_mutex);
102 DebugSourceInfo source_info;
103
104 _init();
105
106 auto dw_address = reinterpret_cast<Dwarf_Addr>(func_address);
107
108 Dwfl_Module* module = dwfl_addrmodule(m_session, dw_address);
109 if (!module)
110 return source_info;
111 Dwarf_Addr bias = 0;
112 Dwarf_Die* dw_die = dwfl_module_addrdie(module, dw_address, &bias);
113 if (dw_die)
114 return source_info;
115
116 Dwarf_Line* dw_source_info = dwarf_getsrc_die(dw_die, dw_address - bias);
117
118 if (dw_source_info) {
119 int line = 0;
120 int column = 0;
121 // WARNING: the returned pointer is no longer valid if m_session is destroyed.
122 const char* source_file = dwarf_linesrc(dw_source_info, nullptr, nullptr);
123 dwarf_lineno(dw_source_info, &line);
124 dwarf_linecol(dw_source_info, &column);
125 source_info = { line, column, source_file };
126 }
127
128 return source_info;
129 }
130
131 public:
132
133 bool m_is_init = false;
134 Dwfl_Callbacks m_callback;
135 Dwfl* m_session = nullptr;
136 std::mutex m_mutex;
137
138 private:
139
140 void _init()
141 {
142 if (m_is_init)
143 return;
144 m_is_init = true;
145
146 m_session = dwfl_begin(&m_callback);
147 // TODO: call dwfl_end()
148 if (!m_session)
149 return;
150
151 dwfl_report_begin(m_session);
152 dwfl_linux_proc_report(m_session, getpid());
153 dwfl_report_end(m_session, nullptr, nullptr);
154 }
155};
156
157#else
158
159// Empty implementation if libdw is not found.
161{
162 public:
163
164 DebugSourceInfo getInfo(unw_word_t func_address)
165 {
166 return {};
167 }
168};
169
170#endif
171
172/*---------------------------------------------------------------------------*/
173/*---------------------------------------------------------------------------*/
174
178class LibUnwindStackTraceService
179: public TraceAccessor
180, public IStackTraceService
181{
182 private:
183
185 struct ProcInfo
186 {
187 public:
188
189 ProcInfo() = default;
190 explicit ProcInfo(const String& aname)
191 : m_name(aname)
192 {}
193
194 public:
195
197 const String& name() const { return m_name; }
199 const char* libraryFileName() const { return m_library_file_name; }
200
201 public:
202
206 const char* m_library_file_name = nullptr;
208 //TODO: do not store this for every function.
209 unw_word_t m_file_loaded_address = 0;
210 unw_word_t m_base_ip = 0;
211 DebugSourceInfo m_source_info;
212 };
213
214 public:
215
216 explicit LibUnwindStackTraceService(const ServiceBuildInfo& sbi)
217 : TraceAccessor(sbi.application()->traceMng())
218 {
219 }
221 : TraceAccessor(tm)
222 {
223 }
224
225 public:
226
227 void build() override
228 {
229 if (!platform::getEnvironmentVariable("ARCANE_GDB_STACK").null())
230 m_want_gdb_info = true;
231 if (!platform::getEnvironmentVariable("ARCANE_USE_BACKTRACE").null())
232 m_use_backtrace = true;
233 }
234
235 public:
236
238 StackTrace stackTrace(int first_function) override;
239 StackTrace stackTraceFunction(int function_index) override;
240
241 private:
242
243 using ProcInfoMap = std::map<unw_word_t, ProcInfo>;
244 ProcInfoMap m_proc_name_map;
245 std::mutex m_proc_name_map_mutex;
246
247 bool m_want_gdb_info = false;
248 bool m_use_backtrace = false;
249 DWHandler m_dw_handler;
250 ProcInfo _getFuncInfo(unw_word_t ip, unw_cursor_t* cursor);
251 ProcInfo _getFuncInfo(const void* ip);
252 String _getGDBStack();
253 StackTrace _backtraceStackTrace(const FixedStackFrameArray& stack_frames);
254 String _generateFileAndOffset(const FixedStackFrameArray& stack_frames);
255 FixedStackFrameArray _backtraceStackFrame(int first_function);
256};
257
258/*---------------------------------------------------------------------------*/
259/*---------------------------------------------------------------------------*/
260
261LibUnwindStackTraceService::ProcInfo LibUnwindStackTraceService::
262_getFuncInfo(unw_word_t ip, unw_cursor_t* cursor)
263{
264 {
265 std::lock_guard<std::mutex> lk(m_proc_name_map_mutex);
266 auto v = m_proc_name_map.find(ip);
267 if (v != m_proc_name_map.end())
268 return v->second;
269 }
270
271 unw_word_t offset;
272 char func_name_buf[10000];
273 char demangled_func_name_buf[10000];
274 unw_get_proc_name(cursor, func_name_buf, 10000, &offset);
275 int dstatus = 0;
276 size_t len = 10000;
277 char* buf = abi::__cxa_demangle(func_name_buf, demangled_func_name_buf, &len, &dstatus);
278 ProcInfo pi;
279 pi.m_base_ip = offset;
280 {
281 Dl_info dl_info;
282 void* addr = (void*)ip;
283 int r2 = dladdr(addr, &dl_info);
284 if (r2 != 0) {
285 const char* dli_fname = dl_info.dli_fname;
286 // Base address of the file being loaded.
287 void* dli_fbase = dl_info.dli_fbase;
288 pi.m_library_file_name = dli_fname;
289 pi.m_file_loaded_address = (unw_word_t)dli_fbase;
290 }
291 }
292
293 if (buf)
294 pi.m_name = std::string_view(buf);
295 else
296 pi.m_name = std::string_view(func_name_buf);
297
298 // We must use 'ip-1' because the address returned by libunwind is that
299 // of the return instruction.
300 pi.m_source_info = m_dw_handler.getInfo(ip - 1);
301
302 {
303 std::lock_guard<std::mutex> lk(m_proc_name_map_mutex);
304 m_proc_name_map.insert(ProcInfoMap::value_type(ip, pi));
305 }
306
307 return pi;
308}
309
310/*---------------------------------------------------------------------------*/
311/*---------------------------------------------------------------------------*/
312
317LibUnwindStackTraceService::ProcInfo LibUnwindStackTraceService::
318_getFuncInfo(const void* addr)
319{
320 {
321 std::lock_guard<std::mutex> lk(m_proc_name_map_mutex);
322 auto v = m_proc_name_map.find((unw_word_t)addr);
323 if (v != m_proc_name_map.end()) {
324 return v->second;
325 }
326 }
327 const size_t buf_size = 10000;
328 char demangled_func_name_buf[buf_size];
329 Dl_info dl_info;
330 int r = dladdr(addr, &dl_info);
331 if (r == 0) {
332 // Error in dladdr.
333 std::cout << "ERROR in dladdr\n";
334 return ProcInfo("Unknown");
335 }
336 const char* dli_sname = dl_info.dli_sname;
337 if (!dli_sname)
338 dli_sname = "";
339 int dstatus = 0;
340 size_t len = buf_size;
341 char* buf = abi::__cxa_demangle(dli_sname, demangled_func_name_buf, &len, &dstatus);
342 //char* buf = (char*)dli_sname;
343 ProcInfo pi;
344 if (buf)
345 pi.m_name = std::string_view(buf);
346 else
347 pi.m_name = std::string_view(dli_sname);
348 {
349 std::lock_guard<std::mutex> lk(m_proc_name_map_mutex);
350 m_proc_name_map.insert(ProcInfoMap::value_type((unw_word_t)addr, pi));
351 }
352 return pi;
353}
354
355/*---------------------------------------------------------------------------*/
356/*---------------------------------------------------------------------------*/
357
358String LibUnwindStackTraceService::
359_getGDBStack()
360{
361 void* array[256];
362 char** names;
363 int i, size;
364
365 fprintf(stderr, "\nNative stacktrace:\n\n");
366
367 size = backtrace(array, 256);
368 names = backtrace_symbols(array, size);
369 for (i = 0; i < size; ++i) {
370 fprintf(stderr, "\t%s\n", names[i]);
371 }
372
373 fflush(stderr);
374 return platform::getGDBStack();
375}
376
377/*---------------------------------------------------------------------------*/
378/*---------------------------------------------------------------------------*/
379
382stackTrace(int first_function)
383{
384 unw_cursor_t cursor;
385 unw_context_t uc;
386
387 String last_str;
388 if (m_want_gdb_info)
389 last_str = _getGDBStack();
390 FixedStackFrameArray backtrace_stack_frames = _backtraceStackFrame(first_function);
391 if (m_use_backtrace)
392 return _backtraceStackTrace(backtrace_stack_frames);
393
394 const size_t hexa_buf_size = 100;
395 char hexa[hexa_buf_size + 1];
396
397 unw_getcontext(&uc);
398 unw_init_local(&cursor, &uc);
399 int current_func = 0;
400 StringBuilder message;
401
402 FixedStackFrameArray stack_frames;
403 while (unw_step(&cursor) > 0) {
404 unw_word_t ip;
405 unw_get_reg(&cursor, UNW_REG_IP, &ip);
406 if (current_func >= first_function) {
407 ProcInfo pi = _getFuncInfo(ip, &cursor);
408 String func_name = pi.m_name;
409 message += " ";
410 snprintf(hexa, hexa_buf_size, "%14llx", (long long)ip);
411 message += hexa;
412 message += " ";
413 message += func_name;
414 if (pi.m_source_info.m_source_file) {
415 message += " \"";
416 message += pi.m_source_info.m_source_file;
417 if (pi.m_source_info.m_line > 0) {
418 message += ":";
419 message += pi.m_source_info.m_line;
420 }
421 message += "\"";
422 }
423 message += "\n";
424
425 stack_frames.addFrame(StackFrame((intptr_t)ip));
426 }
427 ++current_func;
428 }
430
431 if (ss) {
432 // It is better to read the call stack from _backtrace() because
433 // libunwind returns the return address for each function
434 // which causes a shift in the source code line info
435 // (it points to the line after the one being executed).
436 last_str = ss->stackTrace(backtrace_stack_frames.view());
437 }
438 else {
439 message += "\nFileAndOffsetStack:{{\n";
440 message += _generateFileAndOffset(backtrace_stack_frames);
441 message += "}}\n";
442 }
443 message += last_str;
444 return StackTrace(stack_frames, message);
445}
446
447/*---------------------------------------------------------------------------*/
448/*---------------------------------------------------------------------------*/
449
456stackTraceFunction(int function_index)
457{
458 unw_cursor_t cursor;
459 unw_context_t uc;
460 unw_word_t ip;
461 //unw_word_t offset;
462
463 String last_str;
464 if (m_want_gdb_info)
465 last_str = _getGDBStack();
466
467 unw_getcontext(&uc);
468 unw_init_local(&cursor, &uc);
469 int current_func = 0;
470 StringBuilder message;
471
472 while (unw_step(&cursor) > 0) {
473 unw_get_reg(&cursor, UNW_REG_IP, &ip);
474 if (current_func == function_index) {
475 ProcInfo pi = _getFuncInfo(ip, &cursor);
476 String func_name = pi.m_name;
477 message += func_name;
478 break;
479 }
480 ++current_func;
481 }
482 return StackTrace(message.toString() + last_str);
483}
484
485/*---------------------------------------------------------------------------*/
486/*---------------------------------------------------------------------------*/
487
490_backtraceStackFrame(int first_function)
491{
492 void* ips[256];
493 Integer size = backtrace(ips, 256);
494
495 FixedStackFrameArray stack_frames;
496 for (Integer i = first_function; i < size; ++i) {
497 stack_frames.addFrame(StackFrame((intptr_t)ips[i]));
498 }
499 return stack_frames;
500}
501
502/*---------------------------------------------------------------------------*/
503/*---------------------------------------------------------------------------*/
504
508{
509 const size_t buf_size = 100;
510 char hexa[buf_size + 1];
511
512 StringBuilder message;
513 ConstArrayView<StackFrame> frames_view = stack_frames.view();
514 for (StackFrame f : frames_view) {
515 intptr_t ip = f.address();
516 ProcInfo pinfo = _getFuncInfo((void*)ip);
517 String func_name = pinfo.name();
518 message += " ";
519 snprintf(hexa, buf_size, "%10llx", (long long)ip);
520 message += hexa;
521 message += " ";
522 message += func_name;
523 message += "\n";
524 }
525 return StackTrace(stack_frames, message);
526}
527
528/*---------------------------------------------------------------------------*/
529/*---------------------------------------------------------------------------*/
530
543{
544 const size_t buf_size = 100;
545 char hexa[buf_size + 1];
546
547 StringBuilder message;
548 ConstArrayView<StackFrame> frames_view = stack_frames.view();
549 for (StackFrame f : frames_view) {
550 intptr_t ip = f.address();
551 ProcInfo pinfo = _getFuncInfo(reinterpret_cast<const void*>(ip));
552 message += (pinfo.libraryFileName() ? pinfo.libraryFileName() : "()");
553 message += " ";
554 auto file_base_address = pinfo.m_file_loaded_address;
555 // On Linux (CentOS 6 and 7), the address 0x400000 corresponds to that
556 // of the executable load, but if we subtract this address from that
557 // of the function then the symbolizer does not work (whether it is
558 // llvm-symbolize or addr2line)
559 if (file_base_address == 0x400000)
560 file_base_address = 0;
561 intptr_t offset_ip = (ip - file_base_address);
562 snprintf(hexa, buf_size, "%llx", (long long)offset_ip);
563 message += "0x";
564 message += hexa;
565 message += "\n";
566 }
567 return message;
568}
569
570/*---------------------------------------------------------------------------*/
571/*---------------------------------------------------------------------------*/
572
573/*---------------------------------------------------------------------------*/
574/*---------------------------------------------------------------------------*/
575
576class LLVMSymbolizerService
577: public TraceAccessor
578, public ISymbolizerService
579{
580 public:
581
582 explicit LLVMSymbolizerService(const ServiceBuildInfo& sbi)
584 {
585 _init();
586 }
587 explicit LLVMSymbolizerService(ITraceMng* tm)
588 : TraceAccessor(tm)
589 {
590 _init();
591 }
592
593 public:
594
595 void build() {}
596
597 public:
598
600
601 private:
602
603 String m_llvm_symbolizer_path;
604 bool m_is_check_done = false;
605 bool m_is_valid = false;
606
607 private:
608
611 {
612 if (m_is_check_done)
613 return;
614 // Before calling this method, m_llvm_symbolizer_path must contain
615 // the name of the directory where llvm-symbolizer is located.
616 Directory dir(m_llvm_symbolizer_path);
617 String fullpath = dir.file("llvm-symbolizer");
618 Int64 length = platform::getFileLength(fullpath);
619 m_llvm_symbolizer_path = fullpath;
620 if (length > 0)
621 m_is_valid = true;
622 m_is_check_done = true;
623 }
624 void _init()
625 {
626 m_llvm_symbolizer_path = platform::getEnvironmentVariable("ARCANE_LLVMSYMBOLIZER_PATH");
627 }
628};
629
630/*---------------------------------------------------------------------------*/
631/*---------------------------------------------------------------------------*/
632
635{
636 _checkValid();
637 if (!m_is_valid)
638 return String();
639 std::stringstream ostr;
640 // NOTE: the code below is similar to
641 // LibUnwindStackTraceService::_generateFileAndOffset(). It should
642 // merge the two.
643 for (Integer i = 0, n = frames.size(); i < n; ++i) {
644 Dl_info dl_info;
645 intptr_t addr = frames[i].address();
646 int r2 = dladdr((void*)addr, &dl_info);
647 const char* dli_fname = nullptr;
648 intptr_t base_address = 0;
649 if (r2 != 0) {
650 dli_fname = dl_info.dli_fname;
651 // File load base address.
652 void* dli_fbase = dl_info.dli_fbase;
653 intptr_t true_base = reinterpret_cast<intptr_t>(dli_fbase);
654 // On Linux (CentOS 6 and 7), the address 0x400000 corresponds to that
655 // of the executable load, but if we subtract this address from that
656 // of the function then the symbolizer does not work (whether it is
657 // llvm-symbolize or addr2line)
658 if (true_base == 0x400000)
659 true_base = 0;
660 base_address = addr - true_base;
661 }
662 // TODO: write base_address in hex to be able to read it back with addr2line
663 ostr << (dli_fname ? dli_fname : "??") << " " << base_address << '\n';
664 }
665
666 std::string input_str(ostr.str());
667 String output_str;
668 ProcessExecArgs args;
669 args.setCommand(m_llvm_symbolizer_path);
670 Integer input_size = arcaneCheckArraySize(input_str.length());
671 ByteConstArrayView input_bytes(input_size, reinterpret_cast<const Byte*>(input_str.c_str()));
672 args.setInputBytes(input_bytes);
673 args.addArguments("--demangle");
674 args.addArguments("--pretty-print");
675 //args.addArguments("--print-source-context-lines=5");
676 Process::execute(args);
677 return String(args.outputBytes());
678}
679
680/*---------------------------------------------------------------------------*/
681/*---------------------------------------------------------------------------*/
682
684 ServiceProperty("LibUnwind", ST_Application),
686
688 ServiceProperty("LLVMSymbolizer", ST_Application),
690
691ARCANE_DI_REGISTER_PROVIDER(LibUnwindStackTraceService,
692 DependencyInjection::ProviderProperty("LibUnwind"),
693 ARCANE_DI_INTERFACES(IStackTraceService),
694 ARCANE_DI_CONSTRUCTOR(ITraceMng*));
695
696ARCANE_DI_REGISTER_PROVIDER(LLVMSymbolizerService,
697 DependencyInjection::ProviderProperty("LLVMSymbolizer"),
698 ARCANE_DI_INTERFACES(ISymbolizerService),
699 ARCANE_DI_CONSTRUCTOR(ITraceMng*));
700
701/*---------------------------------------------------------------------------*/
702/*---------------------------------------------------------------------------*/
703
704} // End namespace Arcane
705
706/*---------------------------------------------------------------------------*/
707/*---------------------------------------------------------------------------*/
This file contains the various service factories and macros for registering services.
#define ARCANE_SERVICE_INTERFACE(ainterface)
Macro to declare an interface when registering a service.
Constant view of an array of type T.
constexpr Integer size() const noexcept
Number of elements in the array.
Class managing a directory.
Definition Directory.h:36
String file(const String &file_name) const override
Returns the full path of the file file_name in the directory.
Definition Directory.cc:120
Stores a fixed maximum size list of StackFrame.
void addFrame(const StackFrame &frame)
Adds a frame to the list of frames. If nbFrame() is greater than or equal to MAX_FRAME,...
virtual ITraceMng * traceMng() const =0
Trace manager.
Interface of a function call tracing service.
Interface of a source code symbol retrieval service.
virtual String stackTrace(ConstArrayView< StackFrame > frames)=0
Information for the call stack frames.
String stackTrace(ConstArrayView< StackFrame > frames) override
Information for the call stack frames.
void _checkValid()
Checks that the specified path is valid.
Function call trace service using libunwind.
StackTrace _backtraceStackTrace(const FixedStackFrameArray &stack_frames)
Call stack via the backtrace function.
FixedStackFrameArray _backtraceStackFrame(int first_function)
Call stack via the backtrace function.
String _generateFileAndOffset(const FixedStackFrameArray &stack_frames)
Generates a list of file names and offsets for a call stack.
StackTrace stackTrace(int first_function) override
Character string indicating the call stack.
StackTrace stackTraceFunction(int function_index) override
Returns the current call stack.
ConstArrayView< Byte > outputBytes() const
Contains the result of the process's standard output (STDOUT).
Definition Process.h:64
static ProcessExecArgs::ExecStatus execute(ProcessExecArgs &args)
Executes a process whose information is contained in args.
Definition Process.cc:40
IApplication * application() const
Access to the associated IApplication.
Structure containing the information to create a service.
Service creation properties.
Stores the addresses corresponding to a call stack. This class is internal and should not be used out...
Information about function call stacks.
Unicode character string constructor.
String toString() const
Returns the constructed character string.
TraceAccessor(ITraceMng *m)
Constructs an accessor via the trace manager m.
TraceMessage pinfo() const
Flow for a parallel information message.
ITraceMng * traceMng() const
Trace manager.
#define ARCANE_REGISTER_SERVICE(aclass, a_service_property,...)
Macro for registering a service.
Integer len(const char *s)
Returns the length of the string s.
long unsigned int getFileLength(const String &filename)
Length of the file filename. If the file is not readable or does not exist, returns 0.
ISymbolizerService * getSymbolizerService()
Service used to obtain information about source code symbols.
String getGDBStack()
Retrieves the call stack via gdb.
String getEnvironmentVariable(const String &name)
Environment variable named name.
-- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature --
Integer arcaneCheckArraySize(unsigned long long size)
Checks that size can be converted into an 'Integer' to serve as the size of an array....
std::int64_t Int64
Signed integer type of 64 bits.
Int32 Integer
Type representing an integer.
@ ST_Application
The service is used at the application level.
ConstArrayView< Byte > ByteConstArrayView
C equivalent of a 1D array of characters.
Definition UtilsTypes.h:476
unsigned char Byte
Type of a byte.
Definition BaseTypes.h:43
const char * m_library_file_name
Name of the library (.so or .exe) where the method is located.
const char * libraryFileName() const
Name of the library containing the function. May be null.