2012-02-24 40 views
5

Tôi đang làm việc trên một lớp mà tôi muốn sử dụng để đăng nhập ngăn xếp cuộc gọi hiện tại trên máy tính bằng Windows Vista/7. (Rất giống với “Đi bộ dây đeo” http://www.codeproject.com/Articles/11132/Walking-the-callstack).C++ Stack Tracing issue

Trước tiên tôi đã sử dụng RtlCaptureContext để lấy bản ghi ngữ cảnh hiện tại sau đó tôi đã sử dụng StackWalk64 để nhận các khung ngăn xếp riêng lẻ. Bây giờ, tôi nhận ra rằng các truy cập chương trình trong STACKFRAME64.AddrPC thực sự thay đổi cho một dòng mã cụ thể bất cứ khi nào tôi đóng chương trình của tôi và bắt đầu lại. Vì một lý do nào đó, tôi nghĩ rằng Địa chỉ PC cho một dòng mã cụ thể sẽ vẫn giữ nguyên miễn là tôi không thay đổi mã nguồn và biên dịch lại mã nguồn.

Tôi cần Địa chỉ máy tính để sử dụng SymFromAddr và SymGetLineFromAddr64 để nhận thông tin về hàm được gọi, tệp mã và số dòng. Thật không may rằng chỉ hoạt động miễn là Chương trình-Debug-Cơ sở dữ liệu (PDB-File) là xung quanh, nhưng tôi không được phép cung cấp cho khách hàng.

Kế hoạch của tôi là ghi lại địa chỉ PC của ngăn xếp cuộc gọi (bất cứ khi nào cần) và sau đó gửi nó từ máy khách cho tôi. Vì vậy, tôi có thể sử dụng PDB-Files của tôi để tìm ra các chức năng được gọi nhưng tất nhiên chỉ hoạt động nếu các địa chỉ PC là các định danh duy nhất. Vì chúng thay đổi mỗi khi tôi bắt đầu chương trình, tôi không thể sử dụng phương pháp đó.

Bạn có biết cách tốt hơn để đọc ngăn xếp cuộc gọi hoặc để khắc phục sự cố với bộ đếm chương trình thay đổi không?

Tôi nghĩ một giải pháp có thể là luôn nhận được địa chỉ máy tính của một vị trí đã biết và sử dụng nó làm tham chiếu để xác định chỉ chênh lệch giữa các địa chỉ PC khác nhau. Điều đó dường như hoạt động, nhưng tôi không chắc đó có phải là phương pháp hợp lệ hay không và sẽ luôn hoạt động.

Cảm ơn bạn rất nhiều vì đã giúp đỡ! Tôi sẽ xuất bản giải pháp cuối cùng (đóng gói) trong codeproject.com và NẾU BẠN THÍCH tôi sẽ nói rằng bạn đã giúp tôi.

+1

Kiểm tra triển khai của tôi: http://www.dima.to/blog/?p=13 – Alexandru

+0

Theo nhận xét của bạn trong blog, việc triển khai của bạn yêu cầu pdb-s. –

Trả lời

4

Sử dụng biểu mẫu thông tin CONTEXT bạn có thể tìm phần chức năng và bù đắp trong hình ảnh PE. Ví dụ, bạn có thể sử dụng thông tin này để lấy tên hàm từ tệp .map được tạo bởi trình liên kết.

  1. Nhận CONTEXT struct. Bạn quan tâm đến thành viên truy cập chương trình. Vì CONTEXT phụ thuộc vào nền tảng, bạn phải tự mình tìm ra. Bạn đã làm điều đó khi bạn khởi tạo, ví dụ STACKFRAME64.AddrPC.Offset = CONTEXT.Rip cho x64 Windows. Bây giờ chúng tôi bắt đầu ngăn xếp đi bộ và sử dụng STACKFRAME64.AddrPC.Offset, được điền bởi StaclkWalk64 làm điểm bắt đầu của chúng tôi.

  2. Bạn cần dịch nó sang Địa chỉ ảo tương đối (RVA) bằng địa chỉ cơ sở phân bổ: RVA = STACKFRAME64.AddrPC.Offset - AllocationBase. Bạn có thể nhận được AllocationBase sử dụng VirtualQuery.

  3. Khi bạn có điều đó, bạn cần phải tìm phần RVA này nằm ở đâu và trừ địa chỉ bắt đầu phần khỏi nó để lấy SectionOffset: SectionOffset = RVA - SectionBase = STACKFRAME64.AddrPC.Offset - AllocationBase - SectionBase. Để thực hiện điều đó, bạn cần truy cập cấu trúc tiêu đề hình ảnh PE (IMAGE_DOS_HEADER, IMAGE_NT_HEADER, IMAGE_SECTION_HEADER) để lấy số phần trong PE và địa chỉ xuất phát/kết thúc của chúng. Nó khá đơn giản.

Vậy đó. Bây giờ bạn có số phần và bù đắp trong hình ảnh PE. Chức năng bù đắp là bù đắp cao nhất nhỏ hơn SectionOffset trong tập tin .map.

Tôi có thể đăng mã sau, nếu bạn muốn.

EDIT: Mã để in function address (chúng tôi giả định x64 CPU generic):

#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); 
} 

