WebAssembly को ज़्यादा तेज़ी से डीबग करना

Philip Pfaffe
Kim-Anh Tran
Kim-Anh Tran
Eric Leese
Sam Clegg

Chrome Dev समिट 2020 में, हमने पहली बार वेब पर WebAssembly ऐप्लिकेशन के लिए, Chrome की डीबग करने की सुविधा का डेमो दिया. तब से, टीम ने बड़े और यहां तक कि बड़े ऐप्लिकेशन के लिए डेवलपर अनुभव बढ़ाने में काफ़ी ऊर्जा खर्च की है. इस पोस्ट में हम आपको बताएंगे कि हमने अलग-अलग टूल में कौनसे नॉब जोड़े हैं या कैसे काम किए हैं और उन्हें कैसे इस्तेमाल किया जा सकता है!

बड़े स्तर पर डीबग करने की सुविधा

चलिए, 2020 की अपनी पोस्ट में वहीं से शुरू करते हैं जहां हमने छोड़ा था. उस समय हम इस उदाहरण पर गौर कर रहे थे:

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

यह अब भी एक छोटा सा उदाहरण है. इसमें ऐसी कोई भी असल समस्या नहीं दिख सकती जो आपको किसी बड़े ऐप्लिकेशन में दिखे. हालांकि, हम फिर भी आपको नई सुविधाओं के बारे में जानकारी दे सकते हैं. इसे सेट अप करना तेज़ और आसान है. इसे खुद आज़माएं!

पिछली पोस्ट में, हमने इस उदाहरण को कंपाइल और डीबग करने का तरीका बताया था. आइए, फिर से ऐसा करते हैं, लेकिन आइए //performance// पर भी एक नज़र डालते हैं:

$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH

इस निर्देश से 3 एमबी Wasm बाइनरी बनती है. साथ ही, इसमें सबसे ज़्यादा डीबग की जानकारी होती है. llvm-objdump टूल [1] की मदद से इसकी पुष्टि की जा सकती है. उदाहरण के लिए:

$ llvm-objdump -h mandelbrot.wasm

mandelbrot.wasm:        file format wasm

Sections:
Idx Name          Size     VMA      Type
  0 TYPE          0000026f 00000000
  1 IMPORT        00001f03 00000000
  2 FUNCTION      0000043e 00000000
  3 TABLE         00000007 00000000
  4 MEMORY        00000007 00000000
  5 GLOBAL        00000021 00000000
  6 EXPORT        0000014a 00000000
  7 ELEM          00000457 00000000
  8 CODE          0009308a 00000000 TEXT
  9 DATA          0000e4cc 00000000 DATA
 10 name          00007e58 00000000
 11 .debug_info   000bb1c9 00000000
 12 .debug_loc    0009b407 00000000
 13 .debug_ranges 0000ad90 00000000
 14 .debug_abbrev 000136e8 00000000
 15 .debug_line   000bb3ab 00000000
 16 .debug_str    000209bd 00000000

यह आउटपुट हमें वे सभी सेक्शन दिखाता है जो जनरेट की गई Wasm फ़ाइल में हैं. इनमें से ज़्यादातर सेक्शन, स्टैंडर्ड WebAssembly सेक्शन हैं. हालांकि, ऐसे कई कस्टम सेक्शन भी हैं जिनका नाम .debug_ से शुरू होता है. वहीं, बाइनरी में हमारी डीबग की जानकारी होती है! अगर हम सभी साइज़ को जोड़ें, तो हम देखते हैं कि डीबग की जानकारी, हमारी 3 एमबी फ़ाइल का करीब 2.3 एमबी है. अगर हम emcc निर्देश को भी time करते हैं, तो हमें पता चलता है कि हमारी मशीन पर इसे चलने में करीब 1.5 सेकंड लगे. ये आंकड़े एक छोटी सी बेसलाइन तय करते हैं, लेकिन ये इतने छोटे होते हैं कि शायद कोई भी इन पर ध्यान न दे. हालांकि, असल ऐप्लिकेशन में डीबग बाइनरी आसानी से जीबी के साइज़ तक पहुंच सकती है और इसे बनने में कुछ मिनट लग सकते हैं!

स्किपिंग बाइनरीयन

