开篇废话
很久不研究cocos2d-x了,也不知道如今发展如何了。先前写游戏时会分几块。
- 主要功能代码都是用C++编写,编辑器用vs
- android平台相关功能使用 Java 编写,编辑器用 Eclipse
- ios平台相关功能使用 Object-C 编写,编辑器用 Xcode
基本流程就是先在 VS上开发主要功能,等测试通过了,然后再针对不同平台做接入。所以调试方面
- win32工程,可以在VS中调试
- Xcode工程,可以在Xcode中调试
- Android工程,无法调试
集成so包的Android工程是无法在eclipse中调试的,这里的调试主要是指在c/c++端打断点调试。只能通过logcat看输出。比较难搞的bug是,win32工程运行正常,但是打包装到android手机上运行时就崩溃。基本崩溃输出如下:一堆的堆栈信息。
I/DEBUG ( 31): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG ( 31): Build fingerprint: 'generic/google_sdk/generic/:2.2/FRF91/43546:eng/test-keys'
I/DEBUG ( 31): pid: 351, tid: 351 %gt;%gt;%gt; /data/local/ndk-tests/crasher <<<
I/DEBUG ( 31): signal 11 (SIGSEGV), fault addr 0d9f00d8
I/DEBUG ( 31): r0 0000af88 r1 0000a008 r2 baadf00d r3 0d9f00d8
I/DEBUG ( 31): r4 00000004 r5 0000a008 r6 0000af88 r7 00013c44
I/DEBUG ( 31): r8 00000000 r9 00000000 10 00000000 fp 00000000
I/DEBUG ( 31): ip 0000959c sp be956cc8 lr 00008403 pc 0000841e cpsr 60000030
I/DEBUG ( 31): #00 pc 0000841e /data/local/ndk-tests/crasher
I/DEBUG ( 31): #01 pc 000083fe /data/local/ndk-tests/crasher
I/DEBUG ( 31): #02 pc 000083f6 /data/local/ndk-tests/crasher
I/DEBUG ( 31): #03 pc 000191ac /system/lib/libc.so
I/DEBUG ( 31): #04 pc 000083ea /data/local/ndk-tests/crasher
I/DEBUG ( 31): #05 pc 00008458 /data/local/ndk-tests/crasher
I/DEBUG ( 31): #06 pc 0000d362 /system/lib/libc.so
I/DEBUG ( 31):
看到这一堆输出,基本很无头绪。这时就要用到 ndk 中的 ndk-stack 了。
正文
想要搞懂本篇内容,需要了解或者熟悉的知识。
- 熟悉ndk
- 了解cocos2d-x使用ndk 生成so包的过程
- 熟悉android开发中的logcat
- 了解android系统
过程
简单说明和解释
在生成apk之前,会通过ndk的 ndk-build 命令将c/c++源码编译成一个libxxx.so命名的共享库,共享库是在 Linux系统中使用的。
工程中有一个jni的文件夹,这个是每个使用ndk编译都要有的目录,里面主要有两个文件 Android.mk Application.mk 和你的c++源码。具体要编译那个c++源码是在Android.mk配置的,编译的cpu类型,设置相关是在Application.mk中设置的。
当编译成功后,工程中会生成两个目录,一个是libs,一个是obj。
libs中就是最终生成的so文件,不同平台的会放到不同文件夹里,常见的如 armeabi-v7a,x86。
obj中是一堆.a库文件。我们需要的就是obj目录下的.a库文件。
操作过程
为了方便操作,可以把obj拷贝到一个独立目录里,比如在工程根目录新建一个debug目录,然后将obj文件夹拷贝进去。
接下来让程序崩溃一次,获取崩溃日志。这里拿cocos2d-x的Hello World工程做实验。最简便的方式是将HelloWorldScene.cpp 中读取的 HelloWorld图片名字改一下,比如将 HelloWorld.png 首字母小写改成 helloWorld.png。这个图位于工程根目录下的Resources中。如果你运行win32 的话,程序还是会正常运行,重新打包安装到真机上运行就会崩溃。这是因为Android中文件命名是区分大小写的。最终崩溃log如下。
12-02 06:08:08.638 1193-1193/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
12-02 06:08:08.643 1193-1193/? A/DEBUG: Build fingerprint: 'Android/sdk_google_phone_x86_64/generic_x86_64:6.0/MASTER/2872745:userdebug/test-keys'
12-02 06:08:08.643 1193-1193/? A/DEBUG: Revision: '0'
12-02 06:08:08.644 1193-1193/? A/DEBUG: ABI: 'x86'
12-02 06:08:08.644 1193-1193/? A/DEBUG: pid: 2886, tid: 2906, name: GLThread 37 >>> com.good.xb <<<
12-02 06:08:08.646 1193-1193/? A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
12-02 06:08:08.665 1193-1193/? A/DEBUG: eax 00000000 ebx e3c92524 ecx 00000000 edx 00000001
12-02 06:08:08.666 1193-1193/? A/DEBUG: esi e3c92524 edi e3426f60
12-02 06:08:08.666 1193-1193/? A/DEBUG: xcs 00000023 xds 0000002b xes 0000002b xfs 0000005f xss 0000002b
12-02 06:08:08.666 1193-1193/? A/DEBUG: eip e33f7c35 ebp e2eb2378 esp e2eb2210 flags 00210286
12-02 06:08:08.706 1193-1193/? A/DEBUG: #00 pc 00346c35 /data/app/com.good.xb-2/lib/x86/libMyGame.so (HelloWorld::init()+1861)
12-02 06:08:08.706 1193-1193/? A/DEBUG: #01 pc 00346432 /data/app/com.good.xb-2/lib/x86/libMyGame.so (HelloWorld::create()+210)
12-02 06:08:08.706 1193-1193/? A/DEBUG: #02 pc 00346330 /data/app/com.good.xb-2/lib/x86/libMyGame.so (HelloWorld::createScene()+48)
12-02 06:08:08.706 1193-1193/? A/DEBUG: #03 pc 003461c1 /data/app/com.good.xb-2/lib/x86/libMyGame.so (AppDelegate::applicationDidFinishLaunching()+977)
12-02 06:08:08.706 1193-1193/? A/DEBUG: #04 pc 003494cf /data/app/com.good.xb-2/lib/x86/libMyGame.so (cocos2d::Application::run()+47)
12-02 06:08:08.706 1193-1193/? A/DEBUG: #05 pc 003505f3 /data/app/com.good.xb-2/lib/x86/libMyGame.so (Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit+307)
12-02 06:08:08.706 1193-1193/? A/DEBUG: #06 pc 000b1e76 /data/app/com.good.xb-2/oat/x86/base.odex (offset 0x67000) (void org.cocos2dx.lib.Cocos2dxRenderer.nativeInit(int, int)+122)
12-02 06:08:08.706 1193-1193/? A/DEBUG: #07 pc 000b3244 /data/app/com.good.xb-2/oat/x86/base.odex (offset 0x67000) (void org.cocos2dx.lib.Cocos2dxRenderer.onSurfaceCreated(javax.microedition.khronos.opengles.GL10, javax.microedition.khronos.egl.EGLConfig)+88)
12-02 06:08:08.706 1193-1193/? A/DEBUG: #08 pc 72989440 /data/dalvik-cache/x86/system@framework@boot.oat (offset 0x1eb2000)
12-02 06:08:08.706 1193-1193/? A/DEBUG: #09 pc 000f6cb7 <unknown>
截图如下
我是用AndroidStudio中模拟器跑的程序。如果使用Eclipse的话,这段log应该是绿色的。仔细看这段log,会发现有两个特征
- Tag 名字是 DEBUG
- 以* * * * * * 开头
为了方便操作,将这段log复制到一个txt文本中。回到我们先前的debug目录,新建一个log.txt文本,将崩溃log复制进来,保存。
此时我们就有了查找bug的两个必备材料了,接下来就可以使用ndk-stack命令了。
ndk-stack 命令使用格式
$NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi
$NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi -dump foo.txt
解释一下:
$NDK代表ndk所在的路径。
$PROJECT_PATH 代表你工程路径
obj/local/armeabi 代表你编译的cpu类型,针对不同的cpu可以换成armeabi-v7a,x86等等。这些名字都可以在obj/local/下看到。
可以在命令行模式下执行ndk-stack命令。第一个命令是用于log在cmd面板的情况。即使用
adb logcat 命令启动命令行打印log的情况。这时使用ndk-stack 命令,可以将获取的结果显示到cmd面板。这个操作意义不大,忽略。
第二个命令是读取存有log的文本文件,这个是我们要使用的。
为了方便操作,可以将ndk的目录配置到path环境变量中,这样就可以在任意目录使用ndk-stack命令了。
P.S.在Path中配置ndk路径时,如果失败,可以在最后加一个斜线 \,比如我的 E:\android-ndk-r10c\
执行命令
因为我们会在obj所在的根目录执行 ndk-stack 命令,所以最后的命令要简单一些。
ndk-stack -sym obj/local/x86 -dump log.txt
因为我的模拟器是x86的,所以我local下选的是x86 。
执行成功后就会看到一大串输出。这个看起来比较费劲,我们还是让他输出到一个文件吧。稍微修改一下。
ndk-stack -sym obj/local/x86 -dump log.txt >trace.txt
这样执行成功后,就可以在当前目录下看到一个trace.txt,里面就是我们想要的内容。
从上往下看,注意出现的代码行数,像第一个 HelloWorldScene.cpp:71,就表示这个类的71行。查看源码,发现71行代码是
// position the sprite on the center of the screen
sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
在这里崩溃了,嗯,这个sprite可能是空指针,那为啥是空指针呢,巴拉巴拉,就是查找原因的节奏了,最后发现,居然是图片命名大小写的问题,扑街,谁命名的,拉出来,打一顿。。。。。。
至此基本可以定位程序哪里出错了。
最后懒人大法。每次写这命令多麻烦呀,还是写个批处理好了,把上面的命令写到一个批处理文件里,每次执行一下就ok。
注意点
每个版本程序输出的崩溃log要和对应版本的obj一起使用,你不能拿其他版本的obj来解析崩溃log,否则会解析失败。也就是 obj -> so -> log,等同是一个版本的。
如果嫌麻烦,也可以仔细观察那段崩溃信息。里面显示的内容和调用顺序是相反的。仔细看的话也大概可以确定那个方法出错了,只是不能确定是哪行代码。
其实每次android程序崩溃,系统都会将崩溃信息写入到一个文件。
09.387 1532-1549/system_process I/BootReceiver: Copying /data/tombstones/tombstone_00 to DropBox (SYSTEM_TOMBSTONE)
看 log 输出,这有一个tombstones,翻译成中文就是墓碑,这下就懂了吧。
网上有研究tombstones的,请自行前去围观。