在Linux與Windows上獲取當前堆棧信息

来源:http://www.cnblogs.com/fyter/archive/2017/06/26/stackinfo.html
-Advertisement-
Play Games

在編寫穩定可靠的軟體服務時經常用到輸出堆棧信息,以便用戶/開發者獲取準確的運行信息。常用在日誌輸出,錯誤報告,異常檢測。本文介紹Linux與Windows下用C++獲取堆棧信息的方法。 ...


在編寫穩定可靠的軟體服務時經常用到輸出堆棧信息,以便用戶/開發者獲取準確的運行信息。常用在日誌輸出,錯誤報告,異常檢測。

在Linux有比較簡便的函數獲取堆棧信息:

#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>


void handler(int sig) {
  void *array[5];
  size_t size;

  // get void*'s for all entries on the stack
  size = backtrace(array, 5);

  // print out all the frames to stderr
  fprintf(stderr, "Error: signal %d:\n", sig);
  char** msgs = backtrace_symbols(array, size);
  for(int i=1;i<size && msgs[i];++i)
    printf("[%d] %s\n", i, msgs[i]);
  exit(1);
}

void baz() {
 int *foo = (int*)-1; // make a bad pointer
  printf("%d\n", *foo);       // causes segfault
}

void bar() { baz(); }
void foo() { bar(); }


int main(int argc, char **argv) {
  signal(SIGSEGV, handler);   // install our handler
  foo(); // this will call foo, bar, and baz.  baz segfaults.
}

 

以上代碼從參考的stackoverflow中稍作修改而來。核心就是backtrace與backtrace_symbols兩個函數。

 

Windows下推薦用StackWalker這個開源代碼,支持X86,AMD64,IA64。

如果你需要一個最簡的代碼,那麼下麵是我抽取出來的代碼,明顯比Linux要複雜一些。(Win的很多功能實現起來要複雜一些,當然也有很多功能實現要比Linux簡單很多。)

我會做一些講解,在後面。

#include "stdafx.h"
#include <Windows.h>
#include <iostream>
#include <DbgHelp.h>
#include <TlHelp32.h>

using namespace std;

HANDLE ph;

void baz()
{
    int* v = 0;
    *v = 0;
}
void bar()
{
    baz();
}

void foo(){
    __try {
        bar();
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        auto sire = SymInitialize(ph, 0, FALSE);
        sire = SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_FAIL_CRITICAL_ERRORS);
        CONTEXT ctx = { 0 };
        ctx.ContextFlags = CONTEXT_FULL;
        RtlCaptureContext(&ctx);
        STACKFRAME64 sf = { 0 };
    #ifdef _M_IX86 // ignore IA64
        auto imageType = IMAGE_FILE_MACHINE_I386;
        sf.AddrPC.Offset = ctx.Eip;
        sf.AddrPC.Mode = AddrModeFlat;
        sf.AddrFrame.Offset = ctx.Ebp;
        sf.AddrFrame.Mode = AddrModeFlat;
        sf.AddrStack.Offset = ctx.Esp;
        sf.AddrStack.Mode = AddrModeFlat;
    #elif _M_X64
        auto imageType = IMAGE_FILE_MACHINE_AMD64;
        sf.AddrPC.Offset = ctx.Rip;
        sf.AddrPC.Mode = AddrModeFlat;
        sf.AddrFrame.Offset = ctx.Rsp;
        sf.AddrFrame.Mode = AddrModeFlat;
        sf.AddrStack.Offset = ctx.Rsp;
        sf.AddrStack.Mode = AddrModeFlat;
    #endif

        MODULEENTRY32 me;
        auto snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
        auto info = Module32First(snap, &me);
        while (info) {
            auto dw = SymLoadModule64(ph, 0, me.szExePath, me.szModule, (DWORD64)me.modBaseAddr, me.modBaseSize);
            if (!Module32Next(snap, &me))break;
        }
        CloseHandle(snap);
        auto thread = GetCurrentThread();

        PIMAGEHLP_SYMBOL64 sym = (IMAGEHLP_SYMBOL64 *)malloc(sizeof(IMAGEHLP_SYMBOL64) + 100);
        if (!sym)
            return;
        memset(sym, 0, sizeof(IMAGEHLP_SYMBOL64) + 100);
        sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
        sym->MaxNameLength = 100;

        IMAGEHLP_LINE64 line = { 0 };
        line.SizeOfStruct = sizeof(line);
        for (;;) {
            auto result = StackWalk(imageType, ph, thread, &sf, &ctx, 0, SymFunctionTableAccess64, SymGetModuleBase64, 0);
            if (result) {
                DWORD64 offset = 0;
                DWORD offset_for_line = 0;
                CHAR und_fullname[100];

                if (sf.AddrPC.Offset != 0) {
                    if (SymGetSymFromAddr64(ph, sf.AddrPC.Offset, &offset, sym)) {
                        UnDecorateSymbolName(sym->Name, und_fullname, 100, UNDNAME_COMPLETE);
                        cout << und_fullname;
                    }

                    if (SymGetLineFromAddr64(ph, sf.AddrPC.Offset, &offset_for_line, &line)) {
                        cout << " " << line.FileName << "(" << line.LineNumber << ")";
                    }
                    cout << endl;
                }
            }
            else
                break;
        }
        SymCleanup(ph);
    }
}
int main()
{
    ph = GetCurrentProcess();
    foo();
    return 0;
}

 

