java trace stack_Java 异常的栈轨迹(Stack Trace)详解及实例代码

Java 异常的栈轨迹(Stack Trace)详解

捕获到异常时,往往需要进行一些处理。比较简单直接的方式就是打印异常栈轨迹Stack Trace。说起栈轨迹,可能很多人和我一样,第一反应就是printStackTrace()方法。其实除了这个方法,还有一些别的内容也是和栈轨迹有关的。

1.printStackTrace()

首先需要明确,这个方法并不是来自于Exception类。Exception类本身除了定义了几个构造器之外,所有的方法都是从其父类继承过来的。而和异常相关的方法都是从java.lang.Throwable类继承过来的。而printStackTrace()就是其中一个。

这个方法会将Throwable对象的栈轨迹信息打印到标准错误输出流上。输出的大体样子如下:

java.lang.NullPointerException

at MyClass.mash(MyClass.java:9)

at MyClass.crunch(MyClass.java:6)

at MyClass.main(MyClass.java:3)

输出的第一行是toString()方法的输出,后面几行的内容都是之前通过fillInStackTrace()方法保存的内容。关于这个方法,我们后面会讲。

下面看一个例子:

public class TestPrintStackTrace {

public static void f() throws Exception{

throw new Exception("出问题啦!");

}

public static void g() throws Exception{

f();

}

public static void main(String[] args) {

try {

g();

}catch(Exception e) {

e.printStackTrace();

}

}

}

这个例子的输出如下:

java.lang.Exception: 出问题啦!

at TestPrintStackTrace.f(TestPrintStackTrace.java:3)

at TestPrintStackTrace.g(TestPrintStackTrace.java:6)

at TestPrintStackTrace.main(TestPrintStackTrace.java:10)

在这个例子中,在方法f()中抛出异常,方法g()中调用方法f(),在main方法中捕获异常,并且打印栈轨迹信息。因此,输出依次展示了f—>g—>main的过程。

2.getStackTrace()方法

这个方法提供了对printStackTrace()方法所打印信息的编程访问。它会返回一个栈轨迹元素的数组。以上面的输出为例,输出的第2-4行每一行的内容对应一个栈轨迹元素。将这些栈轨迹元素保存在一个数组中。每个元素对应栈的一个栈帧。数组的第一个元素保存的是栈顶元素,也就是上面的f。最后一个元素保存的栈底元素。

下面是一个使用getStackTrace()访问这些轨迹栈元素并打印输出的例子:

public class TestPrintStackTrace {

public static void f() throws Exception{

throw new Exception("出问题啦!");

}

public static void g() throws Exception{

f();

}

public static void main(String[] args) {

try {

g();

}catch(Exception e) {

e.printStackTrace();

System.out.println("------------------------------");

for(StackTraceElement elem : e.getStackTrace()) {

System.out.println(elem);

}

}

}

}

这样的输出和printStackTrace()的输出基本上是一样的,如下:

java.lang.Exception: 出问题啦!

at TestPrintStackTrace.f(TestPrintStackTrace.java:3)

at TestPrintStackTrace.g(TestPrintStackTrace.java:6)

at TestPrintStackTrace.main(TestPrintStackTrace.java:10)

TestPrintStackTrace.f(TestPrintStackTrace.java:3)

TestPrintStackTrace.g(TestPrintStackTrace.java:6)

TestPrintStackTrace.main(TestPrintStackTrace.java:10)

3.fillInStackTrace()

我们在前面也提到了这个方法。要说清楚这个方法,首先要讲一下捕获异常之后重新抛出的问题。在catch代码块中捕获到异常,打印栈轨迹,又重新throw出去。在上一级的方法调用中,再捕获这个异常并且打印出栈轨迹信息。这两个栈轨迹信息会一样吗?我们看一下代码:

public class TestPrintStackTrace {

public static void f() throws Exception{

throw new Exception("出问题啦!");

}

public static void g() throws Exception{

try {

f();

}catch(Exception e) {

e.printStackTrace();

throw e;

}

}

public static void main(String[] args) {

try {

g();

}catch(Exception e) {

e.printStackTrace();

}

}

}

