2012-01-17 17 views
9

निम्न कोड के लिए:यह इनलाइन असेंबली क्यों काम नहीं कर रही है?

long buf[64]; 

register long rrax asm ("rax"); 
register long rrbx asm ("rbx"); 
register long rrsi asm ("rsi"); 

rrax = 0x34; 
rrbx = 0x39; 

__asm__ __volatile__ ("movq $buf,%rsi"); 
__asm__ __volatile__ ("movq %rax, 0(%rsi);"); 
__asm__ __volatile__ ("movq %rbx, 8(%rsi);"); 

printf("buf[0] = %lx, buf[1] = %lx!\n", buf[0], buf[1]); 

मैं निम्नलिखित उत्पादन:

buf[0] = 0, buf[1] = 346161cbc0! 

जबकि यह किया जाना चाहिए था:

buf[0] = 34, buf[1] = 39! 

कोई भी विचार क्यों यह ठीक से काम नहीं कर रहा है, और इसे कैसे हल करें?

+2

जीडीबी के साथ कोड के माध्यम से कदम क्यों नहीं उठाएं ताकि आप देख सकें कि * क्या हो रहा है? –

उत्तर

22

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

__asm__ (
    "movq %1, 0(%0)\n\t" 
    "movq %2, 8(%0)" 
    :        /* Outputs (none) */ 
    : "r"(buf), "r"(rrax), "r"(rrbx) /* Inputs */ 
    : "memory");      /* Clobbered */ 

तुम भी आम तौर पर जीसीसी mov के सबसे संभाल, रजिस्टर का चयन, इत्यादि देना चाहते हैं - भले ही आप स्पष्ट रूप से रजिस्टर विवश (rrax stil %rax है) जानकारी जीसीसी के माध्यम से प्रवाह करते हैं या अनपेक्षित मिल जाएगा परिणाम है।

__volatile__ गलत है।

कारण __volatile__ मौजूद है इसलिए आप गारंटी दे सकते हैं कि कंपाइलर आपके कोड को बिल्कुल कहां रखता है ... जो पूरी तरह अनावश्यक इस कोड की गारंटी देता है। स्मृति सुविधाओं जैसे उन्नत सुविधाओं को लागू करने के लिए यह आवश्यक है, लेकिन अगर आप केवल स्मृति और रजिस्टरों को संशोधित कर रहे हैं तो लगभग पूरी तरह से बेकार है।

जीसीसी पहले से ही जानता है कि यह printf के बाद इस विधानसभा स्थानांतरित नहीं कर सकते क्योंकि printf कॉल तक पहुँचता buf, और buf विधानसभा द्वारा clobbered जा सकता है। जीसीसी पहले से ही जानता है कि यह rrax=0x39; से पहले विधानसभा को स्थानांतरित नहीं कर सकता क्योंकि rax असेंबली कोड में एक इनपुट है। तो __volatile__ आपको क्या मिलता है? कुछ भी तो नहीं।

अपने कोड __volatile__ बिना काम नहीं करता है तो कोड जो किया बजाय तय करना चाहिए सिर्फ __volatile__ जोड़ने और उम्मीद है कि सब कुछ बेहतर बनाता है की में कोई त्रुटि है। __volatile__ कीवर्ड जादू नहीं है और इस तरह से इलाज नहीं किया जाना चाहिए।

वैकल्पिक फिक्स:

अपने मूल कोड के लिए __volatile__ आवश्यक है? नहीं। इनपुट और क्लॉबर वैल्यू को सही तरीके से चिह्नित करें।

/* The "S" constraint means %rsi, "b" means %rbx, and "a" means %rax 
    The inputs and clobbered values are specified. There is no output 
    so that section is blank. */ 
rsi = (long) buf; 
__asm__ ("movq %%rax, 0(%%rsi)" : : "a"(rrax), "S"(rssi) : "memory"); 
__asm__ ("movq %%rbx, 0(%%rsi)" : : "b"(rrbx), "S"(rrsi) : "memory"); 

क्यों __volatile__ तुम यहाँ मदद नहीं करता है: के बाद से सवाल के कोड से ऊपर का दावा है कि यह कभी नहीं rrax का उपयोग करता

rrax = 0x34; /* Dead code */ 

जीसीसी, अच्छी तरह से पूरी तरह से ऊपर लाइन को हटाने के लिए अपने अधिकार के भीतर है।

एक स्पष्ट उदाहरण