編譯請鏈接dbghelp.lib

核心就是StackWalk與SymGetSymFromAddr64,SymGetLineFromAddr64。

StackWalk用於獲取下一層堆棧。

SymGetSymFromAddr64用於獲取當前函數名。

SymGetLineFromAddr64用於獲取函數所在文件及行號。

為了這三個函數正常工作,還要初始化符號相關功能(SymInitialize),取得當前線程描述表(RtlCaptureContext),載入用到的模塊(SymLoadModule64)。

用到了<DbgHelp.h> <TlHelp32.h>這兩個頭文件。

上面代碼執行後會在控制台輸出堆棧信息。

 

參考:

  https://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes

  http://www.codeproject.com/KB/threads/StackWalker.aspx


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.給mysql創建用戶備份的角色,並且授予角色SELECT, RELOAD, SHOW DATABASES, LOCK TABLES等許可權。 2.在系統中找到存儲空間比較大的硬碟創建備份目錄,並且創建shell腳本 註意:-u和用戶名中間是沒有空格的,-p和密碼也是這樣的。3.添加計劃任務,需要安 ...
  • 最近,利用一些時間對oracle資料庫實時同步工具做了一些調研分析,主要關註了linkedin的databus和阿裡的yugong兩個中間件,其中databus需要在每個待同步的表上增加額外的列和觸發器來實現,方案比較重,本文將著重分析一下阿裡的yugong實現方案及給出分析調研報告。 1.yugo ...
  • 一款用JAVA語言開發的Redis管理及監控工具treeNMS橫空出世了。 ...
  • 今天啟動虛擬機,遇到如下錯誤: RAMDISK: incomplete write (31522 != 32768) write error Kernel panic not syncing : VFS: Unable to mount root fs on unknown block(0,0) 網 ...
  • VIM詳細命令有很多,我們選用一些常用的入門命令,足以對付日常的代碼編輯工作了,如果日後有需要使用其他命令,再來查詢也不遲。 vim一般有3種編輯模式,分別是插入模式,正常模式(normal mode),末行模式。 以下主要是在正常模式下的操作,其他模式操作會註明相關模式 1.1 移動游標 h >每 ...
  • 最近手頭上的項目終於忙得差不多了,想起好久沒有更新了的NanUI,再看著每天QQ群未讀消息閃爍的標誌,突然才發現似乎愧對了群里各位喜愛NanUI的朋友們。於是乎,就想趁這幾天有時間,好好的修複一下NanUI已知的BUG,再用有限的時間推進整個項目的進度。 在複習代碼的時候,想起了群里有朋友提出說Na ...
  • 空(None) None可以用來表示某一個變數的值缺失,類似於其他語言中的null。 像其他的空值:0,[]和空的string,布爾變數給的是False而不是True。 結果是: 當一個函數沒有返回任何值時,就會返回None: 結果是: Hi None 字典(Dictionaries) 字典是一種給 ...
  • 在上一節中講解了歸併排序的遞歸版《4.比較排序之歸併排序(遞歸)》,通常來講,遞歸版的歸併排序要更為常用,本節簡單介紹下非遞歸版的歸併排序。思路和遞歸版相同,均為先分解後合併,非遞歸的重點在於如何確定併合理的分解待排序數組。 對於遞歸我們是這麼做的: 對於非遞歸來講,切分的不向遞歸從大到小,非遞歸實 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...