应用层打印PC指针LR指针解决段错误总结

1.需求的产生

写程序难免会出现段错误的情况,这时候很想知道,到底在什么地方崩溃了,对于代码很少,或者你很有把握的时候,或许用二分法配合printf就可以搞定了;而对于非常复杂的代码,比如像Xserver这样的程序,可能就不太好定位了;
(本文讨论的情况都是针对arm环境,并且gdb不方便使用的情况)

2. 解决思路

思路其实很简单,对于用户态段错误的原因,大约可以分为两种,

a) 没有权限访问这个地址;
b) 访问的地址没有映射,比如NULL地址;
当出现这两种情况的时候,linux内核都会向用户态的程序发送SIGSEGV的信号,于是程序执行默认的信号处理函数,就退出了;
所以有两个解决办法:
A) 在内核里面把这些寄存器打印出来;
B) 在上层程序里面把寄存器打印出来;
下面来分别说明:

3. 内核信息打印

内核的执行路径如下:
在这里插入图片描述
我们只需要在__do_user_fault的时候把打印信息打开就可以了,如下:

#ifdef CONFIG_DEBUG_USER
       if (user_debug & UDBG_SEGV) {
              printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n",
                    tsk->comm, sig, addr, fsr);
              show_pte(tsk->mm, addr);
              show_regs(regs);
       }
#endif
改成
printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n",
                     tsk->comm, sig, addr, fsr);
              show_pte(tsk->mm, addr);
              show_regs(regs);

里面会打印出pc寄存器的值,有了这个就可以定位了。

4. 用户态信息打印

这个做法的主要思路就是先拦截SIGSEGV信号,然后在信号处理函数里面打印信息,信号拦截代码如下:

static void  catch_sigsegv()
{
       struct sigaction action;
       memset(&action, 0, sizeof(action));
       action.sa_sigaction = sigsegv_handler;
       action.sa_flags = SA_SIGINFO;
       if(sigaction(SIGSEGV, &action, NULL) < 0)
       {
              perror("sigaction");
		}
}

只需要在main函数里面加入这个函数就可以了:

main(…)
{
	….
	catch_sigsegv();
	…
}

下面来看看这个处理函数sigsegv_handler是怎么写的,代码如下:

static void sigsegv_handler(int signum, siginfo_t* info, void*ptr)
{
       static const char *si_codes[3] = {"", "SEGV_MAPERR", "SEGV_ACCERR"};
        int i;
        ucontext_t *ucontext = (ucontext_t*)ptr;
        void *bt[100];
        char **strings;

        printf("Segmentation Fault Trace:\n");
        printf("info.si_signo = %d\n", signum);
        printf("info.si_errno = %d\n", info->si_errno);
        printf("info.si_code  = %d (%s)\n", info->si_code, si_codes[info->si_code]);
        printf("info.si_addr  = %p\n", info->si_addr);
        
        /*for arm*/
        printf("the arm_fp 0x%3x\n",ucontext->uc_mcontext.arm_fp);
        printf("the arm_ip 0x%3x\n",ucontext->uc_mcontext.arm_ip);
        printf("the arm_sp 0x%3x\n",ucontext->uc_mcontext.arm_sp);
        printf("the arm_lr 0x%3x\n",ucontext->uc_mcontext.arm_lr);
        printf("the arm_pc 0x%3x\n",ucontext->uc_mcontext.arm_pc);
        printf("the arm_cpsr 0x%3x\n",ucontext->uc_mcontext.arm_cpsr);
        printf("the falut_address 0x%3x\n",ucontext->uc_mcontext.fault_address);
        
		**/*backtrace函数有的系统不支持,如hisi平台/FH平台等不支持*/**
        printf("Stack trace (non-dedicated):");
        int sz = backtrace(bt, 20);
        printf("the stack trace is %d\n",sz);
        strings = backtrace_symbols(bt, sz);
        for(i = 0; i < sz; ++i)
        {
                printf("%s\n", strings[i]);
        }
      _exit (-1);
}

测试代码如下:

void test_segv()
{
        char *i=0;
        *i=10;//产生段错误的位置
}
void cause_segv()
{
        printf("this is the cause_segv\n");
        test_segv();//调用函数
}
int main(int argc,char **argv)
{
        catch_sigsegv();//初始化注册捕捉函数
        cause_segv();
        return 0;
}

编译方法:
gcc segment_trace.c -g –rdynamic –o segment_trace
执行:
./segment_trace
输出如下:
this is the catch_sigsegv
Segmentation Fault Trace:
info.si_signo = 11
info.si_errno = 0
info.si_code  = 1 (SEGV_MAPERR)
info.si_addr  = (nil)
the arm_fp 0xb7f8a3d4
the arm_ip 0xb7f8a3d8
the arm_sp 0xb7f8a3c0
the arm_lr 0x8998
the arm_pc 0x8974
the arm_cpsr 0x60000010
the falut_address 0x  0
Stack trace (non-dedicated):the stack trace is 5
./segment_trace(backtrace_symbols+0x1c8) [0x8844]
/lib/libc.so.6(__default_rt_sa_restorer+0) [0xb5e22230]
./segment_trace(cause_segv+0x18) [0x8998]
./segment_trace(main+0x20) [0x89c0]
/lib/libc.so.6(__libc_start_main+0x108) [0xb5e0c10c]

5. 输出信息分析

根据上面的输出可以看出一些端倪:

根据栈信息,可以看出是在cause_segv里面出了问题,但是最后一层栈信息是看不到的,另外需要根据pc寄存器的值来定位:
addr2line -f -e segment_trace 0x8974
test_segv
/home/wf/test/segment_trace.c:55
可以看到说是在55行,一看:
刚好是
*i=10;
这一行,
而且可以看出,函数名是test_segv,
所以基本上不需要打印栈信息,也可以定位了。

6. 注意

这个方法最好不要用在多线程环境里面;
如果打印不出栈信息,需要在内核中去掉:
-fomit-frame-pointer编译选项;

