https://github.com/DoasIsay/ToyCoroutine

 

事情是这样的,在第一次因协程栈溢出导致的进程崩溃后,过了不知道多久由于代码量的增加,协程的栈又溢出了程序又崩溃了,多线程加协程对使用gdb调试而言并不是很友好,可能我还是不会使用gdb吧,从coredump中基本上找不到什么有助于问题定位的信息,有时候连个函数调用栈都没有,你都不知道运行到那个函数那行代码时出现了问题,如果是刚刚修改了代码程序就崩了,还可以大概确定是修改了的这部分代码出了问题,但是如果修改了很多代码,就需要大海捞针一样,用最原始的调试手段,在每个你觉得可疑的地方去print碰运气

于是我觉得协程库要提供一种机制来检测栈溢出,以避免在下一次由于栈溢出导致进程崩溃时我还要排查进程到底是不是由于栈溢出崩溃的,其实要确定是不是由于栈溢出导致的进程崩溃很简单,只要是程序崩溃就不管3721先增大协程栈再说,增加栈空间后不崩了,那就是栈溢出导致的此次崩溃

但是这件事并不是时常发生,当发生的时候总是会忘了栈溢出,到最后实在是找不到原因,没办法了才会又想到栈溢出,为了以后不再重蹈覆辙,还是实现栈溢出检测吧

 

gcc是怎样实现栈溢出检测的?也适用于协程?

一个C/C++协程库的思考与实现之栈溢出检测

 

我们来看一个不加-fstack-protector-all编译选项与加-fstack-protector-all编译选项的可执行文件的的反汇编代码

一个C/C++协程库的思考与实现之栈溢出检测

 

不加-fstack-protector-all编译选项

一个C/C++协程库的思考与实现之栈溢出检测

 

加-fstack-protector-all编译选项

 

一个C/C++协程库的思考与实现之栈溢出检测

从反汇编代码可以看到在加了-fstack-protector-all编译选项后,编译器在fun函数调用的栈帧上-0x8(%rbp)处放入了8个字节的标识,并在代码中插入了检测代码,就是在返回时对比这8个字节跟放入时是不是一致的,如不一致则发生栈溢出,栈布局如下图

为了节约我那宝贵的时间只画出fun函数调用的栈帧

一个C/C++协程库的思考与实现之栈溢出检测

 

 

在编译时加-fstack-protector-all选项会在所有的函数内插入标识与检测代码,如果我们把代码改成这样

一个C/C++协程库的思考与实现之栈溢出检测

 

当向a中放置超过4个字节的内容时,多出的内容就会覆盖标识,在函数返回时,就会检测到栈被破坏了

一个C/C++协程库的思考与实现之栈溢出检测

一个C/C++协程库的思考与实现之栈溢出检测

 

我们称其为栈的下溢,而协程库需要的是检测的是栈的上溢,在协程使用了超过自身栈大小时能及时检测出来,由于协程的栈是在堆上分配的,一旦发生上溢,整个进程使用的堆就会被破坏,在下一次malloc,free时进程就有可能崩溃

一个C/C++协程库的思考与实现之栈溢出检测一个C/C++协程库的思考与实现之栈溢出检测

 

如下图比如有两个协程的栈在堆上是相邻的

一个C/C++协程库的思考与实现之栈溢出检测

 

当协程1使用了超过自身栈大小的空间,越过了图中标红处就会上溢到协程2的栈上,此时协程2的栈就会被破坏,准确的说应该是栈帧被破坏了(进程使用的堆也会被破坏),当协程2的函数调用链开始返回,调用栈回退时协程2就会检测到栈被破了,这样看来gcc的栈保护对协程而言也是有效的,但是有限

  1. 如果协程1只上溢覆盖了协程2的rip,rbp,并未覆盖到标识,协程2的栈虽然已经被被破坏了但还是检测不到,仍然会继续返回到一个未知的地方使用一个未知的栈
  2. 如果协程2在一个函数调用中不返回那么也无法检测到自己的栈已经被破坏了
  3. 因为协程对象与协程栈都是在堆中分配的,如果由于协程栈的上溢而导致协程对象被覆盖破坏,也是无法检测到的

 

gcc提供的栈保护仅仅是为了应对缓冲区溢出导致的栈下溢,那么问题来了为什么gcc不检测栈的上溢?

如果我们在所有的协程对象及栈的起始与结束置8字节的标识,时常检测它,那么就有可能及时主动发现栈或协程对象被破坏了,极大方便了测试时程序崩溃问题的定位

要怎么检测?

栈及对象是协程库分配创建的,协程库知道它们的大小及从什么地方开始结束,在每个函数返回时插入检测点

 

检测什么?

  1. 取出当前栈指针也就是CPU的SP寄存器的值与协程栈的结束地址做比较,如果SP小于协程栈的结束地址,那么此次函数调用已经产生了栈的上溢,当前协程已经使用了超过了给其分配的栈空间,其它协程及栈有可能被破坏
  2. 在协程栈与协程对象的的开始与结束存放若干字节的标识,如8字节” 0x0123456789abcde0”,检测这8字节是否被改变,如被改变则说明自已的栈或对象已经被破坏了

我们实现2就可以了,实现如下图

一个C/C++协程库的思考与实现之栈溢出检测

使用如下图,在函数返回前进行检测

 

一个C/C++协程库的思考与实现之栈溢出检测

 

其实最初我只是想检测协程的栈是否被破坏了,慢慢的我发现既然协程栈与协程对象都是在堆上分配的,协程栈的溢出不就是内存的越界访问?

那么用valgrind不就可以了?

如此说来我又是在瞎搞喽,,,,

当我欣喜若狂的使用valgrind启动程序时进程竟然崩溃了,这是个啥子问题哟,valgrind竟然无法支持ToyCoroutine,,,

相关文章:

  • 2021-10-26
  • 2021-04-03
  • 2021-10-15
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-06-07
猜你喜欢
  • 2021-10-10
  • 2021-12-11
  • 2021-11-07
  • 2022-12-23
  • 2021-06-20
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案