2010-08-03 14 views
18

मैं अपने अपवाद हैंडलर और डीबग फ़ंक्शंस को कॉल स्टैक बैकट्रैस प्रिंट करने में सक्षम होना चाहता हूं, मूल रूप से ग्लिब में बैकट्रैक() लाइब्रेरी फ़ंक्शन की तरह। दुर्भाग्यवश, मेरी सी लाइब्रेरी (न्यूलिब) ऐसी कॉल प्रदान नहीं करती है।कॉल स्टैक बैकट्रैक कैसे प्राप्त करें? (गहराई से एम्बेडेड, कोई पुस्तकालय समर्थन नहीं)

मैं कुछ इस तरह है:

#include <unwind.h> // GCC's internal unwinder, part of libgcc 
_Unwind_Reason_Code trace_fcn(_Unwind_Context *ctx, void *d) 
{ 
    int *depth = (int*)d; 
    printf("\t#%d: program counter at %08x\n", *depth, _Unwind_GetIP(ctx)); 
    (*depth)++; 
    return _URC_NO_REASON; 
} 

void print_backtrace_here() 
{ 
    int depth = 0; 
    _Unwind_Backtrace(&trace_fcn, &depth); 
} 

जो मूल रूप से काम करता है लेकिन जिसके परिणामस्वरूप निशान हमेशा पूरे नहीं हैं। उदाहरण के लिए, यदि मैं

int func3() { print_backtrace_here(); return 0; } 
int func2() { return func3(); } 
int func1() { return func2(); } 
int main() { return func1(); }

बैकट्रैक केवल func3() और मुख्य() दिखाता है। (यह obv है एक खिलौना उदाहरण है, लेकिन मैं disassembly की जाँच की और पुष्टि की है कि इन कार्यों में पूर्ण सब यहाँ और नहीं कर रहे हैं बाहर अनुकूलित या inlined है।।)

अद्यतन: मैं पुराने ARM7 पर इस पश्व-अनुरेखन कोड की कोशिश की सिस्टम लेकिन एक ही (या कम से कम, जितना संभव हो सके समतुल्य) कंपाइलर विकल्प और लिंकर स्क्रिप्ट और यह एक सही, पूर्ण बैकट्रैस प्रिंट करता है (यानी func1 और func2 गुम नहीं हैं) और वास्तव में यह भी बूट प्रारंभ में मुख्य मुख्य को पीछे छोड़ देता है कोड। तो संभवतः समस्या लिंकर स्क्रिप्ट या कंपाइलर विकल्पों के साथ नहीं है। (इसके अलावा, disassembly से पुष्टि की है कि इस एआरएम 7 परीक्षण में कोई फ्रेम सूचक का उपयोग नहीं किया जाता है)।

कोड को फोमिट-फ्रेम-पॉइंटर के साथ संकलित किया गया है, लेकिन मेरा प्लेटफ़ॉर्म (नंगे धातु एआरएम कॉर्टेक्स एम 3) एक एबीआई को परिभाषित करता है जो किसी भी फ्रेम सूचक का उपयोग नहीं करता है। (इस प्रणाली के पिछले संस्करण में पुरानी एपीसीएस एबीआई एआरएम 7 पर मजबूर स्टैक फ्रेम और फ्रेम पॉइंटर के साथ इस्तेमाल किया गया था, और एक here जैसे बैकट्रैक का उपयोग किया गया था, जो पूरी तरह से काम करता था)।

पूरे सिस्टम को अपवाद के साथ संकलित किया गया है, जो आवश्यक मेटाडेटा सुनिश्चित करता है कि _Unwind उपयोग ELF फ़ाइल में शामिल है। (_Unwind मुझे लगता है कि अपवाद हैंडलिंग के लिए बनाया गया है)।

तो, मेरा प्रश्न है: क्या जीसीसी का उपयोग करके एम्बेडेड सिस्टम में विश्वसनीय बैकट्रैक प्राप्त करने का एक "मानक", स्वीकार्य तरीका है?

मुझे आवश्यक होने पर लिंकर स्क्रिप्ट और क्रेट 0 कोड के साथ गड़बड़ करने की कोई बात नहीं है, लेकिन टूलचैन के लिए कोई भी मौका नहीं लेना चाहता।

धन्यवाद!

+0

http: // stackoverflow सहित कई डुप्लिकेट।कॉम/प्रश्न/77005/कैसे-से-जेनरेट-ए-स्टैकट्रैक-कब-my-gcc-c-app-crashes –

+4

नील: क्या आपने सवाल पढ़ा है? (शीर्षक और बोल्ड मुद्रित रेखा के बगल में?) उसे बैकट्रैक मिल जाता है लेकिन इसमें कुछ बुलाए गए कार्यों को याद किया जाता है। – IanH

+0

एंड्रॉइड एनडीके परियोजनाओं में बैकट्रैक प्रिंटिंग प्राप्त करने में यह सहायक था। – chrisvarnz

