2009-04-03 15 views
23

डी, सी, और सी ++ जैसी भाषाओं में इनलाइन x86 असेंबलर का उपयोग करके एलोका() को कैसे कार्यान्वित करता है? मैं इसका थोड़ा संशोधित संस्करण बनाना चाहता हूं, लेकिन पहले मुझे यह जानने की जरूरत है कि मानक संस्करण कैसे कार्यान्वित किया जाता है। कंपाइलर्स से डिस्सेप्लर पढ़ना मदद नहीं करता है क्योंकि वे इतने सारे अनुकूलन करते हैं, और मैं सिर्फ कैनोलिक रूप चाहता हूं।एलोका कार्यान्वयन

संपादित करें: मुझे लगता है कि कठिन हिस्सा यह है कि मैं चाहता हूं कि यह सामान्य फ़ंक्शन कॉल सिंटैक्स हो, यानी नग्न फ़ंक्शन या कुछ का उपयोग करके, इसे सामान्य एलोका() की तरह दिखें।

संपादित करें # 2: आह, क्या बिल्ली है, आप मान सकते हैं कि हम फ्रेम पॉइंटर को छोड़ नहीं रहे हैं।

उत्तर

47

लागू alloca वास्तव में को कंपाइलर सहायता की आवश्यकता होती है। यहां कुछ लोग यह कह रहे हैं कि यह आसान है:

sub esp, <size> 

जो दुर्भाग्य से तस्वीर का केवल आधा है। हां, जो "ढेर पर जगह आवंटित करेगा" लेकिन कुछ मिलचास हैं।

  1. अगर संकलक कोड जो अन्य चर के बजाय esp को सापेक्ष संदर्भ उत्सर्जित था ebp (विशिष्ट अगर आप कोई फ्रेम सूचक के साथ संकलन)। फिर उन संदर्भों को समायोजित करने की आवश्यकता है। फ्रेम पॉइंटर्स के साथ भी, कंपाइलर्स कभी-कभी ऐसा करते हैं।

  2. अधिक महत्वपूर्ण रूप से, परिभाषा के अनुसार, alloca के साथ आवंटित स्थान कार्य समाप्त होने पर "मुक्त" होना चाहिए।

बड़ा एक बिंदु # 2 है। क्योंकि आप को को संकलित करने के लिए कोड को उत्सर्जित करने के लिए <size>esp को फ़ंक्शन के प्रत्येक निकास बिंदु पर जोड़ने के लिए संकलक की आवश्यकता है।

सबसे अधिक संभावना यह है कि संकलक कुछ अंतर्निहित प्रदान करता है जो लाइब्रेरी लेखकों को आवश्यक सहायता के लिए कंपाइलर से पूछने की अनुमति देता है।

संपादित करें:

वास्तव में, glibc (libc की जीएनयू के कार्यान्वयन) में। alloca के कार्यान्वयन बस यह है:

#ifdef __GNUC__ 
# define __alloca(size) __builtin_alloca (size) 
#endif /* GCC. */ 

संपादित करें:

इस बारे में सोच के बाद, कम से कम मेरा मानना ​​है कि को संकलक के लिए किया जाएगा की आवश्यकता होगी हमेशा किसी में एक फ्रेम सूचक का उपयोग फ़ंक्शन ऑप्टिमाइज़ेशन सेटिंग्स के बावजूद alloca का उपयोग करता है। यह सभी स्थानीय लोगों को ebp के माध्यम से सुरक्षित रूप से संदर्भित करने की अनुमति देगा और फ्रेम क्लीनर को फ्रेम सूचक को esp पर पुनर्स्थापित करके संभाला जाएगा।

संपादित करें:

#include <stdlib.h> 
#include <string.h> 
#include <stdio.h> 

#define __alloca(p, N) \ 
    do { \ 
     __asm__ __volatile__(\ 
     "sub %1, %%esp \n" \ 
     "mov %%esp, %0 \n" \ 
     : "=m"(p) \ 
     : "i"(N) \ 
     : "esp"); \ 
    } while(0) 

int func() { 
    char *p; 
    __alloca(p, 100); 
    memset(p, 0, 100); 
    strcpy(p, "hello world\n"); 
    printf("%s\n", p); 
} 

int main() { 
    func(); 
} 

जो दुर्भाग्य से सही ढंग से काम नहीं करता है:

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

