编译可用的Android模拟器ranchu内核

0x00 前言

前几天在使用Android模拟器的时候,发现无法连接PPTP类型的VPN服务器,报如下的错误:

I/mtpd (30035): Creating PPPoX socket
F/mtpd (30035): Socket() Address family not supported by protocol

对应的代码如下:

static int create_pppox()
{
    int pppox = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OPNS);
    log_print(INFO, "Creating PPPoX socket");

    if (pppox == -1) {
        log_print(FATAL, "Socket() %s", strerror(errno));
        exit(SYSTEM_ERROR);
    } else {
        struct sockaddr_pppopns address = {
            .sa_family = AF_PPPOX,
            .sa_protocol = PX_PROTO_OPNS,
            .tcp_socket = the_socket,
            .local = local,
            .remote = remote,
        };
        if (connect(pppox, (struct sockaddr *)&address, sizeof(address))) {
            log_print(FATAL, "Connect() %s", strerror(errno));
            exit(SYSTEM_ERROR);
        }
    }
    return pppox;
}

错误是在第7行报出来的,也就是说:在创建AF_PPPOX类型的socket时失败了。

网上说是因为kernel不支持的原因,需要重新编译kernel。

0x01 编译3.10的内核

查看模拟器中使用的内核版本:

Linux version 3.10.0+ (jinqian@jinqian.mtv.corp.google.com) (gcc version 4.9 20150123 (prerelease) (GCC) ) #448 SMP PREEMPT Mon Feb 29 13:49:38 PST 2016

将网上搜到的编译方法汇总如下(我只编译x86版本,不需要交叉编译):

git clone https://android.googlesource.com/kernel/goldfish.git

cd goldfish

git branch -a

返回如下结果:

remotes/origin/HEAD -> origin/master
remotes/origin/android-3.10
remotes/origin/android-3.18
remotes/origin/android-goldfish-2.6.29
remotes/origin/android-goldfish-3.10
remotes/origin/android-goldfish-3.10-k-dev
remotes/origin/android-goldfish-3.10-l-mr1-dev
remotes/origin/android-goldfish-3.10-m-dev
remotes/origin/android-goldfish-3.10-n-dev
remotes/origin/android-goldfish-3.18
remotes/origin/android-goldfish-3.18-dev
remotes/origin/android-goldfish-3.4
remotes/origin/android-goldfish-3.4-l-mr1-dev
remotes/origin/android-goldfish-4.4-dev
remotes/origin/heads/for/android-goldfish-3.18-dev
remotes/origin/linux-goldfish-3.0-wip
remotes/origin/master

选择android-goldfish-3.10分支

git checkout android-goldfish-3.10

android源码库的路径prebuilts/qemu-kernel/build-kernel.sh是一个内核编译脚本。执行如下命令可以直接编译内核:

prebuilts/qemu-kernel/build-kernel.sh --arch=x86 --config=i386_ranchu --out=/tmp/

如果报以下错误, 请在命令行后面加上: –cross=/usr/bin/

It looks like x86_64-linux-android-gcc is not in your path ! Aborting.

也可以改成android源码中提供的gcc路径前缀

编译完成后会在/tmp目录下生成kernel-qemu文件,将其替换掉模拟器镜像文件目录中的kernel-ranchu文件,重启模拟器即可;或是在启动模拟器的命令行中添加-kernel /tmp/kernel-qemu,启动也可以。

0x02 解决编译的内核无法启动模拟器问题

使用编译的内核启动模拟器后,发现会一直黑屏,无法进入系统。
加入-show-kernel参数后看到启动时一直报:

init: untracked pid 16860 killed by signal 9

查看logcat看到如下crash信息:

I/DEBUG   ( 1312): pid: 1389, tid: 1389, name: surfaceflinger  >>> /system/bin/surfaceflinger <<<
I/DEBUG   ( 1312): signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
I/DEBUG   ( 1312):     eax 00000000  ebx 0000056d  ecx 0000056d  edx 00000006
I/DEBUG   ( 1312):     esi b3056c48  edi 00000002
I/DEBUG   ( 1312):     xcs 00000073  xds 0000007b  xes 0000007b  xfs 00000000  xss 0000007b
I/DEBUG   ( 1312):     eip b2f75686  ebp 0000056d  esp bff6d410  flags 00000286
I/DEBUG   ( 1312): 
I/DEBUG   ( 1312): backtrace:
I/DEBUG   ( 1312):     #00 pc 00074686  /system/lib/libc.so (tgkill+22)
I/DEBUG   ( 1312):     #01 pc 0002217b  /system/lib/libc.so (pthread_kill+155)
I/DEBUG   ( 1312):     #02 pc 000239f4  /system/lib/libc.so (raise+36)
I/DEBUG   ( 1312):     #03 pc 0001bdf4  /system/lib/libc.so (abort+84)
I/DEBUG   ( 1312):     #04 pc 000314e5  /system/lib/libsurfaceflinger.so
I/DEBUG   ( 1312):     #05 pc 000201d7  /system/lib/libsurfaceflinger.so (android::SurfaceFlinger::init()+199)
I/DEBUG   ( 1312):     #06 pc 00000b51  /system/bin/surfaceflinger
I/DEBUG   ( 1312):     #07 pc 00012f94  /system/lib/libc.so (__libc_init+100)
I/DEBUG   ( 1312):     #08 pc 00000cd6  /system/bin/surfaceflinger

原来是surfaceflinger一直crash,导致看不到启动动画。

网上搜了一气,也没有找到原因。切换到3.10的其它几个分支也不行,最好的情况的是在关闭opengl的情况下进入系统了,但是界面显示明显和正常情况不一样。

在山穷水尽之时,看到了http://blog.csdn.net/ayu_ag/article/details/51741679这篇文章,里面提供了根据提交记录查找编译时的版本号的方法。使用和sdk编译时使用的版本,应该不会有问题吧!

现在的模拟器使用的都是ranchu内核,android源码是在7.0的时候才开始提供的,路径是:prebuilts/qemu-kernel/x86/ranchu/kernel-qemu

提交记录中也没有看到有用的信息。

后来把 https://android.googlesource.com/platform/prebuilts/qemu-kernel 项目拉下来,在git log中找到了如下信息:

commit e6e975ca2f2e57dced55759ab40b2851c144a67d
Author: Jin Qian <jinqian@google.com>
Date:   Mon Oct 31 14:28:45 2016 -0700

    Upgrade emulator kernels

    kernel-n-dev-android-goldfish-3.18-arm - build: 3421005
    kernel-n-dev-android-goldfish-3.10-mips64 - build: 3420631
    kernel-n-dev-android-goldfish-3.10-arm - build: 3420641
    kernel-n-dev-android-goldfish-3.18-arm64 - build: 3421004
    kernel-n-dev-android-goldfish-3.10-mips - build: 3420642
    kernel-n-dev-android-goldfish-3.10-arm64 - build: 3420640
    kernel-n-dev-android-goldfish-3.10-x86_64-qemu1 - build: 3420638
    kernel-n-dev-android-goldfish-3.4-x86 - build: 3407988
    kernel-n-dev-android-goldfish-3.10-x86_64 - build: 3420647
    kernel-n-dev-android-goldfish-3.18-mips - build: 3421003
    kernel-n-dev-android-goldfish-3.18-x86_64 - build: 3421007
    kernel-n-dev-android-goldfish-3.4-mips - build: 3407999
    kernel-n-dev-android-goldfish-3.4-arm - build: 3408013
    kernel-n-dev-android-goldfish-3.10-x86 - build: 3420649
    kernel-n-dev-android-goldfish-3.18-x86 - build: 3421006
    kernel-n-dev-android-goldfish-3.18-mips64 - build: 3421008

    Upgrade 3.4 kernel images to 1cc37bf
    1cc37bf MIPS: Attach goldfish machine reset and power-off routines

    Upgrade 3.10 kernel images to fd6659e
    fd6659e Merge branch 'android-3.10' into android-goldfish-3.10
    13f4ded BACKPORT: LogiHID: hid-lg4ff: tech G29 wheel patches
    288f516 android: binder: use copy_from_user_preempt_disabled
    ...
    0e7ad12 BACKPORT: ALSA: usb-audio: Fix double-free in error paths after snd_usb_add_audio_stream() call
    5982d2e BACKPORT: ALSA: usb-audio: Minor code cleanup in create_fixed_stream_quirk()
    b581331 pstore: drop pmsg bounce buffer

    Upgrade 3.18 kernel images to 82a0dee
    82a0dee Merge branch 'android-3.18' into android-goldfish-3.18
    74b2c7a android: binder: support for file-descriptor arrays.
    53b1701 android: binder: support for scatter-gather.
    ...
    cd6ae1f Revert "UPSTREAM: ARM: 8494/1: mm: Enable PXN when running non-LPAE kernel on LPAE processor The VMSA field of MMFR0 (bottom 4 bits) is incremented for each added feature.  PXN is supported if the value is >= 4 and LPAE is supported if it is >= 5."
    7447458 UPSTREAM: perf: Fix race in swevent hash
    5753ff2 ANDROID: dm: Fix symbol exports for dm target callbacks

    Change-Id: I5a635595233199939145487df580c6f519c24335

