LCOV - code coverage report
Current view: top level - libexcept - stack_trace.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 82 82 100.0 %
Date: 2022-06-30 20:42:18 Functions: 4 4 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2011-2022  Made to Order Software Corp.  All Rights Reserved
       2             : //
       3             : // https://snapwebsites.org/
       4             : // contact@m2osw.com
       5             : //
       6             : // This program is free software; you can redistribute it and/or modify
       7             : // it under the terms of the GNU General Public License as published by
       8             : // the Free Software Foundation; either version 2 of the License, or
       9             : // (at your option) any later version.
      10             : //
      11             : // This program is distributed in the hope that it will be useful,
      12             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             : // GNU General Public License for more details.
      15             : //
      16             : // You should have received a copy of the GNU General Public License
      17             : // along with this program; if not, write to the Free Software
      18             : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      19             : 
      20             : // self
      21             : //
      22             : #include    "./exception.h"
      23             : 
      24             : #include    "./demangle.h"
      25             : 
      26             : 
      27             : // C++ includes
      28             : //
      29             : #include    <iostream>
      30             : #include    <memory>
      31             : #include    <vector>
      32             : 
      33             : 
      34             : // C lib includes
      35             : //
      36             : #include    <execinfo.h>
      37             : #include    <link.h>
      38             : #include    <unistd.h>
      39             : 
      40             : 
      41             : /** \file
      42             :  * \brief Implementation of the stack trace.
      43             :  *
      44             :  * This file includes the functions that are used to gather the stack trace
      45             :  * from those very functions.
      46             :  *
      47             :  * By default, the stack trace is turned on in our exception library.
      48             :  * When turned on, we gather the information from the stack using
      49             :  * the functions defined in this file. This can be very helpful when
      50             :  * the code is able to gather the filenames and line numbers as it will
      51             :  * tell you exactly where each function failed.
      52             :  *
      53             :  * If you are running in release mode, it is likely that no line numbers
      54             :  * will be generated since they will be removed from your executables.
      55             :  */
      56             : 
      57             : 
      58             : 
      59             : namespace libexcept
      60             : {
      61             : 
      62             : 
      63             : 
      64             : /** \brief Collect the raw stack trace in a list of strings.
      65             :  *
      66             :  * This function collects the current stack as a trace to log later.
      67             :  *
      68             :  * By default, the stack trace shows you a number of lines equal
      69             :  * to STACK_TRACE_DEPTH (which is 20 at time of writing). You may
      70             :  * specify another number to get more or less lines. Note that a
      71             :  * really large number will generally show you the entire stack since
      72             :  * a number larger than the number of function pointers on the stack
      73             :  * will return the entire stack.
      74             :  *
      75             :  * If you pass 0 as \p stack_trace_depth then the function returns an
      76             :  * empty list of strings.
      77             :  *
      78             :  * The \p stack_trace_depth parameter is silently clamped to 1,000, which
      79             :  * in most cases will be more than enough to get the entire stack trace
      80             :  * available.
      81             :  *
      82             :  * \note
      83             :  * This function is global so we can use it anywhere we'd like to get
      84             :  * a stack trace and not just in exceptions. Very practical in C++
      85             :  * to get a stack trace directly in a list of strings.
      86             :  *
      87             :  * \attention
      88             :  * Use the collect_stack_with_line_numbers() to get demangled function
      89             :  * names and line numbers. Note that this other function is considered
      90             :  * \em very slow so do not use it in a standard exception. Consider
      91             :  * using that other function only when debugging.
      92             :  *
      93             :  * \param[in] stack_trace_depth  The number of lines to capture in our
      94             :  *                               stack trace.
      95             :  * \return The vector of strings with the stack trace.
      96             :  *
      97             :  * \sa collect_stack_trace_with_line_numbers()
      98             :  * \sa set_collect_stack()
      99             :  */
     100           8 : stack_trace_t collect_stack_trace(int stack_trace_depth)
     101             : {
     102           8 :     stack_trace_t stack_trace;
     103             : 
     104           8 :     if(stack_trace_depth > 0)
     105             :     {
     106          14 :         std::vector<void *> array;
     107           7 :         array.resize(std::max(stack_trace_depth, 1'000));
     108           7 :         int const size(backtrace(&array[0], stack_trace_depth));
     109             : 
     110             :         // save a copy of the system array in our class
     111             :         //
     112          14 :         std::unique_ptr<char *, decltype(&::free)> stack_string_list(backtrace_symbols(&array[0], size), &::free);
     113         116 :         for(int idx(0); idx < size; ++idx)
     114             :         {
     115         109 :             char const * stack_string(stack_string_list.get()[idx]);
     116         109 :             stack_trace.push_back(stack_string);
     117             :         }
     118             :     }
     119             : 
     120           8 :     return stack_trace;
     121             : }
     122             : 
     123             : 
     124             : /** \brief Collect the stack trace in a list of strings.
     125             :  *
     126             :  * This function collects the current stack as a trace including
     127             :  * the line numbers and demangled function names as available.
     128             :  *
     129             :  * The function also works like the collect_stack_trace() function.
     130             :  *
     131             :  * \note
     132             :  * The function makes use of the `addr2line` and `c++filt` command
     133             :  * line tools to convert the information. It is likely that if it
     134             :  * fails it means your system does not have those two tools installed.
     135             :  * Also, the addr2line requires the debug information in the libraries
     136             :  * and executables. Without that information, you will still get invalid
     137             :  * answers in your stacktrace.
     138             :  *
     139             :  * See also the libbacktrace library:
     140             :  * https://gcc.gnu.org/viewcvs/gcc/trunk/libbacktrace/
     141             :  *
     142             :  * \param[in] stack_trace_depth  The number of lines to capture in our
     143             :  *                               stack trace.
     144             :  * \return The vector of strings with the stack trace.
     145             :  *
     146             :  * \sa collect_stack_trace()
     147             :  * \sa set_collect_stack()
     148             :  */
     149           9 : stack_trace_t collect_stack_trace_with_line_numbers(int stack_trace_depth)
     150             : {
     151           9 :     stack_trace_t stack_trace;
     152             : 
     153           9 :     if(stack_trace_depth > 0)
     154             :     {
     155          18 :         std::vector<void *> array;
     156           9 :         array.resize(stack_trace_depth);
     157           9 :         int const size(backtrace(&array[0], stack_trace_depth));
     158             : 
     159             :         // save a copy of the system array in our class
     160             :         //
     161          18 :         std::unique_ptr<char *, decltype(&::free)> stack_string_list(backtrace_symbols(&array[0], size), &::free);
     162         124 :         for(int idx(0); idx < size; ++idx)
     163             :         {
     164         115 :             char const * raw_stack_string(stack_string_list.get()[idx]);
     165             : 
     166             :             // the raw stack string is expected to be composed of:
     167             :             //   <filename>(<function-name>+<offset>) [<addr>]
     168             :             //
     169             :             // we extract all those elements and use addr2line and c++filt
     170             :             // to convert that data to a usable function name and line number
     171             :             //
     172         230 :             std::string filename;
     173         230 :             std::string raw_function_name;
     174         230 :             std::string addr;
     175         230 :             std::string result;
     176             : 
     177             :             // go to end of filename
     178             :             //
     179         115 :             char const * s(raw_stack_string);
     180         115 :             char const * start(s);
     181        8363 :             while(*s != '('
     182        4239 :                && *s != '\0')
     183             :             {
     184        4124 :                 ++s;
     185             :             }
     186         115 :             if(*s == '(')
     187             :             {
     188         115 :                 filename = std::string(start, s - start);
     189         115 :                 ++s;
     190             : 
     191         115 :                 start = s;
     192        2575 :                 while(*s != '+'
     193        1230 :                    && *s != ')'
     194        2575 :                    && *s != '\0')
     195             :                 {
     196        1230 :                     ++s;
     197             :                 }
     198             : 
     199         115 :                 if(*s == '+')
     200             :                 {
     201         115 :                     raw_function_name = std::string(start, s - start);
     202         115 :                     ++s;
     203             : 
     204             :                     // skip the offset
     205             :                     //
     206        1695 :                     while(*s != ')'
     207         905 :                        && *s != '\0')
     208             :                     {
     209         790 :                         ++s;
     210             :                     }
     211             :                 }
     212             :                 //else if(*s == ')') {} -- no function name
     213             : 
     214         115 :                 if(*s == ')'
     215         115 :                 && s[1] == ' '
     216         115 :                 && s[2] == '['
     217         115 :                 && s[3] == '0'
     218         115 :                 && s[4] == 'x')
     219             :                 {
     220         115 :                     s += 5;
     221             : 
     222         115 :                     start = s;
     223        2875 :                     while(*s != ']'
     224        1495 :                        && *s != '\0')
     225             :                     {
     226        1380 :                         ++s;
     227             :                     }
     228             : 
     229         115 :                     if(*s == ']')
     230             :                     {
     231         115 :                         addr = std::string(start, s - start);
     232             : 
     233         115 :                         result.clear();
     234             : 
     235             :                         // here we have our info, use it to get sane data
     236             :                         //
     237             :                         // TODO: look into whether we could rewrite the
     238             :                         // eu-addr2line tool in C++ to avoid having to
     239             :                         // run it (because that would be faster)
     240             :                         //
     241             :                         // I first used the "... -e <filename> ..." option
     242             :                         // but with the --pid=..., it ought to be faster.
     243             :                         //
     244             :                         {
     245         115 :                             std::string addr2line("eu-addr2line --pid="
     246         230 :                                             + std::to_string(getpid())
     247         345 :                                             + " "
     248         345 :                                             + addr);
     249         230 :                             std::unique_ptr<FILE, decltype(&::pclose)> p(popen(addr2line.c_str(), "r"), &::pclose);
     250         230 :                             std::string line;
     251             :                             for(;;)
     252             :                             {
     253        4642 :                                 int const c(fgetc(p.get()));
     254        4642 :                                 if(c == EOF)
     255             :                                 {
     256         115 :                                     break;
     257             :                                 }
     258        4527 :                                 if(c != '\n')
     259             :                                 {
     260        4412 :                                     line += c;
     261             :                                 }
     262        4527 :                             }
     263         115 :                             if(line == "??:0")
     264             :                             {
     265             :                                 // this means addr2line failed to find the
     266             :                                 // debug info in your executable/.so
     267             :                                 // we fallback to the default which may help
     268             :                                 // if you have a version with debug info
     269             :                                 //
     270         110 :                                 result += filename
     271         110 :                                         + "["
     272         165 :                                         + addr
     273         165 :                                         + "]";
     274             :                             }
     275             :                             else
     276             :                             {
     277          60 :                                 result = line;
     278             :                             }
     279             :                         }
     280             : 
     281         115 :                         if(!raw_function_name.empty())
     282             :                         {
     283          27 :                             result += " in ";
     284          27 :                             result += demangle_cpp_name(raw_function_name.c_str());
     285             :                         }
     286             :                         else
     287             :                         {
     288          88 :                             result += " <no function name>";
     289             :                         }
     290             :                     }
     291             :                 }
     292             :             }
     293             : 
     294         115 :             if(result.empty())
     295             :             {
     296             :                 // use the raw line as a fallback if we could not parse it
     297             :                 // correctly or a conversion somehow fails...
     298             :                 //
     299             :                 stack_trace.push_back(std::string(raw_stack_string));   // LCOV_EXCL_LINE
     300             :             }
     301             :             else
     302             :             {
     303         115 :                 stack_trace.push_back(result);
     304             :             }
     305             :         }
     306             :     }
     307             : 
     308           9 :     return stack_trace;
     309             : }
     310             : 
     311             : 
     312             : 
     313             : 
     314           6 : }
     315             : // namespace libexcept
     316             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13