2012-02-24 13 views
5

मैं एक कक्षा पर काम कर रहा हूं जिसे मैं विंडोज विस्टा/7 के साथ कंप्यूटर पर वर्तमान कॉल स्टैक लॉग करने के लिए उपयोग करना चाहता हूं। ("कॉलस्टैक चलना" के समान ही http://www.codeproject.com/Articles/11132/Walking-the-callstack)।सी ++ स्टैक ट्रेसिंग समस्या

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

मुझे कॉलम फ़ंक्शन, कोड फ़ाइल और लाइन नंबर के बारे में जानकारी प्राप्त करने के लिए SymFromAddr और SymGetLineFromAddr64 का उपयोग करने के लिए पीसी-पता की आवश्यकता है। दुर्भाग्यवश यह केवल तब तक काम करता है जब तक प्रोग्राम-डीबग-डाटाबेस (पीडीबी-फाइल) आसपास है, लेकिन मुझे क्लाइंट को यह उपलब्ध कराने की अनुमति नहीं है।

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

क्या आप कॉल स्टैक पढ़ने या बदलने वाले प्रोग्राम काउंटर के साथ समस्या को दूर करने के लिए एक बेहतर तरीका जानते हैं?

मुझे लगता है कि एक संभावित समाधान हमेशा एक ज्ञात स्थान के पीसी-पते को प्राप्त करना और विभिन्न पीसी-पते के बीच ऑफसेट निर्धारित करने के संदर्भ के रूप में उपयोग करना है। यह काम करने लगता है, लेकिन मुझे यकीन नहीं है कि यह एक वैध विधि है और हमेशा काम करेगा।

आपकी मदद के लिए बहुत बहुत धन्यवाद! मैं codeproject.com में अंतिम (encapsulated) समाधान प्रकाशित करूंगा और यदि आप चाहें तो मैं कहूंगा कि आपने मेरी मदद की है।

+1

मेरा कार्यान्वयन देखें: http://www.dima.to/blog/?p=13 – Alexandru

+0

ब्लॉग में आपकी टिप्पणियों के अनुसार आपके कार्यान्वयन के लिए पीडीबी-एस की आवश्यकता है। –

उत्तर

4

का उपयोग करते हुए जानकारी प्रपत्र CONTEXT आप समारोह अनुभाग ढूंढें और पीई छवि में ऑफसेट कर सकते हैं। उदाहरण के लिए, आप लिंकर द्वारा जेनरेट की गई .map फ़ाइल से फ़ंक्शन नाम प्राप्त करने के लिए इस जानकारी का उपयोग कर सकते हैं।

  1. CONTEXT संरचना प्राप्त करें। आप कार्यक्रम काउंटर सदस्य में रुचि रखते हैं। चूंकि CONTEXT मंच पर निर्भर है, आप अपने आप के लिए यह पता लगाने के लिए है। जब आप प्रारंभ करते हैं, तो आप इसे पहले ही करते हैं, उदाहरण के लिए x64 विंडोज के लिए STACKFRAME64.AddrPC.Offset = CONTEXT.Rip। अब हम स्टैक पैदल शुरू करते हैं और STACKFRAME64.AddrPC.Offset का उपयोग करते हैं, जो हमारे शुरुआती बिंदु के रूप में StaclkWalk64 से भरा हुआ है।

  2. आवंटन आधार पते का उपयोग करके आपको इसे रिलेटिव वर्चुअल एड्रेस (आरवीए) में अनुवाद करने की आवश्यकता है: RVA = STACKFRAME64.AddrPC.Offset - AllocationBase। आप VirtualQuery का उपयोग कर AllocationBase मिल सकती है।

  3. एक बार आपके पास यह हो जाने के बाद, आपको अनुभाग आरएफए गिरने और धाराऑफसेट प्राप्त करने के लिए सेक्शन प्रारंभ पता घटाए जाने की आवश्यकता है: SectionOffset = RVA - SectionBase = STACKFRAME64.AddrPC.Offset - AllocationBase - SectionBase। ऐसा करने के लिए आपको पीई छवि हेडर संरचना (IMAGE_DOS_HEADER, IMAGE_NT_HEADER, IMAGE_SECTION_HEADER) तक पहुंचने की आवश्यकता है ताकि पीई और उनके प्रारंभ/अंत पते में अनुभागों की संख्या प्राप्त हो सके। यह बहुत सरल है।

यह है कि। अब आपके पास पीई छवि में सेक्शन नंबर और ऑफ़सेट है। फंक्शन ऑफसेट .map फ़ाइल में सेक्शनऑफसेट से कम उच्चतम ऑफ़सेट है।

यदि आप चाहें तो मैं बाद में कोड पोस्ट कर सकता हूं।

संपादित करें: कोड मुद्रित करने के लिए function address (हम 64 सामान्य सीपीयू मान):

#include <iostream> 
#include <windows.h> 
#include <dbghelp.h> 

void GenerateReport(void) 
{ 
    ::CONTEXT lContext; 
    ::ZeroMemory(&lContext, sizeof(::CONTEXT)); 
    ::RtlCaptureContext(&lContext); 

    ::STACKFRAME64 lFrameStack; 
    ::ZeroMemory(&lFrameStack, sizeof(::STACKFRAME64)); 
    lFrameStack.AddrPC.Offset = lContext.Rip; 
    lFrameStack.AddrFrame.Offset = lContext.Rbp; 
    lFrameStack.AddrStack.Offset = lContext.Rsp; 
    lFrameStack.AddrPC.Mode = lFrameStack.AddrFrame.Mode = lFrameStack.AddrStack.Mode = AddrModeFlat; 

    ::DWORD lTypeMachine = IMAGE_FILE_MACHINE_AMD64; 

    for(auto i = ::DWORD(); i < 32; i++) 
    { 
    if(!::StackWalk64(lTypeMachine, ::GetCurrentProcess(), ::GetCurrentThread(), &lFrameStack, lTypeMachine == IMAGE_FILE_MACHINE_I386 ? 0 : &lContext, 
      nullptr, &::SymFunctionTableAccess64, &::SymGetModuleBase64, nullptr)) 
    { 
     break; 
    } 
    if(lFrameStack.AddrPC.Offset != 0) 
    { 
     ::MEMORY_BASIC_INFORMATION lInfoMemory; 
     ::VirtualQuery((::PVOID)lFrameStack.AddrPC.Offset, &lInfoMemory, sizeof(lInfoMemory)); 
     ::DWORD64 lBaseAllocation = reinterpret_cast<::DWORD64>(lInfoMemory.AllocationBase); 

     ::TCHAR lNameModule[ 1024 ]; 
     ::GetModuleFileName(reinterpret_cast<::HMODULE>(lBaseAllocation), lNameModule, 1024); 

     PIMAGE_DOS_HEADER lHeaderDOS = reinterpret_cast<PIMAGE_DOS_HEADER>(lBaseAllocation); 
     PIMAGE_NT_HEADERS lHeaderNT = reinterpret_cast<PIMAGE_NT_HEADERS>(lBaseAllocation + lHeaderDOS->e_lfanew); 
     PIMAGE_SECTION_HEADER lHeaderSection = IMAGE_FIRST_SECTION(lHeaderNT); 
     ::DWORD64 lRVA = lFrameStack.AddrPC.Offset - lBaseAllocation; 
     ::DWORD64 lNumberSection = ::DWORD64(); 
     ::DWORD64 lOffsetSection = ::DWORD64(); 

     for(auto lCnt = ::DWORD64(); lCnt < lHeaderNT->FileHeader.NumberOfSections; lCnt++, lHeaderSection++) 
     { 
     ::DWORD64 lSectionBase = lHeaderSection->VirtualAddress; 
     ::DWORD64 lSectionEnd = lSectionBase + max(lHeaderSection->SizeOfRawData, lHeaderSection->Misc.VirtualSize); 
     if((lRVA >= lSectionBase) && (lRVA <= lSectionEnd)) 
     { 
      lNumberSection = lCnt + 1; 
      lOffsetSection = lRVA - lSectionBase; 
      break; 
     } 
     }  
     std::cout << lNameModule << " : 000" << lNumberSection << " : " << reinterpret_cast< void * >(lOffsetSection) << std::endl; 
    } 
    else 
    { 
     break; 
    } 
    } 
} 

void Run(void); 
void Run(void) 
{ 
GenerateReport(); 
std::cout << "------------------" << std::endl; 
} 

int main(void) 
{ 
    ::SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS); 
    ::SymInitialize(::GetCurrentProcess(), 0, 1); 

    try 
    { 
    Run(); 
    } 
    catch(...) 
    { 
    } 
    ::SymCleanup(::GetCurrentProcess()); 

    return (0); 
} 

