[libcamera-devel] [PATCH v1 1/4] libcamera: base: Add Backtrace class
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Tue Sep 28 04:31:17 CEST 2021
Hi Nicolas,
On Mon, Sep 27, 2021 at 09:41:46AM -0400, Nicolas Dufresne wrote:
> Le vendredi 24 septembre 2021 à 13:23 +0300, Laurent Pinchart a écrit :
> > Create a new class to abstract generation and access to call stack
> > backtraces. The current implementation depends on the glibc backtrace()
> > implementation and is copied from the logger. Future development will
> > bring support for libunwind, transparently for the users of the class.
> >
> > The logger backtrace implementation is dropped, replaced by usage of the
> > new Backtrace class.
> >
> > Signed-off-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
> > ---
> > include/libcamera/base/backtrace.h | 34 +++++++++
> > include/libcamera/base/meson.build | 1 +
> > meson.build | 4 --
> > src/libcamera/base/backtrace.cpp | 107 +++++++++++++++++++++++++++++
> > src/libcamera/base/log.cpp | 25 ++-----
> > src/libcamera/base/meson.build | 5 ++
> > 6 files changed, 153 insertions(+), 23 deletions(-)
> > create mode 100644 include/libcamera/base/backtrace.h
> > create mode 100644 src/libcamera/base/backtrace.cpp
> >
> > diff --git a/include/libcamera/base/backtrace.h b/include/libcamera/base/backtrace.h
> > new file mode 100644
> > index 000000000000..aefc76defa83
> > --- /dev/null
> > +++ b/include/libcamera/base/backtrace.h
> > @@ -0,0 +1,34 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2021, Ideas on Board Oy
> > + *
> > + * backtrace.h - Call stack backtraces
> > + */
> > +#ifndef __LIBCAMERA_BASE_BACKTRACE_H__
> > +#define __LIBCAMERA_BASE_BACKTRACE_H__
>
> In the context of this being modern code base, have you considered using this ?
>
> #pragma once
Yes :-) I think we should do a tree-wide change, just haven't had the
time to do so. Is there any drawback btw ?
> > +
> > +#include <string>
> > +#include <vector>
> > +
> > +#include <libcamera/base/private.h>
> > +
> > +#include <libcamera/base/class.h>
> > +
> > +namespace libcamera {
> > +
> > +class Backtrace
> > +{
> > +public:
> > + Backtrace();
> > +
> > + std::string toString(unsigned int skipLevels = 0) const;
> > +
> > +private:
> > + LIBCAMERA_DISABLE_COPY(Backtrace)
> > +
> > + std::vector<void *> backtrace_;
> > +};
> > +
> > +} /* namespace libcamera */
> > +
> > +#endif /* __LIBCAMERA_BASE_BACKTRACE_H__ */
> > diff --git a/include/libcamera/base/meson.build b/include/libcamera/base/meson.build
> > index 9feb4b9346d5..525aba9d2919 100644
> > --- a/include/libcamera/base/meson.build
> > +++ b/include/libcamera/base/meson.build
> > @@ -3,6 +3,7 @@
> > libcamera_base_include_dir = libcamera_include_dir / 'base'
> >
> > libcamera_base_headers = files([
> > + 'backtrace.h',
> > 'bound_method.h',
> > 'class.h',
> > 'event_dispatcher.h',
> > diff --git a/meson.build b/meson.build
> > index a49c484fe64e..dfed01ba26bc 100644
> > --- a/meson.build
> > +++ b/meson.build
> > @@ -29,10 +29,6 @@ cc = meson.get_compiler('c')
> > cxx = meson.get_compiler('cpp')
> > config_h = configuration_data()
> >
> > -if cc.has_header_symbol('execinfo.h', 'backtrace')
> > - config_h.set('HAVE_BACKTRACE', 1)
> > -endif
> > -
> > if cc.has_header_symbol('unistd.h', 'issetugid')
> > config_h.set('HAVE_ISSETUGID', 1)
> > endif
> > diff --git a/src/libcamera/base/backtrace.cpp b/src/libcamera/base/backtrace.cpp
> > new file mode 100644
> > index 000000000000..913f7ba71b03
> > --- /dev/null
> > +++ b/src/libcamera/base/backtrace.cpp
> > @@ -0,0 +1,107 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2021, Ideas on Board Oy
> > + *
> > + * backtrace.h - Call stack backtraces
> > + */
> > +
> > +#include <libcamera/base/backtrace.h>
> > +
> > +#if HAVE_BACKTRACE
> > +#include <execinfo.h>
> > +#include <stdlib.h>
> > +#endif
> > +
> > +#include <sstream>
> > +
> > +#include <libcamera/base/span.h>
> > +
> > +/**
> > + * \file backtrace.h
> > + * \brief Generate call stack backtraces
>
> _s
I meant the imperative mood :-) I don't mind changing it though.
> > + */
> > +
> > +namespace libcamera {
> > +
> > +/**
> > + * \class Backtrace
> > + * \brief Representation of a call stack backtrace
> > + *
> > + * The Backtrace class represents a function call stack. Constructing an
> > + * instance captures the call stack at the point the instance is constructed.
> > + * The instance can later be used to access the call stack and generate a
> _s
I meant "to access the call stack and [to] generate ...". I'll add the
"to".
> > + * human-readable representation with the toString() function.
> > + *
> > + * Depending on the platform, different backends can be used to generate the
> > + * backtrace. The Backtrace class provides a best effort to capture accurate
> > + * backtraces, but doesn't offer any guarantee of a particular backtrace format.
> > + */
> > +
> > +/**
> > + * \brief Construct a backtrace
> > + *
> > + * The backtrace captures the call stack at the point where it is constructed.
> > + * It can later be converted to a string with toString().
> > + */
> > +Backtrace::Backtrace()
> > +{
> > +#if HAVE_BACKTRACE
> > + backtrace_.resize(32);
> > +
> > + int num_entries = backtrace(backtrace_.data(), backtrace_.size());
> > + if (num_entries < 0) {
> > + backtrace_.clear();
> > + return;
> > + }
> > +
> > + backtrace_.resize(num_entries);
> > +#endif
> > +}
> > +
> > +/**
> > + * \brief Convert a backtrace to a string representation
> _s
We use the imperative mood for the \brief statements.
> > + * \param[in] skipLevels Number of initial levels to skip in the backtrace
> > + *
> > + * The string representation of the backtrace is a multi-line string, with one
> > + * line per call stack entry. The format of the entries isn't specified and is
> > + * platform-dependent.
> > + *
> > + * The \a skipLevels parameter indicates how many initial entries to skip from
> > + * the backtrace. This can be used to hide functions that wrap the construction
> > + * of the Backtrace instance from the call stack. The Backtrace constructor
> > + * itself is automatically skipped and never shown in the backtrace.
> > + *
> > + * If backtrace generation fails for any reason (usually because the platform
> > + * doesn't support this feature), an empty string is returned.
> > + *
> > + * \return A string representation of the backtrace, or an empty string if
> > + * backtrace generation isn't possible
>
> Just an alternative suggestion for when the backtrace is not possible, perhaps
> we could return a string that let user know the backtrace could be be made.
> Otherwise, all callers will have to check for empty string in order to give user
> feedback.
I was thinking about it, but then I thought it may be better not to
assume in this class how a caller may want to use it when backtrace
generation isn't possible. It also allows the caller to check if the
backtrace was successfully generated, which could be useful.
> > + */
> > +std::string Backtrace::toString(unsigned int skipLevels) const
> > +{
> > + /* Skip the first entry, corresponding to the Backtrace construction. */
> > + skipLevels += 1;
> > +
> > + if (backtrace_.size() <= skipLevels)
> > + return std::string();
> > +
> > +#if HAVE_BACKTRACE
> > + Span<void *const> trace{ backtrace_ };
> > + trace = trace.subspan(skipLevels);
> > +
> > + char **strings = backtrace_symbols(trace.data(), trace.size());
> > + if (strings) {
> > + std::ostringstream msg;
> > +
> > + for (unsigned int i = 0; i < trace.size(); ++i)
>
> There is no direct way of enumerating that one ? (direct in the sense no index
> required)
backtrace_symbols() returns an array that contains the number of
elements passed as the second argument, and there's no terminator.
> > + msg << strings[i] << std::endl;
> > +
> > + free(strings);
>
> If you only we had smart pointers like glib does (g_autofree) ;-P
I'll blame backtrace_symbols() for not using C++ ;-) Jokes aside,
"unsafe" code is expected when interfacing with such APIs.
> > + return msg.str();
> > + }
> > +#endif
> > +
> > + return std::string();
> > +}
> > +
> > +} /* namespace libcamera */
> > diff --git a/src/libcamera/base/log.cpp b/src/libcamera/base/log.cpp
> > index a3e3f9ea2712..272c440589d4 100644
> > --- a/src/libcamera/base/log.cpp
> > +++ b/src/libcamera/base/log.cpp
> > @@ -8,9 +8,6 @@
> > #include <libcamera/base/log.h>
> >
> > #include <array>
> > -#if HAVE_BACKTRACE
> > -#include <execinfo.h>
> > -#endif
> > #include <fstream>
> > #include <iostream>
> > #include <list>
> > @@ -23,6 +20,7 @@
> >
> > #include <libcamera/logging.h>
> >
> > +#include <libcamera/base/backtrace.h>
> > #include <libcamera/base/thread.h>
> > #include <libcamera/base/utils.h>
> >
> > @@ -418,31 +416,20 @@ void Logger::write(const LogMessage &msg)
> > */
> > void Logger::backtrace()
> > {
> > -#if HAVE_BACKTRACE
> > std::shared_ptr<LogOutput> output = std::atomic_load(&output_);
> > if (!output)
> > return;
> >
> > - void *buffer[32];
> > - int num_entries = ::backtrace(buffer, std::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 function and
> > * ~LogMessage().
> > */
> > - for (int i = 2; i < num_entries; ++i)
> > - msg << strings[i] << std::endl;
> > + std::string backtrace = Backtrace().toString(2);
> > + if (backtrace.empty())
> > + return;
>
> As mention earlier, we have to do empty string check, and then this function
> does not even let user know that no backtrace was available. This might be
> confusing for leak tracers when the users explicitly ask for backtraces.
I agree a message would be good. I'll experiment with both options
(printing it here, or in Backtrace::toString()).
> >
> > - output->write(msg.str());
> > -
> > - free(strings);
> > -#endif
> > + output->write("Backtrace:\n");
> > + output->write(backtrace);
> > }
> >
> > /**
> > diff --git a/src/libcamera/base/meson.build b/src/libcamera/base/meson.build
> > index 8e125744d823..85af01a19365 100644
> > --- a/src/libcamera/base/meson.build
> > +++ b/src/libcamera/base/meson.build
> > @@ -1,6 +1,7 @@
> > # SPDX-License-Identifier: CC0-1.0
> >
> > libcamera_base_sources = files([
> > + 'backtrace.cpp',
> > 'class.cpp',
> > 'bound_method.cpp',
> > 'event_dispatcher.cpp',
> > @@ -18,6 +19,10 @@ libcamera_base_sources = files([
> > 'utils.cpp',
> > ])
> >
> > +if cc.has_header_symbol('execinfo.h', 'backtrace')
> > + config_h.set('HAVE_BACKTRACE', 1)
> > +endif
> > +
> > libcamera_base_deps = [
> > dependency('threads'),
> > libatomic,
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list