0

Following is my code, from open source only. Why StackWalk64 does not work with release build and debug build without pdbs

HEADER FILE

#ifndef STACK_TRACE_H_
#define STACK_TRACE_H_

#include <string>
#include <deque>

struct StackItem {
  std::string m_name;
  std::string m_file;
  int m_line;

  StackItem() : m_line(0) {}
};

class StackTraceImpl;

class StackTrace {
public:
  typedef std::deque<StackItem>         ItemContainer;
  typedef ItemContainer::const_iterator ItemIterator;

  ItemContainer   m_items;
  StackTraceImpl *m_impl;
public:

  StackTrace();
  virtual ~StackTrace();
  void print() const;

  void popFront() { 
    m_items.pop_front(); 
  }
  ItemIterator begin() const { 
    return m_items.begin(); 
  }
  ItemIterator end() const { 
    return m_items.end(); 
  }

};

#endif

CPP FILE

#include <windows.h>
#include <DbgHelp.h>
#include <tlhelp32.h>
#include <vector>

#include <iostream>

#include "StackTrace.h"

std::size_t const SYMBOL_NAME_MAXLEN = 1024;

struct SymStartup {
  HANDLE process;
  SymStartup(HANDLE process) : process(process) {
    char current[MAX_PATH];
    std::string path;
    if (GetCurrentDirectoryA(MAX_PATH, current) > 0) {
      path += current;
      path += ";";
    }

    if (GetModuleFileNameA(NULL, current, MAX_PATH) > 0) {
      std::string filePath = current;
      std::string::size_type pos = filePath.find_last_of('\\');
      if (pos != std::string::npos)
        filePath.erase(pos);

      path += filePath;
      path += ";";
    }

    if (GetEnvironmentVariableA("_NT_SYMBOL_PATH", current, MAX_PATH) > 0) {
      path += current;
      path += ";";
    }

    if (GetEnvironmentVariableA("_NT_ALTERNATE_SYMBOL_PATH", current, MAX_PATH) > 0) {
      path += current;
      path += ";";
    }

    if (GetEnvironmentVariableA("SYSTEMROOT", current, MAX_PATH) > 0) {
      path += current;
      path += ";";
      path += current;
      path += "\\system32";
      path += ";";
    }

    if (!SymInitialize(process, path.c_str(), FALSE))
      throw 1;

    DWORD options = SymGetOptions();
    options |= SYMOPT_LOAD_LINES;
    options |= SYMOPT_FAIL_CRITICAL_ERRORS;

    options = SymSetOptions(options);
  }

  ~SymStartup() {
    if (process)
      SymCleanup(process);
  }
};


//inline std::string wstr2str(std::wstring const& ws)
//{
//  using namespace std;
//  std::string mbs;
//  ctype<wchar_t> const& conv(use_facet<ctype<wchar_t> >(locale()));
//
//  mbs.reserve(ws.size());
//  for (wstring::const_iterator it = ws.begin(); it != ws.end(); ++it)
//    mbs.push_back(conv.narrow(*it, '?'));
//
//  return mbs;
//}

std::string wstr2str(const std::wstring &wstr) {
    std::string strTo;
    char *szTo = new char[wstr.length() + 1];
    szTo[wstr.size()] = '\0';
    WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, szTo, (int)wstr.length(), NULL, NULL);
    strTo = szTo;
    delete[] szTo;
    return strTo;
}


class StackTraceImpl {
private:            
  void load_modules(HANDLE process, DWORD processID) {
    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processID);
    if (snap == INVALID_HANDLE_VALUE)
      return;

    MODULEENTRY32 entry;
    entry.dwSize = sizeof(entry);

    if (Module32First(snap, &entry)) {
      do {
        std::string fileName    = wstr2str(entry.szExePath);
        std::string moduleName  = wstr2str(entry.szModule);
        SymLoadModule64(process, NULL, fileName.c_str(), moduleName.c_str(), (DWORD64) entry.modBaseAddr, entry.modBaseSize);
      } while (Module32Next(snap, &entry));
    }
    CloseHandle(snap);
  }

  void retrieve_context(CONTEXT& context) {
    std::memset(&context, 0, sizeof(context));
    context.ContextFlags = CONTEXT_FULL;
    RtlCaptureContext(&context);
  }

  void retrieve_frame(CONTEXT& context, STACKFRAME64& frame, DWORD& imageType) {
    std::memset(&frame, 0, sizeof(frame));
#ifdef _M_IX86
    imageType = IMAGE_FILE_MACHINE_I386;
    frame.AddrPC.Offset = context.Eip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Ebp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Esp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
    imageType = IMAGE_FILE_MACHINE_AMD64;
    frame.AddrPC.Offset = context.Rip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Rsp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Rsp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
    imageType = IMAGE_FILE_MACHINE_IA64;
    frame.AddrPC.Offset = context.StIIP;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.IntSp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrBStore.Offset = context.RsBSP;
    frame.AddrBStore.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.IntSp;
    frame.AddrStack.Mode = AddrModeFlat;
#else
#error "Platform not supported!"
#endif
  }

