रस्ट में घबराहट कैसे काम करती है

कैसे जंग आतंक काम करता है


जब आप panic!() कहते हैं तो वास्तव में क्या होता है panic!()
हाल ही में, मैंने इस से जुड़े मानक पुस्तकालय के हिस्सों का अध्ययन करने में बहुत समय बिताया और यह पता चला कि उत्तर बल्कि जटिल है!


रस्ट में घबराहट की सामान्य तस्वीर को समझाते हुए मुझे दस्तावेज़ नहीं मिल पाए, इसलिए यह लिखने लायक है।


(बेशर्म लेख: इस विषय में मेरी दिलचस्पी का कारण यह है कि @Aaron1011 ने @Aaron1011 स्टैक @Aaron1011 लिए समर्थन लागू किया है।


मैं इसे पुराने समय से मिरी में देखना चाहता था, और मेरे पास इसे लागू करने के लिए कभी समय नहीं था, इसलिए यह देखना वास्तव में बहुत अच्छा था कि कोई व्यक्ति PR को नीले रंग से बाहर भेजने के लिए कैसे भेजता है।


कोड की जाँच के कई दौरों के बाद, इसे हाल ही में इंजेक्ट किया गया है।


अभी भी कुछ खुरदरे किनारे हैं , लेकिन मूल बातें अच्छी तरह से परिभाषित हैं।)


इस लेख का उद्देश्य उच्च स्तर की संरचना और संबंधित इंटरफेस का दस्तावेजीकरण करना है जो जंग की तरफ से खेलते हैं।


वास्तविक स्टैक अनइंडिंग तंत्र एक पूरी तरह से अलग मुद्दा है (जिसमें से मैं बोलने के लिए अधिकृत नहीं हूं)।


नोट: इस लेख में इस वचन से घबराहट का वर्णन किया गया है।


यहाँ वर्णित कई इंटरफेस libstd के अस्थिर इंटर्नल हैं और किसी भी समय बदल सकते हैं।


उच्च स्तरीय संरचना


यह समझने की कोशिश करना कि लिबस्टर्ड में कोड पढ़ते समय घबराहट कैसे काम करती है, आप आसानी से भूलभुलैया में खो सकते हैं।
अप्रत्यक्ष के कई स्तर हैं जो केवल लिंकर द्वारा जुड़े हुए हैं,
वहाँ #[panic_handler] और एक "रनटाइम पैनिक हैंडलर" ( -C panic माध्यम से सेट की गई पैनिक रणनीति द्वारा नियंत्रित) और "पैनिक ट्रैप्स" , और यह पता चला है कि #[no_std] के संदर्भ में #[no_std] को पूरी तरह से अलग कोड पाथ की आवश्यकता है ... बहुत बहुत कुछ चल रहा है।


इससे भी बदतर, RFC ने पैनिक ट्रैप्स का वर्णन करते हुए उन्हें "पैनिक हैंडलर" कहा, लेकिन इस शब्द को फिर से परिभाषित किया गया है।


