2

我们遇到了一款名为 Motorla RAZR i 的奇怪设备,它具有 x86 CPU 和 Android 版本 4.1.2(大约 2013 年)。

此设备有时会在本机代码中的 GL 操作中崩溃。我们最终将这个错误减少到只有几行无辜的函数调用:

void stackCorrupt() {
  const EGLint configAttrs[] = {EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE};
  const EGLint contextAttrs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
  const EGLint const surfaceAttrs[] = {EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE};

  EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  eglInitialize(display, 0, 0);

  EGLint numConfigs;
  EGLConfig config;
  eglChooseConfig(display, configAttrs, &config, 1, &numConfigs);

  EGLContext context = eglCreateContext(display, config, NULL, contextAttrs);
  EGLSurface surface = eglCreatePbufferSurface(display, config, surfaceAttrs);

  // commenting out this call makes the stack clean again
  eglMakeCurrent(display, surface, surface, context);

  // only to know that the context is good
  __android_log_print(ANDROID_LOG_DEBUG, "native-lib", "GL_RENDERER %s", glGetString(GL_RENDERER));

  // Cleanup
  eglDestroySurface(display, surface);
  eglDestroyContext(display, context);
  eglTerminate(display);
}

编译时-fstack-protector-strong(如果我理解正确,这是默认值),在特定设备上调用此函数总是会导致

 F /system/bin/app_process: stack corruption detected: aborted

但是,如果我使用-fno-stack-protector,执行将继续,就好像没有发生任何不好的事情一样。这可能(如果我正确理解情况)成为任何进一步执行的陷阱。更危险(因为不明确),将-flto添加到 CFLAGS 会隐藏崩溃。

在上面的代码中,为了简洁起见,我删除了错误检查。实际上,所有调用都会成功。此外,清理部分不会更改eglMakeCurrent()的堆栈抖动行为。

我真的很高兴创建一些沙箱来安全地执行此类代码,而不会危及我的应用程序的其余部分。

附言

  1. 我在 Android 上遇到了另一个奇怪的堆栈损坏案例,使用 glGetProgramiv 时“检测到堆栈损坏”,但似乎没有提出解决方案。

  2. 在测试用例中,但不是在实际应用程序中,egl 上下文的释放会有所帮助:我在CleanupeglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)之前调用。

  3. 反汇编显示-flto关闭堆栈保护器。这不应该是一个很大的失望:如果我们的目标是优化调用,浪费时间检查是愚蠢的吗?

4

1 回答 1

3

TL;NR:这是一个误报。设备上的 EGL 库(自 2013 年以来从未更新)不遵守多年后引入的 TLS 约定,新的 NDK(我们使用稳定的 r13)错误地将我们无辜的函数归咎于堆栈粉碎。

我在2015 年 11 月的 gcc 邮件列表中发现了一条消息,指的是2013 年 4 月的修复:

x86 android:将 -fstack-protector 保护默认更改为 TLS

在任何情况下,为 4.2 之前的 Android 编译本机代码的人很可能会使用旧版本的 Android NDK。

我想建议删除 Bionic 异常,例如将代码更改为:

 /* Handle stack protector */
 if (!opts_set->x_ix86_stack_protector_guard)
   opts->x_ix86_stack_protector_guard = SSP_TLS;

假设是错误的,我们的代码在从 4.0 到 7.0 的所有平台上运行相同的 jar 等等,我们不想在某些目标设备上使用旧版本的 NDK。实际上,NDK r14 仍然提供对 android-9(又名2.3 GINGERBREAD)的支持。

不幸的是,这意味着至少在这个设备上,我们必须使用-fno-stack-protector运行我们的 C++ 代码的调试版本。幸运的是,发布版本 ( APP_OPTIM=release ) 使用 LTO,因此堆栈保护器被禁用。

于 2017-02-14T17:59:04.637 回答