Setjmp



简介

setjmp是C语言解决exception的标准方案

函数间跳转我们常用的函数是goto
(goto是很常用的语句,由于国内众多教科书抵制,造成goto不能用的假象,去看看linux内核代码中有多少goto)
但在函数间的跳转就不能用goto了

我们要知道,要实现这种类型的跳转,和操作系统中任务切换的上下文(context)切换有点类似,我们只需要恢复Label标签处函数上下文即可。函数的上下文包括以下内容:

函数栈帧,主要是栈帧指针BP和栈顶指针SP
程序指针PC,此处为指向 Label 语句的地址
其它寄存器,这是和体系相关的

这样,在执行GOTO Label这条语句,我们恢复Label处的上下文,即完成跳转到Label处的功能。

Linux 会把进程的上下文保存在 task_struct 结构体中,切换时直接恢复。
这就是函数间进行跳转的基本原理,C语言中 setjmp和longjmp就为我们完成了这样的保存上下文和切换上下文的工作。

源代码分析

函数原型

具体实现

我们以下面的例子分析在linux x86_64下的setjmp 与longjmp的具体实现:
示例如下:

运行结果:

setjmp

首先,jmp_buf 的结构在bss段中
我们首先看jmp_buf的数据结构:调试进入到setjmp函数中,其源文件glibc/sysdeps/x86_64/setjmp.S是汇编写的,但是比较晦涩,我们从调试的角度看这段汇编:

这段代码就就是在x86-64体系下存在jmp_buf的内容,其中,我们可见有一个简单的亦或与循环移位的加密:

其中fs:0x30是每次会变化,但在一次运行的过程中是不变的,我们不妨称之为cookie,称整个加密过程为encrypt,用python描述如下:

setjmp的作用就是将当前的程序状态存储到这个jmp_buf结构中,部分关键内容进行加密

jmp_buf

根据汇编中的内容,我们可以知道jmp_buf中保存的内容,整个jmp_buf的结构描述如下,虽然jmp_buf开辟空间不小,但在x64下只用到了一下空间:

longjmp

longjmp的源代码glibc/sysdeps/x86_64/__longjmp.S

可见在longjmp中主要是有一个简单的解密过程,用python描述如下:

之后从rdi,也就是jmp_buf中把相应的内容恢复,这样就实现了类似context的切换过程

整个过程中,理解setjmp最关键的是对整个jmp_buf结构的把握

使用setjmp和longjmp要注意以下几点:

1、setjmp与longjmp结合使用时,它们必须有严格的先后执行顺序,也即先调用setjmp函数,之后再调用longjmp函数,以恢复到先前被保存的context
2. longjmp必须在setjmp调用之后,而且longjmp必须在setjmp的作用域之内。具体来说,在一个函数中使用setjmp来初始化一个全局标号,然后只要该函数未曾返回,那么在其它任何地方都可以通过longjmp调用来跳转到setjmp的下一条语句执行。实际上setjmp函数将发生调用处的局部环境保存在了一个jmp_buf的结构当中,只要主调函数中对应的内存未曾释放(函数返回时局部内存就失效了),那么在调用longjmp的时候就可以根据已保存的jmp_buf参数恢复到setjmp的地方执行。

setjmp异常处理

setjmp有一个重要的功能:exception 的抛出和捕获。
setjmp被用于包住一个例外处理,类似try。longjmp调用类似于throw语句,允许一个异常返回给setjmp一个异常值
读者可自行分析,不在赘述

结果:

参考资料

http://en.wikipedia.org/wiki/Longjmp
http://www.cnblogs.com/hazir/p/c_setjmp_longjmp.html
http://www.sanfoundry.com/c-questions-setjmp-longjmp-functions-standard-library-uses/
http://blog.csdn.net/chenyiming_1990/article/details/8683413

发表评论