اشکال زدایی WebAssembly با ابزارهای مدرن

اینگوار استپانیان
Ingvar Stepanyan

جاده تا اینجا

یک سال پیش، کروم پشتیبانی اولیه خود را از اشکال زدایی WebAssembly در کروم 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 لوکال هاست (مثلاً با سرویس ) ارائه کنیم و آن را در جدیدترین Chrome Canary باز کنیم.

این بار همچنین به یک پسوند کمکی نیاز داریم که با Chrome DevTools یکپارچه شود و به آن کمک کند تا تمام اطلاعات اشکال زدایی رمزگذاری شده در فایل WebAssembly را درک کند. لطفاً با رفتن به این پیوند آن را نصب کنید: goo.gle/wasm-debugging-extension

همچنین می‌خواهید اشکال‌زدایی WebAssembly را در آزمایش‌های DevTools فعال کنید. Chrome DevTools را باز کنید، روی نماد چرخ‌دنده ( ) در گوشه سمت راست بالای صفحه DevTools کلیک کنید، به پنل Experiments بروید و تیک WebAssembly Debugging: فعال کردن پشتیبانی DWARF را بزنید.

پنجره آزمایشی تنظیمات DevTools

وقتی تنظیمات را می بندید، DevTools پیشنهاد می کند که برای اعمال تنظیمات، خود را مجدداً بارگیری کند، پس بیایید این کار را انجام دهیم. این برای راه اندازی یکباره است.

اکنون می‌توانیم به پنل Sources برگردیم، Pause on استثناها (نماد ⏸) را فعال کنیم، سپس Pause on catch exports را علامت بزنید و صفحه را دوباره بارگیری کنید. شما باید DevTools را در یک استثنا متوقف شده ببینید:

اسکرین شات پانل منابع که نحوه فعال کردن «مکث در موارد استثنا شده» را نشان می‌دهد

به‌طور پیش‌فرض، روی یک کد چسب تولید شده توسط Emscripten متوقف می‌شود، اما در سمت راست می‌توانید نمای Call Stack را ببینید که نشان‌دهنده stacktrace خطا است، و می‌توانید به خط C اصلی که لغو را abort کرده است بروید:

DevTools در تابع «assert_less» متوقف شد و مقادیر «x» و «y» را در نمای Scope نشان داد.

اکنون، اگر به نمای Scope نگاه کنید، می‌توانید نام‌ها و مقادیر اصلی متغیرها را در کد 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

وقتی از صفحه ایجاد شده در مرورگر بازدید می کنم، می توانم شکل زیبای فراکتال را با چند رنگ تصادفی ببینم:

صفحه نمایشی

وقتی DevTools را باز می کنم، یک بار دیگر، می توانم فایل اصلی C++ را ببینم. با این حال، این بار، ما خطایی در کد نداریم (وای!)، بنابراین اجازه دهید به جای آن یک نقطه شکست در ابتدای کد خود تعیین کنیم.

وقتی صفحه را دوباره بارگذاری می کنیم، دیباگر درست در منبع C++ ما مکث می کند:

DevTools در تماس «SDL_Init» متوقف شد

ما قبلاً می‌توانیم همه متغیرهای خود را در سمت راست ببینیم، اما فقط width و height در حال حاضر مقداردهی اولیه شده‌اند، بنابراین چیز زیادی برای بررسی وجود ندارد.

بیایید یک نقطه شکست دیگر را در حلقه اصلی Mandelbrot خود تنظیم کنیم و اجرا را از سر بگیریم تا کمی جلوتر برویم.

DevTools در داخل حلقه‌های تودرتو متوقف شد

در این مرحله palette ما با رنگ‌های تصادفی پر شده است و می‌توانیم هم خود آرایه و هم ساختارهای جداگانه SDL_Color را گسترش دهیم و اجزای آنها را بررسی کنیم تا مطمئن شویم همه چیز خوب به نظر می‌رسد (مثلاً آن کانال "آلفا" همیشه روی Opacity کامل تنظیم کنید). به همین ترتیب، می‌توانیم قسمت‌های واقعی و خیالی عدد مختلط ذخیره‌شده در متغیر center را گسترش داده و بررسی کنیم.

