當前位置:首頁 » 操作系統 » linuxbacktrace

linuxbacktrace

發布時間: 2023-03-22 15:05:20

1. c語言 linux 獲取調用者函數名稱

可以做到的,參考一下backtrace_symbols的實現以及液隱相關原理,這裡面比較復雜,也比神埋粗較底層,一兩句話說不清楚
你要是想通過_FUNCTION_這種游鎮方式來搞的話,可以通過如下方式:
#define m_fun(args) fun(__FILE__,__LINE__, args)
進行相應的替換即可

2. linux記錄死機前的函數調用

線上環境進程崩潰,運維為了不背鍋,要求崩潰之後立馬將進程拉起。然而發現有個問題:一旦運維將進程拉起之後,之後使用崩潰的 core 文件來進行分析時,符號信息都丟失,看到的都是問號。

但是,如果崩潰之後未被拉起,可以正常的看到符號。

後來發現,賀悶是運維啟動進程的 shell 腳本,每次啟動之前,會將需要載入的部分業務相關的 so 文件,文件名字修改(名稱里加上了時間戳,類似 lib20200423002608_xxxx.so 這種)。名稱被修改之後,gdb 自然沒法載入載入這個 so 文件。

info shared
在 gdb 里使用 info shared,可以看到這個 so 文件無對應的地址,因為沒有對應的 so 文件被載入。線上環境的 gdb版本是 7.2,啟動時沒有與 so 文件不存在相關的提示。

當然這是後話。

那麼在奔潰時,如何將奔潰時的調用棧記錄到日誌里呢。

可以藉助 backtrace 相關的 3 個函數來實現。

#include <execinfo.h>

int backtrace(void *symaddr[], int size);

char **backtrace_symbols(void *const symaddr[], int size);

void backtrace_symbols_fd(void *const *buffer, int size, int fd);
參數和返回值說明:

backtrace 傳入一個數組 symaddr,用來保存符號的地址;size 為數組的大小。size 應該足夠大,不然會有部分符號丟失。返回值為實際保存的地址數量。

backtrace_symbols 用來根據符號的地址,得到對應的符號。size 為 backtrace 的返回值,表示腔耐實際需要處理的符號數量。

返回的是一個 malloc 得到的字元串數組的起始地址(C 語言中不太嚴謹的講,char* 就是字元串),所以最後需要調用者釋放內存。

#include <stdlib.h>
#include <string>
#include <execinfo.h>
#include <unistd.h>

void getCallStackInfo(std::string &stackInfo)
{
static const int size = 100; //符號數量,100足夠
int nptrs;

void *buffer[size];
char **syms;

nptrs = backtrace(buffer, size); //返回當前調伍拍春用棧實際的符號數量

syms = backtrace_symbols(buffer, nptrs);

if (syms == nullptr)
{
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}

for(int i = 0; i < nptrs; ++i)
{
stackInfo.append(syms[i]);
stackInfo.append("\n");
}

free(syms);
}

void say(int &n)
{
static int call_count = 0;
++n;
++call_count;
printf("call count %d\n", call_count);

if(call_count == 6)
{
std::string stack_info;
getCallStackInfo(stack_info);

printf("%s\n", stack_info.c_str());
return;
}

say(n);
}

int main()
{
int n = 3;
say(n);
return 0;
}

編譯運行,clang++ main.cpp -rdynamic -o main.out && ./main.out

call count 1
call count 2
call count 3
call count 4
call count 5
call count 6
./main.out(_Z16getCallStackInfoRNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE+0x23) [0x400d53]
./main.out(_Z3sayRi+0x6a) [0x400e8a]
./main.out(_Z3sayRi+0xc1) [0x400ee1]
./main.out(_Z3sayRi+0xc1) [0x400ee1]
./main.out(_Z3sayRi+0xc1) [0x400ee1]
./main.out(_Z3sayRi+0xc1) [0x400ee1]
./main.out(_Z3sayRi+0xc1) [0x400ee1]
./main.out(main+0x1f) [0x400f0f]
/lib64/libc.so.6(__libc_start_main+0xf3) [0x7fe51b555873]
./main.out(_start+0x2e) [0x400c6e]

看到調用棧已經被記錄下來,當然符號都是 name mangling 之後的,使用 c++filt _Z3sayRi 可以看到原始名字。

回到記錄奔潰時的調用棧到日誌里的主題上。通常的奔潰都是由於內存問題,那麼可以捕獲 SIGSEGV 信號,在信號處理函數中將當前的調用棧記錄到日誌中就行。寫文件可能需要一個 sleep 延時等待日誌線程處理完畢。

void sig_log_stack_handler(int sig)
{
std::string stackInfo;
getCallStackInfo(stackInfo);
abort();
}
當然嚴格的來說,在信號處理器函數里處理 IO 是不符合標準的,會 UB.

注意編譯時一定要帶上 -rdynamic 選項才有用。如果使用的是 qt creator,這個 -rdynamic 參數時需要傳給鏈接器的,需要在 .pro 文件里加上,加到 QMAKE_CXXFLAGS 是沒得用的。

QMAKE_LFLAGS += -rdynamic

3. Linux mutex為什麼不能用在中斷函數

Linux mutex不能用在中斷函數原因:Backtrace來看,應該是i2c_transfer中調用mutex_lock導致schele調用。

pthread_mutex_lock(&qlock);表示嘗試去把qlock上鎖,它會先判斷qlock是否已經上鎖,如果已經上鎖這個線程就會停在這一步直到其他線程把鎖解開。它才繼續運行。所以代碼中要麼是線程1先執行完後執行線程2,要麼就是線程2先執行,再執行線程1.而線程3一開始就執行了。

