ניפוי באגים ב-WebAssembly באמצעות כלים מודרניים

Ingvar Stepanyan
Ingvar Stepanyan

הדרך עד עכשיו

לפני שנה, הודענו על תמיכה ראשונית ב-Chrome לניפוי באגים מקורי ב-WebAssembly בכלי הפיתוח ל-Chrome.

הפגינו תמיכה בסיסית בצעדים ודיברנו על הזדמנויות שימוש בפרטי DWARF במקום מפות המקור ייפתחו עבורנו בעתיד:

  • פתרון של שמות של משתנים
  • סוגי הדפסה יפה
  • הערכת ביטויים בשפות מקור
  • ...ועוד הרבה יותר!

היום אנחנו שמחים להציג את התכונות שהובטחו לנוח וההתקדמות שצוותי Emscripten וצוותי הפיתוח ל-Chrome השיגו השנה, במיוחד עבור אפליקציות C ו-C++.

לפני שנתחיל, חשוב לזכור שזוהי עדיין גרסת בטא של הממשק החדש, צריך להשתמש בגרסה האחרונה של כל הכלים על אחריותך בלבד, ואם ייתקלו בבעיות, יש לדווח עליהן https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

נתחיל עם אותה דוגמת C פשוטה כמו בפעם הקודמת:

#include <stdlib.h>

void assert_less(int x, int y) {
  if (x >= y) {
    abort();
  }
}

int main() {
  assert_less(10, 20);
  assert_less(30, 20);
}

כדי להדר אותו, אנחנו משתמשים בגרסה העדכנית ביותר של Emscripten. ולהעביר את הדגל -g, בדיוק כמו בפוסט המקורי, כדי לכלול ניפוי באגים. מידע:

emcc -g temp.c -o temp.html

עכשיו אנחנו יכולים להציג את הדף שנוצר משרת HTTP של מארח מקומי ( לדוגמה, באמצעות serve), וגם לפתוח אותו בגרסה האחרונה של Chrome Canary.

הפעם נצטרך גם תוסף עזרה שמשתלב עם Chrome DevTools ועוזר להבין את כל פרטי ניפוי הבאגים שמקודדים בקובץ WebAssembly. כדי להתקין אותו, עוברים אל קישור: goo.gle/wasm-debugging-extension

צריך גם להפעיל ניפוי באגים ב-WebAssembly בכלי הפיתוח ניסויים. פותחים את כלי הפיתוח ל-Chrome ולוחצים על סמל גלגל השיניים () בתוך לוחצים על החלונית ניסויים בפינה השמאלית העליונה של החלונית של כלי הפיתוח. מסמנים את התיבה WebAssembly Debugging: Enable DWARF support.

חלונית הניסויים בהגדרות של כלי הפיתוח

כשסוגרים את ההגדרות, כלי הפיתוח יציעו לטעון את עצמו מחדש כדי להחיל את ההגדרות, בואו נעשה את זה. זהו, רק חד פעמי הגדרה אישית.

עכשיו אפשר לחזור לחלונית Sources, להפעיל את האפשרות Pause on exceptions (סמל ⏸), לסמן את האפשרות Pause on caught exceptions ולטעון מחדש את הדף. האפשרות של כלי הפיתוח אמורה להיות במצב חריג חריג:

צילום מסך של חלונית המקורות שמראה איך מפעילים את &#39;השהיה בחריגים שזוהו&#39;

כברירת מחדל, היא מפסיקה בקוד הדבקה שנוצר על ידי Emscripten, אבל מימין אפשר לראות תצוגת Call Stack שמייצגת את דוח הקריסות את השגיאה, ויכול לעבור לשורת C המקורית שהפעילה abort:

כלי הפיתוח הושהו בפונקציה &#39;assert_less&#39; ומוצגים בהם הערכים של &#39;x&#39; ו-&#39;y&#39; בתצוגת ההיקף

עכשיו, בתצוגה היקף אפשר לראות את השמות המקוריים. והערכים של המשתנים בקוד C/C++, ולא צריכים יותר מה המשמעות של שמות מעוותים כמו $localN ואיך הם קשורים בקוד המקור שכתבת.

חל לא רק על ערכים פרימיטיביים כמו מספרים שלמים, אלא גם על הרכב כמו מבנים, מחלקות, מערכים וכו'.