اگر می‌خواهید به یک ویژگی عمیق تو در تو دسترسی داشته باشید که در غیر این صورت به سختی می‌توان از طریق نمای Scope به آن پیمایش کرد، می‌توانید از ارزیابی کنسول نیز استفاده کنید! با این حال، توجه داشته باشید که عبارات پیچیده‌تر C++ هنوز پشتیبانی نمی‌شوند.

پانل کنسول که نتیجه "palette[10].r" را نشان می دهد

بیایید اجرا را چند بار از سر بگیریم و با نگاه کردن دوباره به نمای Scope ، اضافه کردن نام متغیر به لیست تماشا، ارزیابی آن در کنسول یا با نگه داشتن ماوس روی متغیر در داخل، می‌توانیم ببینیم که x داخلی چگونه تغییر می‌کند. کد منبع:

راهنمای ابزار روی متغیر «x» در منبعی که مقدار آن «3» را نشان می‌دهد

از اینجا، می‌توانیم دستورات C++ را وارد یا گام‌به‌بر کنیم و مشاهده کنیم که متغیرهای دیگر نیز چگونه تغییر می‌کنند:

راهنمای ابزار و نمای محدوده که مقادیر «رنگ»، «نقطه» و سایر متغیرها را نشان می‌دهد

بسیار خوب، پس وقتی اطلاعات اشکال زدایی در دسترس باشد، همه اینها عالی کار می کند، اما اگر بخواهیم کدی را که با گزینه های اشکال زدایی ساخته نشده است، اشکال زدایی کنیم، چه؟

اشکال زدایی Raw WebAssembly

به عنوان مثال، ما از Emscripten خواستیم به جای اینکه خودمان آن را از منبع کامپایل کنیم، یک کتابخانه SDL از پیش ساخته برای ما فراهم کند، بنابراین حداقل در حال حاضر هیچ راهی برای دیباگر برای یافتن منابع مرتبط وجود ندارد. بیایید دوباره وارد شویم تا وارد SDL_RenderDrawColor شویم:

DevTools نمای جداسازی «mandelbrot.wasm» را نشان می دهد

ما به تجربه خام اشکال زدایی WebAssembly بازگشته ایم.

اکنون، کمی ترسناک به نظر می رسد و چیزی نیست که اکثر توسعه دهندگان وب هرگز نیازی به مقابله با آن ندارند، اما گاهی اوقات ممکن است بخواهید کتابخانه ای را که بدون اطلاعات اشکال زدایی ساخته شده است اشکال زدایی کنید - خواه به دلیل اینکه یک کتابخانه شخص ثالث است که کنترلی روی آن ندارید. ، یا به این دلیل که با یکی از آن اشکالاتی روبرو می شوید که فقط در زمان تولید رخ می دهد.

برای کمک به این موارد، ما در تجربه اولیه اشکال زدایی نیز بهبودهایی ایجاد کرده ایم.

اول از همه، اگر قبلاً از اشکال‌زدایی خام WebAssembly استفاده می‌کردید، ممکن است متوجه شوید که کل جداسازی در یک فایل منفرد نشان داده می‌شود - دیگر حدس نمی‌زنید که یک ورودی Sources wasm-53834e3e/ wasm-53834e3e-7 احتمالاً با کدام عملکرد مطابقت دارد.

طرح تولید نام جدید

ما نام ها را در نمای جداسازی نیز بهبود دادیم. قبلاً فقط شاخص های عددی یا در مورد توابع، اصلاً نامی را نمی دیدید.

اکنون با استفاده از نکاتی از بخش نام WebAssembly ، مسیرهای import/export و در نهایت، اگر همه چیز ناموفق بود، نام‌هایی را مشابه سایر ابزارهای جداسازی قطعات ایجاد می‌کنیم، آنها را بر اساس نوع و نمایه مورد مانند $func123 تولید می‌کنیم. می‌توانید ببینید که چگونه، در تصویر بالا، این از قبل به دریافت stacktraces و جداسازی قطعات کمی خواناتر کمک می‌کند.

