שכבות GLES

במכשירים שמותקנת בהם גרסת Android 10 (API ברמה 29) ואילך, השכבות של OpenGL ES (GLES) זמינים. אפליקציה שניתנת לניפוי באגים יכולה לטעון שכבות GLES מה-APK שלה, מהבסיס שלה או משכבת ה-APK שנבחרה.

השימוש בשכבת GLES דומה לשימוש השימוש בשכבת האימות של Vulkan.

הדרישות

שכבות GLES נתמכות רק בגרסאות 2.0 ואילך של GLES.

אתחול שכבה

אחרי אכלוס נקודות כניסה רגילות, Loader EGL יוצר GLES LayerLoader אם השכבות לניפוי באגים מופעלות, הסריקות של LayerLoader יצוינו ארבע שכבות שונות, כמו Vulkan loader כן.

אם יצירת שכבות מופעלת, 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)

עבור כל שכבה ש-LayerLoader מוצאת, היא קוראת AndroidGLESLayer_Initialize, עובר על רשימות הפונקציות של libEGL ושיחות AndroidGLESLayer_GetProcAddress לכל הפונקציות הידועות. הוא תלוי בשכבה כדי לקבוע איך לעקוב אחר הכתובת הבאה. אם השכבה מיירט פונקציה, הוא עוקב אחרי הכתובת של הפונקציה. אם השכבה לא מיירט פונקציה, AndroidGLESLayer_GetProcAddress מחזירה את אותה כתובת פונקציה עבר. לאחר מכן LayerLoader מעדכנת את רשימת ה-hook של הפונקציה כך שיצביע על בנקודת הכניסה של השכבה.

השכבות לא נדרשות לעשות דבר עם המידע AndroidGLESLayer_Initialize ו-get_next_layer_proc_address מספקים, אבל מתן הנתונים מקל על השכבות הקיימות, כגון Android GPU Inspector וגם RenderDoc, לתמיכה Android. עם הנתונים האלה, שכבה יכולה לחפש פונקציות באופן עצמאי במקום בהמתנה לשיחות אל AndroidGLESLayer_GetProcAddress. אם השכבות בוחרות לאתחל את עצמם לפני שהטעינה צריכה לבצע שאילתה על כל נקודות הכניסה, חייבים להשתמש ב-get_next_layer_proc_address. חובה על eglGetProcAddress יועברו בשרשרת לפלטפורמה.

שכבות מיקום

מתבצע חיפוש ב-GLES LayerLoader של שכבות במיקומים הבאים, לפי הסדר בעדיפות:

1. מיקום המערכת לרמה הבסיסית (root)

נדרשת גישה לרמה הבסיסית (root)

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/

2. ספריית הבסיס של האפליקציה

אפליקציית היעד צריכה להיות ניתנת לניפוי באגים, או צריכה להיות לכם גישה לרמה הבסיסית (root):

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

3. APK חיצוני

קובעים את ה-ABI של אפליקציית היעד ואז מתקינים APK שמכיל את השכבות שרוצים לטעון:

adb install --abi armeabi-v7a layers.apk

4. ב-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 11 (רמת API 30) ואילך):

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

    עליכם להשתמש באפשרות הזו כדי ליצור פרופיל לאפליקציה.

  • אפליקציית היעד ניתנת לניפוי באגים. האפשרות הזו מספקת מידע נוסף על תוצאות ניפוי הבאגים, אבל עלולות להשפיע לרעה על ביצועי האפליקציה.

  • אפליקציית היעד רצה ב-build של ניפוי באגים ב-User-ID של מערכת ההפעלה מעניקה גישה לרמה הבסיסית (root).

כדי להפעיל שכבות לכל אפליקציה:

# 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>

יצירת שכבה

שכבות חייבות לחשוף את שתי הפונקציות הבאות שמתוארות אתחול Loader EGL:

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);
  }
}