前情回顾:
通过系统调用进入内核空间的这个虫洞我终于弄清楚了,可我的冒险还要继续······
详情参见:内核地址空间大冒险:系统调用
除0错误
我是一个线程,出生在Linux帝国,今天我的任务是去执行一段人类用C语言编写的代码。
开始的工作很顺利,一共执行了18次系统调用,对于来往于用户空间与内核空间的那个虫洞我已经轻车熟路,再也不是萌新一枚。
后来,我拿到了一段数学运算的代码,来来往往地奔波于内存与寄存器之间,把我累得够呛,热的满头大汗,电脑风扇都转的飞快给我降温。
没多久,一条除法指令摆在我的面前,我瞟了一眼除数居然是0,一种不好的预感涌上心头。没有办法,硬着头皮也得上啊,准备开始执行这个除法。
突然!眼前闪过一道白光,然后变得漆黑,这不是执行系统调用的虫洞吗?可是我并没有执行系统调用啊,怎么跑到这里来了。
我心里开始犯嘀咕,打算等会问问原来的白胡子老头究竟是怎么回事。
不久,光亮开始出现,来到了一个陌生的地方,白雾茫茫。
继续前行,雾逐渐散去,一座大门出现在我面前,我定睛一看,上面写着:0:divide_error。
除法错误?我越发的紧张起来,这是到哪里了?
中断&异常
“年轻人,欢迎来到内核地址空间”,熟悉的问候语响起,走过来一位白发老头,却不是我在系统调用时见过的那位,拄着一根木棍,挂着一只葫芦,看起来年纪比系统调用那个老头还要大一些。
“敢问老先生,我怎么到这里来了,我并没有执行系统调用啊”,我向老头打听情况。
“这里并不是系统调用的入口,因为你执行了除数为0的除法,触发了异常,所以来到了这里”,老头说完喝了一口葫芦里的酒。
“异常,这又是什么意思?”,今天又听到一个新的名词。
只见老头木棍一挥,大雾完全散去,我这才注意到,这里还有好多大门,它们一个挨着一个,形成了一面门墙。
“老先生,这些都是什么啊,这到底是什么地方?”,我对眼前的景象感到越发的好奇。
“这里是中断描述符表——IDT,是所有中断和异常发生时,你们会来到的地方”,老头用了一堆我不懂的话来回答我。
“中断又是什么?和异常又是什么关系?IDT又是做什么的?”,我向老头发出了灵魂三问。
“中断就是有重要的事情发生,要打断你们线程手头的工作,让出CPU必须去处理”
“什么事情,这么重要?”
“比如说有键盘按键被按下,鼠标被移动或点击,网络中有数据包到来等等情况”。
“那异常呢?”
“异常就是你们这些线程在执行代码指令的时候出现了一些错误的情况,比如做除法的时候除数为0,又比如访问的内存地址错误等这些情况,那遇到这些情况怎么办呢?CPU会发现有问题,强制改变你们的执行流,去处理这些异常”。
“听起来,跟中断差不多嘛!“
“确实差不多,所以它们都用IDT来一起记录嘛!不过实际上差别还是很大的哦。最大的区别在于中断是异步,而异常是同步的!“
“这是为什么?”
“因为中断什么时候来你是不知道的,你是被迫被打断的,而异常是你们执行指令主动造成的”
“那IDT又是做什么的?”
“刚才我不是说发生中断和异常你们就会被打断嘛!那打断后该去那里呢?IDT就是把所有中断和异常发生后要去的地方记录成了一个表,也就是你眼前所看到的这一面门墙了,总共256扇门,你现在触发的是除0错误,该抓紧时间去0号门里去处理异常了!”
信号投递
拜别老先生,我走进divide_error这扇门,接着又穿过了common_exception和do_divide_error,
越往前走,周遭越发的阴森,直到来到了force_sig_info,我停下了脚步,想找人打听下情况。
正在这时,从force_sig_info里面走出一人,瞧着年纪长我几岁。
“大哥,前面是什么地界,为何这般阴森”,我上前请教。
“前面就是给你所属进程投递信号的地方了,你是准备去投递什么信号?”
“信号,什么信号?”我不太听得懂大哥的话。
“就是你手里的第一个参数,让我看一下。咦,是个SIGFPE信号,你是遇到除数是0的除法了吗?”大哥居然看出了我的来历。
“不错,我确实是因为除了一下0才来到这里的,不知大哥是如何得知的?”
“因为你手里是SIGFPE,这是在数学运算出错时才会给进程发送的信号,而通常情况下都是除法除以0时候发生,所以我才猜中的。”
“大哥,您口中一直所说的信号,到底是个什么意思?”
“这个信号就是Signal,用来告诉进程有事情发生了。比如常用的CTRL+C进程就是发送SIGINT信号,kill杀进程就是SIGTERM信号,你现在手里的SIGFPE就是表示有数学运算错误。总而言之,这就是个通知而已”
“那这通知发送到了哪里呢?又是什么时候去处理呢?”,我有些好奇。
“这就先不告诉你了,等会你自己去就知道了,快去吧,再见了”,大哥挥手离开。
歇息过后便又起身继续前行,进入force_sig_info后,又先后跨过几个函数来到send_signal,我看到我所属进程的task_struct中有一个专门存放信号的队列,原来信号是放在了这里。
我准备了一个信号对象加入到了进程的信号队列中,大功告成,准备返回。
返回前夕
很快回到了见到白发老头的地方,我一下难住了,我是通过异常这个虫洞来到这里的,现在我该回哪里去呢?
“年轻人,事情都忙完了?”,老头又一次出现了。
“老先生,嗯,我都忙完了,可是我现在该怎么回去呢?”
“你现在看看你的内核堆栈上面存了什么?”
我低头看了一眼我的内核堆栈,发现上面居然保存了除0指令之后那条指令的地址,这正是我要回去的地方。
“这是什么时候存进去的,我不记得我执行过push保存啊”
“在你刚来到这里的时候就存进去了,确实不是你push进去的,而是当你通过异常这个虫洞进入内核空间时,CPU自动完成的”
“原来如此,我知道我要去哪里了,可是我该怎么打开虫洞回去呢?”
“你看前面,有一条iret指令,通过它,你就能开启虫洞之门,回到用户态空间了”,老头向我指了指方向。
“ret指令我倒是经常执行,就是函数返回嘛,这个iret是什么,能有这么强大能力?”
“iret就是interrupt return的意思,专门用于被中断或异常打断的线程处理完毕后返回用户空间使用的。”
“明白了,感谢老先生,我就先告辞了,下次再见”,再次向老头拜别,准备回到我原来的地方,来这里太久了,都有点想念了。
“等一下,少年,你现在还不能回去”,老头拦下了我。
“不能回去?为什么?”
“回去之前还有件事要去处理哦!”
“到底是什么事情啊?”
“你所在的进程有信号来了,需要先去处理!”
“纳尼?那信号是我放的啊?”,我回头一看,老先生竟然已经走远。
“别走啊,老先生请留步......”
未完待续·······
彩蛋
就在我准备起身去处理信号的时候,一道黑影从我眼前掠过,我抬头一看,IDT门墙的20号门有过丝丝晃动,仔细一瞧,这门上的地址和我来时所见似有不同······
欲知后事如何,请关注后续精彩......
精彩回顾:
一个DNS数据包的惊险之旅
DDoS攻击:无限战争
一条SQL注入引出的惊天大案
内核地址空间大冒险:系统调用
闯荡Linux帝国:nginx的创业故事
一个HTTP数据包的奇幻之旅
远去的传说:安全软件群雄混战史
我是一个流氓软件线程
产品vs程序员:你知道www是怎么来的吗?
比特宇宙-TCP/IP的诞生
我是一个IE浏览器线程
我是一个杀毒软件线程
我是一个explorer的线程
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)