Android ROM移植: 记一次dlopen failed: cannot locate symbol解决过程

本文记录一次Android ROM移植过程中遇到的dlopen failed: cannot locate symbol问题的解决过程,希望能对遇到类似问题的Android ROM移植爱好者有所启示。

1. 问题描述

在移植小米13 LineageOS 22.1 (Android 15)时,遇到了这个这个报错​Abort message: 'could not dlopen c2.dolby.hevc.dec.so: dlopen failed: cannot locate symbol "_ZN7android8hardware5media2c24V1_05utils6objcpyEPNS3_10WorkBundleERKNSt3__14listINS7_10unique_ptrI6C2WorkNS7_14default_deleteISA_EEEENS7_9allocatorISD_EEEEPNS4_16BufferPoolSenderE" referenced by "/vendor/lib64/c2.dolby.client.so"...'​. 报错信息表明c2.dolby.client.so依赖的c2.dolby.hevc.dec.so依赖不存在的符号ZN7android***。

2. 符号提供者查找

首先,我们在使用objdump在原stock rom中查找一下该符号的提供者,可以使用如下命令:

for f in {system,system_ext,vendor,odm,product}/**/*.so; do
    aarch64-linux-gnu-objdump -T "$f" 2>/dev/null |grep _ZN7android8hardware5media2c24V1_05utils6objcpyEPNS3_10WorkBundleERKNSt3__14listINS7_10unique_ptrI6C2WorkNS7_14default_deleteISA_EEEENS7_9allocatorISD_EEEEPNS4_16BufferPoolSenderE && echo "$f";
done

结果如下:

0000000000000000      DF *UND*  0000000000000000  Base        _ZN7android8hardware5media2c24V1_05utils6objcpyEPNS3_10WorkBundleERKNSt3__14listINS7_10unique_ptrI6C2WorkNS7_14default_deleteISA_EEEENS7_9allocatorISD_EEEEPNS4_16BufferPoolSenderE
vendor/lib64/c2.dolby.client.so
0000000000050920 g    DF .text  0000000000000008  Base        _ZN7android8hardware5media2c24V1_05utils6objcpyEPNS3_10WorkBundleERKNSt3__14listINS7_10unique_ptrI6C2WorkNS7_14default_deleteISA_EEEENS7_9allocatorISD_EEEEPNS4_16BufferPoolSenderE
vendor/lib64/libcodec2_hidl@1.0.so
0000000000000000      DF *UND*  0000000000000000  Base        _ZN7android8hardware5media2c24V1_05utils6objcpyEPNS3_10WorkBundleERKNSt3__14listINS7_10unique_ptrI6C2WorkNS7_14default_deleteISA_EEEENS7_9allocatorISD_EEEEPNS4_16BufferPoolSenderE
vendor/lib64/libcodec2_hidl@1.1.so
0000000000000000      DF *UND*  0000000000000000  Base        _ZN7android8hardware5media2c24V1_05utils6objcpyEPNS3_10WorkBundleERKNSt3__14listINS7_10unique_ptrI6C2WorkNS7_14default_deleteISA_EEEENS7_9allocatorISD_EEEEPNS4_16BufferPoolSenderE
vendor/lib64/libcodec2_hidl@1.2.so
0003da0c g    DF .text  00000004  Base        _ZN7android8hardware5media2c24V1_05utils6objcpyEPNS3_10WorkBundleERKNSt3__14listINS7_10unique_ptrI6C2WorkNS7_14default_deleteISA_EEEENS7_9allocatorISD_EEEEPNS4_16BufferPoolSenderE
vendor/lib/libcodec2_hidl@1.0.so

从中可以发现该符号提供者是vendor/lib64/libcodec2hidl@1.0.so. 根据该so文件命名猜出其是应该是由AOSP源码编译出来的,而不是无源码的vendor blob. 接下来,可以通过分析一下Android源码看看为什么会缺少该符号,以及该符号是否由其它替代品。

3. Android源码分析

首先,先用​c++filt​对该符号demangle一下,可以得到如下结果:

android::hardware::media::c2::V1_0::utils::objcpy(android::hardware::media::c2::V1_0::WorkBundle*, std::__1::list<std::__1::unique_ptr<C2Work, std::__1::default_delete<C2Work> >, std::__1::allocator<std::__1::unique_ptr<C2Work, std::__1::default_delete<C2Work> > > > const&, android::hardware::media::c2::V1_0::utils::BufferPoolSender*)

