| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_DIAGNOSTICS_H_ |
| #define SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_DIAGNOSTICS_H_ |
| |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <zircon/assert.h> |
| #include <zircon/compiler.h> |
| |
| #include <string_view> |
| #include <tuple> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "field.h" |
| #include "internal/const-string.h" |
| #include "internal/diagnostics-printf.h" |
| |
| namespace elfldltl { |
| |
| // Various template APIs use a polymorphic "diagnostics object" argument. |
| // |
| // This object is responsible for reporting errors and for the policy on when |
| // to bail out of processing ELF data early. All processing using this object |
| // is implicitly related to a single ELF file, so error details and locations |
| // always refer to that file. |
| // |
| // A diagnostics object must implement a few simple methods: |
| // |
| // * `bool FormatError(std::string_view error, ...)` |
| // |
| // This is called to report a fatal error in the ELF data. The return value |
| // tells the caller whether to continue processing to the extent safely |
| // possible after the error. |
| // |
| // The first argument is a string constant with permanent extent which |
| // describes the error and can be referred to indefinitely. If there are |
| // additional arguments they have one of three types: |
| // |
| // * `size_type`, the address-sized unsigned integral type for the file. |
| // This argument is a value from the file that the error complains about. |
| // It's canonical to show these in decimal. |
| // |
| // * `elfldltl::FileOffset<size_type>` |
| // This argument is the offset in the ELF file, where the bad data is. |
| // It's canonical to show these in hexadecimal. |
| // |
| // * `elfldltl::FileAddress<size_type>` |
| // This argument is the address relative to the load bias of the ELF file, |
| // where the bad data is. It's canonical to show these in hexadecimal. |
| // |
| // * `std::string_view`, `const char*`, or string literals |
| // These strings are just concatenated. Note that while the integer-based |
| // types are expected to be formatted with a leading space, strings are |
| // expected to just be appended verbatim. So a typical call might look |
| // like `FormatError("bad value", 123, " in something indexed", 456)` to |
| // yield a result like `"bad value 123 in something indexed 456"`. |
| // |
| // The `elfldltl::FileOffset` and `elfldltl::FileAddress` types each provide |
| // a `static constexpr std::string_view kDescription` with canonical text |
| // to precede the integer value (usually shown in hexadecimal). |
| // |
| // Essentially this is an input-dependent assertion failure. FormatError is |
| // called exclusively for anomalies that can be explained only by a corrupted |
| // ELF file or memory image or by a linker bug. Processing cannot succeed |
| // and no code or data from this file should be used. The diagnostics object |
| // should return true only for the purpose of logging additional errors from |
| // the same file before abandoning it. The processor may attempt additional |
| // work but will only do what it can do safely without assertion failures or |
| // other risks of crashing. The bad data it has already encountered could |
| // lead to a cascade of additional errors with entirely bogus details, but it |
| // might be possible to get coherent reports of multiple independent errors. |
| // |
| // * `bool FormatWarning(std::string_view error, ...)` |
| // |
| // This is like FormatError, but for issues that are less problematic. These |
| // are anomalies that probably constitute bugs in the ELF file, but plausibly |
| // could be the result of build-time errors or dubious practices by the |
| // programmer rather than a bug in the tools or corrupted data per se. It's |
| // probably safe enough to ignore these issues and use the file regardless. |
| // |
| // * `<size_t MaxObjects> bool ResourceLimit(std::string_view error, size_t requested)` |
| // * bool ResourceLimit(size_t max, std::string_view error, size_t requested)` |
| // |
| // The ResourceLimit methods are used to format errors related to imposed |
| // resource limits, like with StaticVector. A ResourceLimit is not caused |
| // by system pressure and is expected that the same call that yielded a |
| // ResourceLimit error on an unchanged object will do so again. The templated |
| // version is preferred and the non templated version should be used when |
| // the limit of the resource are unknown at compile time like |
| // PreallocatedVector with a dynamic extent. |
| // |
| // * `bool UndefinedSymbol(std::string_view sym_name)` |
| // |
| // UndefinedSymbol is an error used when the current linking task cannot |
| // be completed because of an undefined symbol. |
| // |
| // * `bool MissingDependency(std::string_view soname)` |
| // |
| // MissingDependency is used when a DT_NEEDED dependency cannot be found. |
| // |
| // * `bool SystemError(std::string_view error, ...)` |
| // |
| // SystemError is used when the system cannot fulfill an otherwise valid |
| // request likely unrelated to the contents of the ELF file. SystemError |
| // can optionally take PosixError and ZirconError objects to give more |
| // context to the error encountered. Those two types are found in posix.h |
| // and zircon.h, and take either an errno value or zx_status_t respectively. |
| // |
| // * `bool extra_checking() const` |
| // |
| // If this returns true, the processor may do some extra work that is not |
| // necessary for its correct operation but just offers an opportunity to |
| // notice anomalies in the ELF data and report errors or warnings that might |
| // otherwise go unnoticed. Extra checking can be avoided if the use case is |
| // optimized for performance over maximal format strictness, or if the |
| // diagnostics object is ignoring warnings, etc. |
| // |
| |
| // This wraps an unsigned integral type to represent an offset in the ELF file. |
| template <typename size_type> |
| struct FileOffset { |
| static_assert(std::is_integral_v<size_type>); |
| static_assert(std::is_unsigned_v<size_type>); |
| |
| using value_type = size_type; |
| |
| constexpr value_type operator*() const { return offset; } |
| |
| constexpr bool operator==(const FileOffset& other) const { return offset == other.offset; } |
| constexpr bool operator!=(const FileOffset& other) const { return offset != other.offset; } |
| |
| static constexpr std::string_view kDescription = "file offset"; |
| |
| value_type offset; |
| }; |
| |
| // Deduction guides. |
| |
| template <typename size_type> |
| FileOffset(size_type) -> FileOffset<size_type>; |
| |
| template <typename size_type, bool kSwap> |
| FileOffset(UnsignedField<size_type, kSwap>) -> FileOffset<size_type>; |
| |
| // Helper to discover if T is a FileOffset type. |
| template <typename T> |
| inline constexpr bool kIsFileOffset = false; |
| |
| template <typename size_type> |
| inline constexpr bool kIsFileOffset<FileOffset<size_type>> = true; |
| |
| // This wraps an unsigned integral type to represent an address in the ELF |
| // file's load image, i.e. such that the p_vaddr of the first PT_LOAD segment |
| // corresponds to that segment's p_offset in the file. |
| template <typename size_type> |
| struct FileAddress { |
| static_assert(std::is_integral_v<size_type>); |
| static_assert(std::is_unsigned_v<size_type>); |
| |
| using value_type = size_type; |
| |
| constexpr value_type operator*() const { return address; } |
| |
| constexpr bool operator==(const FileAddress& other) const { return address == other.address; } |
| constexpr bool operator!=(const FileAddress& other) const { return address != other.address; } |
| |
| static constexpr std::string_view kDescription = "file-relative address"; |
| |
| value_type address; |
| }; |
| |
| // Deduction guides. |
| template <typename size_type> |
| FileAddress(size_type) -> FileAddress<size_type>; |
| |
| template <typename size_type, bool kSwap> |
| FileAddress(UnsignedField<size_type, kSwap>) -> FileAddress<size_type>; |
| |
| // Helper to discover if T is a FileAddress type. |
| template <typename T> |
| inline constexpr bool kIsFileAddress = false; |
| |
| template <typename size_type> |
| inline constexpr bool kIsFileAddress<FileAddress<size_type>> = true; |
| |
| // These flags are used by the elfldltl::Diagnostics template implementation. |
| // This is the default for its template parameter. Any other class can be used |
| // as long as it provides members that are contextually convertible to bool |
| // with these names. |
| struct DiagnosticsFlags { |
| // If true, keep going after errors so more errors can be diagnosed. |
| bool multiple_errors = false; |
| |
| // If true, then warnings are treated like errors and obey the multiple_errors |
| // setting too. If false, then always keep going after a warning. |
| bool warnings_are_errors = true; |
| |
| // If true, do extra work to diagnose more errors that could be ignored. |
| bool extra_checking = false; |
| }; |
| |
| // This is an empty subclass of std::true_type or std::false_type, but |
| // different Index values yield distinct subclass types. This is necessary for |
| // [[no_unique_address]] semantics to achieve an empty struct if two adjacent |
| // fields have the same Value. |
| template <bool Value, size_t Index> |
| struct FixedBool : std::integral_constant<bool, Value> {}; |
| |
| // An alternative Flags type can be defined like this one to make one or more |
| // of the values fixed, or to change the default value of a mutable flag. |
| struct DiagnosticsPanicFlags { |
| __NO_UNIQUE_ADDRESS FixedBool<false, 0> multiple_errors; |
| __NO_UNIQUE_ADDRESS FixedBool<true, 1> warnings_are_errors; |
| __NO_UNIQUE_ADDRESS FixedBool<false, 2> extra_checking; |
| }; |
| |
| // elfldltl::Diagnostics provides a canonical implementation of a diagnostics |
| // object. It wraps any callable object that takes the std::string_view and |
| // other arguments passed to FormatError. |
| // |
| // The Flags type can be DiagnosticsFlags or any type with those three member |
| // names having types convertible to bool. The Flags object passed to the |
| // constructor (or default-constructed) determines the behavior. The flags() |
| // method returns the Flags copy in the diagnostics object, which can then be |
| // changed in place. The diagnostics object tracks the numbers of errors and |
| // warnings reported, unless Flags::multiple_errors is std::false_type. |
| // |
| // Convenience functions below return some canonical specializations of this. |
| // |
| template <typename Report, class Flags = DiagnosticsFlags> |
| class Diagnostics { |
| public: |
| constexpr Diagnostics(const Diagnostics&) = default; |
| constexpr Diagnostics(Diagnostics&&) noexcept = default; |
| |
| explicit constexpr Diagnostics(Report report) : report_(std::move(report)) {} |
| |
| constexpr Diagnostics(Report report, Flags flags) |
| : report_(std::move(report)), flags_(std::move(flags)) {} |
| |
| constexpr Flags& flags() { return flags_; } |
| constexpr const Flags& flags() const { return flags_; } |
| |
| constexpr Report& report() { return report_; } |
| constexpr const Report& report() const { return report_; } |
| |
| constexpr unsigned int errors() const { return errors_; } |
| |
| constexpr unsigned int warnings() const { return warnings_; } |
| |
| // Reset the counters. |
| // This doesn't do anything to the state of the Report object. |
| constexpr void reset() { |
| errors_ = {}; |
| warnings_ = {}; |
| } |
| |
| // The following methods are the actual "diagnostics" API as described above. |
| |
| template <typename... Args> |
| constexpr bool FormatError(std::string_view error, Args&&... args) { |
| ++errors_; |
| return report_(error, std::forward<Args>(args)...) && flags_.multiple_errors; |
| } |
| |
| template <typename... Args> |
| constexpr bool FormatWarning(std::string_view error, Args&&... args) { |
| ++warnings_; |
| return report_(error, std::forward<Args>(args)...) && |
| (flags_.multiple_errors || !flags_.warnings_are_errors); |
| } |
| |
| constexpr bool extra_checking() const { return flags_.extra_checking; } |
| |
| template <size_t MaxObjects> |
| constexpr bool ResourceLimit(std::string_view error, size_t requested) { |
| return FormatError(error, |
| internal::ConstString(": maximum ") + |
| internal::IntegerConstString<MaxObjects>() + |
| internal::ConstString(" < requested "), |
| requested); |
| } |
| |
| constexpr bool ResourceLimit(size_t max, std::string_view error, size_t requested) { |
| return FormatError(error, internal::ConstString(": maximum"), max, |
| internal::ConstString(" < requested"), requested); |
| } |
| |
| template <typename... Args> |
| constexpr bool SystemError(std::string_view error, Args&&... args) { |
| return FormatError(error, args...); |
| } |
| |
| constexpr bool UndefinedSymbol(std::string_view sym_name) { |
| return FormatError("undefined symbol: ", sym_name); |
| } |
| |
| constexpr bool MissingDependency(std::string_view soname) { |
| return SystemError("cannot open dependency: ", soname); |
| } |
| |
| private: |
| // This is either a wrapper around an integer, or is an empty object. |
| // The tag is unused but makes the two Count types always distinct so |
| // that adjacent empty members with __NO_UNIQUE_ADDRESS can be elided. |
| template <bool Counting, auto Tag> |
| struct Count; |
| |
| // When counting, a trivial wrapper around an integer. |
| template <auto Tag> |
| struct Count<true, Tag> { |
| constexpr Count& operator++() { |
| ++value_; |
| return *this; |
| } |
| |
| constexpr operator unsigned int() const noexcept { return value_; } |
| |
| unsigned int value_ = 0; |
| }; |
| |
| // When not counting, increments are no-ops and the count is always one. |
| template <auto Tag> |
| struct Count<false, Tag> { |
| constexpr Count& operator++() { return *this; } |
| |
| constexpr operator unsigned int() const noexcept { return 1; } |
| }; |
| |
| // If multiple_errors is actually std::false_type, then use the empty objects |
| // so we don't bother to keep counts at all. |
| static constexpr bool kCount = |
| !std::is_base_of_v<std::false_type, decltype(std::declval<Flags>().multiple_errors)>; |
| |
| __NO_UNIQUE_ADDRESS Report report_; |
| __NO_UNIQUE_ADDRESS Flags flags_; |
| __NO_UNIQUE_ADDRESS Count<kCount, &Flags::multiple_errors> errors_; |
| __NO_UNIQUE_ADDRESS Count<kCount, &Flags::warnings_are_errors> warnings_; |
| }; |
| |
| // This creates a callable object to use as the Report function in a |
| // Diagnostics object; it calls the printer function like calling printf. The |
| // remaining prefix arguments are treated like initial arguments passed to |
| // every Diagnostics::FormatError call. The printer is called with a format |
| // string literal, followed by various argument types corresponding to the '%' |
| // formats used therein. That format and argument sequence is generated based |
| // on the argument types as passed to FormatError. |
| template <typename Printer, typename... Prefix> |
| constexpr auto PrintfDiagnosticsReport(Printer&& printer, Prefix&&... prefix) { |
| return [printer = std::forward<Printer>(printer), |
| prefix = std::make_tuple(std::forward<Prefix>(prefix)...)](auto&&... args) { |
| internal::Printf(printer, prefix, std::forward<decltype(args)>(args)...); |
| return true; |
| }; |
| } |
| |
| // This is just PrintfDiagnosticsReport with a printer function that calls |
| // fprintf with the given FILE* argument. |
| template <typename... Prefix> |
| constexpr auto FprintfDiagnosticsReport(FILE* stream, Prefix&&... prefix) { |
| auto printer = [stream](auto&&... args) { |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wformat-nonliteral" |
| fprintf(stream, std::forward<decltype(args)>(args)...); |
| #pragma GCC diagnostic pop |
| }; |
| return PrintfDiagnosticsReport(printer, std::forward<Prefix>(prefix)...); |
| } |
| |
| // This is PrintfDiagnosticsReport but using ZX_PANIC for printf. |
| template <typename... Prefix> |
| constexpr auto PanicDiagnosticsReport(Prefix&&... prefix) { |
| constexpr auto panic = [](const char* format, auto&&... args) { |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wformat-nonliteral" |
| ZX_PANIC(format, std::forward<decltype(args)>(args)...); |
| #pragma GCC diagnostic pop |
| }; |
| return PrintfDiagnosticsReport(panic, std::forward<Prefix>(prefix)...); |
| } |
| |
| // This returns a Diagnostics object that crashes immediately for any error or |
| // warning. There are no library dependencies of any kind. This behavior is |
| // appropriate only for self-relocation and bootstrapping cases where if there |
| // is anything wrong in the ELF data then something went wrong in building this |
| // program itself and it shouldn't be running at all. |
| constexpr auto TrapDiagnostics() { |
| constexpr auto trap = [](auto&&... args) -> bool { |
| __builtin_trap(); |
| return false; |
| }; |
| return Diagnostics(trap, DiagnosticsPanicFlags()); |
| } |
| |
| // This is similar to TrapDiagnostics but it uses the <zircon/assert.h> |
| // ZX_PANIC call to write the message and crash, with an optional fixed prefix. |
| // So it has some library dependencies but might be able to generate some error |
| // output beofre crashing. Any arguments are stored in the diagnostics object |
| // and then treated as if initial arguments to every FormatError et al call so |
| // they can form a prefix on every message. Those arguments are forwarded |
| // perfectly, so if passed as an lvalue reference, the reference will be stored |
| // rather than its referent copied. |
| template <typename... Prefix> |
| constexpr auto PanicDiagnostics(Prefix&&... prefix) { |
| return Diagnostics(PanicDiagnosticsReport(std::forward<Prefix>(prefix)...), |
| DiagnosticsPanicFlags()); |
| } |
| |
| // This returns a Diagnostics object that simply stores a single error or |
| // warning message string. It always request early bail-out for errors on the |
| // expectation that only one error will be reported. But if the same object is |
| // indeed called again for another failure, the new error message will replace |
| // the old one. |
| template <typename T, typename... Flags> |
| constexpr auto OneStringDiagnostics(T& holder, Flags&&... flags) { |
| auto set_error = [&holder](std::string_view error, auto&&... args) { |
| holder = error; |
| return false; |
| }; |
| return Diagnostics(set_error, std::forward<Flags>(flags)...); |
| } |
| |
| // This returns a Diagnostics object that collects a container of messages. |
| template <typename T, typename... Flags> |
| constexpr auto CollectStringsDiagnostics(T& container, Flags&&... flags) { |
| auto add_error = [&container](std::string_view error, auto&&... args) { |
| container.emplace_back(error); |
| return true; |
| }; |
| return Diagnostics(add_error, std::forward<Flags>(flags)...); |
| } |
| |
| } // namespace elfldltl |
| |
| #endif // SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_DIAGNOSTICS_H_ |