[jumping to the gate] 娱乐向setjmp
转载:http://tieba.baidu.com/p/1393753521
很容易写出一个使用setjmp-longjmp的例子:
#include <iostream>
#include <setjmp.h>
static jmp_buf buf;
class test_class {
private:
int val;
public:
test_class(int v = 0) : val(v) {
std::cout << "constructor " << val << std::endl;
}
~test_class() {
std::cout << "destructor" << val << std::endl;
}
};
void rtn(void) {
test_class t(5);
longjmp(buf, 1);
}
int main(void) {
if (!setjmp(buf)) {
rtn();
} else {
std::cout << "main" << std::endl;
while (1);
}
return 0;
}
varna@iflit:~/backup/essay$ ~/workspace/langtest/cc/testjmp
constructor 5
main
^C
varna@iflit:~/backup/essay$
这里我们注意到if语句的两个分支都得到了执行,看起来像是初次调用setjmp的时候返回了0,而rtn调用longjmp时setjmp又返回了longjmp指定的1。读者可能会感到奇怪,毕竟程序是逐条语句执行的,longjmp是否真的调用了setjmp也没法确定。总之,setjmp像我们熟悉的fork一样返回了两个值,当然与fork的差别在于fork在同一进程之内只有一种返回值,无论是0,一个pid或是错误。
那么下面我们来实现一个简单的setjmp-longjmp. 这里的实现是体系相关的,下面的例子都假设是IA-32;出于方便起见也使用了GCC内联汇编而不是独立的汇编单元,并没有考虑到线程安全的因素因而不要真的拿来做协程。同时我也不保证c标准库的实现是否是这样,这只是一个〔来自钟表外侧的猜测〕。
为了获得调用者的栈桢范围,考虑函数对应的汇编代码:
push ebp
mov ebp, esp
...调整esp给出参数、局部变量和临时变量的空间
...函数体
mov esp, ebp
pop ebp
ret
这里我们能看出,调用者的栈桢底被压到栈上,如果读者对call指令有了解的话,会意识到次栈顶此时是call压入的返回地址。之后把ebp置为运行时栈的栈顶esp, 同时也是被调函数的栈桢底。当函数决定返回的时候,从ebp中恢复出曾经的esp, 弹出栈顶以恢复调用者的栈桢底,执行ret指令将弹出目前的栈顶,也就是被调函数的返回地址。这时,esp已经恢复成了调用者真实的栈顶。
如果要简单地图解一下,在被调函数完成esp调整之后的时刻,栈上的状况是
(高址)
(调用者的栈桢)
返回地址(四字节)
被调函数的栈桢底(四字节),此处的地址也是当前ebp的地址
(被调函数的栈桢,此处位于最低地址的有效数据被esp所指向)
这样,当我们拿到ebp之后,0x4(ebp)的位置就是返回地址,而ebp的值加0x8就是调用者的栈桢顶。同时我们知道,cdecl调用使用eax来传递返回值,因此我们可以给出下面的结构用于辅助完成废止性跳转:
struct c_jmpbuf {
unsigned long esp;
unsigned long ebp;
unsigned long eip;
unsigned long eax;
};
这是完成跳转所需要的最少数量的硬件上下文。接着我们可以根据前面的描述实现setjmp, 完成在某一点上对上下文结构的填写:
buf->eax = 0;
asm volatile ("mov %%ebp, %0" : "=r" (buf->ebp));
asm volatile ("mov %%esp, %0" : "=r" (buf->esp));
asm volatile ("mov 0x4(%%ebp), %0" : "=r" (buf->eip));
buf->esp = buf->ebp + 0x8;
buf->ebp = *((unsigned long *) buf->ebp);
return 0;
}
首先获得ebp和esp(这里的实现并没有真的用到esp),之后获得0x4(ebp)处的返回地址填入断点eip. 之后调整出调用者栈桢需要的esp和ebp填入上下文,注意如果懒得用局部变量的话就必须先调整esp, 因为它的值直接依赖于当前栈桢使用的ebp值。
最麻烦的工作已经完成了,下面我们可以轻易实现跳转本身:
int c_longjmp(struct c_jmpbuf *buf, int retval) {
asm volatile ("mov %0, %%eax" : : "r" (retval));
asm volatile ("mov %0, %%ecx" : : "d" (buf->eip));
asm volatile ("mov %0, %%esp" : : "d" (buf->esp));
asm volatile ("mov %0, %%ebp" : : "d" (buf->ebp));
asm volatile ("jmp *%ecx");
}
我们只需要根据上下文恢复四个寄存器:作为返回值的eax, 作为返回地址的eip, 标识栈桢范围的ebp和esp. 之后强行跳转到eip给出的返回地址就可以了。但是这段代码本身只是个简单例子,读者会注意到eip/esp/ebp的恢复过程中都指定了edx作为中间寄存器以避开ebx, 但esi和edi并没有恢复。我并不确定这样的实现在绝大部分情况下都能正常工作,所以如果真的要实现最好使用独立的汇编单元来做,当然可以考虑约定fastcall或者扩展regparm, 不然在上下文突变区里清理栈桢绝对不是什么容易测试的工作。
#include <stdio.h>
#include <stdlib.h>
struct c_jmpbuf {
unsigned long esp;
unsigned long ebp;
unsigned long eip;
unsigned long eax;
};
int c_setjmp(struct c_jmpbuf *buf) {
buf->eax = 0;
asm volatile ("mov %%ebp, %0" : "=r" (buf->ebp));
asm volatile ("mov %%esp, %0" : "=r" (buf->esp));
asm volatile ("mov 0x4(%%ebp), %0" : "=r" (buf->eip));
buf->esp = buf->ebp + 0x8;
buf->ebp = *((unsigned long *) buf->ebp);
return 0;
}
int c_longjmp(struct c_jmpbuf *buf, int retval) {
asm volatile ("mov %0, %%eax" : : "r" (retval));
asm volatile ("mov %0, %%ecx" : : "d" (buf->eip));
asm volatile ("mov %0, %%esp" : : "d" (buf->esp));
asm volatile ("mov %0, %%ebp" : : "d" (buf->ebp));
asm volatile ("jmp *%ecx");
}
struct c_jmpbuf buf, buf1, buf2;
void rtn() {
printf("world\n");
c_longjmp(&buf2, 1);
}
void rtn2() {
if (c_setjmp(&buf2) == 0) {
printf("hello rtn2\n");
rtn();
} else {
printf("again rtn2\n");
c_longjmp(&buf, 1);
}
}
void rtn1() {
if (c_setjmp(&buf1) == 0) {
printf("hello rtn1\n");
rtn2();
} else {
printf("again rtn1\n");
c_longjmp(&buf, 1);
}
}
int main(void) {
unsigned long ret;
asm volatile ("mov %%ebp, %0" : "=r" (ret));
printf("main ebp = %x\n", ret);
if (c_setjmp(&buf) == 0) {
printf("hello main\n");
rtn1();
} else {
printf("again main\n");
}
return 0;
}
下面是运行结果。注意到这里rtn2的废止性返回越过了rtn1返回main. 读者可以修改样例进行测试,如果有失败的情况欢迎提出。
varna@iflit:~/backup/essay$ ~/workspace/langtest/spec/sjmp
main ebp = bfe7c408
hello main
hello rtn1
hello rtn2
world
again rtn2
again main
varna@iflit:~/backup/essay$
最后是一点编写和调试方面的建议。如果读者并不习惯操作程序的运行时结构,可以练习使用一种反汇编工具和一种调试工具,逐步改正自己的程序。反汇编工具可以用来确定实际的返回地址,而调试器则可以显示出各寄存器和各变量的值,从而可以逐个发现并修正错误的地址。
[jumping to the gate] 娱乐向setjmp的更多相关文章
- Android Jni 调用
Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... 11 Chap 3:javah命令帮助信息... 16 Chap 4:用javah产生一个.h文件... 17 Chap5:j ...
- 使用Red Gate Sql Data Compare 数据库同步工具进行SQL Server的两个数据库的数据比较、同步
Sql Data Compare 是比较两个数据库的数据是否相同.生成同步sql的工具. 这一款工具由Red Gate公司出品,我们熟悉的.NET Reflector就是这个公司推出的,它的SQLTo ...
- 使用Red Gate Sql Compare 数据库同步工具进行SQL Server的两个数据库的结构比较、同步
将测试版的项目同步(部署)到正式版的时候,两个数据库的结构比较与同步时,如果修改数据库的时候没有记录好修改了那些表,很难将两个数据库进行同步 RedGate Sql Compare使用简介说明: 1. ...
- 【.net深呼吸】动态类型(娱乐篇)
有朋友跟老周说,动态类型是干吗的,他不太熟悉,希望老周可以讲一讲.没事,这事情老周也比较TMD乐意做的,因为老周写的这些烂文本来就是为了普及基础知识的,坚定不移地为社会基础教育而服务. 首先,咱们要知 ...
- Gate Of Babylon bzoj 1272
Gate Of Babylon (1s 128MB) babylon [问题描述] [输入格式] [输出格式] [样例输入] 2 1 10 13 3 [样例输出] 12 [样例说明] [数据范围] 题 ...
- C和指针 第十六章 标准函数库 本地跳转setjmp.h
setjmp和longjmp提供一种类似goto语句的机制,但它的作用域不局限于同一个函数的作用域之内.这些函数可以用于深层次的嵌套函数调用链. int setjmp(jmp_buf state); ...
- 复利计算APP版-----娱乐一下
先不说那么多,下载地址来一个:http://pan.baidu.com/s/1eSz2GBg 目前版本号为:0.3 lastest 软件上线了!三平台首发! 下载地址: http://shouji.b ...
- Red Gate(SQLToolbelt)SQL Server的安装与注册(破解)
Red Gate(SQLToolbelt)是SQL Server辅佐工具 1.SQL Compare 比较和同步SQL Server数据库结构 2.SQL Data Compare 比较和同步SQL ...
- C 语言中 setjmp 和 longjmp
在 C 语言中,我们不能使用 goto 语句来跳转到另一个函数中的某个 label 处:但提供了两个函数——setjmp 和 longjmp来完成这种类型的分支跳转.后面我们会看到这两个函数在处理异常 ...
随机推荐
- Delphi流的操作 转
一.流的概念 流简单说是建立在面向对象基础上的一种抽象的处理数据的工具,它定义了一些处理数据的基本操作,如读取数据,写入数据等,程序员只需掌握对流进行操作,而不用关心流的另一头数据的真正流向.其实,流 ...
- Spring MVC + Spring MongoDB + Querydsl 通过maven整合实例
效果图 一共3个页面:注册页,欢迎页,用户列表页 很简单的例子,主要是为了把流程走通,没有各种验证. 注册页: 欢迎页: 用户列表页: 源码地址 https://github.com/lemonbar ...
- 自定义checkbox样式
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- 总结下java经常犯的错误
编写代码是一种艺术,认识错误是我们代码改进的重要途径之一.以下情况并非大家都能碰到过,但希望提高代码质量的人都引以为戒.以下各种情况,都是初学者经常犯的错误. 1.1 字符串没有判断是否为 ...
- AIX下RAC搭建 Oracle10G(一)检測系统环境
AIX下RAC搭建系列 环境 节点 节点1 节点2 小机型号 IBM P-series 630 IBM P-series 630 主机名 AIX203 AIX204 交换机 SAN光纤交换机 存储 S ...
- android widget 开发实例 : 桌面便签程序的实现具体解释和源代码 (上)
如有错漏请不吝拍砖指正,转载请注明出处,很感谢 桌面便签软件是android上经常使用软件的一种,比方比較早的Sticky Note,就曾很流行, Sticky Note的介绍能够參见 http:// ...
- 初识QML学习机制
在QML中,一个用户界面被指定为具有属性的对象树,这使得Qt更加便于很少或没有编程经验的人使用,JavaScript在QML中作为一种脚本语言,对QML进行逻辑方面的编程. AD:WOT2015 互联 ...
- JavaScript函数 bind call apply区别
1. apply calll 在JavaScript中 call 和 apply 都是为了改变某个函数运行时上下文而存在的, 换句话说就是为了改变函数内部的this的指向. 这里我们有一个新的对象 b ...
- 操作系统下查看HBA卡信息wwn的方法
一.Windows 系统在Windows系统中,可以使用FC HBA卡厂家提供的管理软件查看光纤适配器的WWN号码,具体如下:Qlogic:SANsurferEmulex:HBAnyware http ...
- wp8模拟器操作键盘
当前焦点在模拟器上的某一个输入控件上时候, 按pagedown/pageup可以切换是用pc键盘还是模拟器键盘