תמיכה מסוג 'מתקדם'

נבחן דוגמה מורכבת יותר כדי להמחיש את הדברים האלה. הזה נצייר פרקטל של מנדלברוט, שבו באמצעות קוד C++:

#include <SDL2/SDL.h>
#include <complex>

int main() {
  // Init SDL.
  int width = 600, height = 600;
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* window;
  SDL_Renderer* renderer;
  SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_OPENGL, &window,
                              &renderer);

  // Generate a palette with random colors.
  enum { MAX_ITER_COUNT = 256 };
  SDL_Color palette[MAX_ITER_COUNT];
  srand(time(0));
  for (int i = 0; i < MAX_ITER_COUNT; ++i) {
    palette[i] = {
        .r = (uint8_t)rand(),
        .g = (uint8_t)rand(),
        .b = (uint8_t)rand(),
        .a = 255,
    };
  }

  // Calculate and draw the Mandelbrot set.
  std::complex<double> center(0.5, 0.5);
  double scale = 4.0;
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      std::complex<double> point((double)x / width, (double)y / height);
      std::complex<double> c = (point - center) * scale;
      std::complex<double> z(0, 0);
      int i = 0;
      for (; i < MAX_ITER_COUNT - 1; i++) {
        z = z * z + c;
        if (abs(z) > 2.0)
          break;
      }
      SDL_Color color = palette[i];
      SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
      SDL_RenderDrawPoint(renderer, x, y);
    }
  }

  // Render everything we've drawn to the canvas.
  SDL_RenderPresent(renderer);

  // SDL_Quit();
}

אפשר לראות שהאפליקציה הזו עדיין קטנה למדי – היא קובץ יחיד שמכיל 50 שורות קוד – אבל הפעם השתמשתי גם בממשקי API חיצוניים, כמו ספריית SDL לצורכי גרפיקה וגם מספרים מורכבים מהספרייה הרגילה של C++‎.

אני אגדיר אותו עם אותו דגל -g כמו שלמעלה כדי לכלול מידע על תוצאות ניפוי באגים, וגם אבקש מ-Emscripten לספק את כרטיס ה-SDL2 והם מאפשרים זיכרון בגודל שרירותי:

emcc -g mandelbrot.cc -o mandelbrot.html \
     -s USE_SDL=2 \
     -s ALLOW_MEMORY_GROWTH=1

כשאני מבקרת בדף שנוצר בדפדפן, אני רואה את הסרטונים צורה פרקטלית עם כמה צבעים אקראיים:

דף הדגמה

כשפותחים את כלי הפיתוח, שוב רואים את קובץ ה-C++ המקורי. הפעם, עם זאת, אין שגיאה בקוד (וואו!), אז במקום זאת נגדיר נקודת עצירה בתחילת הקוד.

כשנטען מחדש את הדף שוב, הכלי לניפוי באגים יושהה מקור C++:

כלי הפיתוח הושהו בקריאה &#39;SDL_Init&#39;

אנחנו כבר רואים את כל המשתנים שלנו בצד ימין, אבל רק width ו-height מאותחלים כרגע, כך שאין הרבה לבדוק.

נגדיר נקודת עצירה נוספת בתוך הלולאה הראשית של מנדלברוט ונמשיך את הביצוע כדי לדלג קצת קדימה.

כלי הפיתוח הושהו בתוך הלולאות המוטמעות

בשלב הזה palette שלנו התמלא בכמה צבעים אקראיים, ואנחנו יכולים להרחיב גם את המערך עצמו וגם את הפרט SDL_Color של המבנה ולבחון את הרכיבים שלו כדי לוודא הכול נראה תקין (לדוגמה, ערוץ ה"אלפא" הזה תמיד מוגדר לשקיפות מלאה). באופן דומה, אנחנו יכולים להרחיב ולבדוק את האמת חלקים מדומים של המספר המורכב שמאוחסנים במשתנה center.

אם רוצים לגשת לנכס בתוך נכס בעומק עמוק שאחרת קשה לבצע אותו נכנסים לתצוגה היקף ומשתמשים במסוף. גם הערכה! עם זאת, חשוב לזכור שעדיין אין תמיכה בביטויים מורכבים יותר של C++‎.