उत्तर

7

जीसीसी वापस अनुकूलन करता है। Func1() और func2() में यह func2()/func3() को कॉल नहीं करता है - इसके बजाय, यह func2()/func3() पर कूदता है, इसलिए func3() तुरंत मुख्य() पर वापस आ सकता है।

आपके मामले में, func1() और func2() को एक स्टैक फ्रेम सेट करने की आवश्यकता नहीं है, लेकिन यदि वे करेंगे (उदाहरण के लिए स्थानीय चर के लिए), तो जीसीसी अभी भी अनुकूलन कर सकता है यदि फ़ंक्शन कॉल अंतिम निर्देश है - फिर यह func3() पर कूदने से पहले ढेर को साफ करता है।

इसे देखने के लिए जेनरेट किए गए असेंबलर कोड पर एक नज़र डालें।


संपादित करें/अद्यतन:

कि इस कारण से है सत्यापित करने के लिए, समारोह कॉल के बाद कुछ करना, कि संकलक द्वारा पुनर्क्रमित नहीं किया जा सकता है (जैसे एक वापसी मान का उपयोग)। या बस -O0 के साथ संकलन करने का प्रयास करें।

+0

डाउनवॉटेड, उन्होंने कहा कि उन्होंने असेंबलर की जांच की। – Puppy

+0

वह कहता है कि कार्य वहां हैं (रेखांकित नहीं), लेकिन उन्होंने यह नहीं कहा कि क्या उन्होंने जांच की है कि क्या कार्यों को बुलाया जाता है या कूद जाता है। – IanH

+0

@DeadMG: डाउनवोट निश्चित रूप से कठोर है। एआरएम के लिए संकलन करते समय पूंछ कॉल आमतौर पर इस तरह अनुकूलित होते हैं, और यह अनुकूलन वास्तव में मनाए गए परिणाम देगा। –

6

चूंकि एआरएम प्लेटफ़ॉर्म फ़्रेम पॉइंटर का उपयोग नहीं करते हैं, इसलिए आप कभी भी नहीं जानते कि स्टैकफ्रेम कितना बड़ा है और R14 में एकल रिटर्न मान से परे स्टैक को आसानी से नहीं चला सकता है।

एक दुर्घटना की जांच करते समय जिसके लिए हमारे पास डीबग प्रतीकों नहीं हैं, हम बस पूरे ढेर को डंप करते हैं और निर्देश श्रेणी में प्रत्येक आइटम के निकटतम प्रतीक को देखते हैं। यह झूठी सकारात्मक भार उत्पन्न करता है लेकिन दुर्घटनाओं की जांच के लिए अभी भी बहुत उपयोगी हो सकता है।

यदि आप शुद्ध ईएलएफ निष्पादन योग्य चला रहे हैं, तो आप अपने रिलीज निष्पादन योग्य से डीबग प्रतीकों को अलग कर सकते हैं। जीडीबी आपके मानक यूनिक्स कोर डंप से क्या हो रहा है यह जानने में आपकी सहायता कर सकता है

+0

+1 हमने एमआईपीएस – bstpierre

+0

पर कुछ ऐसा किया है, आप स्टैक फ्रेम मैन्युअल रूप से पुनर्निर्माण के लिए अलग किए गए निष्पादन योग्य का उपयोग कर झूठी सकारात्मक को कम कर सकते हैं; स्टैक्ड रजिस्टरों की गणना करने के लिए प्रत्येक फ़ंक्शन के पहले कुछ निर्देशों को देखें, और स्टैक पॉइंटर पर कोई और समायोजन करें। –

+0

नाइटपिक: कुछ एआरएम प्लेटफ़ॉर्म फ़्रेम पॉइंटर (आमतौर पर 'r11') का उपयोग करते हैं। लेकिन यह यहां महत्वपूर्ण नहीं है, क्योंकि प्रश्नकर्ता कहता है कि उसका मंच नहीं है। –

0

क्या आपके निष्पादन योग्य में -g विकल्प के साथ संकलन करने से डीबगिंग जानकारी है? मुझे लगता है कि फ्रेम सूचक के बिना एक पूर्ण स्टैक ट्रेस प्राप्त करने की आवश्यकता है।

आपको यह सुनिश्चित करने के लिए -gdwarf-2 की आवश्यकता हो सकती है कि यह एक प्रारूप का उपयोग करता है जिसमें अनचाहे जानकारी शामिल है।

+0

संभव है, हालांकि मुझे पूरा यकीन है (99.9% की तरह) कि डीडब्ल्यूएआरएफ जानकारी वास्तव में इसे फ्लैश में प्रोग्राम किए गए बाइनरी छवि में नहीं बनाती है। मैं कैसे जांचूँगा? – hugov