8048454: push ebp 
8048455: mov ebp,esp 
8048457: sub esp,0x28 
804845a: sub esp,0x64      ; <- this and the line below are our "alloc" 
804845d: mov DWORD PTR [ebp-0x4],esp 
8048460: mov eax,DWORD PTR [ebp-0x4] 
8048463: mov DWORD PTR [esp+0x8],0x64  ; <- whoops! compiler still referencing via esp 
804846b: mov DWORD PTR [esp+0x4],0x0  ; <- whoops! compiler still referencing via esp 
8048473: mov DWORD PTR [esp],eax   ; <- whoops! compiler still referencing via esp   
8048476: call 8048338 <[email protected]> 
804847b: mov eax,DWORD PTR [ebp-0x4] 
804847e: mov DWORD PTR [esp+0x8],0xd  ; <- whoops! compiler still referencing via esp 
8048486: mov DWORD PTR [esp+0x4],0x80485a8 ; <- whoops! compiler still referencing via esp 
804848e: mov DWORD PTR [esp],eax   ; <- whoops! compiler still referencing via esp 
8048491: call 8048358 <[email protected]> 
8048496: mov eax,DWORD PTR [ebp-0x4] 
8048499: mov DWORD PTR [esp],eax   ; <- whoops! compiler still referencing via esp 
804849c: call 8048368 <[email protected]> 
80484a1: leave 
80484a2: ret 

आप देख सकते हैं, यह इतना आसान नहीं है:

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

+0

मुझे लगता है कि आप ठीक हैं; ईएसपी एक्सेस फ़ंक्शन कॉल से पहले तर्क लिख रहे हैं, और ईएसपी-रिश्तेदार सही है। आप '-फनो-संचय-आउटगोइंग-एर्ग' का प्रयास कर सकते हैं या जो कुछ भी और संबंधित तर्क हैं, वे स्टैक के नीचे संशोधित करने के लिए एमओवी का उपयोग करने के बजाय बस पुश का उपयोग करने के लिए जीसीसी प्राप्त करना चाहते हैं। –

+0

लेकिन वास्तव में, संकलक के पीछे एलोका को कार्यान्वित करने की कोशिश कर रहा है यह एक * भयानक * विचार है, जैसा कि आप इस उत्कृष्ट उत्तर के शुरुआती हिस्से में इंगित करते हैं। गलत होने के लिए कई तरीके हैं, और ऐसा करने का कोई कारण नहीं है। अगर लोग एएसएम लिखना चाहते हैं और अपना खुद का ढेर आवंटन करना चाहते हैं, तो सी ++ में इनलाइन-एएसएम का दुरुपयोग करने के बजाय बस शुद्ध एएसएम में लिखें। –

+0

@ पीटरकॉर्डस सच है कि अधिकांश ईएसपी संदर्भ कार्य तर्क हैं, लेकिन क्योंकि यह "आवंटित" ** से पहले ** आवंटित करने की कोशिश की गई है, तो ये चाल उपयोगकर्ता की "आवंटित स्थान" पर टंपल हो जाएंगी। अगर मैं उस जगह का उपयोग करना चाहता हूं तो कौन टूटा हुआ है। उचित धक्का देने वालों को बदलने से उनमें से अधिकांश ठीक हो जाएंगे। इसके अलावा अंतिम esp संदर्भ स्थानीय चर में परिणाम संग्रहीत कर रहा है, और एक बार फिर "सरणी" पर ट्रामल हो जाएगा। यह बहुत जल्दी बुरी तरह से चला जाता है। –

-1

एलोका आसान है, आप बस स्टैक पॉइंटर को ऊपर ले जाएं; फिर इस नए ब्लॉक को इंगित करने के लिए सभी पढ़ने/लिखने के लिए

sub esp, 4 
+0

1) यह 2 ईएसआई नहीं है) ढेर करने के लिए उच्च से बढ़ता है कम से कम – newgre

-1

मैं "प्रवेश" निर्देश की अनुशंसा करता हूं। 286 और नए प्रोसेसर पर उपलब्ध ( 186 पर भी उपलब्ध है, मुझे ऑफहैंड याद नहीं है, लेकिन वे वैसे भी व्यापक रूप से उपलब्ध नहीं थे)।

+0

दुर्भाग्य से, प्रवेश निर्देश काफी हद तक बेकार है (उच्च स्तर की भाषा में एलोका लागू करना) क्योंकि आप पर्याप्त कंपाइलर सहयोग नहीं प्राप्त करेंगे। –

+0

आप निश्चित रूप से [ENTER] (http://www.felixcloutier.com/x86/ENTER.html) इनलाइन-एएसएम में नहीं चाहते हैं, क्योंकि यह ईबीपी को ओवरराइट करता है ताकि संकलक यह नहीं जान सके कि इसके स्थानीय लोग कहां हैं।यह आधुनिक सीपीयू पर भी बहुत धीमी है, यही कारण है कि कंपाइलर्स 'पुश ईबीपी/mov ebp, esp/sub esp, n' का उपयोग करते हैं। तो वास्तव में आप कभी भी ENTER नहीं चाहते हैं, भले ही एएसएम में एक स्टैंड-अलोन फ़ंक्शन लिखना हो। –

4

एलोका सीधे असेंबली कोड में लागू किया गया है। ऐसा इसलिए है क्योंकि आप सीधे उच्च स्तर की भाषाओं से स्टैक लेआउट को नियंत्रित नहीं कर सकते हैं।

यह भी ध्यान दें कि अधिकतर कार्यान्वयन प्रदर्शन कारणों के लिए स्टैक को संरेखित करने जैसे कुछ अतिरिक्त अनुकूलन करेगा।

sub esp, XXX 

जबकि XXX बाइट की संख्या allcoate है

संपादित करें::
आप कार्यान्वयन को देखने के लिए चाहते हैं (और X86 पर ढेर अंतरिक्ष आवंटन की मानक तरीका इस तरह दिखता है आप एमएसवीसी का उपयोग कर रहे हैं) alloca16.asm और chkstk.asm देखें।
पहली फ़ाइल में कोड मूल रूप से वांछित आवंटन आकार को 16 बाइट सीमा तक संरेखित करता है। दूसरी फ़ाइल में कोड वास्तव में उन सभी पृष्ठों पर चलता है जो नए स्टैक क्षेत्र से संबंधित होते हैं और उन्हें छूते हैं। यह संभावित रूप से PAGE_GAURD अपवादों को ट्रिगर करेगा जो ओएस द्वारा स्टैक को बढ़ाने के लिए उपयोग किए जाते हैं।

6

यह करना मुश्किल होगा - असल में, जब तक कि आपके पास कंपाइलर कोड कोड पर पर्याप्त नियंत्रण न हो, इसे पूरी तरह से सुरक्षित रूप से नहीं किया जा सकता है। आपके दिनचर्या को ढेर में हेरफेर करना होगा, जैसे कि जब यह लौटाया गया तो सब कुछ साफ हो गया था, लेकिन स्टैक पॉइंटर ऐसी स्थिति में बना रहा कि स्मृति का ब्लॉक उस स्थान पर बना रहा।

समस्या यह है कि जब तक आप संकलक को सूचित नहीं कर सकते कि स्टैक पॉइंटर को आपके फ़ंक्शन कॉल में संशोधित किया गया है, तो यह अच्छी तरह से तय हो सकता है कि यह स्टैक पॉइंटर के माध्यम से अन्य स्थानीय (या जो भी) को संदर्भित कर सकता है - लेकिन ऑफसेट गलत होगा।

4

डी प्रोग्रामिंग भाषा के लिए, alloca() के लिए स्रोत कोड download के साथ आता है। यह कैसे काम करता है काफी अच्छी तरह से टिप्पणी की है। डीएमडी 1 के लिए, यह /dmd/src/phobos/internal/alloca.d में है। Dmd2 के लिए, यह /dmd/src/druntime/src/compiler/dmd/alloca.d में है।

+0

ठीक है, मुझे लगता है कि काफी जवाब है। यह टिप्पणियों में सही कहता है कि यह एक जादू कार्य है और संकलक समर्थन की आवश्यकता है, यानी मैं वही नहीं कर सकता जो मैं चाहता था। हो सकता है कि मैं इसके बजाय मौजूदा alloca() और mixins के साथ ऐसा करने का एक तरीका पता लगाऊंगा। – dsimcha

1

आप Open Watcom की तरह, एक खुला स्रोत सी संकलक के स्रोतों की जांच कर सकते हैं, और यह अपने आप

4

C और C++ मानकों कि alloca() उपयोग ढेर गया है निर्दिष्ट नहीं करते मिल जाए, क्योंकि alloca() नहीं है सी या सी ++ मानकों (या उस मामले के लिए POSIX) में ¹।

एक कंपाइलर ढेर का उपयोग करके alloca() को भी कार्यान्वित कर सकता है। उदाहरण के लिए, एआरएम रीयल व्यू (आरवीसीटी) कंपाइलर alloca() बफर आवंटित करने के लिए malloc() का उपयोग करता है (referenced on their website here), और संकलक को कोड को उत्सर्जित करने का कारण बनता है जो फ़ंक्शन लौटने पर बफर को मुक्त करता है। इसे स्टैक पॉइंटर के साथ खेलने की आवश्यकता नहीं है, लेकिन अभी भी कंपाइलर समर्थन की आवश्यकता है।

माइक्रोसॉफ्ट विज़ुअल सी ++ एक _malloca() समारोह अगर वहाँ ढेर पर पर्याप्त जगह नहीं है ढेर का उपयोग करता है, लेकिन यह _freea() उपयोग करने के लिए, _alloca() के विपरीत, जो/जरूरत नहीं है स्पष्ट मुक्त चाहते फोन करने वाले की आवश्यकता है।

(आपके निपटारे में सी ++ विनाशकों के साथ, आप स्पष्ट रूप से संकलक समर्थन के बिना सफाई कर सकते हैं, लेकिन आप मनमानी अभिव्यक्ति के अंदर स्थानीय चर घोषित नहीं कर सकते हैं, इसलिए मुझे नहीं लगता कि आप alloca() मैक्रो लिख सकते हैं जो RAII का उपयोग करता है। फिर, जाहिरा तौर पर आप alloca() कुछ भाव में वैसे भी उपयोग कर सकते हैं नहीं है (जैसे function parameters)।)

¹ हाँ, यह एक alloca() कि बस system("/usr/games/nethack") कॉल लिखने के लिए कानूनी है।

3

निरंतरता शुद्ध आईएसओ सी ++ में शैली alloca

चर लंबाई सरणी पासिंग। प्रूफ ऑफ कॉन्सेप्ट कार्यान्वयन।

प्रयोग

void foo(unsigned n) 
{ 
    cps_alloca<Payload>(n,[](Payload *first,Payload *last) 
    { 
     fill(first,last,something); 
    }); 
} 

मूल विचार

template<typename T,unsigned N,typename F> 
auto cps_alloca_static(F &&f) -> decltype(f(nullptr,nullptr)) 
{ 
    T data[N]; 
    return f(&data[0],&data[0]+N); 
} 

template<typename T,typename F> 
auto cps_alloca_dynamic(unsigned n,F &&f) -> decltype(f(nullptr,nullptr)) 
{ 
    vector<T> data(n); 
    return f(&data[0],&data[0]+n); 
} 

template<typename T,typename F> 
auto cps_alloca(unsigned n,F &&f) -> decltype(f(nullptr,nullptr)) 
{ 
    switch(n) 
    { 
     case 1: return cps_alloca_static<T,1>(f); 
     case 2: return cps_alloca_static<T,2>(f); 
     case 3: return cps_alloca_static<T,3>(f); 
     case 4: return cps_alloca_static<T,4>(f); 
     case 0: return f(nullptr,nullptr); 
     default: return cps_alloca_dynamic<T>(n,f); 
    }; // mpl::for_each/array/index pack/recursive bsearch/etc variacion 
} 

LIVE DEMO

cps_alloca on github

0

आप c99 की भिन्न लंबाई सरणियों उपयोग नहीं कर सकते हैं, तो आप के लिए एक यौगिक शाब्दिक डाली उपयोग कर सकते हैं एक शून्य सूचक।

#define ALLOCA(sz) ((void*)((char[sz]){0})) 

यह -ansi (एक जीसीसी विस्तार के रूप में) के लिए भी काम करता है और यहां तक ​​कि जब यह एक फ़ंक्शन तर्क होता है;

some_func(&useful_return, ALLOCA(sizeof(struct useless_return))); 

नकारात्मक पक्ष यह है कि जब C++, के रूप में संकलित जी ++> 4.6 आप दे देंगे एक error: taking address of temporary array ... बजना और आईसीसी में शिकायत नहीं है, हालांकि

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