חלונית מסוף שמציגה את התוצאה של &#39;palette[10].r&#39;

נמשיך לבצע כמה פעמים כדי שנוכל לראות איך x הפנימי וגם על ידי בדיקה חוזרת של התצוגה היקף, את שם המשתנה לרשימת המעקב, לבדוק אותו במסוף או לפי מעבירים את העכבר מעל המשתנה בקוד המקור:

הסבר קצר על המשתנה &#39;x&#39; במקור, שמציג את הערך שלו &#39;3&#39;

מכאן אנחנו יכולים לבצע הצהרות C++ או שלבים נוספים ולראות איך גם משתנים אחרים משתנים:

תיאורי מידע ותצוגת היקף שמציגים ערכים של &#39;color&#39;,‏ &#39;point&#39; ומשתנים אחרים

אוקיי, כל זה עובד מצוין כשיש מידע על תוצאות ניפוי הבאגים, אבל מה קורה אם רוצים לנפות באגים בקוד שלא נוצר עם אפשרויות ניפוי הבאגים?

ניפוי באגים גולמי ב-WebAssembly

לדוגמה, ביקשנו מ-Emscripten לספק ספריית SDL מובנית מראש במקום להצטלם בעצמנו מהמקור, לפחות נכון לעכשיו, לכלי לניפוי באגים אין דרך למצוא מקורות משויכים. עליך להיכנס שוב כדי להיכנס אל SDL_RenderDrawColor:

כלי פיתוח שמציגים תצוגת פירוק של &#39;mandelbrot.wasm&#39;

חזרנו לחוויה הגולמית של ניפוי הבאגים ב-WebAssembly.

עכשיו זה נראה קצת מפחיד וזה לא משהו שרוב מפתחי האינטרנט יאהבו אבל מדי פעם ייתכן שתרצו לנפות באגים שפותחה ללא מידע על תוצאות ניפוי באגים - בין אם בגלל 3rd-ספרייה שאין לך שליטה עליה, או מפני וליצור אחד מהבאגים האלה שקורים רק בסביבת הייצור.

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

קודם כל, אם השתמשתם בעבר בניפוי באגים גולמי ב-WebAssembly, שימו לב שהפירוק כולו מוצג עכשיו בקובץ אחד לנסות לנחש לאיזו פונקציה רשומת מקורות wasm-53834e3e/ wasm-53834e3e-7 עשויה להתאים.

סכימה חדשה ליצירת שם

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

עכשיו אנחנו יוצרים שמות באופן דומה לכלים אחרים לפירוק קוד, באמצעות רמזים מקטע השמות של WebAssembly, נתיבי ייבוא/ייצוא, ולבסוף, אם כל השאר נכשל, יוצרים אותם על סמך הסוג והאינדקס של הפריט, כמו $func123. אפשר בצילום המסך שלמעלה זה כבר עוזר דוחות קריסות ופירוק קריאים יותר.

אם אין מידע זמין על סוגים שונים, יכול להיות שיהיה קשה לבדוק ערכים חוץ מהפרימיטיבים. לדוגמה, יופיעו מצביעים כמספרים שלמים רגילים, בלי לדעת מה מאוחסן מאחוריהם זיכרון.

בדיקת זיכרון

בעבר, אפשר היה להרחיב רק את אובייקט הזיכרון של WebAssembly, שמיוצג על ידי env.memory בתצוגה Scope, כדי לחפש ביטים בודדים. זה עבד בתרחישים טריוויאליים, אבל זה לא היה נוח במיוחד להרחבה ולא אפשר לפרש מחדש את הנתונים בפורמטים שאינם ערכי בייטים. הוספנו גם תכונה חדשה שתעזור בנושא הזה: בודק זיכרון לינארי.

אם תלחצו לחיצה ימנית על env.memory, אמורה להופיע עכשיו אפשרות שנקראת בדיקת זיכרון:

תפריט ההקשר ב-env.memory בחלונית &#39;היקף&#39;, שבה מופיע &#39;בדיקת זיכרון&#39; פריט

לאחר הלחיצה ייפתח בודק זיכרון, שאפשר לבדוק את הזיכרון WebAssembly בתצוגות הקסדצימליות ו-ASCII, לנווט לכתובות ספציפיות ולפרש את הנתונים בפורמטים שונים:

החלונית &#39;בודק הזיכרון&#39; ב&#39;כלי פיתוח&#39; שמציגה תצוגות הקסדצימליות ו-ASCII של הזיכרון

תרחישים מתקדמים וסייגים

יצירת פרופילים של קוד WebAssembly

כשפותחים כלי פיתוח, קוד WebAssembly עובר 'שכבות'. ל ולא שעברה אופטימיזציה כדי לאפשר ניפוי באגים. הגרסה הזאת הרבה יותר איטית, כלומר אי אפשר להסתמך על console.time, performance.now ושיטות אחרות למדידת מהירות הקוד של הקוד, בעוד שכלי הפיתוח מאחר שהמספרים שיתקבלו לא ייצגו את הביצועים בפועל בכלל.

במקום זאת, צריך להשתמש בחלונית הביצועים של כלי הפיתוח שיריץ את הקוד במהירות המלאה ויספק לך פירוט של משך הזמן שהוקדש לפונקציות שונות:

חלונית ליצירת פרופילים שמציגה פונקציות שונות של Wasm

לחלופין, אפשר להריץ את האפליקציה כש-DevTools סגור, ולפתוח אותו בסיום כדי לבדוק את מסוף.

נשפר את התרחישים של הפרופיילינג בעתיד, אבל כרגע אזהרה שכדאי לשים לב אליה. אם אתם רוצים לקבל מידע נוסף על WebAssembly בתרחישים של יצירת שכבות, כדאי לעיין במסמכים שלנו בנושא צינור עיבוד הנתונים של WebAssembly הידור (compilation).

פיתוח ובדיקת באגים במכונות שונות (כולל Docker או מארח)

כשמבצעים פיתוח ב-Docker, במכונה וירטואלית או בשרת פיתוח מרוחק, סביר להניח שתתקלו במצבים שבהם הנתיבים לקובצי המקור שבהם נעשה שימוש במהלך הפיתוח לא תואמים לנתיבים במערכת הקבצים שלכם שבה פועלים הכלים של Chrome DevTools. במקרה כזה, הקבצים יופיעו חלונית המקורות אבל לא נטענת.

כדי לתקן את הבעיה, יישמנו פונקציונליות של מיפוי נתיבים אפשרויות התוסף C/C++. אפשר להשתמש בו כדי למפות מחדש נתיבים שרירותיים עוזרים לכלי הפיתוח לאתר מקורות.

לדוגמה, אם הפרויקט במכונה המארחת נמצא מתחת לנתיב C:\src\my_project, אבל היא נבנתה בתוך קונטיינר Docker שבו שהנתיב מיוצג בתור /mnt/c/src/my_project, אפשר למפות מחדש אותו בחזרה במהלך ניפוי באגים על ידי ציון הנתיבים האלה כקידומות:

דף האפשרויות בתוסף C/C++ לניפוי באגים

התחילית הראשונה עם ההתאמה "wins". אם אתם מכירים גרסה אחרת של C++ כלי לניפוי באגים, האפשרות הזו דומה לפקודה set substitute-path ב-GDB או בהגדרה target.source-map ב-LLDB.

ניפוי באגים בגרסאות build שעברו אופטימיזציה

כמו בכל שפה אחרת, ניפוי הבאגים פועל בצורה הטובה ביותר אם האופטימיזציה מושבת. אופטימיזציות עשויות להטמיע פונקציות אחת בתוך השנייה, לשנות את סדר הקוד או להסיר חלקים מהקוד לגמרי – וכל זה עלול לבלבל את מנתח הבאגים, וכתוצאה מכך אתכם כמשתמשים.

אם אכפת לך מחוויית ניפוי באגים מוגבלת יותר ועדיין ברצונך לנפות באגים ב-build שעבר אופטימיזציה, ואז רוב האופטימיזציות יפעלו הוא מצופה, מלבד בתוך פונקציה, אנחנו מתכננים לטפל בבעיות הנותרות בעתיד, אבל בינתיים, תוכלו להשתמש ב--fno-inline כדי להשבית את התכונה כשאתם מבצעים הידור עם אופטימיזציות ברמת -O, למשל:

emcc -g temp.c -o temp.html \
     -O3 -fno-inline

מפריד המידע על תוצאות ניפוי הבאגים

המידע על תוצאות ניפוי הבאגים שומר הרבה פרטים על הקוד, סוגים, משתנים, פונקציות, היקפים ומיקומים - כל מה שעשוי שימושי לכלי לניפוי באגים. לכן, לעיתים קרובות הוא יכול להיות גדול יותר מהקוד עצמו.

כדי לזרז את הטעינה וההדרכה של מודול WebAssembly, מומלץ לפצל את פרטי ניפוי הבאגים האלה לקובץ WebAssembly נפרד. כדי לעשות זאת ב-Emscripten, מעבירים את הדגל -gseparate-dwarf=… עם שם קובץ רצוי:

emcc -g temp.c -o temp.html \
     -gseparate-dwarf=temp.debug.wasm

במקרה כזה, האפליקציה הראשית תאחסן רק שם קובץ temp.debug.wasm, ותוסף העזרה יוכל לאתר לטעון אותו כשפותחים את כלי הפיתוח.

בשילוב עם פעולות אופטימיזציה כמו שמתואר למעלה, התכונה יכולה אפילו משמשת לשליחת גרסאות build כמעט אופטימליות של ייצור ולאחר מכן לנפות באגים באמצעות קובץ צדדי מקומי. במקרה הזה, נצטרך גם לבטל את כתובת ה-URL השמורה כדי לעזור לתוסף מוצאים את קובץ הצד. לדוגמה:

emcc -g temp.c -o temp.html \
     -O3 -fno-inline \
     -gseparate-dwarf=temp.debug.wasm \
     -s SEPARATE_DWARF_URL=file://[local path to temp.debug.wasm]

כדי להמשיך...

וואו, היו המון תכונות חדשות!

בזכות כל השילובים החדשים האלה, כלי הפיתוח ל-Chrome הופכים לזמינים, כלי רב-עוצמה לניפוי באגים לא רק עבור JavaScript, אלא גם עבור אפליקציות C ו-C++ , ולכן קל יותר מתמיד לקחת אפליקציות, הכוללות מגוון של טכנולוגיות מתקדמות ומביאים אותן לאינטרנט משותף וחוצה פלטפורמות.

עם זאת, המסע שלנו עדיין לא הסתיים. חלק מהדברים שיהיו להמשיך מכאן והלאה:

  • מנקים את הקצוות הגולמיים בחוויית ניפוי הבאגים.
  • הוספת תמיכה בעיצובים מותאמים אישית לסוגים שונים.
  • אנחנו עובדים על שיפורים בפרופיל של אפליקציות WebAssembly.
  • הוספנו תמיכה בכיסוי הקוד כדי שיהיה קל יותר למצוא אותו קוד שלא בשימוש.
  • שיפור התמיכה בביטויים בהערכת המסוף.
  • הוספת תמיכה בשפות נוספות.
  • …ועוד!

בינתיים, נשמח לעזרה שלך. אפשר לנסות את גרסת הבטא הנוכחית בקוד שלך ולדווח על בעיות שנמצאות בכתובת https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

מורידים את הערוצים של התצוגה המקדימה.

כדאי להשתמש ב-Chrome Canary, Dev או בטא כדפדפן הפיתוח שמוגדר כברירת מחדל. הערוצים לתצוגה מקדימה אלה מעניקים לך גישה לתכונות החדשות של כלי הפיתוח, בודקים ממשקי API מתקדמים של פלטפורמות אינטרנט ומוצאים בעיות באתר שלך לפני שהמשתמשים עושים זאת.

יצירת קשר עם הצוות של כלי הפיתוח ל-Chrome

אפשר להשתמש באפשרויות הבאות כדי לדון בתכונות ובשינויים החדשים בפוסט, או בכל נושא אחר שקשור לכלי פיתוח.

  • אפשר לשלוח לנו הצעה או משוב דרך crbug.com.
  • כדי לדווח על בעיה בכלי הפיתוח, לוחצים על אפשרויות נוספות   עוד > עזרה > דיווח על בעיות בכלי הפיתוח ב'כלי פיתוח'.
  • שליחת ציוץ אל @ChromeDevTools.
  • נשמח לשמוע מה חדש בסרטונים ב-YouTube של כלי הפיתוח או בסרטונים ב-YouTube שקשורים לכלי פיתוח.