澳门新葡萄京娱乐场使用 Swift 语言编写 Android 应用入门

Swift标准库可以编译安卓armv7的内核,这使得可以在安卓移动设备上执行Swift语句代码。本文解释了如何在你的安卓手机上运行一个简单的“hello,world”程序。

一、native crash捕获原理

native crash捕获的原理摘选完善自:Android 开发中常见 Crash
的情况。native
crash捕获主要利用了Linux的信号机制(进程间通信方式的一种)。当应用程序异常,Linux内核将产生的错误信息通知当前进程。当前进程在接收到该错误信号后,可以有三种不同的处理方式。
(1)忽略该信号。
(2)捕捉该信号并执行对应的信号处理函数(signal handler)。
(3)执行该信号的缺省操作(如 SIGSEGV, 其缺省操作是终止进程)。

当 Linux 应用程序在执行时发生严重错误,一般会导致程序 crash。其中,Linux
专门提供了一类 crash 信号,在程序接收到此类信号时,缺省操作是将 crash
的现场信息记录到 core 文件,然后终止进程。

Android
Native程序本质上就是一个Linux程序,在执行时发生错误程序crash之后,也会产生一个记录crash现场信息的文件,在Android系统中就是tombstone文件,这个文件保存在路径/data/tombstones/目录下,以tombstone_数字编号命名。

参见下面这个tombstone文件,是我从data/tombstones/目录下拷贝出来的tombstone_00文件,可以看到此文件记录了死亡进程的基本信息(比如进程的进程号(pid)、线程号(tid)),死亡的地址,死亡现场的堆栈调用信息等:

Build fingerprint: 'Android/rk3288/rk3288:5.1.1/LMY49F/elc-liubei06091434:userdebug/test-keys'
Revision: '0'
ABI: 'arm'
pid: 25243, tid: 25260, name: Binder_2  >>> com.tencent.daemon <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x14
    r0 00000000  r1 a1c47c02  r2 a1c47c08  r3 a2180820
    r4 00000000  r5 72da7878  r6 00000006  r7 a1c47c08
    r8 12e01660  r9 b72e3cd0  sl 12e09aa0  fp 12e09b20
    ip b520d450  sp a21807b0  lr a1c0e1db  pc a1b85be0  cpsr 200d0030
    d0  65742e6d6f632330  d1  616d2e746e656330
    d2  3036314070706130  d3  3030313031303031
    d4  7674710e0cf77572  d5  75f677890bf50ff6
    d6  52b34d2a4aaa02e1  d7  71b31cc653c98a50
    d8  0000000000000000  d9  0000000000000000
    d10 0000000000000000  d11 0000000000000000
    d12 0000000000000000  d13 0000000000000000
    d14 0000000000000000  d15 0000000000000000
    d16 0000000000000000  d17 4020000000000000
    d18 4024000000000000  d19 72dd989072dd9858
    d20 72dd974072dd9708  d21 4020000000000000
    d22 6f6181706f618170  d23 72dd96d072dd9698
    d24 6f6181706f618170  d25 6f6181706f618170
    d26 6f6181706f618170  d27 6f6181706f618170
    d28 6f6181706f618170  d29 6f6181706f618170
    d30 6f6181706f618170  d31 4000000000000000
    scr 80000011

backtrace:
    #00 pc 0007ebe0  /data/app/com.tencent.daemon-1/lib/arm/libhwnetcore.so (std::string::_M_assign(char const*, char const*)+7)
    #01 pc 001071d7  /data/app/com.tencent.daemon-1/lib/arm/libhwnetcore.so (Java_com_tencent_wechat_HWNetcore_addCommonRequest+30)
    #02 pc 001e0e79  /data/dalvik-cache/arm/data@app@com.tencent.daemon-1@base.apk@classes.dex  