सूचना, हमारे कॉल स्टैक (अंदर बाहर) है GenerateReport()->Run()->main()। कार्यक्रम निर्गम (मेरी मशीन पर, पथ निरपेक्ष है):

D:\Work\C++\Source\Application\Prototype.Console\Prototype.Console.exe : 0001 : 0000000000002F8D 
D:\Work\C++\Source\Application\Prototype.Console\Prototype.Console.exe : 0001 : 00000000000031EB 
D:\Work\C++\Source\Application\Prototype.Console\Prototype.Console.exe : 0001 : 0000000000003253 
D:\Work\C++\Source\Application\Prototype.Console\Prototype.Console.exe : 0001 : 0000000000007947 
C:\Windows\system32\kernel32.dll : 0001 : 000000000001552D 
C:\Windows\SYSTEM32\ntdll.dll : 0001 : 000000000002B521 
------------------ 

अब, पते के मामले में कॉल स्टैक (अंदर बाहर) 00002F8D->000031EB->00003253->00007947->0001552D->0002B521 है। मुकाबले के पहले तीन .map फ़ाइल सामग्री के लिए ऑफसेट:

... 

0001:00002f40  [email protected]@YAXXZ  0000000140003f40 f FMain.obj 
0001:000031e0  [email protected]@YAXXZ    00000001400041e0 f FMain.obj 
0001:00003220  main      0000000140004220 f FMain.obj 