Emscripten के साथ Wasm ऐप्लिकेशन बनाते समय, इसके आखिरी चरणों में से एक Binaryen ऑप्टिमाइज़र चल रहा है. बाइनरी एक कंपाइलर टूलकिट है. यह WebAssembly(-जैसे) की बाइनरी को ऑप्टिमाइज़ और कानूनी तौर पर तय, दोनों करता है. इस बिल्ड के हिस्से के तौर पर बाइनरीयन चलाना बहुत महंगा है, लेकिन इसकी ज़रूरत कुछ ही स्थितियों में होती है. अगर हमें बाइनरी पास की ज़रूरत नहीं पड़ती, तो डीबग के लिए बिल्ड बनाने में लगने वाले समय को हम कम कर सकते हैं. आम तौर पर, बाइनरीयन पास की ज़रूरत 64 बिट पूर्णांक वैल्यू वाले फ़ंक्शन हस्ताक्षर को कानूनी तौर पर तैयार करने के लिए होती है. -sWASM_BIGINT का इस्तेमाल करके, WebAssembly BigInt इंटिग्रेशन के लिए ऑप्ट इन करके, हमें इस समस्या से बचा जा सकता है.

$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

हमने अच्छी तरह से जांच करने के लिए -sERROR_ON_WASM_CHANGES_AFTER_LINK फ़्लैग को शामिल कर लिया है. इससे यह पता लगाने में मदद मिलती है कि कब बाइनरी चल रही है और अचानक से बाइनरी को फिर से लिख रही है. इस तरह, हम सुनिश्चित कर सकते हैं कि हम तेज़ रास्ते पर बने हुए हैं.

भले ही हमारा उदाहरण काफ़ी छोटा है, फिर भी हम देख सकते हैं कि बाइनरीयन को छोड़ने का क्या असर होता है! time के मुताबिक, यह निर्देश 1 सेकंड से कम समय में चलता है, इसलिए यह पहले से आधा सेकंड तेज़ है!

बेहतर ट्वीक

इनपुट फ़ाइल को स्कैन किया जा रहा है

आम तौर पर, Emscripten प्रोजेक्ट को लिंक करते समय, emcc सभी इनपुट ऑब्जेक्ट फ़ाइलों और लाइब्रेरी को स्कैन करता है. यह ऐसा इसलिए करता है, ताकि आपके प्रोग्राम में JavaScript लाइब्रेरी फ़ंक्शन और नेटिव सिंबल के बीच सटीक डिपेंडेंसी लागू की जा सके. बड़े प्रोजेक्ट के लिए llvm-nm का इस्तेमाल करके, इनपुट फ़ाइलों को एक साथ स्कैन करने से लिंक होने में लगने वाला समय बढ़ सकता है.

इसके बजाय, -sREVERSE_DEPS=all का इस्तेमाल किया जा सकता है, जो emcc को JavaScript फ़ंक्शन की सभी संभावित नेटिव डिपेंडेंसी शामिल करने के लिए कहता है. इस फ़ॉर्म का इस्तेमाल करने पर, कोड का साइज़ छोटा होता है. हालांकि, इससे लिंक का समय बढ़ सकता है. साथ ही, यह डीबग करने में मददगार हो सकता है.

उदाहरण के तौर पर दिए गए छोटे प्रोजेक्ट के लिए इससे कोई फ़र्क़ नहीं पड़ता. हालांकि, अगर आपके प्रोजेक्ट में सैकड़ों या हज़ारों ऑब्जेक्ट फ़ाइलें हैं, तो इससे लिंक होने के समय में काफ़ी सुधार हो सकता है.

“नाम” सेक्शन से अलग करना

बड़े प्रोजेक्ट में, खास तौर पर C++ टेंप्लेट का ज़्यादा इस्तेमाल करने वाले प्रोजेक्ट में, WebAssembly "नाम" सेक्शन बहुत बड़ा हो सकता है. हमारे उदाहरण में यह कुल फ़ाइल साइज़ का सिर्फ़ एक छोटा सा हिस्सा है (ऊपर llvm-objdump का आउटपुट देखें) लेकिन कुछ मामलों में यह बहुत अहम हो सकता है. अगर आपके ऐप्लिकेशन का “नाम” सेक्शन बहुत बड़ा है और डीबग करने की आपकी ज़रूरतों के लिए ड्वॉर्फ़ डीबग जानकारी काफ़ी है, तो “नाम” सेक्शन को हटाना फ़ायदेमंद हो सकता है:

$ emstrip --no-strip-all --remove-section=name mandelbrot.wasm

इससे, DWARF डीबग सेक्शन को सुरक्षित रखते हुए, WebAssembly का "नाम" सेक्शन हट जाएगा.

डीबग फ़िज़न

बहुत ज़्यादा डीबग डेटा वाली बाइनरी, सिर्फ़ बिल्ड टाइम पर ही नहीं, बल्कि डीबग करने में लगने वाले समय पर भी दबाव डालती हैं. डीबगर को डेटा लोड करना होगा और इसके लिए एक इंडेक्स बनाना होगा. ऐसा करने से, वह क्वेरी का तुरंत जवाब दे सकेगा, जैसे कि "लोकल वैरिएबल x किस तरह का है?".