stack:
         a2180770  b520c9b8  /system/lib/libart.so
         a2180774  00000030  
         a2180778  b72e3cd0  [heap]
         a218077c  b45fbe0e  
         a2180780  b72f0628  [heap]
         a2180784  b510e895  /system/lib/libart.so (art::JNI::ReleaseByteArrayElements(_JNIEnv*, _jbyteArray*, signed char*, int))
         a2180788  b72e4310  [heap]
         a218078c  a21807cc  [stack:25260]
         a2180790  a2180820  [stack:25260]
         a2180794  12e01660  /dev/ashmem/dalvik-main space (deleted)       

如果你遇到了任何问题,请参考下面的说明,上传BUG到 .

tombstone文件主要的组成部分:

(1) Build fingerprint

(2) ABI(Application binary
interface):应用程序二进制接口,定义了一套规则,允许编译好的二进制目标代码在所有兼容该ABI的操作系统和硬件平台中无需改动就能运行。而具体的实现是由编译器、CPU和操作系统共同完成的。不同的CPU芯片(如ARM、Intel
x86等)支持不同的ABI架构,常见的ABI类型包括:armeabi,armeabi-v7a,x86,x64,mips。从tombstone文件可看出ABI为ARM类型

(3)
pid(进程号)和tid(线程号),如果pid和tid相同,则在主线程中crash,name里面则是进程名

pid: 25243, tid: 25260, name: Binder_2  >>> com.tencent.daemon <<<

(4) Terminated signal and fault address信息

可以看到是signal 11导致的crash,访问了非法的地址0x4

signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x14

Android中信号量如下所示,只有signal 9可以无条件终止进程:

adb shell kill -l
 1    HUP Hangup                        33     33 Signal 33               
 2    INT Interrupt                     34     34 Signal 34               
 3   QUIT Quit                          35     35 Signal 35               
 4    ILL Illegal instruction           36     36 Signal 36               
 5   TRAP Trap                          37     37 Signal 37               
 6   ABRT Aborted                       38     38 Signal 38               
 7    BUS Bus error                     39     39 Signal 39               
 8    FPE Floating point exception      40     40 Signal 40               
 9   KILL Killed                        41     41 Signal 41               
10   USR1 User signal 1                 42     42 Signal 42               
11   SEGV Segmentation fault            43     43 Signal 43               
12   USR2 User signal 2                 44     44 Signal 44               
13   PIPE Broken pipe                   45     45 Signal 45               
14   ALRM Alarm clock                   46     46 Signal 46               
15   TERM Terminated                    47     47 Signal 47               
16 STKFLT Stack fault                   48     48 Signal 48               
17   CHLD Child exited                  49     49 Signal 49               
18   CONT Continue                      50     50 Signal 50               
19   STOP Stopped (signal)              51     51 Signal 51               
20   TSTP Stopped                       52     52 Signal 52               
21   TTIN Stopped (tty input)           53     53 Signal 53               
22   TTOU Stopped (tty output)          54     54 Signal 54               
23    URG Urgent I/O condition          55     55 Signal 55               
24   XCPU CPU time limit exceeded       56     56 Signal 56               
25   XFSZ File size limit exceeded      57     57 Signal 57               
26 VTALRM Virtual timer expired         58     58 Signal 58               
27   PROF Profiling timer expired       59     59 Signal 59               
28  WINCH Window size changed           60     60 Signal 60               
29     IO I/O possible                  61     61 Signal 61               
30    PWR Power failure                 62     62 Signal 62               
31    SYS Bad system call               63     63 Signal 63               
32     32 Signal 32                     64     64 Signal 64  

下表列举了几个常见的信号量:

信号量 Value 描述
SIGABRT 6 通过C函数abort()发送;为assert()使用
SIGKILL 9 迅速完全终止进程;不能被捕获
SIGFPE 8 浮点数运算错误,如除0操作
SIGSEGV 11 段地址错误,例如空指针、野指针、数组越界等
SIGPIPE 13 管道错误,例如向没有reader的管道中写,linux中socket断掉后继续写
SIGILL 4 非法指令,例如损坏的可执行文件或代码区损坏
SIGBUS 7 不存在的物理地址,更多为硬件或系统引起

(5) Call Stack信息

