一、关键字

定位,GNSS,定位数据无法获取

二、现象描述

设备型号:黄鹂

系统版本:OpenHarmony 5.0

代码版本:OpenHarmony-5.0.0-Release

2.1 正常机制

给定位应用授权所需的位置等权限,打开定位应用,界面卫星数量,经纬度等数据会发生变化

2.2 异常机制

定位应用界面数据始终不会变化

三、原因分析

开发者手机升级5.0的解耦工程,分成了sig和laval两个工程,sig仓作为主干工程,而laval仓用于存放展锐相关资源。

所以location资源被分成两部分:

3.1 编译

    首先拉取最新代码在本地进行了一次工程编译并测试,查看日志,发现vendorGnssAdapter.so找不到,查看配置文件,location_group没有编译,于是将location_group加入编译。

01-01 08:15:59.729  2758  2768 E C03f00/MUSL-LDSO: Error loading header vendorGnssAdapter.so, namespace ndk has no inherits, errno=2
01-01 08:15:59.729  2758  2768 E C03f00/MUSL-LDSO: Error loading header: can't find library vendorGnssAdapter.so in namespace: default
01-01 08:15:59.729  2758  2768 E C03f00/MUSL-LDSO: dlopen_impl load library header failed for vendorGnssAdapter.so
01-01 08:15:59.729  2758  2768 E C02510/HDF_LOG_TAG: Init:dlopen vendorGnssAdapter.so failed: Error loading shared library vendorGnssAdapter.so: No such file or directory
01-01 08:15:59.729  2758  2768 I C02510/HDF_LOG_TAG: LocationVendorInterface constructed.
01-01 08:15:59.729  2758  2768 E C02510/HDF_LOG_TAG: GetGnssVendorInterface:GetGnssVendorInterface() failed.
01-01 08:15:59.729  2758  2768 E C02510/HDF_LOG_TAG: EnableGnss:GetGnssVendorInterface return nullptr.

sig:

重新编译后测试,闭源库的相关资源也找不到,laval仓下location闭源库相关资源也未加入编译,将闭源库相关资源也加入编译。

01-01 08:03:15.174  2724  2728 E C02300/GnssAbility: [(gnss_enable:90)]/vendor/soc_platform/lib64/libgnssmgt.z.so entered
01-01 08:03:15.175  2724  2728 E C03f00/MUSL-LDSO: Open absolute_path library: check ns accessible failed, pathname /vendor/soc_platform/lib64/libgnssmgt.z.so namespace ndk.
01-01 08:03:15.175  2724  2728 E C03f00/MUSL-LDSO: Error loading header /vendor/soc_platform/lib64/libgnssmgt.z.so, namespace ndk has no inherits, errno=2
01-01 08:03:15.175  2724  2728 E C03f00/MUSL-LDSO: Error loading header: can't find library /vendor/soc_platform/lib64/libgnssmgt.z.so in namespace: default
01-01 08:03:15.175  2724  2728 E C03f00/MUSL-LDSO: dlopen_impl load library header failed for /vendor/soc_platform/lib64/libgnssmgt.z.so
01-01 08:03:15.175  2724  2728 E C02300/GnssAbility: [(gnss_enable:98)]/vendor/soc_platform/lib64/libgnssmgt.z.so GNSSMGT handle addr is <private>
01-01 08:03:15.175  2724  2728 E C02300/GnssAbility: [(gnss_enable:102)]/vendor/soc_platform/lib64/libgnssmgt.z.so load failed

laval:

3.2 适配GNSS2.0接口

    各仓下location_group组件加入编译后,报了大量错误,许多接口名以及参数名找不到,查看相关代码,发现在5.0上GNSS接口已经升级V2.0,增加了许多新功能的同时对这些接口名以及数据结构定义做了大规模整改,而开发者手机是根据V1.0做的适配,因此要将vendorGnssAdapter中的接口适配到GNSSV2.0。

同时,有一处代码需要添加补丁,加入patch进行编译:

本以为问题到此就可以解决了,但是编译完成后烧录测试,定位应用还是获取不到数据。

3.3 cppcrash问题分析及解决

    查看相关日志,日志中显示系统运行时出现了cppcrash,根据崩溃类型分析很有可能是空指针问题,初步猜测是调用status_callback时传入的参数有问题。status_callback函数是传递给闭源库的回调,当gps硬件检测到状态变化时会调用status_callback进行数据上报,难道是闭源库上报数据时出现的问题?于是打印了调用回调时传入的参数,一切正常,每次上报的数据都是正常的,不是此处的问题。

Build info:OpenHarmony 5.0.0.702
Timestamp:1970-01-01 08:02:40.101
Pid:2774
Uid:1022
Process name:location_host
Process life time:5s
Reason:Signal:SIGSEGV(SEGV_MAPERR)@0x0000000000000058  probably caused by NULL pointer dereference
Fault thread info:
Tid:2776, Name:OS_IPC_1_2776
#00 pc 0000000000000058 Not mapped
#01 pc 0000000000003c3c /vendor/lib64/vendorGnssAdapter.so(status_callback(GpsStatus*)+112)(204d369fd7990bb2984e01578d1bf201)
#02 pc 0000000000057e34 /vendor/soc_platform/lib64/libgnssmgt.z.so(gps_start+892)(e51333dc6e2a4f7d2211139747f5247b)
#03 pc 0000000000004eac /vendor/lib64/vendorGnssAdapter.so(gnss_start(unsigned int)+72)(204d369fd7990bb2984e01578d1bf201)
#04 pc 000000000001061c /vendor/lib64/liblocation_gnss_stub_2.0.z.so(OHOS::HDI::Location::Gnss::V2_0::GnssInterfaceStub::GnssInterfaceStubStartGnss_(OHOS::MessageParcel&, OHOS::MessageParcel&, OHOS::MessageOption&, OHOS::sptr<OHOS::HDI::Location::Gnss::V2_0::IGnssInterface>)+268)(e1a5650409cd84509f1cd3cfd9d4bc49)
#05 pc 000000000000f2f4 /vendor/lib64/liblocation_gnss_stub_2.0.z.so(OHOS::HDI::Location::Gnss::V2_0::GnssInterfaceStub::OnRemoteRequest(unsigned int, OHOS::MessageParcel&, OHOS::MessageParcel&, OHOS::MessageOption&)+424)(e1a5650409cd84509f1cd3cfd9d4bc49)
#06 pc 0000000000014360 /system/lib64/chipset-pub-sdk/libipc_single.z.so(OHOS::IPCObjectStub::SendRequestInner(unsigned int, OHOS::MessageParcel&, OHOS::MessageParcel&, OHOS::MessageOption&)+156)(10efa66e24dbc5f61d4754b9806f49e6)
#07 pc 0000000000002c48 /vendor/lib64/liblocation_gnss_hdi_driver.z.so(GnssInterfaceDriverDispatch(HdfDeviceIoClient*, int, HdfSBuf*, HdfSBuf*)+264)(066d8081258152ded4b7375d02616914)
#08 pc 000000000000d28c /vendor/lib64/libhdf_host.z.so(DeviceServiceStubDispatch+56)(acc2e9b0aaa3dfa301d25c4737980de9)
#09 pc 00000000000132cc /system/lib64/chipset-pub-sdk/libhdf_ipc_adapter.z.so(HdfRemoteServiceStub::OnRemoteRequest(unsigned int, OHOS::MessageParcel&, OHOS::MessageParcel&, OHOS::MessageOption&)+156)(82cc2cdace29407067d80f09c562cb71)
#10 pc 0000000000014360 /system/lib64/chipset-pub-sdk/libipc_single.z.so(OHOS::IPCObjectStub::SendRequestInner(unsigned int, OHOS::MessageParcel&, OHOS::MessageParcel&, OHOS::MessageOption&)+156)(10efa66e24dbc5f61d4754b9806f49e6)
#11 pc 000000000001dd14 /system/lib64/chipset-pub-sdk/libipc_single.z.so(OHOS::IPC_SINGLE::BinderInvoker::GeneralServiceSendRequest(binder_transaction_data const&, OHOS::MessageParcel&, OHOS::MessageParcel&, OHOS::MessageOption&)+184)(10efa66e24dbc5f61d4754b9806f49e6)
#12 pc 000000000001de74 /system/lib64/chipset-pub-sdk/libipc_single.z.so(OHOS::IPC_SINGLE::BinderInvoker::TargetStubSendRequest(binder_transaction_data const&, OHOS::MessageParcel&, OHOS::MessageParcel&, OHOS::MessageOption&, unsigned int&)+144)(10efa66e24dbc5f61d4754b9806f49e6)
#13 pc 000000000001e1e0 /system/lib64/chipset-pub-sdk/libipc_single.z.so(OHOS::IPC_SINGLE::BinderInvoker::Transaction(binder_transaction_data_secctx&)+632)(10efa66e24dbc5f61d4754b9806f49e6)
#14 pc 000000000001e808 /system/lib64/chipset-pub-sdk/libipc_single.z.so(OHOS::IPC_SINGLE::BinderInvoker::HandleCommandsInner(unsigned int)+572)(10efa66e24dbc5f61d4754b9806f49e6)
#15 pc 000000000001d2f4 /system/lib64/chipset-pub-sdk/libipc_single.z.so(OHOS::IPC_SINGLE::BinderInvoker::HandleCommands(unsigned int)+64)(10efa66e24dbc5f61d4754b9806f49e6)
#16 pc 000000000001d180 /system/lib64/chipset-pub-sdk/libipc_single.z.so(OHOS::IPC_SINGLE::BinderInvoker::StartWorkLoop()+132)(10efa66e24dbc5f61d4754b9806f49e6)
#17 pc 000000000001e890 /system/lib64/chipset-pub-sdk/libipc_single.z.so(OHOS::IPC_SINGLE::BinderInvoker::JoinThread(bool)+76)(10efa66e24dbc5f61d4754b9806f49e6)
#18 pc 00000000000188f8 /system/lib64/chipset-pub-sdk/libipc_single.z.so(OHOS::IPC_SINGLE::IPCWorkThread::ThreadHandler(void*)+564)(10efa66e24dbc5f61d4754b9806f49e6)
#19 pc 0000000000113768 /system/lib/ld-musl-aarch64.so.1(start+236)(eb536f83f62f534f022891b3584bd85a)

