dump_stack()函式分析
簡介
當核心出現比較嚴重的錯誤時,例如發生Oops錯誤或者核心認為系統執行狀態異常,核心就會打印出當前程序的棧回溯資訊,其中包含當前執行程式碼的位置以及相鄰的指令、產生錯誤的原因、關鍵暫存器的值以及函式呼叫關係等資訊,這些資訊對於除錯核心錯誤非常有用。
列印函式呼叫關係的函式就是dump_stack(),該函式不僅可以用在系統出問題的時候,我們在除錯核心的時候,可以通過dump_stack()函式的列印資訊更方便的瞭解核心程式碼執行流程。 dump_stack()函式的實現和系統結構緊密相關,本文介紹ARM體系中dump_stack()函式的實現。該函式定義在arch/arm/kernel/traps.c檔案中,呼叫dump_stack()函式不需要新增標頭檔案,基本上在核心程式碼任何地方都可以直接使用該函式。
相關基本知識
讀者需要了解一些ARM彙編的基本知識。在講程式碼之前,我先簡單說說核心中函式呼叫的一般過程。
關鍵暫存器介紹:
暫存器 | 含義 |
---|---|
r0-r3 | 用作函式傳參,例如函式A呼叫函式B,如果A需要向B傳遞引數,則將引數放到暫存器r0-r3中,如果引數個數大於4,則需要借用函式的棧空間。 |
r4-r11 | 變數暫存器,在函式中可以用來儲存臨時變數。 |
r9(SB) | 靜態基址暫存器。 |
r10(SL) | 棧界限暫存器。 |
r11(FP) | 幀指標暫存器,通常用來訪問函式棧,幀指標指向函式棧中的某個位置。 |
r12(IP) | 內部過程呼叫暫存暫存器。 |
r13(SP) | 棧指標暫存器,用來指向函式棧的棧頂。 |
r14(LR) | 連結暫存器,通常用來儲存函式的返回地址。 |
r15(PC) | 程式計數器,指向程式碼段中下一條將要執行的指令,不過由於流水線的作用,PC會指向將要執行的指令的下一條指令。 |
核心中的函式棧
核心中,一個函式的程式碼最開始的指令都是如下形式:
mov ip, sp
stmfd sp!, {r0 - r3} (可選的)
stmfd sp!, {..., fp, ip, lr, pc}
……
- 1
- 2
- 3
- 4
從其中兩條stmfd(壓棧)指令可以看出,一個函式的函式棧的棧底(高地址)的結構基本是固定的,如下圖: 首先我們約定被呼叫的函式稱為callee函式,而呼叫者函式稱為caller函式。 在進行函式呼叫的回溯時,核心中的dump_stack()函式需要做以下嘗試:
- 首先讀取系統中的FP暫存器的值,我們知道幀指標是指向函式棧的某個位置的,所以通過FP的值可以直接找到當前函式的函式棧的地址。
- 得到當前函式的程式碼段地址,這個很容易,因為當前正在執行的程式碼(可通過PC暫存器獲得)就處在函式的程式碼段中。在函式棧中儲存了一個PC暫存器的備份,通過這個PC暫存器的值可以定位到函式的第一條指令,即函式的入口地址。
- 得到當前函式的入口地址後,核心中儲存了所有函式地址和函式名的對應關係,所以可以打印出函式名(詳見另一篇部落格:核心符號表的查詢過程)。
- 在當前函式的函式棧中還儲存了caller函式的幀指標(FP暫存器的值),所以我們就可以找到caller函式的函式棧的位置。
- 繼續執行2-4步,直到某個函式的函式棧中儲存的幀指標(FP暫存器的值)為0或非法。 發生函式呼叫時,函式棧和程式碼段的關係如下圖所示:
dump_stack()函式
接下來我們就來看一下dump_stack()函式的實現。 dump_stack()主要是呼叫了下面的函式
c_backtrace(fp, mode);
- 1
兩個引數的含義為: fp: current程序棧的fp暫存器。 mode: ptrace用到的PSR模式,在這裡我們不關心。dump_stack傳入的值為0x10。 這兩個引數分別賦值給r0, r1暫存器傳給c_backtrace()函式。 c_backtrace函式定義如下(arch/arm/lib/backtrace.S):
@ 定義幾個區域性變數
#define frame r4
#define sv_fp r5
#define sv_pc r6
#define mask r7
#define offset r8
@ 當前處於dump_backtrace函式的棧中
ENTRY(c_backtrace)
stmfd sp!, {r4 - r8, lr} @ 將r4-r8和lr壓入棧中,我們要使用r4-r8,所以備份一下原來的值。sp指向最後壓入的資料
movs frame, r0 @ frame=r0。r0為傳入的第一個引數,即fp暫存器的值
beq no_frame @ 如果frame為0,則退出
tst r1, #0x10 @ 26 or 32-bit mode? 判斷r1的bit4是否為0
moveq mask, #0xfc000003 @ mask for 26-bit 如果是,即r1=0x10,則mask=0xfc000003,即pc地址只有低26bit有效,且末兩位為0
movne mask, #0 @ mask for 32-bit 如果不是,即r1!=0x10,則mask=0
@ 下面是一段和該函式無關的程式碼,用來計算pc預取指的偏移,一般pc是指向下兩條指令,所以offset一般等於8
1: stmfd sp!, {pc} @ 儲存pc的值到棧中,sp指向pc。
ldr r0, [sp], #4 @ r0=sp的值,即剛剛存的pc的值(將要執行的指令),sp=sp+4即還原sp
adr r1, 1b @ r1 = 標號1的地址,即指令 stmfd sp!, {pc} 的地址
sub offset, r0, r1 @ offset=r0-r1,即pc實際指向的指令和讀取pc的指令之間的偏移
/*
* Stack frame layout:
* optionally saved caller registers (r4 - r10)
* saved fp
* saved sp
* saved lr
* frame => saved pc @ frame即上面的fp,每個函式的fp都指向這個位置
* optionally saved arguments (r0 - r3)
* saved sp => <next word>
*
* Functions start with the following code sequence:
* mov ip, sp
* stmfd sp!, {r0 - r3} (optional)
* corrected pc => stmfd sp!, {..., fp, ip, lr, pc} //將pc壓棧的指令
*/
@ 函式主流程:開始查詢並列印呼叫者函式
for_each_frame: tst frame, mask @ Check for address exceptions
bne no_frame
@ 由sv_pc找到將pc壓棧的那條指令,因為這條指令在程式碼段中的位置有特殊性,可用於定位函式入口。
1001: ldr sv_pc, [frame, #0] @ 獲取儲存在callee棧裡的sv_pc,它指向callee的程式碼段的某個位置
1002: ldr sv_fp, [frame, #-12] @ get saved fp,這個fp就是caller的fp,指向caller的棧中某個位置
sub sv_pc, sv_pc, offset @ sv_pc減去offset,找到將pc壓棧的那條指令,即上面註釋提到的corrected pc。
bic sv_pc, sv_pc, mask @ mask PC/LR for the mode 清除sv_pc中mask為1的位,例如,mask=0x4,則清除sv_pc的bit2。
@ 定位函式的第一條指令,即函式入口地址
1003: ldr r2, [sv_pc, #-4] @ if stmfd sp!, {args} exists, 如果在函式最開始壓入了r0-r3
ldr r3, .Ldsi+4 @ adjust saved 'pc' back one. r3 = 0xe92d0000 >> 10
teq r3, r2, lsr #10 @ 比較stmfd指令機器碼是否相同(不關注是否儲存r0-r9),目的是判斷是否為stmfd指令
subne r0, sv_pc, #4 @ allow for mov: 如果sv_pc前面只有mov ip, sp
subeq r0, sv_pc, #8 @ allow for mov + stmia: 如果sv_pc前面有兩條指令
@ 至此,r0為callee函式的第一條指令的地址,即callee函式的入口地址
@ 列印r0地址對應的符號名,傳給dump_backtrace_entry三個引數:
@ r0:函式入口地址,
@ r1:返回值即caller中的地址,
@ r2:callee的fp
ldr r1, [frame, #-4] @ get saved lr
mov r2, frame
bic r1, r1, mask @ mask PC/LR for the mode
bl dump_backtrace_entry
@ 列印儲存在棧裡的暫存器,這跟棧回溯沒關係,本文中不太關心
ldr r1, [sv_pc, #-4] @ if stmfd sp!, {args} exists, sv_pc前一條指令是否是stmfd指令
ldr r3, .Ldsi+4
teq r3, r1, lsr #10
ldreq r0, [frame, #-8] @ get sp。frame-8指向儲存的IP暫存器,由於mov ip, sp,所以caller的sp=ip
@ 所以r0=caller的棧的低地址。
subeq r0, r0, #4 @ point at the last arg. r0+4就是callee的棧的高地址。
@ 由於引數的壓棧順序為r3,r2,r1,r0,所以這裡棧頂實際上是最後一個引數。
bleq .Ldumpstm @ dump saved registers
@ 列印儲存在棧裡的暫存器,這跟棧回溯沒關係,本文中不太關心
1004: ldr r1, [sv_pc, #0] @ if stmfd sp!, {..., fp, ip, lr, pc}
ldr r3, .Ldsi @ instruction exists, 如果指令為frame指向的指令為stmfd sp!, {..., fp, ip, lr, pc}
teq r3, r1, lsr #10
subeq r0, frame, #16 @ 跳過fp, ip, lr, pc,即找到儲存的r4-r10
bleq .Ldumpstm @ dump saved registers,打印出來r4-r10
@ 對儲存在當前函式棧中的caller的fp做合法性檢查
teq sv_fp, #0 @ zero saved fp means 判斷獲取的caller的fp的值
beq no_frame @ no further frames 如果caller fp=0,則停止迴圈
@ 更新frame變數指向caller函式棧的位置,將上面註釋中的Stack frame layout
cmp sv_fp, frame @ sv_fp-frame
mov frame, sv_fp @ frame=sv_fp
bhi for_each_frame @ cmp的結果,如果frame<sv_fp,即當前fp小於caller的fp,則繼續迴圈
@ 這時frame指向caller棧的fp,由於函式中不會修改fp的值,所以這個fp肯定是指向caller儲存的pc的位置的。
1006: adr r0, .Lbad @ 否則就列印bad frame提示
mov r1, frame
bl printk
no_frame: ldmfd sp!, {r4 - r8, pc}
ENDPROC(c_backtrace)
@ c_backtrace函式結束。
@ 將上面的程式碼放到__ex_table異常表中。其中1001b ... 1006b是指上面的1001-1006標號。
.section __ex_table,"a"
.align 3
.long 1001b, 1006b
.long 1002b, 1006b
.long 1003b, 1006b
.long 1004b, 1006b
.previous
#define instr r4
#define reg r5
#define stack r6
@ 列印暫存器值
.Ldumpstm: stmfd sp!, {instr, reg, stack, r7, lr}
mov stack, r0
mov instr, r1
mov reg, #10
mov r7, #0
1: mov r3, #1
tst instr, r3, lsl reg
beq 2f
add r7, r7, #1
teq r7, #6
moveq r7, #1
moveq r1, #'\n'
movne r1, #' '
ldr r3, [stack], #-4
mov r2, reg
adr r0, .Lfp
bl printk
2: subs reg, reg, #1
bpl 1b
teq r7, #0
adrne r0, .Lcr
blne printk
ldmfd sp!, {instr, reg, stack, r7, pc}
.Lfp: .asciz "%cr%d:%08x"
.Lcr: .asciz "\n"
.Lbad: .asciz "Backtrace aborted due to bad frame pointer <%p>\n"
.align
.Ldsi:
@ 用來判斷是否是stmfd sp!指令,並且引數包含fp, ip, lr, pc,不包含r10
.word 0xe92dd800 >> 10 @ stmfd sp!, {... fp, ip, lr, pc}
@ 用來判斷是否是stmfd sp!指令,並且引數不包含r10, fp, ip, lr, pc
.word 0xe92d0000 >> 10 @ stmfd sp!, {}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-778f64ae39.css" rel="stylesheet">
</div>