为什么写这篇博客:android开发中,部分功能需要依赖c++代码实现,需要将一些c++的源码编译为动态链接库,并在android app上调用实现c++代码功能。在交叉编译开发过程中有遇到很多麻烦,此文总结经验和教训,并希望为其他朋友提供些帮助,避些困扰。本文后半部分以实例形式分析并编译了一个完整的项目,可参考学习。
内容关键词:NDK编译链简介,NDK编译链的使用,多层依赖的动态链接库交叉编译
遇到了哪些问题?
- C++原生编译链,无法编译兼容android各平台的库文件。
- C++库文件的多层级依赖。
- 动态链接库在android项目中的调用。
NDK编译工具链:
https://developer.android.com/ndk/guideshttps://developer.android.com/ndk/guides/other_build_systems
- Native Development Kit:一句话总结,用来在android项目里调用c/c++的代码来完成app的某些功能的工具包。
- 避坑:NDK版本中,以NDK19为界做了调整,编译项目和依赖的库文件时请用同一个版本的NDK及编译工具链,本文用的是clang。(19之前有GCC,之后GCC被clang替代)
笔者算定版本为r21e,其他版本均可,建议19以上,下载地址如下。https://github.com/android/ndk/wiki/Unsupported-Downloads
项目分析:
项目地址:secure_channel_client: 独立编译安装的安全通道客户端SDK - Gitee.com
接下来以此开源项目为例,逐步解释如何将C++项目编译并集成至android app 并调用其功能。
此项目的编译和依赖关系如下图:
我们最终要实现的是client.c的功能(android app中),而其实现依赖三个库文件,teeverifier, pthread, csecure_channel,都需要NDK独立编译。(见子目录下的cmakelist)。
其中libteeverify.so 需要用NDK独立编译,其依赖三个库文件,core.a, libcrypro.so, libcjson.so.
libcsecure_channel.so,依赖源码(项目的thirtypart文件包含),及两个openssl的动态链接库,libssl.so,licrypto.so。
结合依赖关系分析,得到以下编译顺序
- 首先需要编译mircal子项目中core.a;然后是openssl的两个库libcrypro.so,libssl.so; 以及cjson的libcjson.so。
- 有了以上基础库文件,接下来编译libteeverify.so,以及libcsecure_channel.so.
- 最后将库文件放在android的文件目录中,client.c的cmakelist调用以上库文件,完成端侧安全通道的功能。
编译的坑:
了解整个依赖关系和编译逻辑之后,就是实操部分。这部分遇到问题最多,花费时间最长。我会一一列出遇到的问题及解决办法。
- core.a静态链接库编译:路径:\kunpengsecl\attestation\tee\tverlib\miracl>。 文件目录下有一个makefile文件.这一步中,要将原本的gcc替换为NDK目录下的aarch64-linux-android21-clang来编译,CC和CFLAGS分别为两个参数输入到python脚本中(提醒:安装python3)。NDK19版本之前可用GCC,之后版本用Clang,选定这个编译器之后,接下来编译库都用同一个,来避免不兼容问题。
.PHONY: all build test install clean all build: # CC=gcc CFLAGS=-fPIC python3 config64.py -o 33 > ./build.log CC=/自己的路径/ndk_openssl/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang CFLAGS=-fPIC python3 config64.py -o 33 > ./build.log test: @echo "to be implemented!" clean: @rm -rf *.o *.so *.a build.log
- libcjson库编译:按照正常的编译,编译器为同一个clang,替换为本地自己的路径。命令为:
/自己的路径/ndk_openssl/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang -wl,soname libcjson.so -o shared libcjson.so cJSON.c
- 编译好的库文件,libsjson.so 要复制到所有编译器:” /自己的路径/ndk_openssl/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang “ 对应的 系统默认搜索目录下目下,我这里对应的是”/自己路径/ndk_openssl/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/aarch64-linux-android/lib64”。下面编译完openssl,libteeverify.so 也执行相同的操作。
- openssl编译,这个库编译的时候坑比较多。可借鉴以下文章和官方文档,此处要注意NDK版本选择。此处编译的libssl.so和libcrypto.so, 如果和下面编译cseccure_channel.so不兼容,可以尝试删除android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/bin目录
下除aarch64-linux-android21-clang(21为我选定的版本,可以为其他),clang外其他文件。执行第3步。https://github.com/openssl/openssl/blob/master/NOTES-ANDROID.md,http://t.csdnimg.cn/IblIGhttps://github.com/openssl/openssl/blob/master/NOTES-ANDROID.md - 编译libteeverify.so: 路径/kunpengsecl/attestation/tee/tverlib/verifier
icbc@ubuntu:~/Desktop/kunpengsecl/attestation/tee/tverlib/verifier修改makefile中的编译器的位置,动态链接库的位置。编译成功后,执行第3步骤,参考代码如下:LIBTAR = /usr/lib64 INCLUDETAR = /usr/include .PHONY: all build test clean install all build: teeverifier.c teeverifier.h common.h # gcc -fPIC -shared -o libteeverifier.so teeverifier.c ../miracl/core.a -I /usr/local/include -I ../miracl -L /usr/local/lib -lcrypto -lcjson /自己路径/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang -fPIC -shared -Wl,-soname,libteeverifier.so -o libteeverifier.so teeverifier.c ../miracl/core.a -I /usr/local/include -I ../miracl -L /自己路径/openssl-1.1.1i/output -lcrypto -L /自己路径/secure_channel_client/thirdparty/cjson -lcjson install: all build mkdir -p $(DESTDIR)$(LIBTAR) mkdir -p $(DESTDIR)$(INCLUDETAR) install -m 755 libteeverifier.so $(DESTDIR)$(LIBTAR) install -m 644 teeverifier.h $(DESTDIR)$(INCLUDETAR) test: teeverify_test.c teeverifier.c gcc teeverify_test.c ../miracl/core.a -o test.out -I /usr/local/include -I ../miracl -L /usr/local/lib -lcrypto -lcjson clean: @rm -rf *.o *.so *.out
- 编译libcsecure_channel.so, 在secure_channel_client目录下有一个cmakelist,src/下有一个cmakalist,需要对两个cmakelist做些改造。项目根目录下,需要添加CMAKE_C_ COMPILER ,代码如下:
# Copyright (c) Huawei Technologies Co., Ltd. 2020. All rights reserved. # secGear is licensed under the Mulan PSL v2. # You can use this software according to the terms and conditions of the Mulan PSL v2. # You may obtain a copy of Mulan PSL v2 at: # http://license.coscl.org.cn/MulanPSL2 # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR # PURPOSE. # See the Mulan PSL v2 for more details. cmake_minimum_required(VERSION 3.10 FATAL_ERROR) set(CMAKE_C_STANDARD 99) #--------------------------------for ndk-------------------------------------------# # Set the path to the Android NDK set(CMAKE_ANDROID_NDK /自己目录/android-ndk-r21e) # # Specify the toolchain set(CMAKE_C_COMPILER ${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang) # set(CMAKE_CXX_COMPILER ${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++) #--------------------------------for ndk-------------------------------------------# project(secure_channel_client C) set(LOCAL_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(src) file(GLOB SEC_CHL_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h) install(FILES ${SEC_CHL_HEADERS} DESTINATION /usr/include/secGear PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
src/cmakelist:add_library添加crypto,ssl的位置, include_directories添加openssl的include位置. 然后执行mkdir build && cd build , cmake .. && make .
set(PREFIX secure_channel) set(SOURCE_FILE secure_channel_client.c secure_channel_common.c) aux_source_directory(${LOCAL_ROOT_PATH}/thirdparty/cjson/ CJSON_SRC) aux_source_directory(${LOCAL_ROOT_PATH}/src/ra_verify/ RA_SRC) FILE (GLOB_RECURSE BASE64_SRC "${LOCAL_ROOT_PATH}/thirdparty/base64url/*.c") set(CMAKE_C_FLAGS "-g -fstack-protector-all -W -Wall -Werror -Wextra -Werror=array-bounds -D_FORTIFY_SOURCE=2 -O2 -ftrapv -fPIC") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} -s") set(CMAKE_EXE_LINKER_FLAGS "-Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack") if(${CMAKE_VERSION} VERSION_LESS "3.13.0") link_directories(${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) endif() include_directories( /usr/local/include #openssl 添加openssl的include位置 ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${LOCAL_ROOT_PATH}/include ${LOCAL_ROOT_PATH}/src/ra_verify ${LOCAL_ROOT_PATH}/thirdparty/cjson ${LOCAL_ROOT_PATH}/thirdparty/base64url ${LOCAL_ROOT_PATH}/thirdparty/kunpengsecl/verifier ) add_library(c${PREFIX} SHARED ${SOURCE_FILE} ${CJSON_SRC} ${BASE64_SRC} ${RA_SRC}) if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.13.0") target_link_directories(c${PREFIX} PRIVATE ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) endif() #添加crypto,ssl的位置 add_library( crypto SHARED IMPORTED) set_target_properties(crypto PROPERTIES IMPORTED_LOCATION /usr/lib64/libcrypto.so) add_library( ssl SHARED IMPORTED) set_target_properties(ssl PROPERTIES IMPORTED_LOCATION /usr/lib64/libssl.so) target_link_libraries(c${PREFIX} ssl crypto) install(TARGETS c${PREFIX} LIBRARY DESTINATION /usr/lib64 PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
编译完成后在usr/lib64目录下可找到libcsecure_channel.so,找不到的话,在secure_channel_client/build/src下面可以找到对应文件。
-
至此此项目所依赖的所有动态链接库均编译完毕,将client.c的代码通过JNI接口改写至Android的cpp模块内,即可在android app中实现app相同功能。
其他潜在问题:
- 编译过后使用库的时候可能存在,cannot find /某路径/下的/xxxx.so (其他库,入yyyy.so连接此库)的问题。这个问题的原因是编译xxxx.so库时,库名有问题,需要在编译的命令中加入 -wl,-soname,libxxxx.so -o libxxxx.so 来编译这个库。参考:Linux动态库soname的使用(转载)-CSDN博客
- 找不到库的问题:比如编译libcsecurechannel.so,需要libssl,libcrypto,libverifier等库。使用NDK的交叉编译链可能存在找不到库的情况,这个情况是因为,在编译链的默认搜索目录中,没有对应的库文件。壳参照上文第三步找到相应目录。