bug总是存在的,不可能做到没有bug。
很多人和我一样,不知道debug的调试原理,只知道点击IDE的debug调试按钮进行调试程序。
我很好奇,java、C、python、js 这些编程语言是如何做到debug的?
于是乎就有了今天这篇文章
感谢 张银奎 老师录了《Windows 驱动开发 之 WinDbg调试》这套视频,在此之前我也不知道debug是怎么实现的,以前也没有想过这些问题。最近因为工作等一些原因,有幸看到了这套视频,很值得学习。
为此,我准备写一些博客记录一下这套视频中一些关键的内容。
在真正开始讲底层原理前,需要讲一些额外的内容作为补充。
一、用户态 和 内核态众所周知,windows操作系统分为 用户态、内核态。
早期的操作系统并没有这些概念,而是让用户直接操作内存,会导致程序之间内存可能会出现互相覆盖,因此早期的系统不稳定,当程序覆盖到系统比较核心的内容时就会导致系统出现蓝屏等症状。
后来微软为了解决这个问题,将操作系统重新设计,依据用户和系统之间的角色将操作系统划分为2块空间。这种方式就避免了用户程序影响到系统程序。
当然系统之间的程序应该也存在互相影响的可能性,联想到早期windows的频繁更新,推测有一方面的原因在于解决系统程序之间互相影响的bug。
随着这几年使用windows,发现进几年windows的蓝屏等现象越来越少,会不会就是这个原因呢?
平时我们使用windows写的程序,都是在用户态跑的,对于windows一些高级的api,比如创建线程、阻塞线程、唤醒线程,这些操作肯定是在内核态完成的。那么这块内容也经常会在程序员面试的过程当作考题。
在写java程序的过程中,我也在思考,平时写过下面这些代码,通过Thread类创建一个线程
package top.yumbo.demo;
public class ThreadDemo {
public static void main(String[] args) {
new Thread(()->{
System.out.println("hello Thread!");
}).start();
}
}
如果你是一个热爱技术的开发人员,相信你可能会点进去看下,内部是怎么做的,点进去后,你会发现代码原来是这样的,其中start0();则是我们要关注的,也是可以大作文章的。
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();// 这个方法是关键点,调用了后面native方法
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
当你发现这个private native void start0();时,很多人就不知道怎么追踪下去了,实际上native就是由C实现的动态链接库,安装jdk的过程中肯定也看到过这些jdk目录中有一些dll文件。如果你对这个native方法好奇,自己也想尝试写一个native的方法试一下,可以看下我之前的文章《JNI 的hello world 案例》
实际上看到这里就得追踪到jdk源码中去,看下 Thread类对于这个start0它是如何实现的。一想到这里,很多人就停下了学习的脚步。但实际上很多时候,你都会遇到这些问题。在面试的过程中经常会问道并发,锁这些内容,尤其是synchronized,经常会问到底层。
谈到synchronized就嘿嘿一笑,因为这是一个关键字,对于一般人来说,这你让我怎么回答,只有看过相关资料的人才可以答上一二。实际上这里面的学问很多,要是继续追踪下去,很快就会到操作系统内核这些概念。
一句话总结上面内容:操作系统分用户态和内核态,我们跑的程序都在用户态执行,程序需要用到内核态的 api 时才会调用内核态完成相关的运算。
想要强调的一点是:用户态 和 内核态 的基础概念很重要
在知道windows系统分为用户态和内核态后,这个时候才可以介绍
二、Windows 用户态调试模型这个调试模型在《Windows 驱动开发 之 WinDbg调试》第一集中也有介绍,感兴趣的小伙伴可以自行前往。
整个windows调试模型是基于调试事件驱动设计,有下面这些事件
EXCEPTION_DEBUG_EVENT 异常debug事件CREATE_THREAD_DEBUG_EVENT 创建线程debug事件CREATE_PROCESS_DEBUG_EVENT 创建进程debug事件EXIT_THREAD_DEBUG_EVENT 退出线程debug事件EXIT_PROCESS_DEBUG_EVENT 退出进程debug事件LOAD_DLL_DEBUG_EVENT 加载动态链接库debug事件UNLOAD_DLL_DEBUG_EVENT 卸载动态链接库debug事件OUTPUT_DEBUG_STRING_EVENT 输出debug内容事件 断点
可以理解为cpu调试指令,指令为 int3
在听 张银奎 老师介绍的过程中,为了理解这个模型,我自己也画一下这个用户态调试模型,查漏补缺。
用户态调试模型:
在写程序过程中经常会写
try{
}
catch(Exception e){
}
以及throws都由图中的3RaiseException 进行实现
在操作系统中异常来源于下面2种
CPU产生的异常
执行指令时检测到的错误,除0,GP,无效指令Machine Check Exceptions,总线错误,ECC错误,Cache错误预先埋伏的,int3,调试异常
程序产生的异常
RaiseException,Win32 APIC++,throw E,编译器翻译为RaiseException的调用C#,throw,最终任然由RaiseException
由此可见RaiseException相当重要!
我相信java也是调用系统api 与C++一样,毕竟跨进程调试,需要在内核态中完成



