रस्ट डिबगर हुड के नीचे कैसे काम करता है: चरण-दर-चरण विश्लेषण

शुरुआती लोगों के लिए ऑनलाइन Python प्रशिक्षक

Python आसानी से सीखें बिना सिद्धांत के बोझ के। स्वचालित जांच के साथ व्यावहारिक कार्य हल करें और ब्राउज़र में सीधे कोड लिखें।

पाठ्यक्रम शुरू करें

रस्ट डिबगर: step by step के हुड के नीचे कैसे झाँकें

\n\n

क्या आपने कभी सोचा है: «Step by step interactive debugger for Rust: यह हुड के नीचे कैसे काम करता है?» यदि आपने Rust में लिखा है और किसी मुश्किल बग को पकड़ने की कोशिश की है, तो आपने निश्चित रूप से GDB, LLDB या rust-gdb जैसे टूल का सामना किया होगा। लेकिन वे प्रोग्राम के निष्पादन को कैसे रोकते हैं, वेरिएबल के मान कैसे दिखाते हैं और सोर्स कोड पर कदम कैसे बढ़ाते हैं?

\n\n

इस लेख में, हम केवल यह नहीं बताएंगे कि डिबगर का उपयोग कैसे करें। हम अंदर झाँकेंगे: मशीन कोड से लेकर ब्रेकपॉइंट तक, स्टैक फ्रेम से लेकर डिबग सिंबल तक। यदि आप न केवल क्या करना है, बल्कि क्यों यह काम करता है, इसमें रुचि रखते हैं — तो आप सही जगह पर हैं।

\n\n

डिबगिंग के लिए Rust एक विशेष मामला क्यों है?

\n\n

Rust LLVM के माध्यम से नेटिव कोड में कंपाइल होता है। इसका मतलब है कि डिबगिंग के लिए हम वही टूल का उपयोग करते हैं जो C/C++ के लिए करते हैं: GDB और LLDB। लेकिन Rust अपनी जटिलताएँ जोड़ता है:

\n\n
    \n
  • स्वामित्व और उधार — वेरिएबल को स्थानांतरित किया जा सकता है, और डिबगर को इस पर नज़र रखनी चाहिए।
  • \n
  • जेनेरिक का मोनोमॉर्फ़िज़ेशन — प्रत्येक T एक विशिष्ट प्रकार में बदल जाता है, और फ़ंक्शन नाम «एन्क्रिप्टेड» हो जाते हैं।
  • \n
  • ऑप्टिमाइज़ेशन — कंपाइलर कोड को पुनर्व्यवस्थित कर सकता है, और डिबगर को इसे वापस «अनरोल» करना होगा।
  • \n
\n\n

यही कारण है कि step by step interactive debugger for Rust केवल GDB के तहत प्रोग्राम चलाना नहीं है। यह एक पूरी प्रणाली है जो मशीन पतों को वापस सोर्स कोड की पंक्तियों में अनुवादित करती है।

\n\n

ब्रेकपॉइंट हुड के नीचे कैसे काम करता है?

\n\n

कल्पना करें: आप println!("Hello"); पंक्ति पर एक ब्रेकपॉइंट सेट करते हैं। प्रोसेसर में क्या होता है?

\n\n

जब आप डिबगर के तहत प्रोग्राम चलाते हैं, तो यह निम्नलिखित करता है:

\n\n
    \n
  1. डिबग जानकारी पढ़ता है (DWARF प्रारूप) निष्पादन योग्य फ़ाइल से। वहाँ पत्राचार संग्रहीत होता है: मेमोरी में पता → कोड में पंक्ति।
  2. \n
  3. निर्देश को बदलता है उस पते पर एक विशेष कमांड INT3 (x86 पर — 0xCC) से। यह प्रोसेसर के लिए एक «जाल» है।
  4. \n
  5. जब निष्पादन इस बिंदु पर पहुँचता है, तो प्रोसेसर सिग्नल SIGTRAP उत्पन्न करता है।
  6. \n
  7. डिबगर सिग्नल को इंटरसेप्ट करता है, रजिस्टरों की स्थिति को सहेजता है और आपको वर्तमान पंक्ति दिखाता है।
  8. \n
\n\n

यहाँ Rust में एक सरल प्रोग्राम का उदाहरण है:

\n\n
fn main() {\n    let x = 42;\n    let y = x + 10; // <-- ब्रेकपॉइंट\n    println!("y = {}", y);\n}
\n\n

जब आप let y = x + 10; पर ब्रेकपॉइंट सेट करते हैं, तो डिबगर बाइनरी में इस निर्देश का पता ढूंढता है और पहले बाइट को 0xCC से बदल देता है। रुकने के बाद, यह मूल बाइट को पुनर्स्थापित करता है, एक कदम निष्पादित करता है, और फिर से जाल सेट करता है — यदि आप जारी रखना चाहते हैं।

\n\n

Step over, step into, step out — डिबगर कैसे जानता है कि «अगली पंक्ति» कहाँ है?

\n\n

यह शायद सबसे जादुई हिस्सा है। डिबगर कैसे जानता है कि let y = x + 10; के बाद println! आता है?

\n\n

यह सब डिबग टेबल के बारे में है। Rust कंपाइलर (LLVM के माध्यम से) DWARF प्रारूप में एक .debug_line सेक्शन उत्पन्न करता है। यह एक संपीड़ित तालिका है जो निर्देश पतों को सोर्स कोड में पंक्ति संख्याओं से मैप करती है।

\n\n

उदाहरण के लिए, हमारे कोड के लिए तालिका इस तरह दिख सकती है:

\n\n
पता 0x1000 → पंक्ति 2 (let x = 42;)\nपता 0x1010 → पंक्ति 3 (let y = x + 10;)\nपता 0x1020 → पंक्ति 4 (println!)
\n\n

जब आप step over दबाते हैं, तो डिबगर अगले निर्देश के पते की गणना करता है जो वर्तमान पंक्ति से संबंधित नहीं है, और वहाँ एक अस्थायी ब्रेकपॉइंट सेट करता है। निष्पादन इस पते तक जारी रहता है — और आप अगली पंक्ति पर चले जाते हैं।

\n\n

Step into के लिए — डिबगर जाँचता है कि क्या वर्तमान पते पर कोई फ़ंक्शन कॉल है। यदि हाँ, तो यह उस फ़ंक्शन की पहली पंक्ति की तलाश करता है।

ब्लॉग

पुस्तक अनुशंसाएँ