第二十三章 Exception Architecture

本文探讨了Java中的异常处理机制,区分了运行时异常和检查异常的特点与应用场合,并提供了实例代码进行说明。

第二十三章 Exception Architecture

我们看一下应用程序开发中两类比较常见的异常情况,第一类是诸如数据库无法链接,配置文件不存在,SQL语句编译错误等的系统级别的异常,一旦这种异常发生基本上系统无法正常运行。另一类是诸如数据库中的内容不存在,格式不正确,业务逻辑不满足要求等的异常,这种异常往往只影响某一条的数据,不会对系统的正常运行有影响。

Java中的两类异常

Java中的异常分为两种类型运行时的异常(RuntimeException)和检查异常(Checked Exception)。

 

运行时的异常(RuntimeException)可以在程序运行的时候动态的抛出,但是不需要在方法接口中声明要跑出的运行时异常,也不需要在方法调用的时候做try-catch处理。系统的运行时异常是指java.lang.RuntimeExceptioin或者他的子类型。RuntimeException在逻辑上的含义是:

 

检查异常(Checked Exception)必须要在方法的接口中声明,并且在使用方法的时候,其抛出的Checked Exception必须使用try-catch处理,检查异常指的是java.lang.Exception或者他的子类型。

 

注意:有一点需要指出,java.lang.RuntimeException是java.lang.Exception的子类型。所以上述中说的检查异常(Checked Exception)指的是java.lang.Exception和其他的非java.lang.RuntimeException子类的子类型。

 

如下代码展示了RuntimeException的使用:

 

package com.jpleasure.exception;

/**
 * System Exception
 * @author mazhao@dl.cn
 *
 */
public class SystemException extends RuntimeException {

 public SystemException() {
 }

 public SystemException(String message) {
  super(message);
 }

 public SystemException(Throwable cause) {
  super(cause);
 }

 public SystemException(String message, Throwable cause) {
  super(message, cause);
 }
}

以下代码可以编译通过,运行的时候抛出异常:

package com.jpleasure.exception;

public class Main {
 public static void main(String ... args ) {
  testRuntimeException();
  
 }
 
 public static void testRuntimeException() {
  
  methodThrowsRuntimeException();
  
 }
 
 private static void methodThrowsRuntimeException() {
  if(true) {
   throw new SystemException("Hello World!");
  }
 }
}

运行的时候输出内容为:

 Exception in thread "main" com.jpleasure.exception.SystemException: Hello World!
 at com.jpleasure.exception.Main.methodThrowsRuntimeException(Main.java:17)
 at com.jpleasure.exception.Main.testRuntimeException(Main.java:11)
 at com.jpleasure.exception.Main.main(Main.java:5)

以下代码展示了Exception的使用:

 package com.jpleasure.exception;

/**
 * Application Exception
 * @author mazhao@dl.cn
 *
 */
public class ApplicationException extends Exception {

 public ApplicationException() {
  // TODO Auto-generated constructor stub
 }

 public ApplicationException(String message) {
  super(message);
  // TODO Auto-generated constructor stub
 }

 public ApplicationException(Throwable cause) {
  super(cause);
  // TODO Auto-generated constructor stub
 }

 public ApplicationException(String message, Throwable cause) {
  super(message, cause);
  // TODO Auto-generated constructor stub
 }

}

异常使用代码:

package com.jpleasure.exception;

public class Main2 {

 /**
  * @param args
  */
 public static void main(String[] args) {
  

 }
 
 public static void testCheckedException() {
  
  try {
   methodThrowsCheckedException();
  } catch (ApplicationException e) {
   // 异常处理代码
  }
  
 }
 
 private static void methodThrowsCheckedException() throws ApplicationException {
  if(true) {
   throw new ApplicationException("Hello World!");
  }
 }

}

由于ApplicationException是Checked Exception,所以在方法的接口中必须加以声明,并且在使用的时候必须包括在try-catch代码块中。

应用程序开发中的异常

