c++ - भरत - मोबाइल नंबर लोकेशन ऐप्स




लाइन नंबर की जानकारी के साथ जीसीसी का उपयोग कर सी++ के लिए स्टैक ट्रेस कैसे प्राप्त करें? (8)

अनिवार्य रूप से एक ही सवाल पर एक मजबूत चर्चा है: जब मेरा जीसीसी सी ++ ऐप दुर्घटनाग्रस्त हो जाता है तो स्टैकट्रैक कैसे उत्पन्न करें । कई सुझाव प्रदान किए जाते हैं, जिसमें रन-टाइम पर स्टैक निशान उत्पन्न करने के बारे में बहुत सी चर्चाएं शामिल हैं।

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

विभिन्न लिनक्स शैल कोर डंप को सक्षम करने के लिए विभिन्न कमांड का उपयोग करते हैं, लेकिन आप इसे अपने एप्लिकेशन कोड के भीतर से ऐसा कुछ कर सकते हैं ...

#include <sys/resource.h>
...
struct rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
assert( setrlimit( RLIMIT_CORE, &core_limit ) == 0 ); // enable core dumps for debug builds

एक दुर्घटना के बाद, कार्यक्रम राज्य की जांच करने के लिए अपने पसंदीदा डीबगर चलाएं।

$ kdbg executable core

यहां कुछ नमूना आउटपुट है ...

कमांड लाइन पर कोर डंप से स्टैक ट्रेस निकालना भी संभव है।

$ ( CMDFILE=$(mktemp); echo "bt" >${CMDFILE}; gdb 2>/dev/null --batch -x ${CMDFILE} temp.exe core )
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 22857]
#0  0x00007f4189be5fb5 in raise () from /lib/libc.so.6
#0  0x00007f4189be5fb5 in raise () from /lib/libc.so.6
#1  0x00007f4189be7bc3 in abort () from /lib/libc.so.6
#2  0x00007f4189bdef09 in __assert_fail () from /lib/libc.so.6
#3  0x00000000004007e8 in recursive (i=5) at ./demo1.cpp:18
#4  0x00000000004007f3 in recursive (i=4) at ./demo1.cpp:19
#5  0x00000000004007f3 in recursive (i=3) at ./demo1.cpp:19
#6  0x00000000004007f3 in recursive (i=2) at ./demo1.cpp:19
#7  0x00000000004007f3 in recursive (i=1) at ./demo1.cpp:19
#8  0x00000000004007f3 in recursive (i=0) at ./demo1.cpp:19
#9  0x0000000000400849 in main (argc=1, argv=0x7fff2483bd98) at ./demo1.cpp:26

हम डेवलपर गलतियों को पकड़ने के लिए मैक्रो जैसे स्वामित्व वाले assert में स्टैक निशान का उपयोग करते हैं - जब त्रुटि पकड़ी जाती है, तो स्टैक ट्रेस मुद्रित होता है।

मुझे जीसीसी की जोड़ी backtrace() / backtrace_symbols() विधियों अपर्याप्त लगता है:

  1. नाम उलझन में हैं
  2. कोई लाइन जानकारी नहीं

पहली समस्या abi::__cxa_demangle द्वारा हल की जा सकती है।

हालांकि दूसरी समस्या अधिक कठिन है। मुझे backtrace_symbols () के लिए प्रतिस्थापन मिला। यह gcc के backtrace_symbols () से बेहतर है, क्योंकि यह लाइन नंबर पुनर्प्राप्त कर सकता है (यदि -g के साथ संकलित किया गया है) और आपको-गतिशील के साथ संकलन करने की आवश्यकता नहीं है।

Hoverer कोड जीएनयू लाइसेंस प्राप्त है, इसलिए IMHO मैं इसे वाणिज्यिक कोड में उपयोग नहीं कर सकता।

कोई प्रस्ताव?

पुनश्च

जीडीबी कार्यों को पारित तर्कों को मुद्रित करने में सक्षम है। शायद यह पूछने के लिए पहले से ही बहुत अधिक है :)

पीएस 2

इसी तरह का सवाल (धन्यवाद महान)


आप DeathHandler उपयोग कर सकते हैं - छोटी सी ++ कक्षा जो आपके लिए सबकुछ विश्वसनीय बनाती है।


चूंकि जीपीएल लाइसेंस प्राप्त कोड का उद्देश्य विकास के दौरान आपकी मदद करने के लिए है, इसलिए आप इसे अंतिम उत्पाद में शामिल नहीं कर सकते हैं। जीपीएल आपको गैर-जीपीएल संगत कोड से जुड़े जीपीएल लाइसेंस कोड को वितरित करने से रोकता है। जब तक आप केवल जीपीएल कोड इनहाउस का उपयोग करते हैं, तो आपको ठीक होना चाहिए।


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

