Arcane  v3.16.10.0
Documentation développeur
Chargement...
Recherche...
Aucune correspondance
LibUnwindStackTraceService.cc
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2025 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/* Service de trace des appels de fonctions utilisant '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/Process.h"
20#include "arcane/utils/ISymbolizerService.h"
21
22#include "arcane/core/ServiceBuilder.h"
23#include "arcane/core/Directory.h"
24
26#include "arcane/core/AbstractService.h"
27
28#include "arcane_packages.h"
29
30#define UNW_LOCAL_ONLY
31#include <libunwind.h>
32#include <cxxabi.h>
33
34#include <map>
35#include <mutex>
36
37#include <execinfo.h>
38#include <cstdio>
39#include <sys/types.h>
40#include <unistd.h>
41//#include <cstdlib>
42#include <dlfcn.h>
43
44#if defined(ARCANE_HAS_PACKAGE_DW)
45#include <elfutils/libdwfl.h>
46#endif
47
48/*---------------------------------------------------------------------------*/
49/*---------------------------------------------------------------------------*/
50
51namespace Arcane
52{
53
55{
56 int m_line = 0;
57 int m_column = 0;
58 const char* m_source_file = nullptr;
59};
60
61#if defined(ARCANE_HAS_PACKAGE_DW)
74class DWHandler
75{
76 public:
77
78 DWHandler()
79 {
80 m_callback.find_elf = &dwfl_linux_proc_find_elf;
81 m_callback.find_debuginfo = &dwfl_standard_find_debuginfo;
82 m_callback.section_address = nullptr;
83 m_callback.debuginfo_path = nullptr;
84 }
85
86 ~DWHandler()
87 {
88 if (m_session)
89 dwfl_end(m_session);
90 }
91
97 DebugSourceInfo getInfo(unw_word_t func_address)
98 {
99 std::scoped_lock<std::mutex> lock(m_mutex);
100 DebugSourceInfo source_info;
101
102 _init();
103
104 auto dw_address = reinterpret_cast<Dwarf_Addr>(func_address);
105
106 Dwfl_Module* module = dwfl_addrmodule(m_session, dw_address);
107 if (!module)
108 return source_info;
109 Dwarf_Addr bias = 0;
110 Dwarf_Die* dw_die = dwfl_module_addrdie(module, dw_address, &bias);
111 if (dw_die)
112 return source_info;
113
114 Dwarf_Line* dw_source_info = dwarf_getsrc_die(dw_die, dw_address - bias);
115
116 if (dw_source_info) {
117 int line = 0;
118 int column = 0;
119 // ATTENTION : le pointeur retourné n'est plus valide si m_session est détruit.
120 const char* source_file = dwarf_linesrc(dw_source_info, nullptr, nullptr);
121 dwarf_lineno(dw_source_info, &line);
122 dwarf_linecol(dw_source_info, &column);
123 source_info = { line, column, source_file };
124 }
125
126 return source_info;
127 }
128
129 public:
130
131 bool m_is_init = false;
132 Dwfl_Callbacks m_callback;
133 Dwfl* m_session = nullptr;
134 std::mutex m_mutex;
135
136 private:
137
138 void _init()
139 {
140 if (m_is_init)
141 return;
142 m_is_init = true;
143
144 m_session = dwfl_begin(&m_callback);
145 // TODO: faire dwfl_end()
146 if (!m_session)
147 return;
148
149 dwfl_report_begin(m_session);
150 dwfl_linux_proc_report(m_session, getpid());
151 dwfl_report_end(m_session, nullptr, nullptr);
152 }
153};
154
155#else
156
157// Implémentation vide si libdw n'est pas trouvé.
159{
160 public:
161
162 DebugSourceInfo getInfo(unw_word_t func_address)
163 {
164 return {};
165 }
166};
167
168#endif
169
170/*---------------------------------------------------------------------------*/
171/*---------------------------------------------------------------------------*/
175class LibUnwindStackTraceService
176: public AbstractService
177, public IStackTraceService
178{
179 private:
180
182 struct ProcInfo
183 {
184 public:
185
186 ProcInfo() = default;
187 explicit ProcInfo(const String& aname)
188 : m_name(aname)
189 {}
190
191 public:
192
194 const String& name() const { return m_name; }
196 const char* libraryFileName() const { return m_library_file_name; }
197
198 public:
199
203 const char* m_library_file_name = nullptr;
205 //TODO: ne pas stocker cela pour chaque fonction.
206 unw_word_t m_file_loaded_address = 0;
207 unw_word_t m_base_ip = 0;
208 DebugSourceInfo m_source_info;
209 };
210
211 public:
212
213 explicit LibUnwindStackTraceService(const ServiceBuildInfo& sbi)
214 : AbstractService(sbi), m_want_gdb_info(false), m_use_backtrace(false)
215 {
216 m_application = sbi.application();
217 }
218
219 public:
220
221 void build() override
222 {
223 if (!platform::getEnvironmentVariable("ARCANE_GDB_STACK").null())
224 m_want_gdb_info = true;
225 if (!platform::getEnvironmentVariable("ARCANE_USE_BACKTRACE").null())
226 m_use_backtrace = true;
227 }
228
229 public:
230
232 StackTrace stackTrace(int first_function) override;
233 StackTrace stackTraceFunction(int function_index) override;
234
235 private:
236
237 using ProcInfoMap = std::map<unw_word_t,ProcInfo>;
238 ProcInfoMap m_proc_name_map;
239 std::mutex m_proc_name_map_mutex;
240
241 bool m_want_gdb_info = false;
242 bool m_use_backtrace = false;
243 IApplication* m_application = nullptr; //A SUPPRIMER
244 DWHandler m_dw_handler;
245 ProcInfo _getFuncInfo(unw_word_t ip,unw_cursor_t* cursor);
246 ProcInfo _getFuncInfo(const void* ip);
247 String _getGDBStack();
250 FixedStackFrameArray _backtraceStackFrame(int first_function);
251};
252
253/*---------------------------------------------------------------------------*/
254/*---------------------------------------------------------------------------*/
255
256LibUnwindStackTraceService::ProcInfo LibUnwindStackTraceService::
257_getFuncInfo(unw_word_t ip,unw_cursor_t* cursor)
258{
259 {
260 std::lock_guard<std::mutex> lk(m_proc_name_map_mutex);
261 auto v = m_proc_name_map.find(ip);
262 if (v!=m_proc_name_map.end())
263 return v->second;
264 }
265
266 unw_word_t offset;
267 char func_name_buf[10000];
268 char demangled_func_name_buf[10000];
269 unw_get_proc_name(cursor,func_name_buf,10000,&offset);
270 int dstatus = 0;
271 size_t len = 10000;
272 char* buf = abi::__cxa_demangle (func_name_buf,demangled_func_name_buf,&len,&dstatus);
273 ProcInfo pi;
274 pi.m_base_ip = offset;
275 {
276 Dl_info dl_info;
277 void* addr = (void*)ip;
278 int r2 = dladdr(addr,&dl_info);
279 if (r2!=0){
280 const char* dli_fname = dl_info.dli_fname;
281 // Adresse de base de chargement du fichier.
282 void* dli_fbase = dl_info.dli_fbase;
283 pi.m_library_file_name = dli_fname;
284 pi.m_file_loaded_address = (unw_word_t)dli_fbase;
285 }
286 }
287
288 if (buf)
289 pi.m_name = std::string_view(buf);
290 else
291 pi.m_name = std::string_view(func_name_buf);
292
293 // Il faut faire 'ip-1' car l'adresse retournée par libunwind est celle
294 // de l'instruction de retour.
295 pi.m_source_info = m_dw_handler.getInfo(ip - 1);
296
297 {
298 std::lock_guard<std::mutex> lk(m_proc_name_map_mutex);
299 m_proc_name_map.insert(ProcInfoMap::value_type(ip, pi));
300 }
301
302 return pi;
303}
304
305/*---------------------------------------------------------------------------*/
306/*---------------------------------------------------------------------------*/
311LibUnwindStackTraceService::ProcInfo LibUnwindStackTraceService::
312_getFuncInfo(const void* addr)
313{
314 {
315 std::lock_guard<std::mutex> lk(m_proc_name_map_mutex);
316 auto v = m_proc_name_map.find((unw_word_t)addr);
317 if (v!=m_proc_name_map.end()){
318 return v->second;
319 }
320 }
321 const size_t buf_size = 10000;
322 char demangled_func_name_buf[buf_size];
323 Dl_info dl_info;
324 int r = dladdr(addr, &dl_info);
325 if (r==0){
326 // Erreur dans dladdr.
327 std::cout << "ERROR in dladdr\n";
328 return ProcInfo("Unknown");
329 }
330 const char* dli_sname = dl_info.dli_sname;
331 if (!dli_sname)
332 dli_sname = "";
333 int dstatus = 0;
334 size_t len = buf_size;
335 char* buf = abi::__cxa_demangle (dli_sname,demangled_func_name_buf,&len,&dstatus);
336 //char* buf = (char*)dli_sname;
337 ProcInfo pi;
338 if (buf)
339 pi.m_name = std::string_view(buf);
340 else
341 pi.m_name = std::string_view(dli_sname);
342 {
343 std::lock_guard<std::mutex> lk(m_proc_name_map_mutex);
344 m_proc_name_map.insert(ProcInfoMap::value_type((unw_word_t)addr,pi));
345 }
346 return pi;
347}
348
349/*---------------------------------------------------------------------------*/
350/*---------------------------------------------------------------------------*/
351
352String LibUnwindStackTraceService::
353_getGDBStack()
354{
355 void *array [256];
356 char **names;
357 int i, size;
358
359 fprintf (stderr, "\nNative stacktrace:\n\n");
360
361 size = backtrace (array, 256);
362 names = backtrace_symbols (array, size);
363 for (i =0; i < size; ++i) {
364 fprintf (stderr, "\t%s\n", names [i]);
365 }
366
367 fflush (stderr);
368 return platform::getGDBStack();
369}
370
371/*---------------------------------------------------------------------------*/
372/*---------------------------------------------------------------------------*/
373
376stackTrace(int first_function)
377{
378 unw_cursor_t cursor;
379 unw_context_t uc;
380
381 String last_str;
382 if (m_want_gdb_info)
383 last_str = _getGDBStack();
384 FixedStackFrameArray backtrace_stack_frames = _backtraceStackFrame(first_function);
385 if (m_use_backtrace)
386 return _backtraceStackTrace(backtrace_stack_frames);
387
388 const size_t hexa_buf_size = 100;
389 char hexa[hexa_buf_size+1];
390
391 unw_getcontext(&uc);
392 unw_init_local(&cursor, &uc);
393 int current_func = 0;
394 StringBuilder message;
395
396 FixedStackFrameArray stack_frames;
397 while (unw_step(&cursor) > 0) {
398 unw_word_t ip;
399 unw_get_reg(&cursor, UNW_REG_IP, &ip);
400 if (current_func>=first_function){
401 ProcInfo pi = _getFuncInfo(ip,&cursor);
402 String func_name = pi.m_name;
403 message += " ";
404 snprintf(hexa,hexa_buf_size,"%14llx",(long long)ip);
405 message += hexa;
406 message += " ";
407 message += func_name;
408 if (pi.m_source_info.m_source_file) {
409 message += " \"";
410 message += pi.m_source_info.m_source_file;
411 if (pi.m_source_info.m_line > 0) {
412 message += ":";
413 message += pi.m_source_info.m_line;
414 }
415 message += "\"";
416 }
417 message += "\n";
418
419 stack_frames.addFrame(StackFrame((intptr_t)ip));
420 }
421 ++current_func;
422 }
424
425 if (ss){
426 // Il faut mieux lire la pile d'appel de _backtrace() car
427 // la libunwind retourne l'adresse de retour pour chaque fonction
428 // ce qui provoque un décalage dans les infos des lignes du code
429 // source (on pointe vers la ligne après celle qu'on est en train d'exécuter).
430 last_str = ss->stackTrace(backtrace_stack_frames.view());
431 }
432 else{
433 message += "\nFileAndOffsetStack:{{\n";
434 message += _generateFileAndOffset(backtrace_stack_frames);
435 message += "}}\n";
436 }
437 message += last_str;
438 return StackTrace(stack_frames,message);
439}
440
441/*---------------------------------------------------------------------------*/
442/*---------------------------------------------------------------------------*/
449stackTraceFunction(int function_index)
450{
451 unw_cursor_t cursor;
452 unw_context_t uc;
453 unw_word_t ip;
454 //unw_word_t offset;
455
456 String last_str;
457 if (m_want_gdb_info)
458 last_str = _getGDBStack();
459
460 unw_getcontext(&uc);
461 unw_init_local(&cursor, &uc);
462 int current_func = 0;
463 StringBuilder message;
464
465 while (unw_step(&cursor) > 0) {
466 unw_get_reg(&cursor, UNW_REG_IP, &ip);
467 if (current_func==function_index){
468 ProcInfo pi = _getFuncInfo(ip,&cursor);
469 String func_name = pi.m_name;
470 message += func_name;
471 break;
472 }
473 ++current_func;
474 }
475 return StackTrace(message.toString()+last_str);
476}
477
478/*---------------------------------------------------------------------------*/
479/*---------------------------------------------------------------------------*/
480
483_backtraceStackFrame(int first_function)
484{
485 void *ips [256];
486 Integer size = backtrace (ips, 256);
487
488 FixedStackFrameArray stack_frames;
489 for( Integer i=first_function; i<size; ++i ){
490 stack_frames.addFrame(StackFrame((intptr_t)ips[i]));
491 }
492 return stack_frames;
493}
494
495/*---------------------------------------------------------------------------*/
496/*---------------------------------------------------------------------------*/
497
501{
502 const size_t buf_size = 100;
503 char hexa[buf_size+1];
504
505 StringBuilder message;
506 ConstArrayView<StackFrame> frames_view = stack_frames.view();
507 for( StackFrame f : frames_view ){
508 intptr_t ip = f.address();
509 ProcInfo pinfo = _getFuncInfo((void*)ip);
510 String func_name = pinfo.name();
511 message += " ";
512 snprintf(hexa,buf_size,"%10llx",(long long)ip);
513 message += hexa;
514 message += " ";
515 message += func_name;
516 message += "\n";
517 }
518 return StackTrace(stack_frames,message);
519}
520
521/*---------------------------------------------------------------------------*/
522/*---------------------------------------------------------------------------*/
535{
536 const size_t buf_size = 100;
537 char hexa[buf_size+1];
538
539 StringBuilder message;
540 ConstArrayView<StackFrame> frames_view = stack_frames.view();
541 for( StackFrame f : frames_view ){
542 intptr_t ip = f.address();
543 ProcInfo pinfo = _getFuncInfo(reinterpret_cast<const void*>(ip));
544 message += (pinfo.libraryFileName() ? pinfo.libraryFileName() : "()");
545 message += " ";
546 auto file_base_address = pinfo.m_file_loaded_address;
547 // Sous Linux (CentOS 6 et 7), l'adresse 0x400000 correspond à celle
548 // de chargement de l'exécutable mais si on retranche cette adresse de celle
549 // de la fonction alors le symboliser ne fonctionne pas (que ce soit
550 // llvm-symbolize ou addr2line)
551 if (file_base_address==0x400000)
552 file_base_address = 0;
553 intptr_t offset_ip = (ip - file_base_address);
554 snprintf(hexa,buf_size,"%llx",(long long)offset_ip);
555 message += "0x";
556 message += hexa;
557 message += "\n";
558 }
559 return message;
560}
561
562/*---------------------------------------------------------------------------*/
563/*---------------------------------------------------------------------------*/
564
565/*---------------------------------------------------------------------------*/
566/*---------------------------------------------------------------------------*/
567
568class LLVMSymbolizerService
569: public AbstractService
570, public ISymbolizerService
571{
572 public:
573
574 explicit LLVMSymbolizerService(const ServiceBuildInfo& sbi)
575 : AbstractService(sbi)
576 , m_is_check_done(false)
577 , m_is_valid(false)
578 {}
579
580 public:
581
582 void build() override
583 {
584 m_llvm_symbolizer_path = platform::getEnvironmentVariable("ARCANE_LLVMSYMBOLIZER_PATH");
585 }
586
587 public:
588
590
591 private:
592
593 String m_llvm_symbolizer_path;
594 bool m_is_check_done = false;
595 bool m_is_valid = false;
596
597 private:
598
601 {
602 if (m_is_check_done)
603 return;
604 // Avant appel à cette méthode, m_llvm_symbolizer_path doit contenir
605 // le nom du répertoire dans lequel se trouve llvm-symbolizer.
606 Directory dir(m_llvm_symbolizer_path);
607 String fullpath = dir.file("llvm-symbolizer");
608 Int64 length = platform::getFileLength(fullpath);
609 m_llvm_symbolizer_path = fullpath;
610 if (length>0)
611 m_is_valid = true;
612 m_is_check_done = true;
613 }
614};
615
616/*---------------------------------------------------------------------------*/
617/*---------------------------------------------------------------------------*/
618
621{
622 _checkValid();
623 if (!m_is_valid)
624 return String();
625 std::stringstream ostr;
626 // NOTE: le code ci dessous est similaire à
627 // LibUnwindStackTraceService::_generateFileAndOffset(). Il faudrait
628 // fusionner les deux.
629 for( Integer i=0, n=frames.size(); i<n; ++i ){
630 Dl_info dl_info;
631 intptr_t addr = frames[i].address();
632 int r2 = dladdr((void*)addr,&dl_info);
633 const char* dli_fname = nullptr;
634 intptr_t base_address = 0;
635 if (r2!=0){
636 dli_fname = dl_info.dli_fname;
637 // Adresse de base de chargement du fichier.
638 void* dli_fbase = dl_info.dli_fbase;
639 intptr_t true_base = reinterpret_cast<intptr_t>(dli_fbase);
640 // Sous Linux (CentOS 6 et 7), l'adresse 0x400000 correspond à celle
641 // de chargement de l'exécutable, mais si on retranche cette adresse de celle
642 // de la fonction alors le symboliser ne fonctionne pas (que ce soit
643 // llvm-symbolize ou addr2line)
644 if (true_base==0x400000)
645 true_base = 0;
646 base_address = addr - true_base;
647 }
648 // TODO: écrire base_address en hexa pour pouvoir le relire avec addr2line
649 ostr << (dli_fname ? dli_fname : "??") << " " << base_address << '\n';
650 }
651
652 std::string input_str(ostr.str());
653 String output_str;
654 ProcessExecArgs args;
655 args.setCommand(m_llvm_symbolizer_path);
656 Integer input_size = arcaneCheckArraySize(input_str.length());
657 ByteConstArrayView input_bytes(input_size, reinterpret_cast<const Byte*>(input_str.c_str()));
658 args.setInputBytes(input_bytes);
659 args.addArguments("--demangle");
660 args.addArguments("--pretty-print");
661 //args.addArguments("--print-source-context-lines=5");
662 Process::execute(args);
663 return String(args.outputBytes());
664}
665
666/*---------------------------------------------------------------------------*/
667/*---------------------------------------------------------------------------*/
668
670 ServiceProperty("LibUnwind",ST_Application),
672
674 ServiceProperty("LLVMSymbolizer",ST_Application),
676
677/*---------------------------------------------------------------------------*/
678/*---------------------------------------------------------------------------*/
679
680} // End namespace Arcane
681
682/*---------------------------------------------------------------------------*/
683/*---------------------------------------------------------------------------*/
Ce fichier contient les différentes fabriques de services et macro pour enregistrer les services.
#define ARCANE_SERVICE_INTERFACE(ainterface)
Macro pour déclarer une interface lors de l'enregistrement d'un service.
AbstractService(const ServiceBuildInfo &)
Constructeur à partir d'un ServiceBuildInfo.
Vue constante d'un tableau de type T.
constexpr Integer size() const noexcept
Nombre d'éléments du tableau.
Classe gérant un répertoire.
Definition Directory.h:35
String file(const String &file_name) const override
Retourne le chemin complet du fichier file_name dans le répertoire.
Definition Directory.cc:120
Conserve une liste de taille fixe maximale de StackFrame.
void addFrame(const StackFrame &frame)
Ajoute frame à la liste des frames. Si nbFrame() est supérieur ou égal à MAX_FRAME,...
Interface de l'application.
Interface d'un service de trace des appels de fonctions.
Interface d'un service de récupération des symboles du code source.
virtual String stackTrace(ConstArrayView< StackFrame > frames)=0
Informations pour la pile d'appel frames.
String stackTrace(ConstArrayView< StackFrame > frames) override
Informations pour la pile d'appel frames.
void _checkValid()
Vérifie que le chemin spécifié est valid.
void build() override
Construction de niveau build du service.
Service de trace des appels de fonctions utilisant la libunwind.
StackTrace _backtraceStackTrace(const FixedStackFrameArray &stack_frames)
Pile d'appel via la fonction backtrace.
FixedStackFrameArray _backtraceStackFrame(int first_function)
Pile d'appel via la fonction backtrace.
String _generateFileAndOffset(const FixedStackFrameArray &stack_frames)
Génère liste des noms de fichier et offset d'une pile d'appel.
void build() override
Construction de niveau build du service.
StackTrace stackTrace(int first_function) override
Chaîne de caractère indiquant la pile d'appel.
StackTrace stackTraceFunction(int function_index) override
Retourne la pile à d'appel actuelle.
ByteConstArrayView outputBytes() const
Contient le résultat de la sortie standard (STDOUT) du processus.
Definition Process.h:60
static ProcessExecArgs::ExecStatus execute(ProcessExecArgs &args)
Exécute un processus dont les infos sont contenues dans args.
Definition Process.cc:43
IApplication * application() const
Accès à l'application IApplication associé.
Structure contenant les informations pour créer un service.
Propriétés de création d'un service.
Conserve les adresses correspondantes à une pile d'appel. Cette classe est interne et ne dois pas êtr...
Informations sur la pile d'appel des fonctions.
Constructeur de chaîne de caractère unicode.
String toString() const
Retourne la chaîne de caractères construite.
Chaîne de caractères unicode.
TraceMessage pinfo() const
Flot pour un message d'information en parallèle.
#define ARCANE_REGISTER_SERVICE(aclass, a_service_property,...)
Macro pour enregistrer un service.
Integer len(const char *s)
Retourne la longueur de la chaîne s.
ISymbolizerService * getSymbolizerService()
Service utilisé pour obtenir des informations sur les symboles du code source.
ARCCORE_BASE_EXPORT long unsigned int getFileLength(const String &filename)
Longueur du fichier filename. Si le fichier n'est pas lisible ou n'existe pas, retourne 0.
ARCCORE_BASE_EXPORT String getEnvironmentVariable(const String &name)
Variable d'environnement du nom name.
String getGDBStack()
Récupère la pile d'appel via gdb.
-*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
Integer arcaneCheckArraySize(unsigned long long size)
Vérifie que size peut être converti dans un 'Integer' pour servir de taille à un tableau....
std::int64_t Int64
Type entier signé sur 64 bits.
Int32 Integer
Type représentant un entier.
@ ST_Application
Le service s'utilise au niveau de l'application.
ConstArrayView< Byte > ByteConstArrayView
Equivalent C d'un tableau à une dimension de caractères.
Definition UtilsTypes.h:563
unsigned char Byte
Type d'un octet.
Definition BaseTypes.h:43
const char * m_library_file_name
Nom de la bibliothèque (.so ou .exe) dans laquelle se trouve la méthode.
unw_word_t m_file_loaded_address
Adresse de chargement de la bibliothèque.
String m_name
Nom (démanglé) de la procédure.
const char * libraryFileName() const
Nom de la bibliothèque contenant la fonction. Peut-être nul.
const String & name() const
Nom de la fonction.