[libcamera-devel] [PATCH] libcamera: Print backtrace on fatal errors

Laurent Pinchart laurent.pinchart at ideasonboard.com
Tue Nov 26 03:01:48 CET 2019


Hi Kieran,

On Mon, Nov 25, 2019 at 10:57:53AM +0000, Kieran Bingham wrote:
> On 23/11/2019 12:43, Laurent Pinchart wrote:
> > When a fatal error occurs the program aborts, and all the logger
> > provides is the location of the line that caused the error. Extend this
> > with a full backtrace to help debugging.
> > 
> > The backtrace is generated using the backtrace() call, a GNU extension
> > to the C library. It is available in glibc and uClibc but not in musl.
> > Test for availability of the function to condition compilation of the
> > backtrace printing. Implementing backtrace support with musl is an
> > exercise left to the reader if desired.
> 
> Nice, I like features like this. I had this as a TODO note in one of my
> branches, so I'm glad you've got to it first!.
> 
> Have you looked at libSegFault as well perhaps by any chance?

I wasn't aware of it, thanks for the pointer.

> I'm wondering if something like the following might be a useful addition
> in this topic:
> 
> +if cc.find_library('SegFault', required: false).found()
> +    message('Found libSegFault')
> +    common_arguments += [ '-lSegFault', ]
> +endif
> +
> 
> Although - also possibly conditioned on being a debug build.

In this particular case it wouldn't have helped, as I could only
reproduce the issue with release builds. I thus wonder if we shouldn't
LD_PRELOAD libSegFault.so instead when running tests, like we use
valgrind on tests from time to time. Or would libasan be a better option
in that case ?

What I'm trying to figure out is how much overlap there is between this
patch and libSegFault or libasan.

> This works nicely for me so far, so:
> 
>   Tested-by: Kieran Bingham <kieran.bingham at ideasonboard.com>
> 
> It might be argued that the LogOutput changes required here could be
> split to clarify them, as it took a bit of parsing to understand what
> had to be moved and why. I don't see much requirement to split it, but
> it might be nice to add a description to the changelog to help
> understanding.
> 
> From my parsing - something like:
> 
> The LogOutput class is extended to support writing string messages
> directly to the output. Strings written directly will be considered as
> LogDebug messages when written to the Syslog.
> 
> (Adapting that for correctness if you do decide to add it of course)

Text added, thank you.

