最新的ndkr20编译c_史上最优雅的NDK加载pass方案
關(guān)鍵詞:
不需要編譯llvm
僅依賴NDK,不需要額外的其他環(huán)境
不會(huì)遇到配置引起的符號(hào)NotFound問題
不污染NDK
一、背景介紹
現(xiàn)在代碼保護(hù)技術(shù)很多是在llvm上實(shí)現(xiàn)的,例如 ollvm 和 hikari,作者給出的實(shí)現(xiàn)是將源碼混雜在llvm中,這樣做非常不優(yōu)雅。近來越來越多安全工作者都開始接觸和研究基于llvm的代碼保護(hù),工欲善其事必先利其器,在編譯、運(yùn)行均是本機(jī)的環(huán)境下,不會(huì)出問題,因此本文介紹的是,如何優(yōu)雅地在NDK中加載pass。
安卓開發(fā)者使用混淆技術(shù)來保護(hù)native代碼時(shí),一般有兩種選擇:
第一個(gè)選擇是獲得git上 ollvm 或 hikari 的代碼,編譯后,替換掉NDK中原先的toolchain。
這是最不優(yōu)雅的方式,因?yàn)榫S護(hù)起來很麻煩,因?yàn)樾枰幾g整個(gè)llvm工程,并且對(duì)NDK有侵入性,無法保證修改前和修改后NDK的功能不發(fā)生變化。
第二個(gè)選擇是,編譯llvm工程,替換掉NDK中原先的toolchain,并且在相同環(huán)境下,移植 ollvm 或hikari 為獨(dú)立的plugin,(移植方案我的github里有寫 https://github.com/LeadroyaL/llvm-pass-tutorial )用編譯為插件的形式,動(dòng)態(tài)加載插件。
相比第一個(gè)方案,極大降低維護(hù)的代價(jià),只編譯一個(gè)pass即可,但仍然對(duì)NDK有侵入性。
這兩種方案的共同特點(diǎn)是:都需要編譯整個(gè)llvm項(xiàng)目,初次部署時(shí)要消耗大量的時(shí)間和資源,另外在選擇llvm版本時(shí),也會(huì)糾結(jié)適配性的問題(雖然通常不會(huì)出現(xiàn)適配問題)
筆者曾經(jīng)使用的是第二種方案,經(jīng)過研究,本文提出第三種方案,使用NDK中的環(huán)境編譯pass并加載pass,優(yōu)雅程度上來看,有以下的特點(diǎn):
最最重要的,不需要編譯llvm項(xiàng)目,節(jié)省巨大的時(shí)間和資源消耗;
其次,不修改原先的NDK運(yùn)行環(huán)境,和原生的NDK是最像的,沒有侵入性;
再次,上下文均和NDK完全一致,不需要擔(dān)心符號(hào)問題,不需要額外安裝軟件和環(huán)境,有NDK的環(huán)境就足矣;
本文演示的環(huán)境是:ubuntu18.04(任意linux均可)、ndk-r20(任意NDK版本均可)、cmake(選擇較高版本)
二、使用NDK的環(huán)境編譯一個(gè)pass
眾所周知,編譯Pass時(shí)需要使用llvm的環(huán)境,由于NDK中的llvm環(huán)境是破損的,所以開發(fā)者一般自己編譯一份llvm環(huán)境出來,替換掉NDK中的llvm環(huán)境,包括我本人之前也是這樣處理的,這樣做的原因是NDK中的llvm是破損的,因?yàn)镹DK來自AOSP編譯好的toolchain,而AOSP在制作toolchain的過程中是移除了部分文件的。
上文提到,本文的方案是不需要親自編譯llvm的,因此就需要使用NDK中的破損的llvm環(huán)境來編譯一個(gè)pass。
根據(jù)對(duì) https://android.googlesource.com/toolchain/llvm_android/ 的閱讀和調(diào)試,NDK中的llvm缺失的是一部分binary文件、全部靜態(tài)鏈接庫(kù)文件、全部頭文件,采用的是靜態(tài)連接的方式,它的clang是較為獨(dú)立的文件(它會(huì)依賴libc++,因此成為較為獨(dú)立)。
cmake_minimum_required(VERSION 3.4)
# we need LLVM_HOME in order not automatically set LLVM_DIR
if(NOT DEFINED ENV{LLVM_HOME})
message(FATAL_ERROR "$LLVM_HOME is not defined")
else ()
set(ENV{LLVM_DIR} $ENV{LLVM_HOME}/lib/cmake/llvm)
endif()
find_package(LLVM REQUIRED CONFIG)
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})
add_subdirectory(skeleton) # Use your pass name here.
幸運(yùn)的是,NDK中的lib/cmake/llvm還在,里面的cmake文件都是原汁原味的的。
不幸的是,由于AOSP在編譯toolchain時(shí)設(shè)置了 defines['LLVM_LIBDIR_SUFFIX'] = '64' ,導(dǎo)致find_package的路徑應(yīng)該是 lib64/cmake/llvm,需要稍加修改
之后進(jìn)行 mkdir b;cd b;cmake ..
會(huì)報(bào)如下的錯(cuò)誤:
? b git:(master) ? cmake ..
CMake Error at /home/leadroyal/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/lib64/cmake/llvm/LLVMExports.cmake:806 (message):
The imported target "LLVMDemangle" references the file
"/home/leadroyal/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/lib64/libLLVMDemangle.a"
but this file does not exist. Possible reasons include:
* The file was deleted, renamed, or moved to another location.
* An install or uninstall procedure did not complete successfully.
* The installation package was faulty and contained
"/home/leadroyal/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/lib64/cmake/llvm/LLVMExports.cmake"
but not all the files it references.
Call Stack (most recent call first):
/home/leadroyal/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/lib64/cmake/llvm/LLVMConfig.cmake:173 (include)
CMakeLists.txt:8 (find_package)
-- Configuring incomplete, errors occurred!
See also "/home/leadroyal/llvm-pass-tutorial/b/CMakeFiles/CMakeOutput.log".
因?yàn)镹DK不含有.a文件,而cmake會(huì)檢查這些文件,用于靜態(tài)連接,被認(rèn)為初始化失敗,出錯(cuò)。
看源碼對(duì)應(yīng)的位置:
# Loop over all imported files and verify that they actually exist
foreach(target ${_IMPORT_CHECK_TARGETS} )
foreach(file ${_IMPORT_CHECK_FILES_FOR_${target}} )
if(NOT EXISTS "${file}" )
message(FATAL_ERROR "The imported target \"${target}\" references the file
\"${file}\"
but this file does not exist. Possible reasons include:
* The file was deleted, renamed, or moved to another location.
* An install or uninstall procedure did not complete successfully.
* The installation package was faulty and contained
\"${CMAKE_CURRENT_LIST_FILE}\"
but not all the files it references.
")
endif()
endforeach()
unset(_IMPORT_CHECK_FILES_FOR_${target})
endforeach()
在文件不存在時(shí),報(bào) message(FATAL_ERROR xxxxxx),從而中斷編譯,但我們本來就是編譯動(dòng)態(tài)鏈接庫(kù)的,不需要.a文件,所以這里做一個(gè)patch,降低log_level,使用WARNING等級(jí)。
- message(FATAL_ERROR "The imported target \"${target}\" references the file
+ message(WARNING "The imported target \"${target}\" references the file
接下來面對(duì)第二個(gè)問題,之前提到過,NDK中缺失我們需要的頭文件,它們本該出現(xiàn)在include/llvm中,因此這句話失效了
include_directories(${LLVM_INCLUDE_DIRS})
但我們又不能隨便找一堆頭文件過來,版本之間可能有變更,萬一用到一些配置不一樣的頭文件,就會(huì)出現(xiàn)非預(yù)期(例如經(jīng)常出錯(cuò)的LLVM_ENABLE_ABI_BREAKING_CHECKS配置)
此時(shí)的思路是,找到NDK中l(wèi)lvm生成時(shí)的那份commit,從中獲取include文件,有兩個(gè)方案
第一個(gè)方案是找到源碼并使用cmake幫我們提取一遍。
第二個(gè)方案是直接使用aosp提供的prebuilt文件,顯然為了方便我們選擇后者。
toolchain 在生成時(shí)會(huì)有一份描述版本信息的文件,在ndk生成時(shí)也被拷貝過來了
? linux-x86_64 cat /home/leadroyal/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/AndroidVersion.txt
8.0.7
based on r346389c
【AOSP相關(guān)訪問google的前提條件你懂的】
即可獲得到這個(gè)目錄的壓縮包。
如果可以接受NDK被污染(我使用的是這個(gè)方案),可以將它放到NDK的toolchain中,這樣就可以繼續(xù)使用 ${LLVM_INCLUDE_DIRS} 這個(gè)變量;
如果不能接受NDK被污染,就隨便放個(gè)目錄,使用 include_directories(/path/to/clang-r346389c/include)
比如放在NDK里的include里,是這個(gè)樣子(c++目錄本來就有)
? include lsa
total 5.4M
drwxr-xr-x 8 leadroyal leadroyal 4.0K Oct 21 02:13 .
drwxr-xr-x 15 leadroyal leadroyal 4.0K Oct 20 23:16 ..
drwxr-xr-x 4 leadroyal leadroyal 4.0K Oct 21 02:12 c++
drwxr-xr-x 33 leadroyal leadroyal 4.0K Oct 21 02:12 llvm
drwxr-xr-x 3 leadroyal leadroyal 4.0K Oct 21 02:12 llvm-c
然后有幾率遇到C++版本的問題,llvm10以上需要添加
set(CMAKE_CXX_STANDARD 14)
在這在情況下使用的CMakeLists.txt最終是:
cmake_minimum_required(VERSION 3.4)
if(NOT DEFINED ENV{LLVM_HOME})
message(FATAL_ERROR "$LLVM_HOME is not defined")
endif()
if(NOT DEFINED ENV{LLVM_DIR})
set(ENV{LLVM_DIR} $ENV{LLVM_HOME}/lib64/cmake/llvm)
endif()
find_package(LLVM REQUIRED CONFIG)
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
set(CMAKE_CXX_STANDARD 14)
add_subdirectory(skeleton) # Use your pass name here.
修復(fù)完include問題后,就可以舒舒服服地使用cmake來生成demo了,如下:
export LLVM_HOME=/home/leadroyal/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64
? b git:(master) ? cmake ..
-- The C compiler identification is GNU 7.4.0
-- The CXX compiler identification is GNU 7.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/leadroyal/llvm-pass-tutorial/b
? b git:(master) ? cmake --build .
Scanning dependencies of target SkeletonPass
[ 50%] Building CXX object skeleton/CMakeFiles/SkeletonPass.dir/Skeleton.cpp.o
[100%] Linking CXX shared module libSkeletonPass.so
[100%] Built target SkeletonPass
三、使用NDK的環(huán)境加載一個(gè)pass
編譯部分完成了,接下來是加載部分,我們隨便找一個(gè)android native項(xiàng)目,修改build.gradle中的flag
externalNativeBuild {
cmake {
cppFlags "-Xclang -load -Xclang /home/leadroyal/llvm-pass-tutorial/b/skeleton/libSkeletonPass.so"
}
}
gradle build 命令后,可能會(huì)如下報(bào)錯(cuò)(當(dāng)編譯pass時(shí)使用了GNU系列的c++時(shí)候會(huì)遇到,常見于ubuntu,因?yàn)镹DK使用的是llvm系列的c++)
如果出現(xiàn)如下報(bào)錯(cuò)的話,解決方案如下,如果沒有報(bào)錯(cuò),請(qǐng)?zhí)^這部分
通常被搜索的關(guān)鍵詞是:_ZNK4llvm12FunctionPass17createPrinterPassERNS_11raw_ostreamERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
========GNU使用兼容libc++的方案(沒遇到可以跳過) =======
./gradlew build
error: unable to load plugin '/home/leadroyal/llvm-pass-tutorial/b/skeleton/libSkeletonPass.so': '/home/leadroyal/llvm-pass-tutorial/b/skeleton/libSkeletonPass.so: undefined symbol: _ZNK4llvm12FunctionPass17createPrinterPassERNS_11raw_ostreamERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE'
很奇怪,提醒這個(gè)符號(hào)找不到,但是我們編譯時(shí)能找到、連接時(shí)找不到,就很奇怪。
demangle一下:
c++filt _ZNK4llvm12FunctionPass17createPrinterPassERNS_11raw_ostreamERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
llvm::FunctionPass::createPrinterPass(llvm::raw_ostream&, std::__cxx11::basic_string, std::allocator > const&) const
去NDK的相關(guān)目錄下grep,發(fā)現(xiàn)了該符號(hào):
? lib64 pwd
/home/leadroyal/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/lib64
? lib64 strings * | grep _ZNK4llvm12FunctionPass17createPrinterPass
strings: Warning: 'clang' is a directory
strings: Warning: 'cmake' is a directory
_ZNK4llvm12FunctionPass17createPrinterPassERNS_11raw_ostreamERKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEE
demangle一下:
c++filt _ZNK4llvm12FunctionPass17createPrinterPassERNS_11raw_ostreamERKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEE
llvm::FunctionPass::createPrinterPass(llvm::raw_ostream&, std::__1::basic_string, std::__1::allocator > const&) const
對(duì)比一下二者,注意一個(gè)細(xì)節(jié),參數(shù)命名空間不一致:
NDK里的叫std::__1::basic_string,我們編出來的叫std::__cxx11::basic_string
NDK里的叫std::__1::char_traits,我們編出來的叫std::char_traits
如果是老司機(jī)的話,一眼就知道它們使用了不同版本的c++,最初的源碼是一致的,解決起來不難。
用__cxx11的叫l(wèi)ibc++,用__1的叫l(wèi)ibstdc++。
解決方案是在連接時(shí)使用libc++,set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++"),但由于ubuntu裝的一般是gcc系列,而gcc系列是沒有l(wèi)ibc++的,編譯會(huì)crash如下:
Using built-in specs.
COLLECT_GCC=/usr/bin/c++
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
c++: error: unrecognized command line option ‘-stdlib=libc++’
gcc沒有l(wèi)ibc++,只有l(wèi)lvm系列擁有l(wèi)ibc++,所以需要將編譯器切換到clang。
重申我們之前的原則:不需要安裝額外的軟件,恰好NDK提供了一個(gè)clang給我們,為了方便我就用它提供的了(畢竟安裝一個(gè)clang也挺麻煩的)
我把libc++的頭文件放在 /home/leadroyal/Android/Sdk/r346389c/include/ 下
放好后對(duì)它進(jìn)行include,在這在情況下使用的CMakeLists.txt最終是:
cmake_minimum_required(VERSION 3.4)
set(CMAKE_C_COMPILER /home/leadroyal/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/clang)
set(CMAKE_CXX_COMPILER /home/leadroyal/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++)
if(NOT DEFINED ENV{LLVM_HOME})
message(FATAL_ERROR "$LLVM_HOME is not defined")
endif()
if(NOT DEFINED ENV{LLVM_DIR})
set(ENV{LLVM_DIR} $ENV{LLVM_HOME}/lib64/cmake/llvm)
endif()
find_package(LLVM REQUIRED CONFIG)
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
include_directories(/home/leadroyal/Android/Sdk/r346389c/include/c++/v1)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
add_subdirectory(skeleton) # Use your pass name here.
我們使用gcc和clang編譯兩份pass出來,對(duì)比一下前后的區(qū)別:
使用GCC編譯出來的文件
? b git:(master) ? ldd skeleton/libSkeletonPass.so
linux-vdso.so.1 (0x00007ffc3c3d5000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007ff114c76000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007ff114a5e000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff11466d000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007ff1142cf000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff115205000)
使用clang編譯出來的文件
? b git:(master) ? ldd skeleton/libSkeletonPass.so
linux-vdso.so.1 (0x00007ffc369e2000)
libc++.so.1 => not found
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f002042c000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f0020214000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f001fe23000)
/lib64/ld-linux-x86-64.so.2 (0x00007f00209d3000)
雖然后者提醒libc++.so.1找不到,感覺很詫異,于是去查ndk clang的依賴
? bin ldd /home/leadroyal/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/clang
linux-vdso.so.1 (0x00007ffc99bc7000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f3bb9d24000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f3bb9b07000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f3bb98ff000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f3bb96fb000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f3bb935d000)
libc++.so.1 => /home/leadroyal/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/../lib64/libc++.so.1 (0x00007f3bba07e000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f3bb9145000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3bb8d54000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3bb9f43000)
發(fā)現(xiàn)在NDK里確實(shí)存在libc++.so.1環(huán)境,問題解決,我們回歸主題,最后一步,使用NDK加載它!
========GNU使用兼容libc++的方案 end =======
我們先用簡(jiǎn)單的c文件驗(yàn)證我們的pass,沒有任何問題
? /tmp cat test.c
#include
int main(){
printf("HelloWorld\n");
return 0;
}
? /tmp /home/leadroyal/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -Xclang -load -Xclang /home/leadroyal/llvm-pass-tutorial/b/skeleton/libSkeletonPass.so test.c
I saw a function called main!
? /tmp ./a.out
HelloWorld
最后一步,見證奇跡的時(shí)刻!
? MyApplication ./gradlew clean build
............
> Task :app:externalNativeBuildDebug
Build native-lib_armeabi-v7a
ninja: Entering directory `/home/leadroyal/AndroidStudioProjects/MyApplication/app/.cxx/cmake/debug/armeabi-v7a'
[1/2] Building CXX object CMakeFiles/native-lib.dir/native-lib.cpp.o
I saw a function called Java_com_example_myapplication_MainActivity_stringFromJNI!
I saw a function called _ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2EPKc!
I saw a function called _ZN7_JNIEnv12NewStringUTFEPKc!
I saw a function called _ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE5c_strEv!
I saw a function called _ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev!
淚流滿面!我們終于成功編譯并且加載了這個(gè)Pass!
四、當(dāng)我們來到macOS上
同Linux一樣,先修復(fù)cmake文件,再下載include/llvm和incude/llvm-c,因?yàn)閙acOS默認(rèn)就是clang了,所以不會(huì)存在libstdc++和libc++沖突的問題,編譯過程全程沒有任何障礙。
但是在加載時(shí)卻遇到了如下的錯(cuò)誤,也是在網(wǎng)上經(jīng)常被貼出來問問題的報(bào)錯(cuò)
? /tmp $ANDROID_NDK/20.0.5594570/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang -Xclang -load -Xclang /home/leadroyal/llvm-pass-tutorial/b/skeleton/libSkeletonPass.so test.c
error: unable to load plugin '/home/leadroyal/llvm-pass-tutorial/b/skeleton/libSkeletonPass.so':
'dlopen(/home/leadroyal/llvm-pass-tutorial/b/skeleton/libSkeletonPass.so, 9): Symbol not found:
__ZN4llvm12FunctionPass17assignPassManagerERNS_7PMStackENS_15PassManagerTypeE
Referenced from: /home/leadroyal/llvm-pass-tutorial/b/skeleton/libSkeletonPass.so
Expected in: flat namespace
in /home/leadroyal/llvm-pass-tutorial/b/skeleton/libSkeletonPass.so'
demangle一下
c++filt __ZN4llvm12FunctionPass17assignPassManagerERNS_7PMStackENS_15PassManagerTypeE
llvm::FunctionPass::assignPassManager(llvm::PMStack&, llvm::PassManagerType)
這個(gè)符號(hào)是llvm中導(dǎo)出的符號(hào),供開發(fā)者調(diào)用,libSkeletonPass.so需要該符號(hào),但是clang的進(jìn)程空間里沒有這個(gè)符號(hào)。
經(jīng)過仔細(xì)對(duì)照,發(fā)現(xiàn)不僅僅缺失這一個(gè)符號(hào),缺失的是一大堆相關(guān)的符號(hào),而且都是較為基礎(chǔ)的符號(hào),只是最先被尋找的是這個(gè)就停下來了。
【先劇透一下,這個(gè)符號(hào)缺失是apple基礎(chǔ)工具的bug,但是google沒有發(fā)現(xiàn)這個(gè)bug,已報(bào)告https://issuetracker.google.com/issues/143160164】
這時(shí)有另一個(gè)線索:我們自己編譯出來的pass是可以正常加載pass的,一定是AOSP動(dòng)了手腳,這里省去大量的diff時(shí)間,直接說結(jié)果。
記作X:使用llvm默認(rèn)配置(與Android無關(guān))編譯出來的clang,可以找到符號(hào)
記作Y:使用AOSP得到的stage2-install/bin/clang,可以找到符號(hào)
記作Z:使用AOSP得到的toolchain中的clang,無法找到符號(hào)
X/Y 可以說明, https://android.googlesource.com/toolchain/llvm_android/ 中對(duì)llvm的編譯配置,是不影響符號(hào)的
Y/Z 可以說明,strip前和strip后會(huì)導(dǎo)致符號(hào)缺失。在ubuntu上符號(hào)仍然被保留,在macOS上符號(hào)會(huì)消失。
for bin_filename in os.listdir(bin_dir):
binary = os.path.join(bin_dir, bin_filename)
if os.path.isfile(binary):
if bin_filename not in necessary_bin_files:
remove(binary)
elif strip and bin_filename not in script_bins:
check_call(['strip', binary])
之后我將X進(jìn)行/usr/bin/strip,發(fā)現(xiàn)仍然可以加載pass,這時(shí)就開始犯暈,開始缺乏思路。
于是出現(xiàn)了另一個(gè)可能引發(fā)問題的原因:我編譯X、strip-X都是在CommandLineTools 10.15上完成的,但編譯Y、strip-Y是在CommandLineTools 10.13上完成的,二者的strip不完全一致!
經(jīng)過最后一個(gè)實(shí)驗(yàn),發(fā)現(xiàn)低版本的/usr/bin/strip會(huì)錯(cuò)誤地移除掉很多符號(hào),導(dǎo)致加載失敗,日志如下,我分別用10.13/10.14/10.15的strip去處理stage2-install/bin/clang文件,發(fā)現(xiàn)10.13/14處理出來的文件是錯(cuò)誤的。
至此,真相大白,失敗的原因是:AOSP在編譯NDK時(shí)觸發(fā)了macOS自帶的strip的bug。
最后的掙扎:NDK中存在一個(gè)完備的、擁有符號(hào)的文件 LLVM.dylib 中的,如果我們讓libSkeleton.so依賴它,從LLVM.dylib中獲取符號(hào)會(huì)怎樣?
最終結(jié)果是,關(guān)鍵變量PassManager在NDK-clang中是沒有符號(hào)的,雖然在LLVM.dylib中可以找到,但二者已經(jīng)完全不是同一個(gè)instance了。
因此,macOS宣告失敗,等將來AOSP把這個(gè)bug修掉,就可以復(fù)用史上最優(yōu)雅的方法了。
五、當(dāng)我們來到Windows
對(duì)不起,能力有限告辭。。。
六、其他
不想看到的事情:
根據(jù)這次commit,開發(fā)者建議砍掉toolchain里的.cmake文件,因?yàn)樽髡咭呀?jīng)砍掉.a文件了,防止.cmake加載失敗引起的誤會(huì)。我也是弄完這一系列才看到這條commit,于是想盡自己的綿薄之力回滾一下,希望能成功吧。
以及,開發(fā)者建議砍掉ndk里的.cmake文件,體現(xiàn)在這次commit里
反正ndk-r21肯定是沒有cmake了,到時(shí)候就從toolchain里下載回來吧。
本文介紹了一種非常優(yōu)雅的NDK加載Pass方案,目前從未聽說國(guó)內(nèi)外有人使用該方案,感覺非常有意義,在此分享出來,希望更多人可以用到它、共同推動(dòng)安全行業(yè)的發(fā)展,完結(jié)撒花~
總結(jié)
以上是生活随笔為你收集整理的最新的ndkr20编译c_史上最优雅的NDK加载pass方案的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 重置npm设置_密码重置用户流程4部曲
- 下一篇: hmcl整合包导入_SSM实战第一篇_S