在main方法中捕获的异常,是在g()方法中抛出的,按理说这两个打印栈轨迹的信息应该不同,第二次打印的信息应该没有关于f的信息。但是事实上,两次打印栈轨迹信息是一样的。输出结果如下:

java.lang.Exception: 出问题啦!

at TestPrintStackTrace.f(TestPrintStackTrace.java:3)

at TestPrintStackTrace.g(TestPrintStackTrace.java:7)

at TestPrintStackTrace.main(TestPrintStackTrace.java:16)

java.lang.Exception: 出问题啦!

at TestPrintStackTrace.f(TestPrintStackTrace.java:3)

at TestPrintStackTrace.g(TestPrintStackTrace.java:7)

at TestPrintStackTrace.main(TestPrintStackTrace.java:16)

也就是说,捕获到异常又立即抛出,在上级方法调用中再次捕获这个异常,打印的栈轨迹信息是一样的。原因在于没有将当前线程当前状态下的轨迹栈的状态保存进Throwabe中。现在我们引入fillInStackTrace()方法。这个方法刚好做的就是这样的保存工作。我们看一下这个方法的原型:

public Throwable fillInStackTrace()

这个方法是有返回值的。返回的是保存了当前栈轨迹信息的Throwable对象。我们看看使用fillInStackTrace()方法处理后,打印的栈轨迹信息有什么不同,代码如下:

public class TestPrintStackTrace {

public static void f() throws Exception{

throw new Exception("出问题啦!");

}

public static void g() throws Exception{

try {

f();

}catch(Exception e) {

e.printStackTrace();

//不要忘了强制类型转换

throw (Exception)e.fillInStackTrace();

}

}

public static void main(String[] args) {

try {

g();

}catch(Exception e) {

e.printStackTrace();

}

}

}

输出如下:

java.lang.Exception: 出问题啦!

at TestPrintStackTrace.f(TestPrintStackTrace.java:3)

at TestPrintStackTrace.g(TestPrintStackTrace.java:7)

at TestPrintStackTrace.main(TestPrintStackTrace.java:17)

java.lang.Exception: 出问题啦!

at TestPrintStackTrace.g(TestPrintStackTrace.java:11)

at TestPrintStackTrace.main(TestPrintStackTrace.java:17)

我们看到,在main方法中打印栈轨迹已经没有了f相关的信息了。