long global; 
void store_5(void) 
{ 
    register long rax asm ("rax"); 
    rax = 5; 
    __asm__ __volatile__ ("movq %%rax, (global)"); 
} 

disassembly, कम या ज्यादा आप इसे -O0 में उम्मीद के रूप में है

movl $5, %rax 
movq %rax, (global) 

लेकिन अनुकूलन बंद के साथ, आप विधानसभा के बारे में काफी खराब हो सकता है। आइए -O2:

movq %rax, (global) 

व्हाउप्स! rax = 5; कहां गए थे? यह मृत कोड है, क्योंकि %rax फ़ंक्शन में कभी भी उपयोग नहीं किया जाता है - कम से कम जहां तक ​​जीसीसी जानता है। जीसीसी विधानसभा के अंदर नहीं देखता है। क्या होता है जब हम __volatile__ हटाते हैं?

; empty 

ठीक है, आप सोच सकते हैं __volatile__ अपने कीमती विधानसभा की निकालने से जीसीसी रखकर आप एक सेवा कर रहा है, लेकिन यह सिर्फ तथ्य यह है कि जीसीसी कुछ भी करने से नहीं है अपने विधानसभा सोचता मास्किंग है। जीसीसी सोचता है कि आपकी असेंबली में कोई इनपुट नहीं होता है, कोई आउटपुट नहीं होता है, और क्लॉबर्स को कोई स्मृति नहीं होती है। बेहतर होगा कि तुम इसे बाहर सीधा था:

movq %rax, (global) 

बेहतर:

long global; 
void store_5(void) 
{ 
    register long rax asm ("rax"); 
    rax = 5; 
    __asm__ __volatile__ ("movq %%rax, (global)" : : : "memory"); 
} 

अब हम निम्नलिखित उत्पादन मिलता है। लेकिन अगर आप आदानों के बारे में जीसीसी कहता हूं, यह है कि %rax ठीक से पहले आरंभ नहीं हो जाता सुनिश्चित करेंगे:

long global; 
void store_5(void) 
{ 
    register long rax asm ("rax"); 
    rax = 5; 
    __asm__ ("movq %%rax, (global)" : : "a"(rax) : "memory"); 
} 

उत्पादन, अनुकूलन के साथ:

सही! और हमें __volatile__ का उपयोग करने की भी आवश्यकता नहीं है।

__volatile__ क्यों मौजूद है?

__volatile__ के लिए प्राथमिक सही उपयोग यह है कि यदि आपका असेंबली कोड इनपुट, आउटपुट या क्लोबबरिंग मेमोरी के अलावा कुछ और करता है। शायद यह विशेष रजिस्ट्रारों के साथ गड़बड़ करता है जो जीसीसी को आईओ को नहीं जानता है, या प्रभावित नहीं करता है। आप इसे लिनक्स कर्नेल में बहुत कुछ देखते हैं, लेकिन इसका उपयोग उपयोगकर्ता अंतरिक्ष में अक्सर दुरुपयोग किया जाता है।

__volatile__ कीवर्ड बहुत मोहक है क्योंकि हम प्रोग्रामर अक्सर सोचते हैं कि हम लगभग पहले से ही असेंबली भाषा में प्रोग्रामिंग कर रहे हैं। नहीं थे। सी कंपाइलर बहुत सारे डेटा प्रवाह विश्लेषण करते हैं - इसलिए आपको अपने असेंबली कोड के लिए कंपाइलर में डेटा प्रवाह की व्याख्या करने की आवश्यकता है। इस तरह, संकलक सुरक्षित रूप से असेंबली के अपने हिस्से में हेरफेर कर सकता है जैसे कि यह उत्पन्न होने वाली असेंबली में हेरफेर करता है।

यदि आप स्वयं को __volatile__ का उपयोग करके पाते हैं, तो वैकल्पिक रूप से आप एक असेंबली फ़ाइल में एक संपूर्ण फ़ंक्शन या मॉड्यूल लिख सकते हैं।

+1

__volatile__ एएसएम में कंपाइलर को कोड को ठीक से रखने के लिए कहां है जहां इसे रखा गया है। यह चर के लिए अस्थिर की तरह नहीं है। – MetallicPriest

+3

@ मेटालिक प्रिस्टेस्ट: हां, यह वही है जो अस्थिर है, और यही कारण है कि यहां जरूरी नहीं है। यदि आप इसे समझ में नहीं आते हैं, तो जीसीसी इनलाइन असेंबली हाउटो * को शुरू से ही खत्म करने के लिए पढ़ें * क्योंकि यह भागों को छोड़ने में मदद नहीं करता है। –