... 

जहां 00002f40 निकटतम छोटे 00002F8D को ऑफसेट और इतने पर है। पिछले तीन पतों सीआरटी/ओएस कार्यों कि फोन main (_tmainCRTstartup आदि) का उल्लेख है - हम उन्हें अनदेखा करना चाहिए ...

तो, हम देख सकते हैं कि हम .map फ़ाइल की मदद से स्टैक ट्रेस ठीक करने के लिए सक्षम हैं।फेंकने वाले अपवाद के लिए स्टैक ट्रेस उत्पन्न करने के लिए, आपको केवल GenerateReport() कोड अपवाद कन्स्ट्रक्टर में रखना है (वास्तव में, यह GenerateReport() मेरे कस्टम अपवाद वर्ग कन्स्ट्रक्टर कोड (इसके कुछ भाग) से लिया गया था)।

+0

वाह, यह बहुत शक्तिशाली लगता है और मुझे यह देखना अच्छा लगेगा कि आप इसे कैसे कार्यान्वित करेंगे! मैं .map फ़ाइल बनाने में कामयाब रहा। अगर आप मेरी मदद करना चाहते हैं तो कृपया मुझे बताएं। – user667967

+0

@ user667967 कोड पोस्ट किया गया है। – lapk

+1

आपकी मदद के लिए बहुत बहुत धन्यवाद! मैंने अभी आपके कोड का परीक्षण किया और यह काम करता है! – user667967

1

मैं अपने दृश्य स्टूडियो परियोजना की सेटिंग को देख सुझाव है: Linker-> Advanced-> अपने सभी कार्यक्रमों के लिए यादृच्छिक बेस पता और आश्रित DLLs (है कि आप फिर से बनाया जा सकता है) और फिर कोशिश करें। यही एकमात्र चीज है जो दिमाग में आती है।

आशा है कि मदद करता है।

+1

एएसएलआर परिस्थिति मूर्खतापूर्ण है। –

+0

त्वरित और गंदे समाधान के लिए धन्यवाद! मैं इसे अल्प अवधि में उपयोग करूंगा। – user667967

2

आपको प्रोग्राम की चल रही मेमोरी मैपिंग भेजने की आवश्यकता है जो आपको क्लाइंट से लोड की गई मूल पता लाइब्रेरी/प्रोग्राम बताती है।

फिर आप मूल पते के साथ प्रतीक की गणना कर सकते हैं।

3

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

  • विंडोज विशिष्ट DbgHlp मिनीडम्प एपीआई: MiniDumpWriteDump। आप ऐप को इसे सीधे कॉल नहीं करना चाहिए, लेकिन इसके बजाय आपको एक छोटे से .exe के साथ भेजना चाहिए कि यह एक प्रक्रिया की एक डंप (तर्क के रूप में दी गई प्रक्रिया आईडी) और आपके ऐप को त्रुटि स्थिति का सामना करते समय, इसे लॉन्च करना चाहिए। exe और फिर इसके पूरा होने के लिए वेटर। इसका कारण यह है कि 'डम्पर' प्रक्रिया डंप के दौरान डंप की गई प्रक्रिया को स्थिर कर देगी, इसलिए डंप होने वाली प्रक्रिया डंप लेने वाली प्रक्रिया नहीं हो सकती है। यह योजना उन सभी ऐप्स के साथ आम है जो WER लागू करती हैं। उल्लेख नहीं है कि परिणामस्वरूप डंप एक वास्तविक है। एमडीएमपी जिसे आप WinDbg में लोड कर सकते हैं (या VisualStudio में यदि यह आपकी कल्पना है)।

  • क्रॉस प्लेटफॉर्म खुला स्रोत समाधान: Breakpad। क्रोम, फ़ायरफ़ॉक्स, पिकासा और अन्य प्रसिद्ध ऐप्स द्वारा उपयोग किया जाता है।

तो, मुख्य रूप से, पहिया को फिर से शुरू न करें। (अपने कोड को डिजिटल रूप में अर्हता प्राप्त करने हस्ताक्षर किया जाना चाहिए) रिपोर्टिंग त्रुटि के लिए, एकत्रीकरण, सूचनाएं, ट्रैकिंग और स्वचालित क्लाइंट प्रतिक्रियाओं, ऊपर उल्लिखित WER माइक्रोसॉफ्ट द्वारा की पेशकश की तरह की तरह एक ओर नोट के रूप में, वहाँ भी सेवाओं है कि कर रहे हैं मूल्य जोड़ देता हूँ, airbreak.io, exceptioneer.com , bugcollect.com (यह एक सही मायने में तुम्हारा द्वारा पैदा करते हैं) और अन्य, लेकिन afaik। केवल WER देशी विंडोज ऐप्स के साथ काम करता है।

+0

+1 मुझे इस तरह के विस्तृत उत्तर पसंद है। –

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