From a03225ca40368c90f2ff4565b579bbcd9c5822c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20S=C5=82omi=C5=84ski?= Date: Fri, 27 Dec 2024 19:48:01 +0100 Subject: [PATCH 1/9] internal: Add missing volatile to inline assembly Without this, the compiler can (and will) hoist the inline assembly in potentially broken ways. --- options/internal/aarch64-include/mlibc/thread.hpp | 4 ++-- options/internal/m68k-include/mlibc/thread.hpp | 2 +- options/internal/riscv64-include/mlibc/thread.hpp | 2 +- options/internal/x86-include/mlibc/thread.hpp | 4 ++-- options/internal/x86_64-include/mlibc/thread.hpp | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/options/internal/aarch64-include/mlibc/thread.hpp b/options/internal/aarch64-include/mlibc/thread.hpp index b62d832322..1e5b30536c 100644 --- a/options/internal/aarch64-include/mlibc/thread.hpp +++ b/options/internal/aarch64-include/mlibc/thread.hpp @@ -8,13 +8,13 @@ namespace mlibc { inline Tcb *get_current_tcb() { // On AArch64, TPIDR_EL0 points to 0x10 bytes before the first TLS block. uintptr_t ptr; - asm ("mrs %0, tpidr_el0" : "=r"(ptr)); + asm volatile ("mrs %0, tpidr_el0" : "=r"(ptr)); return reinterpret_cast(ptr + 0x10 - sizeof(Tcb)); } inline uintptr_t get_sp() { uintptr_t sp; - asm ("mov %0, sp" : "=r"(sp)); + asm volatile ("mov %0, sp" : "=r"(sp)); return sp; } diff --git a/options/internal/m68k-include/mlibc/thread.hpp b/options/internal/m68k-include/mlibc/thread.hpp index 9142b08f19..26cb187035 100644 --- a/options/internal/m68k-include/mlibc/thread.hpp +++ b/options/internal/m68k-include/mlibc/thread.hpp @@ -17,7 +17,7 @@ inline Tcb *get_current_tcb() { inline uintptr_t get_sp() { uintptr_t sp; - asm ("move.l %%sp, %0" : "=r"(sp)); + asm volatile ("move.l %%sp, %0" : "=r"(sp)); return sp; } diff --git a/options/internal/riscv64-include/mlibc/thread.hpp b/options/internal/riscv64-include/mlibc/thread.hpp index 7428b759b7..3efec9e9a2 100644 --- a/options/internal/riscv64-include/mlibc/thread.hpp +++ b/options/internal/riscv64-include/mlibc/thread.hpp @@ -16,7 +16,7 @@ inline Tcb *get_current_tcb() { inline uintptr_t get_sp() { uintptr_t sp; - asm ("mv %0, sp" : "=r"(sp)); + asm volatile ("mv %0, sp" : "=r"(sp)); return sp; } diff --git a/options/internal/x86-include/mlibc/thread.hpp b/options/internal/x86-include/mlibc/thread.hpp index 3475fb4968..8727db0ba3 100755 --- a/options/internal/x86-include/mlibc/thread.hpp +++ b/options/internal/x86-include/mlibc/thread.hpp @@ -7,13 +7,13 @@ namespace mlibc { inline Tcb *get_current_tcb() { uintptr_t ptr; - asm ("movl %%gs:0, %0" : "=r"(ptr)); + asm volatile ("movl %%gs:0, %0" : "=r"(ptr)); return reinterpret_cast(ptr); } inline uintptr_t get_sp() { uintptr_t esp; - asm ("mov %%esp, %0" : "=r"(esp)); + asm volatile ("mov %%esp, %0" : "=r"(esp)); return esp; } diff --git a/options/internal/x86_64-include/mlibc/thread.hpp b/options/internal/x86_64-include/mlibc/thread.hpp index ed02b677e9..474f562613 100644 --- a/options/internal/x86_64-include/mlibc/thread.hpp +++ b/options/internal/x86_64-include/mlibc/thread.hpp @@ -7,13 +7,13 @@ namespace mlibc { inline Tcb *get_current_tcb() { uintptr_t ptr; - asm ("movq %%fs:0, %0" : "=r"(ptr)); + asm volatile ("movq %%fs:0, %0" : "=r"(ptr)); return reinterpret_cast(ptr); } inline uintptr_t get_sp() { uintptr_t rsp; - asm ("mov %%rsp, %0" : "=r"(rsp)); + asm volatile ("mov %%rsp, %0" : "=r"(rsp)); return rsp; } From 47f2dca46786b9e10de9d7dbf1308c5529e00ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20S=C5=82omi=C5=84ski?= Date: Fri, 27 Dec 2024 05:33:44 +0100 Subject: [PATCH 2/9] elf: Add missing Elf32_{Verneed,Vernaux} definitions --- options/elf/include/elf.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/options/elf/include/elf.h b/options/elf/include/elf.h index 0c89dacb68..dc2a772720 100644 --- a/options/elf/include/elf.h +++ b/options/elf/include/elf.h @@ -111,6 +111,14 @@ typedef struct { Elf64_Word vda_next; /* Offset in bytes to next verdaux entry */ } Elf64_Verdaux; +typedef struct { + Elf32_Half vn_version; + Elf32_Half vn_cnt; + Elf32_Word vn_file; + Elf32_Word vn_aux; + Elf32_Word vn_next; +} Elf32_Verneed; + typedef struct { Elf64_Half vn_version; Elf64_Half vn_cnt; @@ -119,6 +127,14 @@ typedef struct { Elf64_Word vn_next; } Elf64_Verneed; +typedef struct { + Elf32_Word vna_hash; + Elf32_Half vna_flags; + Elf32_Half vna_other; + Elf32_Word vna_name; + Elf32_Word vna_next; +} Elf32_Vernaux; + typedef struct { Elf64_Word vna_hash; Elf64_Half vna_flags; From edf6a4c35124eb4145e66ab7a53e282779b024fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20S=C5=82omi=C5=84ski?= Date: Fri, 27 Dec 2024 04:32:09 +0100 Subject: [PATCH 3/9] elf: Add version-related flag definitions --- options/elf/include/elf.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/options/elf/include/elf.h b/options/elf/include/elf.h index dc2a772720..24ff1377e4 100644 --- a/options/elf/include/elf.h +++ b/options/elf/include/elf.h @@ -686,6 +686,10 @@ typedef struct { #define AT_GID 13 #define AT_EGID 14 +/* Values for Elfxx_Verdef::vd_flags and Elfxx_Vernaux::vna_flags */ +#define VER_FLG_BASE 1 /* Version definition of the file itself */ +#define VER_FLG_WEAK 2 /* Weak version identifier */ + /* rtld requires presence of some a_type (AT_*) values that are not standardized in the ELF spec */ #if !defined(AT_EXECFN) || !defined(AT_RANDOM) || !defined(AT_SECURE) #error "sysdeps' auxv.h is missing some defines that are required for rtld operation" From a31cfa5a63939dfb4708664305d6e790f12f29d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20S=C5=82omi=C5=84ski?= Date: Fri, 27 Dec 2024 03:04:25 +0100 Subject: [PATCH 4/9] rtld: Add architecture-specific version-related type aliases --- options/rtld/aarch64/elf.hpp | 6 ++++++ options/rtld/m68k/elf.hpp | 6 ++++++ options/rtld/riscv64/elf.hpp | 6 ++++++ options/rtld/x86/elf.hpp | 6 ++++++ options/rtld/x86_64/elf.hpp | 6 ++++++ 5 files changed, 30 insertions(+) diff --git a/options/rtld/aarch64/elf.hpp b/options/rtld/aarch64/elf.hpp index 802d1a22d9..79c639dec6 100644 --- a/options/rtld/aarch64/elf.hpp +++ b/options/rtld/aarch64/elf.hpp @@ -17,6 +17,12 @@ using elf_addr = Elf64_Addr; using elf_info = Elf64_Xword; using elf_addend = Elf64_Sxword; +using elf_version = Elf64_Half; +using elf_verdef = Elf64_Verdef; +using elf_verdaux = Elf64_Verdaux; +using elf_verneed = Elf64_Verneed; +using elf_vernaux = Elf64_Vernaux; + #define ELF_R_SYM ELF64_R_SYM #define ELF_R_TYPE ELF64_R_TYPE #define ELF_ST_BIND ELF64_ST_BIND diff --git a/options/rtld/m68k/elf.hpp b/options/rtld/m68k/elf.hpp index 255ef860af..45a9444da8 100644 --- a/options/rtld/m68k/elf.hpp +++ b/options/rtld/m68k/elf.hpp @@ -17,6 +17,12 @@ using elf_addr = Elf32_Addr; using elf_info = Elf32_Word; using elf_addend = Elf32_Sword; +using elf_version = Elf32_Half; +using elf_verdef = Elf32_Verdef; +using elf_verdaux = Elf32_Verdaux; +using elf_verneed = Elf32_Verneed; +using elf_vernaux = Elf32_Vernaux; + #define ELF_R_SYM ELF32_R_SYM #define ELF_R_TYPE ELF32_R_TYPE #define ELF_ST_BIND ELF32_ST_BIND diff --git a/options/rtld/riscv64/elf.hpp b/options/rtld/riscv64/elf.hpp index 5d7039aa34..f64f1f4803 100644 --- a/options/rtld/riscv64/elf.hpp +++ b/options/rtld/riscv64/elf.hpp @@ -17,6 +17,12 @@ using elf_addr = Elf64_Addr; using elf_info = Elf64_Xword; using elf_addend = Elf64_Sxword; +using elf_version = Elf64_Half; +using elf_verdef = Elf64_Verdef; +using elf_verdaux = Elf64_Verdaux; +using elf_verneed = Elf64_Verneed; +using elf_vernaux = Elf64_Vernaux; + #define ELF_R_SYM ELF64_R_SYM #define ELF_R_TYPE ELF64_R_TYPE #define ELF_ST_BIND ELF64_ST_BIND diff --git a/options/rtld/x86/elf.hpp b/options/rtld/x86/elf.hpp index 95800aa91e..0dc12ac87f 100644 --- a/options/rtld/x86/elf.hpp +++ b/options/rtld/x86/elf.hpp @@ -17,6 +17,12 @@ using elf_addr = Elf32_Addr; using elf_info = Elf32_Word; using elf_addend = Elf32_Sword; +using elf_version = Elf32_Half; +using elf_verdef = Elf32_Verdef; +using elf_verdaux = Elf32_Verdaux; +using elf_verneed = Elf32_Verneed; +using elf_vernaux = Elf32_Vernaux; + #define ELF_R_SYM ELF32_R_SYM #define ELF_R_TYPE ELF32_R_TYPE #define ELF_ST_BIND ELF32_ST_BIND diff --git a/options/rtld/x86_64/elf.hpp b/options/rtld/x86_64/elf.hpp index 2a806449bc..a55dfe8300 100644 --- a/options/rtld/x86_64/elf.hpp +++ b/options/rtld/x86_64/elf.hpp @@ -17,6 +17,12 @@ using elf_addr = Elf64_Addr; using elf_info = Elf64_Xword; using elf_addend = Elf64_Sxword; +using elf_version = Elf64_Half; +using elf_verdef = Elf64_Verdef; +using elf_verdaux = Elf64_Verdaux; +using elf_verneed = Elf64_Verneed; +using elf_vernaux = Elf64_Vernaux; + #define ELF_R_SYM ELF64_R_SYM #define ELF_R_TYPE ELF64_R_TYPE #define ELF_ST_BIND ELF64_ST_BIND From aec73796df9afe3fd7a6d38c51663fbce71ee799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20S=C5=82omi=C5=84ski?= Date: Fri, 27 Dec 2024 03:42:40 +0100 Subject: [PATCH 5/9] rtld: Parse version information from DSOs --- options/rtld/generic/linker.cpp | 185 +++++++++++++++++++++++++++++++- options/rtld/generic/linker.hpp | 86 +++++++++++++++ 2 files changed, 267 insertions(+), 4 deletions(-) diff --git a/options/rtld/generic/linker.cpp b/options/rtld/generic/linker.cpp index ec11f1b92b..88e4bbe27e 100644 --- a/options/rtld/generic/linker.cpp +++ b/options/rtld/generic/linker.cpp @@ -120,9 +120,11 @@ SharedObject *ObjectRepository::injectObjectFromDts(frg::string_view name, object->baseAddress = base_address; object->dynamic = dynamic; _parseDynamic(object); + _parseVerdef(object); _addLoadedObject(object); _discoverDependencies(object, globalScope.get(), rts); + _parseVerneed(object); return object; } @@ -137,9 +139,11 @@ SharedObject *ObjectRepository::injectObjectFromPhdrs(frg::string_view name, name.data(), std::move(path), true, globalScope.get(), rts); _fetchFromPhdrs(object, phdr_pointer, phdr_entry_size, num_phdrs, entry_pointer); _parseDynamic(object); + _parseVerdef(object); _addLoadedObject(object); _discoverDependencies(object, globalScope.get(), rts); + _parseVerneed(object); return object; } @@ -279,9 +283,11 @@ frg::expected ObjectRepository::requestObjectWithNa } _parseDynamic(object); + _parseVerdef(object); _addLoadedObject(object); _discoverDependencies(object, localScope, rts); + _parseVerneed(object); return object; } @@ -324,9 +330,11 @@ frg::expected ObjectRepository::requestObjectAtPath } _parseDynamic(object); + _parseVerdef(object); _addLoadedObject(object); _discoverDependencies(object, localScope, rts); + _parseVerneed(object); return object; } @@ -762,14 +770,27 @@ void ObjectRepository::_parseDynamic(SharedObject *object) { case DT_SONAME: soname_offset = dynamic->d_un.d_val; break; + // handle version information + case DT_VERSYM: + object->versionTableOffset = dynamic->d_un.d_ptr; + break; + case DT_VERDEF: + object->versionDefinitionTableOffset = dynamic->d_un.d_ptr; + break; + case DT_VERDEFNUM: + object->versionDefinitionCount = dynamic->d_un.d_val; + break; + case DT_VERNEED: + object->versionRequirementTableOffset = dynamic->d_un.d_ptr; + break; + case DT_VERNEEDNUM: + object->versionRequirementCount = dynamic->d_un.d_val; + break; // ignore unimportant tags case DT_NEEDED: // we handle this later case DT_RELA: case DT_RELASZ: case DT_RELAENT: case DT_RELACOUNT: case DT_REL: case DT_RELSZ: case DT_RELENT: case DT_RELCOUNT: case DT_RELR: case DT_RELRSZ: case DT_RELRENT: - case DT_VERSYM: - case DT_VERDEF: case DT_VERDEFNUM: - case DT_VERNEED: case DT_VERNEEDNUM: #ifdef __riscv case DT_TEXTREL: // Work around https://sourceware.org/bugzilla/show_bug.cgi?id=24673. #endif @@ -795,6 +816,160 @@ void ObjectRepository::_parseDynamic(SharedObject *object) { } } +void ObjectRepository::_parseVerdef(SharedObject *object) { + if(!object->versionDefinitionTableOffset) { + if(verbose) + mlibc::infoLogger() + << "mlibc: Object " << object->name + << " defines no versions" << frg::endlog; + return; + } + + if(verbose) + mlibc::infoLogger() + << "mlibc: Object " << object->name + << " defines " << object->versionDefinitionCount + << " version(s)" << frg::endlog; + + uintptr_t address = + object->baseAddress + + object->versionDefinitionTableOffset; + + for(size_t i = 0; i < object->versionDefinitionCount; i++) { + elf_verdef def; + memcpy(&def, reinterpret_cast(address), sizeof(elf_verdef)); + + // Required by spec. + __ensure(def.vd_version == 1); + __ensure(def.vd_cnt >= 1); + // TODO(qookie): Handle weak versions. + __ensure(!(def.vd_flags & ~VER_FLG_BASE)); + + // NOTE(qookie): glibc also ignores any additional Verdaux entries after the + // first one. + elf_verdaux aux; + memcpy(&aux, reinterpret_cast(address + def.vd_aux), sizeof(elf_verdaux)); + + const char *name = + reinterpret_cast( + object->baseAddress + + object->stringTableOffset + aux.vda_name); + + if(verbose) + mlibc::infoLogger() + << "mlibc: Object " << object->name + << " defines version " << name + << " (index " << def.vd_ndx << ")" + << frg::endlog; + + SymbolVersion ver{name, def.vd_hash}; + object->definedVersions.push(ver); + object->knownVersions.insert(def.vd_ndx, ver); + + address += def.vd_next; + } +} + +void ObjectRepository::_parseVerneed(SharedObject *object) { + if(!object->versionRequirementTableOffset) { + if(verbose) + mlibc::infoLogger() << "mlibc: Object " << object->name << " requires no versions" << frg::endlog; + return; + } + + if(verbose) + mlibc::infoLogger() + << "mlibc: Object " << object->name + << " requires " << object->versionRequirementCount + << " version(s)" << frg::endlog; + + uintptr_t address = + object->baseAddress + + object->versionRequirementTableOffset; + + for(size_t i = 0; i < object->versionRequirementCount; i++) { + elf_verneed need; + memcpy(&need, reinterpret_cast(address), sizeof(elf_verneed)); + + // Required by spec. + __ensure(need.vn_version == 1); + + frg::string_view file = + reinterpret_cast( + object->baseAddress + + object->stringTableOffset + need.vn_file); + + // Figure out the target object from file + SharedObject *target = nullptr; + for(auto dep : object->dependencies) { + if(verbose) + mlibc::infoLogger() + << "mlibc: Trying " << dep->name << " (SONAME: " + << dep->soName << ") to satisfy " << file << frg::endlog; + if(dep->name == file || (dep->soName && dep->soName == file)) { + target = dep; + break; + } + } + if(!target) + mlibc::panicLogger() + << "mlibc: No object named \"" + << file + << "\" found for VERNEED entry of object " + << object->name << frg::endlog; + + if(verbose) + mlibc::infoLogger() + << "mlibc: Object " << object->name + << " requires " << need.vn_cnt + << " version(s) from DSO " + << file << frg::endlog; + + uintptr_t auxAddr = address + need.vn_aux; + for(size_t j = 0; j < need.vn_cnt; j++) { + elf_vernaux aux; + memcpy(&aux, reinterpret_cast(auxAddr), sizeof(elf_vernaux)); + + const char *name = + reinterpret_cast( + object->baseAddress + + object->stringTableOffset + aux.vna_name); + + + if(verbose) + mlibc::infoLogger() + << "mlibc: Object " << object->name + << " requires version " << name + << " (index " << aux.vna_other + << ") from DSO " << file + << frg::endlog; + + frg::optional ver; + for(auto &def : target->definedVersions) { + if(def.hash() != aux.vna_hash) continue; + if(def.name() == name) { + ver = def; + break; + } + } + + if(!ver) + mlibc::panicLogger() + << "mlibc: Object " << target->name + << " does not define version \"" + << name << "\" needed by object " + << object->name << frg::endlog; + + bool isDefault = !(aux.vna_other & 0x8000); + // Bit 15 indicates whether the static linker should ignore this version. + object->knownVersions.insert(aux.vna_other & 0x7FFF, isDefault ? ver->makeDefault() : *ver); + + auxAddr += aux.vna_next; + } + address += need.vn_next; + } +} + void ObjectRepository::_discoverDependencies(SharedObject *object, Scope *localScope, uint64_t rts) { if(object->isMainObject) { @@ -850,7 +1025,9 @@ SharedObject::SharedObject(const char *name, frg::string path, globalOffsetTable(nullptr), entry(nullptr), tlsSegmentSize(0), tlsAlignment(0), tlsImageSize(0), tlsImagePtr(nullptr), tlsInitialized(false), hashTableOffset(0), symbolTableOffset(0), - stringTableOffset(0), lazyRelocTableOffset(0), lazyTableSize(0), + stringTableOffset(0), + knownVersions({}, getAllocator()), definedVersions(getAllocator()), + lazyRelocTableOffset(0), lazyTableSize(0), lazyExplicitAddend(false), symbolicResolution(false), eagerBinding(false), haveStaticTls(false), dependencies(getAllocator()), tlsModel(TlsModel::null), diff --git a/options/rtld/generic/linker.hpp b/options/rtld/generic/linker.hpp index d8017d204c..65d70e8f22 100644 --- a/options/rtld/generic/linker.hpp +++ b/options/rtld/generic/linker.hpp @@ -14,6 +14,8 @@ struct ObjectRepository; struct Scope; struct Loader; struct SharedObject; +struct ObjectSymbol; +struct SymbolVersion; extern uint64_t rtsCounter; @@ -33,6 +35,8 @@ enum class LinkerError { invalidProgramHeader }; +uint32_t elf64Hash(frg::string_view string); + // -------------------------------------------------------- // ObjectRepository // -------------------------------------------------------- @@ -84,6 +88,9 @@ struct ObjectRepository { void _parseDynamic(SharedObject *object); + void _parseVerdef(SharedObject *object); + void _parseVerneed(SharedObject *object); + void _discoverDependencies(SharedObject *object, Scope *localScope, uint64_t rts); void _addLoadedObject(SharedObject *object); @@ -175,6 +182,23 @@ struct SharedObject { uintptr_t symbolTableOffset; uintptr_t stringTableOffset; + // Version tables of this shared object + uintptr_t versionTableOffset = 0; + uintptr_t versionDefinitionTableOffset = 0; + size_t versionDefinitionCount = 0; + uintptr_t versionRequirementTableOffset = 0; + size_t versionRequirementCount = 0; + + // Versions we know about for this object's VERSYM. + frg::hash_map< + elf_version, + SymbolVersion, + frg::hash, + MemoryAllocator + > knownVersions; + // Versions that this object defines. + frg::vector definedVersions; + const char *runPath = nullptr; // save the lazy JUMP_SLOT relocation table @@ -333,6 +357,68 @@ struct ObjectSymbol { frg::optional resolveInObject(SharedObject *object, frg::string_view string); +// -------------------------------------------------------- +// SymbolVersion +// -------------------------------------------------------- + +struct SymbolVersion { + SymbolVersion(const char *name, uint32_t hash) + : _local{false}, _global{false}, _default{false} + , _name{name}, _hash{hash} { } + + SymbolVersion(int idx) + : _local{idx == 0}, _global{idx == 1}, _default{false} + , _name{""}, _hash{0} { } + + SymbolVersion(const char *name) + : _local{false}, _global{false}, _default{false} + , _name{name}, _hash{elf64Hash(name)} { } + + bool isLocal() const { + return _local; + } + + bool isGlobal() const { + return _global; + } + + bool isDefault() const { + return _default; + } + + frg::string_view name() const { + if(_local) return "(*local*)"; + if(_global) return "(*global*)"; + return _name; + } + + uint32_t hash() const { + return _hash; + } + + bool operator==(const SymbolVersion &other) const { + if(_local || other._local) return _local && other._local; + if(_global || other._global) return _global && other._global; + if(_hash != other._hash) return false; + return _name == other._name; + } + + SymbolVersion makeDefault() const { + auto copy = *this; + copy._default = true; + + return copy; + } + +private: + bool _local, _global; + bool _default; + + frg::string_view _name; + uint32_t _hash; +}; + + // -------------------------------------------------------- // Scope // -------------------------------------------------------- From b5bb52af4b054d77d129b59100f3256917954699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20S=C5=82omi=C5=84ski?= Date: Fri, 27 Dec 2024 03:44:01 +0100 Subject: [PATCH 6/9] rtld: Handle versioned symbol resolution correctly All resolution functions now optionally take the version of the symbol to resolve to. Specifying no version means the default is picked (for dlsym etc). --- options/rtld/generic/linker.cpp | 163 +++++++++++++++++++++++--------- options/rtld/generic/linker.hpp | 17 +++- options/rtld/generic/main.cpp | 13 ++- 3 files changed, 136 insertions(+), 57 deletions(-) diff --git a/options/rtld/generic/linker.cpp b/options/rtld/generic/linker.cpp index 88e4bbe27e..ecb5335278 100644 --- a/options/rtld/generic/linker.cpp +++ b/options/rtld/generic/linker.cpp @@ -30,6 +30,7 @@ constexpr bool stillSlightlyVerbose = false; constexpr bool logBaseAddresses = false; constexpr bool logRpath = false; constexpr bool logLdPath = false; +constexpr bool logSymbolVersions = false; constexpr bool eagerBinding = true; #if defined(__x86_64__) || defined(__i386__) @@ -862,9 +863,11 @@ void ObjectRepository::_parseVerdef(SharedObject *object) { << " (index " << def.vd_ndx << ")" << frg::endlog; - SymbolVersion ver{name, def.vd_hash}; - object->definedVersions.push(ver); - object->knownVersions.insert(def.vd_ndx, ver); + if(!(def.vd_flags & VER_FLG_BASE)) { + SymbolVersion ver{name, def.vd_hash}; + object->definedVersions.push(ver); + object->knownVersions.insert(def.vd_ndx, ver); + } address += def.vd_next; } @@ -930,12 +933,14 @@ void ObjectRepository::_parseVerneed(SharedObject *object) { elf_vernaux aux; memcpy(&aux, reinterpret_cast(auxAddr), sizeof(elf_vernaux)); + // TODO(qookie): Handle weak versions. + __ensure(!aux.vna_flags); + const char *name = reinterpret_cast( object->baseAddress + object->stringTableOffset + aux.vna_name); - if(verbose) mlibc::infoLogger() << "mlibc: Object " << object->name @@ -1041,16 +1046,73 @@ SharedObject::SharedObject(const char *name, const char *path, frg::string { path, getAllocator() }, is_main_object, localScope, object_rts) {} +frg::tuple SharedObject::getSymbolByIndex(size_t index) { + SymbolVersion ver{1}; // If we don't have any version information, treat all symbols as global. + ObjectSymbol sym{ + this, + reinterpret_cast( + baseAddress + + symbolTableOffset + + index * sizeof(elf_sym))}; + + if(versionTableOffset) { + // Pull out the VERSYM entry for this symbol + elf_version verIdx; + memcpy( + &verIdx, + reinterpret_cast( + baseAddress + + versionTableOffset + + index * sizeof(elf_version)), + sizeof(elf_version) + ); + + // Bit 15 indicates that this version is not the default one. + bool isDefault = !(verIdx & 0x8000); + verIdx &= 0x7FFF; + + // 0 and 1 are special, 0 is local, 1 is global (not in VERDEF/VERNEED) + if(verIdx != 0 && verIdx != 1) { + auto maybeVer = knownVersions.find(verIdx); + if(maybeVer == knownVersions.end()) + mlibc::panicLogger() + << "mlibc: Symbol " << sym.getString() + << " of object " << name + << " has invalid version index " << verIdx + << frg::endlog; + + ver = maybeVer->get<1>(); + } else { + ver = SymbolVersion{verIdx}; + } + + if(isDefault) + ver = ver.makeDefault(); + + if(logSymbolVersions) + mlibc::infoLogger() + << "mlibc: Symbol " << sym.getString() + << " of object " << name + << " has version " << ver.name() + << " and " << (ver.isDefault() ? "is" : "isn't") + << " the default version" + << frg::endlog; + } else { + // If we have no version information, the only symbol we've got is the default. + ver = ver.makeDefault(); + } + + return {sym, ver}; +} + void processLateRelocation(Relocation rel) { // resolve the symbol if there is a symbol frg::optional p; if(rel.symbol_index()) { - auto symbol = (elf_sym *)(rel.object()->baseAddress + rel.object()->symbolTableOffset - + rel.symbol_index() * sizeof(elf_sym)); - ObjectSymbol r(rel.object(), symbol); + auto [sym, ver] = rel.object()->getSymbolByIndex(rel.symbol_index()); p = Scope::resolveGlobalOrLocal(*globalScope, rel.object()->localScope, - r.getString(), rel.object()->objectRts, Scope::resolveCopy); + sym.getString(), rel.object()->objectRts, Scope::resolveCopy, ver); } switch(rel.type()) { @@ -1369,7 +1431,8 @@ uint32_t gnuHash(frg::string_view string) { } // TODO: move this to some namespace or class? -frg::optional resolveInObject(SharedObject *object, frg::string_view string) { +frg::optional resolveInObject(SharedObject *object, frg::string_view string, + frg::optional version) { // Checks if the symbol can be used to satisfy the dependency. auto eligible = [&] (ObjectSymbol cand) { if(cand.symbol()->st_shndx == SHN_UNDEF) @@ -1382,6 +1445,21 @@ frg::optional resolveInObject(SharedObject *object, frg::string_vi return true; }; + // Checks if the symbol's version matches the desired version. + auto correctVersion = [&] (SymbolVersion candVersion) { + // TODO(qookie): Not sure if local symbols should participate in dynamic symbol resolution + if(!version && (candVersion.isDefault() || candVersion.isLocal() || candVersion.isGlobal())) + return true; + // Caller requested default version, but this isn't it. + if(!version) + return false; + // If the requested version is global (caller has VERNEED but not for this symbol), + // use the default one. + if(version->isGlobal() && !candVersion.isGlobal() && !candVersion.isLocal() && candVersion.isDefault()) + return true; + return *version == candVersion; + }; + if (object->hashStyle == HashStyle::systemV) { auto hash_table = (Elf64_Word *)(object->baseAddress + object->hashTableOffset); Elf64_Word num_buckets = hash_table[0]; @@ -1389,9 +1467,8 @@ frg::optional resolveInObject(SharedObject *object, frg::string_vi auto index = hash_table[2 + bucket]; while(index != 0) { - ObjectSymbol cand{object, (elf_sym *)(object->baseAddress - + object->symbolTableOffset + index * sizeof(elf_sym))}; - if(eligible(cand) && frg::string_view{cand.getString()} == string) + auto [cand, ver] = object->getSymbolByIndex(index); + if(eligible(cand) && frg::string_view{cand.getString()} == string && correctVersion(ver)) return cand; index = hash_table[2 + num_buckets + index]; @@ -1431,9 +1508,8 @@ frg::optional resolveInObject(SharedObject *object, frg::string_vi // chains[] contains an array of hashes, parallel to the symbol table. auto chash = chains[index - hash_table->symbolOffset]; if ((chash & ~1) == (hash & ~1)) { - ObjectSymbol cand{object, (elf_sym *)(object->baseAddress - + object->symbolTableOffset + index * sizeof(elf_sym))}; - if(eligible(cand) && frg::string_view{cand.getString()} == string) + auto [cand, ver] = object->getSymbolByIndex(index); + if(eligible(cand) && frg::string_view{cand.getString()} == string && correctVersion(ver)) return cand; } @@ -1446,7 +1522,7 @@ frg::optional resolveInObject(SharedObject *object, frg::string_vi } frg::optional Scope::_resolveNext(frg::string_view string, - SharedObject *target) { + SharedObject *target, frg::optional version) { // Skip objects until we find the target, and only look for symbols after that. size_t i; for (i = 0; i < _objects.size(); i++) { @@ -1463,7 +1539,7 @@ frg::optional Scope::_resolveNext(frg::string_view string, if(_objects[i]->isMainObject) continue; - frg::optional p = resolveInObject(_objects[i], string); + frg::optional p = resolveInObject(_objects[i], string, version); if(p) return p; } @@ -1485,25 +1561,28 @@ void Scope::appendObject(SharedObject *object) { } frg::optional Scope::resolveGlobalOrLocal(Scope &globalScope, - Scope *localScope, frg::string_view string, uint64_t skipRts, ResolveFlags flags) { - auto sym = globalScope.resolveSymbol(string, skipRts, flags | skipGlobalAfterRts); + Scope *localScope, frg::string_view string, uint64_t skipRts, ResolveFlags flags, + frg::optional version) { + auto sym = globalScope.resolveSymbol(string, skipRts, flags | skipGlobalAfterRts, version); if(!sym && localScope) - sym = localScope->resolveSymbol(string, skipRts, flags | skipGlobalAfterRts); + sym = localScope->resolveSymbol(string, skipRts, flags | skipGlobalAfterRts, version); return sym; } frg::optional Scope::resolveGlobalOrLocalNext(Scope &globalScope, - Scope *localScope, frg::string_view string, SharedObject *origin) { - auto sym = globalScope._resolveNext(string, origin); + Scope *localScope, frg::string_view string, SharedObject *origin, + frg::optional version) { + auto sym = globalScope._resolveNext(string, origin, version); if(!sym && localScope) { - sym = localScope->_resolveNext(string, origin); + sym = localScope->_resolveNext(string, origin, version); } return sym; } // TODO: let this return uintptr_t frg::optional Scope::resolveSymbol(frg::string_view string, - uint64_t skipRts, ResolveFlags flags) { + uint64_t skipRts, ResolveFlags flags, + frg::optional version) { for (auto object : _objects) { if((flags & resolveCopy) && object->isMainObject) continue; @@ -1517,7 +1596,7 @@ frg::optional Scope::resolveSymbol(frg::string_view string, continue; } - frg::optional p = resolveInObject(object, string); + frg::optional p = resolveInObject(object, string, version); if(p) return p; } @@ -1784,20 +1863,18 @@ void Loader::_processRelocations(Relocation &rel) { // resolve the symbol if there is a symbol frg::optional p; if(rel.symbol_index()) { - auto symbol = (elf_sym *)(rel.object()->baseAddress + rel.object()->symbolTableOffset - + rel.symbol_index() * sizeof(elf_sym)); - ObjectSymbol r(rel.object(), symbol); + auto [sym, ver] = rel.object()->getSymbolByIndex(rel.symbol_index()); p = Scope::resolveGlobalOrLocal(*globalScope, rel.object()->localScope, - r.getString(), rel.object()->objectRts, 0); + sym.getString(), rel.object()->objectRts, 0, ver); if(!p) { - if(ELF_ST_BIND(symbol->st_info) != STB_WEAK) + if(ELF_ST_BIND(sym.symbol()->st_info) != STB_WEAK) mlibc::panicLogger() << "Unresolved load-time symbol " - << r.getString() << " in object " << rel.object()->name << frg::endlog; + << sym.getString() << " in object " << rel.object()->name << frg::endlog; if(verbose) mlibc::infoLogger() << "rtld: Unresolved weak load-time symbol " - << r.getString() << " in object " << rel.object()->name << frg::endlog; + << sym.getString() << " in object " << rel.object()->name << frg::endlog; } } @@ -2025,19 +2102,17 @@ void Loader::_processLazyRelocations(SharedObject *object) { switch (type) { case R_JUMP_SLOT: if(eagerBinding) { - auto symbol = (elf_sym *)(object->baseAddress + object->symbolTableOffset - + symbol_index * sizeof(elf_sym)); - ObjectSymbol r(object, symbol); - auto p = Scope::resolveGlobalOrLocal(*globalScope, object->localScope, r.getString(), object->objectRts, 0); + auto [sym, ver] = object->getSymbolByIndex(symbol_index); + auto p = Scope::resolveGlobalOrLocal(*globalScope, object->localScope, sym.getString(), object->objectRts, 0, ver); if(!p) { - if(ELF_ST_BIND(symbol->st_info) != STB_WEAK) + if(ELF_ST_BIND(sym.symbol()->st_info) != STB_WEAK) mlibc::panicLogger() << "rtld: Unresolved JUMP_SLOT symbol " - << r.getString() << " in object " << object->name << frg::endlog; + << sym.getString() << " in object " << object->name << frg::endlog; if(verbose) mlibc::infoLogger() << "rtld: Unresolved weak JUMP_SLOT symbol " - << r.getString() << " in object " << object->name << frg::endlog; + << sym.getString() << " in object " << object->name << frg::endlog; *((uintptr_t *)rel_addr) = 0; }else{ *((uintptr_t *)rel_addr) = p->virtualAddress(); @@ -2061,15 +2136,13 @@ void Loader::_processLazyRelocations(SharedObject *object) { SharedObject *target = nullptr; if (symbol_index) { - auto symbol = (elf_sym *)(object->baseAddress + object->symbolTableOffset - + symbol_index * sizeof(elf_sym)); - ObjectSymbol r(object, symbol); - auto p = Scope::resolveGlobalOrLocal(*globalScope, object->localScope, r.getString(), object->objectRts, 0); + auto [sym, ver] = object->getSymbolByIndex(symbol_index); + auto p = Scope::resolveGlobalOrLocal(*globalScope, object->localScope, sym.getString(), object->objectRts, 0, ver); if (!p) { - __ensure(ELF_ST_BIND(symbol->st_info) != STB_WEAK); + __ensure(ELF_ST_BIND(sym.symbol()->st_info) != STB_WEAK); mlibc::panicLogger() << "rtld: Unresolved TLSDESC for symbol " - << r.getString() << " in object " << object->name << frg::endlog; + << sym.getString() << " in object " << object->name << frg::endlog; } else { target = p->object(); if (p->symbol()) diff --git a/options/rtld/generic/linker.hpp b/options/rtld/generic/linker.hpp index 65d70e8f22..9bf147635a 100644 --- a/options/rtld/generic/linker.hpp +++ b/options/rtld/generic/linker.hpp @@ -230,6 +230,8 @@ struct SharedObject { void *phdrPointer = nullptr; size_t phdrEntrySize = 0; size_t phdrCount = 0; + + frg::tuple getSymbolByIndex(size_t index); }; struct Relocation { @@ -355,7 +357,8 @@ struct ObjectSymbol { const elf_sym *_symbol; }; -frg::optional resolveInObject(SharedObject *object, frg::string_view string); +frg::optional resolveInObject(SharedObject *object, frg::string_view string, + frg::optional version); // -------------------------------------------------------- // SymbolVersion @@ -429,20 +432,24 @@ struct Scope { static inline constexpr ResolveFlags skipGlobalAfterRts = 1 << 1; static frg::optional resolveGlobalOrLocal(Scope &globalScope, - Scope *localScope, frg::string_view string, uint64_t skipRts, ResolveFlags flags); + Scope *localScope, frg::string_view string, uint64_t skipRts, ResolveFlags flags, + frg::optional version); static frg::optional resolveGlobalOrLocalNext(Scope &globalScope, - Scope *localScope, frg::string_view string, SharedObject *origin); + Scope *localScope, frg::string_view string, SharedObject *origin, + frg::optional version); Scope(bool isGlobal = false); void appendObject(SharedObject *object); - frg::optional resolveSymbol(frg::string_view string, uint64_t skipRts, ResolveFlags flags); + frg::optional resolveSymbol(frg::string_view string, uint64_t skipRts, ResolveFlags flags, + frg::optional version); bool isGlobal; private: - frg::optional _resolveNext(frg::string_view string, SharedObject *target); + frg::optional _resolveNext(frg::string_view string, SharedObject *target, + frg::optional version); public: // TODO: Make this private again. (Was made public for __dlapi_reverse()). frg::vector _objects; }; diff --git a/options/rtld/generic/main.cpp b/options/rtld/generic/main.cpp index de7995b924..7460f9bf3c 100644 --- a/options/rtld/generic/main.cpp +++ b/options/rtld/generic/main.cpp @@ -207,10 +207,8 @@ extern "C" void *lazyRelocate(SharedObject *object, unsigned int rel_index) { __ensure(type == R_X86_64_JUMP_SLOT); __ensure(ELF_CLASS == ELFCLASS64); - auto symbol = (elf_sym *)(object->baseAddress + object->symbolTableOffset - + symbol_index * sizeof(elf_sym)); - ObjectSymbol r(object, symbol); - auto p = Scope::resolveGlobalOrLocal(*globalScope, object->localScope, r.getString(), object->objectRts, 0); + auto [sym, ver] = object->getSymbolByIndex(symbol_index); + auto p = Scope::resolveGlobalOrLocal(*globalScope, object->localScope, sym.getString(), object->objectRts, 0, ver); if(!p) mlibc::panicLogger() << "Unresolved JUMP_SLOT symbol" << frg::endlog; @@ -686,9 +684,10 @@ void *__dlapi_resolve(void *handle, const char *string, void *returnAddress) { } frg::optional target; + frg::optional targetVersion = frg::null_opt; if (handle == RTLD_DEFAULT) { - target = globalScope->resolveSymbol(string, 0, 0); + target = globalScope->resolveSymbol(string, 0, 0, targetVersion); } else if (handle == RTLD_NEXT) { SharedObject *origin = initialRepository->findCaller(returnAddress); if (!origin) { @@ -696,7 +695,7 @@ void *__dlapi_resolve(void *handle, const char *string, void *returnAddress) { << "(ra = " << returnAddress << ")" << frg::endlog; } - target = Scope::resolveGlobalOrLocalNext(*globalScope, origin->localScope, string, origin); + target = Scope::resolveGlobalOrLocalNext(*globalScope, origin->localScope, string, origin, targetVersion); } else { // POSIX does not unambiguously state how dlsym() is supposed to work; it just // states that "The symbol resolution algorithm used shall be dependency order @@ -726,7 +725,7 @@ void *__dlapi_resolve(void *handle, const char *string, void *returnAddress) { for(size_t i = 0; i < queue.size(); i++) { auto current = queue[i]; - target = resolveInObject(current, string); + target = resolveInObject(current, string, targetVersion); if(target) break; From d7b1d721231677e177d42d9e488122923eab2a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20S=C5=82omi=C5=84ski?= Date: Fri, 27 Dec 2024 03:45:21 +0100 Subject: [PATCH 7/9] posix,rtld: Make use of version in dlvsym --- options/posix/generic/dlfcn.cpp | 7 +++---- options/rtld/generic/main.cpp | 5 ++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/options/posix/generic/dlfcn.cpp b/options/posix/generic/dlfcn.cpp index fc34a3b985..91e855ba4e 100644 --- a/options/posix/generic/dlfcn.cpp +++ b/options/posix/generic/dlfcn.cpp @@ -15,7 +15,7 @@ struct __dlapi_symbol { extern "C" const char *__dlapi_error(); extern "C" void *__dlapi_open(const char *, int, void *); -extern "C" void *__dlapi_resolve(void *, const char *, void *); +extern "C" void *__dlapi_resolve(void *, const char *, void *, const char *); extern "C" int __dlapi_reverse(const void *, __dlapi_symbol *); extern "C" int __dlapi_close(void *); @@ -36,14 +36,13 @@ void *dlopen(const char *file, int flags) { [[gnu::noinline]] void *dlsym(void *__restrict handle, const char *__restrict string) { auto ra = __builtin_extract_return_addr(__builtin_return_address(0)); - return __dlapi_resolve(handle, string, ra); + return __dlapi_resolve(handle, string, ra, NULL); } [[gnu::noinline]] void *dlvsym(void *__restrict handle, const char *__restrict string, const char *__restrict version) { - mlibc::infoLogger() << "mlibc: dlvsym ignores version " << version << frg::endlog; auto ra = __builtin_extract_return_addr(__builtin_return_address(0)); - return __dlapi_resolve(handle, string, ra); + return __dlapi_resolve(handle, string, ra, version); } //gnu extensions diff --git a/options/rtld/generic/main.cpp b/options/rtld/generic/main.cpp index 7460f9bf3c..48138174fc 100644 --- a/options/rtld/generic/main.cpp +++ b/options/rtld/generic/main.cpp @@ -666,7 +666,7 @@ void *__dlapi_open(const char *file, int flags, void *returnAddress) { } extern "C" [[ gnu::visibility("default") ]] -void *__dlapi_resolve(void *handle, const char *string, void *returnAddress) { +void *__dlapi_resolve(void *handle, const char *string, void *returnAddress, const char *version) { if (logDlCalls) { const char *name; bool quote = false; @@ -686,6 +686,9 @@ void *__dlapi_resolve(void *handle, const char *string, void *returnAddress) { frg::optional target; frg::optional targetVersion = frg::null_opt; + if(version) + targetVersion = SymbolVersion{version}; + if (handle == RTLD_DEFAULT) { target = globalScope->resolveSymbol(string, 0, 0, targetVersion); } else if (handle == RTLD_NEXT) { From 960f1b2ad70199f213198f2be6bae2f2ea804ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20S=C5=82omi=C5=84ski?= Date: Thu, 26 Dec 2024 22:00:37 +0100 Subject: [PATCH 8/9] tests: Add test for symbol versioning --- tests/rtld/meson.build | 1 + tests/rtld/symver/libfoo.c | 15 ++++++++++++++ tests/rtld/symver/libfoo.ver | 14 +++++++++++++ tests/rtld/symver/meson.build | 11 ++++++++++ tests/rtld/symver/test.c | 39 +++++++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+) create mode 100644 tests/rtld/symver/libfoo.c create mode 100644 tests/rtld/symver/libfoo.ver create mode 100644 tests/rtld/symver/meson.build create mode 100644 tests/rtld/symver/test.c diff --git a/tests/rtld/meson.build b/tests/rtld/meson.build index c8dfe73647..2a8d852d59 100644 --- a/tests/rtld/meson.build +++ b/tests/rtld/meson.build @@ -15,6 +15,7 @@ rtld_test_cases = [ 'scope5', 'tls_align', 'relr', + 'symver', ] host_libc_rtld_nosan_test_cases = [ diff --git a/tests/rtld/symver/libfoo.c b/tests/rtld/symver/libfoo.c new file mode 100644 index 0000000000..35141700f8 --- /dev/null +++ b/tests/rtld/symver/libfoo.c @@ -0,0 +1,15 @@ +int foo_v1(void) { + return 1; +} + +int foo_v2(void) { + return 2; +} + +int foo_v3(void) { + return 3; +} + +asm(".symver foo_v1, foo@FOO_1"); +asm(".symver foo_v2, foo@FOO_2"); +asm(".symver foo_v3, foo@@FOO_3"); // default version (due to @@ vs @) diff --git a/tests/rtld/symver/libfoo.ver b/tests/rtld/symver/libfoo.ver new file mode 100644 index 0000000000..ee16bdc2d3 --- /dev/null +++ b/tests/rtld/symver/libfoo.ver @@ -0,0 +1,14 @@ +FOO_1 { + global: foo; + local: *; +}; + +FOO_2 { + global: foo; + local: *; +}; + +FOO_3 { + global: foo; + local: *; +}; diff --git a/tests/rtld/symver/meson.build b/tests/rtld/symver/meson.build new file mode 100644 index 0000000000..65dde2f1d1 --- /dev/null +++ b/tests/rtld/symver/meson.build @@ -0,0 +1,11 @@ +version_script = meson.current_source_dir() / 'libfoo.ver' + +libfoo = shared_library('foo', 'libfoo.c', + link_args : ['-Wl,--version-script', version_script]) +test_depends = [libfoo] +test_link_with = [libfoo] + +libfoo_native = shared_library('native-foo', 'libfoo.c', native: true, + link_args : ['-Wl,--version-script', version_script]) +test_native_depends = [libfoo_native] +test_native_link_with = [libfoo_native] diff --git a/tests/rtld/symver/test.c b/tests/rtld/symver/test.c new file mode 100644 index 0000000000..3a53a9f650 --- /dev/null +++ b/tests/rtld/symver/test.c @@ -0,0 +1,39 @@ +#include +#include +#include + +#ifdef USE_HOST_LIBC +#define LIBFOO "libnative-foo.so" +#else +#define LIBFOO "libfoo.so" +#endif + +int foo(void); + +int main() { + int ver = foo(); + fprintf(stderr, "called foo version %d\n", ver); + assert(ver == 3); // version 3 is the default for libfoo.so + + void *libfoo_h = dlopen(LIBFOO, RTLD_GLOBAL | RTLD_NOW); + assert(libfoo_h); + + int (*foo1)(void) = dlvsym(libfoo_h, "foo", "FOO_1"); + assert(foo1); + + int (*foo2)(void) = dlvsym(libfoo_h, "foo", "FOO_2"); + assert(foo2); + + int (*foo3)(void) = dlvsym(libfoo_h, "foo", "FOO_3"); + assert(foo3); + + int (*foo_def)(void) = dlsym(libfoo_h, "foo"); + assert(foo_def); + + assert(foo1() == 1); + assert(foo2() == 2); + assert(foo3() == 3); + assert(foo3 == foo_def); + + return 0; +} From 41173bc85349259c76604703a73fa9e670e75b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20S=C5=82omi=C5=84ski?= Date: Fri, 27 Dec 2024 19:54:47 +0100 Subject: [PATCH 9/9] tests: Compile tests with -fno-builtin Some compiler optimizations done via builtins can cause the tests to fail (e.g. compiler optimizes out failing call to calloc, and assumes that it succeeds). --- tests/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/meson.build b/tests/meson.build index a4f2a4a109..7813694341 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -147,10 +147,10 @@ extra_cflags_test_cases = { test_sources = [] test_link_args = [] -test_c_args = ['-D_GNU_SOURCE'] +test_c_args = ['-D_GNU_SOURCE', '-fno-builtin'] use_pie = false -host_test_c_args = [] +host_test_c_args = ['-fno-builtin'] c_compiler = meson.get_compiler('c')