+0

यदि मैं बोल्डफैस्ड "' __volatile__' गलत है तो नहीं, तो मैं आपका जवाब दूंगा "; ऐसा इसलिए है क्योंकि मूल poster_ वास्तव में _did_ द्वारा बताए गए असेंबली _ की आवश्यकता है। यह कुछ अन्य कारणों के लिए गलत है (जैसा कि आपने नोट किया है, गायब क्लॉबर)। फिर भी, _separate_ 'asm()' कथन, अगर कोई उनका उपयोग करने पर जोर देता है (शायद ही कभी एक अच्छा विचार), तो उसे ऑर्डर करने के लिए मजबूर होना आवश्यक है। हां, अगर आपको पसंद है तो मुझे नाइटपीकी कॉल करें ;-) –

4

कंपाइलर रजिस्टरों का उपयोग करता है, और यह आपके द्वारा रखे गए मानों पर लिख सकता है।

इस मामले में, संकलक rbxrrbx असाइनमेंट के बाद और इनलाइन असेंबली सेक्शन के बाद रजिस्टर का उपयोग करता है।

सामान्य रूप से, आपको इनलाइन असेंबली कोड अनुक्रमों के बाद और बाद में अपने मूल्य रखने के लिए रजिस्टरों की अपेक्षा नहीं करनी चाहिए।

+0

लेकिन मैं printf से पहले buf को पॉइंटर के रूप में उपयोग करता हूं। इससे कोई फर्क नहीं पड़ता, अगर printf इसका उपयोग करता है या नहीं। buf [0] और buf [1] वैसे भी सही मान होना चाहिए, नहीं? भले ही मैं printf से rrsi को हटा दूं, फिर भी यह वही गलत मान प्रिंट करता है। – MetallicPriest

+1

@ugoren: यदि आप 'asm' कीवर्ड का उपयोग कर जीसीसी असाइन रजिस्टर्स बनाते हैं, तो यह उन्हें ठीक से फैलाएगा और उन्हें फिर से लोड करेगा ताकि वे फ़ंक्शन कॉल में सहेजे जा सकें। –

+0

@ मेटालिक प्रिस्ट, आप विशिष्ट मामले के बारे में सही हैं। मुझे अपना जवाब संपादित करना चाहिए, लेकिन मुझे तकनीकी परेशानी हो रही है। लेकिन जैसा कि मैंने लिखा है, सामान्य विचार है। – ugoren

1

थोड़ा ऑफ-विषय लेकिन मैं जीसीसी इनलाइन असेंबली पर थोड़ा सा अनुसरण करना चाहता हूं।

(गैर-) __volatile__ की आवश्यकता इस तथ्य से आती है कि जीसीसी इनलाइन असेंबली को अनुकूलित करता है। जीसीसी साइड इफेक्ट्स/पूर्वापेक्षाएँ के लिए असेंबली स्टेटमेंट का निरीक्षण करता है, और यदि उन्हें पता चलता है कि यह अस्तित्व में नहीं है तो यह असेंबली निर्देश को स्थानांतरित करने या को को हटाने का निर्णय ले सकता है। सभी __volatile__ संकलक को बताना है "देखभाल करना बंद करें और इसे वहां रखें"।

जो आमतौर पर आप वास्तव में नहीं चाहते हैं।

यह वह जगह है जहां बाधाओं में आते हैं के लिए की जरूरत नाम ओवरलोड हो गया है और वास्तव में जीसीसी इनलाइन विधानसभा में अलग अलग चीजों के लिए इस्तेमाल किया:।

  • बाधाओं asm() ब्लॉक में इस्तेमाल किया इनपुट/आउटपुट ऑपरेंड निर्दिष्ट
  • बाधाएं "क्लॉबर सूची" निर्दिष्ट करती हैं, जो बताती है कि "राज्य" (रजिस्टर्स, हालत कोड, मेमोरी) asm() से प्रभावित होते हैं।
  • की कमी (, रजिस्टर, पते, ऑफसेट, स्थिरांक ...) ऑपरेंड के वर्गों को निर्दिष्ट
  • बाधाओं संघों/कोडांतरक संस्थाओं और C/C++ चर के बीच बाइंडिंग/भाव

