لایه‌های GLES

در دستگاه‌هایی که از Android 10 (سطح میانای برنامه کاربردی ۲۹) و بالاتر استفاده می‌کنند، لایه‌بندی OpenGL ES (GLES) دردسترس است. برنامه قابل‌اشکال‌زدایی می‌تواند لایه‌های GLES را از APK، از دایرکتوری پایه، یا از APK لایه انتخاب‌شده بار کند.

استفاده از لایه GLES مشابه استفاده از لایه اعتبارسنجی Vulkan است.

الزامات

لایه‌های GLES فقط در GLES نسخه‌های ۲.۰ و بالاتر پشتیبانی می‌شود.

مقداردهی اولیه لایه

پس‌از پر کردن نقاط ورودی استاندارد، «بارکننده EGL» یک GLES LayerLoader را نمونه‌سازی می‌کند. اگر لایه‌های اشکال‌زدایی فعال باشند، LayerLoader دایرکتوری‌های مشخص‌شده را برای لایه‌ها اسکن می‌کند، همان‌طور که بارکننده Vulkan انجام می‌دهد.

اگر لایه‌بندی فعال باشد، LayerLoader فهرست لایه مشخص‌شده‌ای را جستجو و شماره‌گذاری می‌کند. فهرست لایه با نام فایل‌های جداشده با دونقطه مشخص می‌شود.

LayerLoader لایه‌ها را به ترتیبی که مشخص می‌کنید پیمایش می‌کند، بنابراین اولین لایه مستقیماً زیر برنامه است. برای هر لایه، LayerLoader نقاط ورودی AndroidGLESLayer_Initialize و AndroidGLESLayer_GetProcAddress را ردیابی می‌کند. لایه‌ها باید این میاناهای قابل‌بارگیری را ارائه دهند.

typedef void* (*PFNEGLGETNEXTLAYERPROCADDRESSPROC)(void*, const char*);
void* AndroidGLESLayer_Initialize(void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address))

AndroidGLESLayer_Initialize() شناسه‌ای برای لایه مورداستفاده (layer_id) و نقطه ورودی‌ای ارائه می‌دهد که می‌توان آن را برای جستجوی توابع زیر لایه فراخوانی کرد. از نقطه ورودی می‌توان به‌صورت نشان‌داده‌شده در نمونه کد زیر استفاده کرد:

const char* func = "eglFoo";
void* gpa = get_next_layer_proc_address(layer_id, func);

‫AndroidGLESLayer_GetProcAddress نشانی تماس بعدی را در زنجیره‌ای که لایه باید پس‌از اتمام تماس بگیرد می‌گیرد. اگر فقط یک لایه وجود داشته باشد، next برای اکثر عملکردها مستقیماً به درایور اشاره می‌کند.

typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer;
void* AndroidGLESLayer_GetProcAddress(const char *funcName, EGLFuncPointer next)

برای هر لایه‌ای که GLES LayerLoader پیدا می‌کند، AndroidGLESLayer_Initialize را فراخوانی می‌کند، فهرست‌های تابع libEGL را مرور می‌کند، و AndroidGLESLayer_GetProcAddress را برای همه تابع‌های شناخته‌شده فراخوانی می‌کند. اینکه چگونه نشانی بعدی ردیابی شود به لایه بستگی دارد. اگر لایه تابعی را رهگیری کند، نشانی تابع را ردیابی می‌کند. اگر لایه تابعی را رهگیری نکند، AndroidGLESLayer_GetProcAddress همان نشانی تابعی را که به آن ارسال شده است برمی‌گرداند. سپس LayerLoader فهرست قلاب تابع را به‌روز می‌کند تا به نقطه ورودی لایه اشاره کند.

لایه‌ها ملزم نیستند با اطلاعاتی که AndroidGLESLayer_Initialize و get_next_layer_proc_address ارائه می‌دهند کاری انجام دهند، اما ارائه داده‌ها باعث می‌شود لایه‌های موجود، مانند Android GPU Inspector و RenderDoc، راحت‌تر از Android پشتیبانی کنند. با آن داده‌ها، لایه می‌تواند به‌جای منتظر ماندن برای تماس با AndroidGLESLayer_GetProcAddress، به‌طور مستقل عملکردها را جستجو کند. اگر لایه‌ها تصمیم بگیرند که خودشان را قبل‌از اینکه بارکننده همه نقاط ورودی را پرسمان کند مقداردهی اولیه کنند، باید از get_next_layer_proc_address استفاده کنند. eglGetProcAddress باید ازطریق زنجیره به پلاتفرم منتقل شود.

لایه‌های مکان

GLES LayerLoader لایه‌ها را در مکان‌های زیر به‌ترتیب اولویت جستجو می‌کند:

۱. مکان سیستم برای ریشه

این کار به دسترسی ریشه نیاز دارد

adb root
adb disable-verity
adb reboot
adb root
adb shell setenforce 0
adb shell mkdir -p /data/local/debug/gles
adb push <layer>.so /data/local/debug/gles/

۲. فهرست راهنمای پایه برنامه

برنامه هدف باید قابل‌اشکال‌زدایی باشد، یا باید دسترسی ریشه داشته باشید:

adb push libGLTrace.so /data/local/tmp
adb shell run-as com.android.gl2jni cp /data/local/tmp/libGLTrace.so .
adb shell run-as com.android.gl2jni ls | grep libGLTrace
libGLTrace.so

۳. APK خارجی

رابط دودویی کاربردی (ABI) برنامه هدف را تعیین کنید، سپس APK حاوی لایه‌هایی را که می‌خواهید بار شود نصب کنید:

adb install --abi armeabi-v7a layers.apk

۴. در APK برنامه هدف

مثال زیر نشان می‌دهد که چگونه لایه‌ها را در APK برنامه قرار دهید:

$ jar tf GLES_layers.apk
lib/arm64-v8a/libGLES_glesLayer1.so
lib/arm64-v8a/libGLES_glesLayer2.so
lib/arm64-v8a/libGLES_glesLayer3.so
lib/armeabi-v7a/libGLES_glesLayer1.so
lib/armeabi-v7a/libGLES_glesLayer2.so
lib/armeabi-v7a/libGLES_glesLayer3.so
resources.arsc
AndroidManifest.xml
META-INF/CERT.SF
META-INF/CERT.RSA
META-INF/MANIFEST.MF

فعال کردن لایه‌ها

می‌توانید لایه‌های GLES را برای هر برنامه یا به‌صورت سراسری فعال کنید. تنظیمات هر برنامه در بازراه‌اندازی‌ها حفظ می‌شود، درحالی‌که خصوصیت‌های سراسری در بازراه‌اندازی پاک می‌شود.

مدل و خط‌مشی‌های امنیتی Android تفاوت قابل‌توجهی با پلاتفرم‌های دیگر دارد. برای بار کردن لایه‌های خارجی، یکی از موارد زیر باید درست باشد:

  • فایل مانیفست برنامه هدف شامل عنصر فراداده زیر است (فقط برای برنامه‌هایی اعمال می‌شود که Android نسخه ۱۱ (سطح میانای برنامه‌سازی کاربردی ۳۰) یا بالاتر را هدف قرار می‌دهند):

    <meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true" />

    باید از این گزینه برای نمایه‌سازی برنامه‌تان استفاده کنید.

  • برنامه هدف قابل‌اشکال‌زدایی است. این گزینه اطلاعات اشکال‌زدایی بیشتری به شما می‌دهد، اما ممکن است بر عملکرد برنامه‌تان تأثیر منفی بگذارد.

  • برنامه هدف در ساخت userdebug سیستم‌عامل اجرا می‌شود که دسترسی ریشه را اعطا می‌کند.

برای فعال کردن لایه‌ها برای هر برنامه:

# Enable layers
adb shell settings put global enable_gpu_debug_layers 1

# Specify target application
adb shell settings put global gpu_debug_app <package_name>

# Specify layer list (from top to bottom)
# Layers are identified by their filenames, such as "libGLLayer.so"
adb shell settings put global gpu_debug_layers_gles <layer1:layer2:layerN>

# Specify packages to search for layers
adb shell settings put global gpu_debug_layer_app <package1:package2:packageN>

برای غیرفعال کردن لایه‌ها برای هر برنامه:

# Delete the global setting that enables layers
adb shell settings delete global enable_gpu_debug_layers

# Delete the global setting that selects target application
adb shell settings delete global gpu_debug_app

# Delete the global setting that specifies layer list
adb shell settings delete global gpu_debug_layers_gles

# Delete the global setting that specifies layer packages
adb shell settings delete global gpu_debug_layer_app

برای فعال کردن لایه‌ها به‌صورت سراسری:

# This attempts to load layers for all applications, including native
# executables
adb shell setprop debug.gles.layers <layer1:layer2:layerN>

ایجاد لایه

لایه‌ها باید دو تابع زیر را که در مقداردهی اولیه EGL Loader شرح داده شده است نشان دهند:

AndroidGLESLayer_Initialize
AndroidGLESLayer_GetProcAddress

لایه‌های غیرفعال

برای لایه‌ای که فقط تعداد کمی از توابع را رهگیری می‌کند، لایه مقداردهی‌شده غیرفعال بهینه است. لایه مقداردهی‌شده غیرفعال منتظر می‌ماند تا GLES LayerLoader تابع موردنیازش را مقداردهی کند.

نمونه کد زیر نحوه ایجاد لایه غیرفعال را نشان می‌دهد.

namespace {

std::unordered_map<std::string, EGLFuncPointer> funcMap;

EGLAPI EGLBoolean EGLAPIENTRY glesLayer_eglChooseConfig (
  EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size,
  EGLint *num_config) {

  EGLFuncPointer entry = funcMap["eglChooseConfig"];

  typedef EGLBoolean (*PFNEGLCHOOSECONFIGPROC)(
    EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*);

  PFNEGLCHOOSECONFIGPROC next = reinterpret_cast<PFNEGLCHOOSECONFIGPROC>(entry);

  return next(dpy, attrib_list, configs, config_size, num_config);
}

EGLAPI EGLFuncPointer EGLAPIENTRY eglGPA(const char* funcName) {

  #define GETPROCADDR(func) if(!strcmp(funcName, #func)) { \
    return (EGLFuncPointer)glesLayer_##func; }

  GETPROCADDR(eglChooseConfig);

  // Don't return anything for unrecognized functions
  return nullptr;
}

EGLAPI void EGLAPIENTRY glesLayer_InitializeLayer(
  void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) {
     // This function is purposefully empty, since this layer does not proactively
     // look up any entrypoints
  }

EGLAPI EGLFuncPointer EGLAPIENTRY glesLayer_GetLayerProcAddress(
  const char* funcName, EGLFuncPointer next) {
  EGLFuncPointer entry = eglGPA(funcName);
  if (entry != nullptr) {
    funcMap[std::string(funcName)] = next;
    return entry;
  }
  return next;
}

}  // namespace

extern "C" {
  __attribute((visibility("default"))) EGLAPI void AndroidGLESLayer_Initialize(
    void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) {
    return (void)glesLayer_InitializeLayer(layer_id, get_next_layer_proc_address);
  }
  __attribute((visibility("default"))) EGLAPI void* AndroidGLESLayer_GetProcAddress(
    const char *funcName, EGLFuncPointer next) {
    return (void*)glesLayer_GetLayerProcAddress(funcName, next);
  }
}

لایه‌های فعال

برای لایه‌های رسمی‌تر که باید در ابتدا کاملاً مقداردهی اولیه شوند، یا لایه‌هایی که باید افزونه‌های ناشناخته برای EGL Loader را جستجو کنند، مقداردهی اولیه لایه فعال لازم است. این لایه از get_next_layer_proc_address که AndroidGLESLayer_Initialize ارائه می‌دهد برای جستجوی تابع استفاده می‌کند. این لایه همچنان باید به درخواست‌های AndroidGLESLayer_GetProcAddress از بارکننده پاسخ دهد تا پلاتفرم بداند تماس‌ها را به کجا هدایت کند. نمونه کد زیر نحوه ایجاد لایه فعال را نشان می‌دهد.

namespace {

std::unordered_map<std::string, EGLFuncPointer> funcMap;

EGLAPI EGLBoolean EGLAPIENTRY glesLayer_eglChooseConfig (
  EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size,
  EGLint *num_config) {

  EGLFuncPointer entry = funcMap["eglChooseConfig"];

  typedef EGLBoolean (*PFNEGLCHOOSECONFIGPROC)(
    EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*);

  PFNEGLCHOOSECONFIGPROC next = reinterpret_cast<PFNEGLCHOOSECONFIGPROC>(entry);

  return next(dpy, attrib_list, configs, config_size, num_config);
}

EGLAPI EGLFuncPointer EGLAPIENTRY eglGPA(const char* funcName) {

  #define GETPROCADDR(func) if(!strcmp(funcName, #func)) { \
    return (EGLFuncPointer)glesLayer_##func; }

  GETPROCADDR(eglChooseConfig);

  // Don't return anything for unrecognized functions
  return nullptr;
}

EGLAPI void EGLAPIENTRY glesLayer_InitializeLayer(
  void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) {

  // Note: This is where the layer would populate its function map with all the
  // functions it cares about
  const char* func = “eglChooseConfig”;
  funcMap[func] = get_next_layer_proc_address(layer_id, func);
}

EGLAPI EGLFuncPointer EGLAPIENTRY glesLayer_GetLayerProcAddress(
  const char* funcName, EGLFuncPointer next) {
  EGLFuncPointer entry = eglGPA(funcName);
  if (entry != nullptr) {
    return entry;
  }

  return next;
}

}  // namespace

extern "C" {
  __attribute((visibility("default"))) EGLAPI void AndroidGLESLayer_Initialize(
    void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) {
    return (void)glesLayer_InitializeLayer(layer_id, get_next_layer_proc_address);
  }
  __attribute((visibility("default"))) EGLAPI void* AndroidGLESLayer_GetProcAddress(
    const char *funcName, EGLFuncPointer next) {
    return (void*)glesLayer_GetLayerProcAddress(funcName, next);
  }
}