diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /mozglue/linker/ElfLoader.h | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mozglue/linker/ElfLoader.h')
-rw-r--r-- | mozglue/linker/ElfLoader.h | 634 |
1 files changed, 634 insertions, 0 deletions
diff --git a/mozglue/linker/ElfLoader.h b/mozglue/linker/ElfLoader.h new file mode 100644 index 0000000000..059c092f6d --- /dev/null +++ b/mozglue/linker/ElfLoader.h @@ -0,0 +1,634 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ElfLoader_h +#define ElfLoader_h + +#include <vector> +#include <dlfcn.h> +#include <signal.h> +#include "mozilla/Atomics.h" +#include "mozilla/RefCounted.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "Zip.h" +#include "Elfxx.h" +#include "Mappable.h" + +/** + * dlfcn.h replacement functions + */ +extern "C" { +void* __wrap_dlopen(const char* path, int flags); +const char* __wrap_dlerror(void); +void* __wrap_dlsym(void* handle, const char* symbol); +int __wrap_dlclose(void* handle); + +#ifndef HAVE_DLADDR +typedef struct { + const char* dli_fname; + void* dli_fbase; + const char* dli_sname; + void* dli_saddr; +} Dl_info; +#endif +int __wrap_dladdr(const void* addr, Dl_info* info); + +struct dl_phdr_info { + Elf::Addr dlpi_addr; + const char* dlpi_name; + const Elf::Phdr* dlpi_phdr; + Elf::Half dlpi_phnum; +}; + +typedef int (*dl_phdr_cb)(struct dl_phdr_info*, size_t, void*); +int __wrap_dl_iterate_phdr(dl_phdr_cb callback, void* data); + +#ifdef __ARM_EABI__ +const void* __wrap___gnu_Unwind_Find_exidx(void* pc, int* pcount); +#endif + +/** + * faulty.lib public API + */ +MFBT_API size_t __dl_get_mappable_length(void* handle); + +MFBT_API void* __dl_mmap(void* handle, void* addr, size_t length, off_t offset); + +MFBT_API void __dl_munmap(void* handle, void* addr, size_t length); + +MFBT_API bool IsSignalHandlingBroken(); +} + +/* Forward declarations for use in LibHandle */ +class BaseElf; +class CustomElf; +class SystemElf; + +/** + * Specialize RefCounted template for LibHandle. We may get references to + * LibHandles during the execution of their destructor, so we need + * RefCounted<LibHandle>::Release to support some reentrancy. See further + * below. + */ +class LibHandle; + +namespace mozilla { +namespace detail { + +template <> +inline void RefCounted<LibHandle, AtomicRefCount>::Release() const; + +#ifdef DEBUG +template <> +inline RefCounted<LibHandle, AtomicRefCount>::~RefCounted() { + MOZ_ASSERT(mRefCnt == 0x7fffdead); +} +#endif + +} /* namespace detail */ +} /* namespace mozilla */ + +/** + * Abstract class for loaded libraries. Libraries may be loaded through the + * system linker or this linker, both cases will be derived from this class. + */ +class LibHandle : public mozilla::external::AtomicRefCounted<LibHandle> { + public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(LibHandle) + /** + * Constructor. Takes the path of the loaded library and will store a copy + * of the leaf name. + */ + LibHandle(const char* path) + : directRefCnt(0), + path(path ? strdup(path) : nullptr), + mappable(nullptr) {} + + /** + * Destructor. + */ + virtual ~LibHandle(); + + /** + * Returns the pointer to the address to which the given symbol resolves + * inside the library. It is not supposed to resolve the symbol in other + * libraries, although in practice, it will for system libraries. + */ + virtual void* GetSymbolPtr(const char* symbol) const = 0; + + /** + * Returns whether the given address is part of the virtual address space + * covered by the loaded library. + */ + virtual bool Contains(void* addr) const = 0; + + /** + * Returns the base address of the loaded library. + */ + virtual void* GetBase() const = 0; + + /** + * Returns the file name of the library without the containing directory. + */ + const char* GetName() const; + + /** + * Returns the full path of the library, when available. Otherwise, returns + * the file name. + */ + const char* GetPath() const { return path; } + + /** + * Library handles can be referenced from other library handles or + * externally (when dlopen()ing using this linker). We need to be + * able to distinguish between the two kind of referencing for better + * bookkeeping. + */ + void AddDirectRef() { + mozilla::external::AtomicRefCounted<LibHandle>::AddRef(); + ++directRefCnt; + } + + /** + * Releases a direct reference, and returns whether there are any direct + * references left. + */ + bool ReleaseDirectRef() { + const MozRefCountType count = --directRefCnt; + MOZ_ASSERT(count + 1 > 0); + MOZ_ASSERT(count + 1 <= + mozilla::external::AtomicRefCounted<LibHandle>::refCount()); + mozilla::external::AtomicRefCounted<LibHandle>::Release(); + return !!count; + } + + /** + * Returns the number of direct references + */ + MozRefCountType DirectRefCount() { return directRefCnt; } + + /** + * Returns the complete size of the file or stream behind the library + * handle. + */ + size_t GetMappableLength() const; + + /** + * Returns a memory mapping of the file or stream behind the library + * handle. + */ + void* MappableMMap(void* addr, size_t length, off_t offset) const; + + /** + * Unmaps a memory mapping of the file or stream behind the library + * handle. + */ + void MappableMUnmap(void* addr, size_t length) const; + +#ifdef __ARM_EABI__ + /** + * Find the address and entry count of the ARM.exidx section + * associated with the library + */ + virtual const void* FindExidx(int* pcount) const = 0; +#endif + + protected: + /** + * Returns a mappable object for use by MappableMMap and related functions. + */ + virtual Mappable* GetMappable() const = 0; + + /** + * Returns the instance, casted as the wanted type. Returns nullptr if + * that's not the actual type. (short of a better way to do this without + * RTTI) + */ + friend class ElfLoader; + friend class CustomElf; + friend class SEGVHandler; + friend int __wrap_dl_iterate_phdr(dl_phdr_cb callback, void* data); + virtual BaseElf* AsBaseElf() { return nullptr; } + virtual SystemElf* AsSystemElf() { return nullptr; } + + private: + mozilla::Atomic<MozRefCountType> directRefCnt; + char* path; + + /* Mappable object keeping the result of GetMappable() */ + mutable RefPtr<Mappable> mappable; +}; + +/** + * Specialized RefCounted<LibHandle>::Release. Under normal operation, when + * mRefCnt reaches 0, the LibHandle is deleted. Its mRefCnt is however + * increased to 1 on normal builds, and 0x7fffdead on debug builds so that the + * LibHandle can still be referenced while the destructor is executing. The + * mRefCnt is allowed to grow > 0x7fffdead, but not to decrease under that + * value, which would mean too many Releases from within the destructor. + */ +namespace mozilla { +namespace detail { + +template <> +inline void RefCounted<LibHandle, AtomicRefCount>::Release() const { +#ifdef DEBUG + if (mRefCnt > 0x7fff0000) MOZ_ASSERT(mRefCnt > 0x7fffdead); +#endif + MOZ_ASSERT(mRefCnt > 0); + if (mRefCnt > 0) { + if (0 == --mRefCnt) { +#ifdef DEBUG + mRefCnt = 0x7fffdead; +#else + ++mRefCnt; +#endif + delete static_cast<const LibHandle*>(this); + } + } +} + +} /* namespace detail */ +} /* namespace mozilla */ + +/** + * Class handling libraries loaded by the system linker + */ +class SystemElf : public LibHandle { + public: + /** + * Returns a new SystemElf for the given path. The given flags are passed + * to dlopen(). + */ + static already_AddRefed<LibHandle> Load(const char* path, int flags); + + /** + * Inherited from LibHandle + */ + virtual ~SystemElf(); + virtual void* GetSymbolPtr(const char* symbol) const; + virtual bool Contains(void* addr) const { return false; /* UNIMPLEMENTED */ } + virtual void* GetBase() const { return nullptr; /* UNIMPLEMENTED */ } + +#ifdef __ARM_EABI__ + virtual const void* FindExidx(int* pcount) const; +#endif + + protected: + virtual Mappable* GetMappable() const; + + /** + * Returns the instance, casted as SystemElf. (short of a better way to do + * this without RTTI) + */ + friend class ElfLoader; + virtual SystemElf* AsSystemElf() { return this; } + + /** + * Remove the reference to the system linker handle. This avoids dlclose() + * being called when the instance is destroyed. + */ + void Forget() { dlhandle = nullptr; } + + private: + /** + * Private constructor + */ + SystemElf(const char* path, void* handle) + : LibHandle(path), dlhandle(handle) {} + + /* Handle as returned by system dlopen() */ + void* dlhandle; +}; + +/** + * The ElfLoader registers its own SIGSEGV handler to handle segmentation + * faults within the address space of the loaded libraries. It however + * allows a handler to be set for faults in other places, and redispatches + * to the handler set through signal() or sigaction(). + */ +class SEGVHandler { + public: + bool hasRegisteredHandler() { + if (!initialized) FinishInitialization(); + return registeredHandler; + } + + bool isSignalHandlingBroken() { return signalHandlingBroken; } + + static int __wrap_sigaction(int signum, const struct sigaction* act, + struct sigaction* oldact); + + protected: + SEGVHandler(); + ~SEGVHandler(); + + private: + /** + * The constructor doesn't do all initialization, and the tail is done + * at a later time. + */ + void FinishInitialization(); + + /** + * SIGSEGV handler registered with __wrap_signal or __wrap_sigaction. + */ + struct sigaction action; + + /** + * ElfLoader SIGSEGV handler. + */ + static void handler(int signum, siginfo_t* info, void* context); + + /** + * Temporary test handler. + */ + static void test_handler(int signum, siginfo_t* info, void* context); + + /** + * Size of the alternative stack. The printf family requires more than 8KB + * of stack, and our signal handler may print a few things. + */ + static const size_t stackSize = 12 * 1024; + + /** + * Alternative stack information used before initialization. + */ + stack_t oldStack; + + /** + * Pointer to an alternative stack for signals. Only set if oldStack is + * not set or not big enough. + */ + MappedPtr stackPtr; + + bool initialized; + bool registeredHandler; + bool signalHandlingBroken; + bool signalHandlingSlow; +}; + +/** + * Elf Loader class in charge of loading and bookkeeping libraries. + */ +class ElfLoader : public SEGVHandler { + public: + /** + * The Elf Loader instance + */ + static ElfLoader Singleton; + + /** + * Loads the given library with the given flags. Equivalent to dlopen() + * The extra "parent" argument optionally gives the handle of the library + * requesting the given library to be loaded. The loader may look in the + * directory containing that parent library for the library to load. + */ + already_AddRefed<LibHandle> Load(const char* path, int flags, + LibHandle* parent = nullptr); + + /** + * Returns the handle of the library containing the given address in + * its virtual address space, i.e. the library handle for which + * LibHandle::Contains returns true. Its purpose is to allow to + * implement dladdr(). + */ + already_AddRefed<LibHandle> GetHandleByPtr(void* addr); + + /** + * Returns a Mappable object for the path. Paths in the form + * /foo/bar/baz/archive!/directory/lib.so + * try to load the directory/lib.so in /foo/bar/baz/archive, provided + * that file is a Zip archive. + */ + static Mappable* GetMappableFromPath(const char* path); + + void ExpectShutdown(bool val) { expect_shutdown = val; } + bool IsShutdownExpected() { return expect_shutdown; } + + private: + bool expect_shutdown; + + protected: + /** + * Registers the given handle. This method is meant to be called by + * LibHandle subclass creators. + */ + void Register(LibHandle* handle); + void Register(CustomElf* handle); + + /** + * Forget about the given handle. This method is meant to be called by + * LibHandle subclass destructors. + */ + void Forget(LibHandle* handle); + void Forget(CustomElf* handle); + + friend class SystemElf; + friend const char* __wrap_dlerror(void); + friend void* __wrap_dlsym(void* handle, const char* symbol); + friend int __wrap_dlclose(void* handle); + /* __wrap_dlerror() returns this custom last error if non-null or the system + * dlerror() value if this is null. Must refer to a string constant. */ + mozilla::Atomic<const char*, mozilla::Relaxed> lastError; + + private: + ElfLoader() : expect_shutdown(true), lastError(nullptr) { + pthread_mutex_init(&handlesMutex, nullptr); + } + + ~ElfLoader(); + + /* Initialization code that can't run during static initialization. */ + void Init(); + + /* System loader handle for the library/program containing our code. This + * is used to resolve wrapped functions. */ + RefPtr<LibHandle> self_elf; + +#if defined(ANDROID) + /* System loader handle for the libc. This is used to resolve weak symbols + * that some libcs contain that the Android linker won't dlsym(). Normally, + * we wouldn't treat non-Android differently, but glibc uses versioned + * symbols which this linker doesn't support. */ + RefPtr<LibHandle> libc; + + /* And for libm. */ + RefPtr<LibHandle> libm; +#endif + + /* Bookkeeping */ + typedef std::vector<LibHandle*> LibHandleList; + LibHandleList handles; + + pthread_mutex_t handlesMutex; + + protected: + friend class CustomElf; + friend class LoadedElf; + + /* Definition of static destructors as to be used for C++ ABI compatibility */ + typedef void (*Destructor)(void* object); + + /** + * C++ ABI makes static initializers register destructors through a specific + * atexit interface. On glibc/linux systems, the dso_handle is a pointer + * within a given library. On bionic/android systems, it is an undefined + * symbol. Making sense of the value is not really important, and all that + * is really important is that it is different for each loaded library, so + * that they can be discriminated when shutting down. For convenience, on + * systems where the dso handle is a symbol, that symbol is resolved to + * point at corresponding CustomElf. + * + * Destructors are registered with __*_atexit with an associated object to + * be passed as argument when it is called. + * + * When __cxa_finalize is called, destructors registered for the given + * DSO handle are called in the reverse order they were registered. + */ +#ifdef __ARM_EABI__ + static int __wrap_aeabi_atexit(void* that, Destructor destructor, + void* dso_handle); +#else + static int __wrap_cxa_atexit(Destructor destructor, void* that, + void* dso_handle); +#endif + + static void __wrap_cxa_finalize(void* dso_handle); + + /** + * Registered destructor. Keeps track of the destructor function pointer, + * associated object to call it with, and DSO handle. + */ + class DestructorCaller { + public: + DestructorCaller(Destructor destructor, void* object, void* dso_handle) + : destructor(destructor), object(object), dso_handle(dso_handle) {} + + /** + * Call the destructor function with the associated object. + * Call only once, see CustomElf::~CustomElf. + */ + void Call(); + + /** + * Returns whether the destructor is associated to the given DSO handle + */ + bool IsForHandle(void* handle) const { return handle == dso_handle; } + + private: + Destructor destructor; + void* object; + void* dso_handle; + }; + + private: + /* Keep track of all registered destructors */ + std::vector<DestructorCaller> destructors; + + /* Forward declaration, see further below */ + class DebuggerHelper; + + public: + /* Loaded object descriptor for the debugger interface below*/ + struct link_map { + /* Base address of the loaded object. */ + const void* l_addr; + /* File name */ + const char* l_name; + /* Address of the PT_DYNAMIC segment. */ + const void* l_ld; + + private: + friend class ElfLoader::DebuggerHelper; + /* Double linked list of loaded objects. */ + link_map *l_next, *l_prev; + }; + + private: + /* Data structure used by the linker to give details about shared objects it + * loaded to debuggers. This is normally defined in link.h, but Android + * headers lack this file. */ + struct r_debug { + /* Version number of the protocol. */ + int r_version; + + /* Head of the linked list of loaded objects. */ + link_map* r_map; + + /* Function to be called when updates to the linked list of loaded objects + * are going to occur. The function is to be called before and after + * changes. */ + void (*r_brk)(void); + + /* Indicates to the debugger what state the linked list of loaded objects + * is in when the function above is called. */ + enum { + RT_CONSISTENT, /* Changes are complete */ + RT_ADD, /* Beginning to add a new object */ + RT_DELETE /* Beginning to remove an object */ + } r_state; + }; + + /* Memory representation of ELF Auxiliary Vectors */ + struct AuxVector { + Elf::Addr type; + Elf::Addr value; + }; + + /* Helper class used to integrate libraries loaded by this linker in + * r_debug */ + class DebuggerHelper { + public: + DebuggerHelper(); + + void Init(AuxVector* auvx); + + explicit operator bool() { return dbg; } + + /* Make the debugger aware of a new loaded object */ + void Add(link_map* map); + + /* Make the debugger aware of the unloading of an object */ + void Remove(link_map* map); + + /* Iterates over all link_maps */ + class iterator { + public: + const link_map* operator->() const { return item; } + + const link_map& operator++() { + item = item->l_next; + return *item; + } + + bool operator<(const iterator& other) const { + if (other.item == nullptr) return item ? true : false; + MOZ_CRASH( + "DebuggerHelper::iterator::operator< called with something else " + "than DebuggerHelper::end()"); + } + + protected: + friend class DebuggerHelper; + explicit iterator(const link_map* item) : item(item) {} + + private: + const link_map* item; + }; + + iterator begin() const { return iterator(dbg ? dbg->r_map : nullptr); } + + iterator end() const { return iterator(nullptr); } + + private: + r_debug* dbg; + link_map* firstAdded; + }; + friend int __wrap_dl_iterate_phdr(dl_phdr_cb callback, void* data); + DebuggerHelper dbg; +}; + +#endif /* ElfLoader_h */ |