Git交叉编译到Android平台
Xayah

前言

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

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

Android NDK 版本:r21e

编译环境:Ubuntu 21.04

步骤

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

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

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

下载 GitCurl 的源码并解压

二、交叉编译Curl

打开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=/home/xayah/NDK # NDK根目录绝对路径

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

export MINSDKVERSION=24 # 最小目标SDK版本配置(24 即为 Android 7.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=./installed ..

cmake --build . --config Release
make install

成功编译产物可见于out/installed

我们将其移动/home/xayah/curl/installed备份

三、交叉编译Git

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

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

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

cd到源码根目录,输入:

1
make configure

此时即可生成configure

让我们试试:

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=/home/xayah/NDK                              # NDK根目录绝对路径

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

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

export API=24 # 最小目标SDK版本配置(24 即为 Android 7.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 --with-curl=/home/xayah/curl/installed --prefix=/home/xayah/git/install

可惜出现了错误

1
2
3
checking whether system succeeds to read fopen'ed directory... configure: error: in `/home/xayah/下载/git-2.32.0':
configure: error: cannot run test program while cross compiling
See `config.log' for more details

原来是交叉编译测试程序出了问题,我们的宿主机当然无法测试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
make configure
./configure --host=$TARGET --with-curl=/home/xayah/curl/installed --prefix=/home/xayah/git/install

一次试试?

遇到了类似问题

1
2
3
checking whether snprintf() and/or vsnprintf() return bogus value... configure: error: in `/home/xayah/下载/git-2.32.0':
configure: error: cannot run test program while cross compiling
See `config.log' for more details

删掉

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])

1
2
make configure
./configure --host=$TARGET --with-curl=/home/xayah/curl/installed --prefix=/home/xayah/git/install

一次试试?

Nice!这次成功了:

1
2
3
configure: creating ./config.status
config.status: creating config.mak.autogen
config.status: executing config.mak.autogen commands

接着我们:

1
make -j128

很不幸

1
2
run-command.c:520:35: error: use of undeclared identifier 'PTHREAD_CANCEL_DISABLE'
CHECK_BUG(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &as->cs),

这里有两种解决办法

方案一

通过查阅资料可知, PTHREAD_CANCEL_DISABLEglibcpthread 的一个常量0 ,因此我们将这里直接赋值为0即可。

继续编译

1
2
3
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [Makefile:2566:git-shell] 错误 1
/home/xayah/NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/../lib/gcc/aarch64-linux-android/4.9.x/../../../../aarch64-linux-android/bin/ld: cannot find -lrt: cannot find -lrt

又是他!Android由于性能及安全原因,放弃了glibc在其平台上的支持,所以相应地交叉编译链也不含有这个库,但是Android有其替代方案,所以我们这里直接把它去掉即可。

打开源码根目录Makefile,找到 NEEDS_LIBRT ,这里是一个 ifdef 判断,我们反其道行之,将其改为 ifndef 即可。

1
2
3
ifndef NEEDS_LIBRT
EXTLIBS += -lrt
endif

又遇到了一个问题!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/home/xayah/下载/git-2.32.0/run-command.c:520: undefined reference to `pthread_setcancelstate'
libgit.a(run-command.o): In function `atfork_parent':
/home/xayah/下载/git-2.32.0/run-command.c:531: undefined reference to `pthread_setcancelstate'
libgit.a(run-command.o): In function `atfork_prepare':
/home/xayah/下载/git-2.32.0/run-command.c:520: undefined reference to `pthread_setcancelstate'
libgit.a(run-command.o): In function `atfork_parent':
/home/xayah/下载/git-2.32.0/run-command.c:531: undefined reference to `pthread_setcancelstate'
libgit.a(run-command.o): In function `atfork_prepare':
/home/xayah/下载/git-2.32.0/run-command.c:520: undefined reference to `pthread_setcancelstate'
libgit.a(run-command.o): In function `atfork_parent':
/home/xayah/下载/git-2.32.0/run-command.c:531: undefined reference to `pthread_setcancelstate'
libgit.a(run-command.o): In function `atfork_prepare':
/home/xayah/下载/git-2.32.0/run-command.c:520: undefined reference to `pthread_setcancelstate'
libgit.a(run-command.o): In function `atfork_parent':
/home/xayah/下载/git-2.32.0/run-command.c:531: undefined reference to `pthread_setcancelstate'
libgit.a(run-command.o): In function `atfork_prepare':
/home/xayah/下载/git-2.32.0/run-command.c:520: undefined reference to `pthread_setcancelstate'
libgit.a(run-command.o): In function `atfork_parent':
/home/xayah/下载/git-2.32.0/run-command.c:531: undefined reference to `pthread_setcancelstate'
libgit.a(run-command.o): In function `atfork_prepare':
/home/xayah/下载/git-2.32.0/run-command.c:520: undefined reference to `pthread_setcancelstate'
libgit.a(run-command.o): In function `atfork_parent':
/home/xayah/下载/git-2.32.0/run-command.c:531: undefined reference to `pthread_setcancelstate'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