调用栈信息记录了程序在Crash前的函数调用关系以及正在执行函数的信息。#澳门新葡萄京娱乐场,00,#01等为函数调用栈中栈帧的编号,编号越小的栈帧表示最近调用的函数信息,所以栈帧标号为#00
表示的是当前正在执行并导致程序crash的函数信息。pc后面的十六进制表示当前函数正在执行语句在共享链接库或可执行文件中的位置,/data/app/com.tencent.daemon-1/lib/arm/libhwnetcore.so表示执行指令在哪个文件中,括号里面注明了对应的函数。

backtrace:
    #00 pc 0007ebe0  /data/app/com.tencent.daemon-1/lib/arm/libhwnetcore.so (std::string::_M_assign(char const*, char const*)+7)
    #01 pc 001071d7  /data/app/com.tencent.daemon-1/lib/arm/libhwnetcore.so (Java_com_tencent_wechat_HWNetcore_addCommonRequest+30)
    #02 pc 001e0e79  /data/dalvik-cache/arm/data@app@com.tencent.daemon-1@base.apk@classes.dex

常见问题解答

二、解析native crash堆栈的三种常用方法

为了能正确解析出来crash的堆栈,我们需要保存好obj下面的so,libs目录下的so丢失了调试信息和符号表,不能正确地解析出来原代码。

让我们来回答如下经常被问及的问题吧:

1、使用ndk-stack

ndk-stack命令从r6版本开始提供,能自动分析tombstone文件,将崩溃时的调用内存地址和c++代码一一对应,位于ndk目录下。

(1)直接利用adb logcat作为input

adb logcat | $NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi

(2)解析文件

adb logcat > /tmp/foo.txt  (可选,已经有crash file,直接解析即可)

$NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi -dump foo.txt

这是否以为着我能够用Swift快速的开发安卓应用?

2、使用arm-linux-androideabi-addr2line

arm-linux-androideabi-addr2line和arm-linux-androideabi-objdump工具需要根据目标机器不同的CPU结构进行选择,位于ndk的交叉编译器工具链目录下。由于我们是Android平台,arm架构,选择arm-linux-androidabi-4.9下的工具即可,这两个工具均位于目录:

$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin

addr2line用于将地址转换为文件名称和行号,如果没有地址指定,将从stdin读取。通过addrline
-h获得各个参数的含义:

arm-linux-androideabi-addr2line -h  

The options are:
  @<file>                Read options from <file>
  -a --addresses         Show addresses
  -b --target=<bfdname>  Set the binary file format
  -e --exe=<executable>  Set the input file name (default is a.out)
  -i --inlines           Unwind inlined functions
  -j --section=<name>    Read section-relative offsets instead of addresses
  -p --pretty-print      Make the output easier to read for humans
  -s --basenames         Strip directory names
  -f --functions         Show function names
  -C --demangle[=style]  Demangle function names
  -h --help              Display this information
  -v --version           Display the program's version

一般用-C选项还原函数名称,-f选项展示函数名称,-e选项指定input
file,使用范例如下,0x2c015d为令程序崩溃的汇编指令地址:

./arm-linux-androideabi-addr2line -C -f -e  /Users/lily/prj/obj/local/armeabi/libhwnetcore.so 0x2c015d  

onStatisticsCallBack(StatisticsValue, std::string, std::string)
/Users/lily/git_project/prj/jni/Java2C.cpp:177 (discriminator 4)

如果不使用-C选项,得到的解析结果就是

_Z20onStatisticsCallBack15StatisticsValueSsSs
/Users/lily/prj/jni/Java2C.cpp:177 (discriminator 4)

做梦,虽然Swift编译器可以胜任在安卓设备上编译Swift代码并运行。这需要的不仅仅是用Swift标准库编写一个APP,更多的是你需要一些框架来搭建你的应用用户界面,以上这些Swift标准库不能提供。

3、使用arm-linux-androideabi-objdump

arm-linux-androideabi-objdump与arm-linux-androideabi-addr2line位于同一目录下,用来从二进制文件中展示信息。使用objdump能够定位到出错的函数信息。使用方式为:

./arm-linux-androideabi-objdump <option(s)> <file(s)>

可选参数为:

 At least one of the following switches must be given:
  -a, --archive-headers    Display archive header information
  -f, --file-headers       Display the contents of the overall file header
  -p, --private-headers    Display object format specific file header contents
  -P, --private=OPT,OPT... Display object format specific contents
  -h, --[section-]headers  Display the contents of the section headers
  -x, --all-headers        Display the contents of all headers
  -d, --disassemble        Display assembler contents of executable sections
  -D, --disassemble-all    Display assembler contents of all sections
  -S, --source             Intermix source code with disassembly
  -s, --full-contents      Display the full contents of all sections requested
  -g, --debugging          Display debug information in object file
  -e, --debugging-tags     Display debug information using ctags style
  -G, --stabs              Display (in raw form) any STABS info in the file
  -W[lLiaprmfFsoRt] or
  --dwarf[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
          =frames-interp,=str,=loc,=Ranges,=pubtypes,
          =gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
          =addr,=cu_index]
                           Display DWARF info in the file
  -t, --syms               Display the contents of the symbol table(s)
  -T, --dynamic-syms       Display the contents of the dynamic symbol table
  -r, --reloc              Display the relocation entries in the file
  -R, --dynamic-reloc      Display the dynamic relocation entries in the file
  @<file>                  Read options from <file>
  -v, --version            Display this program's version number
  -i, --info               List object formats and architectures supported
  -H, --help               Display this information

其中-d选项用于展示可执行部分的汇编程序内容,-D展示全部的汇编程序内容,-x选项用于显示所有header的内容,-S选项显示源代码。使用范例如下,使用的so为obj下面的so:

./arm-linux-androideabi-objdump -dx /Users/lily/prj/obj/local/armeabi/libhwnetcore.so > /Users/lily/prj/obj/local/armeabi/dxobjdump.txt 

再看一下
0x2c015d这个地址,搜索2c015d,可以看到对应的函数为onStatisticsCallBack

002bffe4 <_Z20onStatisticsCallBack15StatisticsValueSsSs>:
...
2c015c: f043 fa92   bl  303684 <__aeabi_llsl+0x2231c>
...

使用-S -D耗时会多一些:

./arm-linux-androideabi-objdump -S -D /Users/lily/prj/obj/local/armeabi/libhwnetcore.so > /Users/lily/prj/obj/local/armeabi/SDobjdump.txt

另一方面,一个理论上可以从Swift调用Java应用程序接口,但是不同于Objective-C,Swift编译器对Swift-to-Java桥接毫无作用。

附录

从ndk r11开始,Android NDK已经废弃了gcc,Android默认使用clang/llvm,
gcc只支持到4.9,但由于google的libc++库还不完善,所以gcc还会继续保留一段时间。

要想使用ndk-build命令或NDK,需要安装 GNU Make 3.81
或更新版本,通过下列命令可以看到GNU Make 3.81已安装。

gnumake --version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

This program built for i386-apple-darwin11.3.0

预备知识

为了能顺利使用这份向导,你需要:

1.
可以编译Swift源码的Linux环境。stdlib目前只能在Linux环境下编译成安卓可用版本。在尝试为安卓构建之前,确保你能够参考Swift项目的README为Linux做编译。

  1. 安卓NDK,高于或等于21版本,在以下链接提供下载:

.

3.
一台可以远程调试的安卓设备。我们需要通过远程调试来讲stdlib结果部署到安卓设备上。你可以按以下官方向导来远程调试: .

1、ndk-build 常用参数

ndk-build 文件是 Android NDK r4 中引入的一个 shell
脚本。其用途是调用正确的 NDK
构建脚本。可通过内部构建和从命令行调用两种方式使用ndk-build。

(1)内部构建
运行 ndk-build 脚本相当于运行以下命令:

$GNUMAKE -f <ndk>/build/core/build-local.mk
<parameters>

$GNUMAKE 指向 GNU Make 3.81 或更新版本,<ndk> 指向 NDK 安装目录。
可以使用此信息从其他 shell 脚本甚至自己的 Make 文件调用 ndk-build。

