Git交叉编译到Android平台
Xayah Lv3

前言

这是一个很久之前的想法了,但是之前一直编译不成功。

这两天仔细研究了下,证明还是可行的。

Android NDK 版本:r23b

编译环境:Ubuntu 20.04

NDK目录:~/NDK

步骤

一、下载交叉编译所需源码

想要交叉编译Git,需要先交叉编译CurlZlib新版本NDK已经包含预编译Zlib,所以我们只需要交叉编译Curl即可。

至于为什么要交叉编译Curl,是因为git clone命令在clone https等仓库时,需要依赖该库创建的git-remote-https等EFL文件。否则,在clone时会发生找不到remote-https等错误

而若Curl依赖OpenSSL,因此还得先交叉编译OpenSSL

下载 GitCurlOpenSSL 的源码并解压

二、交叉编译OpenSSL并安装到NDK

OpenSSL官方仓库中,可以找到编译到Android平台的 文档
导出NDK临时变量

1
export ANDROID_NDK_ROOT=~/NDK

导出PATH临时变量

1
export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$ANDROID_NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin:$PATH

配置Makefile

1
./Configure android-arm64 -D__ANDROID_API__=26 --prefix=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/sysroot

编译

1
make -j128

安装到prefix目录

1
make install -j128

三、交叉编译Curl并安装到NDK

打开Curl的源码,我们可以发现它提供了两种编译方式,Autoconf MakefileCmake

使用Autoconf Makefile方式可以编译带OpenSSLCurl,但是由于某种未知原因,在后面在编译Git无法识别

因此我们选择Cmake方式。

INSTALL.cmake中我们可以得到一些编译的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Current flaws in the curl CMake build
=====================================

Missing features in the cmake build:

- Builds libcurl without large file support
- Does not support all SSL libraries (only OpenSSL, Schannel,
Secure Transport, and mbed TLS, NSS, WolfSSL)
- Doesn't allow different resolver backends (no c-ares build support)
- No RTMP support built
- Doesn't allow build curl and libcurl debug enabled
- Doesn't allow a custom CA bundle path
- Doesn't allow you to disable specific protocols from the build
- Doesn't find or use krb4 or GSS
- Rebuilds test files too eagerly, but still can't run the tests
- Doesn't detect the correct strerror_r flavor when cross-compiling (issue #1123)

由此可知,利用Cmake编译出来的Curl不带SSL库,但这并不影响我们后续编译Git相应的git-remote-https等二进制文件

cd解压后Curl根目录,输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mkdir out && cd out

export NDK=~/NDK # NDK根目录绝对路径

export ABI=arm64-v8a # ABI配置(arm64-v8a 即为 AArch64)

export MINSDKVERSION=26 # 最小目标SDK版本配置(26 即为 Android 8.0)

cmake \
-DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=$ABI \
-DANDROID_NATIVE_API_LEVEL=$MINSDKVERSION \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot ..

cmake --build . --config Release
make install

四、交叉编译Git

接下来是我们的重头戏了。

进入Git源码解压后的根目录,可以发现Git有两种编译方式,一是非Autoconf Makefile,二是Autoconf Makefile前者我尝试过多次,皆以失败告终。所以这次我们尝试后者

我们的宿主机因为无法测试Android平台上的二进制文件,所以我们把测试代码删掉。

进入configure.ac删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#
# Define FREAD_READS_DIRECTORIES if your are on a system which succeeds
# when attempting to read from an fopen'ed directory.
AC_CACHE_CHECK([whether system succeeds to read fopen'ed directory],
[ac_cv_fread_reads_directories],
[
AC_RUN_IFELSE(
[AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT],
[[
FILE *f = fopen(".", "r");
return f != NULL;]])],
[ac_cv_fread_reads_directories=no],
[ac_cv_fread_reads_directories=yes])
])
if test $ac_cv_fread_reads_directories = yes; then
FREAD_READS_DIRECTORIES=UnfortunatelyYes
else
FREAD_READS_DIRECTORIES=
fi
GIT_CONF_SUBST([FREAD_READS_DIRECTORIES])

再删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#
# Define SNPRINTF_RETURNS_BOGUS if your are on a system which snprintf()
# or vsnprintf() return -1 instead of number of characters which would
# have been written to the final string if enough space had been available.
AC_CACHE_CHECK([whether snprintf() and/or vsnprintf() return bogus value],
[ac_cv_snprintf_returns_bogus],
[
AC_RUN_IFELSE(
[AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT
#include "stdarg.h"

int test_vsnprintf(char *str, size_t maxsize, const char *format, ...)
{
int ret;
va_list ap;
va_start(ap, format);
ret = vsnprintf(str, maxsize, format, ap);
va_end(ap);
return ret;
}],
[[char buf[6];
if (test_vsnprintf(buf, 3, "%s", "12345") != 5
|| strcmp(buf, "12")) return 1;
if (snprintf(buf, 3, "%s", "12345") != 5
|| strcmp(buf, "12")) return 1]])],
[ac_cv_snprintf_returns_bogus=no],
[ac_cv_snprintf_returns_bogus=yes])
])
if test $ac_cv_snprintf_returns_bogus = yes; then
SNPRINTF_RETURNS_BOGUS=UnfortunatelyYes
else
SNPRINTF_RETURNS_BOGUS=
fi
GIT_CONF_SUBST([SNPRINTF_RETURNS_BOGUS])

观察源码结构,可以发现并没有configure文件,但存在configure.ac,所以我们可以make一个configure

cd到源码根目录,输入:

1
make configure

此时即可生成configure

git编译时会默认编译pthread,而Android由于性能及安全原因,放弃了glibc在其平台上的支持,所以相应地交叉编译链也不含有这个库。

Android有其替代方案,所以我们在make时将NEEDS_LIBRT赋值为空从而不编译它即可。