هنگامی که هیچ نوع اطلاعاتی در دسترس نیست، ممکن است بررسی مقادیری غیر از مقادیر اولیه دشوار باشد - برای مثال، اشاره گرها به صورت اعداد صحیح معمولی نشان داده می شوند، بدون اینکه بدانند چه چیزی در پشت آنها در حافظه ذخیره شده است.

بازرسی حافظه

قبلاً، فقط می‌توانستید شی حافظه WebAssembly را که با env.memory در نمای Scope نمایش داده می‌شد، گسترش دهید تا تک تک بایت‌ها را جستجو کنید. این در برخی از سناریوهای پیش پا افتاده کار می کرد، اما به خصوص برای گسترش مناسب نبود و اجازه نمی داد که داده ها را در قالب هایی غیر از مقادیر بایت تفسیر مجدد کنند. ما یک ویژگی جدید را نیز برای کمک به این موضوع اضافه کرده‌ایم: یک بازرس حافظه خطی.

اگر روی env.memory راست کلیک کنید، اکنون باید گزینه جدیدی به نام Inspect memory را مشاهده کنید:

منوی زمینه در "env.memory" در بخش Scope که آیتم "Inspect Memory" را نشان می دهد.

پس از کلیک بر روی آن، یک Memory Inspector ظاهر می شود، که در آن می توانید حافظه WebAssembly را در نماهای هگزادسیمال و اسکی بررسی کنید، به آدرس های خاصی بروید و همچنین داده ها را در قالب های مختلف تفسیر کنید:

صفحه بازرس حافظه در DevTools که نماهای هگز و ASCII از حافظه را نشان می دهد.

سناریوهای پیشرفته و هشدارها

پروفایل کد WebAssembly

وقتی DevTools را باز می‌کنید، کد WebAssembly به یک نسخه بهینه‌نشده تقسیم می‌شود تا اشکال‌زدایی را فعال کند. این نسخه بسیار کندتر است، به این معنی که وقتی DevTools باز است نمی‌توانید به console.time ، performance.now و دیگر روش‌های اندازه‌گیری سرعت کد خود اعتماد کنید، زیرا اعدادی که دریافت می‌کنید نشان دهنده دنیای واقعی نیستند. اصلا عملکرد

درعوض، باید از پنل DevTools Performance استفاده کنید که کد را با سرعت کامل اجرا می‌کند و زمان صرف شده در عملکردهای مختلف را به تفصیل در اختیار شما قرار می‌دهد:

پانل پروفایل که عملکردهای مختلف Wasm را نشان می دهد

از طرف دیگر، می توانید برنامه خود را با DevTools بسته اجرا کنید و پس از اتمام آن را باز کنید تا کنسول را بررسی کنید.

ما در آینده سناریوهای پروفایل را بهبود خواهیم داد، اما در حال حاضر این یک هشدار است که باید از آن آگاه بود. اگر می‌خواهید درباره سناریوهای لایه‌بندی WebAssembly بیشتر بدانید، اسناد ما را در خط لوله جمع‌آوری WebAssembly بررسی کنید.

ساخت و اشکال زدایی در ماشین های مختلف (از جمله داکر / میزبان)

هنگام ساختن در یک Docker، ماشین مجازی یا روی سرور ساخت راه دور، احتمالاً با موقعیت‌هایی مواجه می‌شوید که مسیرهای فایل‌های مبدأ استفاده شده در طول ساخت، با مسیرهای سیستم فایل خودتان که در آن ابزار توسعه‌دهنده Chrome در حال اجرا است، مطابقت ندارد. در این حالت، فایل‌ها در پنل Sources نمایش داده می‌شوند اما بارگیری نمی‌شوند.

برای رفع این مشکل، ما یک عملکرد نگاشت مسیر را در گزینه های پسوند C/C++ پیاده سازی کرده ایم. می توانید از آن برای ترسیم مجدد مسیرهای دلخواه و کمک به DevTools در یافتن منابع استفاده کنید.

