首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么在使用SetUnhandledExceptionFilter时堆栈遍历不能正常工作?

为什么在使用SetUnhandledExceptionFilter时堆栈遍历不能正常工作?
EN

Stack Overflow用户
提问于 2014-03-18 18:24:05
回答 3查看 1.8K关注 0票数 1

我使用以下代码在异常上遍历堆栈(注意:必须发布时运行它,以便正确地将堆栈跟踪的所需输出接收到控制台,而不是在调试模式下,否则它只会显示弹出式):

代码语言:javascript
运行
复制
#include "stdafx.h"
#include <process.h>
#include <iostream>
#include <Windows.h>
#include "dbghelp.h"

using namespace std;

#define TRACE_MAX_FUNCTION_NAME_LENGTH 1024
#define TRACE_LOG_ERRORS FALSE
#define TRACE_DUMP_NAME L"Exception.dmp"

void function2()
{
    int a = 0;
    int b = 0;
    throw new exception;
}

void function1()
{
    int a = 0;
    function2();
}

void function0()
{
    function1();
}

static void threadFunction(void *param)
{
    function0();
}

LONG WINAPI UnhandledExceptionFilter(PEXCEPTION_POINTERS exception)
{
    CONTEXT context = *(exception->ContextRecord);
    HANDLE thread = GetCurrentThread();
    HANDLE process = GetCurrentProcess();
    STACKFRAME64 frame;
    memset(&frame, 0, sizeof(STACKFRAME64));
    DWORD image;
#ifdef _M_IX86
    image = 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
    image = IMAGE_FILE_MACHINE_AMD64;
    frame.AddrPC.Offset = context.Rip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Rbp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Rsp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
    image = 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 "This platform is not supported."
#endif
    SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO)+(TRACE_MAX_FUNCTION_NAME_LENGTH - 1) * sizeof(TCHAR));
    symbol->MaxNameLen = TRACE_MAX_FUNCTION_NAME_LENGTH;
    symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
    line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
    DWORD displacement;
    SymInitialize(process, NULL, TRUE);
    while (StackWalk(image, process, thread, &frame, &context, NULL, NULL, NULL, NULL))
    {
        if (SymFromAddr(process, frame.AddrPC.Offset, NULL, symbol))
        {
            if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, line))
            {
                printf("\tat %s in %s: line: %lu: address: 0x%0X\n", symbol->Name, line->FileName, line->LineNumber, symbol->Address);
            }
            else if (TRACE_LOG_ERRORS)
            {
                printf("Error from SymGetLineFromAddr64: %lu.\n", GetLastError());
            }
        }
        else if (TRACE_LOG_ERRORS)
        {
            printf("Error from SymFromAddr: %lu.\n", GetLastError());
        }
    }
    DWORD error = GetLastError();
    if (error && TRACE_LOG_ERRORS)
    {
        printf("Error from StackWalk64: %lu.\n", error);
    }
    HANDLE dumpFile = CreateFile(TRACE_DUMP_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
    exceptionInformation.ThreadId = GetCurrentThreadId();
    exceptionInformation.ExceptionPointers = exception;
    exceptionInformation.ClientPointers = FALSE;
    if (MiniDumpWriteDump(process, GetProcessId(process), dumpFile, MiniDumpNormal, exception ? &exceptionInformation : NULL, NULL, NULL))
    {
        printf("Wrote a dump.");
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

int _tmain(int argc, _TCHAR* argv[])
{
    SetUnhandledExceptionFilter(UnhandledExceptionFilter);
    _beginthread(threadFunction, 0, NULL);
    printf("Press any key to exit.\n");
    cin.get();
    return 0;
}

输出:

代码语言:javascript
运行
复制
Press any key to exit.
        at threadFunction in c:\users\<youruseraccount>\documents\visual studio 2013\project
s\stacktracing\stacktracing\stacktracing.cpp: line: 135: address: 0x498B12D0
Wrote a dump.

问题是,上面的跟踪只包含line: 135,它对应于对threadFunction中的function0();的调用。但是,作为堆栈跟踪的一部分,我希望它包括line: 29,它在其中执行throw new exception;。为什么它不把它作为堆栈跟踪的一部分呢?我如何使它也包括堆栈跟踪的这一部分?FatalExceptionFilter,但这是不好的,因为它有自己的警告,因为它必须在任何地方使用,我想要一个顶级的解决方案。我想捕捉所有顶级异常,我想知道它们到底是在哪里抛出的。

这个代码是在Windows 8.1 64位计算机上运行的。它是一个在发布版build/platform x64下编译的MSVC++控制台应用程序。

Update:我已经使用_set_se_translator方法和Petr的建议尝试了以下内容,但是它似乎仍然不想工作。实际上,零异常的除法被抛出时不被处理,没有什么处理它:

代码语言:javascript
运行
复制
#include "stdafx.h"
#include <process.h>
#include <iostream>
#include <Windows.h>
#include "dbghelp.h"

using namespace std;

#define TRACE_MAX_FUNCTION_NAME_LENGTH 1024
#define TRACE_LOG_ERRORS FALSE
#define TRACE_DUMP_NAME L"Exception.dmp"

void function2()
{
    int a = 0;
    int b = 0;
    // The loop below should throw an unhandled exception.
    for (int *i = 0; *i < 100; i++)
    {
        *i = 10000;
    }
}

void function1()
{
    int a = 0;
    function2();
}

void function0()
{
    function1();
}

void ShowStackTrace(EXCEPTION_POINTERS* exception)
{
    CONTEXT context = *(exception->ContextRecord);
    HANDLE thread = GetCurrentThread();
    HANDLE process = GetCurrentProcess();
    STACKFRAME64 frame;
    memset(&frame, 0, sizeof(STACKFRAME64));
    DWORD image;
#ifdef _M_IX86
    image = 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
    image = IMAGE_FILE_MACHINE_AMD64;
    frame.AddrPC.Offset = context.Rip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Rbp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Rsp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
    image = 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 "This platform is not supported."
#endif
    SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO)+(TRACE_MAX_FUNCTION_NAME_LENGTH - 1) * sizeof(TCHAR));
    symbol->MaxNameLen = TRACE_MAX_FUNCTION_NAME_LENGTH;
    symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
    line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
    DWORD displacement;
    SymInitialize(process, NULL, TRUE);
    while (StackWalk(image, process, thread, &frame, &context, NULL, NULL, NULL, NULL))
    {
        if (SymFromAddr(process, frame.AddrPC.Offset, NULL, symbol))
        {
            if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, line))
            {
                printf("\tat %s in %s: line: %lu: address: 0x%0X\n", symbol->Name, line->FileName, line->LineNumber, symbol->Address);
            }
            else if (TRACE_LOG_ERRORS)
            {
                printf("Error from SymGetLineFromAddr64: %lu.\n", GetLastError());
            }
        }
        else if (TRACE_LOG_ERRORS)
        {
            printf("Error from SymFromAddr: %lu.\n", GetLastError());
        }
    }
    DWORD error = GetLastError();
    if (error && TRACE_LOG_ERRORS)
    {
        printf("Error from StackWalk64: %lu.\n", error);
    }
    HANDLE dumpFile = CreateFile(TRACE_DUMP_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
    exceptionInformation.ThreadId = GetCurrentThreadId();
    exceptionInformation.ExceptionPointers = exception;
    exceptionInformation.ClientPointers = FALSE;
    if (MiniDumpWriteDump(process, GetProcessId(process), dumpFile, MiniDumpNormal, exception ? &exceptionInformation : NULL, NULL, NULL))
    {
        printf("Wrote a dump.");
    }
}

