अब तक की राह
एक साल पहले, Chrome ने Chrome DevTools में नेटिव WebAssembly की डीबगिंग के लिए, शुरुआती सहायता का एलान किया था.
हमने बुनियादी चरणों की सहायता के बारे में बताया. साथ ही, आने वाले समय में सोर्स मैप के बजाय 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
अब जनरेट किए गए पेज को localhost एचटीटीपी सर्वर से दिखाया जा सकता है. उदाहरण के लिए, serve की मदद से. साथ ही, इसे Chrome Canary के नए वर्शन में खोला जा सकता है.
इस बार, हमें एक हेल्पर एक्सटेंशन की भी ज़रूरत होगी, जो Chrome DevTools के साथ इंटिग्रेट होता है. साथ ही, WebAssembly फ़ाइल में एन्कोड की गई, डीबग करने से जुड़ी सारी जानकारी को समझने में मदद करता है. कृपया इस लिंक पर जाकर इसे इंस्टॉल करें: goo.gle/wasm-debugging-extension
आपको DevTools के एक्सपेरिमेंट में, WebAssembly को डीबग करने की सुविधा भी चालू करनी होगी. Chrome DevTools खोलें. इसके बाद, DevTools पैनल के सबसे ऊपर दाएं कोने में मौजूद गियर (⚙) आइकॉन पर क्लिक करें. इसके बाद, प्रयोग पैनल पर जाएं और WebAssembly डीबगिंग: DWARF सपोर्ट चालू करें पर सही का निशान लगाएं.
सेटिंग बंद करने पर, DevTools सेटिंग लागू करने के लिए, खुद को फिर से लोड करने का सुझाव देगा. इसलिए, हम ऐसा ही करेंगे. एक बार के लिए सेटअप करने के लिए बस इतना ही.
अब हम सोर्स पैनल पर वापस जा सकते हैं और अपवाद मिलने पर रोकें (⏸ आइकॉन) को चालू कर सकते हैं. इसके बाद, पहचाने गए अपवाद मिलने पर रोकें को चुनें और पेज को फिर से लोड करें. आपको DevTools को किसी अपवाद पर रोका हुआ दिखेगा:
डिफ़ॉल्ट रूप से, यह Emscripten से जनरेट किए गए ग्लू कोड पर रुक जाता है. हालांकि, दाईं ओर आपको कॉल स्टैक व्यू दिख सकता है, जो गड़बड़ी के स्टैक ट्रेस को दिखाता है. साथ ही, abort
को कॉल करने वाली ओरिजनल C लाइन पर भी जाया जा सकता है:
अब, स्कोप व्यू में जाकर, 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 लाइनें हैं. हालांकि, इस बार मैंने कुछ बाहरी एपीआई का भी इस्तेमाल किया है. जैसे, ग्राफ़िक्स के लिए SDL लाइब्रेरी और C++ स्टैंडर्ड लाइब्रेरी से कॉम्प्लेक्स नंबर.
मैं इसे ऊपर दिए गए -g
फ़्लैग के साथ ही कंपाइल करूंगा, ताकि डीबग करने से जुड़ी जानकारी शामिल की जा सके. साथ ही, मैं Emscripten से SDL2 लाइब्रेरी उपलब्ध कराने और मनमुताबिक साइज़ की मेमोरी इस्तेमाल करने की अनुमति देने के लिए कहूंगा:
emcc -g mandelbrot.cc -o mandelbrot.html \ -s USE_SDL=2 \ -s ALLOW_MEMORY_GROWTH=1
ब्राउज़र में जनरेट किए गए पेज पर जाने पर, मुझे कुछ रंगों के साथ खूबसूरत फ़्रैक्टल आकार दिखता है:
DevTools खोलने पर, मुझे फिर से ओरिजनल C++ फ़ाइल दिखती है. हालांकि, इस बार कोड में कोई गड़बड़ी नहीं है (वाह!), इसलिए अपने कोड की शुरुआत में कुछ ब्रेकपॉइंट सेट करें.
जब हम पेज को फिर से लोड करेंगे, तो डीबगर हमारे C++ सोर्स में ही रुक जाएगा:
हमें दाईं ओर अपने सभी वैरिएबल पहले से दिख रहे हैं. हालांकि, फ़िलहाल सिर्फ़ width
और height
को शुरू किया गया है. इसलिए, जांचने के लिए ज़्यादा कुछ नहीं है.
आइए, अपने मुख्य मैंडलब्रॉट लूप में एक और ब्रेकपॉइंट सेट करें और थोड़ा आगे जाने के लिए, प्रोग्राम को फिर से शुरू करें.
इस समय, हमारे palette
में कुछ रंग भरे गए हैं. साथ ही, हम ऐरे और अलग-अलग SDL_Color
स्ट्रक्चर, दोनों को बड़ा कर सकते हैं. साथ ही, उनके कॉम्पोनेंट की जांच करके यह पुष्टि कर सकते हैं कि सब कुछ ठीक है या नहीं. उदाहरण के लिए, "अल्फा" चैनल हमेशा पूरी ओपैसिटी पर सेट है. इसी तरह, center
वैरिएबल में सेव की गई कॉम्प्लेक्स संख्या के रीयल और इमेजरी पार्ट को बड़ा करके देखा जा सकता है.
अगर आपको किसी ऐसी प्रॉपर्टी को ऐक्सेस करना है जो स्कोप व्यू से ऐक्सेस करना मुश्किल है, तो कंसोल के आकलन का इस्तेमाल किया जा सकता है! हालांकि, ध्यान दें कि फ़िलहाल C++ के ज़्यादा जटिल एक्सप्रेशन काम नहीं करते.
आइए, कुछ समय के लिए फ़ंक्शन को फिर से शुरू करें. इससे हमें यह पता चलेगा कि अंदर मौजूद x
भी कैसे बदल रहा है. इसके लिए, स्कोप व्यू में फिर से देखें, वॉच लिस्ट में वैरिएबल का नाम जोड़ें, कंसोल में उसका आकलन करें या सोर्स कोड में वैरिएबल पर कर्सर घुमाएं:
यहां से, हम C++ स्टेटमेंट में स्टप-इन या स्टप-ओवर कर सकते हैं. साथ ही, यह भी देख सकते हैं कि अन्य वैरिएबल कैसे बदल रहे हैं:
ठीक है, डीबग करने की जानकारी उपलब्ध होने पर यह तरीका बहुत अच्छा काम करता है. हालांकि, अगर हमें ऐसे कोड को डीबग करना है जिसे डीबग करने के विकल्पों के साथ नहीं बनाया गया है, तो क्या होगा?
रॉ WebAssembly डीबगिंग
उदाहरण के लिए, हमने Emscripten से कहा है कि वह हमें पहले से बनी SDL लाइब्रेरी उपलब्ध कराए. हम खुद सोर्स से इसे कंपाइल नहीं करना चाहते. इसलिए, कम से कम फ़िलहाल, डीबगर के पास इससे जुड़े सोर्स ढूंढने का कोई तरीका नहीं है.
SDL_RenderDrawColor
में जाने के लिए, फिर से शुरू करें:
हम WebAssembly को डीबग करने के पुराने तरीके पर वापस आ गए हैं.
अब, यह थोड़ा डरावना लग रहा है और ज़्यादातर वेब डेवलपर को कभी भी इससे निपटने की ज़रूरत नहीं पड़ेगी. हालांकि, कभी-कभी आपको बिना डीबग जानकारी वाली लाइब्रेरी को डीबग करना पड़ सकता है. ऐसा इसलिए, क्योंकि यह 3rd-पार्टी लाइब्रेरी है और आपके पास इसका कंट्रोल नहीं है या आपको उनमें से किसी बग का सामना करना पड़ रहा है जो सिर्फ़ प्रोडक्शन में होता है.
ऐसे मामलों में मदद करने के लिए, हमने डिबग करने के बुनियादी तरीके में भी कुछ सुधार किए हैं.
सबसे पहले, अगर आपने पहले कभी रॉ WebAssembly डीबगिंग का इस्तेमाल किया है, तो आपको पता चल सकता है कि अब पूरी डिसअसेम्बली एक ही फ़ाइल में दिखती है. अब यह अनुमान लगाने की ज़रूरत नहीं है कि सोर्स एंट्री wasm-53834e3e/
wasm-53834e3e-7
किस फ़ंक्शन से जुड़ी है.
नाम जनरेट करने की नई योजना
हमने डिसएसेम्बली व्यू में भी नामों को बेहतर बनाया है. पहले आपको सिर्फ़ संख्या वाले इंडेक्स दिखते थे या फ़ंक्शन के मामले में, कोई नाम नहीं दिखता था.
अब हम डिसअसेम्बल करने वाले अन्य टूल की तरह ही नाम जनरेट कर रहे हैं. इसके लिए, हम WebAssembly के नाम वाले सेक्शन और इंपोर्ट/एक्सपोर्ट पाथ से मिले हिंट का इस्तेमाल करते हैं. अगर इनसे भी नाम जनरेट नहीं हो पाता है, तो हम $func123
जैसे आइटम के टाइप और इंडेक्स के आधार पर नाम जनरेट करते हैं. ऊपर दिए गए स्क्रीनशॉट में देखा जा सकता है कि इससे पहले से ही, थोड़ा बेहतर स्टैक ट्रेस और डिसअसेम्बली पाने में मदद मिलती है.
जब टाइप की जानकारी उपलब्ध नहीं होती है, तो प्राइमिटिव के अलावा किसी भी वैल्यू की जांच करना मुश्किल हो सकता है. उदाहरण के लिए, पॉइंटर सामान्य पूर्णांक के तौर पर दिखेंगे. साथ ही, यह पता नहीं चलेगा कि मेमोरी में उनके पीछे क्या स्टोर किया गया है.
मेमोरी की जांच
पहले, अलग-अलग बाइट देखने के लिए, सिर्फ़ WebAssembly मेमोरी ऑब्जेक्ट को बड़ा किया जा सकता था. यह ऑब्जेक्ट, स्कोप व्यू में env.memory
से दिखाया जाता है. यह कुछ सामान्य स्थितियों में काम करता था, लेकिन इसे बड़ा करना खास तौर पर आसान नहीं था. साथ ही, इससे बाइट वैल्यू के अलावा किसी दूसरे फ़ॉर्मैट में डेटा को फिर से समझने की अनुमति नहीं मिलती थी. हमने इस काम में आपकी मदद करने के लिए, एक नई सुविधा भी जोड़ी है: लीनियर मेमोरी इंस्पेक्टर.
env.memory
पर राइट क्लिक करने पर, आपको मेमोरी की जांच करें नाम का एक नया विकल्प दिखेगा:
क्लिक करने के बाद, आपको एक मेमोरी इंस्पेक्टर दिखेगा. इसमें, हेक्साडेसिमल और ASCII व्यू में WebAssembly मेमोरी की जांच की जा सकती है. साथ ही, खास पतों पर नेविगेट किया जा सकता है और डेटा को अलग-अलग फ़ॉर्मैट में देखा जा सकता है:
बेहतर स्थितियां और सावधानियां
WebAssembly कोड की प्रोफ़ाइलिंग करना
DevTools खोलने पर, WebAssembly कोड को डीबग करने की सुविधा चालू करने के लिए, "कम परफ़ॉर्म करने वाले वर्शन" में "कम किया जाता है". यह वर्शन काफ़ी धीमा है. इसका मतलब है कि DevTools खुले होने पर, console.time
, performance.now
, और कोड की स्पीड को मेज़र करने के अन्य तरीकों पर भरोसा नहीं किया जा सकता. ऐसा इसलिए, क्योंकि आपको जो संख्याएं मिलेंगी वे असल परफ़ॉर्मेंस को बिलकुल नहीं दिखाएंगी.
इसके बजाय, आपको DevTools के परफ़ॉर्मेंस पैनल का इस्तेमाल करना चाहिए. इससे कोड पूरी स्पीड से चलेगा और आपको अलग-अलग फ़ंक्शन में बिताए गए समय की पूरी जानकारी मिलेगी:
इसके अलावा, DevTools बंद करके भी ऐप्लिकेशन को चलाया जा सकता है. इसके बाद, कंसोल की जांच करने के लिए, DevTools खोलें.
हम आने वाले समय में, प्रोफ़ाइल बनाने की सुविधा को बेहतर बनाएंगे. हालांकि, फ़िलहाल इस बात का ध्यान रखें. अगर आपको वेब असेंबली के टीयर करने के उदाहरणों के बारे में ज़्यादा जानना है, तो वेब असेंबली कंपाइलेशन पाइपलाइन के बारे में हमारे दस्तावेज़ देखें.
अलग-अलग मशीनों (Docker / होस्ट सहित) पर बिल्ड करना और डीबग करना
Docker, वर्चुअल मशीन या रिमोट बिल्ड सर्वर में बिल्ड करते समय, आपको ऐसी स्थितियों का सामना करना पड़ सकता है जहां बिल्ड के दौरान इस्तेमाल की गई सोर्स फ़ाइलों के पाथ, आपके फ़ाइल सिस्टम के उन पाथ से मेल नहीं खाते जहां Chrome DevTools चल रहे हैं. इस मामले में, फ़ाइलें सोर्स पैनल में दिखेंगी, लेकिन लोड नहीं होंगी.
इस समस्या को ठीक करने के लिए, हमने C/C++ एक्सटेंशन के विकल्पों में पाथ मैपिंग की सुविधा लागू की है. इसका इस्तेमाल, मनमुताबिक पाथ को फिर से मैप करने और DevTools को सोर्स ढूंढने में मदद करने के लिए किया जा सकता है.
उदाहरण के लिए, अगर आपकी होस्ट मशीन पर प्रोजेक्ट, पाथ C:\src\my_project
में है, लेकिन उसे Docker कंटेनर में बनाया गया था, जहां उस पाथ को /mnt/c/src/my_project
के तौर पर दिखाया गया था, तो डीबग करने के दौरान उसे फिर से मैप किया जा सकता है. इसके लिए, उन पाथ को प्रीफ़िक्स के तौर पर बताएं:
मैच होने वाला पहला प्रीफ़िक्स "जीतता है". अगर आपको C++ के अन्य डीबगर के बारे में पता है, तो यह विकल्प GDB में set substitute-path
कमांड या LLDB में target.source-map
सेटिंग जैसा ही है.
ऑप्टिमाइज़ किए गए बिल्ड को डीबग करना
अन्य भाषाओं की तरह ही, डीबग करने की सुविधा सबसे अच्छी तरह तब काम करती है, जब ऑप्टिमाइज़ेशन बंद हों. ऑप्टिमाइज़ेशन की वजह से, फ़ंक्शन एक-दूसरे में इनलाइन हो सकते हैं, कोड का क्रम बदल सकता है या कोड के कुछ हिस्से हट सकते हैं. इन सभी वजहों से, डीबगर और उपयोगकर्ता को भ्रम हो सकता है.
अगर आपको डीबग करने के सीमित अनुभव से कोई फ़र्क़ नहीं पड़ता और आपको ऑप्टिमाइज़ किए गए बिल्ड को डीबग करना है, तो फ़ंक्शन इनलाइन करने के अलावा, ज़्यादातर ऑप्टिमाइज़ेशन उम्मीद के मुताबिक काम करेंगे. हम आने वाले समय में बाकी समस्याओं को हल करेंगे.हालांकि, फ़िलहाल -O
लेवल के ऑप्टिमाइज़ेशन के साथ कंपाइल करते समय, इसे बंद करने के लिए -fno-inline
का इस्तेमाल करें. उदाहरण के लिए:
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 खोलने पर, हेल्पर एक्सटेंशन उसे ढूंढकर लोड कर पाएगा.
ऊपर बताए गए ऑप्टिमाइज़ेशन के साथ इस सुविधा का इस्तेमाल, अपने ऐप्लिकेशन के ऑप्टिमाइज़ किए गए प्रोडक्शन बिल्ड को शिप करने के लिए भी किया जा सकता है. इसके बाद, स्थानीय साइड फ़ाइल की मदद से उन्हें डीबग किया जा सकता है. इस मामले में, हमें एक्सटेंशन को साइड फ़ाइल ढूंढने में मदद करने के लिए, सेव किए गए यूआरएल को बदलना होगा. उदाहरण के लिए:
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 न सिर्फ़ JavaScript के लिए, बल्कि C और C++ ऐप्लिकेशन के लिए भी एक बेहतर और बेहतरीन डीबगर बन जाता है. इससे, अलग-अलग टेक्नोलॉजी में बनाए गए ऐप्लिकेशन को, शेयर किए जा सकने वाले और अलग-अलग प्लैटफ़ॉर्म पर काम करने वाले वेब पर लाने में पहले से ज़्यादा आसानी होती है.
हालांकि, हमारी यात्रा अभी पूरी नहीं हुई है. हम आगे इन चीज़ों पर काम करेंगे:
- डीबग करने की प्रोसेस को आसान बनाना.
- कस्टम टाइप फ़ॉर्मैटर के लिए सहायता जोड़ी गई है.
- WebAssembly ऐप्लिकेशन के लिए, प्रोफ़ाइल बनाने की सुविधा को बेहतर बनाने पर काम किया जा रहा है.
- कोड कवरेज के लिए सहायता जोड़ी गई है, ताकि इस्तेमाल न किए गए कोड को आसानी से ढूंढा जा सके.
- कंसोल के आकलन में एक्सप्रेशन के लिए सहायता को बेहतर बनाना.
- ज़्यादा भाषाओं के लिए सहायता जोड़ी जा रही है.
- …और ऐसे ही अन्य ट्रेंड!
इस बीच, अपने कोड पर मौजूदा बीटा वर्शन आज़माकर, हमें मदद करें. साथ ही, अगर आपको कोई समस्या मिलती है, तो https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350 पर जाकर हमें इसकी शिकायत करें.
झलक वाले चैनल डाउनलोड करना
Chrome कैनरी, डेवलपर या बीटा को अपने डिफ़ॉल्ट डेवलपमेंट ब्राउज़र के तौर पर इस्तेमाल करें. इन झलक वाले चैनलों की मदद से, आपको DevTools की नई सुविधाओं का ऐक्सेस मिलता है. साथ ही, इनसे आपको वेब प्लैटफ़ॉर्म के सबसे नए एपीआई की जांच करने में मदद मिलती है. इसके अलावा, इनकी मदद से उपयोगकर्ताओं से पहले ही अपनी साइट पर समस्याओं का पता लगाया जा सकता है!
Chrome DevTools की टीम से संपर्क करना
DevTools से जुड़ी नई सुविधाओं, अपडेट या किसी भी अन्य चीज़ के बारे में चर्चा करने के लिए, यहां दिए गए विकल्पों का इस्तेमाल करें.
- crbug.com पर जाकर, हमें सुझाव/राय दें या शिकायत करें. साथ ही, किसी सुविधा का अनुरोध करें.
- DevTools में ज़्यादा विकल्प > सहायता > DevTools से जुड़ी समस्या की शिकायत करें का इस्तेमाल करके, DevTools से जुड़ी समस्या की शिकायत करें.
- @ChromeDevTools पर ट्वीट करें.
- DevTools के YouTube वीडियो में नया क्या है या DevTools के बारे में सलाह देने वाले YouTube वीडियो पर टिप्पणियां करें.