Print stack backtracking information in linux kernel - dump_stack() function analysis

Posted by Cosizzle on Sun, 16 Jan 2022 08:56:39 +0100

Reference article:

https://blog.csdn.net/jasonchen_gbd/article/details/45585133

brief introduction

When the kernel has a serious error, such as Oops error or the kernel thinks the system running state is abnormal, the kernel will print the stack backtracking information of the current process, including the location of the current executing code and adjacent instructions, the cause of the error, the value of the key register and the function call relationship, This information is useful for debugging kernel errors.

The function that prints the function call relationship is dump_stack(), which can be used not only when the system has problems, but also when debugging the kernel through dump_ The printing information of stack () function makes it easier to understand the kernel code execution process.
dump_ The implementation of stack () function is closely related to the system structure. This paper introduces dump in ARM system_ Implementation of stack() function. This function is defined in arch / ARM / kernel / traps In the C file, call dump_. The stack () function does not need to add header files. It can be used directly anywhere in the kernel code.

Relevant basic knowledge

Readers need to know some basic knowledge of ARM assembly. Before talking about the code, let me briefly talk about the general process of function calls in the kernel.

Introduction to key registers:

Function stack in kernel

In the kernel, the initial instructions of a function code are in the following form:

1             mov   ip, sp
2             stmfd sp!, {r0 - r3} (Optional)
3             stmfd sp!, {..., fp, ip, lr, pc}
4             ......

It can be seen from the two stmfd (stack pressing) instructions that the structure of the bottom (high address) of the function stack of a function is basically fixed, as shown in the following figure:

 

First, we agree that the called function is called callee function, and the caller function is called caller function.

During the backtracking of function calls, dump in the kernel_ The stack() function needs to try the following:

  1. First, read the value of FP register in the system. We know that the frame pointer points to a position of the function stack, so we can directly find the address of the function stack of the current function through the value of FP.
  2. It is easy to get the code segment address of the current function, because the currently executing code (available through PC register) is in the code segment of the function. A backup of PC register is saved in the function stack. Through the value of this PC register, you can locate the first instruction of the function, that is, the entry address of the function.
  3. After getting the entry address of the current function, the kernel saves the corresponding relationship between all function addresses and function names, so you can print out the function name (see another blog: the search process of kernel symbol table for details).
  4. The frame pointer of the caller function (the value of FP register) is also saved in the function stack of the current function, so we can find the position of the function stack of the caller function.
  5. Continue to perform steps 2-4 until the frame pointer (the value of FP register) saved in the function stack of a function is 0 or illegal.

When a function call occurs, the relationship between the function stack and the code segment is shown in the following figure:

 

 

dump_stack() function

Next, let's take a look at dump_ Implementation of stack() function.
dump_stack() mainly calls the following functions

1 c_backtrace(fp, mode);

The meanings of the two parameters are:
fp: fp register of current process stack.
Mode: we don't care about the PSR mode used by ptrace here. dump_ The value passed in by stack is 0x10.
These two parameters are assigned to R0 and R1 register is passed to c_backtrace() function.
c_ The backtrace function is defined as follows (arch/arm/lib/backtrace.S):

  1 @ Define several local variables
  2 #define frame   r4
  3 #define sv_fp   r5
  4 #define sv_pc   r6
  5 #define mask    r7
  6 #define offset  r8
  7 
  8 @ Currently in dump_backtrace Function stack
  9 ENTRY(c_backtrace)
 10         stmfd   sp!, {r4 - r8, lr}  @ take r4-r8 and lr Push into the stack, we want to use r4-r8,So back up the original value. sp Point to the last pressed data
 11         movs    frame, r0   @ frame=r0. r0 Is the first parameter passed in, i.e fp Register value
 12         beq no_frame        @ If frame 0, exit
 13 
 14         tst r1, #0x10       @ 26 or 32-bit mode? judge r1 of bit4 Is it 0
 15         moveq   mask, #0xfc000003   @ mask for 26-bit If yes, i.e r1=0x10,be mask=0xfc000003,Namely pc The address is only low 26 bit Valid, and the last two digits are 0
 16         movne   mask, #0        @ mask for 32-bit If not, i.e r1!=0x10,be mask=0
 17 
 18         @ The following is a section of code unrelated to the function, which is used to calculate pc Prefetch refers to the offset of, generally pc It points to the next two instructions, so offset Generally equal to 8
 19 1:      stmfd   sp!, {pc}       @ storage pc To the stack,sp point pc. 
 20         ldr r0, [sp], #4        @ r0=sp That is, the value just saved pc The value of (the instruction to be executed), sp=sp+4 Namely restore sp
 21         adr r1, 1b              @ r1 = Address of label 1, i.e. instruction stmfd sp!, {pc} Address of
 22         sub offset, r0, r1      @ offset=r0-r1,Namely pc Instructions and reads actually pointed to pc Offset between instructions
 23 
 24 /*
 25  * Stack frame layout:
 26  *             optionally saved caller registers (r4 - r10)
 27  *             saved fp
 28  *             saved sp
 29  *             saved lr
 30  *    frame => saved pc     @ frame That is, fp above, fp of each function points to this position
 31  *             optionally saved arguments (r0 - r3)
 32  * saved sp => <next word>
 33  *
 34  * Functions start with the following code sequence:
 35  *                  mov   ip, sp
 36  *                  stmfd sp!, {r0 - r3} (optional)
 37  * corrected pc =>  stmfd sp!, {..., fp, ip, lr, pc} //Instruction to stack pc
 38  */
 39  @ Function main process: start to find and print the caller function
 40 for_each_frame: tst frame, mask     @ Check for address exceptions
 41         bne no_frame
 42 
 43         @ from sv_pc Find will pc The instruction pressing the stack, because the position of this instruction in the code segment is special, can be used to locate the function entry.
 44 1001:       ldr sv_pc, [frame, #0]      @ Get saved in callee In the stack sv_pc,It points to callee Somewhere in the code snippet
 45 1002:       ldr sv_fp, [frame, #-12]    @ get saved fp,this fp namely caller of fp,point caller Somewhere in the stack
 46 
 47         sub sv_pc, sv_pc, offset    @ sv_pc subtract offset,Find will pc The instruction of pressing the stack, which is mentioned in the above note corrected pc. 
 48         bic sv_pc, sv_pc, mask      @ mask PC/LR for the mode eliminate sv_pc in mask A bit of 1, for example, mask=0x4,Then clear sv_pc of bit2. 
 49 
 50         @ Locate the first instruction of the function, that is, the function entry address
 51 1003:       ldr r2, [sv_pc, #-4]    @ if stmfd sp!, {args} exists, If you press in at the beginning of the function r0-r3
 52         ldr r3, .Ldsi+4             @ adjust saved 'pc' back one. r3 = 0xe92d0000 >> 10
 53         teq r3, r2, lsr #10         @ compare stmfd Are the machine instruction codes the same(Do not pay attention to whether to save r0-r9),The purpose is to determine whether it is stmfd instructions
 54         subne   r0, sv_pc, #4       @ allow for mov: If sv_pc Only in front mov   ip, sp
 55         subeq   r0, sv_pc, #8       @ allow for mov + stmia: If sv_pc There are two instructions ahead
 56         @ So far, r0 by callee The address of the first instruction of the function, i.e callee The entry address of the function
 57 
 58         @ Print r0 The symbolic name corresponding to the address, passed to dump_backtrace_entry Three parameters:
 59         @ r0:Function entry address,
 60         @ r1:The return value is caller Address in,
 61         @ r2: callee of fp
 62         ldr r1, [frame, #-4]    @ get saved lr
 63         mov r2, frame
 64         bic r1, r1, mask        @ mask PC/LR for the mode
 65         bl  dump_backtrace_entry
 66 
 67         @ Print the register stored in the stack, which has nothing to do with stack backtracking, which is not concerned in this article
 68         ldr r1, [sv_pc, #-4]    @ if stmfd sp!, {args} exists, sv_pc Is the previous instruction stmfd instructions
 69         ldr r3, .Ldsi+4
 70         teq r3, r1, lsr #10 
 71         ldreq   r0, [frame, #-8]    @ get sp. frame-8 Point to saved IP Register, due to mov   ip, sp,therefore caller of sp=ip
 72                                     @ therefore r0=caller The low address of the stack.
 73         subeq   r0, r0, #4      @ point at the last arg. r0+4 namely callee The high address of the stack.
 74                                 @ Because the stacking order of parameters is r3,r2,r1,r0,So here the top of the stack is actually the last parameter.
 75         bleq    .Ldumpstm       @ dump saved registers
 76 
 77         @ Print the register stored in the stack, which has nothing to do with stack backtracking, which is not concerned in this article
 78 1004:       ldr r1, [sv_pc, #0]     @ if stmfd sp!, {..., fp, ip, lr, pc}
 79         ldr r3, .Ldsi       @ instruction exists, If the instruction is frame The instruction pointed to is stmfd sp!, {..., fp, ip, lr, pc}
 80         teq r3, r1, lsr #10
 81         subeq   r0, frame, #16 @ skip fp, ip, lr, pc,The saved is found r4-r10
 82         bleq    .Ldumpstm       @ dump saved registers,Print out r4-r10
 83 
 84         @ To save in the current function stack caller of fp Check the legitimacy
 85         teq sv_fp, #0       @ zero saved fp means Judge acquired caller of fp Value of
 86         beq no_frame        @ no further frames   If caller fp=0,Then stop the cycle
 87 
 88         @ to update frame Variable pointing caller The position of the function stack Stack frame layout
 89         cmp sv_fp, frame        @ sv_fp-frame
 90         mov frame, sv_fp        @ frame=sv_fp
 91         bhi for_each_frame      @ cmp Results, if frame<sv_fp,I.e. current fp less than caller of fp,Then continue the cycle
 92         @ At this time frame point caller Stack fp,Because it will not be modified in the function fp So this fp It must point to caller Preserved pc The location of the.
 93 
 94 1006:       adr r0, .Lbad       @ Otherwise, print bad frame Tips
 95         mov r1, frame
 96         bl  printk
 97 no_frame:   ldmfd   sp!, {r4 - r8, pc}
 98 ENDPROC(c_backtrace)
 99 @ c_backtrace End of function.
100 
101         @ Put the above code in__ex_table Exception table. Including 1001 b ... 1006b Refers to 1001 above-1006 grade.
102         .section __ex_table,"a"
103         .align  3
104         .long   1001b, 1006b
105         .long   1002b, 1006b
106         .long   1003b, 1006b
107         .long   1004b, 1006b
108         .previous
109 
110 #define instr r4
111 #define reg   r5
112 #define stack r6
113 
114 @ Print register value
115 .Ldumpstm:  stmfd   sp!, {instr, reg, stack, r7, lr}
116         mov stack, r0
117         mov instr, r1
118         mov reg, #10
119         mov r7, #0
120 1:      mov r3, #1
121         tst instr, r3, lsl reg
122         beq 2f
123         add r7, r7, #1
124         teq r7, #6
125         moveq   r7, #1
126         moveq   r1, #'\n'
127         movne   r1, #' '
128         ldr r3, [stack], #-4
129         mov r2, reg
130         adr r0, .Lfp
131         bl  printk
132 2:      subs    reg, reg, #1
133         bpl 1b
134         teq r7, #0
135         adrne   r0, .Lcr
136         blne    printk
137         ldmfd   sp!, {instr, reg, stack, r7, pc}
138 
139 .Lfp:       .asciz  "%cr%d:%08x"
140 .Lcr:       .asciz  "\n"
141 .Lbad:      .asciz  "Backtrace aborted due to bad frame pointer <%p>\n"
142         .align
143 .Ldsi:  
144         @ Used to determine whether it is stmfd sp!Instruction, and the parameter contains fp, ip, lr, pc,Not included r10
145         .word   0xe92dd800 >> 10    @ stmfd sp!, {... fp, ip, lr, pc}
146         @ Used to determine whether it is stmfd sp!Instruction, and the parameter does not contain r10, fp, ip, lr, pc
147         .word   0xe92d0000 >> 10    @ stmfd sp!, {}