如上述代码中说明的那样处于使用方便我们总是将系统异常设置为RuntimeException的子类,在统一的地方处理。

 

为什么要将系统异常设置为Runtime的呢?

 

try-catch块主要是为了在异常发生的时候有可以挽救的方式,所以会在try部分异常发生的时候进入catch部分,但是系统异常(例如数据库无法链接,配置文件不存在等)一旦发生基本上没有挽救的办法,所以将系统异常声明为RuntimeException的子类。

另外,系统异常是一种很少会发生的异常,所以在开发中基本不需要考虑,所以将系统异常生命为RuntimeException的子类,这样在开发的时候基本不需要考虑系统异常发生的情况,只需要统一的做错误处理即可。

 

 

应用程序异常通常设置为Checked Exception。需要在使用的地方逐个处理。

 

 

异常消息定义

异常消息通常不会在构造异常的时候传入,通常情况下在构造异常的时候传入的是异常消息的编号。

 

编号一般包含的内容有:系统ID,子系统ID,消息类型,流水号等。系统ID,子系统ID,流水号等都比较好理解,那么消息类型是什么呢?我们的消息常见的有以下一些类型:提示性的,警告性的,错误性的。所以我们可以分别以C(Confirm),W(Warnning),E(Error)来区分消息的类型

 

消息一般存储在属性文件(Properties)中使用ResourceBundle类或者Properties类来获取。当使用到多国语言的错误消息的时候需要使用ResourceBundle类。

有些时候也可以存储在代码中,这个时候需要使用到ListResourceBundle类。

异常发生时的操作

第一就是要记录错误日志,需要将类,方法,操作数据,异常类型,异常内容的信息记录下来,便于系统维护人员分析问题数据。

第二及时要抛出错误信息,反馈给系统用户,让客户知道系统无法处理他的当前操作。如果有必要还需要提示客户可能的错误情况,希望用户改善。

 

如何获得异常的详细信息

从异常中可以获得详细的抛出信息,例如都那些代码涉及到这个异常,分别在那些类的那些方法中,代码行数是多少,代码文件名字是什么等。

例如类LoginLogic调用了LoginDao,而Main调用了LoginLogic,代码为:

 

package com.jpleasure.exception;

/**
 * Created by IntelliJ IDEA.
 * User: ma.zhao@dl.cn
 * Date: 2007-9-3
 * Time: 22:55:47
 * To change this template use File | Settings | File Templates.
 */
public class LoginDao {
    public void testLogin() throws SystemException {
        if(true) {
            throw new SystemException("System Exception Occurs!");
        }
    }
}

package com.jpleasure.exception;

import java.util.Random;

/**
 * Created by IntelliJ IDEA.
 * User: ma.zhao@dl.cn
 * Date: 2007-9-3
 * Time: 22:56:30
 * To change this template use File | Settings | File Templates.
 */
public class LoginLogic {
    private LoginDao dao = new LoginDao();

    public void testLogin() throws ApplicationException {

        Random randam = new Random(System.currentTimeMillis());
        int randamInt = randam.nextInt();

        if (randamInt % 2 == 0) {
            dao.testLogin();
        } else {
            throw new ApplicationException("Application Exception Occurs!");
        }
    }
}


 

异常处理代码:

 

package com.jpleasure.exception;

/**
 * Created by IntelliJ IDEA.
 * User: ma.zhao@dl.cn
 * Date: 2007-9-3
 * Time: 22:57:38
 * To change this template use File | Settings | File Templates.
 */
public class Main {
    public static void main(String[] args) {

        LoginLogic logic = new LoginLogic();

        try {
            logic.testLogin();
        } catch (ApplicationException e) {
            printException(e);
        } catch (SystemException e) {
            printException(e);
        }

    }

