检查 MacOS Big Sur 上的库是否存在 背景

问题描述

是否有一种方法可以复制 _dyld_shared_cache_contains_path 的行为,同时适用于 MacOS Big Sur 和 MacOS Catalina?


我的第一次尝试(使用 dlopen)

#include <mach-o/dyld.h>
#include <dlfcn.h>
#include <stdio.h>

int library_exists(const char* path) {
    void* handle = dlopen(path,RTLD_LAZY);
    if (handle == NULL) return 0;
    dlclose(handle);
    return 1;
}

int main(void) {
    int result;
    char* path;

    path = "/usr/lib/libc.dylib";
    result = _dyld_shared_cache_contains_path(path);
    printf("_dyld_shared_cache_contains_path(%s) == %d",path,result);
    result = library_exists(path);
    printf("library_exists(%s) == %d",result);

    path = "libc.dylib";
    result = _dyld_shared_cache_contains_path(path);
    printf("_dyld_shared_cache_contains_path(%s) == %d",result);
}

输出

_dyld_shared_cache_contains_path(/usr/lib/libc.dylib) == 1
library_exists(/usr/lib/libc.dylib) == 1
_dyld_shared_cache_contains_path(libc.dylib) == 0
library_exists(libc.dylib) == 1

这非常接近,除了“libc.dylib”在传递给 2 个函数时有不同的行为。

背景

MacOS Big Sur 从文件系统中删除了共享库,并将它们放入缓存中。函数 _dyld_shared_cache_contains_path 与此更改一起在 中可用。

MacOS Big Sur 11.0.1 changenotes

macOS Big Sur 11.0.1 中的新功能,系统附带所有系统提供的库的内置动态链接器缓存。作为此更改的一部分,文件系统上不再存在动态库的副本。尝试通过在路径中查找文件或枚举目录来检查动态库是否存在的代码将失败。相反,通过尝试 dlopen() 路径来检查库是否存在,这将正确检查缓存中的库。 (62986286)

我想要一个可移植的二进制文件,它可以检查 MacOS Catalina 或 MacOS Big Sur 上是否存在共享库,而无需为特定版本的 MacOS 重新编译。如果我们引用 _dyld_shared_cache_contains_path 并尝试在 MacOS Catalina 上编译 - 编译将失败。我希望它具有与 _dyld_shared_cache_contains_path 相同的行为。

解决方法

您可以通过设置一堆环境变量来修复 dlopen...但您必须生成一个新进程。

man dlopen 有一个更详尽的描述,但基本上你看到的是后备效果,因为 libc.dylib 只是一个“叶子名称”,dyld 搜索由环境变量指定的一堆路径那个图书馆。

以下环境变量会影响此行为:

  • DYLD_LIBRARY_PATH
  • DYLD_FRAMEWORK_PATH
  • DYLD_FALLBACK_LIBRARY_PATH
  • DYLD_FALLBACK_FRAMEWORK_PATH
  • DYLD_IMAGE_SUFFIX

如果未设置,则这两个 FALLBACK 默认为某些系统路径 - 联机帮助页在此方面不是最新的,但根据标志,确切的值可以包括以下路径或其子集由内核设置:

  • /usr/local/lib
  • /usr/lib
  • /Library/Frameworks
  • /System/Library/Frameworks

DYLD_IMAGE_SUFFIX 有点不同,但如果你将它设置为 .dylib,那么 dlopen("libc") 会成功,所以这也是不需要的。

您可以通过将这些环境变量设置为空字符串来禁用所有回退行为。

因此,如果您像 DYLD_FALLBACK_LIBRARY_PATH='' ./test 一样调用上面的代码,它将按预期工作:

_dyld_shared_cache_contains_path(/usr/lib/libc.dylib) == 1
library_exists(/usr/lib/libc.dylib) == 1
_dyld_shared_cache_contains_path(libc.dylib) == 0
library_exists(libc.dylib) == 0

但是,如果您在代码中执行 setenv("DYLD_FALLBACK_LIBRARY_PATH","",1);,您会发现它不起作用。原因是 dyld 只在进程初始化时查看环境变量,之后的任何内容都将被忽略。

这是一个使用 execve 的工作示例:

#include <mach-o/dyld.h>
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

extern char **environ;

int library_exists(const char* path) {
    void* handle = dlopen(path,RTLD_LAZY);
    if (handle == NULL) return 0;
    dlclose(handle);
    return 1;
}

int main(int argc,const char **argv) {
    int result;
    char* path;

    const char *vars[] = { "DYLD_LIBRARY_PATH","DYLD_FRAMEWORK_PATH","DYLD_FALLBACK_LIBRARY_PATH","DYLD_FALLBACK_FRAMEWORK_PATH","DYLD_IMAGE_SUFFIX" };
    char fail = 0;
    for(size_t i = 0; i < 5; ++i)
    {
        char *v = getenv(vars[i]);
        if(!v || *v != '\0')
        {
            fail = 1;
            setenv(vars[i],1);
        }
    }
    if(fail)
    {
        return execve(argv[0],(char*const*)argv,environ);
    }

    path = "/usr/lib/libc.dylib";
    result = _dyld_shared_cache_contains_path(path);
    printf("_dyld_shared_cache_contains_path(%s) == %d\n",path,result);
    result = library_exists(path);
    printf("library_exists(%s) == %d\n",result);

    path = "libc.dylib";
    result = _dyld_shared_cache_contains_path(path);
    printf("_dyld_shared_cache_contains_path(%s) == %d\n",result);
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...