中斷函數防止方法:

要防止中斷沖突,其實就是要知道什麼設備容易產生中斷沖突,只要知道了這點,在使用這些設備時稍微注意一下就可以了。下面我列出一些容易沖突的設備,希望對讀者有用。

1、音效卡:一些早期的ISA型音效卡,系統很有可能不認,就需要用戶手動設置(一般為5)。

2、內置數據機和滑鼠:一般滑鼠用COM1,內置數據機使用COM2的中斷(一般為3),這時要注意此時COM2上不應有其它設備。

4. 安裝linux提示「loader exited unexpectedly!backtrace……」

安裝盤壞了(ISO文件有問題了),重新下載一個再試

5. linux c開發: 在程序退出時進行處理

有時候,希望程序退出時能進行一些處理,比如保存狀態叢和,釋放一些資源。c語言開發的linux程序,有可能正常退出(exit),有可能異常crash,而異常crash可能是響應了某信號的默認處理。這里總結一下這些情況,如何獲取拆敏一個統一的退出處理的點,說白了就是寫一個回調函數,讓他在程序正常或異常退出時調用。

這個例子裡面其實是將異常退出處理和正常退出處理結合起來了旅鄭枝。對於SIGTERM(即kill進程)和SIGINT(即ctrl-c結束前台進程),我們當做是正常退出,在其信號處理函數裡面,直接調用了exit(0),而exit(0)又會被server_on_exit捕獲到。對於異常退出也是類似,只是調用了exit(-1)表示是異常的。同時異常退出我們會列印出當前的進程堆棧信息,server_backtrace的實現下一篇再說。另外注意的是SIGKILL信號是無法捕獲的。而調用abort導致的退出,也是通過SIGABRT信號捕獲到進行處理了。其他幾種異常退出的信號也是比較常見,一並捕獲到進行處理。這樣對於異常退出,我們即可統一的log堆棧信息,又可直接繼續正常退出時的處理流程了。

6. Linux下 C崩潰了!誰能幫忙看看列印出來的 Backtrace 信息

王者榮耀去衣圖

7. 如何linux內核報告問題

Linux Kernel BUG:soft lockup CPU#1 stuck分析
1.線上內核bug日誌
kernel: Deltaway too big! 18428729675200069867 ts=18446743954022816244 write stamp =18014278822746377
kernel:------------[ cut here ]------------
kernel:WARNING: at kernel/trace/ring_buffer.c:1988 rb_reserve_next_event+0x2ce/0x370()(Not tainted)
kernel:Hardware name: ProLiant DL360 G7
kernel:Moles linked in: fuse ipv6 power_meter bnx2 sg microcode serio_raw iTCO_wdtiTCO_vendor_support hpilo hpwdt i7core_edac edac_core shpchp ext4 mbcache jbd2sd_mod crc_t10dif hpsa radeon ttm drm_kms_helper drm i2c_algo_bit i2c_coredm_mirror dm_region_hash dm_log dm_mod [last unloaded: scsi_wait_scan]
kernel: Pid:5483, comm: master Not tainted 2.6.32-220.el6.x86_64 #1
kernel: CallTrace:
kernel:[<ffffffff81069b77>] ? warn_slowpath_common+0x87/0xc0
kernel:[<ffffffff81069bca>] ? warn_slowpath_null+0x1a/0x20
kernel:[<ffffffff810ea8ae>] ? rb_reserve_next_event+0x2ce/0x370
kernel:[<ffffffff810eab02>] ? ring_buffer_lock_reserve+0xa2/0x160
kernel:[<ffffffff810ec97c>] ? trace_buffer_lock_reserve+0x2c/0x70
kernel:[<ffffffff810ecb16>] ? trace_current_buffer_lock_reserve+0x16/0x20
kernel:[<ffffffff8107ae1e>] ? ftrace_raw_event_hrtimer_cancel+0x4e/0xb0
kernel:[<ffffffff81095e7a>] ? hrtimer_try_to_cancel+0xba/0xd0
kernel:[<ffffffff8106f634>] ? do_setitimer+0xd4/0x220
kernel:[<ffffffff8106f88a>] ? alarm_setitimer+0x3a/0x60
kernel:[<ffffffff8107c27e>] ? sys_alarm+0xe/0x20
kernel:[<ffffffff8100b308>] ? tracesys+0xd9/0xde
kernel: ---[end trace 4d0a1ef2e62cb1a2 ]---
abrt-mp-oops: Reported 1 kernel oopses to Abrt
kernel: BUG: softlockup - CPU#11 stuck for 4278190091s! [qmgr:5492]
kernel:Moles linked in: fuse ipv6 power_meter bnx2 sg microcode serio_raw iTCO_wdtiTCO_vendor_support hpilo hpwdt i7core_edac edac_core shpchp ext4 mbcache jbd2sd_mod crc_t10dif hpsa radeon ttm drm_kms_helper drm i2c_algo_bit i2c_coredm_mirror dm_region_hash dm_log dm_mod [last unloaded: scsi_wait_scan]
kernel: CPU 11
kernel:Moles linked in: fuse ipv6 power_meter bnx2 sg microcode serio_raw iTCO_wdtiTCO_vendor_support hpilo hpwdt i7core_edac edac_core shpchp ext4 mbcache jbd2sd_mod crc_t10dif hpsa radeon ttm drm_kms_helper drm i2c_algo_bit i2c_coredm_mirror dm_region_hash dm_log dm_mod [last unloaded: scsi_wait_scan]
kernel:
kernel: Pid:5492, comm: qmgr Tainted: G W ---------------- 2.6.32-220.el6.x86_64 #1 HPProLiant DL360 G7
kernel: RIP:0010:[<ffffffff8106f730>] [<ffffffff8106f730>]do_setitimer+0x1d0/0x220
kernel: RSP:0018:ffff88080a661ef8 EFLAGS: 00000286
kernel: RAX:ffff88080b175a08 RBX: ffff88080a661f18 RCX: 0000000000000000
kernel: RDX:0000000000000000 RSI: 0000000000000082 RDI: ffff88080c8c4c40
kernel: RBP:ffffffff8100bc0e R08: 0000000000000000 R09: 0099d7270e01c3f1
kernel: R10:0000000000000000 R11: 0000000000000246 R12: ffffffff810ef9a3
kernel: R13:ffff88080a661e88 R14: 0000000000000000 R15: ffff88080a65a544
kernel: FS:00007f10b245f7c0(0000) GS:ffff88083c4a0000(0000) knlGS:0000000000000000
kernel: CS:0010 DS: 0000 ES: 0000 CR0: 000000008005003b
kernel: CR2:00007ff955977380 CR3: 000000100a80b000 CR4: 00000000000006e0
kernel: DR0:0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
kernel: DR3:0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
kernel:Process qmgr (pid: 5492, threadinfo ffff88080a660000, task ffff880809577500)
kernel: Stack:
kernel:00007f10b323def0 00007f10b248ead0 00007f10b26d0f78 00007f10b248ede0
kernel:<0> ffff88080a661f68 ffffffff8106f88a 0000000000000000 0000000000000000
kernel:<0> 000000000000014c 00000000000f423d 0000000000000000 0000000000000000
kernel: CallTrace:
kernel:[<ffffffff8106f88a>] ? alarm_setitimer+0x3a/0x60
kernel:[<ffffffff8107c27e>] ? sys_alarm+0xe/0x20
kernel:[<ffffffff8100b308>] ? tracesys+0xd9/0xde
kernel: Code:89 ef e8 74 66 02 00 83 3d 15 69 b5 00 00 75 37 49 8b 84 24 70 07 00 00 48 0508 08 00 00 66 ff 00 66 66 90 fb 66 0f 1f 44 00 00 <31> c0 e9 64 fe ff ff49 8b 84 24 68 07 00 00 48 c7 80 d0 00 00
kernel: CallTrace:
kernel:[<ffffffff8106f769>] ? do_setitimer+0x209/0x220
kernel:[<ffffffff8106f88a>] ? alarm_setitimer+0x3a/0x60
kernel:[<ffffffff8107c27e>] ? sys_alarm+0xe/0x20
kernel:[<ffffffff8100b308>] ? tracesys+0xd9/0xde
abrt-mp-oops: Reported 1 kernel oopses to Abrt