1
2
3
ifdef NEEDS_LIBRT
EXTLIBS += -lrt
endif

而在configure.ac中可以发现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
AC_ARG_ENABLE([pthreads],
[AS_HELP_STRING([--enable-pthreads=FLAGS],
[FLAGS is the value to pass to the compiler to enable POSIX Threads.]
[The default if FLAGS is not specified is to try first -pthread]
[and then -lpthread.]
[--disable-pthreads will disable threading.])],
[
if test "x$enableval" = "xyes"; then
AC_MSG_NOTICE([Will try -pthread then -lpthread to enable POSIX Threads])
elif test "x$enableval" != "xno"; then
PTHREAD_CFLAGS=$enableval
AC_MSG_NOTICE([Setting '$PTHREAD_CFLAGS' as the FLAGS to enable POSIX Threads])
else
AC_MSG_NOTICE([POSIX Threads will be disabled.])
NO_PTHREADS=YesPlease
USER_NOPTHREAD=1
fi],
[
AC_MSG_NOTICE([Will try -pthread then -lpthread to enable POSIX Threads.])
])

所以我们可以使用 –disable-pthreads 参数来取消编译 phread 相关部分。

于是接下来我们输入以下命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export NDK=~/NDK                                      # NDK根目录绝对路径

export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64 # 交叉编译链路径

export TARGET=aarch64-linux-android # 交叉编译目标

export API=26 # 最小目标SDK版本配置(26 即为 Android 8.0)

export AR=$TOOLCHAIN/bin/llvm-ar

export CC=$TOOLCHAIN/bin/$TARGET$API-clang

export AS=$CC

export CXX=$TOOLCHAIN/bin/$TARGET$API-clang++

export LD=$TOOLCHAIN/bin/ld

export RANLIB=$TOOLCHAIN/bin/llvm-ranlib

export READELF=$TOOLCHAIN/bin/readelf

export STRIP=$TOOLCHAIN/bin/llvm-strip

./configure --host=$TARGET --prefix=/data/local/tmp --disable-pthreads

注意这里的--prefix,需要定义为Android环境下git运行目录,否则会出现找不到templates等问题。

然后编译

1
make NEEDS_LIBRT= NO_TCLTK=1 -j128

注意这里的NO_TCLTK,如果不定义它的话,会默认编译git-gui,这不是我们需要的,所以将其定义为1

五、推送并测试

先安装到宿主机~/git/install

1
make install NEEDS_LIBRT= NO_TCLTK=1 DESTDIR=~/git/install -j128

可得如下产物

1
2
xayah@xayah-virtual-machine:~/git/install/data/local/tmp$ ls
bin libexec share

~/git/install/data/local/tmp 打包推送Android /data/local/tmp目录
由于一些魔法因素打包zip竟足足有200M+!可能是因为压缩算法不同,打包tar.xz就会小很多,大概10M左右。

1
2
3
4
5
6
7
8
PS C:\Users\Xayah\Desktop> adb push git.zip /data/local/tmp
git.zip: 1 file pushed, 0 skipped. 38.1 MB/s (648532238 bytes in 16.219s)
PS C:\Users\Xayah\Desktop> adb shell
cas:/ $ cd /data/local/tmp
cas:/data/local/tmp $ unzip git.zip >/dev/null
cas:/data/local/tmp $ cd bin
cas:/data/local/tmp/bin $ ls
git git-cvsserver git-receive-pack git-shell git-upload-archive git-upload-pack gitk

让我们git clone试试

1
2
3
4
5
6
7
8
9
cas:/data/local/tmp/bin $ ./git clone https://github.com/git/git mGit
Cloning into 'mGit'...
remote: Enumerating objects: 311108, done.
remote: Counting objects: 100% (72/72), done.
remote: Compressing objects: 100% (32/32), done.
remote: Total 311108 (delta 41), reused 70 (delta 40), pack-reused 311036
Receiving objects: 100% (311108/311108), 164.19 MiB | 10.32 MiB/s, done.
Resolving deltas: 100% (232238/232238), done.
Segmentation fault

Segmentation fault!这可是个头疼的事。不过没有关系,我们可以使用 gdbserver 调试,看看到底是哪里出了问题

首先将NDK中相应的gdbserver推送到Android平台,我的手机是aarch64(现在大多数安卓手机都是这个平台),也就是arm64,所以推送该文件夹下的gdbserver即可。

1
adb push gdbserver /data/local/tmp/bin

接下来新建一个终端forward一个自定义端口

1
adb forward tcp:12345 tcp:12345

接下来在另一个终端中进入adb shell

1
./gdbserver 0.0.0.0:12345 ./git clone https://github.com/git/git mGit

现在gdbserver已经启动了,我们转向另一个终端,启动gdb

image

现在gdbgdbserver均已启动,我们指定目标端口

image

输入c继续运行

image

段错误出现了!可以看出错误是出在copy_gecos()这个函数。
在源码中搜索这个函数,可以在ident.c中找到。
定位至相应位置

image

这个函数看起来貌似和git config有一丝关系,难道是因为我们没有定义git用户信息
让我们试试

1
./git config --global user.name "Xayah"
image

果然报错了!看来是.gitconfig没有创建成功,在源码中搜索.gitconfig,可以发现git获取.gitconfig的路径是$HOME/.gitconfig

那么我们试试把$HOME定义为当前目录

1
export HOME=/data/local/tmp/bin

再配置用户信息

1
2
./git config --global user.name "Xayah"
./git config --global user.email zds1249475336@gmail.com

这次没有报错了,并且当前目录也成功生成了.gitconfig

image

这次我们再试试git clone

1
./git clone https://github.com/git/git mGit
image

成功!

由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 42.1k 访客数 访问量