范例:
我的ndk目录位于/Users/lily/Library/Android/sdk/ndk-bundle/,进入到proj目录后,调用如下命令即可进行编译

gnumake -f /Users/lily/Library/Android/sdk/ndk-bundle/build/core/build-local.mk

(2)使用命令行

ndk-build 的所有参数将直接传递到运行 NDK 构建脚本的底层 GNU make。

ndk-build <option>  

clean 移除之前生成的任意二进制文件

V=1  启动构建,并显示构建命令  

-B 强制执行完全的重新构建  

NDK_DEBUG=1 强制执行可调试版构建(这个参数很有用,启用这个参数obj下可保留符号表)  

NDK_DEBUG=0 强制执行发布  

NDK_HOST_32BIT = 1 始终使用32位模式下的工具链(针对某些附带64和32位两个版本的工具链,64位的工具速度更快,能处理更大的程序,更好地利用主机资源)    

-C <project> 构建位于<project>的项目路径的原生代码(可不cd切换到项目路径) 

-jX X为线程数,ndk-build默认为单线程编译,一般为CPU核数-1

安卓上的”Hello, world”

2、指定编译的toolchain

在Application.mk中添加

NDK_TOOLCHAIN_VERSION = 4.9

未指定工具链之前,使用ndk默认的工具链,编译log如下所示

/Users/lily/Library/Android/sdk/ndk-bundle/ndk-build -j8 NDK_DEBUG=1
[armeabi] Compile++ thumb: hwnetcore <= Java2C.cpp
[armeabi] Compile++ thumb: hwnetcore <= CallStack.cpp
[armeabi] Compile++ thumb: hwnetcore <= Java2C_LogLogic.cpp
[armeabi] Compile++ thumb: hwnetcore <= Java2C_Xlog.cpp
[armeabi] Prebuilt       : libstlport_shared.so <= <NDK>/sources/cxx-stl/stlport/libs/armeabi/
[armeabi] Gdbserver      : [arm-linux-androideabi] libs/armeabi/gdbserver
[armeabi] Gdbsetup       : libs/armeabi/gdb.setup 

指定了4.9的工具链以后,编译log如下所示,可以清楚地看到使用的工具链

ndk-build -j4 NDK_DEBUG=1  

[armeabi] Compile++ thumb: hwnetcore <= Java2C.cpp
[armeabi] Compile++ thumb: hwnetcore <= CallStack.cpp
[armeabi] Compile++ thumb: hwnetcore <= Java2C_LogLogic.cpp
[armeabi] Compile++ thumb: hwnetcore <= Java2C_Xlog.cpp
[armeabi] Prebuilt       : libstlport_shared.so <= <NDK>/sources/cxx-stl/stlport/libs/armeabi/
[armeabi] Gdbserver      : [arm-linux-androideabi-4.9] libs/armeabi/gdbserver
[armeabi] Gdbsetup       : libs/armeabi/gdb.setup

1. 构建Swift Android stdlib 依赖

你可能注意到了,为了构建Linux下的Swift stdlib,你需要 apt-get install
libicu-dev icu-devtools。 简单来说,构建在安卓设备上使用的Swift
stdlib需要libiconv和libicu。然而,你需要这些库的安卓设备版本。

为安卓设备构建libiconv和libicu:

  1. 确定你安装了 curl, antoconf, antomake, libtook 和git。

2.
克隆 SwiftAndroid/libiconv-libicu-android 项目。通过命令行执行以下命令:git
clone git@github.com:SwiftAndroid/libiconv-libicu-android.git。

  1. 在命令行执行 which
    ndk-build。确定在你下载的安卓NDK里ndk-build能显示可执行路径。如果不能显示,你需要将安卓NDK的目录加到你的PATH里。

  2. 在命令行输入 libiconv-libicu-android 目录,然后执行 build.sh。

  3. 确定构建脚本在你的libiconv-libicu-android目录构建了
    armeabi-v7a/icu/source/i18n和armeabi-v7a/icu/source/common目录。

  4. 构建安卓使用的Switf stdlib