डीबग फ़िज़न की मदद से, हम किसी बाइनरी के लिए डीबग जानकारी को दो हिस्सों में बांट सकते हैं: पहला, जो बाइनरी में रहता है और दूसरा, जो एक अलग, तथाकथित DWARF ऑब्जेक्ट (.dwo) फ़ाइल में होता है. इसे चालू करने के लिए, -gsplit-dwarf फ़्लैग को Emscripten पर पास करें:

$ emcc -sUSE_SDL=2 -g -gsplit-dwarf -gdwarf-5 -O0 -o mandelbrot.html mandelbrot.cc  -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

नीचे, हम अलग-अलग कमांड दिखाते हैं. साथ ही, यह भी बताते हैं कि किन फ़ाइलों को, बिना डीबग डेटा को कंपाइल करके, डीबग डेटा की मदद से, और आखिर में डीबग डेटा और डीबग फ़िज़न, दोनों से जनरेट किया जाता है.

जनरेट किए गए अलग-अलग कमांड और जनरेट की गई फ़ाइलें

DWARF डेटा को बांटते समय, डीबग डेटा का एक हिस्सा बाइनरी के साथ रहता है, जबकि बड़ा हिस्सा mandelbrot.dwo फ़ाइल में रखा जाता है (जैसा कि ऊपर बताया गया है).

mandelbrot के लिए हमारे पास सिर्फ़ एक सोर्स फ़ाइल है. हालांकि, आम तौर पर प्रोजेक्ट इससे बड़े होते हैं और उनमें एक से ज़्यादा फ़ाइलें शामिल होती हैं. डीबग फ़िज़न हर एक के लिए एक .dwo फ़ाइल जनरेट करता है. डीबगर के मौजूदा बीटा वर्शन (0.1.6.1615) के लिए, हमें उन सभी को एक तथाकथित DWARF पैकेज (.dwp) में इस तरह इकट्ठा करना होगा, ताकि अलग-अलग डीबग की इस जानकारी को लोड किया जा सके:

$ emdwp -e mandelbrot.wasm -o mandelbrot.dwp

dwo फ़ाइलों को DWARF पैकेज में बंडल करना

अलग-अलग ऑब्जेक्ट से DWARF पैकेज बनाने का एक फ़ायदा यह है कि आपको सिर्फ़ एक और फ़ाइल देने की ज़रूरत होती है! फ़िलहाल, हम आने वाली रिलीज़ में सभी ऑब्जेक्ट को लोड करने पर काम कर रहे हैं.

DWARF 5 में क्या है?

आपने देखा होगा कि हमने ऊपर दिए गए emcc कमांड, -gdwarf-5 में एक और फ़्लैग जोड़ दिया है. DWARF सिंबल के वर्शन 5 को चालू करने से, हमें तेज़ी से डीबग करने में मदद मिलती है. फ़िलहाल, यह सिंबल डिफ़ॉल्ट के तौर पर सेट नहीं है. इससे, कुछ जानकारी मुख्य बाइनरी में सेव हो जाती है, जो डिफ़ॉल्ट वर्शन 4 में उपलब्ध नहीं होती. खास तौर पर, हम सोर्स फ़ाइलों के पूरे सेट को सिर्फ़ मुख्य बाइनरी से तय कर सकते हैं. इससे डीबगर, पूरे सिंबल का डेटा लोड और पार्स किए बिना, पूरे सोर्स ट्री को दिखाने और ब्रेकपॉइंट सेट करने जैसी बुनियादी कार्रवाइयां कर सकता है. यह स्प्लिट सिंबल की मदद से डीबग करने की प्रोसेस को तेज़ बनाता है. इसलिए, हम हमेशा -gsplit-dwarf और -gdwarf-5 कमांड लाइन फ़्लैग का एक साथ इस्तेमाल करते हैं!

DWARF5 डीबग फ़ॉर्मैट से हमें काम की एक और सुविधा का ऐक्सेस भी मिलता है. यह डीबग डेटा में एक नाम इंडेक्स उपलब्ध कराता है, जो -gpubnames फ़्लैग को पास करने पर जनरेट होता है:

$ emcc -sUSE_SDL=2 -g -gdwarf-5 -gsplit-dwarf -gpubnames -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

डीबग करने के सेशन के दौरान, सिंबल लुकअप तब अक्सर होता है, जब किसी इकाई को उसके नाम से खोजा जाता है. उदाहरण के लिए, किसी वैरिएबल या टाइप को खोजते समय. नाम का इंडेक्स, खोज के इस नाम को तेज़ी से समझाने वाली कंपाइलेशन यूनिट पर ले जाता है. नाम का इंडेक्स न होने पर, पूरे डीबग डेटा को एक साथ खोजना होगा. इससे, इकट्ठा की गई उस इकाई के बारे में पता चलेगा जो नाम वाली उस इकाई के बारे में बताती है जिसे हम खोज रहे हैं.

जिज्ञासु लोगों के लिए: डीबग डेटा को देखना

