Debugging OpenGL ES on unrooted Android device?


#1

I was given some code, which I tried to port as an OF addon. This code works well with a desktop OF build; however, when I try to use it in an Android build, the rendering is not as expected. While I do not at present understand fully this code (as I am not an OpenGL expert), I suspect it has something to do with textures and transparencies, and/or differences between standard OpenGL and GLES. So I tried to do some OpenGL (ES) debugging on Android, but I cannot get it to work. The Android device that I work with is unrooted, which is likely the reason for my failure - unfortunately, it is borrowed, and I would rather not root it. So my question, in general, is: can I get OpenGL debugging to work with OF on an unrooted Android device, and if so - how?

This is what I’ve tried so far:

1. gltracer

I’ve tried using gltracer, Android’s own Tracer for OpenGL ES. In my case, it can be accessed via [Android SDK dir]/sdk/tools/monitor, then choosing “Window > Open Perspective… > Tracer for OpenGL ES”; then click the icon for “Connects to the device and collects OpenGL trace information”, and in the window enter this:

My app has debuggable: true set, and when I click on Trace, the trace does get collected in the destination file (on the PC), however the app runs extremely slow (for about 1-2 mins of runtime, almost 500 MB are collected, but apparently only for some 12 frames). The strange thing is that when I open the saved trace in the tracer, if I select glDrawElements commands, in Details window I do get a rendering as on the device:

… however, if I select any of the textures, I always get a blank/empty texture (or, rarely, some random pixels):

So, my subquestion here would be: why cannot I get the textures properly; could that be related to the device not being rooted? How could I otherwise get the correct textures with this approach?

Actually, it turns out these textures - or at least the “Texture Mipmap State” - are unpacked as images in /tmp when the tracer reads the trace file; there are some .dat files which I don’t know how to open, but also some .png files, which I can see are “all zeroes” - for the ones I see as blank:

$ hexdump -C /tmp/1452590078932-0/tex8208098562390494194.png 
00000000  89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|
00000010  00 00 02 00 00 00 02 00  08 06 00 00 00 f4 78 d4  |..............x.|
00000020  fa 00 00 04 0f 49 44 41  54 78 da ed c1 31 01 00  |.....IDATx...1..|
00000030  00 00 c2 a0 f5 4f 6d 07  6f a0 00 00 00 00 00 00  |.....Om.o.......|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000430  00 00 e0 37 02 f0 00 01  2f da c0 4b 00 00 00 00  |...7..../..K....|
00000440  49 45 4e 44 ae 42 60 82                           |IEND.B`.|

EDIT: Actually, gltracer did reveal something; namely, when the application comes to the problematic rendering, I get this in adb logcat:

...
W/Adreno-ES20(16293): <core_glTexSubImage2D:562>: GL_INVALID_OPERATION
...

… and, when I step through the gl calls in the list, I get the message:

Error applying transformations for glTexSubImage2D(target = GL_TEXTURE_2D, level = 0, xoffset = 0, yoffset = 0, width = 256, height = 256, format = GL_BGRA_EXT, type = GL_UNSIGNED_BYTE, pixels = 0x??)
java.lang.RuntimeException

… but this error is not reported when I step through, say,

glTexSubImage2D(target = GL_TEXTURE_2D, level = 0, xoffset = 0, yoffset = 0, width = 256, height = 256, format = GL_RGBA, type = GL_UNSIGNED_BYTE, pixels = 0x??)

… which probably points to a problem in using format = GL_BGRA_EXT with glTexSubImage2D? But it’s difficult to confirm this otherwise…


2. apitrace

Because of the above problems (and others, such as not being able to copy/paste some of the data shown in the Android gltracer window; being unable to obtain C++ code backtrace for a particular OpenGL call in Android gltracer), I also tried apitrace. I have tested it with the desktop version of my app, and apitrace there works great: I get OpenGL calls, as well as all textures; however, I don’t have a problem with the desktop version.

So, I followed apitrace/INSTALL.markdown, and managed to build an Android version of apitrace. Here, I get an Android .apk built, however, it packs only these files:

$ unzip -l /path/to/apitrace_git/build/android-build/build/outputs/apk/android-build-debug.apk
Archive:  /path/to/apitrace_git/build/android-build/build/outputs/apk/android-build-debug.apk
  Length      Date    Time    Name
---------  ---------- -----   ----
     2308  2015-12-15 01:23   AndroidManifest.xml
     9193  2015-12-15 01:23   res/drawable-hdpi-v4/ic_launcher.png
     5057  2015-12-15 01:23   res/drawable-mdpi-v4/ic_launcher.png
    14068  2015-12-15 01:23   res/drawable-xhdpi-v4/ic_launcher.png
    18365  2015-12-15 01:23   res/drawable-xxhdpi-v4/ic_launcher.png
     1148  2015-12-15 01:23   resources.arsc
    15928  2015-12-15 01:23   classes.dex
  7294732  2015-12-15 01:23   lib/armeabi-v7a/libretrace.so
  5630724  2015-12-15 01:23   lib/armeabi-v7a/libgnustl_shared.so
      808  2015-12-15 01:23   META-INF/MANIFEST.MF
      837  2015-12-15 01:23   META-INF/CERT.SF
      776  2015-12-15 01:23   META-INF/CERT.RSA
---------                     -------
 12993944                     12 files

… and I really cannot tell what is supposed to run here; I can install the .apk on the Android device, I can also open it, but it seems to be doing nothing?! I really cannot tell what this .apk is supposed to be used for…

Anyways, if one consults apitrace/Android.markdown (used to be called Dalvik.markdown), one can see this android-build-debug.apk is not even mentioned - what one is supposed to use, is /path/to/apitrace_git/build/wrappers/egltrace.so. However, there is a problem because I have an unrooted device:

  • Since the device is unrooted, I cannot run adb root
  • Likewise, I cannot copy anything to the /data folder on the Android device
  • Finally, I cannot run adb shell setprop to set arbitrary properties

As a check for setprop, I ran this in adb shell:

shell@flo:/ $ setprop persist.this.is.my.property testing
shell@flo:/ $ getprop persist.this.is.my.property
shell@flo:/ $
shell@flo:/ $ getprop camera.disable_zsl_mode
1

… and while I can read some properties, I definitely cannot set my arbitrary ones (even if there is no error raised during setprop; so it is the following getprop that confirms that the property hasn’t been set).

In terms of apitrace/Android.markdown, that means I cannot set the wrap.$PROCNAME property, which would load egltrace.so via LD_PRELOAD environment variable. (Note also the 31 character property limitation, which otherwise leads to the error “could not set property”, see http://stackoverflow.com/questions/19011887/how-do-i-run-valgrind-with-an-android-app)

So, first, I copied libgnustl_shared.so and egltrace.so (renamed to libegltrace.so) to a new folder, libs/armeabi-v7a/ in my OF project, which packs these shared object files in the .apk of my OF Android app, thereby making them present on the device (see ofxQCAR-Android : OF overwrites custom Android.mk when trying to add external library). Then I tried to manually “force” LD_PRELOAD in adb shell:

shell@flo:/ LD_PRELOAD=/data/app/cc.openframeworks.MY_APP-2/lib/arm/libegltrace.so am start -S cc.openframeworks.MY_APP/cc.openframeworks.MY_APP.OFActivity
CANNOT LINK EXECUTABLE DEPENDENCIES: library "libgnustl_shared.so" not found

shell@flo:/ LD_PRELOAD=/data/app/cc.openframeworks.MY_APP-2/lib/arm/libgnustl_shared.so:/data/app/cc.openframeworks.MY_APP-2/lib/arm/libegltrace.so \
TRACE_FILE=MY_APP.trace \ 
am start -S \
cc.openframeworks.MY_APP/cc.openframeworks.MY_APP.OFActivity
Segmentation fault

Well, clearly that didn’t work either - and the segfault produces this log in adb logcat:

D/apitrace(25758): apitrace: loaded into /system/bin/sh
D/apitrace(25758): apitrace: loaded into app_process
D/AndroidRuntime(25758): >>>>>> START com.android.internal.os.RuntimeInit uid 2000 <<<<<<
D/AndroidRuntime(25758): CheckJNI is OFF
D/apitrace(25758): apitrace: warning: caught signal 11
--------- beginning of crash
F/libc    (25758): Fatal signal 11 (SIGSEGV), code 1, fault addr 0x2b in tid 25758 (main)
I/DEBUG   (  184): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG   (  184): Build fingerprint: 'google/razor/flo:5.1.1/LMY48M/2167285:user/release-keys'
I/DEBUG   (  184): Revision: '0'
I/DEBUG   (  184): ABI: 'arm'
I/DEBUG   (  184): pid: 25758, tid: 25758, name: main  >>> app_process <<<
I/DEBUG   (  184): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x2b
W/NativeCrashListener(  544): Couldn't find ProcessRecord for pid 25758
I/DEBUG   (  184):     r0 b4427c00  r1 beb5552c  r2 00000000  r3 0000002f
E/DEBUG   (  184): AM write failure (32 / Broken pipe)
I/DEBUG   (  184):     r4 b4427c00  r5 00000001  r6 b4427c00  r7 b4427c18
I/DEBUG   (  184):     r8 b4427c00  r9 00000000  sl beb5552c  fp 0aaaaaaa
I/DEBUG   (  184):     ip b00f97a4  sp beb55488  lr b00d76f3  pc b6f52848  cpsr 60070010
I/DEBUG   (  184):
I/DEBUG   (  184): backtrace:
I/DEBUG   (  184):     #00 pc 000bc848  /data/app/cc.openframeworks.MY_APP-2/lib/arm/libgnustl_shared.so (std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::string const&)+12)
I/DEBUG   (  184):     #01 pc 00000014  <unknown>
I/DEBUG   (  184):

Finally, I found http://www.hackermusings.com/2012/03/debugging-opengl-on-android-without-losing-your-sanity/, which instead of using this approach, uses dlopen; so I do something like this in my OF main.cpp:

string tracepath = ofToDataPath("MY_APP.trace", true); 
setenv("TRACE_FILE", tracepath.c_str(), true); 
void* handle = dlopen("libegltrace.so", RTLD_NOW); // RTLD_LAZY
if (!handle) {
    ofLogNotice() << "Cannot open library: " << dlerror() << '\n';
}

… and this, to an extent, works - in the sense, that when I run the app, I get these entries in adb logcat upon starting and exiting the application:

D/apitrace(16995): apitrace: loaded into cc.openframeworks.MY_APP
....
D/apitrace(16995): apitrace: unloaded from cc.openframeworks.MY_APP

… however, there is no other mention of apitrace in the log, and there is no log file created anywhere on the Android device. Apparently, after the dlopen, I should also somehow “overload” symbols, but that seems to be not trivial for C++ (see:

); and besides, the “loaded into…” message is produced by LocalWriter::LocalWriter() constructor method in /path/to/apitrace_git/common/trace_writer_local.cpp; but the opening of the trace file is in void LocalWriter::open(void) - and I’m really not clear on how to trigger this function from my OF application (apparently, some sort of extern "C" would be required, but where, and for which functions/symbols?)

So my question here is: given that the LD_PRELOAD approach seemingly doesn’t work for me (due to not being root) - is there anything else I could do, to use apitrace's egltrace.so on Android? The dlopen approach seems promising - but what else should I do, in order to trigger actual tracing into a trace file on Android, from my OF application?


#2

Hi Thanks for your guiding.

I use the latest Android-NDK to build apitrace, but suffered error. Would you mind to give some advice on it? Thanks!

/home/garywang/3t_work/apitrace/thirdparty/khronos/EGL/egl.h:288:1: note: in expansion of macro 'EGLAPI'
EGLAPI EGLBoolean EGLAPIENTRY eglDestroySync (EGLDisplay dpy, EGLSync sync);
^
/home/garywang/3t_work/apitrace/thirdparty/khronos/KHR/khrplatform.h:106:67: error: '__NDK_FPABI__' does not name a type
#   define KHRONOS_APICALL __attribute__((visibility("default"))) __NDK_FPABI__
                                                                   ^
/home/garywang/3t_work/apitrace/thirdparty/khronos/EGL/eglplatform.h:50:16: note: in expansion of macro 'KHRONOS_APICALL'
#define EGLAPI KHRONOS_APICALL
                ^
/home/garywang/3t_work/apitrace/thirdparty/khronos/EGL/egl.h:289:1: note: in expansion of macro 'EGLAPI'
EGLAPI EGLint EGLAPIENTRY eglClientWaitSync (EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout);
^
/home/garywang/3t_work/apitrace/thirdparty/khronos/KHR/khrplatform.h:106:67: error: '__NDK_FPABI__' does not name a type
#   define KHRONOS_APICALL __attribute__((visibility("default"))) __NDK_FPABI__
                                                                   ^
/home/garywang/3t_work/apitrace/thirdparty/khronos/EGL/eglplatform.h:50:16: note: in expansion of macro 'KHRONOS_APICALL'
#define EGLAPI KHRONOS_APICALL
                ^

Do you have any idea on it?

thanks!