gcc - विस्तारित इनलाइन ASM में प्रिंटफ कॉलिंग



64-bit x86-64 (1)

आपके कोड की विशिष्ट समस्या: RDI एक फ़ंक्शन कॉल के दौरान बनाए नहीं रखी जाती है (नीचे देखें)। प्रिंट करने के लिए पहली कॉल से पहले यह सही है लेकिन प्रिंटफ से बंद है। आपको इसे पहले कहीं और अस्थायी रूप से संग्रहीत करने की आवश्यकता होगी। एक रजिस्टर जो क्लोबर्ड नहीं है वह सुविधाजनक होगा। आप printf से पहले एक प्रतिलिपि सहेज सकते हैं, और बाद में इसे RDI पर वापस कॉपी कर सकते हैं।

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

अन्य बातों के अलावा 64-बिट सिस्टम V ABI 128-बाइट रेड ज़ोन को अनिवार्य करता है। इसका मतलब है कि आप संभावित भ्रष्टाचार के बिना कुछ भी नहीं कर सकते हैं। याद रखें: एक कॉल करने से स्टैक पर एक वापसी पता चलता है। इस समस्या को हल करने का त्वरित और गंदा तरीका RSP से 128 घटाना है जब आपका इनलाइन असेंबलर शुरू होता है और समाप्त होने पर 128 वापस जोड़ते हैं।

% Rsp द्वारा इंगित किए गए स्थान से परे 128-बाइट क्षेत्र को आरक्षित माना जाता है और सिग्नल या बाधित हैंडलर द्वारा संशोधित नहीं किया जाता है। इसलिए, फ़ंक्शन इस क्षेत्र का उपयोग अस्थायी डेटा के लिए कर सकते हैं जिनकी फ़ंक्शन कॉल में आवश्यकता नहीं है। विशेष रूप से, पत्ती फ़ंक्शंस इस क्षेत्र को अपने संपूर्ण स्टैक फ्रेम के लिए उपयोग कर सकते हैं, बजाय इसके कि प्रस्तावक और उपसंहार में स्टैक पॉइंटर को समायोजित किया जाए। इस क्षेत्र को रेड जोन के रूप में जाना जाता है।

किसी अन्य समस्या के बारे में चिंतित होने के लिए किसी भी फ़ंक्शन कॉल से पहले स्टैक के लिए 16-बाइट गठबंधन होना आवश्यक है (या संभवतः 32-बाइट पैरामीटर के आधार पर)। यह 64-बिट ABI द्वारा आवश्यक है:

इनपुट तर्क क्षेत्र के अंत को 16 (32, यदि __m256 स्टैक पर पारित किया जाता है) बाइट सीमा में संरेखित किया जाएगा। दूसरे शब्दों में, मान (% rsp + 8) हमेशा 16 (32) का गुणक होता है, जब नियंत्रण को फ़ंक्शन प्रविष्टि बिंदु पर स्थानांतरित किया जाता है।

नोट : एक समारोह में एक कॉल पर 16-बाइट संरेखण के लिए यह आवश्यकता जीसीसी के लिए 32-बिट लिनक्स पर भी आवश्यक है : = 0.5:

सी प्रोग्रामिंग भाषा के संदर्भ में, फ़ंक्शन तर्क को रिवर्स ऑर्डर में स्टैक पर धकेल दिया जाता है। लिनक्स में, जीसीसी सम्मेलनों को कॉल करने के लिए वास्तविक मानक निर्धारित करता है। जीसीसी संस्करण 4.5 के बाद से, एक फ़ंक्शन को कॉल करते समय स्टैक को 16-बाइट सीमा से जोड़ा जाना चाहिए (पिछले संस्करणों में केवल 4-बाइट संरेखण की आवश्यकता थी।)

चूंकि हम इनलाइन printf में printf कहते हैं, इसलिए हमें यह सुनिश्चित करना चाहिए कि हम कॉल करने से पहले स्टैक को 16-बाइट की सीमा से संरेखित करें।

आपको यह भी पता होना चाहिए कि फ़ंक्शन को कॉल करते समय कुछ रजिस्टर एक फ़ंक्शन कॉल में संरक्षित होते हैं और कुछ नहीं होते हैं। विशेष रूप से जिन्हें फ़ंक्शन कॉल द्वारा बंद किया जा सकता है, वे 64-बिट एबीआई (अन्य लिंक देखें) के चित्र 3.4 में सूचीबद्ध हैं। वे रजिस्टर RAX , RCX , RDX , RD8 - RD11 , XMM0 - XMM15 , MMX0 - MMX7 , ST0 - ST7 हैं । ये सभी संभावित रूप से नष्ट हो जाते हैं इसलिए क्लोबर सूची में डाल दिया जाना चाहिए यदि वे इनपुट और आउटपुट बाधाओं में प्रकट नहीं होते हैं।