    private static void printException(Exception e) {
        String msg = e.getMessage();
        System.out.println("exception message:" + msg);
        StackTraceElement[] stes = e.getStackTrace();
        for (int i = 0; i < stes.length; i++) {
            printStackTraceElement(stes[i]);
        }
    }

    
    private static void printStackTraceElement(StackTraceElement ste) {
        String fileName = ste.getFileName();
        String className = ste.getClassName();
        String methodName = ste.getMethodName();
        int line = ste.getLineNumber();
        System.out.println("file:" + fileName + "/t class:" + className + "/t method:" + methodName + "/t line:" + line);
        System.out.println("");
    }
}

 

 

 
<think> 我们正在处理一个硬件异常错误,具体错误信息为: 'Undefined Exception happen.CP0_STATUS=10000003 CP0_CAUSE=8 CP0_EPC=83fb7fc4 CP0_BADVADDR=0' 这些寄存器是MIPS架构中的协处理器0(CP0)寄存器,用于处理异常。让我们逐个分析这些寄存器的值: 1. **CP0_STATUS (Status Register) = 0x10000003**: - 状态寄存器用于控制处理器的操作模式、中断使能等。 - 0x10000003 的二进制形式(32位)为:0001 0000 0000 0000 0000 0000 0000 0011 - 在MIPS中,Status寄存器各个位的含义: - Bit 0 (IE): 中断使能。1表示允许中断。 - Bit 1 (EXL): 异常级别。1表示处于异常处理中。 - Bit 2 (ERL): 错误级别。1表示处于错误状态(例如总线错误)。 - 另外,我们可以看到Bit 28 (CU0)被置位(从右向左第28位,实际上是第31-0位,所以第28位是第4位?注意:MIPS寄存器位编号是从0开始,最高位是31。这里0x10000003的最高非零位是第28位(1<<28))。 - 实际上,0x10000003 = 0x10000000 | 0x3,其中0x10000000是CU0位(协处理器0使能位)被置位,而0x3表示低两位为11(即EXL和ERL都未置位?不对,标准位定义:低8位中,第0位是IE,第1位是EXL,第2位是ERL)。实际上,0x3的二进制是11,所以第0位和第1位为1,即IE和EXL被置位。而0x10000000是第28位(CU0)被置位。 具体位解释: - IE (bit0): 1 (中断使能) - EXL (bit1): 1 (处理器处于异常级别,通常表示正在处理异常) - ERL (bit2): 0 - CU0 (bit28): 1 (允许使用协处理器0) 2. **CP0_CAUSE (Cause Register) = 8**: - 原因寄存器指示了当前异常的原因。 - 值8(十六进制0x8,二进制1000)表示异常代码(Cause寄存器的第2到6位)为1000(二进制)。 - 在MIPS中,异常代码(ExcCode)占据Cause寄存器的第2到6位(共5位)。将8右移2位(因为最低两位是其他标志)得到异常代码:8 >> 2 = 2(十进制),即二进制010。但实际上,我们直接看第2-6位:8的二进制是...001000,所以第2-6位(从0开始计数)是:第2位为1,第3位为0,第4位为0,第5位为0,第6位0(更高位)。实际上,MIPS中Cause寄存器的ExcCode位于第2-6位(bit2到bit6)。所以8(二进制...1000)的bit5:bit2是:0001(注意这里我们只看第2-6位,即5个位)。正确的做法是: 8(十进制) = 二进制:0000 0000 0000 0000 0000 0000 0000 1000 ExcCode位于bit2~bit6(共5位),即从bit2(第2位)开始,所以是第2位到第6位:00000(因为第2位是0?不对,第3位是0,第4位0,第5位0,第6位0?)。实际上,我们需要将8右移2位然后取低5位: 8 >> 2 = 2(即二进制10),然后取低5位就是00010,即十进制的2。 - 根据MIPS架构,ExcCode=2表示发生了“TLB缺失异常”(TLB Miss Exception),具体是取指令时的TLB缺失(Instruction TLB Miss)还是数据TLB缺失(Data TLB Miss)?在MIPS中,ExcCode=2表示TLBL(TLB Load)或TLBS(TLB Store),但这里我们还需要结合具体上下文。 但是,注意:在MIPS中,ExcCode=8(十进制8)对应的异常是什么?不对,我们刚才计算ExcCode为2(因为8>>2=2)。实际上,异常代码2对应的是TLBL(TLB load异常)或TLBS(TLB store异常)?实际上,MIPS中ExcCode的定义如下: 0: 中断(Interrupt) 1: TLB修改异常(TLB Modification) 2: TLB缺失(取指令)TLBL (Load) 或 TLB缺失(取数据)TLBS (Store) ? 实际上,ExcCode=2表示TLBL(TLB load)异常,ExcCode=3表示TLBS(TLB store)异常。 所以,这里ExcCode=2,表示TLB load异常(即取指令时发生TLB缺失)。 另外,Cause寄存器还有其他位,比如第31位(BD)表示异常是否发生在分支延迟槽中。这里值为8(0x8)没有设置BD位(BD位是第31位,所以值为0),表示异常发生在非分支延迟槽的指令中。 3. **CP0_EPC (Exception Program Counter) = 0x83fb7fc4**: - EPC寄存器存储了发生异常的指令的地址。也就是说,当异常发生时,处理器正在执行位于0x83fb7fc4处的指令。 - 这个地址通常是程序计数器(PC)的值,指向导致异常的指令。 4. **CP0_BADVADDR (Bad Virtual Address) = 0**: - 这个寄存器存储了导致异常的虚拟地址。对于某些异常(如TLB缺失、地址错误),BADVADDR会记录访问的无效地址。 - 这里值为0,表示程序试图访问虚拟地址0。在大多数系统中,虚拟地址0是未映射的(通常是NULL指针),访问它会导致异常。 综上,错误原因分析: - 程序试图访问虚拟地址0(NULL指针),导致TLB缺失异常(具体为TLB load异常,因为ExcCode=2)。 - 异常发生在指令地址0x83fb7fc4处。 因此,这个错误通常是由于程序试图解引用一个空指针(NULL pointer)导致的。 解决方案: 1. **定位问题代码**:通过EPC的值(0x83fb7fc4)定位到发生异常的指令。可以使用调试器(如GDB)加载程序,然后查看该地址对应的代码。如果没有调试信息,可以通过反汇编程序来查看。 2. **检查代码逻辑**:检查该指令附近的代码,找出为何会访问地址0。可能是由于指针未初始化、指针被释放后未置空或者函数返回了空指针等。 3. **使用内存调试工具**:如Valgrind(在Linux下)来检测内存访问错误(包括空指针解引用)。 4. **检查指针使用**:在代码中检查所有指针操作,确保在使用指针前进行了有效性检查(非空判断)。 5. **检查硬件抽象层(如果有)**:如果错误发生在嵌入式系统或操作系统的内核中,需要检查内存映射设置,确保地址0没有被映射到可访问的物理地址(通常不会映射,因为0地址是非法区域)。 由于错误信息中BADVADDR=0,所以最大的可能性是程序试图读取地址0(可能是读取一个指针所指向的值,而该指针为NULL)。 在开发过程中,避免此类错误的常见做法: - 初始化指针为NULL,并在使用前检查。 - 释放指针后将其置为NULL。 - 避免返回局部变量的地址(会导致悬垂指针)。 - 使用智能指针(在C++中)等机制管理指针生命周期。 注意:这个错误信息是硬件级别的,通常出现在嵌入式系统、操作系统内核或使用底层编程(如C/C++)的应用程序中。 根据引用[2],我们还注意到一个关于Py_Finalize的访问冲突问题,但那个问题与Python解释器有关,而当前错误是硬件异常(MIPS架构),两者似乎无关。因此,我们忽略引用[2]的内容。 引用[1][3][4]涉及TensorFlow和Python环境设置,与当前错误无关。因此,我们主要基于错误信息本身进行分析。 如果无法获取程序源代码,或者错误发生在第三方库中,可以尝试以下步骤: 1. 更新软件和驱动:确保使用的软件和库是最新版本。 2. 检查硬件:内存故障或硬件问题也可能导致此类错误,但可能性较小(因为BADVADDR=0,更可能是软件问题)。 3. 搜索错误代码:根据异常代码(TLB缺失,BADVADDR=0)和EPC地址,在相关开发社区或文档中搜索类似问题。 总结:这个错误是由于程序试图访问虚拟地址0(空指针)导致的TLB缺失异常。解决方案集中在代码调试和指针使用检查上。 </think> 根据提供的错误信息 `Undefined Exception happen.CP0_STATUS=10000003 CP0_CAUSE=8 CP0_EPC=83fb7fc4 CP0_BADVADDR=0`,这是典型的 **MIPS 架构处理器异常**,分析如下: ### 错误原因分析 1. **CP0_CAUSE=8** - 表示异常代码为 `8` ($1000_2$) - **ExcCode = 2** (二进制 `010`,位于 CAUSE 寄存器 bit2-6) - 对应 **TLB 缺失异常**(TLB Miss Exception),具体类型: - 若发生在 **指令取指阶段**:Instruction TLB Miss * 若发生在 **数据访问阶段**:Data TLB Load/Store Miss 2. **CP0_BADVADDR=0** - 关键线索:**访问了虚拟地址 0** ($0x00000000$) - 典型原因: - 解引用 **空指针**(NULL pointer dereference) - 未初始化的函数指针 - 内存越界访问到零地址区域 3. **CP0_STATUS=10000003** - 二进制 $00010000000000000000000000000011_2$ - 标志位含义: - Bit0(IE)=1:中断启用 - Bit1(EXL)=1:处理器处于异常状态 - Bit28(CU0)=1:协处理器0可用 4. **CP0_EPC=83fb7fc4** - 异常发生时的程序计数器值 - 指向触发异常的指令位置 ($0x83fb7fc4$) > 📌 **核心问题**:程序试图访问 **未映射的零地址空间**(通常是空指针解引用),触发 TLB 缺失硬件异常[^1]。 --- ### 解决方案 #### 第一步:定位问题代码 1. 通过 EPC 地址 $0x83fb7fc4$ 反汇编程序: ```bash mipsel-linux-gnu-objdump -d your_program | grep 83fb7fc4 ``` 2. 检查该地址附近的指令: - 是否包含 **加载/存储指令**(如 `lw`, `sw`, `lb`) - 检查指令操作的寄存器值是否为 0 #### 第二步:排查常见原因 ```c // 典型错误示例 void buggy_code() { int* ptr = NULL; *ptr = 42; // 触发异常:向0地址写入 void (*func)() = NULL; func(); // 触发异常:跳转到0地址 } ``` - ✅ **检查所有指针操作**: - 指针是否在解引用前被赋值为 NULL? - 动态内存分配是否失败(malloc/calloc 返回 NULL)? - ✅ **检查函数指针**: - 回调函数是否未正确初始化? - ✅ **检查数组越界**: - 是否访问了 `array[-1]` 或 `array[size]`? #### 第三步:调试工具辅助 1. **GDB 调试**: ```bash qemu-mipsel -g 1234 your_program & mipsel-linux-gnu-gdb your_program (gdb) target remote localhost:1234 (gdb) x/i 0x83fb7fc4 // 查看异常指令 ``` 2. **Valgrind 检测**(需交叉编译版本): ```bash valgrind --tool=memcheck ./your_program ``` #### 第四步:预防措施 ```c // 添加指针安全检查 #define SAFE_DEREF(ptr, default_val) \ ((ptr) ? *(ptr) : (default_val)) // 使用静态分析工具 // 编译时添加检测选项 gcc -fsanitize=address -fno-omit-frame-pointer ... ``` --- ### 相关问题 1. 如何解读 MIPS 架构下 CP0_CAUSE 寄存器的异常编码? 2. 在嵌入式系统中,如何配置 MMU/TLB 避免零地址访问异常? 3. 当程序出现随机性崩溃时,如何区分内存越界和空指针问题? > 引用说明:CP0 寄存器行为参考 MIPS® Architecture for Programmers Volume III[^1],调试方法参考 Linux 核心调试指南[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值