void ShowStackTrace(CONTEXT *aContext)
{
    CONTEXT context = *aContext;
    HANDLE thread = GetCurrentThread();
    HANDLE process = GetCurrentProcess();
    STACKFRAME64 frame;
    memset(&frame, 0, sizeof(STACKFRAME64));
    DWORD image;
#ifdef _M_IX86
    image = 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
    image = IMAGE_FILE_MACHINE_AMD64;
    frame.AddrPC.Offset = context.Rip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Rbp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Rsp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
    image = 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 "This platform is not supported."
#endif
    SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO)+(TRACE_MAX_FUNCTION_NAME_LENGTH - 1) * sizeof(TCHAR));
    symbol->MaxNameLen = TRACE_MAX_FUNCTION_NAME_LENGTH;
    symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
    line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
    DWORD displacement;
    SymInitialize(process, NULL, TRUE);
    while (StackWalk(image, process, thread, &frame, &context, NULL, NULL, NULL, NULL))
    {
        if (SymFromAddr(process, frame.AddrPC.Offset, NULL, symbol))
        {
            if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, line))
            {
                printf("\tat %s in %s: line: %lu: address: 0x%0X\n", symbol->Name, line->FileName, line->LineNumber, symbol->Address);
            }
            else if (TRACE_LOG_ERRORS)
            {
                printf("Error from SymGetLineFromAddr64: %lu.\n", GetLastError());
            }
        }
        else if (TRACE_LOG_ERRORS)
        {
            printf("Error from SymFromAddr: %lu.\n", GetLastError());
        }
    }
    DWORD error = GetLastError();
    if (error && TRACE_LOG_ERRORS)
    {
        printf("Error from StackWalk64: %lu.\n", error);
    }
    HANDLE dumpFile = CreateFile(TRACE_DUMP_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (MiniDumpWriteDump(process, GetProcessId(process), dumpFile, MiniDumpNormal, NULL, NULL, NULL))
    {
        printf("Wrote a dump.");
    }
}