यह एक बच्चे की प्रक्रिया में जीडीबी निष्पादित करके, कांटा () का उपयोग करके किया जाता है, और इसे एक स्टैक-ट्रेस प्रदर्शित करने के लिए स्क्रिप्टिंग किया जाता है जबकि आपका एप्लिकेशन इसे पूरा करने की प्रतीक्षा करता है। यह कोर-डंप के उपयोग के बिना और एप्लिकेशन को निरस्त किए बिना किया जा सकता है। मैंने सीखा है कि इस सवाल को देखने से ऐसा कैसे करें : प्रोग्राम से जीडीबी को अपने स्टैकट्रैक को प्रिंट करने के लिए कैसे बेहतर है?

उस प्रश्न के साथ पोस्ट किया गया उदाहरण मेरे लिए बिल्कुल सही नहीं है, इसलिए मेरा "निश्चित" संस्करण है (मैंने इसे उबंटू 9.04 पर चलाया)।

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

void print_trace() {
    char pid_buf[30];
    sprintf(pid_buf, "%d", getpid());
    char name_buf[512];
    name_buf[readlink("/proc/self/exe", name_buf, 511)]=0;
    int child_pid = fork();
    if (!child_pid) {           
        dup2(2,1); // redirect output to stderr
        fprintf(stdout,"stack trace for %s pid=%s\n",name_buf,pid_buf);
        execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, NULL);
        abort(); /* If gdb failed to start */
    } else {
        waitpid(child_pid,NULL,0);
    }
}

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

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

यहां इस विधि के साथ देखे गए स्टैक निशान का एक उदाहरण दिया गया है।

0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
[Current thread is 0 (process 15573)]
#0  0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
#1  0x0000000000400bd5 in print_trace () at ./demo3b.cpp:496
2  0x0000000000400c09 in recursive (i=2) at ./demo3b.cpp:636
3  0x0000000000400c1a in recursive (i=1) at ./demo3b.cpp:646
4  0x0000000000400c1a in recursive (i=0) at ./demo3b.cpp:646
5  0x0000000000400c46 in main (argc=1, argv=0x7fffe3b2b5b8) at ./demo3b.cpp:70

नोट: मुझे यह valgrind के उपयोग के साथ असंगत पाया गया है (शायद वाल्ग्रींड के वर्चुअल मशीन के उपयोग के कारण)। जब आप किसी जीडीबी सत्र के अंदर प्रोग्राम चला रहे हों तो यह भी काम नहीं करता है (प्रक्रिया में "ptrace" का दूसरा उदाहरण लागू नहीं कर सकता है)।


बहुत समय पहले मैंने एक समान सवाल का जवाब नहीं दिया था। आपको विधि # 4 पर उपलब्ध स्रोत कोड पर एक नज़र डालना चाहिए, जो लाइन नंबर और फ़ाइल नामों को भी प्रिंट करता है।


मुझे लगता है कि रेखा संख्या वर्तमान ईआईपी मूल्य से संबंधित हैं, है ना?

समाधान 1:
फिर आप GetThreadContext() जैसे कुछ का उपयोग कर सकते हैं, सिवाय इसके कि आप लिनक्स पर काम कर रहे हैं। मैं थोड़ी देर के आसपास googled और कुछ समान पाया, ptrace() :

Ptrace () सिस्टम कॉल एक माध्यम प्रदान करता है जिसके द्वारा एक मूल प्रक्रिया किसी अन्य प्रक्रिया के निष्पादन को देख और नियंत्रित कर सकती है, और इसकी मूल छवि और रजिस्टरों की जांच और परिवर्तन कर सकती है। [...] पैरेंट फोर्क (2) को कॉल करके एक ट्रेस शुरू कर सकता है और जिसके परिणामस्वरूप बच्चे एक PTRACE_TRACEME कर सकता है, उसके बाद (आमतौर पर) निष्पादन (3) द्वारा किया जाता है। वैकल्पिक रूप से, माता-पिता PTRACE_ATTACH का उपयोग कर मौजूदा प्रक्रिया का पता लगा सकते हैं।

अब मैं सोच रहा था, आप एक 'मुख्य' कार्यक्रम कर सकते हैं जो आपके बच्चे को भेजे गए सिग्नल की जांच करता है, वास्तविक कार्यक्रम जिस पर आप काम कर रहे हैं। fork() बाद fork() यह waitid() करें waitid() :

