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

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

Chrome Dev Summit 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 के कैनरी, डेव या बीटा वर्शन का इस्तेमाल करें. झलक दिखाने वाले इन चैनलों से, आपको DevTools की नई सुविधाओं का ऐक्सेस मिलता है. साथ ही, सबसे नए वेब प्लैटफ़ॉर्म एपीआई टेस्ट करने और उपयोगकर्ताओं के ऐसा करने से पहले ही, अपनी साइट पर समस्याओं का पता लगाने में मदद मिलती है!

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

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

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