class CustomException {
public:
    CustomException(EXCEPTION_POINTERS *exception = nullptr) 
    {
        CONTEXT context;
        ZeroMemory(&context, sizeof(CONTEXT));
        if (exception)
        {
            // In case of an SEH exception.
            ShowStackTrace(exception);
        }
        else
        {
            // In case of a C++ exception.
            RtlCaptureContext(&context);
            ShowStackTrace(&context);
        }
    }
};

void SEHExceptionTranslator(unsigned int, EXCEPTION_POINTERS *exception){
    throw CustomException(exception);
}

static void threadFunction(void *param)
{
    _set_se_translator(SEHExceptionTranslator);
    function0();
}

LONG WINAPI UnhandledExceptionFilter(PEXCEPTION_POINTERS exception)
{
    CONTEXT context = *(exception->ContextRecord);
    HANDLE thread = GetCurrentThread();
    HANDLE process = GetCurrentProcess();
    STACKFRAME64 frame;
    memset(&frame, 0, sizeof(STACKFRAME64));
    DWORD image;
#ifdef _M_IX86
    image = 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
    image = IMAGE_FILE_MACHINE_AMD64;
    frame.AddrPC.Offset = context.Rip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Rbp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Rsp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
    image = 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 "This platform is not supported."
#endif
    SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO)+(TRACE_MAX_FUNCTION_NAME_LENGTH - 1) * sizeof(TCHAR));
    symbol->MaxNameLen = TRACE_MAX_FUNCTION_NAME_LENGTH;
    symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
    line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
    DWORD displacement;
    while (StackWalk(image, process, thread, &frame, &context, NULL, NULL, NULL, NULL))
    {
        if (SymFromAddr(process, frame.AddrPC.Offset, NULL, symbol))
        {
            if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, line))
            {
                printf("\tat %s in %s: line: %lu: address: 0x%0X\n", symbol->Name, line->FileName, line->LineNumber, symbol->Address);
            }
            else if (TRACE_LOG_ERRORS)
            {
                printf("Error from SymGetLineFromAddr64: %lu.\n", GetLastError());
            }
        }
        else if (TRACE_LOG_ERRORS)
        {
            printf("Error from SymFromAddr: %lu.\n", GetLastError());
        }
    }
    DWORD error = GetLastError();
    if (error && TRACE_LOG_ERRORS)
    {
        printf("Error from StackWalk64: %lu.\n", error);
    }
    HANDLE dumpFile = CreateFile(TRACE_DUMP_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
    exceptionInformation.ThreadId = GetCurrentThreadId();
    exceptionInformation.ExceptionPointers = exception;
    exceptionInformation.ClientPointers = FALSE;
    if (MiniDumpWriteDump(process, GetProcessId(process), dumpFile, MiniDumpNormal, exception ? &exceptionInformation : NULL, NULL, NULL))
    {
        printf("Wrote a dump.");
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

int _tmain(int argc, _TCHAR* argv[])
{
    SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
    SymInitialize(GetCurrentProcess(), NULL, TRUE);
    _set_se_translator(SEHExceptionTranslator);
    SetUnhandledExceptionFilter(UnhandledExceptionFilter);
    _beginthread(threadFunction, 0, NULL);
    printf("Press any key to exit.\n");
    cin.get();
    SymCleanup(GetCurrentProcess());
    return 0;
}
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2014-03-18 19:22:19

编辑:在与OP的作者进行了广泛的讨论之后,我们找到了在调用堆栈跟踪中只有最顶层函数function0()才“注册”时“意外”行为的解释。事实上,正如在注释部分早期推测的那样,这是因为所有其他函数都会在发布版构建中内联。用解密规范装饰所有函数,确保它们不是内联的。在这种情况下,将获得预期的调用堆栈跟踪.下面描述的处理C++/SE异常的方案仍然有效,尽管它无助于OP作者无法更改生产代码的问题,而且只能处理未处理的异常。编辑的结束.

只是在评论的部分太长之前做出一个快速的回答。

  1. 检查这条线是否有一个堆栈跟踪例程,该例程还允许您从.map文件中获取函数的名称。
  2. DbgHelp.dll中的函数是单线程的,应该在整个过程中调用一次。这意味着::SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);::SymInitialize(::GetCurrentProcess(), 0, 1);::SymCleanup(::GetCurrentProcess());应该分别在main()的开头和结尾被调用。
  3. 要跟踪C++异常的调用堆栈,可以将堆栈跟踪放在自定义C++异常类构造函数中。这样,在构建throw MyException();MyException对象时,可以跟踪调用堆栈。
  4. 为了在SE被引发时(例如,除以零),使用_set_se_translator()并创建一个translator函数,该函数抛出一个C++异常类对象,并将EXCEPTION_POINTERS *传递给它的构造函数。然后使用EXCEPTION_POINTERS *保存线程上下文来跟踪调用堆栈。

基本上,您有一个定制的C++异常类,其构造函数看起来如下(警告:没有测试):

代码语言:javascript
运行
复制
class MyException {
 public:
  MyException(EXCEPTION_POINTERS * _ptr = nullptr) {
   ::CONTEXT context_;
   ::ZeroMemory( &context_, sizeof(::CONTEXT));
   CONTEXT * pcontext_ = &context_;

   if(_ptr) pcontext_ = _ptr->ContextRecord; // in case of SE translator
   else ::RtlCaptureContext(&context_); // in case of 'throw MyException();'

   // Call-stack tracing using pcontext_ here...
  }

  // Other stuff for MyException class...
 };

因此,要将它与throw_set_se_translator一起使用,您可以使用

代码语言:javascript
运行
复制
throw MyException();

代码语言:javascript
运行
复制
void my_translator(unsigned int, EXCEPTION_POINTERS * _ptr){
 throw MyException(_ptr);
}
票数 2
EN

Stack Overflow用户

发布于 2018-04-20 21:55:42

使用最近的dbghelp.dll (以及相应的最近的MSVC),您也可以找到内联帧的功能/位置。

StackWalk()之前添加以下内容

代码语言:javascript
运行
复制
DWORD frameCount = 0;

将其添加到StackWalk()循环(但在SymFromAddr()之前):

代码语言:javascript
运行
复制
DWORD64 addr = frame.AddrPC.Offset;
// make sure the location of the calling function is reported, and not of the next statement
if (frameCount != 0 && addr != 0) addr--;
frameCount++;
// number of inlined frames, if any
DWORD inlineTrace = SymAddrIncludeInlineTrace (process, addr);
if (inlineTrace != 0)
{
    DWORD inlineContext, frameIndex;
    // the inline context is needed
    if (SymQueryInlineTrace (process, addr, 0, addr, addr, &inlineContext, &frameIndex))
    {
        for (DWORD i = 0; i < inlineTrace; i++)
        {
            DWORD64 displacement64 = 0;
            // similar to SymFromAddr()
            if (SymFromInlineContext (process, addr, inlineContext, &displacement64, symbol))
            {
                DWORD displacement = 0;
                // similar to SymGetLineFromAddr64()
                if (SymGetLineFromInlineContext (process, addr, inlineContext, 0, &displacement, line))
                {
                    printf("inline: at %s in %s: line: %lu: address: 0x%0X\n",
                            symbol->Name, line->FileName, line->LineNumber, symbol->Address);
                }
            }

            // raise to get inline context of next inlined frame
            inlineContext++;
        }
    }
}
票数 2
EN

Stack Overflow用户

发布于 2021-02-24 16:49:20

我在这个页面上绊倒了,因为我一直很难从异常中获得正确的文件名/行号,尽管手动创建了产生正确堆栈跟踪的致命错误。结果只是简单地包括了PDB,确保了所有调用堆栈尽可能准确。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/22487887

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档