以上就是关于Java栈轨迹的一些我之前没有掌握的内容,记下来备忘。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
chm格式,目录如下。 第1章 PE文件格式深入研究 1.1 PE文件格式格式纵览 1.1.1 区块(Section) 1.1.2 相对虚拟地址(Relative Virtual Addresses) 1.1.3 数据目录 1.1.4 输入函数(Importing Functions) 1.2 PE文件结构 1.2.1 The MS-DOS头部 1.2.2 IMAGE_NT_HEADERS头部 1.2.3 区块表(The Section Table) 1.2.4 各种块(Sections)的描述 1.2.5 输出表 1.2.6 输出转向(Export Forwarding) 1.2.7 输入表 1.2.8 绑定输入(Bound import) 1.2.9 延迟装入数据(Delayload Data) 1.2.10 资源 1.2.11 基址重定位(Base Relocations) 1.2.12 调试目录(DebugDirectory) 1.2.13 NET头部 1.2.14 TLS初始化 1.2.15 程序异常数据 第2章 PE分析工具编写 2.1 文件格式检查 2.2 FileHeader和OptionalHeader内容的读取 2.3 得到数据目录(Data Dircetory)信息 2.4 得到块表(SectionTable)信息 2.5 得到输出表(ExportTable)信息 2.6 得到输入表(ImportTable)信息 第3章 Win32 调试API 3.1 Win32调试API原理 3.1.1 调试相关函数简要说明 3.1.2 调试事件 3.1.3 如何在调试时创建并跟踪一个进程 3.1.4 最主要的循环体 3.1.5 如何处理调试事件 3.1.6 线程环境详解 3.1.7 如何在另一个进程中注入代码 3.2 利用调试API编写脱壳机 3.2.1 tElock 0.98脱壳简介 3.2.2 脱壳机的编写 3.3 利用调试API制作内存补丁 3.3.1 跨进程内存存取机制 3.3.2 Debug API机制 第4章 Windows下的异常处理 4.1 基本概念 4.1.1 Windows下的软件异常 4.1.2 未公开的可靠吗 4.2 结构化异常处理(SEH) 4.2.1 异常处理的基本过程 4.2.2 SEH的分类 4.2.3 相关API 4.2.4 SEH相关数据结构 4.3 异常处理程序设计 4.3.1 顶层(top-level)异常处理 4.3.2 线程异常处理 4.3.3 异常处理的堆展开(Stack unwind) 4.3.4 异常处理程序设计中的几个注意事项: 4.4 SEH的简单应用 4.4.1 Win9x下利用SEH进ring0 4.4.2 利用SEH实现对自身的单步自跟踪 4.4.3 其它应用 4.5 系统背后的秘密 4.6 VC是如何封装系统提供的SEH机制的 4.6.1 扩展的EXCEPTION_REGISTRATION级相关结构 4.6.2 数据结构组织 4.7 Windows XP下的向量化异常处理(VEH) 第5章 软件加密技术 5.1 反调试技术(Anti-Debug) 5.1.1 句柄检测 5.1.2 SoftICE后门指令 5.1.3 int68子类型 5.1.4 ICECream子类型 5.1.5 判断NTICE服务是否运行 5.1.6 INT 1 检测 5.1.7 利用UnhandledExceptionFilter检测 5.1.8 INT 41子类型 5.2 反跟踪技术(Anti-Trace) 5.2.1 断点检测 5.2.2 利用SEH反跟踪 5.2.3 SMC技术实现 5.3 反加载技术(Anti-Loader) 5.3.1 利用TEB检测 5.3.2 利用IsDebuggerPresent函数检测 5.3.3 检查父进程 5.4 反DUMP技术(Anti-Dump) 5.5 文件完整性检验 5.5.1 CRC校验实现 5.5.2 校验和(Checksum) 5.5.3 内存映像校验 5.6 反监视技术(Anti-Monitor) 5.6.1 窗口方法检测 5.6.2 句柄检测 5.7 反静态分析技术 5.7.1 扰乱汇编代码 5.7.2 花指令 5.7.3 信息隐藏 5.8 代码与数据结合技术 5.9 软件保护的若干忠告 第6章 加壳软件编写 6.1 外壳编写基础 6.1.1 判断文件是否是PE格式的EXE文件 6.1.2 文件基本数据的读入 6.1.3 额外数据保留 6.1.4 重定位数据的去除 6.1.5 文件的压缩 6.1.6 资源区块的处理 6.1.7 区块的融合 6.1.8 输入表的处理 6.1.9 外壳部分的编写 6.1.10 将外壳部分添加至原程序 6.1.10 小结 6.2 加壳程序综合运用的实例 6.2.1 程序简介 6.2.2 加壳子程序(WJQ_ShellBegin()) 6.2.3 PE外壳程序 6.2.4 加进Anti技术 6.2.5 通过外壳修改被加壳PE 6.2.6 VC++调用汇编子程序 第7章 如何让壳与程序融为一体 7.1 序 7.1.1 为何需要壳和程序一体化 7.1.2 为阅读此章节需要的知识 7.1.3 基于此章节用的的例子程序说明 7.2 欺骗检查壳的工具 7.2.1 fi是如何检查壳的 7.2.2 欺骗fi 7.3 判断自己是否给脱壳了 7.3.1 判断文件尺寸 7.3.2 检查标记 7.3.3 外部检测(使用dll) 7.3.4 hook 相关的api(防止loader和调试api) 7.4 使用sdk把程序和壳溶为一体 7.4.1 sdk的意义 7.4.2 做一个带sdk的壳 7.5 后记:关于壳和程序的思考 第8章 Visual Basic 6 逆向工程 8.1 简介 8.2 P-code传奇 8.3 VB编译奥秘 8.4 VB与COM 8.5 VB可执行程序结构研究 8.6 VB程序事件解读 8.7 VB程序图形界面(GUI)解读 8.8 VB程序执行代码研究 8.9 我们的工具 8.10 VB程序保护篇 附录A 在Visual C++中使用内联汇编 附录B 在Visual Basic中使用汇编

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值