public:
  void retrieve(StackTrace::ItemContainer& items) {
    HANDLE process = 0;

    try {
      items.clear();
      process = GetCurrentProcess();
      SymStartup startup(process);
      load_modules(process, GetCurrentProcessId());

      HANDLE thread = GetCurrentThread();

      CONTEXT context;
      retrieve_context(context);

      DWORD imageType = 0;
      STACKFRAME64 frame;
      retrieve_frame(context, frame, imageType);

      std::vector<char> symbolData(sizeof(IMAGEHLP_SYMBOL64) + SYMBOL_NAME_MAXLEN, 0);

      IMAGEHLP_SYMBOL64* symbol = reinterpret_cast<IMAGEHLP_SYMBOL64*>(&symbolData[0]);                    
      symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
      symbol->MaxNameLength = SYMBOL_NAME_MAXLEN;

      IMAGEHLP_LINE64 m_line;
      std::memset(&m_line, 0, sizeof(IMAGEHLP_LINE64));
      m_line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);

      for (int frameNum = 0; true; ++frameNum) {
        if (!StackWalk64(imageType, process, thread, &frame, &context, NULL, &SymFunctionTableAccess64, &SymGetModuleBase64, NULL))
          break;

        if (frame.AddrPC.Offset == frame.AddrReturn.Offset)
          break;                        

        if (frame.AddrPC.Offset != 0) {
          StackItem item;

          DWORD64 displacement64 = 0;
          if (SymGetSymFromAddr64(process, frame.AddrPC.Offset, &displacement64, symbol)) {
            char symbolName[SYMBOL_NAME_MAXLEN];
            std::strncpy(symbolName, symbol->Name, SYMBOL_NAME_MAXLEN);

            UnDecorateSymbolName(symbol->Name, symbolName, SYMBOL_NAME_MAXLEN, UNDNAME_COMPLETE);
            item.m_name.assign(symbolName, symbolName + std::strlen(symbolName));
          }

          DWORD displacement = 0;
          if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, &m_line)) {
            item.m_line = m_line.LineNumber;
            item.m_file.assign(m_line.FileName, m_line.FileName + std::strlen(m_line.FileName));
          }
          items.push_back(item);
        }
        if (frame.AddrReturn.Offset == 0)
          break;
      }
    } catch (...) {
    }                
  }
};

StackTrace::StackTrace() : m_impl(new StackTraceImpl) {
  m_impl->retrieve(m_items);
  if (m_items.size() > 1)
            m_items.erase(m_items.begin(), m_items.begin() + 2);
}

StackTrace::~StackTrace() {
}

void StackTrace::print() const {
  for (StackTrace::ItemIterator it = m_items.begin(), end = m_items.end(); it != end; ++it)
            std::cout  << it->m_file << "(" << it->m_line << ") : " << it->m_name << std::endl;
}

MAIN FILE

#include "StackTrace.h"
void func1() {
  StackTrace st;
  st.print();
}
void func2() {
  func1();
}
void func3() {
  func2();
}
void func4() {
  func3();
}
void func5() {
  func4();
}
void func6() {
  func5();
}
int main ( int argc, char **argv ) {
  func5();
  return 0;
}

DEBUG BUILD OUTPUT

E:\Avinash\my_work\StackWalk64>Debug\StackWalk64.exe
e:\avinash\my_work\stackwalk64\stackwalk64\stackwalk64main.cpp(3) : func1
e:\avinash\my_work\stackwalk64\stackwalk64\stackwalk64main.cpp(8) : func2
e:\avinash\my_work\stackwalk64\stackwalk64\stackwalk64main.cpp(11) : func3
e:\avinash\my_work\stackwalk64\stackwalk64\stackwalk64main.cpp(14) : func4
e:\avinash\my_work\stackwalk64\stackwalk64\stackwalk64main.cpp(17) : func5
e:\avinash\my_work\stackwalk64\stackwalk64\stackwalk64main.cpp(23) : main
f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c(555) : __tmainCRTStartup
f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c(371) : mainCRTStartup
(0) : BaseThreadInitThunk
(0) : RtlInitializeExceptionChain
(0) : RtlInitializeExceptionChain

RELEASE BUILD OUTPUT

E:\Avinash\my_work\StackWalk64>Release\StackWalk64.exe
f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c(555) : __tmainCRTStartup
(0) : BaseThreadInitThunk
(0) : RtlInitializeExceptionChain
(0) : RtlInitializeExceptionChain

AFTER DELETING PDB FILES

E:\Avinash\my_work\StackWalk64\Debug>StackWalk64.exe
(0) :
(0) :
(0) :
(0) :
(0) :
(0) :
(0) :
(0) :
(0) : BaseThreadInitThunk
(0) : RtlInitializeExceptionChain
(0) : RtlInitializeExceptionChain
Avinash
  • 12,851
  • 32
  • 116
  • 186

1 Answers1

9

You cannot expect to reliably walk the stack on optimized code. Eliminating stack frames is on the top of the hit list for the code optimizer. The "Omit frame pointer" optimization is an important one, that frees up an extra register (EBP), always important for x86 code. It is usually off by default but the code generator applies it anyway when it can inline functions.

The strongest one is the "Inline function expansion" optimization, it replaces a function call by the code in the target function body. Which does a good number on your test code, it completely eliminates all of your functions. In other words, the code inside func1() gets moved into main().

Disabling these optimizations is unwise, it can greatly affect the efficiency of your code. The C and C++ languages were designed to be fast, debuggability was not a primary consideration. Which is why a Debug and Release configuration exists in the first place. Do keep in mind that your test code is too artificial to be a reliable indicator of what will happen in real code. You'll get something out of a stack trace, just not what you are used to when debugging code.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536