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