कई मामलों में, डेवलपर्स की घोषणा दुरुपयोग__volatile__ क्योंकि उन्होंने देखा कि उनके कोड को या तो चारों ओर ले जाया जा रहा है या इसके बिना गायब हो रहा है। यदि ऐसा होता है, तो आमतौर पर यह एक संकेत है कि डेवलपर ने का प्रयास नहीं किया है ताकि जीसीसी को साइड इफेक्ट्स/असेंबली के पूर्वापेक्षाएँ के बारे में बताया जा सके। उदाहरण के लिए, इस गाड़ी कोड:

register int foo __asm__("rax") = 1234; 
register int bar __adm__("rbx") = 4321; 

asm("add %rax, %rbx"); 
printf("I'm expecting 'bar' to be 5555 it is: %d\n", bar); 

यह कई कीड़े मिला है: (!)

  • एक के लिए, यह केवल संकलित एक जीसीसी बग के कारण। आम तौर पर, इनलाइन असेंबली में रजिस्टर नाम लिखने के लिए, डबल %% की आवश्यकता होती है, लेकिन उपरोक्त में यदि आप वास्तव में उन्हें निर्दिष्ट करते हैं तो आपको एक कंपाइलर/असेंबलर त्रुटि मिलती है, /tmp/ccYPmr3g.s:22: Error: bad register name '%%rax'
  • दूसरा, यह कंपाइलर को नहीं कह रहा है कि आपको कहां और कहाँ चर की आवश्यकता है। इसके बजाए, यह मानता है संकलक सम्मान asm() सचमुच। यह माइक्रोसॉफ्ट विजुअल सी ++ के लिए सच हो सकता है लेकिन जीसीसी के लिए मामले नहीं है।

आप इसे संकलन हैं अनुकूलन के बिना, यह बनाता है:

0000000000400524 <main>: 
[ ... ] 
    400534:  b8 d2 04 00 00   mov $0x4d2,%eax 
    400539:  bb e1 10 00 00   mov $0x10e1,%ebx 
    40053e:  48 01 c3    add %rax,%rbx 
    400541:  48 89 da    mov %rbx,%rdx 
    400544:  b8 5c 06 40 00   mov $0x40065c,%eax 
    400549:  48 89 d6    mov %rdx,%rsi 
    40054c:  48 89 c7    mov %rax,%rdi 
    40054f:  b8 00 00 00 00   mov $0x0,%eax 
    400554:  e8 d7 fe ff ff   callq 400430 <[email protected]> 
[...]
आप अपने add अनुदेश पा सकते हैं, और दो रजिस्टरों की initializations, और यह उम्मीद प्रिंट होगा। यदि, दूसरी ओर, आप अनुकूलन को क्रैंक करते हैं, तो कुछ और होता है:
0000000000400530 <main>: 
    400530:  48 83 ec 08    sub $0x8,%rsp 
    400534:  48 01 c3    add %rax,%rbx 
    400537:  be e1 10 00 00   mov $0x10e1,%esi 
    40053c:  bf 3c 06 40 00   mov $0x40063c,%edi 
    400541:  31 c0     xor %eax,%eax 
    400543:  e8 e8 fe ff ff   callq 400430 <[email protected]> 
[ ... ]
"उपयोग" रजिस्टरों दोनों की आपकी प्रारंभिकता अब वहां नहीं है।कंपाइलर ने उन्हें त्याग दिया क्योंकि ऐसा कुछ भी नहीं देख सकता था, और जब इसे असेंबली निर्देश रखा गया था, तो इसे दो चर के किसी भी उपयोग से पहले रखा गया। यह वहां है लेकिन यह कुछ भी नहीं करता है (सौभाग्य से वास्तव में ... यदि rax/ rbx उपयोग में किया गया था जो बता सकता है कि क्या हुआ होगा ...)।

और इसका कारण यह है कि आपने वास्तव में जीसीसी को बताया कि असेंबली इन रजिस्टरों/इन ऑपरेंड मानों का उपयोग कर रही है। इसमें volatile के साथ कुछ भी नहीं है, लेकिन इस तथ्य के साथ आप एक बाधा मुक्त asm() अभिव्यक्ति का उपयोग कर रहे हैं।

तरीका यह सही ढंग से बाधाओं के माध्यम से है ऐसा करने के लिए, यानी आप का उपयोग करेंगे:

int foo = 1234; 
int bar = 4321; 

asm("add %1, %0" : "+r"(bar) : "r"(foo)); 
printf("I'm expecting 'bar' to be 5555 it is: %d\n", bar); 

