​openssl Android編譯指南

  • 2019 年 10 月 4 日
  • 筆記

openssl Android編譯指南


1. openssl選擇分支OpenSSL_1_1_1-stable

git checkout OpenSSL_1_1_1-stable

1.1 修改build.info

打開根目錄下的build.info, 注釋下面幾行, 在Line:590~594, 否則會有類似錯誤提示 ${LDCMD:-g++} ld: unknown option: --sysroot=.

  # PROGRAMS_NO_INST=buildtest_cc_$name    # SOURCE[buildtest_cc_$name]=buildtest_$name.cc    # GENERATE[buildtest_$name.cc]=generate_buildtest.pl $name    # INCLUDE[buildtest_cc_$name]=../include    # DEPEND[buildtest_cc_$name]=../libssl ../libcrypto

1.2 修改Configurations/15-android.conf

默認生成的so命名為libcrypto.so.1.1, 這樣樣式的在Android中使用System.loadLibrary載入會失敗, 因為Android系統不能識別這種so.

查看手機的/system/lib, 也可以發現系統內部的so也都是lib*.so這樣的命名.

直接修改so的名字是不行的, 因為在so的SONAME內會指出該so的名字, 該so依賴的其他so名字.(見個例子使用macos上的rpl執行全文字元串替換)

> android-ndk-r18b/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-readobj -dynamic-table libssl.1.1.so    0x00000001 NEEDED               Shared library: [libcrypto.1.1.so]    0x00000001 NEEDED               Shared library: [libdl.so]    0x00000001 NEEDED               Shared library: [libc.so]    0x0000000E SONAME               Library soname: [libssl.1.1.so]

為了使Android可以正常載入so, 我們改變生成so的名字. so的版本號在版本管理時還是很有用的, 所以我們生成的so命名格式為libcrypto.1.1.so, 這樣既有版本號, 也不影響Android正常載入.

