कैसे जंग आतंक काम करता है
जब आप 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
रहता (जैसा कि पहले ही उल्लेख किया गया है)।
लिबरकोर में आतंक एपीआई के कुछ तत्व भाषा तत्व हैं क्योंकि कंपाइलर कोड पीढ़ी के दौरान इन फ़ंक्शन को कॉल करता है:
निष्कर्ष
हम 4 एपीआई स्तर से गुजरे, जिनमें से 2 को आयातित फ़ंक्शन कॉल के माध्यम से पुनर्निर्देशित किया गया और लिंकर द्वारा हल किया गया।
कैसी यात्रा है!
लेकिन हम अंत तक पहुंच गए हैं।
मुझे उम्मीद है कि आप रास्ते से नहीं घबराएंगे । ;)
मैंने कुछ चीजों का जिक्र अद्भुत के रूप में किया।
यह पता चला है कि उनमें से सभी इस तथ्य से संबंधित हैं कि पैनिक इंटरसेप्टर और पैनिक प्रोसेसर पैनिक PanicInfo
संरचना को अपने इंटरफेस में साझा करते हैं, जिसमें एक वैकल्पिक रूप से स्वरूपित message
और एक मिटाए गए प्रकार के साथ payload
होता है:
- एक पैनिक इंटरसेप्टर हमेशा
payload
में पहले से ही फॉर्मेट किया हुआ मैसेज पा सकता है, इसलिए message
इंटरसेप्टर के लिए बेकार लगता है। वास्तव में, message
मौजूद नहीं हो सकता है, भले ही payload
में कोई मैसेज हो (उदाहरण के लिए, std::panic!("message")
)। - पैनिक हैंडलर वास्तव में
payload
प्राप्त नहीं करेगा, इसलिए क्षेत्र हैंडलर के लिए व्यर्थ लगता है।
पैनिक हैंडलर के वर्णन से RFC को पढ़कर ऐसा लगता है कि यह प्लान core::panic!
लिए था core::panic!
मनमाने ढंग से उपयोगी डेटा का भी समर्थन करते हैं, लेकिन अभी तक यह भौतिक नहीं हुआ है।
हालाँकि, इस भविष्य के विस्तार के साथ, मुझे लगता है कि हमारे पास अपरिवर्तनीय है कि जब message
Some
, तो या तो payload == &NoPayload
(इसलिए उपयोगी डेटा बेमानी है) या payload
एक स्वरूपित संदेश है (इसलिए संदेश निरर्थक है)।
मुझे आश्चर्य है कि क्या कोई ऐसा मामला है जब दोनों क्षेत्र उपयोगी होंगे और यदि नहीं, तो क्या हम उन्हें enum
दो संस्करण बनाकर इसका एनकोड कर सकते हैं?
वर्तमान डिजाइन के लिए इस प्रस्ताव के खिलाफ शायद अच्छे कारण हैं; दस्तावेज़ी प्रारूप में उन्हें कहीं और प्राप्त करना बहुत अच्छा होगा। :)
कहने के लिए और भी बहुत कुछ है, लेकिन इस बिंदु पर मैं आपको उस स्रोत कोड के लिंक का पालन करने के लिए आमंत्रित करता हूं जिसे मैंने ऊपर शामिल किया था।
मन में एक उच्च स्तरीय संरचना के साथ, आपको इस कोड का पालन करने में सक्षम होना चाहिए।
अगर लोगों को लगता है कि इस समीक्षा को हमेशा के लिए कहीं रखा जाना चाहिए, तो मुझे इस लेख को किसी तरह के प्रलेखन में बदलकर खुशी होगी - हालाँकि मुझे यकीन नहीं है कि यह एक अच्छी जगह होगी।
और अगर आपने जो लिखा है, उसमें कोई त्रुटि है, तो कृपया मुझे बताएं!