输入你的Swift目录,然后运行构建脚本,将路径传递给安卓NDK和libicu/libiconv目录:

$ utils/build-script /
    -R /                                           # Build in ReleaseAssert mode.
    --android /                                    # Build for Android.
    --android-ndk ~/android-ndk-r10e /             # Path to an Android NDK.
    --android-ndk-version 21 /                     # The NDK version to use. Must be 21 or greater.
    --android-icu-uc ~/libicu-android/armeabi-v7a/libicuuc.so /
    --android-icu-uc-include ~/libicu-android/armeabi-v7a/icu/source/common /
    --android-icu-i18n ~/libicu-android/armeabi-v7a/libicui18n.so /
    --android-icu-i18n-include ~/libicu-android/armeabi-v7a/icu/source/i18n/

3、与NDK_DEBUG = 1相同效果的debug编译选项

在Application.mk中进行配置

APP_OPTIM := debug

参考文献:
1、google官方ndk-build
2、google官方ndk-stack
3、Android 开发中常见 Crash
的情况
4、ABI百度百科
5、Android平台Native代码的崩溃捕获机制及实现

3. 编译hello.swift并在安卓设备上运行

创建一个简单的Swift文件,命名为 hello.swift:

print("Hello, Android")

使用步骤2中构建好的Swift编译器来编译Swift源码,目标设定为安卓:

$ build/Ninja/ReleaseAssert/swift-linux-x86_64/swiftc /                   # The Swift compiler built in the previous step.
    -target armv7-none-linux-androideabi /                                # Targeting android-armv7.
    -sdk ~/android-ndk-r10e/platforms/android-21/arch-arm /               # Use the same NDK path and version as you used to build the stdlib in the previous step.
    -L ~/android-ndk-r10e/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a /  # Link the Android NDK's libc++ and libgcc.
    -L ~/android-ndk-r10e/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64/lib/gcc/arm-linux-androideabi/4.8 /
    hello.swift

这样应该会在你执行命令的目录下生成一个hello可执行文件。如果你试图在你的Linux环境下执行这个可执行文件,你会看到如下错误:

cannot execute binary file: Exec format error

这正是我们想要的错误:因为这是为执行在安卓设备上构建的可执行文件–它不应该能在Linux上执行。下一步,让我们将它部署到安卓设备上来执行它。

4. 将构建好的产品部署到设备

你可以使用adb push
命令来将构建好的产品从Linux环境拷贝到安卓设备。当你执行adb
devices命令前确定你的设备连接好并且可以被列出,然后执行以下命令来拷贝Swift
Android stdlib:

$ adb push build/Ninja-ReleaseAssert/swift-linux-x86_64/lib/swift/android/libswiftCore.so /data/local/tmp
$ adb push build/Ninja-ReleaseAssert/swift-linux-x86_64/lib/swift/android/libswiftGlibc.so /data/local/tmp
$ adb push build/Ninja-ReleaseAssert/swift-linux-x86_64/lib/swift/android/libswiftSwiftOnoneSupport.so /data/local/tmp
$ adb push build/Ninja-ReleaseAssert/swift-linux-x86_64/lib/swift/android/libswiftRemoteMirror.so /data/local/tmp
$ adb push build/Ninja-ReleaseAssert/swift-linux-x86_64/lib/swift/android/libswiftSwiftExperimental.so /data/local/tmp

另外,你也需要拷贝安卓NDK的libc++:

$ adb push ~/android-ndk-r10e/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_shared.so /data/local/tmp

最后,你需要拷贝你前一步构建好的hello可执行文件:

$ adb push hello /data/local/tmp

5. 在安卓设备上执行“Hello, World”

你可以在安卓设备上使用 adb shell 命令来执行hello可执行文件:

$ adb shell LD_LIBRARY_PATH=/data/local/tmp hello

你可以看到以下输出:

Hello, Android

祝贺你!你刚刚在安卓上运行了你的第一个Swift程序。

发表评论

电子邮件地址不会被公开。 必填项已用*标注