打開Configurations/15-android.conf, 在第173行增加shared_extension => ".$(SHLIB_VERSION_NUMBER).so", 修改完成後如下所示:

    "android" => {          inherit_from     => [ "linux-generic32" ],          template         => 1,          cflags           => add(sub { android_ndk()->{cflags} }),          cppflags         => add(sub { android_ndk()->{cppflags} }),          cxxflags         => add(sub { android_ndk()->{cflags} }),          bn_ops           => sub { android_ndk()->{bn_ops} },          bin_cflags       => "-pie",          enable           => [ ],          # 重定義`shared_extension`, 修改生成的so名字          shared_extension => ".$(SHLIB_VERSION_NUMBER).so",      },

2. 編譯前需要安裝的工具

perl  make  ndk

最好在Linux/MacOS下編譯, 在Windows下編譯最好使用MSYS2, Windows下編譯官網沒有正式測試.

目前Android在ndk r18里移除了gcc, 在ndk r19里不再推薦Standalone Toolchains, 所以這裡我們裸用clang來進行編譯. 使用的ndk為r18b. 但是隨著編譯另外的curl和ffmpeg, 我覺得還是用 Standalone Toolchains更方便些.

同時筆者喜歡使用ripgrep替代grep, 在一些輔助命令中, 你可能會看到rg.

3. 配置和編譯

Android是跨平台編譯, 你應該使用./Configure而不是./config.

Android支援平台列表:

android-arm  android-arm64  android-mips  android-mip64  android-x86  android-x86_64

不要傳遞--cross-compile-prefix, ./Configure會基於你輸入的platform自動選擇.

不過為了調用指定平台的$(CROSS_COMPILE)/gcc編譯器, 你仍然需要在PATH里指定特定gcc所在位置, 參考下文配置.

除了PATH外, 你還必須設置ANDROID_NDK_HOME環境變數來指定NDK的目錄, 如/some/where/android-ndk-<ver>.

這兩個變數, PATHANDROID_NDK_HOME在配置和編譯時期都很重要.

NDK通常支援多個Android API級別, 你可以在android-ndk-<ver>/platforms下面找到所有支援的API級別, 如android-14, android-21.

openssl默認會選擇最新的API level. 如果你想支援比較老的target platform, 傳遞-D__ANDROID_API__=Num來配置,

這裡Num是target platform的版本號.

示例: compile for JELLY_BEAN(Android 4.1, API Level16) on ARM with NDK r18b

export ANDROID_NDK=/Users/along/Library/Android/android-ndk-r18b  export PATH=$ANDROID_NDK/toolchains/llvm/prebuilt/darwin-x86_64/bin:$PATH  export CC=clang  export RANLIB=:  export AR=llvm-ar  export ARFLAGS=rs  ./Configure android-arm -D__ANDROID_API__=16 --prefix=$(pwd)/android-arm  make  make install

這裡clang表示, 當ARFLAGS=rs時, RANLIB不是必須的, 所以這裡留空.

這樣就會在android-arm目錄下生成.so和.a文件.

> tree -L 2  .  ├── bin  │   ├── c_rehash  │   └── openssl  ├── include  │   └── openssl  ├── lib  │   ├── engines-1.1  │   ├── libcrypto.1.1.so  │   ├── libcrypto.a  │   ├── libcrypto.so -> libcrypto.1.1.so  │   ├── libssl.1.1.so  │   ├── libssl.a  │   ├── libssl.so -> libssl.1.1.so  │   └── pkgconfig    > rg -uu --files . | rg so$|a$ | xargs ls -alh  -rwxr-xr-x  1 along  staff   2.5M Mar 28 16:45 ./lib/libcrypto.1.1.so  -rw-r--r--  1 along  staff   3.9M Mar 28 16:45 ./lib/libcrypto.a  -rwxr-xr-x  1 along  staff   577K Mar 28 16:45 ./lib/libssl.1.1.so  -rw-r--r--  1 along  staff   737K Mar 28 16:45 ./lib/libssl.a

注意

早期的OpenSSL版本依賴於CROSS_SYSROOT變數, 這個變數一般設置為$ANDROID_NDK_HOME/platforms/android-<api>/arch-<arch>, 是為了指定headers-n-libraries位置. 新版本openssl中, 為了兼容舊項目, 這個變數仍可以被識別. 但是由於CROSS_SYSROOT包含了Android的API level, 再傳遞-D__ANDROID_API__=Num可能導致衝突, 因為openssl不支援混合提供這兩個變數. 建議不在使用CROSS_SYSROOT.

4. 使用

在CMakeList中使用如下

cmake_minimum_required(VERSION 3.4.1)  # 要打的so名字 native-lib  add_library(native-lib SHARED native-lib.cpp)  # 引入openssl的頭文件  target_include_directories(native-lib PRIVATE ${CMAKE_SOURCE_DIR}/include)  # 引入 libssl.1.1.so  add_library(mylibssl SHARED IMPORTED)  set_target_properties(mylibssl PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../jniLibs/armeabi-v7a/libssl.1.1.so)  # 引入 libcrpto.1.1.so  add_library(mylibcrypto SHARED IMPORTED)  set_target_properties(mylibcrypto PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../jniLibs/armeabi-v7a/libcrypto.1.1.so)  # 引入系統log  find_library(log-lib log)  # 注意find_library和add_library的庫名, 引用方式不一樣. 使用{mylibssl}時, 不會報錯, 但是libssl.so並沒有被找到, 只會提示找不到函數定義  target_link_libraries(native-lib          ${log-lib}          mylibssl          mylibcrypto          )

這樣在Java程式碼中就可以使用如下方式載入.

        System.loadLibrary("ssl.1.1");          System.loadLibrary("crypto.1.1");

5. 解惑

遇到不解的, 還是查看github上的openssl文檔和issue, 裡面有很多前人提的問題.

Android是INSTALLNOTES.ANDROID, 編譯遇到問題, 可以查看INSTALL的Troubleshooting章節.

官網推薦的幫助:

        INSTALL         Linux, Unix, Windows, OpenVMS, ...          NOTES.*         INSTALL addendums for different platforms

最後提醒下, 不要過度參考"https://wiki.openssl.org/index.php/Android", github上openssl的維護人員稱該文檔太久沒有更新, 參考INSTALL和NOTES.*.

6. 參考文檔:

https://github.com/openssl/openssl/blob/6bc62a620e/NOTES.ANDROID

https://github.com/openssl/openssl/blob/6bc62a620e/INSTALL

https://developer.android.com/ndk/guides/standalone_toolchain