From 8dccbaedd236021242d49a6ba65f494be3b3fa3e Mon Sep 17 00:00:00 2001 From: Elfgo Date: Thu, 7 Aug 2025 06:27:14 +0000 Subject: [PATCH] [lld][Compiler-RT] NDK API Compatibility Feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As the versions of HarmonyOS continue to proliferate, the HarmonyOS NDK has also evolved from version 10 all the way to 20. In each NDK version, there are newly added APIs as well as deprecated ones. If an app developer wants to build an application that supports versions from xx to yy and utilizes new APIs introduced within this version range, they need to ensure compatibility when using these APIs. This way, when the app is deployed on a system image that does not support certain APIs, the app can still run normally—though the specific functionality relying on those APIs will be unavailable. 1.For NDK APIs, version checks should be performed before invocation—APIs should only be called if the current system version ≥ their minimum supported version. This can be achieved using LLVM's existing __builtin_available(platform, version) function. At runtime, this function translates to a call to __isOSVersionAtLeast(Major, Minor, Subminor) in compiler-rt/lib/builtins/os_version_check.c. Currently, the logic inside __isOSVersionAtLeast needs to be adapted for OHOS (OpenHarmony). Solution Design: Read the system version. Compare version numbers: Check the current system version against the version specified in __builtin_available. Return a Boolean result: true if the system version ≥ the specified version. false otherwise. This ensures backward compatibility—if an API is unavailable on older systems, the app can gracefully fall back without crashing. (Note: Adjust Major, Minor, and Subminor based on HarmonyOS's actual versioning scheme if needed.) 2.For Weak Library Handling Dynamic libraries missing on older devices can be marked as weak libraries. This prevents runtime errors when loading these libraries, allowing apps to function (with reduced features) even if certain dependencies are unavailable. Solution Design: Developer Specification Developers use the linker flag --ohos-weak-library xxx.so to notify lld that the library should be linked weakly. Compiler/Linker Action During linking, the compiler adds a DT_OHOS_WEAK_LIBRARY entry in the .dynamic section of the ELF file to indicate weak linking for the specified library. Loader (musl) Enhancement The loader (musl) is modified to: Load libraries listed in DT_NEEDED as usual. Also check for DT_OHOS_WEAK_LIBRARY entries. Weak Library Loading When loading a weak library (DT_OHOS_WEAK_LIBRARY): If the library exists, load it normally. If the library is missing, silently skip (no error) and continue execution. This ensures backward compatibility while maintaining app stability on older OS versions. Change-Id: Iab1c2ef2f0a13abab9f7c991d3eb5c13fbce40a4 Signed-off-by: Elfgo --- compiler-rt/lib/builtins/os_version_check.c | 32 +++++++++++++++++++ lld/ELF/Config.h | 1 + lld/ELF/Driver.cpp | 1 + lld/ELF/Options.td | 2 ++ lld/ELF/SyntheticSections.cpp | 17 ++++++++-- .../include/llvm/BinaryFormat/DynamicTags.def | 3 ++ llvm/tools/llvm-objdump/ELFDump.cpp | 5 ++- llvm/tools/llvm-readobj/ELFDumper.cpp | 4 +++ 8 files changed, 61 insertions(+), 4 deletions(-) diff --git a/compiler-rt/lib/builtins/os_version_check.c b/compiler-rt/lib/builtins/os_version_check.c index ebfb2dfc72dd..b1a3dd0f3d98 100644 --- a/compiler-rt/lib/builtins/os_version_check.c +++ b/compiler-rt/lib/builtins/os_version_check.c @@ -316,6 +316,38 @@ int32_t __isOSVersionAtLeast(int32_t Major, int32_t Minor, int32_t Subminor) { (IsPreRelease && Major == __ANDROID_API_FUTURE__); } +#elif __OHOS__ + +#include +#include +#include +#include + +static int OHVersion; +static int DistOsVersion; + +extern __attribute__((weak)) int OH_GetSdkApiVersion(void); +extern __attribute__((weak)) int OH_GetDistributionOSApiVersion(void); + +static void readOHOSVersion(void) { + OHVersion = OH_GetSdkApiVersion(); + DistOsVersion = OH_GetDistributionOSApiVersion(); +} + +int32_t __isOSVersionAtLeast(int32_t Major, int32_t Minor, int32_t Subminor) { + // `OH_GetSdkApiVersion()` and `OH_GetDistributionOSApiVersion()` are begining + // from API10. There are no longer any devices below API12 now. If there really + // is an API10 device (theoretically), we set the default value to 1 here. + if (Major < 10) + return 1; + + // Just readOHOSVersion once When first call `__isOSVersionAtLeast` + static pthread_once_t once = PTHREAD_ONCE_INIT; + pthread_once(&once, readOHOSVersion); + + return OHVersion > Major || (OHVersion == Major && DistOsVersion >= Subminor); +} + #else // Silence an empty translation unit warning. diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h index d6babe9f74c2..04a1155ee402 100644 --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -150,6 +150,7 @@ struct Configuration { std::vector symbolOrderingFile; std::vector thinLTOModulesToCompile; std::vector undefined; + std::vector weakLibrary; // OHOS_LOCAL std::vector dynamicList; std::vector buildIdVector; llvm::MapVector, diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp index daf16ed3ca44..22d0670cd2e8 100644 --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1081,6 +1081,7 @@ static void readConfigs(opt::InputArgList &args) { OPT_no_android_memtag_stack, false); config->androidMemtagMode = getMemtagMode(args); config->auxiliaryList = args::getStrings(args, OPT_auxiliary); + config->weakLibrary = args::getStrings(args, OPT_weak_library); // OHOS_LOCAL if (opt::Arg *arg = args.getLastArg(OPT_Bno_symbolic, OPT_Bsymbolic_non_weak_functions, OPT_Bsymbolic_functions, OPT_Bsymbolic)) { diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td index 4e6c20f5c7f6..c2d5272d3594 100644 --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -362,6 +362,8 @@ defm rosegment: BB<"rosegment", defm rpath: Eq<"rpath", "Add a DT_RUNPATH to the output">; +defm weak_library: Eq<"ohos-weak-library", "Mark library and its references as weak imports">; // OHOS_LOCAL + def relocatable: F<"relocatable">, HelpText<"Create relocatable object file">; defm retain_symbols_file: diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp index 2d73481c417f..51e755eb17e5 100644 --- a/lld/ELF/SyntheticSections.cpp +++ b/lld/ELF/SyntheticSections.cpp @@ -1416,9 +1416,20 @@ DynamicSection::computeContents() { addInt(config->enableNewDtags ? DT_RUNPATH : DT_RPATH, part.dynStrTab->addString(config->rpath)); - for (SharedFile *file : ctx->sharedFiles) - if (file->isNeeded) - addInt(DT_NEEDED, part.dynStrTab->addString(file->soName)); + // OHOS_LOCAL begin + for (SharedFile *file : ctx->sharedFiles) { + if (file->isNeeded) { + StringRef fileSoName = file->soName; + // If this dylib is set as weak-library, we don't place it in DT_NEEDED + // rather in DT_OHOS_WEAK_LIBRARY. + if (llvm::any_of(config->weakLibrary, + [&](StringRef s) { return fileSoName.equals(s); })) + addInt(DT_OHOS_WEAK_LIBRARY, part.dynStrTab->addString(fileSoName)); + else + addInt(DT_NEEDED, part.dynStrTab->addString(fileSoName)); + } + } + // OHOS_LOCAL end if (isMain) { if (!config->soName.empty()) diff --git a/llvm/include/llvm/BinaryFormat/DynamicTags.def b/llvm/include/llvm/BinaryFormat/DynamicTags.def index ae25ec53813c..6abbfafa148b 100644 --- a/llvm/include/llvm/BinaryFormat/DynamicTags.def +++ b/llvm/include/llvm/BinaryFormat/DynamicTags.def @@ -91,6 +91,9 @@ DYNAMIC_TAG_MARKER(HIOS, 0x6FFFFFFF) // End of environment specific tags. DYNAMIC_TAG_MARKER(LOPROC, 0x70000000) // Start of processor specific tags. DYNAMIC_TAG_MARKER(HIPROC, 0x7FFFFFFF) // End of processor specific tags. +// OHOS_LOCAL begin +DYNAMIC_TAG(OHOS_WEAK_LIBRARY, 0x60000000) // weak library +// OHOS_LOCAL end // Android packed relocation section tags. // https://android.googlesource.com/platform/bionic/+/6f12bfece5dcc01325e0abba56a46b1bcf991c69/tools/relocation_packer/src/elf_file.cc#31 DYNAMIC_TAG(ANDROID_REL, 0x6000000F) diff --git a/llvm/tools/llvm-objdump/ELFDump.cpp b/llvm/tools/llvm-objdump/ELFDump.cpp index 68c635abf99c..b90cb9d0b75f 100644 --- a/llvm/tools/llvm-objdump/ELFDump.cpp +++ b/llvm/tools/llvm-objdump/ELFDump.cpp @@ -14,6 +14,7 @@ #include "ELFDump.h" #include "llvm-objdump.h" +#include "llvm/BinaryFormat/ELF.h" #include "llvm/Demangle/Demangle.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Support/Format.h" @@ -196,7 +197,9 @@ static void printDynamicSection(const ELFFile &Elf, StringRef Filename) { ELFT::Is64Bits ? "0x%016" PRIx64 "\n" : "0x%08" PRIx64 "\n"; if (Dyn.d_tag == ELF::DT_NEEDED || Dyn.d_tag == ELF::DT_RPATH || Dyn.d_tag == ELF::DT_RUNPATH || Dyn.d_tag == ELF::DT_SONAME || - Dyn.d_tag == ELF::DT_AUXILIARY || Dyn.d_tag == ELF::DT_FILTER) { + Dyn.d_tag == ELF::DT_AUXILIARY || Dyn.d_tag == ELF::DT_FILTER || + // OHOS_LOCAL + Dyn.d_tag == ELF::DT_OHOS_WEAK_LIBRARY) { Expected StrTabOrErr = getDynamicStrTab(Elf); if (StrTabOrErr) { const char *Data = StrTabOrErr.get().data(); diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp index cf69601eab93..c373efe23039 100644 --- a/llvm/tools/llvm-readobj/ELFDumper.cpp +++ b/llvm/tools/llvm-readobj/ELFDumper.cpp @@ -2429,6 +2429,7 @@ std::string ELFDumper::getDynamicEntry(uint64_t Type, case DT_ANDROID_RELASZ: return std::to_string(Value) + " (bytes)"; case DT_NEEDED: + case DT_OHOS_WEAK_LIBRARY: // OHOS_LOCAL case DT_SONAME: case DT_AUXILIARY: case DT_USED: @@ -2440,6 +2441,9 @@ std::string ELFDumper::getDynamicEntry(uint64_t Type, {DT_AUXILIARY, "Auxiliary library"}, {DT_USED, "Not needed object"}, {DT_FILTER, "Filter library"}, {DT_RPATH, "Library rpath"}, {DT_RUNPATH, "Library runpath"}, + // OHOS_LOCAL begin + {DT_OHOS_WEAK_LIBRARY, "Weak Library"}, + // OHOS_LOCAL end }; return (Twine(TagNames.at(Type)) + ": [" + getDynamicString(Value) + "]") -- Gitee