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

Ingvar Stepanyan
Ingvar Stepanyan

הדרך עד עכשיו

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

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

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

היום אנחנו שמחים להציג את התכונות שהובטחו, ואת ההתקדמות שצוותי Emscripten ו-Chrome DevTools עשו במהלך השנה, במיוחד לאפליקציות 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 כלי פיתוח ועוזרים להבין את כל המידע על תוצאות ניפוי הבאגים מקודד בקובץ WebAssembly. כדי להתקין אותו, עוברים אל קישור: goo.gle/wasm-debugging-extension

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

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

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

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

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

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

DevTools הושהה בפונקציה assert_less ומוצגים הערכים של x ו-y בתצוגת ההיקף

עכשיו, בתצוגה היקף, אפשר לראות את השמות המקוריים והערכים של המשתנים בקוד 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++:

כלי הפיתוח הושהה בקריאה ל-`SDL_Init`

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

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

כלי הפיתוח מושהים בתוך הלולאות הפנימיות

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

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

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

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

תיאור מידע מעל המשתנה x במקור, שבו מוצג הערך שלו, 3

מכאן אפשר להריץ הוראות C++‏ step-in או step-over ולראות איך משתנים אחרים משתנים גם כן:

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

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

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

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

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

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

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

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

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

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

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

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

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

בדיקת זיכרון

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

הקידומת הראשונה שתואמת "מנצחת". אם אתם מכירים את ה-debuggers האחרים של 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, ותוספי העזרה יוכלו לאתר אותו ולטעון אותו כשתפתחו את DevTools.

בשילוב עם אופטימיזציות כמו אלה שמתוארות למעלה, אפשר להשתמש בתכונה הזו גם כדי לשלוח גרסאות 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&amp;template=0&amp;component=1456350.

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

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

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

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