还是因为 pthread 的问题,这里我们把run-command.c所有的 pthread_setcancelstate 删掉:

1
2
CHECK_BUG(pthread_setcancelstate(0, &as->cs),
"disabling cancellation");
1
2
CHECK_BUG(pthread_setcancelstate(as->cs, NULL),
"re-enabling cancellation");

这次终于编译成功了!

方案二

实际上我们可以不使用 pthread

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=/home/xayah/NDK                              # NDK根目录绝对路径

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

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

export API=24 # 最小目标SDK版本配置(24 即为 Android 7.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 --with-curl=/home/xayah/curl/installed --prefix=/home/xayah/git/install --disable-pthreads

再修改源码根目录 Makefile

1
2
3
ifndef NEEDS_LIBRT
EXTLIBS += -lrt
endif

然后编译

1
make -j128

同样可以编译成功

安装

接下来我们把它安装到/home/xayah/git/install

1
make install -j128

安装成功后:

1
2
3
4
5
6
7
8
xayah@xayah-virtual-machine:~/git/install$ pwd
/home/xayah/git/install
xayah@xayah-virtual-machine:~/git/install$ ls
bin libexec share
xayah@xayah-virtual-machine:~/git/install$ cd bin
xayah@xayah-virtual-machine:~/git/install/bin$ ls
git gitk git-shell git-upload-pack
git-cvsserver git-receive-pack git-upload-archive

接下来我们推送到Android试试吧!

测试

由于一些魔法因素打包zip竟足足有200M+!可能是因为压缩算法不同,打包tar.xz就会小很多,大概10M左右。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
cas:/data/local/tmp/bin $ ./git clone https://github.com/git/git
fatal: destination path 'git' already exists and is not an empty directory.
128|cas:/data/local/tmp/bin $ ./git clone https://github.com/git/git mGit
Cloning into 'mGit'...
warning: templates not found in /home/xayah/git/install/share/git-core/templates
fatal: unable to find remote helper for 'https'
128|cas:/data/local/tmp/bin $

竟然报错了…通过查阅资料发现,这是找不到环境变量,所以我们设置一下:

1
export PATH=$PATH:/data/local/tmp/libexec/git-core

输出如下:

1
2
3
4
5
6
7
8
9
10
11
cas:/data/local/tmp/bin $ export PATH=$PATH:/data/local/tmp/libexec/git-core
cas:/data/local/tmp/bin $ ./git clone https://github.com/git/git mGit
Cloning into 'mGit'...
warning: templates not found in /home/xayah/git/install/share/git-core/templates
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
2
3
PS C:\Users\Xayah\Desktop> adb push gdbserver /data/local/tmp/bin
gdbserver: 1 file pushed, 0 skipped. 120.6 MB/s (1343712 bytes in 0.011s)
PS C:\Users\Xayah\Desktop> adb shell chmod 777 /data/local/tmp/bin/gdbserver

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

1
adb forward tcp:12345 tcp:12345

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

1
2
3
4
5
6
PS C:\Users\Xayah\Desktop> adb shell
cas:/ $ cd data/local/tmp/bin
cas:/data/local/tmp/bin $ export PATH=$PATH:/data/local/tmp/libexec/git-core
cas:/data/local/tmp/bin $ ./gdbserver 0.0.0.0:12345 ./git clone https://github.com/git/git mGit
Process ./git created; pid = 31451
Listening on port 12345

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

1
PS C:\Users\Xayah> cd D:\Downloads\Compressed\android-ndk-r21e-windows-x86_64\android-ndk-r21e\prebuilt\windows-x86_64\bin

启动gdb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS D:\Downloads\Compressed\android-ndk-r21e-windows-x86_64\android-ndk-r21e\prebuilt\windows-x86_64\bin> ./gdb
D:\Downloads\Compressed\android-ndk-r21e-windows-x86_64\android-ndk-r21e\prebuilt\windows-x86_64\bin\gdb-orig.exe: warning: Couldn't determine a path for the index cache directory.
GNU gdb (GDB) 8.3
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-w64-mingw32".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb)

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

未完待续….

  • 本文标题:Git交叉编译到Android平台
  • 本文作者:Xayah
  • 创建时间:2021-08-02 12:05:30
  • 本文链接:http://acmezone.tk/2021/08/02/Git交叉编译到Android平台/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!