> 
>   Reviewed-by: Kieran Bingham <kieran.bingham at ideasonboard.com>
> 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
> > ---
> >  meson.build           |  4 ++
> >  src/libcamera/log.cpp | 93 ++++++++++++++++++++++++++++++++++---------
> >  2 files changed, 79 insertions(+), 18 deletions(-)
> > 
> > diff --git a/meson.build b/meson.build
> > index 0a222ba96dcb..634488589a46 100644
> > --- a/meson.build
> > +++ b/meson.build
> > @@ -26,6 +26,10 @@ libcamera_version = libcamera_git_version.split('+')[0]
> >  cc = meson.get_compiler('c')
> >  config_h = configuration_data()
> >  
> > +if cc.has_header_symbol('execinfo.h', 'backtrace')
> > +    config_h.set('HAVE_BACKTRACE', 1)
> > +endif
> > +
> >  if cc.has_header_symbol('stdlib.h', 'secure_getenv', prefix : '#define _GNU_SOURCE')
> >      config_h.set('HAVE_SECURE_GETENV', 1)
> >  endif
> > diff --git a/src/libcamera/log.cpp b/src/libcamera/log.cpp
> > index 50f345b98c74..f4eb8c11adc3 100644
> > --- a/src/libcamera/log.cpp
> > +++ b/src/libcamera/log.cpp
> > @@ -7,6 +7,9 @@
> >  
> >  #include "log.h"
> >  
> > +#if HAVE_BACKTRACE
> > +#include <execinfo.h>
> > +#endif
> >  #include <fstream>
> >  #include <iostream>
> >  #include <list>
> > @@ -108,10 +111,11 @@ public:
> >  
> >  	bool isValid() const;
> >  	void write(const LogMessage &msg);
> > +	void write(const std::string &msg);
> >  
> >  private:
> > -	void writeSyslog(const LogMessage &msg);
> > -	void writeStream(const LogMessage &msg);
> > +	void writeSyslog(LogSeverity severity, const std::string &msg);
> > +	void writeStream(const std::string &msg);
> >  
> >  	std::ostream *stream_;
> >  	LoggingTarget target_;
> > @@ -180,34 +184,55 @@ bool LogOutput::isValid() const
> >   * \param[in] msg Message to write
> >   */
> >  void LogOutput::write(const LogMessage &msg)
> > +{
> > +	std::string str;
> > +
> > +	switch (target_) {
> > +	case LoggingTargetSyslog:
> > +		str = std::string(log_severity_name(msg.severity())) + " "
> > +		    + msg.category().name() + " " + msg.fileInfo() + " "
> > +		    + msg.msg();
> > +		writeSyslog(msg.severity(), str);
> > +		break;
> > +	case LoggingTargetStream:
> > +	case LoggingTargetFile:
> > +		str = "[" + utils::time_point_to_string(msg.timestamp()) + "]"
> > +		    + log_severity_name(msg.severity()) + " "
> > +		    + msg.category().name() + " " + msg.fileInfo() + " "
> > +		    + msg.msg();
> > +		writeStream(str);
> > +		break;
> > +	default:
> > +		break;
> > +	}
> > +}
> > +
> > +/**
> > + * \brief Write string to log output
> > + * \param[in] str String to write
> > + */
> > +void LogOutput::write(const std::string &str)
> >  {
> >  	switch (target_) {
> >  	case LoggingTargetSyslog:
> > -		writeSyslog(msg);
> > +		writeSyslog(LogDebug, str);
> >  		break;
> >  	case LoggingTargetStream:
> >  	case LoggingTargetFile:
> > -		writeStream(msg);
> > +		writeStream(str);
> >  		break;
> >  	default:
> >  		break;
> >  	}
> >  }
> >  
> > -void LogOutput::writeSyslog(const LogMessage &msg)
> > +void LogOutput::writeSyslog(LogSeverity severity, const std::string &str)
> >  {
> > -	std::string str = std::string(log_severity_name(msg.severity())) + " " +
> > -	      		  msg.category().name() + " " + msg.fileInfo() + " " +
> > -			  msg.msg();
> > -	syslog(log_severity_to_syslog(msg.severity()), "%s", str.c_str());
> > +	syslog(log_severity_to_syslog(severity), "%s", str.c_str());
> >  }
> >  
> > -void LogOutput::writeStream(const LogMessage &msg)
> > +void LogOutput::writeStream(const std::string &str)
> >  {
> > -	std::string str = "[" + utils::time_point_to_string(msg.timestamp()) +
> > -			  "]" + log_severity_name(msg.severity()) + " " +
> > -			  msg.category().name() + " " + msg.fileInfo() + " " +
> > -			  msg.msg();
> >  	stream_->write(str.c_str(), str.size());
> >  	stream_->flush();
> >  }
> > @@ -223,6 +248,7 @@ public:
> >  	static Logger *instance();
> >  
> >  	void write(const LogMessage &msg);
> > +	void backtrace();
> >  
> >  	int logSetFile(const char *path);
> >  	int logSetStream(std::ostream *stream);
> > @@ -240,9 +266,6 @@ private:
> >  	void registerCategory(LogCategory *category);
> >  	void unregisterCategory(LogCategory *category);
> >  
> > -	void writeSyslog(const LogMessage &msg);
> > -	void writeStream(const LogMessage &msg);
> > -
> >  	std::unordered_set<LogCategory *> categories_;
> >  	std::list<std::pair<std::string, LogSeverity>> levels_;
> >  
> > @@ -370,6 +393,38 @@ void Logger::write(const LogMessage &msg)
> >  	output->write(msg);
> >  }
> >  
> > +/**
> > + * \brief Write a backtrace to the log
> > + */
> > +void Logger::backtrace()
> > +{
> > +#if HAVE_BACKTRACE
> 
> Will this still be usable/desirable on release builds when
> function/symbol names are possibly unavailable?
> 
> If not - then we might want to make this:
> 
> 	#if HAVE_BACKTRACE && !defined(NDEBUG)
> 
> But I'm not entirely averse to having the backtrace in regardless in
> fact, as it will likely backtrace all the way into the calling
> application (which should have symbols if it's being developed) and
> could add value in that scenario regardless.

I think it's still desirable, as I'm using it with release builds :-)

> > +	std::shared_ptr<LogOutput> output = std::atomic_load(&output_);
> > +	if (!output)
> > +		return;
> > +
> > +	void *buffer[32];
> > +	int num_entries = ::backtrace(buffer, ARRAY_SIZE(buffer));
> > +	char **strings = backtrace_symbols(buffer, num_entries);
> > +	if (!strings)
> > +		return;
> > +
> > +	std::ostringstream msg;
> > +	msg << "Backtrace:" << std::endl;
> > +
> > +	/*
> > +	 * Skip the first two entries that correspond to this method and
> > +	 * ~LogMessage().
> > +	 */
> > +	for (int i = 2; i < num_entries; ++i)
> > +		msg << strings[i] << std::endl;
> > +
> > +	output->write(msg.str());
> > +
> > +	free(strings);
> > +#endif
> > +}
> > +
> >  /**
> >   * \brief Set the log file
> >   * \param[in] path Full path to the log file
> > @@ -783,8 +838,10 @@ LogMessage::~LogMessage()
> >  	if (severity_ >= category_.severity())
> >  		Logger::instance()->write(*this);
> >  
> > -	if (severity_ == LogSeverity::LogFatal)
> > +	if (severity_ == LogSeverity::LogFatal) {
> > +		Logger::instance()->backtrace();
> >  		std::abort();
> > +	}
> >  }
> >  
> >  /**

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list