निम्न कोड को यह सुनिश्चित करने के लिए अधिकांश शर्तों को पूरा करना चाहिए कि इनलाइन असेंबलर जो किसी अन्य फ़ंक्शन को कॉल करता है वह अनजाने में क्लोबर रजिस्टर नहीं करेगा, रेडज़ोन को संरक्षित करता है, और कॉल से पहले 16-बाइट संरेखण बनाए रखता है:

int main()
{
    const char* test = "test\n";
    long dummyreg; /* dummyreg used to allow GCC to pick available register */

    __asm__ __volatile__ (
        "add $-128, %%rsp\n\t"   /* Skip the current redzone */
        "mov %%rsp, %[temp]\n\t" /* Copy RSP to available register */
        "and $-16, %%rsp\n\t"    /* Align stack to 16-byte boundary */
        "mov %[test], %%rdi\n\t" /* RDI is address of string */
        "xor %%eax, %%eax\n\t"   /* Variadic function set AL. This case 0 */
        "call printf\n\t"
        "mov %[test], %%rdi\n\t" /* RDI is address of string again */
        "xor %%eax, %%eax\n\t"   /* Variadic function set AL. This case 0 */
        "call printf\n\t"
        "mov %[temp], %%rsp\n\t" /* Restore RSP */
        "sub $-128, %%rsp\n\t"   /* Add 128 to RSP to restore to orig */
        :  [temp]"=&r"(dummyreg) /* Allow GCC to pick available output register. Modified
                                    before all inputs consumed so use & for early clobber*/
        :  [test]"r"(test),      /* Choose available register as input operand */
           "m"(test)             /* Dummy constraint to make sure test array
                                    is fully realized in memory before inline
                                    assembly is executed */
        : "rax", "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11",
          "xmm0","xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
          "xmm8","xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
          "mm0","mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm6",
          "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)"
        );

    return 0;
}

मैंने इनपुट अड़चन का उपयोग करके टेम्पलेट को उपलब्ध पते का चयन करने की अनुमति दी जिससे कि str एड्रेस को पास किया जा सके। यह सुनिश्चित करता है कि हमारे पास कॉल करने के लिए printf एड्रेस को str स्टोर करने के लिए एक रजिस्टर है। मुझे एक डमी रजिस्टर का उपयोग करके अस्थायी रूप से RSP संग्रहीत करने के लिए उपलब्ध स्थान चुनने के लिए कोडांतरक टेम्पलेट भी मिलता है। चुने गए रजिस्टरों में इनपुट / आउटपुट / क्लोब ऑपरैंड के रूप में पहले से ही चुने गए / सूचीबद्ध किसी भी व्यक्ति को शामिल नहीं किया जाएगा।

यह बहुत ही गन्दा लगता है, लेकिन इसे सही ढंग से करने में विफलता बाद में समस्याओं का कारण बन सकती है क्योंकि आप कार्यक्रम अधिक जटिल हो जाते हैं। यही कारण है कि इनलाइन असेंबलर के भीतर सिस्टम वी 64-बिट एबीआई के अनुरूप कॉलिंग फ़ंक्शन आमतौर पर चीजों को करने का सबसे अच्छा तरीका नहीं है।

मैं जीसीसी में 64-बिट लिनक्स पर विस्तारित इनलाइन एएसएम में एक ही स्ट्रिंग को दो बार आउटपुट करने की कोशिश कर रहा हूं।

int main()
{
    const char* test = "test\n";

    asm(
        "movq %[test], %%rdi\n"    // Debugger shows rdi = *address of string*  
        "movq $0, %%rax\n"

        "push %%rbp\n"
        "push %%rbx\n"
        "call printf\n"         
        "pop %%rbx\n"
        "pop %%rbp\n"

        "movq %[test], %%rdi\n" // Debugger shows rdi = 0
        "movq $0, %%rax\n"

        "push %%rbp\n"
        "push %%rbx\n"
        "call printf\n"     
        "pop %%rbx\n"
        "pop %%rbp\n"
        : 
        :  [test] "g" (test)
        : "rax", "rbx","rcx", "rdx", "rdi", "rsi", "rsp"
        );

    return 0;
}

अब, स्ट्रिंग केवल एक बार आउटपुट की जाती है। मैंने कई चीजों की कोशिश की है, लेकिन मुझे लगता है कि मैं कॉलिंग कन्वेंशन के बारे में कुछ चेतावनी याद कर रहा हूं। मुझे यह भी पक्का नहीं है कि अगर क्लोबर लिस्ट सही है या मुझे आरबीपी और आरबीएक्स को बचाने और बहाल करने की जरूरत है।

स्ट्रिंग को दो बार आउटपुट क्यों नहीं किया जाता है?

डिबगर के साथ देखने से मुझे पता चलता है कि किसी तरह जब दूसरी बार स्ट्रिंग को rdi में लोड किया जाता है, तो स्ट्रिंग के वास्तविक पते के बजाय इसका मान 0

मैं समझा नहीं सकता कि क्यों, ऐसा लगता है जैसे पहली कॉल के बाद स्टैक दूषित हो गया है? क्या मुझे इसे किसी तरह से बहाल करना है?





calling-convention