里面的“fd6659e”就是我们需要的版本号

git checkout fd6659e

再执行编译,就可以正常运行模拟器了。

0x03 如何编译内核模块

在正常编译出内核前,我还尝试了编译ko模块来绕过编译完整内核的问题。这里一并记录一下。

build-kernel.sh脚本事实上会先根据arch/x86/configs/i386_ranchu_defconfig文件生成.config文件。

要编译出ko,需要在i386_ranchu_defconfig中加入以下行:

CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_MODULES_USE_ELF_REL=y
CONFIG_IP6_NF_IPTABLES=y
CONFIG_IPV6=y

然后将需要编译成模块的配置改为m,如:

CONFIG_PPP=m
CONFIG_PPP_BSDCOMP=m
CONFIG_PPP_DEFLATE=m
CONFIG_PPP_MPPE=m
CONFIG_PPPOLAC=m
CONFIG_PPPOPNS=m

执行命令:

make i386_ranchu_defconfig
export ARCH=x86
make -j16

编译出来的内核文件路径是:arch/x86/boot/bzImage

而且,期望的ko文件也编译出来了。

drivers/net/slip/slhc.ko
drivers/net/ppp/ppp_mppe.ko
drivers/net/ppp/bsd_comp.ko
drivers/net/ppp/ppp_generic.ko
drivers/net/ppp/pppolac.ko
drivers/net/ppp/pppox.ko
drivers/net/ppp/pppopns.ko
drivers/net/ppp/ppp_deflate.ko

但是在测试的时候,发现ko中依赖到了net/core/net_namespace.c中的一个函数,而这个函数是通过CONFIG_NET_NS开关控制的。sdk提供的kernel里应该关闭了这个开关,因此,这些ko事实上是不能加载进去的。

而且,从6.0开始,sdk提供的kernel已经不再支持动态加载ko,估计是担心有安全风险。

0x04 如何支持PPTP

使用默认参数编译出来的内核,已经是支持ppp的了。但是测试发现,依然不能连上VPN服务器。原因是PPTP中使用GRE协议作为数据通道协议,该协议与TCP、UDP是同一层的,而模拟器实现的NAT只支持TCP和UDP协议,想要支持的话,必须要修改模拟器源码。

由于模拟器不像普通NAT网络里会有多台机器,而且一般来说,同时只会有一个进程在发起VPN连接,所以,可以不进行数据包的转换,直接透传。

分享