9
यह आप -funwind-tables या -fasynchronous-unwind-tables कुछ लक्ष्यों _Unwind_Backtrace ठीक से काम करने के लिए इस क्रम में आवश्यक है की जरूरत के लिए

!

+1

मुझे नहीं पता कि यह विकल्प क्या करता है, लेकिन आपको लिंक करते समय --no-merge-exidx-प्रविष्टियों को निर्दिष्ट करने की भी आवश्यकता हो सकती है। http://old.nabble.com/Stack-backtrace-for-ARM- थंब-td29264138.html –

+0

@ जस्टिनएल। - लिंक वर्तमान मर चुका है, एफडब्ल्यूआईडब्ल्यू। –

3

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

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

आप जीसीसी उपयोग करते हैं, की कोशिश -fno-optimize-sibling-calls

एक और काम जीसीसी विकल्प -mno-sched-prolog, जो अनुदेश इसे पसंद, समारोह प्रस्तावना है, जो महत्वपूर्ण है में पुन: क्रम यदि आप कोड बाइट-दर-बाइट पार्स करने के लिए चाहते हैं, से बचाता है यहां किया जाता है: http://www.kegel.com/stackcheck/checkstack-pl.txt

0

यह hacky है, लेकिन मैंने पाया यह अच्छा काम करता है पर्याप्त कोड/रैम अंतरिक्ष की राशि की आवश्यकता पर विचार:

आप एआरएम अंगूठे मोड का उपयोग कर रहे मानते हुए, निम्नलिखित के साथ संकलन विकल्प:

-mtpcs-frame -mtpcs-leaf-frame -fno-omit-frame-pointer 

कॉलस्टैक पुनर्प्राप्त करने के लिए निम्न फ़ंक्शन का उपयोग किया जाता है। अधिक जानकारी के लिए टिप्पणियों का संदर्भ लें:

/* 
* This should be compiled with: 
* -mtpcs-frame -mtpcs-leaf-frame -fno-omit-frame-pointer 
* 
* With these options, the Stack pointer is automatically pushed to the stack 
* at the beginning of each function. 
* 
* This function basically iterates through the current stack finding the following combination of values: 
* - <Frame Address> 
* - <Link Address> 
* 
* This combination will occur for each function in the call stack 
*/ 
static void backtrace(uint32_t *caller_list, const uint32_t *caller_list_end, const uint32_t *stack_pointer) 
{ 
    uint32_t previous_frame_address = (uint32_t)stack_pointer; 
    uint32_t stack_entry_counter = 0; 

    // be sure to clear the caller_list buffer 
    memset(caller_list, 0, caller_list_end-caller_list); 

    // loop until the buffer is full 
    while(caller_list < caller_list_end) 
    { 
     // Attempt to obtain next stack pointer 
     // The link address should come immediately after 
     const uint32_t possible_frame_address = *stack_pointer; 
     const uint32_t possible_link_address = *(stack_pointer+1); 

     // Have we searched past the allowable size of a given stack? 
     if(stack_entry_counter > PLATFORM_MAX_STACK_SIZE/4) 
     { 
      // yes, so just quite 
      break; 
     } 
     // Next check that the frame addresss (i.e. stack pointer for the function) 
     // and Link address are within an acceptable range 
     else if((possible_frame_address > previous_frame_address) && 
       ((possible_frame_address < previous_frame_address + PLATFORM_MAX_STACK_SIZE)) && 
       ((possible_link_address & 0x01) != 0) && // in THUMB mode the address will be odd 
       (possible_link_address > PLATFORM_CODE_SPACE_START_ADDRESS && 
       possible_link_address < PLATFORM_CODE_SPACE_END_ADDRESS)) 
     { 
      // We found two acceptable values 

      // Store the link address 
      *caller_list++ = possible_link_address; 

      // Update the book-keeping registers for the next search 
      previous_frame_address = possible_frame_address; 
      stack_pointer = (uint32_t*)(possible_frame_address + 4); 
      stack_entry_counter = 0; 
     } 
     else 
     { 
      // Keep iterating through the stack until be find an acceptable combination 
      ++stack_pointer; 
      ++stack_entry_counter; 
     } 
    } 

} 

आपको अपने प्लेटफ़ॉर्म के लिए #defines अपडेट करना होगा।

तो वर्तमान कॉल स्टैक के साथ एक बफर को भरने के लिए निम्नलिखित फोन:

uint32_t callers[8]; 
uint32_t sp_reg; 
__ASM volatile ("mov %0, sp" : "=r" (sp_reg)); 
backtrace(callers, &callers[8], (uint32_t*)sp_reg); 

फिर, यह नहीं बल्कि hacky है, लेकिन मैं इसे बहुत अच्छी तरह से काम करने के लिए मिल गया है। बफर कॉल स्टैक में प्रत्येक फ़ंक्शन कॉल के लिंक पते के साथ पॉप्युलेट किया जाएगा।

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

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