在 MacOS Big Sur 上检查库是否存在

Checking for library existence on 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", path, result);

    path = "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", path, 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

New in macOS Big Sur 11.0.1, the system ships with a built-in dynamic linker cache of all system-provided libraries. As part of this change, copies of dynamic libraries are no longer present on the filesystem. Code that attempts to check for dynamic library presence by looking for a file at a path or enumerating a directory will fail. Instead, check for library presence by attempting to dlopen() the path, which will correctly check for the library in the cache. (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 != '[=11=]')
        {
            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", path, result);

    path = "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", path, result);
}