內核棧回溯原理學習應用


問題:

    一台客戶現場機器,運行一周左右偶然發生一次應用段錯誤或者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庫中棧回溯了。

具體實現方法:

  1. 以memcpy中崩潰為例, 從libc.so文件的” .dynsym” section找到memcpy庫函數的struct elf64_sym結構,該結構的成員st_value就是memcpy庫函數的原始首地址
  2. 從可執行程序”.got.plt” section找到庫函數memcpy的運行首地址,memcpy的運行首地址減去其原始首地址就是庫函數的原始首地址與運行首地址之差,命名為dx
  3. 從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",&section_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",&section_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",&section_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",&section_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",&section_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",&section_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",&section_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",&section_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",&section_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",&section_name[p_section->sh_name]) == 0)
 814         {
 815             #ifdef CONFIG_ARM64
 816                 user_stack_printk("%s find  sh_addr:0x%llx\n",&section_name[p_section->sh_name],p_section->sh_addr);
 817             #else
 818                 user_stack_printk("%s find  sh_addr:0x%x\n",&section_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",&section_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",&section_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",&section_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",&section_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",&section_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",&section_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);
View Code

使用方法:

#內核對異常應用棧回溯基本方法

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 其他應用,內核對應用程序所有進程/線程棧回溯,對調試偶然出現的應用程序鎖死,實時觀察應用程序應用層函數調用流程,有比較大的用處。這個功能目前在開發中。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM