問題:
一台客戶現場機器,運行一周左右偶然發生一次應用段錯誤或者double free問題,cpu可能是arm、mips、x86等架構,有什么好的方法捕捉異常日志?
困難點:
1. 研發環境常使用gdb+coredump技術解決此類問題,客戶現場等非研發環境的偶現應用異常問題,不方便使用,操作起來有一定難度
2. 不同架構(arm32、arm64、mips、x86),不同版本C庫和gdb,棧回溯效果差異很大。PC ubuntu系統測試,glibc 2.15,發生應用double free,直接打印棧回溯信息,其他架構的CPU上測試沒有這個功能。arm64架構的某款CPU上測試,gdb對strip過的應用程序無法棧回溯, PC ubuntu系統測試沒有這個問題。
解決方案分析:
當應用程序發生段錯誤,將執行內核do_page_fault函數,在該函數獲取應用段錯誤當時struct pt_regs,調用get_user從用戶空間棧獲取棧回溯有關數據,就可以對應用程序棧回溯,實現gdb類似效果。原理沒有問題,實際驗證也可行。這個棧回溯過程,也可以讀取段錯誤應用的elf文件信息,這樣棧回溯時能分析並打印出棧回溯過程每一級函數的名字。這個方法有幾個優點。
- 把內核對應用異常棧回溯的代碼編譯進內核,任何時候應用程序發生段錯誤、double free,內核都可以對異常應用棧回溯,不需要使用gdb工具
- 內核對異常應用棧回溯,如果支持mips、arm32、arm64、x86等架構,不需要應用程序針對特定架構實現異常棧回溯功能
- 內核對異常應用棧回溯日志,是應用異常時函數調用關系,日志可以保存到存儲設備,隨時查看,並且一目了然,不需要問題發生后再運行gdb+coredump重現問題
- 如果可執行程序strip過,arm64架構下也能實現內核對異常應用棧回溯,而gdb對這種情況無能為力
棧回溯的原理
如上圖所示,是一個傳統的arm架構下函數棧數據分布,函數棧由fp和sp寄存器分別指向棧底和棧頂
當執行入棧操作時,lr和fp寄存器的值存入棧中,然后令fp寄存器指向函數棧的棧頂,本例是函數棧第二片內存地址(函數無局部變量)。棧回溯時,首先根據fp寄存器指向的地址,取出保存在函數棧中lr和fp寄存器的數據,lr的值是函數返回地址,fp的值是上一級函數棧的棧頂地址
1.堆棧指針r13(SP):每一種異常模式都有其自己獨立的r13,它通常指向異常模式所專用的堆棧,也就是說五種異常模式、非異常模式(用戶模式和系統模式),
都有各自獨立的堆棧,用不同的堆棧指針來索引。這樣當ARM進入異常模式的時候,程序就可以把一般通用寄存器壓入堆棧,返回時再出棧,保證了各種模式下程序的狀態的完整性 2.連接寄存器r14(LR):每種模式下r14都有自身版組,它有兩個特殊功能 (1)保存子程序返回地址。使用BL或BLX時,跳轉指令自動把返回地址放入r14中;子程序通過把r14復制到PC來實現返回 (2)當異常發生時,異常模式的r14用來保存異常返回地址,將r14如棧可以處理嵌套中斷。 3、程序計數器r15(PC):PC是有讀寫限制的。當
沒有超過讀取限制的時候,讀取的值是指令的地址加上8個字節,由於ARM指令總是以字對齊的,故bit[1:0]總是00。
當用str或stm存儲PC的時候,偏移量有可能是8或12等其它值
unwind 形式的棧回溯
1 /* 2 * Unwind a single frame starting with *sp for the symbol at *pc. It 3 * updates the *pc and *sp with the new values. 4 */ 5 int unwind_frame(struct stackframe *frame) 6 { 7 unsigned long low; 8 const struct unwind_idx *idx; 9 struct unwind_ctrl_block ctrl; 10 11 /* store the highest address on the stack to avoid crossing it*/ 12 low = frame->sp; 13 ctrl.sp_high = ALIGN(low, THREAD_SIZE); 14 15 pr_debug("%s(pc = %08lx lr = %08lx sp = %08lx)\n", __func__, 16 frame->pc, frame->lr, frame->sp); 17 18 if (!kernel_text_address(frame->pc)) 19 return -URC_FAILURE; 20 21 idx = unwind_find_idx(frame->pc); //根據PC寄存器的值查找段內存地址 22 if (!idx) { 23 pr_warn("unwind: Index not found %08lx\n", frame->pc); 24 return -URC_FAILURE; 25 } 26 27 ctrl.vrs[FP] = frame->fp; 28 ctrl.vrs[SP] = frame->sp; 29 ctrl.vrs[LR] = frame->lr; 30 ctrl.vrs[PC] = 0; 31 32 if (idx->insn == 1) 33 /* can't unwind */ 34 return -URC_FAILURE; 35 else if ((idx->insn & 0x80000000) == 0) 36 /* prel31 to the unwind table */ 37 ctrl.insn = (unsigned long *)prel31_to_addr(&idx->insn); 38 else if ((idx->insn & 0xff000000) == 0x80000000) 39 /* only personality routine 0 supported in the index */ 40 ctrl.insn = &idx->insn; 41 else { 42 pr_warn("unwind: Unsupported personality routine %08lx in the index at %p\n", 43 idx->insn, idx); 44 return -URC_FAILURE; 45 } 46 47 /* check the personality routine */ 48 if ((*ctrl.insn & 0xff000000) == 0x80000000) { 49 ctrl.byte = 2; 50 ctrl.entries = 1; 51 } else if ((*ctrl.insn & 0xff000000) == 0x81000000) { 52 ctrl.byte = 1; 53 ctrl.entries = 1 + ((*ctrl.insn & 0x00ff0000) >> 16); 54 } else { 55 pr_warn("unwind: Unsupported personality routine %08lx at %p\n", 56 *ctrl.insn, ctrl.insn); 57 return -URC_FAILURE; 58 } 59 60 ctrl.check_each_pop = 0; 61 62 while (ctrl.entries > 0) { 63 int urc; 64 if ((ctrl.sp_high - ctrl.vrs[SP]) < sizeof(ctrl.vrs)) 65 ctrl.check_each_pop = 1; 66 /*分析出棧指令相關編碼數據,分析函數入棧函數的指令*/ 67 urc = unwind_exec_insn(&ctrl); 68 if (urc < 0) 69 return urc; 70 if (ctrl.vrs[SP] < low || ctrl.vrs[SP] >= ctrl.sp_high) 71 return -URC_FAILURE; 72 } 73 74 if (ctrl.vrs[PC] == 0) 75 ctrl.vrs[PC] = ctrl.vrs[LR]; 76 77 /* check for infinite loop */ 78 if (frame->pc == ctrl.vrs[PC]) 79 return -URC_FAILURE; 80 81 frame->fp = ctrl.vrs[FP]; 82 frame->sp = ctrl.vrs[SP];//上一級函數棧底 83 frame->lr = ctrl.vrs[LR];//函數返回地址 84 frame->pc = ctrl.vrs[PC]; 85 86 return URC_OK; 87 }
問題:
假設應用程序函數執行流程是test_c()->test_b()->test_a(),test_a()函數發生段錯誤,內核將自動執行do_page_fault(……,struct pt_regs *regs)函數,該結構體中regs->pc是發生段錯誤test_a()函數的指令地址,假如是0x400538,regs->regs[29]就是fp寄存器。怎么實現內核對段錯誤應用的棧回溯?
策略:(對應用段錯誤棧回溯)
模仿unwind_frame函數,增加user_unwind_frame函數,以實現do_page_fault函數中,對段錯誤應用程序棧回溯,代碼如圖。經過棧回溯,假設從test_a函數棧中分析出test_a函數返回地址是0x400550(處於test_b函數中),繼續棧回溯,找到test_b函數的返回地址是0x400588(處於test_c函數)
這樣可以在內核do_page_fault中,對段錯誤應用程序棧回溯,執行過程打印如下: user thread backstrace pc1:0x400538 pc2:0x400550 pc3:0x400588
反匯編后可以知道函數調用流程是test_c()->test_b()->test_a()。這個方法還可以繼續優化:還是do_page_fault函數中,對應用棧回溯過程,讀取可執行程序elf文件信息,分析並打印出該指令地址所在的函數的名字。這需要用到elf可執行程序文件數據分布的原理,尤其是elf文件 section部分的數據
在內核里讀取elf可執行程序文件的“.symtab”和”.strtab” section的數據,就可以分析出該文件的test_c()、test_b()、test_a()三個函數名字字符串、函數運行首地址、函數指令字節數。比如數據如下
函數名字 函數指令首地址 函數指令結束地址 test_c 0x400518 0x400518 +0x27 test_b 0x400545 0x400545 +0x35
user thread backstrace [<0x400538>] test_a + 0x20/0x27 [<0x400550>] test_b +0x0b/0x35 [<0x400588>] test_c + 0x08 /0x20
分析實例
#include <stdio.h> #include <stdlib.h> char buf[5]; int test_a() { printf("%s \n", __func__); memcpy(buf, "12345677", 7); return 0; } int test_b() { printf("%s \n", __func__); memset(buf, 0, sizeof(buf)); test_a(); return 0; } int test_c() { printf("%s \n", __func__); sleep(1); test_b(); return 0; } int main() { printf("%s \n", __func__); test_c(); return 0; }
例子是一個可執行程序test演示代碼,用到了memcpy等庫函數,本例是C庫文件libc.so中的函數
可執行程序文件的“.dynstr” section包含了用到的庫函數名字,” .dynsym” section的數據是一個個struct elf64_sym結構體,每個對應一個用到的庫函數結構體。兩個section表述的庫函數信息是一一對應的,如下圖:
ibc.so庫文件的“.dynstr” section包含了C庫所有的庫函數名字,” .dynsym” section的數據也是一個個struct elf64_sym結構體,每個對應一個C庫的庫函數結構體
libc.so的”.dynsym” section的庫函數結構體struct elf64_sym中, st_value是庫函數原始首地址、st_size是庫函數指令字節數。為什么是原始首地址?因為可執行程序調用C庫函數時,會對C庫函數進行一次重定向,然后映射到可執行程序的應用空間,最后才執行C庫函數的指令代碼
1 “.plt” section匯編代碼 2 0000000000400480 <memcpy@plt>: 3 ………….. 4 400484: f944fa11 ldr x17, [x16,#2544] 5 400488: 9127c210 add x16, x16, #0x9f0 6 40048c: d61f0220 br x17
1 test_a函數匯編代碼 2 0000000000400650 <test_a>: 3 400650: a9bf7bfd stp x29, x30, [sp,#-16]! 4 400654: 910003fd mov x29, sp 5 …………………. 6 400660: 97ffffa0 bl 4004e0 <puts@plt> 7 ……………… 8 400678: 97ffff82 bl 400480 <memcpy@plt>
如test_a函數匯編代碼,當執行memcpy函數,實際是先執行“.plt” section的memcpy@plt 函數。然后在memcpy@plt函數匯編代碼里,ldr x17, [x16,#2544]計算出memcpy庫函數實際運行地址在“.got.plt” section的內存地址0x410a38,取出該地址的數據存於x17寄存器。如右圖所示,就是把橙色內存單元的數據0x7f91db5a40保存到x17,然后br x17就是跳轉到memcpy庫函數實際首地址,執行該函數的代碼
使用方法:
如果我們能知道libc.so中所有庫函數的運行首地址和結束地址,這樣當在C庫中崩潰,比如此時pc值是0x7f91db5a60,我們就能知道0x7f91db5a60處於哪個庫函數,這樣就知道怎么在C庫中棧回溯了。
具體實現方法:
- 以memcpy中崩潰為例, 從libc.so文件的” .dynsym” section找到memcpy庫函數的struct elf64_sym結構,該結構的成員st_value就是memcpy庫函數的原始首地址
- 從可執行程序”.got.plt” section找到庫函數memcpy的運行首地址,memcpy的運行首地址減去其原始首地址就是庫函數的原始首地址與運行首地址之差,命名為dx
- 從libc.so分離出所有庫函數的struct elf64_sym結構,知道每個庫函數的原始首地址,原始首地址+dx就是每個庫函數的運行首地址,再結合st_size就知道庫函數的運行結束地址。從libc.so文件的“.dynstr” section又知道了每個庫函數的名字。這樣知道了每個庫函數運行首地址、結束地址、函數名字,就具備了棧回溯的條件
double free應用:
double free是C庫檢測到異常,然后向當前進程發送SIGABRT信號,然后進入內核空間,會執行到do_send_specific函數發送信號。在該函數中,檢測到是SIGABRT信號,通過task_pt_regs(current)獲取異常進程進入內核空間前pc、lr、fp等寄存器,然后運用前文的棧回溯原理,對double free應用流程棧回溯,如下是演示效果。
演示效果
應用在test_a函數調用free庫函數兩次后,內核打印:
[< 0x7f91dxxxx>] raise() 0x38/0x78 [< 0x7f91dxxxx>] abort() 0x1b0/0x308 [<0x000400538>] test_a() 0x6c/0xa4 [<0x000400550>] test_b() 0x20/0x458 [<0x000400588>] test_c() 0x20/0x64
源碼:
https://github.com/dongzhiyan-stack/user_stack_backstrace-in-kernel.git

1 /* 2 * 內核對異常應用棧回溯 3 * 4 * author : hujunpeng 5 * email : dongzhiyan_linux@163.com 6 */ 7 8 #include <linux/tick.h> 9 #include <linux/stddef.h> 10 #include <linux/unistd.h> 11 #include <linux/export.h> 12 #include <linux/ptrace.h> 13 #include <linux/sys.h> 14 #include <linux/user.h> 15 #include <linux/init.h> 16 #include <linux/completion.h> 17 #include <linux/kallsyms.h> 18 #include <linux/random.h> 19 #include <linux/module.h> 20 #include <linux/kernel.h> 21 #include <linux/fs.h> 22 #include <linux/mm.h> 23 #include <linux/mman.h> 24 #include <linux/signal.h> 25 #include <linux/binfmts.h> 26 #include <linux/string.h> 27 #include <linux/file.h> 28 #include <linux/slab.h> 29 #include <linux/elfcore.h> 30 #include <linux/init.h> 31 #include <linux/highuid.h> 32 #include <linux/compiler.h> 33 #include <linux/highmem.h> 34 #include <linux/pagemap.h> 35 #include <linux/vmalloc.h> 36 #include <linux/security.h> 37 #include <linux/random.h> 38 #include <linux/elf.h> 39 #include <linux/utsname.h> 40 #include <linux/coredump.h> 41 #include <linux/sched.h> 42 #include <asm/uaccess.h> 43 #include <asm/param.h> 44 #include <asm/page.h> 45 #include <asm/stacktrace.h> 46 47 #ifdef CONFIG_MIPS 48 #include <asm/asm.h> 49 #include <asm/mipsregs.h> 50 #include <asm/processor.h> 51 #include <asm/uaccess.h> 52 #include <asm/io.h> 53 #include <asm/inst.h> 54 #endif 55 56 #define FUN_NAME_LEN 50 57 struct sym_fun_info{ 58 char name[FUN_NAME_LEN]; 59 unsigned long fun_first_instruct_addr; 60 unsigned long fun_end_instruct_addr; 61 }; 62 struct user_stack_unwind{ 63 unsigned long elf_text_start; 64 unsigned long elf_text_end; 65 unsigned long thread_stack_start; 66 struct sym_fun_info sym_info; 67 struct task_struct *thread; 68 struct mutex stack_backstrace_lock; 69 }; 70 71 struct mips_frame_info { 72 void *func; 73 unsigned long func_size; 74 int frame_size; 75 int pc_offset; 76 }; 77 struct elf_file_info{ 78 struct elf_shdr section_dynsym;//保存elf文件的 dynsym section結構體 79 struct elf_shdr section_dynstr;//保存elf文件的 dynstr section結構體 80 struct elf_shdr section_symtab;//保存elf文件的 symtab section結構體 81 82 struct elf_sym *first_lib_sym;//指向elf文件dynsym section數據區,該數據區是一個個庫函數的struct elf_sym結構體,elf可執行程序和lib庫文件都用到 83 unsigned char *elf_lib_fun_str;//指向elf文件dynstr section的數據區,該數據區是一個個庫函數名字字符串,elf可執行程序和lib庫文件都用到 84 struct elf_sym *first_elf_sym;//保存elf 可執行程序文件中.symtab section區數據,該數據區是一個個可執行程序自己的函數的struct elf_sym結構體 85 unsigned char *elf_fun_str;//保存elf 可執行程序文件中.strtab section區數據,該數據區是一個個可執行程序函數名字字符串 86 87 unsigned long *got_addr;//保存got段的內存首地址 88 unsigned long elf_lib_fun_off;//庫函數原始首函數地址與實際運行首地址的之差 89 int elf_strip;//可執行程序strip過置1,否則為0 90 }; 91 92 static struct elf_file_info elf_info,lib_info; 93 static struct user_stack_unwind user_stack_unwind_info; 94 95 static int print_user_ip_sym(unsigned long pc); 96 static char *get_elflib_path_file_name(struct task_struct *task,unsigned long addr); 97 static long get_file_size(struct file *file); 98 static int get_lib_fun_offset(struct elf_file_info *elf_info,struct elf_file_info *lib_info); 99 static int get_lib_fun_info(struct sym_fun_info * sym_lib_info,struct elf_file_info *lib_info,unsigned long addr,unsigned long lib_fun_offset); 100 static int get_elf_fun_info(struct sym_fun_info * elf_sym_info,struct elf_file_info *elf_info,unsigned long addr); 101 102 #define OPEN_PRINT 0 103 #define user_stack_printk(fmt,...) \ 104 do{if(OPEN_PRINT) \ 105 printk(fmt,##__VA_ARGS__); \ 106 }while(0) 107 108 #ifdef CONFIG_MIPS 109 #define elf_sym elf32_sym 110 111 #define J_TARGET(pc,target) \ 112 (((unsigned long)(pc) & 0xf0000000) | ((target) << 2)) 113 114 static inline int is_ra_save_ins(union mips_instruction *ip) 115 { 116 #ifdef CONFIG_CPU_MICROMIPS 117 union mips_instruction mmi; 118 119 /* 120 * swsp ra,offset 121 * swm16 reglist,offset(sp) 122 * swm32 reglist,offset(sp) 123 * sw32 ra,offset(sp) 124 * jradiussp - NOT SUPPORTED 125 * 126 * microMIPS is way more fun... 127 */ 128 if (mm_insn_16bit(ip->halfword[0])) { 129 mmi.word = (ip->halfword[0] << 16); 130 return ((mmi.mm16_r5_format.opcode == mm_swsp16_op && 131 mmi.mm16_r5_format.rt == 31) || 132 (mmi.mm16_m_format.opcode == mm_pool16c_op && 133 mmi.mm16_m_format.func == mm_swm16_op)); 134 } 135 else { 136 mmi.halfword[0] = ip->halfword[1]; 137 mmi.halfword[1] = ip->halfword[0]; 138 return ((mmi.mm_m_format.opcode == mm_pool32b_op && 139 mmi.mm_m_format.rd > 9 && 140 mmi.mm_m_format.base == 29 && 141 mmi.mm_m_format.func == mm_swm32_func) || 142 (mmi.i_format.opcode == mm_sw32_op && 143 mmi.i_format.rs == 29 && 144 mmi.i_format.rt == 31)); 145 } 146 #else 147 /* sw / sd $ra, offset($sp) */ 148 return (ip->i_format.opcode == sw_op || ip->i_format.opcode == sd_op) && 149 ip->i_format.rs == 29 && 150 ip->i_format.rt == 31; 151 #endif 152 } 153 154 static inline int is_jump_ins(union mips_instruction *ip) 155 { 156 #ifdef CONFIG_CPU_MICROMIPS 157 /* 158 * jr16,jrc,jalr16,jalr16 159 * jal 160 * jalr/jr,jalr.hb/jr.hb,jalrs,jalrs.hb 161 * jraddiusp - NOT SUPPORTED 162 * 163 * microMIPS is kind of more fun... 164 */ 165 union mips_instruction mmi; 166 167 mmi.word = (ip->halfword[0] << 16); 168 169 if ((mmi.mm16_r5_format.opcode == mm_pool16c_op && 170 (mmi.mm16_r5_format.rt & mm_jr16_op) == mm_jr16_op) || 171 ip->j_format.opcode == mm_jal32_op) 172 return 1; 173 if (ip->r_format.opcode != mm_pool32a_op || 174 ip->r_format.func != mm_pool32axf_op) 175 return 0; 176 return (((ip->u_format.uimmediate >> 6) & mm_jalr_op) == mm_jalr_op); 177 #else 178 if (ip->j_format.opcode == j_op) 179 return 1; 180 if (ip->j_format.opcode == jal_op) 181 return 1; 182 if (ip->r_format.opcode != spec_op) 183 return 0; 184 return ip->r_format.func == jalr_op || ip->r_format.func == jr_op; 185 #endif 186 } 187 188 static inline int is_sp_move_ins(union mips_instruction *ip) 189 { 190 #ifdef CONFIG_CPU_MICROMIPS 191 /* 192 * addiusp -imm 193 * addius5 sp,-imm 194 * addiu32 sp,sp,-imm 195 * jradiussp - NOT SUPPORTED 196 * 197 * microMIPS is not more fun... 198 */ 199 if (mm_insn_16bit(ip->halfword[0])) { 200 union mips_instruction mmi; 201 202 mmi.word = (ip->halfword[0] << 16); 203 return ((mmi.mm16_r3_format.opcode == mm_pool16d_op && 204 mmi.mm16_r3_format.simmediate && mm_addiusp_func) || 205 (mmi.mm16_r5_format.opcode == mm_pool16d_op && 206 mmi.mm16_r5_format.rt == 29)); 207 } 208 return (ip->mm_i_format.opcode == mm_addiu32_op && 209 ip->mm_i_format.rt == 29 && ip->mm_i_format.rs == 29); 210 #else 211 /* addiu/daddiu sp,sp,-imm */ 212 if (ip->i_format.rs != 29 || ip->i_format.rt != 29) 213 return 0; 214 if (ip->i_format.opcode == addiu_op || ip->i_format.opcode == daddiu_op) 215 return 1; 216 #endif 217 return 0; 218 } 219 /** 220 * user_process_lookup_size_offset - 根據傳入的指令地址計算所處函數的指令字節數和該指令地址的偏移 221 * @addr - 傳入的指令地址 222 * @symbolsize - 根據addr計算出的函數指令字節數 223 * @offset - addr相對函數指令首地址的偏移 224 * 225 * returns: 226 * 1:找到addr所處的函數 227 * 0: 沒有找到addr所處的函數 228 */ 229 static int user_process_lookup_size_offset(unsigned long addr, unsigned long *symbolsize,unsigned long *offset) 230 { 231 struct sym_fun_info sym_func_info; 232 int ret; 233 234 //如果addr在可執行程序代碼段 235 if(addr >= user_stack_unwind_info.elf_text_start && addr <= user_stack_unwind_info.elf_text_end) 236 { 237 ret = get_elf_fun_info(&sym_func_info,&elf_info,addr); 238 if(ret) 239 { 240 user_stack_printk("%s get_elf_fun_info:%d error\n",__func__,ret); 241 return 0; 242 } 243 } 244 else//addr在庫中 245 { 246 //根據可執行程序的.dynstr和.dynsym信息分析出庫函數的運行地址和庫函數原始的偏差值 247 ret = get_lib_fun_offset(&elf_info,&lib_info); 248 if(ret) 249 { 250 user_stack_printk("%s get_lib_fun_offset:%d error\n",__func__,ret); 251 return 0; 252 } 253 memset(&sym_func_info,0,sizeof(struct sym_fun_info)); 254 //根據addr獲取庫函數所處的函數首地址、函數指令字節數等信息 255 ret = get_lib_fun_info(&sym_func_info,&lib_info,addr,elf_info.elf_lib_fun_off); 256 if(ret) 257 { 258 /*mips架構double free棧回溯時,中途會遇到未知C庫函數。比如test_a ->C庫未知函數1->C庫未知函數2->abort->raise。 259 棧回溯時,abort->raise兩個函數都能打印出來,但是回溯到未知函數2,就會終止,arm64用gdb棧回溯也是這樣,直接終止調。 260 但是我的arm64內核double free棧回溯能完整回溯,這是比gdb優越的另一點。由於mips棧回溯依賴每個函數的指令首地址,現在 261 碰到C庫未知函數2,當然不知道該函數名字和指令首地址,那就直接return 0棧回溯結束,這就從原理上證實,mips架構double free 262 棧回溯存在問題,有時間研究一下"C庫未知函數2"出現的原因。 263 */ 264 user_stack_printk("%s get_lib_fun_info:%d error\n",__func__,ret); 265 return 0; 266 } 267 } 268 269 *offset = addr - sym_func_info.fun_first_instruct_addr; 270 *symbolsize = sym_func_info.fun_end_instruct_addr - sym_func_info.fun_first_instruct_addr; 271 272 return 1; 273 } 274 /** 275 * get_frame_info - 根據傳入的函數指令首地址,依次分析匯編指令,根據匯編指令找到函數棧大小和函數返回地址在棧中的保存位置 276 * @info - info->func就是函數指令首地址,info->frame_size保存函數棧大小,info->pc_offset保存函數返回地址在函數棧中的偏移 277 * 278 * returns: 279 * 0:分析匯編指令后,找到函數棧大小和函數返回地址在棧中的保存位置 280 * 1:沒有根據匯編指令分析出函數函數返回地址在棧中的保存位置 281 * <0:其他異常 282 */ 283 static int get_frame_info(struct mips_frame_info *info) 284 { 285 #ifdef CONFIG_CPU_MICROMIPS 286 union mips_instruction *ip = (void *) (((char *) info->func) - 1); 287 #else 288 union mips_instruction *ip = info->func; 289 #endif 290 291 union mips_instruction *tmp_ip = ip; 292 union mips_instruction ip_data; 293 unsigned long tmp_data; 294 unsigned long *p_ip; 295 296 unsigned max_insns = info->func_size / sizeof(union mips_instruction); 297 unsigned i; 298 299 info->pc_offset = -1; 300 info->frame_size = 0; 301 302 if (!ip) 303 goto err; 304 305 if (max_insns == 0) 306 max_insns = 128U; /* unknown function size */ 307 max_insns = min(128U, max_insns); 308 309 for (i = 0; i < max_insns; i++, ip++) { 310 //保留原ip值,下方恢復ip值 311 tmp_ip = ip; 312 //union mips_instruction 結構大小為unsigned long,一條指令占的空間大小 313 p_ip = (unsigned long*)ip; 314 if(get_user(tmp_data,p_ip)) 315 { 316 printk(KERN_ERR"%s get_user error ip:0x%p\n",__func__,ip); 317 return -EFAULT; 318 } 319 memcpy(&ip_data,&tmp_data,sizeof(union mips_instruction)); 320 ip = &ip_data; 321 322 if (is_jump_ins(ip)) 323 break; 324 if (!info->frame_size) { 325 if (is_sp_move_ins(ip)) 326 { 327 #ifdef CONFIG_CPU_MICROMIPS 328 if (mm_insn_16bit(ip->halfword[0])) 329 { 330 unsigned short tmp; 331 332 if (ip->halfword[0] & mm_addiusp_func) 333 { 334 tmp = (((ip->halfword[0] >> 1) & 0x1ff) << 2); 335 info->frame_size = -(signed short)(tmp | ((tmp & 0x100) ? 0xfe00 : 0)); 336 } else { 337 info->frame_size = -(signed short)(tmp & 0xf); 338 } 339 ip = (void *) &ip->halfword[1]; 340 ip--; 341 } else 342 #endif 343 info->frame_size = - ip->i_format.simmediate; 344 } 345 ip = tmp_ip; 346 continue; 347 } 348 if (info->pc_offset == -1 && is_ra_save_ins(ip)) { 349 info->pc_offset = 350 ip->i_format.simmediate / sizeof(long); 351 break; 352 } 353 //恢復原ip值,目的是不破壞函數原有框架 354 ip = tmp_ip; 355 } 356 if (info->frame_size && info->pc_offset >= 0) /* nested */ 357 return 0; 358 if (info->pc_offset < 0) /* leaf */ 359 return 1; 360 /* prologue seems boggus... */ 361 printk(KERN_ERR"%s error end\n",__func__); 362 err: 363 return -1; 364 } 365 /** user_unwind_stack_by_address - 根據當前函數的pc值,計算出上一級函數的棧頂地址和當前函數的返回地址 366 * @stack_page - 線程內核棧棧頂 367 * @*sp - 保存上一級函數棧頂 368 * @pc - 當前函數的pc值,就是棧回溯過程打印的函數地址 369 * @*ra - 崩潰函數中沒有調用其他函數時,是應用段錯誤當時的ra寄存數據,這種情況第一次棧回溯時使用 370 * 371 * returns: 372 * >0:找到當前函數返回地址,就是上一級函數中的指令地址 373 * 0: 沒有找到當前函數返回地址 374 */ 375 static unsigned long user_unwind_stack_by_address(unsigned long stack_page, 376 unsigned long *sp, 377 unsigned long pc, 378 unsigned long *ra) 379 { 380 struct mips_frame_info info; 381 unsigned long size, ofs; 382 int leaf; 383 extern void ret_from_irq(void); 384 extern void ret_from_exception(void); 385 if (!stack_page) 386 return 0; 387 388 if (!user_process_lookup_size_offset(pc, &size, &ofs)) 389 { 390 user_stack_printk("%s can not find vaild user function at pc:0x%lx\n",__func__,pc); 391 return 0; 392 } 393 /* 394 * Return ra if an exception occurred at the first instruction 395 */ 396 if (unlikely(ofs == 0)) { 397 pc = *ra; 398 *ra = 0; 399 return pc; 400 } 401 402 info.func = (void *)(pc - ofs); 403 info.func_size = ofs; /* analyze from start to ofs */ 404 leaf = get_frame_info(&info); 405 if (leaf < 0) 406 return 0; 407 408 //判斷sp是否超出當前進程的用戶空間棧底 409 if(*sp + info.frame_size > user_stack_unwind_info.thread_stack_start) 410 { 411 user_stack_printk("%s expand user thread stack\n",__func__); 412 return 0; 413 } 414 415 if (leaf) 416 { 417 /* 418 * For some extreme cases, get_frame_info() can 419 * consider wrongly a nested function as a leaf 420 * one. In that cases avoid to return always the 421 * same value. 422 */ 423 pc = pc != *ra ? *ra : 0; 424 } 425 else 426 { 427 //pc = ((unsigned long *)(*sp))[info.pc_offset]; 428 unsigned long *tmp; 429 tmp = (unsigned long *)(*sp) + info.pc_offset; 430 if(get_user(pc,tmp)) 431 { 432 printk(KERN_ERR"%s get_user sp info.pc_offset error\n",__func__); 433 return 0; 434 } 435 } 436 437 *sp += info.frame_size; 438 *ra = 0; 439 440 return pc; 441 } 442 /** user_unwind_stack - 根據當前函數的pc值,計算出上一級函數的棧頂地址和當前函數的返回地址 443 * @task - 當前進程task結構 444 * @*sp - 保存上一級函數棧頂 445 * @pc - 當前函數的pc值,就是棧回溯過程打印的函數地址 446 * @*ra - 崩潰函數中沒有調用其他函數時,是應用段錯誤當時的ra寄存數據,這種情況第一次棧回溯時使用 447 * 448 * returns: 449 * >0:找到當前函數返回地址,就是上一級函數中的指令地址 450 * 0: 沒有找到當前函數返回地址 451 */ 452 static unsigned long user_unwind_stack(struct task_struct *task, unsigned long *sp,unsigned long pc, unsigned long *ra) 453 { 454 unsigned long stack_page = (unsigned long)task_stack_page(task); 455 456 return user_unwind_stack_by_address(stack_page, sp, pc, ra); 457 } 458 /** show_user_backtrace - mips架構棧回溯的核心,在該函數計算和打印棧回溯的各級函數信息 459 * @task - 當前進程task結構 460 * @regs - 異常進程的struct pt_regs結構,包含棧回溯過程需要的pc、ra、sp等寄存器 461 * 462 * returns:void 463 */ 464 static void show_user_backtrace(struct task_struct *task, const struct pt_regs *regs) 465 { 466 unsigned long sp = regs->regs[29]; 467 unsigned long ra = regs->regs[31]; 468 unsigned long pc = regs->cp0_epc; 469 unsigned long where; 470 int cycle_count = 0; 471 if (!task) 472 task = user_stack_unwind_info.thread; 473 474 printk("Call Trace:\n"); 475 do { 476 where = pc; 477 /*如果可執行程序stirp過,並且崩潰發生在可執行程序代碼段,這樣第一次棧回溯時用pc寄存器值,第二次棧回溯用的ra寄存器的值。 478 如果崩潰發生在C庫,C庫棧回溯不受影響,但是從C庫回到可執行程序代碼段時,比如此時pc = user_unwind_stack(),從最后一級C庫 479 函數棧中分析出函數返回地址並返回給pc,這是在可執行程序代碼段,然后下次循環,user_unwind_stack()就會因為找不到pc所在的函數 480 而返回0,print_user_ip_sym()打印上一個pc值,退出while循環。*/ 481 if((0 == cycle_count) && (elf_info.elf_strip)&& (pc >= user_stack_unwind_info.elf_text_start && pc <= user_stack_unwind_info.elf_text_end)) 482 pc = ra; 483 else 484 pc = user_unwind_stack(task, &sp, pc, &ra); 485 486 print_user_ip_sym(where); 487 cycle_count++; 488 }while (pc); 489 printk("\n"); 490 } 491 492 #elif defined CONFIG_ARM64 493 494 #define elf_sym elf64_sym 495 /** instructions_belong_to_one_fun - 判斷pc1和pc2兩個指令地址是否處於同一個函數 496 * @pc1 - 函數指令地址1 497 * @pc2 - 函數指令地址2 498 * 499 * returns: 500 * 1:pc1和pc2兩個指令地址處於同一個函數 501 * 0:pc1和pc2兩個指令地址不處於同一個函數 502 */ 503 static int instructions_belong_to_one_fun(struct elf_file_info *elf_info,unsigned long pc1,unsigned long pc2) 504 { 505 struct elf_sym *elf_fun_sym; 506 int i; 507 508 elf_fun_sym = (struct elf_sym*)elf_info->first_elf_sym; 509 510 //這里只判斷pc1和pc2是否處於同一個可執行程序函數的情況,不判斷是否處於同一個動態庫函數的情況 511 for(i = 0;i < elf_info->section_symtab.sh_size/sizeof(struct elf_sym);i++) 512 { 513 //elf_fun_sym->st_value 是可執行程序文件中每個函數的首地址 514 if((pc1 >= elf_fun_sym->st_value) && (pc1 < elf_fun_sym->st_value + elf_fun_sym->st_size)) 515 { 516 if((pc2 >= elf_fun_sym->st_value) && (pc2 < elf_fun_sym->st_value + elf_fun_sym->st_size)) 517 return 1; 518 } 519 elf_fun_sym ++; 520 } 521 return 0; 522 } 523 /** user_unwind_frame - arm64架構從當前函數棧中分析出當前函數返回地址和和上一級函數棧的地址 524 * @frame->sp 保存上一級函數棧頂 525 * @frame->fp 保存上一級函數的棧的第二片內存地址 526 * @frame->pc 保存當前函數的返回地址 527 * 528 * returns: 529 * 0:獲取frame結構成員成功 530 * <0:獲取frame結構成員失敗 531 */ 532 static int user_unwind_frame(struct stackframe *frame) 533 { 534 unsigned long high, low; 535 unsigned long fp = frame->fp; 536 537 low = frame->sp; 538 high = ALIGN(low, THREAD_SIZE); 539 540 //判斷sp是否超出當前進程的用戶空間棧底 541 if(frame->sp >= user_stack_unwind_info.thread_stack_start) 542 { 543 user_stack_printk("%s expand user thread stack\n",__func__); 544 return -EFAULT; 545 } 546 547 frame->sp = fp + 0x10; 548 549 //frame->fp = *(unsigned long *)(fp); 550 //從用戶空間獲取上一級函數的棧的第二片內存地址 551 if(get_user(frame->fp, (unsigned long *)(fp))) 552 { 553 printk(KERN_ERR"%s get_user1 error fp:0x%lx\n",__func__,fp); 554 return -EFAULT; 555 } 556 //frame->pc = *(unsigned long *)(fp + 8); 557 //從用戶空間獲取崩潰函數的返回地址 558 if(get_user(frame->pc, (unsigned long *)(fp + 8))) 559 { 560 printk(KERN_ERR"%s get_user2 error fp:0x%lx\n",__func__,fp); 561 return -EFAULT; 562 } 563 return 0; 564 } 565 /** show_user_backtrace - arm64棧回溯的核心,在該函數計算和打印棧回溯的各級函數信息 566 * @task - 當前進程task結構 567 * @regs - 異常進程的struct pt_regs結構,包含棧回溯過程需要的pc、sp、fp等寄存器 568 * 569 * returns:void 570 */ 571 static void show_user_backtrace(struct task_struct *task, const struct pt_regs *regs) 572 { 573 struct stackframe frame; 574 int ret,cycle_count; 575 unsigned long where; 576 unsigned long second_fun; 577 struct sym_fun_info sym_func_info; 578 579 frame.fp = regs->regs[29]; 580 frame.sp = regs->sp; 581 frame.pc = regs->pc; 582 583 if(get_user(second_fun, (unsigned long *)(regs->regs[29] + 8))) 584 { 585 printk(KERN_ERR"%s get_user error fp:0x%llx sp:0x%llx\n",__func__,regs->regs[29]+8,regs->sp); 586 return; 587 } 588 589 cycle_count = 0; 590 while (1) 591 { 592 /*這里的if判斷用於崩潰函數test_a_沒有調用其他函數的情況,正常函數lr寄存器數據和函數棧第二片內存中的數據是一致的,崩潰函數沒有調用其他函數時,開始開頭指令沒有把lr和fp寄存器入棧,此時的fp寄存器regs->regs[29]保存的數據還是上一級函數棧的第二片內存地址,則第一片內存地址中的數據一定是再上一級的函數地址,此時與lr寄存器regs->regs[30]肯定不想等,就是下邊的second_fun != regs->regs[30]。lr寄存器只要有函數調用,就保存函數返回地址。有個特例,如果崩潰函數有調用其他函數,但是崩潰位置在函數調用后,比如test_a_函數調用了printf后崩潰了,此時lr寄存器數據是printf("22")的下一條指令地址,就是lr還保持執行printf函數時的狀態,看來lr寄存器的數據只在函數調用時被修改,在函數返回后不會恢復。這種情況second_fun != regs->regs[30]也成立,就是靠second_fun != regs->regs[30]函數,判斷出regs->pc 和 regs->regs[30]指向的指令地址不屬於同一個函數的,就可以過濾這種情況了。 593 void test_a_() 594 { 595 int *p =NULL; 596 printf("22"); 597 *p = 0; 598 } 599 */ 600 where = frame.pc; 601 if((0 == cycle_count)&& (task == current) && (second_fun != regs->regs[30]) && (0 == instructions_belong_to_one_fun(&elf_info,regs->regs[30],regs->pc))) 602 { 603 frame.pc = regs->regs[30]; 604 } 605 else 606 { 607 //獲取函數的返回地址存於frame.pc和上一級函數的棧的第二片內存地址存於frame.fp 608 ret = user_unwind_frame(&frame); 609 if (ret < 0) 610 break; 611 } 612 613 //在可執行程序代碼段 614 if(where >= user_stack_unwind_info.elf_text_start && where < user_stack_unwind_info.elf_text_end) 615 { 616 //可執行程序沒有strip過 617 if(0 == elf_info.elf_strip) 618 { 619 //根據addr獲取可執行程序的函數的首地址、函數指令字節數等信息,保存到user_stack_unwind.sym_info結構 620 ret = get_elf_fun_info(&sym_func_info,&elf_info,where); 621 if(ret) 622 { 623 user_stack_printk("%s get_elf_fun_info:%d error\n",__func__,ret); 624 return; 625 } 626 } 627 } 628 else//在庫函數代碼段 629 { 630 //根據可執行程序文件和lib庫文件的.dynstr和.dynsym信息分析出庫函數的運行首地址和庫函數首原始的偏差值 631 ret = get_lib_fun_offset(&elf_info,&lib_info); 632 if(ret) 633 { 634 user_stack_printk("%s get_lib_fun_offset:%d error\n",__func__,ret); 635 return; 636 } 637 memset(&sym_func_info,0,sizeof(struct sym_fun_info)); 638 //根據addr獲取庫函數所處的函數首地址、函數指令字節數等信息,保存到user_stack_unwind.sym_info結構 639 ret = get_lib_fun_info(&sym_func_info,&lib_info,where,elf_info.elf_lib_fun_off); 640 if(ret) 641 { 642 /* 643 arm64 double free過程,test_a ->C庫未知函數1->C庫未知函數2->abort->raise,在回溯到C庫未知函數2時, 644 就找不到C庫函數,此時get_lib_fun_info返回-1,但是不出錯返回,繼續棧回溯,最后能完整回溯到可執行程序代碼段,gdb做不到。 645 arm64棧回溯使用fp寄存器定位函數棧,不依賴函數函數指令首地址,所以遇到未知C庫函數,照樣能棧回溯。 646 */ 647 user_stack_printk("%s get_lib_fun_info:%d error\n",__func__,ret); 648 //return 0; 649 } 650 } 651 cycle_count ++; 652 653 print_user_ip_sym(where); 654 } 655 } 656 #else 657 #error "unsupport architecture!!!!!!" 658 #endif 659 660 661 /** print_user_ip_sym - 打印pc所處函數的名字及相對函數指令首地址的偏移等信息 662 * @pc - 棧回溯過程每一級函數的指令地址 663 * 664 * returns: 665 * 1:找到pc所處的函數 666 * 0:沒有找到pc所處的函數 667 */ 668 static int print_user_ip_sym(unsigned long pc) 669 { 670 unsigned int fun_size,pc_off; 671 struct sym_fun_info *sym_info; 672 673 //user_stack_unwind_info.sym_info 保存庫函數的指令信息,新的改造,也保存可執行程序的自身函數信息 674 sym_info = &user_stack_unwind_info.sym_info; 675 if(pc >= sym_info->fun_first_instruct_addr && pc <= sym_info->fun_end_instruct_addr) 676 { 677 fun_size = sym_info->fun_end_instruct_addr - sym_info->fun_first_instruct_addr; 678 pc_off = pc - sym_info->fun_first_instruct_addr; 679 680 #ifdef CONFIG_ARM64 681 printk(KERN_ALERT"<0x%010lx> %s() 0x%x/0x%x\n",pc,sym_info->name,pc_off,fun_size); 682 #else 683 printk(KERN_ALERT"<0x%08lx> %s() 0x%x/0x%x\n",pc,sym_info->name,pc_off,fun_size); 684 #endif 685 memset(sym_info,0x00,sizeof(struct sym_fun_info)); 686 return 1; 687 } 688 else if(elf_info.elf_strip)//可執行程序沒有strip過 689 { 690 #ifdef CONFIG_ARM64 691 printk(KERN_ALERT"<0x%010lx> xxxxxx\n",pc); 692 #else 693 printk(KERN_ALERT"<0x%08lx> xxxxxx\n",pc); 694 #endif 695 return 1; 696 } 697 else 698 user_stack_printk("cat not find valid user function\n"); 699 700 return 0; 701 } 702 /** read_elf_section_info - 讀取elf可執行程序和庫文件的 .dynsym .dynstr .plt .got.plt section的數據保存到struct elf_file_info *elf_info結構, 703 * @elf_file - elf可執行程序和庫文件的struct file結構 704 * @elf_info - 該結構體成員保存elf文件的.dynsym .dynstr .plt .got.plt section的數據 705 * @is_elf_file - 1:elf可執行程序 0:elf庫文件 706 * 707 * returns: 708 * 0:讀取elf文件的.dynsym .dynstr .plt .got.plt section的數據成功 709 * <0:讀取失敗 710 */ 711 static int read_elf_section_info(struct file *elf_file,struct elf_file_info *elf_info,int is_elf_file) 712 { 713 // struct elf_shdr *section_head; 714 struct elf_shdr *p_section = NULL; 715 char *section_name; 716 int i; 717 long retval; 718 struct elfhdr elf_head; 719 unsigned char *section_data = NULL; 720 721 //讀取elf文件頭 722 retval = kernel_read(elf_file,0,(unsigned char *)&elf_head,sizeof(struct elfhdr)); 723 if (retval <= 0) { 724 retval = -EIO; 725 goto err; 726 } 727 section_data = kmalloc(sizeof(struct elf_shdr)*elf_head.e_shnum,GFP_KERNEL); 728 if(!section_data) 729 { 730 retval = -ENOMEM; 731 printk(KERN_ERR"%s kmalloc fail 1\n",__func__); 732 goto err; 733 } 734 //讀取所有section結構體信息到section_data數組 735 retval = kernel_read(elf_file,elf_head.e_shoff,section_data,sizeof(struct elf_shdr)*elf_head.e_shnum); 736 if (retval <= 0) { 737 retval = -EIO; 738 goto err; 739 } 740 //p_section 指向第一個section首地址 741 p_section = (struct elf_shdr *)section_data; 742 //section指向編號是elf_head->e_shstrndx的section,這個section對應的數據是每個section的名字字符串集合 743 p_section += elf_head.e_shstrndx; 744 section_name = kmalloc(p_section->sh_size,GFP_KERNEL); 745 if(!section_name) 746 { 747 retval = -ENOMEM; 748 printk(KERN_ERR"%s kmalloc fail 2\n",__func__); 749 goto err; 750 } 751 752 //section_name 指向編號是elf_head->e_shstrndx的section的數據區首地址,這個section的數據各個section的名字字符串。p_section->sh_offset是該section對應的數據的偏移 753 retval = kernel_read(elf_file,p_section->sh_offset,section_name,p_section->sh_size); 754 if (retval <= 0) { 755 user_stack_printk("%s line:%d kernel_read fail\n",__func__,__LINE__); 756 retval = -EIO; 757 goto err; 758 } 759 //指向第一個section結構 760 p_section = (struct elf_shdr *)section_data; 761 for(i = 0;i < elf_head.e_shnum;i++) 762 { 763 //.dynsym 段 每個section 的 sh_name 是該section名字字符串的索引 764 if(/*SHT_SYMTAB == p_section->sh_type && */strcmp(".dynsym",§ion_name[p_section->sh_name]) == 0) 765 { 766 #ifdef CONFIG_ARM64 767 user_stack_printk("%s find ,section sh_offset:0x%llx sh_size:0x%llx\n",§ion_name[p_section->sh_name],p_section->sh_offset,p_section->sh_size); 768 #else 769 user_stack_printk("%s find ,section sh_offset:0x%x sh_size:0x%x\n",§ion_name[p_section->sh_name],p_section->sh_offset,p_section->sh_size); 770 #endif 771 memcpy(&elf_info->section_dynsym,p_section,sizeof(struct elf_shdr));//保存.dynstr 段的信息 772 elf_info->first_lib_sym = kmalloc(p_section->sh_size,GFP_KERNEL);// 773 if(!elf_info->first_lib_sym) 774 goto err; 775 //從dynsym段指定的文件偏移地址復制dynsym段的數據到 elf_info.first_lib_sym,這些數據就是struct elf_sym結構的集合,每一個struct elf32_sym結構代表一個函數信息,包括該函數名字符串索引、函數默認運行地址、函數指令字節數 776 retval = kernel_read(elf_file,p_section->sh_offset,(unsigned char *)elf_info->first_lib_sym,p_section->sh_size); 777 if (retval <= 0) { 778 user_stack_printk("%s line:%d kernel_read fail d\n",__func__,__LINE__); 779 retval = -EIO; 780 goto err; 781 } 782 } 783 //.dynstr 段 784 else if(/*SHT_STRTAB == p_section->sh_type && */strcmp(".dynstr",§ion_name[p_section->sh_name]) == 0) 785 { 786 #ifdef CONFIG_ARM64 787 user_stack_printk("%s find ,section sh_offset:0x%llx sh_size:0x%llx\n",§ion_name[p_section->sh_name],p_section->sh_offset,p_section->sh_size); 788 #else 789 user_stack_printk("%s find ,section sh_offset:0x%x sh_size:0x%x\n",§ion_name[p_section->sh_name],p_section->sh_offset,p_section->sh_size); 790 #endif 791 memcpy(&elf_info->section_dynstr,p_section,sizeof(struct elf_shdr));//保存.dynstr 段section結構 792 elf_info->elf_lib_fun_str = kmalloc(p_section->sh_size,GFP_KERNEL);// 793 if(!elf_info->elf_lib_fun_str) 794 goto err; 795 //從dynstr段指定的文件偏移地址復制函數字符串數據到 elf_info->elf_lib_fun_str 796 retval = kernel_read(elf_file,p_section->sh_offset,elf_info->elf_lib_fun_str,p_section->sh_size); 797 if (retval <= 0) { 798 user_stack_printk("%s line:%d kernel_read fail d\n",__func__,__LINE__); 799 retval = -EIO; 800 goto err; 801 } 802 } 803 //.plt段,plt段是庫函數跳轉表,我們執行的printf庫函數,是先跳轉到這個段的printf@GLIBC_2.0 函數,然后跳轉到got段函數表,這里是每個庫函數的重定向后的函數首地址,在這里運行到c庫真實的printf函數 804 else if(/*SHT_STRTAB == p_section->sh_type && */strcmp(".plt",§ion_name[p_section->sh_name]) == 0) 805 { 806 #ifdef CONFIG_ARM64 807 user_stack_printk("%s find ,section sh_addr:0x%llx sh_offset:0x%llx sh_size:0x%llx\n",§ion_name[p_section->sh_name],p_section->sh_addr,p_section->sh_offset,p_section->sh_size); 808 #else 809 user_stack_printk("%s find ,section sh_addr:0x%x sh_offset:0x%x sh_size:0x%x\n",§ion_name[p_section->sh_name],p_section->sh_addr,p_section->sh_offset,p_section->sh_size); 810 #endif 811 } 812 //.got段,該段的sh_addr成員是程序運行后.got.plt段的用戶空間內存地址,這片內存的數據是plt段庫函數的重定向后庫函數首地址 813 else if(/*SHT_STRTAB == p_section->sh_type && */strcmp(".got.plt",§ion_name[p_section->sh_name]) == 0) 814 { 815 #ifdef CONFIG_ARM64 816 user_stack_printk("%s find sh_addr:0x%llx\n",§ion_name[p_section->sh_name],p_section->sh_addr); 817 #else 818 user_stack_printk("%s find sh_addr:0x%x\n",§ion_name[p_section->sh_name],p_section->sh_addr); 819 #endif 820 elf_info->got_addr = (unsigned long *)p_section->sh_addr; 821 } 822 823 //是elf可執行程序 824 if(is_elf_file) 825 { 826 //.symtab 段,可執行程序自己的函數的一個個 elf_sym 結構 827 if(/*SHT_SYMTAB == p_section->sh_type && */strcmp(".symtab",§ion_name[p_section->sh_name]) == 0) 828 { 829 #ifdef CONFIG_ARM64 830 user_stack_printk("%s find ,section sh_offset:0x%llx sh_size:0x%llx\n",§ion_name[p_section->sh_name],p_section->sh_offset,p_section->sh_size); 831 #else 832 user_stack_printk("%s find ,section sh_offset:0x%x sh_size:0x%x\n",§ion_name[p_section->sh_name],p_section->sh_offset,p_section->sh_size); 833 #endif 834 memcpy(&elf_info->section_symtab,p_section,sizeof(struct elf_shdr));//保存.symtab 段section結構 835 elf_info->first_elf_sym = kmalloc(p_section->sh_size,GFP_KERNEL);// 836 if(!elf_info->first_elf_sym) 837 goto err; 838 //從.symtab段指定的文件偏移地址讀取.symtab段的數據到 elf_info->first_elf_sym,,這些數據就是struct elf_sym結構的集合,每一個struct elf_sym結構代表一個函數信息,包括該函數名字符串索引、函數默認運行地址、函數指令字節數 839 retval = kernel_read(elf_file,p_section->sh_offset,(unsigned char *)elf_info->first_elf_sym,p_section->sh_size); 840 if (retval <= 0) { 841 user_stack_printk("%s line:%d kernel_read fail d\n",__func__,__LINE__); 842 retval = -EIO; 843 goto err; 844 } 845 } 846 //.strtab 段,可執行程序自己的函數名字字符串存儲在這里 847 else if(/*SHT_STRTAB == p_section->sh_type && */strcmp(".strtab",§ion_name[p_section->sh_name]) == 0) 848 { 849 #ifdef CONFIG_ARM64 850 user_stack_printk("%s find ,section sh_offset:0x%llx sh_size:0x%llx\n",§ion_name[p_section->sh_name],p_section->sh_offset,p_section->sh_size); 851 #else 852 user_stack_printk("%s find ,section sh_offset:0x%x sh_size:0x%x\n",§ion_name[p_section->sh_name],p_section->sh_offset,p_section->sh_size); 853 #endif 854 elf_info->elf_fun_str = kmalloc(p_section->sh_size,GFP_KERNEL);// 855 if(!elf_info->elf_fun_str) 856 goto err; 857 //從.strtab段指定的文件偏移地址讀取函數字符串數據到 elf_info->elf_fun_str 858 retval = kernel_read(elf_file,p_section->sh_offset,elf_info->elf_fun_str,p_section->sh_size); 859 if (retval <= 0) { 860 user_stack_printk("%s line:%d kernel_read fail d\n",__func__,__LINE__); 861 retval = -EIO; 862 goto err; 863 } 864 865 elf_info->elf_strip = 0; 866 } 867 868 } 869 p_section++; 870 } 871 872 retval = 0; 873 err: 874 if(section_data) 875 kfree(section_data); 876 return retval; 877 } 878 /** get_lib_fun_offset - 計算庫函數的實際運行首地址和原始首地址之差保存到 elf_info->elf_lib_fun_off 879 * @elf_info - 可執行程序的struct elf_file_info 結構 880 * @lib_info - 庫文件的struct elf_file_info 結構 881 * 882 * returns: 883 * 0:計算出庫函數的實際運行首地址和原始首地址之差 884 * <0:沒有計算庫函數的實際運行首地址和原始首地址之差 885 * 886 *note:這個函數的功能詳細描述:根據可執行程序的got段內存中存儲的庫函數strcmp運行地址got_lib_fun_val(假設got段第四片內存保存的數據是strcmp庫函數的運行地址,got_lib_fun_val保存這個運行地址),然后在lib庫文件中,.dynstr段搜索函數名字字符串是"strcmp"的函數,而.dynsym段保存的數據————函數struct elf_sym結構與 .dynstr段的函數名字字符串也是一一對應的。 887 比如, 假如.dynstr 段的第一個函數名字字符串是 "strcmp", .dynsym段的第一個struct elf_sym結構就是strcmp庫函數的,該結構的st_value是strcmp庫函數的俄原始地址,st_size是庫函數的指令字節數。 888 知道了strcmp庫函數的運行地址got_lib_fun_val,又在lib庫文件中.dynstr段找到了strcmp的字符串,同樣的偏移找到了 .dynsym段strcmp庫函數的struct elf_sym結構,就知道了它的原始函數地址st_value。got_lib_fun_val-st_value就是庫函數的運行地址和原始地址的差值off,應該適用於所有庫函數。之后我知道一個庫函數的st_value,就知道了它的運行地址首地址st_value+off,函數指令結束地址end,那知道任何一個庫函數中的崩潰地址pc, pc > st_value+off並且 pc < end時,就知道崩潰庫函數指令pc處於哪個庫函數了,當然也知道它的名字字符串。 889 有一點需要注意,庫函數的運行地址和原始地址的低12位數據是一樣的,測試證實了這一點,我覺得這與PAGE_SIZE是2的12次方有關。 890 */ 891 static int get_lib_fun_offset(struct elf_file_info *elf_info,struct elf_file_info *lib_info) 892 { 893 struct elf_sym *elf_lib_sym,*lib_sym; 894 //section_dynstr first_lib_sym; 895 unsigned char *lib_fun_name,*elf_lib_fun_name; 896 unsigned long *p_got_lib_fun; 897 unsigned long got_lib_fun_val = 0; 898 int i; 899 int ret = -1; 900 901 if(elf_info->elf_lib_fun_off) 902 { 903 user_stack_printk(KERN_DEBUG"%s elf_lib_fun_off already ok\n",__func__); 904 return 0; 905 } 906 907 //可執行程序的 908 elf_lib_sym = (struct elf_sym*)elf_info->first_lib_sym; 909 elf_lib_fun_name = (char *)elf_info->elf_lib_fun_str; 910 p_got_lib_fun = (unsigned long *)elf_info->got_addr;//這個是用戶態的地址,要用get_user復制數據 911 #ifdef CONFIG_MIPS 912 p_got_lib_fun += 2;//函數指針偏移到第3片內存,前幾片內存存儲的是got段相關信息,第3片內存開始存儲的數據才是庫函數的首地址數據 913 #else 914 p_got_lib_fun += 3; 915 #endif 916 917 //庫文件的 918 lib_sym = (struct elf_sym *)lib_info->first_lib_sym; 919 lib_fun_name = (char *)lib_info->elf_lib_fun_str; 920 921 //調試可執行程序用到的庫函數信息 922 #if OPEN_PRINT 923 //elf_info->section_dynsym.sh_size 是elf庫文件.dynsym段總大小,除以struct elf_sym大小,就是庫函數總數,一個函數信息用一個struct elf_sym結構表示 924 for(i = 0;i < elf_info->section_dynsym.sh_size/sizeof(struct elf_sym);i++) 925 { 926 //從用戶空間的got段內存復制庫函數的首地址到got_lib_fun_val,這個地址是重定向后的地址,真實的庫函數指令首地址 927 if(get_user(got_lib_fun_val,p_got_lib_fun)) 928 { 929 printk(KERN_ERR"%s get_user error 0x%p\n",__func__,p_got_lib_fun); 930 return -EFAULT; 931 } 932 user_stack_printk(KERN_DEBUG" %s got_lib_fun_val:0x%lx p_got_lib_fun:0x%p %s\n",__func__,got_lib_fun_val,p_got_lib_fun,&elf_lib_fun_name[elf_lib_sym->st_name]); 933 934 #ifdef CONFIG_MIPS 935 if((got_lib_fun_val > 0x70000000) && (STT_FUNC == ELF_ST_TYPE(elf_lib_sym->st_info))) 936 #elif defined CONFIG_ARM64 937 //加上STT_FUNC限制,必須是func類型,測試發現_ITM_deregisterTMCIoneTab函數干擾,但是他的屬性是NOTYPE,他也是.dynsym段的成員 938 if((got_lib_fun_val > 0x7000000000) && (STT_FUNC == ELF_ST_TYPE(elf_lib_sym->st_info))) 939 #else 940 #error "not support !!!!!" 941 #endif 942 { 943 user_stack_printk(KERN_DEBUG"!!!%s elf_info find %s got_lib_fun_val:0x%lx p_got_lib_fun:0x%p\n",__func__,&elf_lib_fun_name[elf_lib_sym->st_name],got_lib_fun_val,p_got_lib_fun); 944 //指向.plt.got區下一片內存地址,.plt.got區的內存地址,amr64從第四片內存開始,都是庫函數的運行地址,假設所有庫函數都運行過了。而可執行程序文件的.dynsym區除了庫函數,還有NOTIFY屬性的干擾。所以elf_lib_sym++每次都執行,p_got_lib_fun++只有是有效庫函數時才執行。 945 //p_got_lib_fun++;//指向下一個庫函數首指令地址所在內存 946 } 947 if(STT_FUNC == ELF_ST_TYPE(elf_lib_sym->st_info)) 948 p_got_lib_fun++;//指向下一個庫函數首指令地址所在內存 949 950 elf_lib_sym ++;//指向像一個庫函數 struct elf_sym 結構 951 } 952 953 elf_lib_sym = (struct elf_sym*)elf_info->first_lib_sym; 954 elf_lib_fun_name = (char *)elf_info->elf_lib_fun_str; 955 p_got_lib_fun = (unsigned long *)elf_info->got_addr;//這個是用戶態的地址,要用get_user復制數據 956 #ifdef CONFIG_MIPS 957 p_got_lib_fun += 2; 958 #else 959 p_got_lib_fun += 3; 960 #endif 961 #endif 962 963 //elf_info->section_dynsym.sh_size 是elf庫文件.dynsym段總大小,除以struct elf_sym大小,就是庫函數總數,一個函數信息用一個struct elf_sym結構表示 964 for(i = 0;i < elf_info->section_dynsym.sh_size/sizeof(struct elf_sym);i++) 965 { 966 //從用戶空間的got段內存復制庫函數的首地址到got_lib_fun_val,這個地址是重定向后的地址,真實的庫函數指令首地址 967 if(get_user(got_lib_fun_val,p_got_lib_fun)) 968 { 969 printk(KERN_ERR"%s get_user error 0x%p\n",__func__,p_got_lib_fun); 970 return -EFAULT; 971 } 972 973 #ifdef CONFIG_MIPS 974 if((got_lib_fun_val > 0x70000000) && (STT_FUNC == ELF_ST_TYPE(elf_lib_sym->st_info))) 975 #elif defined CONFIG_ARM64 976 //加上STT_FUNC限制,必須是func類型,測試發現_ITM_deregisterTMCIoneTab函數干擾,但是他的屬性是NOTYPE,他也是.dynsym段的成員 977 if((got_lib_fun_val > 0x7000000000) && (STT_FUNC == ELF_ST_TYPE(elf_lib_sym->st_info))) 978 #else 979 #error "not support !!!!!" 980 #endif 981 { 982 user_stack_printk(KERN_DEBUG"%s elf_info find %s got_lib_fun_val:0x%lx\n",__func__,&elf_lib_fun_name[elf_lib_sym->st_name],got_lib_fun_val); 983 //p_got_lib_fun++;//指向下一個庫函數首指令地址所在內存 984 break; 985 } 986 987 if(STT_FUNC == ELF_ST_TYPE(elf_lib_sym->st_info)) 988 p_got_lib_fun++;//指向下一個庫函數首指令地址所在內存 989 990 elf_lib_sym ++;//指向像一個庫函數struct elf_sym結構 991 } 992 993 //此時elf_lib_sym指向的可執行程序中的.dynsym段用到的庫函數的struct elf_sym結構,got_lib_fun_val是該庫函數的 994 //運行指令首地址,&elf_lib_fun_name[elf_lib_sym->st_name]就是該庫函名字符串 995 996 /*在庫文件中的.dynstr段和.dynsym段分析與 &elf_lib_fun_name[elf_lib_sym->st_name] 庫函數名字字符串一致的 997 庫函數,找到它的struct elf_sym *lib_sym結構,取出它的st_value就是庫函數的原始首地址,與got_lib_fun_val的 998 差值就是庫函數的運行首地址與原始首地址的偏差*/ 999 1000 for(i = 0;i < lib_info->section_dynsym.sh_size/sizeof(struct elf_sym);i++) 1001 { 1002 if(0 == strcmp(&elf_lib_fun_name[elf_lib_sym->st_name],&lib_fun_name[lib_sym->st_name])) 1003 { 1004 elf_info->elf_lib_fun_off = got_lib_fun_val - lib_sym->st_value; 1005 1006 #ifdef CONFIG_ARM64 1007 user_stack_printk(KERN_DEBUG"%s lib_info find %s st_value:0x%llx elf_lib_fun_off:0x%lx\n",__func__,&lib_fun_name[lib_sym->st_name],lib_sym->st_value,elf_info->elf_lib_fun_off); 1008 #else 1009 user_stack_printk(KERN_DEBUG"%s lib_info find %s st_value:0x%x elf_lib_fun_off:0x%lx\n",__func__,&lib_fun_name[lib_sym->st_name],lib_sym->st_value,elf_info->elf_lib_fun_off); 1010 #endif 1011 1012 ret =0; 1013 break; 1014 } 1015 1016 lib_sym++; 1017 } 1018 1019 if(0 != ret) 1020 user_stack_printk("%s cat not find match lib fun name from elf_lib_sym\n",__func__); 1021 return ret; 1022 } 1023 /** get_lib_fun_info - 根據addr計算出它所處於的庫函數的名字、函數運行首地址、函數運行結束地址 1024 * @sym_lib_info - 保存庫函數的名字、函數運行首地址、函數運行結束地址 1025 * @lib_info - 該結構體成員主要包含elf庫文件的 dynsym、dynstr section數據的首地址 1026 * @addr - 棧回溯過程的函數地址 1027 * @lib_fun_offset - 庫函數的運行首地址和結束地址之差 1028 * 1029 * returns: 1030 * 0: 根據addr計算出它所處於的庫函數 1031 * <0: 沒有找到addr所處的庫函數 1032 */ 1033 static int get_lib_fun_info(struct sym_fun_info * sym_lib_info,struct elf_file_info *lib_info,unsigned long addr,unsigned long lib_fun_offset) 1034 { 1035 struct elf_sym *lib_sym; 1036 unsigned char *lib_fun_name; 1037 int i; 1038 int ret = -1; 1039 1040 lib_sym = (struct elf_sym*)lib_info->first_lib_sym; 1041 lib_fun_name = (char *)lib_info->elf_lib_fun_str; 1042 1043 //elf_info->section_dynsym.sh_size 是elf庫文件.dynsym段總大小,除以struct elf_sym大小,就是庫函數總數,一個函數信息用一個struct elf_sym結構表示 1044 for(i = 0;i < lib_info->section_dynsym.sh_size/sizeof(struct elf_sym);i++) 1045 { 1046 //lib_sym->st_value 是lib庫文件中每個庫函數的默認函數首地址,lib_sym->st_value + lib_fun_offset 是庫函數重定向后的函數首地址 1047 if((addr >= lib_sym->st_value + lib_fun_offset) && (addr < lib_sym->st_value + lib_fun_offset + lib_sym->st_size)) 1048 { 1049 //lib_fun_name 是庫函數名字字符串集合首地址,elf_lib_sym->st_name是當前函數名字字符串在lib_fun_name數組的索引 1050 strncpy(sym_lib_info->name,&lib_fun_name[lib_sym->st_name],FUN_NAME_LEN); 1051 sym_lib_info->fun_first_instruct_addr = lib_sym->st_value + lib_fun_offset;//庫函數指令首地址 1052 sym_lib_info->fun_end_instruct_addr = lib_sym->st_value + lib_fun_offset + lib_sym->st_size;//庫函數指令結束地址 1053 memcpy(&user_stack_unwind_info.sym_info,sym_lib_info,sizeof(struct sym_fun_info)); 1054 1055 #ifdef CONFIG_ARM64 1056 user_stack_printk(KERN_DEBUG"%s find %s first_fun_addr:0x%lx size:0x%llx st_value:0x%llx\n",__func__,sym_lib_info->name,sym_lib_info->fun_first_instruct_addr,lib_sym->st_size,lib_sym->st_value); 1057 #else 1058 user_stack_printk(KERN_DEBUG"%s find %s first_fun_addr:0x%lx size:0x%x st_value:0x%x\n",__func__,sym_lib_info->name,sym_lib_info->fun_first_instruct_addr,lib_sym->st_size,lib_sym->st_value); 1059 #endif 1060 /*測試證實,double free棧回溯時,第一級函數是gsignal或者raise,這兩個函數的st_value和st_size完全一樣,就是兩個不同的函數 1061 名字,但是對應同一個函數。但是gsignal會先搜索到,gdb此時棧回溯時打印的是raise函數,所以這里就不直接return 0,而是一直搜索, 1062 使用最后找到的庫函數*/ 1063 ret = 0; 1064 return 0; 1065 } 1066 1067 lib_sym ++;//指向下一個庫函數struct elf_sym結構 1068 } 1069 return ret; 1070 } 1071 /** get_elf_fun_info - 根據addr計算出它所處於的可執行程序中的函數名字、函數運行首地址、函數運行結束地址 1072 * @sym_lib_info - 保存函數的名字、函數運行首地址、函數運行結束地址 1073 * @lib_info - 該結構體成員主要包含elf可執行程序文件的 dynsym、dynstr section數據的首地址 1074 * @addr - 棧回溯過程的函數地址 1075 * 1076 * returns: 1077 * 0: 根據addr計算出它所處於的函數 1078 * <0: 沒有找到addr所處的函數 1079 */ 1080 static int get_elf_fun_info(struct sym_fun_info * elf_sym_info,struct elf_file_info *elf_info,unsigned long addr) 1081 { 1082 struct elf_sym *elf_fun_sym; 1083 unsigned char *elf_fun_name; 1084 int i; 1085 int ret = -1; 1086 1087 elf_fun_sym = (struct elf_sym*)elf_info->first_elf_sym; 1088 elf_fun_name = (char *)elf_info->elf_fun_str; 1089 1090 //elf_info->section_dynsym.sh_size 是elf文件.dynsym段總大小,除以struct elf_sym大小,就是函數總數,一個函數信息用一個struct elf_sym結構表示 1091 for(i = 0;i < elf_info->section_symtab.sh_size/sizeof(struct elf_sym);i++) 1092 { 1093 //elf_fun_sym->st_value 是可執行程序文件中每個函數的函數首地址 1094 if((addr >= elf_fun_sym->st_value) && (addr < elf_fun_sym->st_value + elf_fun_sym->st_size)) 1095 { 1096 //elf_fun_name 是函數名字字符串集合首地址,elf_lib_sym->st_name是當前函數名字字符串在lib_fun_name數組的索引 1097 strncpy(elf_sym_info->name,&elf_fun_name[elf_fun_sym->st_name],FUN_NAME_LEN); 1098 elf_sym_info->fun_first_instruct_addr = elf_fun_sym->st_value;//函數指令首地址 1099 elf_sym_info->fun_end_instruct_addr = elf_fun_sym->st_value + elf_fun_sym->st_size;//函數指令結束地址 1100 memcpy(&user_stack_unwind_info.sym_info,elf_sym_info,sizeof(struct sym_fun_info)); 1101 1102 #ifdef CONFIG_ARM64 1103 user_stack_printk(KERN_DEBUG"%s find %s first_fun_addr:0x%lx size:0x%llx st_value:0x%llx\n",__func__,elf_sym_info->name,elf_sym_info->fun_first_instruct_addr,elf_fun_sym->st_size,elf_fun_sym->st_value); 1104 #else 1105 user_stack_printk(KERN_DEBUG"%s find %s first_fun_addr:0x%lx size:0x%x st_value:0x%x\n",__func__,elf_sym_info->name,elf_sym_info->fun_first_instruct_addr,elf_fun_sym->st_size,elf_fun_sym->st_value); 1106 #endif 1107 ret = 0; 1108 return 0; 1109 } 1110 1111 elf_fun_sym ++;//指向下一個函數struct elf_sym結構 1112 } 1113 return ret; 1114 } 1115 /** get_elflib_path_file_name - 根據傳入的addr這個某個庫函數指令地址計算出屬於哪個庫文件 1116 * @task - 當前棧回溯進程 1117 * @addr - 與棧回溯有關的某個庫函數指令地址 1118 * 1119 * returns: 1120 * NULL:沒有找到與addr構成文件映射的庫文件 1121 * 其他:與addr所在內存構成文件映射的庫文件名字字符串 1122 */ 1123 static char *get_elflib_path_file_name(struct task_struct *task,unsigned long addr) 1124 { 1125 struct vm_area_struct *vma; 1126 char buf[50]; 1127 char *filename; 1128 //基本原理是,根據傳入的addr在進程vma鏈表里搜索,找到地址符合的vma 1129 vma = find_vma(task->mm,addr); 1130 if(NULL == vma) 1131 { 1132 printk(KERN_ERR"cat not find valid elf_lib file at addr:0x%lx\n",addr); 1133 return NULL; 1134 } 1135 if(vma && vma->vm_file) 1136 { 1137 filename = d_path(&vma->vm_file->f_path,buf, sizeof(buf)); 1138 printk("elflib file path: %s \n",filename); 1139 return filename; 1140 } 1141 return NULL; 1142 } 1143 /** get_elf_path_file - 得到異常可執行程序的文件名字 1144 * @task - 棧回溯進程的task結構 1145 * @text_start - 可執行程序代碼段首地址 1146 * @text_end - 可執行程序代碼段結束地址 1147 * 1148 * returns: 1149 * NULL:沒有找到可執行程序文件 1150 * 其他:可執行程序的文件名稱 1151 */ 1152 static char *get_elf_path_file(struct task_struct *task,unsigned long *text_start,unsigned long *text_end) 1153 { 1154 struct vm_area_struct *vma; 1155 struct mm_struct *mm; 1156 char buf[50]; 1157 char *filename; 1158 1159 mm = get_task_mm(task); 1160 if(!mm) 1161 return NULL; 1162 1163 //進程的用戶空間vma鏈表掛在mm->mmap起始的vma里,第一個vma應該是進程elf文件路徑 1164 vma = mm->mmap; 1165 if(vma && vma->vm_file) 1166 { 1167 filename = d_path(&vma->vm_file->f_path,buf, sizeof(buf)); 1168 printk("elf file path: %s \n",filename); 1169 //可執行程序的代碼段起始地址和結束地址,這個vma是可執行程序應用空間的第一個vma,第一個vma就是text段 1170 *text_start = vma->vm_start; 1171 *text_end = vma->vm_end; 1172 return filename; 1173 } 1174 return NULL; 1175 } 1176 /** get_file_size - 內核態得到文件大小 1177 * @file - 文件的struct file結構 1178 * 1179 * returns: 1180 * -1:獲取文件大小失敗 1181 * 其他:文件大小 1182 */ 1183 static long get_file_size(struct file *file) 1184 { 1185 struct kstat st; 1186 if (vfs_getattr(&file->f_path, &st)) 1187 return -1; 1188 if (!S_ISREG(st.mode)) 1189 return -1; 1190 if (st.size != (long)st.size) 1191 return -1; 1192 return st.size; 1193 } 1194 /** user_stack_backstrace - 內核對異常應用棧回溯的入口函數 1195 * @regs - 棧回溯進程當時的struct pt_regs結構 1196 * @task - 棧回溯進程的結構 1197 * 1198 * returns: 1199 * 0:棧回溯過程沒有報錯 1200 * <0:棧回溯過程發生報錯 1201 */ 1202 int user_stack_backstrace(struct pt_regs *regs,struct task_struct *task) 1203 { 1204 char elf_path_name[100],lib_path_name[100]; 1205 int retval = 0; 1206 unsigned long text_start,text_end; 1207 unsigned long addr; 1208 mm_segment_t oldfs; 1209 struct file *elf_file = NULL; 1210 struct file *lib_file = NULL; 1211 1212 printk(KERN_ALERT"user thread:%s pid:%d stack strace\n",current->comm,current->pid); 1213 1214 //mutex_init(&user_stack_unwind_info.stack_backstrace_lock); 1215 1216 strncpy(elf_path_name,get_elf_path_file(current,&text_start,&text_end),sizeof(elf_path_name)); 1217 if(elf_path_name[0] == '\0') 1218 { 1219 printk(KERN_ERR"cat not find elf path file\n"); 1220 retval = -ENOEXEC; 1221 goto err; 1222 } 1223 1224 memset(&user_stack_unwind_info,0,sizeof(struct user_stack_unwind)); 1225 memset(&elf_info,0,sizeof(struct elf_file_info)); 1226 memset(&lib_info,0,sizeof(struct elf_file_info)); 1227 elf_info.elf_strip = 1;//初值先默認strip過,如果read_elf_section_info發現有strtab段再清0 1228 1229 user_stack_unwind_info.elf_text_start = text_start; 1230 user_stack_unwind_info.elf_text_end = text_end; 1231 user_stack_unwind_info.thread_stack_start = task->mm->start_stack; 1232 user_stack_unwind_info.thread = task; 1233 1234 oldfs = get_fs(); 1235 set_fs(KERNEL_DS); 1236 1237 elf_file = open_exec(elf_path_name); 1238 retval = PTR_ERR(elf_file); 1239 if (IS_ERR(elf_file)) 1240 { 1241 printk(KERN_ERR"open elf file:%s fail\n",elf_path_name); 1242 retval = -ENOEXEC; 1243 goto err; 1244 } 1245 printk("%s size:%ld\n",elf_path_name,get_file_size(elf_file)); 1246 1247 #ifdef CONFIG_MIPS 1248 addr = regs->cp0_epc; 1249 #else 1250 addr = regs->pc; 1251 #endif 1252 1253 //崩潰地址在庫中 1254 if(addr > user_stack_unwind_info.elf_text_end) 1255 { 1256 strncpy(lib_path_name,get_elflib_path_file_name(user_stack_unwind_info.thread,addr),sizeof(lib_path_name)); 1257 lib_file = open_exec(lib_path_name); 1258 retval = PTR_ERR(lib_file); 1259 if (IS_ERR(lib_file)) 1260 { 1261 printk(KERN_ERR"open lib file:%s fail\n",lib_path_name); 1262 retval = -ENOEXEC; 1263 goto err; 1264 } 1265 //獲取動態庫的symtab、dynsym、dynstr、symstr、plt、got.plt等section的數據 1266 retval = read_elf_section_info(lib_file,&lib_info,0); 1267 if(retval) 1268 { 1269 goto err; 1270 } 1271 } 1272 1273 //獲取可執行程序的symtab、dynsym、dynstr、symstr、plt、got.plt等section的數據 1274 retval = read_elf_section_info(elf_file,&elf_info,1); 1275 if(retval) 1276 { 1277 goto err; 1278 } 1279 1280 set_fs(oldfs); 1281 1282 show_user_backtrace(current,regs); 1283 1284 retval = 0; 1285 1286 err: 1287 1288 if(elf_info.first_lib_sym) 1289 kfree(elf_info.first_lib_sym); 1290 if(elf_info.elf_lib_fun_str) 1291 kfree(elf_info.elf_lib_fun_str); 1292 if(elf_info.first_elf_sym) 1293 kfree(elf_info.first_elf_sym); 1294 if(elf_info.elf_fun_str) 1295 kfree(elf_info.elf_fun_str); 1296 1297 if(lib_info.first_lib_sym) 1298 kfree(lib_info.first_lib_sym); 1299 if(lib_info.elf_lib_fun_str) 1300 kfree(lib_info.elf_lib_fun_str); 1301 1302 if (elf_file) 1303 fput(elf_file); 1304 if (lib_file) 1305 fput(lib_file); 1306 return retval; 1307 } 1308 EXPORT_SYMBOL(user_stack_backstrace);
使用方法:
#內核對異常應用棧回溯基本方法
1 將 user_stack_unwind.c 編譯進內核
2 內核對應用段錯誤棧回溯實現方法:arm64架構,在 do_page_fault->__do_user_fault 函數中,添加 user_stack_backstrace(regs,current)。mips架構,do_page_fault 函數,if (user_mode(regs))分支里添加,user_stack_backstrace(regs,current)。
3 內核對應用double free 問題棧回溯實現方法: do_send_specific() 函數最后添加,if(SIGABRT == sig) user_stack_backstrace(task_pt_regs(current),current)。arm64架構對double free問題能完整棧回溯,mips架構由於棧回溯原理問題,棧回溯過程會出錯返回。
4 其他應用,內核對應用程序所有進程/線程棧回溯,對調試偶然出現的應用程序鎖死,實時觀察應用程序應用層函數調用流程,有比較大的用處。這個功能目前在開發中。