那可能是函数内部的问题?

static void status_callback(GpsStatus *status) {
  /* GNSS status unknown. */
  //  GNSS_STATUS_NONE = 0,
  /* GNSS has begun navigating. */
  //  GNSS_STATUS_SESSION_BEGIN = 1,
  /* GNSS has stopped navigating. */
  //  GNSS_STATUS_SESSION_END = 2,
  /* GNSS has powered on but is not navigating. */
  //  GNSS_STATUS_ENGINE_ON = 3,
  /* GNSS is powered off. */
  //  GNSS_STATUS_ENGINE_OFF = 4
  uint16_t ohos_status = status->status;
  g_GCS_.gnssCallback.gnssWorkingStatusUpdate(&ohos_status);
}

除了接收返回的数据,就只有一步函数调用,前面已经验证参数是没有问题的,于是打印了这个回调的地址,果不其然,就是导致崩溃的地方!

01-01 08:03:50.852  2932  2934 I C02300/GnssAbility: [(status_callback:300)]=================status_callback: g_GCS_.gnssCallback.gnssWorkingStatusUpdate to: 0000000000000058

gnssWorkingStatusUpdate是哪里来的?经过代码逻辑梳理,它是从hdi层传递下来的回调,作用是将返回的数据上报到hdi层。

那下一步去看一下一开始gnssWorkingStatusUpdate是在哪里被接收的,打印下函数地址看看在一开始保存的函数地址是否有效,是不是在传递的过程中出现了问题。