<think>好的,用户现在想了解“指针指针如何设置”。之前他们的问题是关于使用putimage时指针访问冲突的问题,现在转向指针指针,说明他们可能在使用多层指针时遇到了问题,或者想更深入理解指针的概念。 首先,我需要确认用户的基础知识。指针指针是C/C++中比较高级的概念,常见于动态内存分配、函数参数传递等场景。用户之前的问题涉及结构体指针,现在可能是在代码中遇到了需要二级指针的情况,比如动态创建二维数组,或者在函数中修改指针的值。 接下来,我需要考虑如何清晰地解释指针指针。可能需要从基本概念入手,逐步深入,用简单的例子说明其用途和操作方法。同时,用户之前的问题中出现过访问冲突,所以在讲解时应该强调内存管理和错误预防,比如避免空指针和内存泄漏。 用户可能需要具体的代码示例,所以我会准备一些典型的用例,比如动态二维数组、函数内修改一级指针等。此外,还要指出常见错误,比如未初始化指针指针,或者错误地解引用,帮助用户避免类似问题。 另外,用户可能对指针指针在函数参数传递中的应用不太清楚,比如为什么有时候需要传递指针指针。这部分需要详细解释,通过例子说明传值和传址的区别,特别是在需要修改指针本身的时候,必须使用指针指针或者指针的引用。 最后,总结使用场景和注意事项,帮助用户理解何时以及如何使用指针指针,同时提醒他们注意内存管理和错误检查,防止出现之前遇到的访问冲突问题。</think>在C/C++中,**指针指针**(即二级指针,`int**`、`char***`等)用于处理需要间接访问或动态管理内存层级关系的场景。以下是系统化的解析与实操指南: --- ### **一、核心概念** #### 1. 定义与内存模型 - **本质**:指针指针是存储另一个指针地址的变量 - **内存层级**: ```mermaid graph LR A[二级指针pp] --> B[一级指针p] B --> C[实际数据n] ``` - **数学表示**:若`int n=5`, `int *p=&n`, `int **pp=&p`,则: $$ pp \rightarrow p \rightarrow n $$ #### 2. 声明语法 ```c int** pp = NULL; // 二级指针初始化 char*** ppp; // 三级指针(罕见但合法) ``` --- ### **二、典型应用场景** #### 1. 动态二维数组 ```c int rows = 3, cols = 4; int** matrix = (int**)malloc(rows * sizeof(int*)); // 分配行指针数组 for(int i=0; i<rows; i++) { matrix[i] = (int*)malloc(cols * sizeof(int)); // 为每行分配列空间 } // 使用:matrix[row][col] = value; ``` #### 2. 函数内修改一级指针 ```c void initPointer(int** pp) { *pp = (int*)malloc(sizeof(int)); // 修改外部指针的指向 **pp = 100; // 通过二级指针操作数据 } int main() { int* p = NULL; initPointer(&p); // 传递指针的地址 printf("%d", *p); // 输出100 free(p); } ``` #### 3. 字符串数组管理 ```c char** strArray = (char**)malloc(5 * sizeof(char*)); for(int i=0; i<5; i++) { strArray[i] = (char*)malloc(20 * sizeof(char)); sprintf(strArray[i], "String%d", i+1); } ``` --- ### **三、操作指针指针的关键步骤** #### 步骤1:内存分配 ```c int** pp = (int**)malloc(sizeof(int*)); // 分配一个二级指针空间 *pp = (int*)malloc(sizeof(int)); // 为一级指针分配空间 **pp = 42; // 写入数据 ``` #### 步骤2:层级访问 ```c int n = 10; int* p = &n; int** pp = &p; // 等效访问路径 printf("%d", n); // 直接访问 → 10 printf("%d", *p); // 一级解引用 → 10 printf("%d", **pp); // 二级解引用 → 10 ``` #### 步骤3:安全释放 ```c // 释放二维数组示例 for(int i=0; i<rows; i++) { free(matrix[i]); // 先释放每行的列空间 } free(matrix); // 再释放行指针数组 matrix = NULL; // 避免悬空指针 ``` --- ### **四、常见错误与调试** #### 1. 未初始化直接使用 ```c int** pp; // 未初始化 **pp = 5; // 崩溃:访问随机地址 ``` #### 2. 错误解引用层级 ```c int** pp = (int**)malloc(sizeof(int*)); *pp = 5; // 错误:将整数赋给指针(应*pp = (int*)malloc(...)) ``` #### 3. 内存泄漏 ```c int** pp = (int**)malloc(sizeof(int*)); // 忘记释放*pp和pp ``` #### 调试技巧: - 使用`printf`打印各级地址: ```c printf("pp地址: %p\n", (void*)pp); printf("*pp值(一级指针地址): %p\n", (void*)*pp); printf("**pp值(实际数据): %d\n", **pp); ``` - Valgrind检测工具:`valgrind --leak-check=full ./your_program` --- ### **五、高级应用:三级指针示例** ```c // 动态三维数组 int x=2, y=3, z=4; int*** cube = (int***)malloc(x * sizeof(int**)); for(int i=0; i<x; i++) { cube[i] = (int**)malloc(y * sizeof(int*)); for(int j=0; j<y; j++) { cube[i][j] = (int*)malloc(z * sizeof(int)); } } // 访问:cube[i][j][k] ``` --- ### **六、性能与设计建议** 1. **必要性评估**: - 优先使用`std::vector`(C++) - 避免超过二级指针的嵌套(可读性差) 2. **替代方案**: ```cpp // C++中使用智能指针管理 std::unique_ptr<std::unique_ptr<int[]>[]> matrix( new std::unique_ptr<int[]>[rows] ); ``` 3. **防御性编程**: ```c // 安全访问模板 if(pp != NULL && *pp != NULL) { **pp = value; // 确保两级指针有效 } ``` 通过理解这些原理并配合实际调试,可有效掌握指针指针这一关键编程技术。重点注意**内存层级关系**和**生命周期管理**,这是避免访问冲突的核心。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值