במכשירים שמותקנת בהם מערכת Android 10 (API ברמה 29) ומעלה, יש אפשרות להשתמש בשכבות OpenGL ES (GLES). אפליקציה שאפשר לבצע בה ניפוי באגים יכולה לטעון שכבות GLES מתוך קובץ ה-APK שלה, מתוך ספריית הבסיס שלה או מתוך קובץ APK של שכבה שנבחרה.
השימוש בשכבת GLES דומה לשימוש בשכבת האימות של Vulkan.
דרישות
שכבות GLES נתמכות רק בגרסאות GLES 2.0 ואילך.
אתחול שכבה
אחרי שממלאים את נקודות הכניסה הרגילות, הכלי EGL Loader יוצר מופע של 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)
לכל שכבה ש-GLES 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. ספריית הבסיס של האפליקציה
אפשרות ניפוי הבאגים של אפליקציית היעד חייבת להיות מופעלת, או שצריכה להיות לכם גישת שורש:
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" />
כדאי להשתמש באפשרות הזו כדי ליצור פרופיל של האפליקציה.
אפשר לבצע ניפוי באגים באפליקציית היעד. האפשרות הזו מספקת מידע נוסף לניפוי באגים, אבל היא עלולה להשפיע לרעה על ביצועי האפליקציה.
אפליקציית היעד מופעלת בגרסת userdebug של מערכת ההפעלה, שמעניקה גישת 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>
יצירת שכבה
שכבות חייבות לחשוף את שתי הפונקציות הבאות שמתוארות בקטע EGL Loader initialization:
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); } }