2.內核軟死鎖(soft lockup)bug原因分析
Soft lockup名稱解釋:所謂,soft lockup就是說,這個bug沒有讓系統徹底死機,但是若干個進程(或者kernel thread)被鎖死在了某個狀態(一般在內核區域),很多情況下這個是由於內核鎖的使用的問題。
Linux內核對於每一個cpu都有一個監控進程,在技術界這個叫做watchdog(看門狗)。通過ps –ef | grep watchdog能夠看見,進程名稱大概是watchdog/X(數字:cpu邏輯編號1/2/3/4之類的)。這個進程或者線程每一秒鍾運行一次,否則會睡眠和待機。這個進程運行會收集每一個cpu運行時使用數據的時間並且存放到屬於每個cpu自己的內核數據結構。在內核中有很多特定的中斷函數。這些中斷函數會調用soft lockup計數,他會使用當前的時間戳與特定(對應的)cpu的內核數據結構中保存的時間對比,如果發現當前的時間戳比對應cpu保存的時間大於設定的閥值,他就假設監測進程或看門狗線程在一個相當可觀的時間還沒有執。Cpu軟鎖為什麼會產生,是怎麼產生的?如果linux內核是經過精心設計安排的CPU調度訪問,那麼怎麼會產生cpu軟死鎖?那麼只能說由於用戶開發的或者第三方軟體引入,看我們伺服器內核panic的原因就是qmgr進程引起。因為每一個無限的循環都會一直有一個cpu的執行流程(qmgr進程示一個後台郵件的消息隊列服務進程),並且擁有一定的優先順序。Cpu調度器調度一個驅動程序來運行,如果這個驅動程序有問題並且沒有被檢測到,那麼這個驅動程序將會暫用cpu的很長時間。根據前面的描述,看門狗進程會抓住(catch)這一點並且拋出一個軟死鎖(soft lockup)錯誤。軟死鎖會掛起cpu使你的系統不可用。
如果是用戶空間的進程或線程引起的問題backtrace是不會有內容的,如果內核線程那麼在soft lockup消息中會顯示出backtrace信息。
3.根據linux內核源碼分析錯誤
根據我們第一部分內核拋出的錯誤信息和call trace(linux內核的跟蹤子系統)來分析產生的具體原因。
首先根據我們的centos版本安裝相應的linux內核源碼,具體步驟如下:
(1)下載源碼的rpm包kernel-2.6.32-220.17.1.el6.src.rpm
(2)安裝相應的依賴庫,命令:yuminstall rpm-build redhat-rpm-config asciidoc newt-devel
(3)安裝源碼包:rpm -ikernel-2.6.32-220.17.1.el6.src.rpm
(4)進入建立源碼的目錄:cd~/rpmbuild/SPECS
(5)建立生成源碼目錄:rpmbuild-bp --target=`uname -m` kernel.spec