简化一下可以得:

android::hardware::media::c2::V1_0::utils::objcpy(android::hardware::media::c2::V1_0::WorkBundle*, list<unique_ptr<C2Work>> const&, android::hardware::media::c2::V1_0::utils::BufferPoolSender*)

然后,在Android Code Search 中搜索一下objcpy, 可以发现objcpy相关源码位于frameworks/av/media/codec2/hal/hidl/1.0/utils/include/codec2/hidl/1.0/types.h和其实现文件types.cpp, 如下:

typedef ::android::BufferPoolSender<BufferPoolTypes> BufferPoolSender;
bool objcpy(WorkBundle* d, const std::list<std::unique_ptr<C2Work>>& s, BufferPoolSender* bpSender = nullptr);

从中可以看出,在Android 15中,BufferPoolSender不再为​android::hardware::media::c2::V1_0::utils::BufferPoolSender​。

查看git提交历史,可以是发现这个ab22b476668b449f2a7e48819387d4cbdfd6e750 commit将BufferPoolSender改为成了BufferPoolSender template, 并将其从types.h移动到了BufferPoolSender.h, 如下:

// Android 14 android::hardware::media::c2::V1_0::utils::BufferPoolSender
 struct BufferPoolSender {
     typedef ::android::hardware::media::bufferpool::V2_0::ResultStatus ResultStatus;
     typedef ::android::hardware::media::bufferpool::V2_0::BufferStatusMessage BufferStatusMessage;
     typedef ::android::hardware::media::bufferpool:: BufferPoolData BufferPoolData;
     virtual ResultStatus send(const std::shared_ptr<BufferPoolData>& bpData, BufferStatusMessage* bpMessage) = 0;
     virtual ~BufferPoolSender() = default;
 };

// Android 15 android::BufferPoolSender
template <typename BufferPoolTypes>
struct BufferPoolSender {
    typedef typename BufferPoolTypes::BufferPoolData        BufferPoolData;
    typedef typename BufferPoolTypes::ResultStatus          ResultStatus;
    typedef typename BufferPoolTypes::BufferPoolStatus      BufferPoolStatus;
    typedef typename BufferPoolTypes::BufferStatusMessage   BufferStatusMessage;
    virtual BufferPoolStatus send(const std::shared_ptr<BufferPoolData>& bpData, BufferStatusMessage* bpMessage) = 0;
    virtual ~BufferPoolSender() = default;
};
struct BufferPoolTypes {
    typedef bufferpool::BufferPoolData              BufferPoolData;
    typedef bufferpool::V2_0::ResultStatus          BufferPoolStatus;
    typedef bufferpool::V2_0::ResultStatus          ResultStatus;
    typedef bufferpool::V2_0::BufferStatusMessage   BufferStatusMessage;
};

两者都没有数据成员,且具有两个签名一致的virtual函数,因此两者对象的内存布局应该完全一致,也就是说​可以将Android 14 BufferPoolSender对象强转为Android 15 BufferPoolSender对象​.

因此,问题中缺少的ZN7android***符号可以根据Android 15 objcpy函数来实现,下一小节将讨论具体做法。

4. 缺失符号ZN7android***实现方法

由于可以Android 14 BufferPoolSender对象强转为Android 15 BufferPoolSender对象,因此缺失符号ZN7android***可通过透传调用Android 15 objcpy来实现,如下:

#include <codec2/hidl/1.0/types.h>

using ::android::hardware::media::c2::V1_0::utils::BufferPoolSender;
using ::android::hardware::media::c2::V1_0::WorkBundle;

// android::hardware::media::c2::V1_0::utils::objcpy
extern "C" {
bool _ZN7android8hardware5media2c24V1_05utils6objcpyEPNS3_10WorkBundleERKNSt3__14listINS7_10unique_ptrI6C2WorkNS7_14default_deleteISA_EEEENS7_9allocatorISD_EEEEPNS_16BufferPoolSenderINS4_15BufferPoolTypesEEE(WorkBundle* d, const std::list<std::unique_ptr<C2Work>>& s, BufferPoolSender* bpSender);

bool _ZN7android8hardware5media2c24V1_05utils6objcpyEPNS3_10WorkBundleERKNSt3__14listINS7_10unique_ptrI6C2WorkNS7_14default_deleteISA_EEEENS7_9allocatorISD_EEEEPNS4_16BufferPoolSenderE(WorkBundle* d, const std::list<std::unique_ptr<C2Work>>& s, BufferPoolSender* bpSender) {
    return _ZN7android8hardware5media2c24V1_05utils6objcpyEPNS3_10WorkBundleERKNSt3__14listINS7_10unique_ptrI6C2WorkNS7_14default_deleteISA_EEEENS7_9allocatorISD_EEEEPNS_16BufferPoolSenderINS4_15BufferPoolTypesEEE(d, s, bpSender);
}
}