मुझे लगता है कि शुरू करने के लिए सबसे अच्छी जगह दो दिशाओं को नियंत्रित करने वाले इंटरफेस के साथ है:


  • रनटाइम पैनिक हैंडलर का उपयोग libstd द्वारा किया जाता है ताकि यह नियंत्रित किया जा सके कि स्ट्रैडर को पैनिक जानकारी प्रिंट करने के बाद क्या होता है।
    यह आतंक की रणनीति से निर्धारित होता है: या तो हम ( -C panic=abort ) को बाधित करते हैं या स्टैक की अनिच्छा शुरू करते हैं ( -C panic=unwind )।
    (रन-टाइम पैनिक को catch_unwind करना भी catch_unwind लिए एक कार्यान्वयन प्रदान करता है, लेकिन हम यहां इसके बारे में बात नहीं करेंगे।)


  • पैनिक हैंडलर का उपयोग libcore द्वारा लागू किया जाता है (a) कोड जनरेशन द्वारा डाली गई घबराहट (जैसे कि अंकगणितीय अतिप्रवाह या सीमाओं के बाहर सरणी / स्लाइस इंडेक्सिंग) और (b) core::panic! मैक्रो (यह एक panic! है; लिबकोर में मैक्रो और #[no_std] संदर्भ #[no_std] ) में।



इन दोनों इंटरफेसों को extern ब्लॉक के माध्यम से कार्यान्वित किया जाता है: क्रमशः लिस्ट / लिबकोर, बस कुछ फंक्शन को इम्पोर्ट करते हैं, जिसे वे क्रेट ट्री में रखते हैं और कहीं और इस फंक्शन को लागू किया जाता है।


केवल बंधन के दौरान आयात की अनुमति है; कोड को स्थानीय रूप से देखते हुए, कोई यह नहीं कह सकता है कि संबंधित इंटरफ़ेस का वास्तविक कार्यान्वयन कहां है।
यह आश्चर्य की बात नहीं है कि मैं रास्ते में कई बार खो गया था।


भविष्य में, ये दोनों इंटरफेस बहुत उपयोगी होंगे; जब आप गड़बड़ करते हैं। जांच करने वाली पहली बात यह है कि क्या आपने पैनिक हैंडलर और रन-टाइम पैनिक हैंडलर को भ्रमित किया है
(और याद रखें कि आतंक अवरोधक भी हैं, हम उन्हें प्राप्त करेंगे।)
यह हर समय मेरे साथ होता है।


इसके अलावा, core::panic! और std::panic! समान नहीं ; जैसा कि हम देखेंगे, वे पूरी तरह से अलग कोड पथ का उपयोग करते हैं।


libcore और libstd प्रत्येक एक आतंक पैदा करने के अपने तरीके को लागू करते हैं:


  • core::panic! कामचोर बहुत छोटा है: यह सिर्फ तुरंत हैंडलर के आतंक को दर्शाता है।


  • libstd std::panic! ("सामान्य" panic! इन रस्ट) एक पूरी तरह से कार्यात्मक आतंक इंजन लॉन्च करता है जो उपयोगकर्ता द्वारा नियंत्रित आतंक अवरोधन प्रदान करता है।
    डिफ़ॉल्ट हुक stderr में एक पैनिक संदेश प्रदर्शित करेगा।
    इंटरसेप्शन फ़ंक्शन पूरा होने के बाद, libstd इसे रन-टाइम पैनिक हैंडलर को सौंपता है।


    libstd एक पैनिक हैंडलर भी प्रदान करता है जो एक ही मैकेनिज्म को कॉल करता है, इसलिए core::panic! यहाँ भी समाप्त होता है।



आइए अब हम इन भागों को अधिक विस्तार से देखें।


कार्यक्रम के निष्पादन के दौरान आतंक से निपटने


पैनिक रनटाइम ( इस RFC द्वारा दर्शाया गया) के लिए __rust_start_panic(payload: usize) -> u32 जो लिबस्टर्ड द्वारा आयात किया जाता है और बाद में लिंकर द्वारा हल किया जाता है।


यहाँ तर्क तर्क वास्तव में *mut &mut dyn core::panic::BoxMeUp usize और usize *mut &mut dyn core::panic::BoxMeUp - यह वह जगह है जहां *mut &mut dyn core::panic::BoxMeUp *mut &mut dyn core::panic::BoxMeUp *mut &mut dyn core::panic::BoxMeUp "उपयोगी डेटा" है (यह पता *mut &mut dyn core::panic::BoxMeUp उपलब्ध जानकारी)।


BoxMeUp एक अस्थिर आंतरिक कार्यान्वयन विवरण है, लेकिन इस प्रकार को देखते हुए , हम देखते हैं कि यह सब वास्तव में करता है dyn Any + Send , जो कि एक उपयोगी पैनिक डेटा है जो catch_unwind और thread::spawn द्वारा लौटाया जाता है।


BoxMeUp::box_me_up रिटर्न Box<dyn Any + Send> , लेकिन कच्चे सूचक के रूप में (चूंकि Box उस संदर्भ में उपलब्ध नहीं है जहां यह प्रकार परिभाषित है); BoxMeUp::get बस सामग्री उधार लें।


इस इंटरफ़ेस के दो कार्यान्वयन libpanic_unwind में libpanic_unwind : libpanic_unwind लिए -C panic=unwind अधिकांश प्लेटफ़ॉर्म पर डिफ़ॉल्ट () और libpanic_abort लिए -C panic=abort


std::panic!


पैनिक रनटाइम इंटरफ़ेस के शीर्ष पर, libstd डिफ़ॉल्ट Rust पैनिक मेकेनिज्म को std::panicking आतंरिक मॉड्यूल पर लागू करता है।


rust_panic_with_hook


मुख्य कार्य जिसके माध्यम से लगभग सब कुछ चल रहा है, rust_panic_with_hook :


 fn rust_panic_with_hook( payload: &mut dyn BoxMeUp, message: Option<&fmt::Arguments<'_>>, file_line_col: &(&str, u32, u32), ) -> ! 

यह फ़ंक्शन पैनिक के स्रोत के स्थान को स्वीकार करता है, एक वैकल्पिक अनफ़ॉर्मैट संदेश ( fmt::Arguments Documentation) और उपयोगी डेटा देखें।


इसका मुख्य कार्य वर्तमान आतंक अवरोधक को ट्रिगर करना है।
पैनिक इंटरसेप्टर्स के पास PanicInfo तर्क है, इसलिए हमें पैनिक स्रोत के स्थान, पैनिक मैसेज के लिए फॉर्मेट की जानकारी और उपयोगी डेटा की आवश्यकता है।
यह तर्क से मेल खाता है rust_panic_with_hook बहुत अच्छी तरह से!
file_line_col और message सीधे पहले दो तत्वों के लिए उपयोग किया जा सकता है; payload BoxMeUp इंटरफ़ेस के माध्यम से &(dyn Any + Send) में बदल जाता है।


दिलचस्प है, मानक पैनिक इंटरसेप्टर पूरी तरह से message अनदेखी करता है; आप जो देख रहे हैं वह पेलोड को &str या String (कोई फर्क नहीं पड़ता कि क्या काम करता है) को कास्टिंग कर रहा है।
निश्चित रूप से, कॉलर को यह सुनिश्चित करना चाहिए कि message स्वरूपण, यदि मौजूद है, तो वही परिणाम उत्पन्न करता है।
(और जिन पर हम नीचे चर्चा करते हैं, वे इसकी गारंटी देते हैं।)


अंत में, rust_panic_with_hook वर्तमान रनटाइम आतंक हैंडलर rust_panic_with_hook भेजा जाता है।


फिलहाल, केवल payload अभी भी प्रासंगिक है - और क्या महत्वपूर्ण है: message ( '_ जीवनकाल के साथ '_ इंगित करता है कि अल्पकालिक लिंक समाहित हो सकते हैं, लेकिन उपयोगी पैनिक डेटा स्टैक को प्रचारित करेगा और इसलिए 'static जीवनकाल 'static साथ होना चाहिए)।


'static बाधा बहुत अच्छी तरह से छिपी हुई है, लेकिन थोड़ी देर बाद मुझे एहसास हुआ कि Any साधन 'static अर्थ है 'static (और याद रखें कि dyn BoxMeUp उपयोग केवल Box<dyn Any + Send> ) प्राप्त करने के लिए किया जाता है।


लिब्स्टड प्रवेश बिंदु


rust_panic_with_hook std::panicking rust_panic_with_hook लिए एक निजी कार्य है; मॉड्यूल इस केंद्रीय फ़ंक्शन के शीर्ष पर तीन प्रवेश बिंदु प्रदान करता है और एक जो इसे बायपास करता है:


  • डिफ़ॉल्ट आतंक हैंडलर कार्यान्वयन जो समर्थन करता है (जैसा कि हम देखेंगे) core::panic! से core::panic! और अंतर्निहित दहशत (अंकगणित अतिप्रवाह या सरणी / टुकड़ा अनुक्रमण से)।
    PanicInfo को इनपुट के रूप में प्राप्त होता है, और इसे rust_panic_with_hook तर्क में बदलना चाहिए।
    उत्सुकता से, हालांकि PanicInfo घटक और rust_panic_with_hook तर्क काफी समान हैं, और ऐसा लगता है कि उन्हें बस आगे भेजा जा सकता है, यह नहीं है
    इसके बजाय, libstd PanicInfo से पूरी तरह से payload घटक को PanicInfo करता है और वास्तविक payload ( rust_panic_with_hook को दिया गया) सेट करता है ताकि इसमें एक message


    विशेष रूप से, इसका मतलब है कि रन- टाइम पैनिक हैंडलर no_std अनुप्रयोगों के लिए no_std नहीं रखता है।
    यह केवल तब ही खेल में आता है जब libstd में पैनिक हैंडलर के कार्यान्वयन का उपयोग किया जाता है।
    ( -C panic माध्यम से चुनी गई आतंक की रणनीति अभी भी मायने रखती है, क्योंकि यह कोड पीढ़ी को भी प्रभावित करती है।
    उदाहरण के लिए, -C panic=abort कोड सरल हो सकता है, क्योंकि आपको स्टैक अनइंडिंग का समर्थन करने की आवश्यकता नहीं है)।


  • begin_panic_fmt , std::panic! के संस्करण का समर्थन करता है std::panic! (यानी यह प्रयोग तब किया जाता है जब आप एक मैक्रो में कई तर्क पास करते हैं)।
    असल में, PanicInfo ( डमी पेलोड्स के साथ) में प्रारूप स्ट्रिंग के तर्कों को PanicInfo और डिफ़ॉल्ट पैनिक हैंडलर को कॉल करने के लिए जो हमने अभी चर्चा की है, होता है।


  • begin_panic समर्थन std::panic! std::panic!
    दिलचस्प है, यह अन्य दो प्रवेश बिंदुओं की तुलना में पूरी तरह से अलग कोड पथ का उपयोग करता है!
    विशेष रूप से, यह एकमात्र प्रविष्टि बिंदु है जो आपको मनमाने ढंग से उपयोगी डेटा स्थानांतरित करने की अनुमति देता है।
    यह पेलोड बस Box<dyn Any + Send> ताकि इसे rust_panic_with_hook , और उस पर पास किया जा सके।


    विशेष रूप से, एक पैनिक इंटरसेप्टर जो PanicData से message क्षेत्र को देखता है, वह std::panic!("do panic") में संदेश नहीं देख पाएगा std::panic!("do panic") , लेकिन यह std::panic!("panic with data: {}", data) में संदेश देख सकता है std::panic!("panic with data: {}", data) चूंकि उत्तरार्द्ध begin_panic_fmt से begin_panic_fmt
    यह बहुत भयानक लगता है। (लेकिन यह भी ध्यान दें कि PanicData::message() अभी तक स्थिर नहीं है।)


  • update_count_then_panic अजीब निकला: यह प्रवेश बिंदु फिर से शुरू होने का समर्थन करता resume_unwind नहीं करता है और वास्तव में आतंक की resume_unwind पैदा नहीं करता है।
    इसके बजाय, इसे तुरंत पैनिक हैंडलर के पास भेज दिया जाता है।
    उदाहरण के लिए, begin_panic कॉलर को मनमाने ढंग से उपयोगी डेटा का चयन करने की अनुमति देता है।
    begin_panic विपरीत, कॉलिंग फ़ंक्शन पेलोड को पैकिंग और आकार देने के लिए जिम्मेदार है; update_count_then_panic फ़ंक्शन केवल रन-टाइम आतंक हैंडलर के लिए अपने तर्क को लगभग शब्दशः अग्रेषित करता है।



घबराया हुआ हैंडलर


std::panic! तंत्र वास्तव में उपयोगी है, लेकिन इसके लिए Box माध्यम से ढेर पर डेटा रखने की आवश्यकता होती है, जो हमेशा उपलब्ध नहीं होता है।
लिबरकोर को आतंक पैदा करने का एक तरीका देने के लिए, आतंक के संचालकों को पेश किया गया था।
जैसा कि हमने देखा है, अगर libstd उपलब्ध है, तो यह इस इंटरफ़ेस core::panic! का कार्यान्वयन प्रदान करता है core::panic! कामवासना के विचारों में घबराहट।


पैनिक हैंडलर के लिए इंटरफ़ेस fn panic(info: &core::panic::PanicInfo) -> ! फंक्शन fn panic(info: &core::panic::PanicInfo) -> ! libcore आयात, और यह बाद में लिंकर द्वारा हल किया गया है।
PanicInfo प्रकार पैनिक इंटरसेप्टर्स के लिए समान है: इसमें पैनिक सोर्स का स्थान, पैनिक मैसेज और उपयोगी डेटा ( dyn Any + Send ) शामिल हैं।
पैनिक मैसेज को fmt::Arguments , यानी एक तर्क के साथ एक प्रारूप स्ट्रिंग में प्रस्तुत किया जाता है, जिसे अभी तक तैयार नहीं किया गया है।


core::panic!


पैनिक प्रोसेसर इंटरफ़ेस के अलावा, libcore न्यूनतम पैनिक एपीआई प्रदान करता है।
core::panic! मैक्रो fmt::Arguments बनाता है जो तब पैनिक हैंडलर को दिया जाता है
स्वरूपण यहां नहीं होता है, क्योंकि इसके लिए ढेर पर स्मृति के आवंटन की आवश्यकता होगी; यही कारण है कि PanicInfo में अपने तर्कों के साथ " PanicInfo " प्रारूप स्ट्रिंग शामिल है।


उत्सुकता से, PanicInfo से payload फ़ील्ड को पैनिक हैंडलर के पास PanicInfo , जो हमेशा एक डमी मूल्य पर सेट होता है।
यह बताता है कि लिबस्टर्ड पैनिक हैंडलर पेलोड डेटा को अनदेखा क्यों करता है (और इसके बजाय message से नया पेलोड डेटा बनाता है), लेकिन यह मुझे आश्चर्यचकित करता है कि यह फ़ील्ड पैनिक हैंडलर एपीआई का हिस्सा क्यों है।
इसका एक और परिणाम यह है कि core::panic!("message") और std::panic!("message") (किसी भी स्वरूपण के बिना विकल्प) वास्तव में बहुत अलग दहशत पैदा करते हैं: पहला fmt::Arguments में बदल जाता है fmt::Arguments , पैनिक हैंडलर इंटरफेस के माध्यम से पारित किया गया, और फिर libstd प्रारूपित करके उपयोगी String डेटा बनाता है।
उत्तरार्द्ध, हालांकि, सीधे उपयोगी डेटा के रूप में उपयोग करता है, और message क्षेत्र None रहता (जैसा कि पहले ही उल्लेख किया गया है)।


लिबरकोर में आतंक एपीआई के कुछ तत्व भाषा तत्व हैं क्योंकि कंपाइलर कोड पीढ़ी के दौरान इन फ़ंक्शन को कॉल करता है:


  • panic एक तत्व को कहा जाता है जब कंपाइलर को एक आतंक पैदा करने की आवश्यकता होती है जिसे किसी भी स्वरूपण (उदाहरण के लिए, अंकगणितीय अतिप्रवाह) की आवश्यकता नहीं होती है; यह वही फ़ंक्शन है जो core::panic! का भी समर्थन करता है core::panic! एक तर्क core::panic!
  • जब कोई सरणी / स्लाइस सीमा जांच विफल हो जाती है, तो panic_bounds_check को कहा जाता है; यह उसी विधि को core::panic! कहता है core::panic! स्वरूपण के साथ

निष्कर्ष


हम 4 एपीआई स्तर से गुजरे, जिनमें से 2 को आयातित फ़ंक्शन कॉल के माध्यम से पुनर्निर्देशित किया गया और लिंकर द्वारा हल किया गया।
कैसी यात्रा है!
लेकिन हम अंत तक पहुंच गए हैं।
मुझे उम्मीद है कि आप रास्ते से नहीं घबराएंगे । ;)


मैंने कुछ चीजों का जिक्र अद्भुत के रूप में किया।
यह पता चला है कि उनमें से सभी इस तथ्य से संबंधित हैं कि पैनिक इंटरसेप्टर और पैनिक प्रोसेसर पैनिक PanicInfo संरचना को अपने इंटरफेस में साझा करते हैं, जिसमें एक वैकल्पिक रूप से स्वरूपित message और एक मिटाए गए प्रकार के साथ payload होता है:


  • एक पैनिक इंटरसेप्टर हमेशा payload में पहले से ही फॉर्मेट किया हुआ मैसेज पा सकता है, इसलिए message इंटरसेप्टर के लिए बेकार लगता है। वास्तव में, message मौजूद नहीं हो सकता है, भले ही payload में कोई मैसेज हो (उदाहरण के लिए, std::panic!("message") )।
  • पैनिक हैंडलर वास्तव में payload प्राप्त नहीं करेगा, इसलिए क्षेत्र हैंडलर के लिए व्यर्थ लगता है।

पैनिक हैंडलर के वर्णन से RFC को पढ़कर ऐसा लगता है कि यह प्लान core::panic! लिए था core::panic! मनमाने ढंग से उपयोगी डेटा का भी समर्थन करते हैं, लेकिन अभी तक यह भौतिक नहीं हुआ है।
हालाँकि, इस भविष्य के विस्तार के साथ, मुझे लगता है कि हमारे पास अपरिवर्तनीय है कि जब message Some , तो या तो payload == &NoPayload (इसलिए उपयोगी डेटा बेमानी है) या payload एक स्वरूपित संदेश है (इसलिए संदेश निरर्थक है)।


मुझे आश्चर्य है कि क्या कोई ऐसा मामला है जब दोनों क्षेत्र उपयोगी होंगे और यदि नहीं, तो क्या हम उन्हें enum दो संस्करण बनाकर इसका एनकोड कर सकते हैं?


वर्तमान डिजाइन के लिए इस प्रस्ताव के खिलाफ शायद अच्छे कारण हैं; दस्तावेज़ी प्रारूप में उन्हें कहीं और प्राप्त करना बहुत अच्छा होगा। :)


कहने के लिए और भी बहुत कुछ है, लेकिन इस बिंदु पर मैं आपको उस स्रोत कोड के लिंक का पालन करने के लिए आमंत्रित करता हूं जिसे मैंने ऊपर शामिल किया था।


मन में एक उच्च स्तरीय संरचना के साथ, आपको इस कोड का पालन करने में सक्षम होना चाहिए।
अगर लोगों को लगता है कि इस समीक्षा को हमेशा के लिए कहीं रखा जाना चाहिए, तो मुझे इस लेख को किसी तरह के प्रलेखन में बदलकर खुशी होगी - हालाँकि मुझे यकीन नहीं है कि यह एक अच्छी जगह होगी।


और अगर आपने जो लिखा है, उसमें कोई त्रुटि है, तो कृपया मुझे बताएं!

Source: https://habr.com/ru/post/hi482358/


All Articles