Thông báo, gọi chồng chúng tôi là (từ trong ra ngoài) GenerateReport()->Run()->main(). đầu ra Chương trình (trên máy tính của tôi, con đường là tuyệt đối):

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 
------------------ 

Bây giờ, cuộc gọi stack về địa chỉ là (từ trong ra ngoài) 00002F8D->000031EB->00003253->00007947->0001552D->0002B521. So sánh ba offsets đầu tiên nội dung .map file:

... 

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 

... 

nơi 00002f40 là gần nhất nhỏ hơn bù đắp cho 00002F8D và vân vân. Cuối cùng ba địa chỉ tham khảo chức năng CRT/hệ điều hành mà gọi main (_tmainCRTstartup vv) - chúng ta nên bỏ qua chúng ...

Vì vậy, chúng ta có thể thấy rằng chúng tôi có thể phục hồi vết đống với sự giúp đỡ của .map tập tin.Để tạo ra stack trace cho ném exception, tất cả những gì bạn phải làm là đặt mã GenerateReport() vào constructor ngoại lệ (trên thực tế, GenerateReport() này được lấy từ mã constructor class ngoại lệ tùy chỉnh của tôi (một phần của nó)).

+0

Wow, nghe có vẻ rất mạnh mẽ và tôi rất thích xem bạn sẽ thực hiện điều đó như thế nào! Tôi đã quản lý để tạo tập tin .map. Vui lòng cho tôi biết nếu bạn muốn giúp tôi. – user667967

+0

@ user667967 Mã được đăng. – lapk

+1

Cảm ơn bạn rất nhiều vì sự giúp đỡ của bạn! Tôi vừa kiểm tra mã của bạn và nó hoạt động! – user667967

1

tôi sẽ đề nghị xem xét thiết lập dự án Visual Studio của bạn: Linker-> Advanced-> ngẫu nhiên cơ sở Địa chỉ cho tất cả các chương trình của bạn và dlls phụ thuộc (mà bạn có thể xây dựng lại) và thử lại. Đó là điều duy nhất nảy ra trong đầu.

Hy vọng điều đó sẽ hữu ích.

+1

Phá vỡ ASLR là không khôn ngoan. –

+0

Cảm ơn vì giải pháp nhanh chóng và bẩn thỉu! Tôi sẽ sử dụng nó trong ngắn hạn. – user667967

2

Bạn cần gửi bản đồ bộ nhớ đang chạy của chương trình cho biết thư viện/chương trình địa chỉ cơ sở được tải từ máy khách cho bạn.

Sau đó, bạn có thể tính toán biểu tượng bằng địa chỉ cơ sở.

3

Bản thân ngăn xếp không đủ, bạn cần bản đồ mô-đun nạp để bạn có thể kết hợp bất kỳ địa chỉ nào (ngẫu nhiên, đúng) với mô-đun và định vị biểu tượng PDB. Nhưng bạn đang thực sự reinventing the wheel, bởi vì có ít nhất hai cũng được hỗ trợ out-of-the-box giải pháp để giải quyết vấn đề này:

  • Windows DbgHlp cụ minidump API: MiniDumpWriteDump. Ứng dụng của bạn không nên gọi trực tiếp, nhưng thay vào đó bạn nên gửi kèm một tệp .exe nhỏ mà tất cả nó thực hiện lấy một kết xuất (quá trình ID được đặt làm đối số) và ứng dụng của bạn, khi gặp phải tình trạng lỗi, nên khởi chạy điều này. exe và sau đó waitr cho hoàn thành của nó. Lý do là quá trình 'dumper' sẽ làm đóng băng quá trình đổ rác trong quá trình đổ, vì vậy quá trình được bán phá giá không thể là quá trình tương tự khi lấy kết xuất. Lược đồ này là phổ biến với tất cả các ứng dụng triển khai WER. Chưa kể rằng kết quả dump là một .mdmp đúng mà bạn có thể tải trong WinDbg (hoặc trong VisualStudio nếu đó là ưa thích của bạn).

  • giải pháp nguồn mở nền tảng chéo: Breakpad. Được sử dụng bởi Chrome, Firefox, Picassa và các ứng dụng nổi tiếng khác.

Vì vậy, chủ yếu, không phát minh lại bánh xe. Như một lưu ý phụ, cũng có các dịch vụ làm tăng giá trị cho báo cáo lỗi, như tổng hợp, thông báo, theo dõi và phản hồi của khách hàng tự động, như WER đã đề cập do Microsoft cung cấp (mã của bạn phải được ký điện tử để đủ điều kiện), airbreak.io, exceptioneer.com , bugcollect.com (cái này được tạo bởi bạn thật sự) và khác, nhưng afaik. chỉ WER hoạt động với các ứng dụng Windows gốc.

+0

+1 vì tôi thích câu trả lời phức tạp như vậy. –