gnssWorkingStatusUpdate是作为gnssCallback中的一个成员函数被gnss_enable接收的,在gnss_enable中打印函数地址:

01-01 08:03:50.450  2932  2935 I C02300/GnssAbility: [(gnss_enable:100)]=================gnss_enable:g_GCS_.gnssCallback.gnssWorkingStatusUpdate address: 0000007F1580B9F0

    说明一开始接收到的这个函数地址是有效的,并且按照逻辑gnss_enable只会在使能的时候调用一次,而日志中却被调用了很多次,并且,每次gnss_enable接收到的回调地址都是正常的,一到闭源库中数据上报需要调用前,函数地址就变成0000000000000058,真是太奇怪了。

    一时找不出为什么会出现这么奇怪的跳变,也没法从日志中找到更多有用信息,只能再去梳理代码逻辑,对比4.1r源码看看有什么不同的地方,因为4.1r上是可以获取到数据的。经过代码逻辑梳理,对比分析,发现在5.0.0r上新增了许多功能,对许多已有函数也进行了功能扩展,最终定位到GnssInterfaceImpl::EnableGnss中的差异:

4.1r:

int32_t GnssInterfaceImpl::EnableGnss(const sptr<IGnssCallback>& callbackObj)
{
    HDF_LOGI("%{public}s.", __func__);
    if (callbackObj == nullptr) {
        HDF_LOGE("%{public}s:invalid callbackObj", __func__);
        return HDF_ERR_INVALID_PARAM;
    }
    std::unique_lock<std::mutex> lock(g_mutex);
    const sptr<IRemoteObject>& remote = OHOS::HDI::hdi_objcast<IGnssCallback>(callbackObj);
    if (remote == nullptr) {
        HDF_LOGE("%{public}s:invalid remote", __func__);
        return HDF_ERR_INVALID_PARAM;
    }
    if (g_locationCallBackMap.size() >= MAX_CALLBACK_SIZE) {
        HDF_LOGE("%{public}s:gnss has been enabled already", __func__);
        return HDF_SUCCESS;
    }
    static GnssCallbackStruct gnssCallback;
    GetGnssCallbackMethods(&gnssCallback);
    auto gnssInterface = LocationVendorInterface::GetInstance()->GetGnssVendorInterface();
    if (gnssInterface == nullptr) {
        HDF_LOGE("%{public}s:GetGnssVendorInterface return nullptr.", __func__);
        return HDF_ERR_INVALID_PARAM;
    }
    int ret = gnssInterface->enable_gnss(&gnssCallback);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("enable_gnss failed.");
        return HDF_FAILURE;
    }
    AddGnssDeathRecipient(callbackObj);
    g_locationCallBackMap[remote.GetRefPtr()] = callbackObj;
    return ret;
}

5.0.0r:

int32_t GnssInterfaceImpl::EnableGnss(const sptr<IGnssCallback>& callbackObj)
{
    HDF_LOGI("%{public}s.", __func__);
    if (callbackObj == nullptr) {
        HDF_LOGE("%{public}s:invalid callbackObj", __func__);
        return HDF_ERR_INVALID_PARAM;
    }
    std::unique_lock<std::mutex> lock(g_mutex);
    const sptr<IRemoteObject>& remote = OHOS::HDI::hdi_objcast<IGnssCallback>(callbackObj);
    if (remote == nullptr) {
        HDF_LOGE("%{public}s:invalid remote", __func__);
        return HDF_ERR_INVALID_PARAM;
    }
    static GnssCallbackStruct gnssCallback;
    GetGnssCallbackMethods(&gnssCallback);
    auto gnssInterface = LocationVendorInterface::GetInstance()->GetGnssVendorInterface();
    if (gnssInterface == nullptr) {
        HDF_LOGE("%{public}s:GetGnssVendorInterface return nullptr.", __func__);
        return HDF_ERR_INVALID_PARAM;
    }
    int ret = gnssInterface->enableGnss(&gnssCallback);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("enableGnss failed.");
        return HDF_FAILURE;
    }
    static GnssNetInitiatedCallbacks niCallback;
    niCallback.reportNiNotification = NiNotifyCallback;
    int moduleType = static_cast<int>(GnssModuleIfaceCategory::GNSS_NET_INITIATED_MODULE_INTERFACE);
    auto niInterface = static_cast<const GnssNetInitiatedInterface*>
        (LocationVendorInterface::GetInstance()->GetModuleInterface(moduleType));
    if (niInterface != nullptr) {
        niInterface->setCallback(&niCallback);
    } else {
        HDF_LOGE("%{public}s:can not get gnssNiInterface.", __func__);
    }
    
    if (g_locationCallBackMap.size() > 0) {
        for (const auto& iter : g_locationCallBackMap) {
            const auto& callback = iter.second;
            if (callback != nullptr) {
                RemoveGnssDeathRecipient(callback);
            }
        }
        g_locationCallBackMap.clear();
    }
    AddGnssDeathRecipient(callbackObj);
    g_locationCallBackMap[remote.GetRefPtr()] = callbackObj;
    return ret;
}

    可以看到,在4.1r上获取到gnssInterface接口调用gnssInterface->enableGnss(&gnssCallback)之后这个函数基本上结束了,而在5.0.0r上新增了niInterface,并且这里要通过GetModuleInterface去获取niInterface接口,开发者手机上目前是不需要这个接口的,并且也没有对它进行适配,按正常逻辑来说,通过GNSS_NET_INITIATED_MODULE_INTERFACE到适配代码中获取niInterface,由于没有适配,应该直接返回空指针,打印can not get gnssNiInterface即可,不需要再走下面的逻辑。

    但是巧合的是,在4.1r上的定位模块接口分类枚举值3对应的是去获取gnssInterface接口,而在5.0.0r上定位模块接口分类枚举值3被修改成去获取niInterface接口,但是根据原本的适配代码,枚举值3对应的还是去获取的gnssInterface接口,也就是说,5.0.0r上通过GNSS_NET_INITIATED_MODULE_INTERFACE原本想获取的是niInterface但实际上获取的还是gnssInterface接口,并在获取到接口之后又对其设置回调,但本身gnssInterface身上并没有setCallback方法,这是niInterface对象身上的方法,从而导致后续的崩溃。然后服务又重新启动,重新获取接口,然后又崩溃,如此周而复始,出现日志中打印的函数指针一次次跳变,一次次导致cppcrash,终于找到原因了,真是一只披着羊皮的狼啊!

4.1r上定义的定位模块接口分类枚举值:

enum class GnssModuleIfaceClass {
    AGPS_INTERFACE = 1,
    GNSS_GEOFENCING_INTERFACE = 2,
    GNSS_CACHE_INTERFACE = 3,
};

5.0.0r上定义的定位模块接口分类枚举值:

enum class GnssModuleIfaceCategory {
    AGNSS_MODULE_INTERFACE = 1,
    GNSS_GEOFENCING_MODULE_INTERFACE = 2,
    GNSS_NET_INITIATED_MODULE_INTERFACE = 3,
    GNSS_MEASUREMENT_MODULE_INTERFACE = 4,
};

    同时,在4.1r中gnssInterface接口并没有通过GetModuleInterface去获取过,而是直接通过GetGnssVendorInterface来获取的,所以原本适配代码中通过模块接口获取的gnssInterface并没有用处,并且在5.0.0r上已经做了整改,不需要再通过模块接口去获取gnssInterface,为了避免干扰于是将适配代码中有关这块的适配全部删除。

至此,界面终于有定位数据显示了。

完整pr:

https://gitee.com/openharmony-sig/device_soc_spreadtrum/pulls/181
https://gitee.com/openharmony-sig/vendor_hardmony/pulls/247
https://gitee.com/cooperation-team-7885/device_platform/pulls/117

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