इस संकलक बताता है कि विधानसभा:

  1. एक रजिस्टर में एक तर्क है, "+r"(...) कि दोनों को असेंबली कथन से पहले प्रारंभ करने की आवश्यकता है, और असेंबली कथन द्वारा संशोधित किया गया है, और इसके साथ परिवर्तनीय bar को संबद्ध करें।
  2. में एक रजिस्टर में दूसरा तर्क है, "r"(...) जिसे असेंबली स्टेटमेंट से पहले शुरू किया जाना चाहिए और कथन द्वारा संशोधित/संशोधित नहीं किया गया है। यहां, इसके साथ foo संबद्ध करें।

नोटिस कोई पंजीकरण असाइनमेंट निर्दिष्ट नहीं है - संकलक चुनता है कि संकलन के चर/राज्य के आधार पर।उपरोक्त के (अनुकूलित) आउटपुट:

0000000000400530 <main>: 
    400530:  48 83 ec 08    sub $0x8,%rsp 
    400534:  b8 d2 04 00 00   mov $0x4d2,%eax 
    400539:  be e1 10 00 00   mov $0x10e1,%esi 
    40053e:  bf 4c 06 40 00   mov $0x40064c,%edi 
    400543:  01 c6     add %eax,%esi 
    400545:  31 c0     xor %eax,%eax 
    400547:  e8 e4 fe ff ff   callq 400430 <[email protected]> 
[ ... ]
जीसीसी इनलाइन असेंबली बाधाएं लगभग किसी भी रूप में या अन्य में लगभग हमेशा आवश्यक हैं, लेकिन संकलक को समान आवश्यकताओं का वर्णन करने के कई संभावित तरीके हो सकते हैं; इसके बाद के संस्करण के बजाय, आप भी लिख सकते हैं:

asm("add %1, %0" : "=r"(bar) : "r"(foo), "0"(bar)); 

यह बताता है जीसीसी:

  1. बयान एक निर्गम संकार्य, चर bar, कि बयान के बाद, "=r"(...) एक रजिस्टर में पाया जाएगा है
  2. बयान एक इनपुट संकार्य, चर foo है, जो एक रजिस्टर में रखा जा रहा है, है "r"(...)
  3. संकार्य शून्य भी एक इनपुट संकार्य है औरके साथ प्रारंभ करने के लिए

या, फिर से एक विकल्प:

asm("add %1, %0" : "+r"(bar) : "g"(foo)); 

जो बताता है जीसीसी:

  1. bla (जम्हाई - पहले की तरह ही, bar दोनों इनपुट/आउटपुट)
  2. कथन में एक इनपुट ऑपरेंड है, वेरिएबल foo, जो कथन परवाह नहीं करता है कि यह एक regis में है या नहीं टेर, स्मृति या एक संकलन समय निरंतर में

परिणाम पूर्व से अलग है (कि "g"(...) बाधा है):

0000000000400530 <main>: 
    400530:  48 83 ec 08    sub $0x8,%rsp 
    400534:  bf 4c 06 40 00   mov $0x40064c,%edi 
    400539:  31 c0     xor %eax,%eax 
    40053b:  be e1 10 00 00   mov $0x10e1,%esi 
    400540:  81 c6 d2 04 00 00  add $0x4d2,%esi 
    400546:  e8 e5 fe ff ff   callq 400430 <[email protected]> 
[ ... ]
क्योंकि अब, जीसीसी वास्तव में पता लगा है foo एक संकलन समय स्थिर है और बस add निर्देश में मान को एम्बेड किया गया है! क्या वह साफ नहीं है?

मान्य है, यह जटिल है और इसका उपयोग करने में लग रहा है। लाभ यह है कि कंपाइलर को चुनने दें जो कि ऑपरेटरों को समग्र रूप से कोड को अनुकूलित करने की अनुमति देता है; यदि, उदाहरण के लिए, एक इनलाइन असेंबली कथन का उपयोग मैक्रो और/या static inline फ़ंक्शन में किया जाता है, तो कॉलर संदर्भ कॉलिंग संदर्भ के आधार पर, कोड के विभिन्न तत्कालताओं पर विभिन्न रजिस्टरों का चयन कर सकते हैं। या यदि कोई निश्चित मान एक स्थान पर संकलित-समय मूल्यांकन/स्थिर है लेकिन दूसरे में नहीं है, तो संकलक इसके लिए बनाई गई असेंबली को तैयार कर सकता है।

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

संबंधित मुद्दे