下面開始真正的根據內核bug日誌分析源碼:
(1)第一階段內核錯誤日誌分析(時間在Dec 4 14:03:34這個階段的日誌輸出代碼分析,其實這部分代碼不會導致cpu軟死鎖,主要是第二階段錯誤日誌顯示導致cpu軟死鎖)
我們首先通過日誌定位到相關源代碼:看下面日誌:Dec 4 14:03:34 BP-YZH-1-xxxx kernel: WARNING: atkernel/trace/ring_buffer.c:1988 rb_reserve_next_event+0x2ce/0x370() (Not tainted)
根據日誌內容我們可以很容易的定位到kernel/trace/ring_buffer.c這個文件的1988行代碼如下:WARN_ON(1)。
先簡單解釋一下WARN_ON的作用:WARN_ON只是列印出當前棧信息,不會panic。所以會看到後面有一大堆的棧信息。這個宏定義如下:
#ifndef WARN_ON
#defineWARN_ON(condition) ({ \
int __ret_warn_on = !!(condition); \
if (unlikely(__ret_warn_on)) \
__WARN(); \
unlikely(__ret_warn_on); \
})
#endif
這個宏很簡單保證傳遞進來的條件值為0或者1(兩次邏輯非操作的結果),然後使用分支預測技術(保證執行概率大的分支緊鄰上面的指令)判斷是否需要調用__WARN()宏定義。如果滿足條件執行了__WARN()宏定義也接著執行一條空指令;。上面調用WARN_ON宏是傳遞的1,所以會執行__WARN()。下面繼續看一下__WARN()宏定義如下:
#define __WARN() warn_slowpath_null(__FILE__,__LINE__)
從接下來的call trace信息中我們也確實發現調用了warn_slowpath_null這個函數。通過在linux內核源代碼中搜索這個函數的實現,發現在panic.c(內核恐慌時的相關功能實現)中實現如下:
voidwarn_slowpath_null(const char *file, int line)
{
warn_slowpath_common(file, line,__builtin_return_address(0),
TAINT_WARN, NULL);
}
EXPORT_SYMBOL(warn_slowpath_null);//都出這個符號,讓其他模塊可以使用這個函數
同樣的我們看到了warn_slowpath_common這個函數,而在call trace當中這個函數在warn_slowpath_null函數之前列印出來,再次印證了這個流程是正確的。同樣在panic.c這個文件中我發現了warn_slowpath_common這個函數的實現如下:
static voidwarn_slowpath_common(const char *file, int line, void *caller,
unsigned taint, struct slowpath_args *args)
{
const char *board;

printk(KERN_WARNING "------------[ cut here]------------\n");
printk(KERN_WARNING "WARNING: at %s:%d %pS()(%s)\n",
file, line, caller, print_tainted());
board = dmi_get_system_info(DMI_PRODUCT_NAME);//得到dmi系統信息
if (board)
printk(KERN_WARNING "Hardware name:%s\n", board);//通過我們的日誌信息可以發現我們硬體名稱是ProLiant DL360 G7

if (args)
vprintk(args->fmt, args->args);

print_moles();//列印系統模塊信息
mp_stack();//mp信息輸出(call trace開始)
print_oops_end_marker();//列印oops結束
add_taint(taint);
}
分析這個函數的實現不難發現我們的很多日誌信息從這里開始輸出,包括列印一些系統信息,就不繼續深入分析了(請看代碼注釋,裡面調用相關函數列印對應信息,通過我分析這些函數的實現和我們的日誌信息完全能夠對應,其中mp_stack是與cpu體系結構相關的,我們的伺服器應該是屬於x86體系)。這里在繼續分析一下mp_stack函數的實現,因為這個是與cpu體系結構相關的,而且這個函數直接反應出導致內核panic的相關進程。這個函數實現如下:
/*
* The architecture-independent mp_stackgenerator
*/
void mp_stack(void)
{
unsigned long stack;

printk("Pid: %d, comm: %.20s %s %s %.*s\n",
current->pid, current->comm,print_tainted(),
init_utsname()->release,
(int

8. memorymappingsegment發生段錯誤,如何回溯

Linux應用中用backtrace和memory map信息定位段錯誤代碼的方法 原創
2020-03-27 14:33:21

川渝小神丟
碼齡11年
關注
前言:

在Linux應用軟體中,段錯誤(segmentation fault)是開發過程中比較棘手的問題,在博文(Linux環境下段錯誤的產生原因及調試方法小結)中簡要介紹了段錯誤的產生原因和簡單的調試方法。

本文主要描述面對大型程序,比如多線程,調用多個動態庫的情況下發生段錯誤分析方法。這個時候如果使用gdb和gcc,由於程序過於復雜,gdb將很難處理,並且對於那些偶爾出現段錯誤的情況gdb基本上無法定位。

另外,還可以使用dmesg,nm,ldd,objmp等工具結合分析匯編代碼調試段錯誤,但是對於大型程序一般都有編譯優化,分析匯編地址將不是那麼容易,另外需要一定匯編基礎,對於龐大程序分析匯編代碼也是噩夢。

最後,使用printf。printf是個最方便方式,如果對於必現的段錯誤,使用printf再結合二分法,一般都能定位到。但是就算段錯誤必現,對於那些多線程大型程序,使用printf就需要對代碼有較深入的了解,憑經驗找到可能觸發異常的地方,並且每次加了調試語句後需要重新編譯燒寫,效率非常低下。對於那些很難復現的段錯誤,printf更難入手。

綜上所述,單單使用上述某一種方法都不能很好地分析滿足如下條件的段錯誤:

1) 大型項目,代碼量幾十萬行以上。

2) 多線程,多動態庫。

3) 出現段錯誤不一定必現(偶爾出現)。

本文將使用backtrace和memory map信息定位出發時異常的大致位置(往往是某個函數名,如果此函數層數非常多,那麼還需要printf定位最終位置),然後通過printf二分法精確到具體位置,針對偶爾出現的段錯誤,使用applog日誌記錄的方式。下面將詳細描述具體方法。

一、段錯誤信號

在某些大型程序中,程序初始化時首先會安裝SIGSEGV信號及指定處理函數,在處理函數中完成特殊段仿冊指定的處理後,最後在處理函數中調用longjmp函數回到主進程報錯並退出主進程。backtrace和memory map信息就是在上述信號處理函數中寫入系統日誌中的,前面的主進程退出後,系統日誌仍然保留在flash上供後續分析。在系統日誌中,backtrace提供了觸發異常的堆棧調用點地址序列,memory map信息提供各目標文件在內存中的地址分配。先通過backtarce中記錄的目標文件和地址數據,結合memory map中目標文件代碼段起始地址,計算偏移量。

使用此偏移量和目標文件用addr2line命令可查得對應此地址的代碼信息。下面先描述linux下段錯誤信號描述(注意Unix系統和linux系統中的signal函數有差異)。

1. 如果沒有安裝SIGSEGV信握宏號和並指定處理函數,系統將對這個信號進行默認處理。通常是殺掉當前進程,並在終端輸出Segmentation fault信息。

2. 如果已經安裝SIGSEGV信號和並指定處理函數,產生這個信號後,系統先運行信號處理函數,信號處理函數運行完後當前進程繼續運行。通過實驗,在發生了這個信號後,大核並且當前進程沒有退出,那麼系統會多次進入信號處理函數。

3. 當安裝了SIGSEGV信號和並指定處理函數後,發生段錯誤的線程將調用處理函數,因此在處理函數中可以獲取哪個線程發生了段錯誤(一般通過線程ID)。即發生段錯誤線程的線程ID和段錯誤處理函數中獲取的線程ID是一樣的。 (也就是說,當線程A發生段錯誤,CPU的PC指針指向Linux內核,運行內核程序,Linux內核捕捉到段錯誤信號,這個時候要運行用戶空間中的信號處理函數,因此記錄下內核空間相關地址後切換到用戶空間,把PC指針指向段錯誤處理函數並運行。PC指針在進入內核空間之前運行的線程A,因此當時PC處於線程A的地址空間內,因此當段錯誤發生後,PC指針指向信號處理函數也應該處於線程A的地址空間內,因此在信號處理函數內獲取的線程ID就是出現問題的線程的線程ID,在信號處理函數中backtrace信息就是出現問題線程問題點相關地址信息)通過上述分析,已經知道段錯誤發生線程,為了進一步縮小范圍,確定發生段錯誤線程中到底哪個函數有問題,就是下面即將描述backtrace和memory map信息。

4. 同中斷類似,內核也為每個進程准備了一個信號向量表,信號向量表中記錄著每個信號所對應的處理機制,默認情況下是調用默認處理機制。當進程為某個信號注冊了信號處理程序後,發生該信號時,內核就會調用注冊的函數。

5. 詳細參考http://www.spongeliu.com/165.html

二、backtrace()

1. int backtrace(void **buffer,int size)

該函數用於獲取當前線程的調用堆棧(即獲取程序中當前函數的棧回溯信息,即一系列的函數調用關系),獲取的信息將會被存放在buffer中,它是 一個指針列表。參數 size 用來指定buffer中可以保存多少個void* 元素。函數返回值是實際獲取的指針個數,最大不超過size大小。在buffer中的指針實際是從堆棧中獲取的返回地址,每一個堆棧框架有一個返回地址。

注意:某些編譯器的優化選項對獲取正確的調用堆棧有干擾,另外內聯函數沒有堆棧框架;刪除框架指針也會導致無法正確解析堆棧內容.

2. char ** backtrace_symbols (void *const *buffer, int size)

backtrace_symbols將從backtrace函數獲取的信息轉化為一個字元串數組. 參數buffer應該是從backtrace函數獲取的指針數組,size是該數組中的元素個數(backtrace的返回值)。

函數返回值是一個指向字元串數組的指針,它的大小同buffer相同.每個字元串包含了一個相對於buffer中對應元素的可列印信息.它包括函數名,函數的偏移地址,和函數的實際返回地址。比如backtrace信息中,./main-test(SEGV_Test+0x10)[0x8bc8],main-test為程序名稱,SEGV_Test函數名,0x10為函數的偏移地址,0x8bc8為此函數實際返回地址。經過翻譯後的函數回溯信息放到backtrace_symbols()的返回值中,如果失敗則返回NULL。

現在,只有使用ELF二進制格式的程序才能獲取函數名稱和偏移地址.在其他系統,只有16進制的返回地址能被獲取.另外,你可能需要傳遞相應的符號給鏈接器,以能支持函數名功能(比如,在使用GNU ld鏈接器的系統中,需要傳遞(-rdynamic), -rdynamic可用來通知鏈接器將所有符號添加到動態符號表中,如果鏈接器支持-rdynamic,建議將其加上)。

該函數的返回值是通過malloc函數申請的空間,因此調用者必須使用free函數來釋放指針。注意:如果不能為字元串獲取足夠的空間函數的返回值將會為NULL。

3. void backtrace_symbols_fd (void *const *buffer, int size, int fd)

backtrace_symbols_fd()的buffer和size參數和backtrace_symbols()函數相同,只是它翻譯後的函數回溯信息不是放到返回值中,而是一行一行的放到文件描述符fd對應的文件中。

4. 在段錯誤信號處理函數中調用backtrace()和backtrace_symbols()函數用於確定出現問題線程的調用堆棧。

由於發生段錯誤後,內核才會把pc寄存器值設為信號處理函數地址,繼而執行應用空間的信號處理函數,因此在信號處理函數中調用backtrace棧回溯函數即可知道段錯誤發生的地方附近相關函數調用關系,間接找到段錯誤發生地點。雖然在信號處理函數中調用backtrace等函數並不能保證是一個安全的信號處理函數,但是用於調試完全可以忽略這一點。

5. 使用上述幾個函數需要注意:

1)backtrace的實現依賴於棧指針(fp寄存器),在gcc編譯過程中任何非零的優化等級(-On參數)或加入了棧指針優化參數- fomit-frame-pointer後多將不能正確得到程序棧信息。

2)backtrace_symbols的實現需要符號名稱的支持,在gcc編譯過程中需要加入-rdynamic參數。

3)內聯函數沒有棧幀,它在編譯過程中被展開在調用的位置。

4)未調用優化(Tail-call Optimization)將復用當前函數棧,而不再生成新的函數棧,這將導致棧信息不能正確被獲取。

三、memory map信息

1. /proc/PID/maps

Proc/pid/maps顯示進程映射了的內存區域和訪問許可權。

[root@ES_Controller:/var]#cat /proc/1/maps
00008000-0006a000 r-xp 00000000 1f:03 27 /bin/busybox
00071000-00072000 rw-p 00061000 1f:03 27 /bin/busybox
00072000-00095000 rwxp 00072000 00:00 0 [heap]
40000000-40002000 rw-p 40000000 00:00 0
412d8000-412f4000 r-xp 00000000 1f:03 396 /lib/ld-2.5.so
412fb000-412fd000 rw-p 0001b000 1f:03 396 /lib/ld-2.5.so
41300000-41416000 r-xp 00000000 1f:03 405 /lib/libc-2.5.so
41416000-4141d000 ---p 00116000 1f:03 405 /lib/libc-2.5.so
4141d000-41420000 rw-p 00115000 1f:03 405 /lib/libc-2.5.so
41420000-41423000 rw-p 41420000 00:00 0
beb71000-beb86000 rw-p befeb000 00:00 0 [stack]
內核每進程的vm_area_struct項/proc/pid/maps中的項

在其他進程(如控制台)執行/proc/pid/maps,會顯示進程號為pid對應的memory map信息;如果在本進程中想獲取本身的memory map信息,需要執行/proc/self/maps。

2. /proc/self/maps

我對/proc/self/maps的理解如下:

如果一個進程正在運行,那麼可以在終端執行/proc/進程PID/maps查看到memory map,但在當前進程代碼運行時需要memory map信息時,可以在代碼中執行/proc/self/maps查看memory map信息,也就是說在哪個程序中執行/proc/self/maps,那麼memory map信息就是對應進程的內存布局信息。

3. memory map

(1) 首先是映像文件(靜態編譯)

00008000-0000a000 r-xp 00000000 00:0d 16281360 /mnt/nfs/main-test/main-test
00011000-00012000 rw-p 00001000 00:0d 16281360 /mnt/nfs/main-test/main-test
映像文件名為main-test,第一行只讀可執行,說明為代碼段,00008000為代碼段的起始地址,如果用backtrace信息中出現問題點棧回溯中的某個函數的實際返回地址減去這個程序代碼段起始地址,那麼就剛好得到了出現問題點相對於整個程序代碼中的位置,再借用addr2line工具就可 以得到出現問題點所在源代碼對應行。

上述信息中第二行為可讀寫,那麼一般為程序的數據段。數據段對應信息的下一行如下,即堆(heap),當且僅當malloc調用時存在,是由kernel把匿名內存map到虛存空間,堆則在程序中沒有調用malloc的情況下不存在;

00012000-00033000 rwxp 00012000 00:00 0 [heap]
另外就是靜態編譯下的動態鏈接庫,最後是堆棧[stack]。

(2) 然後是共享庫(.SO,動態編譯)

40b58000-40bb7000 r-xp 00000000 1f:04 22773 /application/example.so
40bb7000-40bbe000 ---p 0005f000 1f:04 22773 /application/example.so
40bbe000-40bbf000 rw-p 0005e000 1f:04 22773 /application/example.so
同樣,第一行對應共享庫的代碼段;第三行(rw-p)為共享庫的數據段,第二行即不讀;也不可寫,且不可執行,不知道是什麼。共享庫在一個大型程序中,一般都有個入口函數,如果創建一個線程時,把這個入口函數作為線程函數,那麼這個共享庫實際上被載入到內存後,在一個線程中運行。

四、addr2line工具定位出錯點

1. addr2line定義

Addr2line (它是標準的 GNU Binutils 中的一部分)是一個可以將指令的地址和可執行映像轉換成文件名、函數名和源代碼行數的工具。

命令參數如下:

Usage: addr2line [option(s)] [addr(s)]
Convert addresses into line number/file name pairs.
If no addresses are specified on the command line, they will be read from stdin
The options are:
@<file> Read options from <file>
-a --addresses Show addresses
-b --target=<bfdname> Set the binary file format
-e --exe=<executable> Set the input file name (default is a.out)
-i --inlines Unwind inlined functions
-j --section=<name> Read section-relative offsets instead of addresses
-p --pretty-print Make the output easier to read for humans
-s --basenames Strip directory names
-f --functions Show function names
-C --demangle[=style] Demangle function names
-h --help Display this information
-v --version Display the program's version
2. 用addr2line定位

(1) 靜態鏈接情況

執行編譯命令:arm-linux-gcc -rdynamic -lpthread -g main.c -o main-test

即把所有源程序和庫全部連接成一個可執行文件main-test。

backtrace信息如下:

./main-test(pthread_create+0xac4)[0x95ec]
/lib/libc.so.6(__default_rt_sa_restorer_v2+0x0)[0x4132b240]
./main-test(test1+0x44)[0x8cdc]
./main-test(SEGV_Test+0x10)[0x8d5c]
./main-test(thread3+0x40)[0x92ec]
/lib/libpthread.so.0[0x41444858]
memory map信息如下:

00008000-0000a000 r-xp 00000000 00:0d 16281360 /mnt/nfs/main-test/main-test
00011000-00012000 rw-p 00001000 00:0d 16281360 /mnt/nfs/main-test/main-test
00012000-00033000 rwxp 00012000 00:00 0 [heap]
40000000-40001000 rw-p 40000000 00:00 0
40001000-400df000 r-xp 00000000 1f:03 446 /lib/preloadable_libiconv.so
400df000-400e7000 ---p 000de000 1f:03 446 /lib/preloadable_libiconv.so
400e7000-400e8000 rw-p 000de000 1f:03 446 /lib/preloadable_libiconv.so
400e8000-400ea000 rw-p 400e8000 00:00 0
從Memory map信息第一行可以知道靜態編譯的程序main-test代碼段地址空間為0x8000-0xa000,而backtrace信息中0x8cdc等地址也在這個地址空間范圍內(這種實際地址不用backtrace地址減去memory map地址計算偏移,然後再用addr2line命令定位)。因此執行如下命令:

addr2line 0x8cdc -e main-test -f
在使用時,用 -e 選項來指定可執行映像是main-test。通過使用 -f 選項,可以告訴工具輸出函數名。上述命令將會輸出在程序main-test中,指令地址為0x8cdc對應源代碼文件、源代碼文件中的函數名、地址對應行號。執行輸出信息如下:

[root@test main-test]$ addr2line 0x8cdc -e main-test -f
test1
/home/mytest/main-test/main.c:355
上述信息中,說明問題出現在在main.c中的355行附近(test1函數中)。

(2) 動態鏈接情況

然而,調試的程序往往沒有這么簡單,通常會載入用到各種各樣的動態鏈接庫。如果錯誤是發生在動態鏈接庫中那麼處理將變得困 難一些。下面另外一個例子:

backtrace信息如下:

./application(InitialConfigTest+0x6ec) [0xac00].
/lib/libc.so.6(__default_rt_sa_restorer_v2+0) [0x4132b240].
/application/example.so [0x41c68b48].
/application/example.so(testquery+0x28) [0x41c68f6c].
./application [0x19f58].
./application [0x1a660].
/application/lib/libpub.so [0x401d6a68].
/lib/libpthread.so.0 [0x41444858].
部分memory map信息如下:

41c5f000-41cbe000 r-xp 00000000 1f:04 22773 /application/example.so
41cbe000-41cc5000 ---p 0005f000 1f:04 22773 /application/example.so
41cc5000-41cc6000 rw-p 0005e000 1f:04 22773 /application/example.so
用backtrace中的地址0x41c68b48做實驗,即執行命令 addr2line 0x41c68b48 -e example.so -f 後,addr2line輸出信息如下:

[root@test main-test]$ addr2line 0x41c68b48 -e example.so -f
??
??:0
出現這種情況是由於動態鏈接庫是程序運行時動態載入的,因此其載入地址每次可能不一樣(關於與位置無關映像,用file命令查看elf格式文件,如果是shared object,那麼表示此程序支持PIE與位置無關;如果是executable表示編譯和連接分別沒有加-fPIE和-pie選項。與位置無關編譯和鏈接增加參數:-fPIE for compiler,-pie for linker),可見0x41c68b48是一個非常大的地址, 和能得到正常信息的地址如0xac00相差甚遠,0xac00其也不是一個實際的物理地址(用戶空間的程序無法直接訪問物理地址),而是經過MMU(內存管理單元)映射過的。

因此,只需要得到此次example.so的載入地址後(動態庫代碼段起始地址,在memory map信息中第一行的最左面那個地址),再用backtrace中的實際地址0x41c68b48減去example.so的載入地址得到的結果再利用addr2line命令就可以正確的得到出錯的地方。

另外在backtrace信息中,(testquery+0x28)其實也是在描述出錯的地方,這里表示的是發生在符號testquery(函數名)偏移0x28處的地方,也就是說如果我們能得到符號testquery也就是函數testquery在程序中的入口地址再加上偏移量0x28也能得到正常的出錯地址。

0x41c68b48-0x41c5f000 = 0x9b48

addr2line 0x9b48 -e example.so -f

執行上述命令後,addr2line輸出信息如下,從而能定位到問題所在行:

[root@test main-test]$ addr2line 0x9b48 -e example.so -f
example_delay
/home/example/example.c:1883
注意:就算是動態鏈接,如果段錯誤出現在非動態庫中,即應用程序中,這個時候執行addr2line命令時,可執行程序backtrace地址不需要 減去memory map信息中的起始地址。

3. 使用addr2line注意

為了在使用addr2line工具後能得到准確的結果,在編譯程序時應該加上-g選項通知編譯器生成調試符號。

五、總結

1. 為了正確地獲取backtrace信息,編譯時需要加上-rdynamic選項,為了正確地利用addr2line輸出信息,編譯時需要加上-g選項。完整命令如下:

arm-linux-gcc -rdynamic -lpthread -g main.c -o main-test

2. 如果是靜態編譯程序,只需要backtrace信息和addr2line工具即獲取出錯點。

3. 如果是動態編譯程序,且問題出現在動態鏈接庫時(因為動態編譯時,動態鏈接庫是運行時才載入的,載入地址就是在memory map信息中第一行的最左面那個地址),那麼就需要使用backtrace信息中問題點實際地址減去memory map信息中代碼段其實地址,得出地址偏移,然後使用addr2line工具定位到行。

4. 在大型程序中,backtrace信息往往會出現多個目標地址的情況,如下:

./application(InitialConfigTest+0x6ec) [0xac00].
/lib/libc.so.6(__default_rt_sa_restorer_v2+0) [0x4132b240].
/application/example.so [0x41c68b48].
/application/example.so(testquery+0x28) [0x41c68f6c].
./application [0x19f58].
./application [0x1a660].
/application/lib/libpub.so [0x401d6a68].
/lib/libpthread.so.0 [0x41444858].
上述例子屬於動態編譯,application為應用程序,example.so為動態鏈接庫。不管是應用程序還是動態鏈接庫,都出現了多個目標地址的情況。

如:

/application/example.so [0x40b61b48].
/application/example.so(testquery+0x28) [0x41c68f6c].

一般選擇第一個比較准確(沒有地址偏移的那個)

六、偶爾出現段錯誤分析經驗

未完待續...

七、其他相關資料:

1. http://blog.csdn.net/jxgz_leo/article/details/53458366
2. http://www.cnblogs.com/panfeng412/archive/2011/11/06/segmentation-fault-in-linux.html
3. http://blog.csdn.net/guoping16/article/details/6583957
4. Linux內核出現段錯誤,會列印出棧信息(dmesg命令可以看到這些信息)。
5. linux中Oops信息的調試及棧回溯(sù):http://blog.csdn.net/u012839187/article/details/78963443。
6. Linux core 文件介紹:https://www.cnblogs.com/dongquan/archive/2012/01/20/2328355.html

9. linux backtrace如何分析

是伏局念指kernel mp的backtrace分析嗎?
先google,如果google不到也就沒有啥捷徑,得系統學習下,主要是kernel代碼和驅動的原理。
經臘激驗也很重要,有些問題crash的點和實際發生錯誤的地方不在一個地方,如果沒經驗很難出結果。
大多都是驅動或者硬體固件的問題,如果問缺困題影響面較大,就收集信息提交給伺服器售後或者操作系統的售後去分析吧。

10. 如何快速定位Linux Panic出錯的代碼行

內核Panic時,一般會列印回調,並列印出當前出錯的地址:
kernel/panic.c:panic():

#ifdef CONFIG_DEBUG_BUGVERBOSE
/*
* Avoid nested stack-mping if a panic occurs ring oops processing
*/
if (!test_taint(TAINT_DIE) && oops_in_progress <= 1)
mp_stack();
#endif

而mp_stack()調用關系如下:

mp_stack() --> __mp_stack() --> show_stack() --> mp_backtrace()

mp_backtrace()會列印整個回調,例如:

[<001360ac>] (unwind_backtrace+0x0/0xf8) from [<00147b7c>] (warn_slowpath_common+0x50/0x60)
[<00147b7c>] (warn_slowpath_common+0x50/0x60) from [<00147c40>] (warn_slowpath_null+0x1c/0x24)
[<00147c40>] (warn_slowpath_null+0x1c/0x24) from [<0014de44>] (local_bh_enable_ip+0xa0/0xac)
[<0014de44>] (local_bh_enable_ip+0xa0/0xac) from [<0019594c>] (bdi_register+0xec/0x150)

通常,上面的回調會列印出出錯的地址。
解決方案
通過分析,要快速定位出錯的代碼行,其實就是快速查找到出錯的地址對應的代碼?
相應的工具有addr2line, gdb, objmp等,這幾個工具在How to read a Linux kernel panic?都有介紹,我們將針對上面的實例做更具體的分析。
需要提到的是,代碼的實際運行是不需要符號的,只需要地址就行。所以如果要調試代碼扮悄,必須確保調試符號已經編譯到內核中,不然,回調里頭列印的是一堆地址,根本看不到符號,那麼對於上面提到的情況二緩譽而言,將無法准確定位問題。
情況一
在代碼編譯連接時,每個函數都有起始地址和長度,這個地址是程序運行時的地址,而函數內部,每條指令相對於函數開始地址會有偏移。那麼有了地址以後,就可以定位到該地址落在哪個函數的區間內,然後找到該函數,進而通過計算偏移,定位到代碼行。
情況二
但是,如果拿到的日誌文件所在的系統版本跟當前的代碼版本不一致,那麼編譯後的地址就會有差異。那麼簡單地直接通過地廳哪渣址就可能找不到原來的位置,這個就可能需要回調里頭的函數名信息。先通過函數名定位到所在函數,然後通過偏移定位到代碼行。

熱點內容
解壓到當前文件夾右鍵 發布:2024-04-26 03:57:08 瀏覽:979
html5android教程視頻下載 發布:2024-04-26 03:09:59 瀏覽:867
伺服器的描述是什麼 發布:2024-04-26 03:08:32 瀏覽:394
個人加密 發布:2024-04-26 03:01:23 瀏覽:521
linuxusbgadget 發布:2024-04-26 02:52:54 瀏覽:304
我的世界空島世界伺服器地址 發布:2024-04-26 01:39:08 瀏覽:248
尼爾機械紀元加密 發布:2024-04-26 01:37:11 瀏覽:868
在控制台輸出sql語句 發布:2024-04-26 01:08:12 瀏覽:432
動畫java 發布:2024-04-26 01:02:40 瀏覽:12
得力文件夾5302 發布:2024-04-26 00:21:32 瀏覽:91