DWARF डेटा की झलक देखने के लिए, llvm-dwarfdump का इस्तेमाल किया जा सकता है. आइए, इसे आज़माएं:

llvm-dwarfdump mandelbrot.wasm

इससे हमें उन “कंपाइल यूनिट” (आम तौर पर, सोर्स फ़ाइलें) के बारे में खास जानकारी मिलती है जिनके लिए हमारे पास डीबग करने की जानकारी है. इस उदाहरण में, हमारे पास सिर्फ़ mandelbrot.cc के लिए डीबग की जानकारी है. सामान्य जानकारी से हमें यह पता चलेगा कि हमारे पास एक स्केलेटन यूनिट है, जिसका मतलब है कि इस फ़ाइल में हमारे पास अधूरा डेटा है और एक अलग .dwo फ़ाइल है, जिसमें बाकी डीबग की जानकारी शामिल है:

mandelbrot.vasm और डीबग की जानकारी

आप इस फ़ाइल में मौजूद दूसरी टेबल भी देख सकते हैं, जैसे कि जो लाइन टेबल में, Wasm बाइट कोड को C++ लाइनों पर मैप करती है (llvm-dwarfdump -debug-line का इस्तेमाल करके देखें).

हम अलग .dwo फ़ाइल में मौजूद डीबग की जानकारी पर भी नज़र डाल सकते हैं:

llvm-dwarfdump mandelbrot.dwo

mandelbrot.vasm और डीबग की जानकारी

कम शब्दों में कहें: डीबग फ़िज़न इस्तेमाल करने का क्या फ़ायदा है?

अगर कोई ऐप्लिकेशन बड़े ऐप्लिकेशन के साथ काम कर रहा है, तो डीबग की जानकारी को अलग-अलग करने के कई फ़ायदे हैं:

  1. ज़्यादा तेज़ी से लिंक करना: लिंकर को अब डीबग की पूरी जानकारी पार्स करने की ज़रूरत नहीं है. आम तौर पर, लिंकर को बाइनरी में मौजूद पूरे DWARF डेटा को पार्स करना होता है. डीबग करने की जानकारी के बड़े हिस्से को अलग-अलग फ़ाइलों में बांटकर, लिंकर छोटी बाइनरी से निपटते हैं. इस वजह से, लिंक करने में कम समय लगता है (खास तौर पर बड़े ऐप्लिकेशन के लिए).

  2. ज़्यादा तेज़ी से डीबग करने की सुविधा: डीबगर, कुछ सिंबल को खोजने के लिए .dwo/.dwp फ़ाइलों में मौजूद दूसरे सिंबल को पार्स नहीं कर सकता. कुछ लुकअप के लिए (जैसे कि Wasm-to-C++ फ़ाइलों की लाइन मैपिंग पर किए गए अनुरोध), हमें अतिरिक्त डीबग डेटा देखने की ज़रूरत नहीं होती. इससे हमारा समय बचता है. हमें अतिरिक्त डीबग डेटा को लोड और पार्स करने की ज़रूरत नहीं होती.

1: अगर आपके सिस्टम पर llvm-objdump का नया वर्शन मौजूद नहीं है और emsdk का इस्तेमाल किया जा रहा है, तो वह आपको emsdk/upstream/bin डायरेक्ट्री में मिल जाएगा.

झलक दिखाने वाले चैनलों को डाउनलोड करें

Chrome Canary, Dev या बीटा को अपने डिफ़ॉल्ट डेवलपमेंट ब्राउज़र के तौर पर इस्तेमाल करें. झलक दिखाने वाले इन चैनलों की मदद से, DevTools की नई सुविधाओं को ऐक्सेस किया जा सकता है और वेब प्लैटफ़ॉर्म के बेहतरीन एपीआई की जांच की जा सकती है. साथ ही, उपयोगकर्ताओं से पहले ही अपनी साइट की समस्याओं का पता लगाया जा सकता है!

Chrome DevTools की टीम से संपर्क करना

पोस्ट में नई सुविधाओं और बदलावों या DevTools से जुड़ी किसी भी अन्य चीज़ के बारे में चर्चा करने के लिए, नीचे दिए गए विकल्पों का इस्तेमाल करें.

  • crbug.com के ज़रिए हमें कोई सुझाव या फ़ीडबैक सबमिट करें.
  • ज़्यादा विकल्प   ज़्यादा दिखाएँ > का इस्तेमाल करके DevTools से जुड़ी समस्या की शिकायत करें सहायता > DevTools में DevTools से जुड़ी समस्याओं की शिकायत करें.
  • @ChromeDevTools पर ट्वीट करें.
  • DevTools YouTube वीडियो या DevTools के बारे में सलाह YouTube वीडियो में नया क्या है, इस पर टिप्पणी करें.