इन सभी सिस्टम कॉल का उपयोग कॉलिंग प्रक्रिया के बच्चे में राज्य परिवर्तनों की प्रतीक्षा करने के लिए किया जाता है, और उस बच्चे के बारे में जानकारी प्राप्त होती है जिसका राज्य बदल गया है।

और अगर eip के मूल्य प्राप्त करने के लिए एक SIGSEGV (या कुछ समान) पकड़ा जाता है तो कॉल ptrace()

पीएस: मैंने इन सिस्टम कॉलों का कभी भी उपयोग नहीं किया है (ठीक है, वास्तव में, मैंने उन्हें पहले कभी नहीं देखा है;) इसलिए मुझे नहीं पता कि यह संभव है और न ही आपकी मदद कर सकता है। कम से कम मुझे उम्मीद है कि ये लिंक उपयोगी हैं। ;)

समाधान 2: पहला समाधान काफी जटिल है, है ना? मैं एक बहुत ही सरल के साथ आया: signal() का उपयोग करके उन संकेतों को eip जिन्हें आप रुचि रखते हैं और एक साधारण फ़ंक्शन को कॉल करते हैं जो स्टैक में संग्रहीत eip मान को पढ़ता है:

...
signal(SIGSEGV, sig_handler);
...

void sig_handler(int signum)
{
    int eip_value;

    asm {
        push eax;
        mov eax, [ebp - 4]
        mov eip_value, eax
        pop eax
    }

    // now you have the address of the
    // **next** instruction after the
    // SIGSEGV was received
}

वह एएसएम वाक्यविन्यास बोरलैंड का है, बस इसे GAS अनुकूलित करें। ;)


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

सामान्य आवेषण की तुलना में इस दृष्टिकोण का लाभ यह है कि आप debug_assert ट्रिगर होने के बाद प्रोग्राम को चलाना जारी रख सकते हैं (जब डीबगर में चलते हैं)। दूसरे शब्दों में, debug_assert () जोर से अधिक लचीला है ()।

   #include <iostream>
   #include <cassert>
   #include <sys/resource.h> 

// note: The assert expression should show up in
// stack trace as parameter to this function
void debug_breakpoint( char const * expression )
   {
   asm("int3"); // x86 specific
   }

#ifdef NDEBUG
   #define debug_assert( expression )
#else
// creates a conditional breakpoint
   #define debug_assert( expression ) \
      do { if ( !(expression) ) debug_breakpoint( #expression ); } while (0)
#endif

void recursive( int i=0 )
   {
   debug_assert( i < 5 );
   if ( i < 10 ) recursive(i+1);
   }

int main( int argc, char * argv[] )
   {
   rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
   setrlimit( RLIMIT_CORE, &core_limit ); // enable core dumps
   recursive();
   }

नोट: कभी-कभी डिबगर्स के भीतर "सशर्त ब्रेकपॉइंट्स" सेटअप धीमा हो सकता है। ब्रेकपॉइंट प्रोग्रामेटिक रूप से स्थापित करके, इस विधि का प्रदर्शन सामान्य assert () के बराबर होना चाहिए।

नोट: जैसा लिखा है, यह इंटेल x86 आर्किटेक्चर के लिए विशिष्ट है - अन्य प्रोसेसर के पास ब्रेकपॉइंट उत्पन्न करने के लिए अलग-अलग निर्देश हो सकते हैं।


समाधानों में से एक है जीटीबी "बीटी" -स्क्रिप्ट के साथ विफल assert हैंडलर में शुरू करना है। इस तरह के जीडीबी शुरू करने को एकीकृत करना बहुत आसान नहीं है, लेकिन यह आपको बैकट्रैक और तर्क और नामों को विचलित कर देगा (या आप सी ++ फाइल प्रोग्राम के माध्यम से जीडीबी आउटपुट पास कर सकते हैं)।

दोनों प्रोग्राम (जीडीबी और सी ++ फिल्ट) आपके आवेदन में शामिल नहीं होंगे, इसलिए जीपीएल आपको ओपनसोर्स पूर्ण एप्लिकेशन की आवश्यकता नहीं होगी।

एक ही दृष्टिकोण (एक जीपीएल प्रोग्राम निष्पादित करें) आप बैकट्रैस-प्रतीकों के साथ उपयोग कर सकते हैं। बस% eip की एसीआईआई सूची और निष्पादन फ़ाइल (/ proc / self / maps) के मानचित्र उत्पन्न करें और इसे बाइनरी अलग करने के लिए पास करें।






stack-trace