精简 chromium 源码后编译 Android cronet 失败:undefined symbol: iswblank

0、缘起

由于工作需要,团队决定自己维护一份 Android cronet 库的源码,但由于 chromium 项目代码过于庞大,需要移除不必要的代码,移除之后再编译来测试是否成功编译出 cronet 库。
精简过程中需要删除一些可能不需要依赖的目录,以及修改相关的 BUILD.gn 文件,这些改动的后果都是不太能确定的。

1、总结

  1. 错误本身跟 android_ndk 版本没关系,因为编译成功和编译失败的参数都是一致的

  2. chromium 代码以及依赖项都是跟系统平台相关的,因此不要在两个不兼容平台之间随意拷贝源码。

  3. 精简 chromium 仓库时对 BUILD.gn 以及相关目录的增删必须要用文档记录,并做到及时更新,否则重头做一遍的代价太高。

2、问题定位过程

下面是详细的错误日志,可以看到第一个错误是由于 __posix_l_fallback.h 文件引用的 iswblank 未定义;是在编译生成目标文件 /libc++/locale.o 时出现的。经过查阅,该方法是一个 c++ 的标准库函数.

ninja: Entering directory `out/test-Cronet/'
[9/5622] SOLINK ./libnetty-tcnative.so
FAILED: libnetty-tcnative.so libnetty-tcnative.so.TOC lib.unstripped/libnetty-tcnative.so 
python "../../build/toolchain/gcc_solink_wrapper.py" --readelf="../../third_party/android_ndk/toolchains/arm-
linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-readelf" --
nm="../../third_party/android_ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-
androideabi-nm" --strip=../../buildtools/third_party/eu-strip/bin/eu-strip --sofile="./lib.unstripped/libnetty-
tcnative.so" --tocfile="./libnetty-tcnative.so.TOC" --output="./libnetty-tcnative.so" -- ../../third_party/llvm-
build/Release+Asserts/bin/clang++ -shared -Wl,--fatal-warnings -fPIC -Wl,-z,noexecstack -Wl,-z,relro -Wl,-
z,now -Wl,-z,defs -Wl,--as-needed --gcc-toolchain=../../third_party/android_ndk/toolchains/arm-linux-
androideabi-4.9/prebuilt/linux-x86_64 -fuse-ld=lld -Wl,--color-diagnostics -Wl,--no-rosegment -Wl,--exclude-
libs=libgcc.a -Wl,--exclude-libs=libvpx_assembly_arm.a --target=arm-linux-androideabi -Werror --
sysroot=../../third_party/android_ndk/platforms/android-16/arch-arm -nostdlib -Wl,--warn-shared-textrel -
L../../third_party/android_ndk/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a -o "./lib.unstripped/libnetty-
tcnative.so" -Wl,-soname="libnetty-tcnative.so" @"./libnetty-tcnative.so.rsp"
ld.lld: error: undefined symbol: iswblank
>>> referenced by __posix_l_fallback.h:79 (../../buildtools/third_party/libc++/trunk/include/support/xlocale/__posix_l_fallback.h:79)
>>>               obj/buildtools/third_party/libc++/libc++/locale.o:(std::__1::ctype_byname::do_is(unsigned long, wchar_t) const)

ld.lld: error: undefined symbol: iswblank
>>> referenced by __posix_l_fallback.h:79 (../../buildtools/third_party/libc++/trunk/include/support/xlocale/__posix_l_fallback.h:79)
>>>               obj/buildtools/third_party/libc++/libc++/locale.o:(std::__1::ctype_byname::do_is(wchar_t const*, wchar_t const*, unsigned long*) const)

ld.lld: error: undefined symbol: iswblank
>>> referenced by locale.cpp:1333 (../../buildtools/third_party/libc++/trunk/src/locale.cpp:1333)
>>>               obj/buildtools/third_party/libc++/libc++/locale.o:(std::__1::ctype_byname::do_scan_is(unsigned long, wchar_t const*, wchar_t const*) const)

ld.lld: error: undefined symbol: iswblank
>>> referenced by __posix_l_fallback.h:79 (../../buildtools/third_party/libc++/trunk/include/support/xlocale/__posix_l_fallback.h:79)
>>>               obj/buildtools/third_party/libc++/libc++/locale.o:(std::__1::ctype_byname::do_scan_is(unsigned long, wchar_t const*, wchar_t const*) const)

ld.lld: error: undefined symbol: iswblank
>>> referenced by __posix_l_fallback.h:79 (../../buildtools/third_party/libc++/trunk/include/support/xlocale/__posix_l_fallback.h:79)
>>>               obj/buildtools/third_party/libc++/libc++/locale.o:(std::__1::ctype_byname::do_scan_not(unsigned long, wchar_t const*, wchar_t const*) const)

ld.lld: error: undefined symbol: __ctype_get_mb_cur_max
>>> referenced by __bsd_locale_fallbacks.h:30 (../../buildtools/third_party/libc++/trunk/include/__bsd_locale_fallbacks.h:30)
>>>               obj/buildtools/third_party/libc++/libc++/locale.o:(std::__1::__libcpp_mb_cur_max_l(__locale_t*))
clang: error: linker command failed with exit code 1 (use -v to see invocation)
[11/5622] CXX obj/third_party/googletest/gtest/gtest-port.o
ninja: build stopped: subcommand failed.

我在 mac 平台和 ubuntu 平台均尝试使用了该方法,均能成功执行,意味着 iswblank 在使用默认的系统运行时库时并非是未定义的,测试程序如下:

// test.cc

#include 
int main(){
    char ch = 'a';
    if(::iswblank(ch)){
        std::cout << "true iswblank" << std::endl;
    }else{
        std::cout << "false iswblank" << std::endl;
    }
    return 0;
}

此时想到 gn 的编译往往不用系统的运行时库,而是依赖 chromium 仓库中独立的库。
在 buildtools/third_party/libc++/ 目录下找到了其 BUILD.gn 文件,从而追踪到了 locale.o 的源文件 locale.cpp,在该文件中添加 iswblank 的一行测试代码导致编译失败,此时可以看到编译 release 的详细参数:

../../third_party/llvm-build/Release+Asserts/bin/clang++ -MMD -MF 
obj/buildtools/third_party/libc++/libc++/locale.o.d -D_LIBCPP_BUILDING_LIBRARY -
D_LIBCPP_OVERRIDABLE_FUNC_VIS=__attribute__\(\(__visibility__\(\"default\"\)\)\) -
DLIBCXX_BUILDING_LIBCXXABI -DNO_TCMALLOC -DSAFE_BROWSING_DB_REMOTE -
DOFFICIAL_BUILD -DCHROMIUM_BUILD -D_GNU_SOURCE -DANDROID -DHAVE_SYS_UIO_H -
DANDROID_NDK_VERSION_ROLL=r16_1 -DCR_CLANG_REVISION=\"357692-1\" -
D_LIBCPP_ABI_UNSTABLE -D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS -
D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCPP_ENABLE_NODISCARD -
DCR_LIBCXX_REVISION=358423 -D__GNU_SOURCE=1 -DCHROMIUM_CXX_TWEAK_INLINES -
DNDEBUG -DNVALGRIND -DDYNAMIC_ANNOTATIONS_ENABLED=0 -I../.. -Igen -fprofile-sample-
use=../../chrome/android/profiles/afdo.prof -fprofile-sample-accurate -fno-strict-aliasing --param=ssp-buffer-
size=4 -fstack-protector -funwind-tables -fPIC -fcolor-diagnostics -fmerge-all-constants -fcrash-diagnostics-
dir=../../tools/clang/crashreports -Xclang -mllvm -Xclang -instcombine-lower-dbg-declare=0 -flto=thin -fsplit-lto-
unit -fcomplete-member-pointers -ffunction-sections -fno-short-enums --target=arm-linux-androideabi -
isystem../../third_party/android_ndk/sysroot/usr/include/arm-linux-androideabi -D__ANDROID_API__=16 -
DHAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC=1 -march=armv7-a -mfloat-abi=softfp -
mtune=generic-armv7-a -Xclang -fdebug-compilation-dir -Xclang . -no-canonical-prefixes -mfpu=vfpv3-d16 -
mthumb -Oz -fno-ident -fdata-sections -ffunction-sections -fomit-frame-pointer -gdwarf-3 -g1 -fdebug-info-for-
profiling -fvisibility=hidden -Xclang -add-plugin -Xclang find-bad-constructs -Xclang -plugin-arg-find-bad-
constructs -Xclang check-ipc -Wheader-hygiene -Wstring-conversion -Wtautological-overlap-compare -fstrict-
aliasing -fPIC -Werror -Wall -Wno-unused-variable -Wno-missing-field-initializers -Wno-unused-parameter -
Wno-c++11-narrowing -Wno-unneeded-internal-declaration -Wno-undefined-var-template -Wno-ignored-
pragma-optimize -std=c++14 -nostdinc++ -isystem../../buildtools/third_party/libc++/trunk/include -
isystem../../buildtools/third_party/libc++abi/trunk/include --sysroot=../../third_party/android_ndk/sysroot -
isystem../../third_party/android_ndk/sources/android/support/include -fvisibility-inlines-hidden -fexceptions -frtti 
-c ../../buildtools/third_party/libc++/trunk/src/locale.cpp -o obj/buildtools/third_party/libc++/libc++/locale.o

使用上述编译来编译 test.cc 得到 test.o;通过 nm 分析 test.o 发现此时 iswblank 是未定义的符号:

# nm -A test.o | grep iswblank
>>> test.o:         U iswblank

因此需要检查该编译参数中依赖的库是否完整或者是版本不一致导致的。
分析编译参数发现,android_ndk 版本为 r16_1,而网上有介绍它跟 iswblank 未定义有一定的关联,需要确认该参数是否可以指定为其他值,它为什么默认设置为 r16_1 的:

-DANDROID_NDK_VERSION_ROLL=r16_1  -D__ANDROID_API__=16 -march=armv7-a

或者可以对比一下能正确编译 release 的时候参数是什么样的,是否有区别,如果有,是怎么导致的,如果没有那么是否是因为缺少什么文件,下面是能正确编译的详细参数:

../../third_party/llvm-build/Release+Asserts/bin/clang++ -MMD -MF 
obj/buildtools/third_party/libc++/libc++/locale.o.d -D_LIBCPP_BUILDING_LIBRARY -
D_LIBCPP_OVERRIDABLE_FUNC_VIS=__attribute__\(\(__visibility__\(\"default\"\)\)\) -
DLIBCXX_BUILDING_LIBCXXABI -DNO_TCMALLOC -DSAFE_BROWSING_DB_REMOTE -
DOFFICIAL_BUILD -DCHROMIUM_BUILD -D_GNU_SOURCE -DANDROID -DHAVE_SYS_UIO_H -
DANDROID_NDK_VERSION_ROLL=r16_1 -DCR_CLANG_REVISION=\"357692-1\" -
D_LIBCPP_ABI_UNSTABLE -D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS -
D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS -D_LIBCPP_ENABLE_NODISCARD -
DCR_LIBCXX_REVISION=358423 -D__GNU_SOURCE=1 -DCHROMIUM_CXX_TWEAK_INLINES -
DNDEBUG -DNVALGRIND -DDYNAMIC_ANNOTATIONS_ENABLED=0 -I../.. -Igen -fprofile-sample-
use=../../chrome/android/profiles/afdo.prof -fprofile-sample-accurate -fno-strict-aliasing --param=ssp-buffer-
size=4 -fstack-protector -funwind-tables -fPIC -fcolor-diagnostics -fmerge-all-constants -fcrash-diagnostics-
dir=../../tools/clang/crashreports -Xclang -mllvm -Xclang -instcombine-lower-dbg-declare=0 -flto=thin -fsplit-lto-
unit -fcomplete-member-pointers -ffunction-sections -fno-short-enums --target=arm-linux-androideabi -
isystem../../third_party/android_ndk/sysroot/usr/include/arm-linux-androideabi -D__ANDROID_API__=16 -
DHAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC=1 -march=armv7-a -mfloat-abi=softfp -
mtune=generic-armv7-a -Xclang -fdebug-compilation-dir -Xclang . -no-canonical-prefixes -mfpu=vfpv3-d16 -
mthumb -Oz -fno-ident -fdata-sections -ffunction-sections -fomit-frame-pointer -gdwarf-3 -g1 -fdebug-info-for-
profiling -fvisibility=hidden -Xclang -add-plugin -Xclang find-bad-constructs -Xclang -plugin-arg-find-bad-
constructs -Xclang check-ipc -Wheader-hygiene -Wstring-conversion -Wtautological-overlap-compare -fstrict-
aliasing -fPIC -Werror -Wall -Wno-unused-variable -Wno-missing-field-initializers -Wno-unused-parameter -
Wno-c++11-narrowing -Wno-unneeded-internal-declaration -Wno-undefined-var-template -Wno-ignored-
pragma-optimize -std=c++14 -nostdinc++ -isystem../../buildtools/third_party/libc++/trunk/include -
isystem../../buildtools/third_party/libc++abi/trunk/include --sysroot=../../third_party/android_ndk/sysroot -
isystem../../third_party/android_ndk/sources/android/support/include -fvisibility-inlines-hidden -fexceptions -frtti 
-c ../../buildtools/third_party/libc++/trunk/src/locale.cpp -o obj/buildtools/third_party/libc++/libc++/locale.o

经过对比发现,两者的参数是一致的。

由于之前删除过 src/third_party/android_ndk/sources/cxx-stl/llvm-libc++ ,后来为了方便,我又从 mac 下的 chromium 仓库的

拷贝 llvm-libc++ 到 cronet 仓库,mac 下的 chromium 是 android 分支,与维护的是同一个版本;由于涉及不同的平台,mac 平

台和 Linux 平台,可能下载的代码会有些差别;保险起见,重新从 Linux 下的 chromium 仓库下载 llvm-libc++,替换现在维护的

仓库,同时替换 src/ 下的 BUILD.gn 为初始版本,并删除 open264、ffmpeg 等的依赖。终于问题不再出现。

因为做了两处改动,因此需再次一一验证,是哪一次改动影响了结果。

首先备份 linux 平台下的 llvm-libc++,然后拷贝一份 mac 下 chromium 仓库中的 llvm-c++ 至当前仓库对应的目录。

再次编译,出现下述错误,__ctype_get_mb_cur_max 未定义错误是由于 llvm-libc++ 的版本与当前编译的系统平台不一致导致的,比如在 Linux 下编译 cronet,需要保证源码及依赖是拉取的 Linux 平台(或 Android)的,而非 Mac 或其他平台的。

ld.lld: error: undefined symbol: __ctype_get_mb_cur_max
>>> referenced by __bsd_locale_fallbacks.h:30 (../../buildtools/third_party/libc++/trunk/include/__bsd_locale_fallbacks.h:30)
>>>               thinlto-cache/Thin-02860b.tmp.o:(std::__1::__libcpp_mb_cur_max_l(__locale_t*))
clang: error: linker command failed with exit code 1 (use -v to see invocation)
[1200/6835] CXX obj/third_party/ced/ced/compact_enc_det.o

所以在编译 android 版本或者 linux 版本的 cronet 时,需要考虑 linux 平台下的 chromium 仓库的源码,而不要拷贝 mac 平台的源码。

你可能感兴趣的:(cronet)