之所以用extern "C", 是因为Android 15目前已没有真正的BufferPoolSender定义,进而导致C++ name mangle将不能生成目标符号。

由于动态链接时linker不会校验参数类型(最多校验一下参数内存占用大小,参数校验是compiler的责任),因此上述代码可以被简化为:

// android::hardware::media::c2::V1_0::utils::objcpy
extern "C" {
bool _ZN7android8hardware5media2c24V1_05utils6objcpyEPNS3_10WorkBundleERKNSt3__14listINS7_10unique_ptrI6C2WorkNS7_14default_deleteISA_EEEENS7_9allocatorISD_EEEEPNS_16BufferPoolSenderINS4_15BufferPoolTypesEEE(void* d, const void* s, void* bpSender);

bool _ZN7android8hardware5media2c24V1_05utils6objcpyEPNS3_10WorkBundleERKNSt3__14listINS7_10unique_ptrI6C2WorkNS7_14default_deleteISA_EEEENS7_9allocatorISD_EEEEPNS4_16BufferPoolSenderE(void* d, const void* s, void* bpSender) {
    return _ZN7android8hardware5media2c24V1_05utils6objcpyEPNS3_10WorkBundleERKNSt3__14listINS7_10unique_ptrI6C2WorkNS7_14default_deleteISA_EEEENS7_9allocatorISD_EEEEPNS_16BufferPoolSenderINS4_15BufferPoolTypesEEE(d, s, bpSender);
}
}

上述代码将被用于生成libcodec2hidl@1.0shim.so, 其将作为c2.dolby.hevc.dec.so的依赖so以提供缺失的ZN7android***符号。 下面将介绍如何将上述代码融入Android构建系统以生成该so.

5. Android构建系统融入

现代Android一般使用Soong来构建简单新模块,因此本文也将如此。 libcodec2hidl@1.0shim模块的构建规则文件Android.bp如下

cc_library_shared {
    name: "libcodec2_hidl@1.0_shim",
    include_dirs: [
        "frameworks/av/media/codec2/hal/hidl/1.0/utils/include",
    ],
    shared_libs: [
        "libcodec2_hidl@1.0",
    ],
    srcs: [
        "libcodec2/libcodec2_hidl@1.0.cpp",
    ],
    vendor: true,
}

其中libcodec2/libcodec2hidl@1.0.cpp内容为上一节中的C++代码,​vendor: true​表明libcodec2hidl@1.0shim.so将和c2.dolby.hevc.dec.so一样被安装到vendor分区

为了让Android构建系统能够编译本模块,需要将其加入PRODUCTPACKAGES中:​PRODUCT_PACKAGES += libcodec2_hidl@1.0_shim​.

为了让extract util能够自动给c2.dolby.hevc.dec.so添加libcodec2hidl@1.0shim.so依赖,需要在extract-files.py中加入如下代码:

blob_fixups: blob_fixups_user_type = {
    'vendor/lib64/c2.dolby.hevc.dec.so': blob_fixup()
        .add_needed("libcodec2_hidl@1.0_shim.so"),
}

module = ExtractUtilsModule(
    'sm8550-common',
    'xiaomi',
    blob_fixups=blob_fixups,
    lib_fixups=lib_fixups,
    namespace_imports=namespace_imports,
)

其本质上相当于​patchelf --add-needed libcodec2_hidl@1.0_shim.so c2.dolby.hevc.dec.so​.

6. 总结

Android新版本可能因采用新实现方法而会缺少某些vendor blob依赖的老版本符号,而这些老版本符号一般可以通过透传调用新版本符号来实现。 文中讨论了vendor blob依赖缺少的老版本符号问题的一种解决过程,希望其能帮助到遇到类似问题的Android ROM移植爱好者。