به عنوان مثال، اگر پروژه در دستگاه میزبان شما تحت مسیر C:\src\my_project قرار دارد، اما در داخل یک ظرف Docker ساخته شده است که در آن مسیر به صورت /mnt/c/src/my_project نشان داده شده است، می‌توانید آن را در حین اشکال‌زدایی دوباره نقشه‌برداری کنید. با تعیین آن مسیرها به عنوان پیشوند:

صفحه گزینه‌های برنامه افزودنی اشکال‌زدایی C/C++

اولین پیشوند همسان "برنده" است. اگر با دیگر اشکال‌زدای C++ آشنا هستید، این گزینه مشابه دستور set substitute-path در GDB یا یک تنظیم target.source-map در LLDB است.

اشکال زدایی ساخت های بهینه شده

مانند هر زبان دیگری، اشکال زدایی در صورتی که بهینه سازی غیرفعال باشد بهترین کار را دارد. بهینه‌سازی‌ها ممکن است توابع را در یکدیگر قرار دهند، کد را مجدداً مرتب کنند، یا قسمت‌هایی از کد را به طور کلی حذف کنند - و همه اینها فرصتی برای سردرگمی اشکال‌زدا و در نتیجه شما به عنوان کاربر دارد.

اگر از تجربه اشکال‌زدایی محدودتر مشکلی ندارید و همچنان می‌خواهید یک ساخت بهینه‌شده را اشکال‌زدایی کنید، بیشتر بهینه‌سازی‌ها همانطور که انتظار می‌رود کار خواهند کرد، به جز برای توابع داخلی. ما قصد داریم در آینده به مشکلات باقیمانده بپردازیم، اما در حال حاضر، لطفاً از -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 را باز می‌کنید، آن را بیابد و بارگذاری کند.

هنگامی که با بهینه‌سازی‌هایی که در بالا توضیح داده شد ترکیب می‌شود، این ویژگی می‌تواند حتی برای ارسال بیلدهای تولید تقریباً بهینه برنامه شما و بعداً آنها را با یک فایل جانبی محلی اشکال‌زدایی کند. در این مورد، ما همچنین باید 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 DevTools نه تنها برای جاوا اسکریپت، بلکه برای برنامه‌های C و C++ تبدیل به یک اشکال‌زدایی بادوام، قدرتمند و قابل دوام می‌شود و گرفتن برنامه‌ها را که در فناوری‌های مختلف ساخته شده‌اند و به اشتراک گذاشتن آن‌ها را آسان‌تر از همیشه می‌کند. وب کراس پلتفرم

با این حال، سفر ما هنوز تمام نشده است. برخی از مواردی که از اینجا به بعد روی آنها کار خواهیم کرد:

  • تمیز کردن لبه های ناهموار در تجربه اشکال زدایی.
  • اضافه کردن پشتیبانی از فرمت‌کننده‌های نوع سفارشی.
  • کار بر روی بهبود پروفایل برای برنامه های WebAssembly.
  • افزودن پشتیبانی برای پوشش کد برای آسان‌تر یافتن کدهای استفاده نشده.
  • بهبود پشتیبانی از عبارات در ارزیابی کنسول.
  • افزودن پشتیبانی برای زبان های بیشتر
  • ... و بیشتر!

در همین حال، لطفاً با آزمایش بتای فعلی روی کد خود و گزارش هرگونه مشکل پیدا شده به https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350 به ما کمک کنید.

کانال های پیش نمایش را دانلود کنید

استفاده از Chrome Canary ، Dev یا Beta را به عنوان مرورگر توسعه پیش‌فرض خود در نظر بگیرید. این کانال‌های پیش‌نمایش به شما امکان دسترسی به جدیدترین ویژگی‌های DevTools را می‌دهند، به شما اجازه می‌دهند APIهای پلتفرم وب پیشرفته را آزمایش کنید و به شما کمک می‌کنند تا قبل از کاربران، مشکلات سایت خود را پیدا کنید!

با تیم Chrome DevTools در تماس باشید

از گزینه‌های زیر برای بحث در مورد ویژگی‌های جدید، به‌روزرسانی‌ها یا هر چیز دیگری مربوط به DevTools استفاده کنید.