[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来完成这种类型的分支跳转.后面我们会看到这两个函数在处理异常 ...
随机推荐
- STL 查找vector容器中的指定对象:find()与find_if()算法
1 从vector容器中查找指定对象:find()算法 STL的通用算法find()和find_if()可以查找指定对象,参数1,即首iterator指着开始的位置,参数2,即次iterator指着停 ...
- Linux企业级项目实践之网络爬虫(23)——系统测试:找出系统中的bug
为了验证爬虫的业务流程.性能和健壮性需要进行测试. 软件测试是描述一种用来促进鉴定软件的正确性.完整性.安全性和质量的过程.软件测试的经典定义是:在规定的条件下对程序进行操作,以发现程序错误,衡量软件 ...
- android真机调试
android开发可以使用google那个自带的模拟器来调试,不过那个模拟器启动实在太慢,太耗时了,不过,如果我们有android手机的话,我们可以直接在手机上调试,这样的话,速度就很快: 具体步骤如 ...
- FutureTask 测试用例
package currentTest.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent ...
- JS字符串拼接优化
// 请把以下用于连接字符串的JavaScript代码修改为更高效的方式 var htmlString = ‘ < div class=”container” > ’ + ‘ < u ...
- 常用文件的文件头(附JAVA测试类)
1. MIDI (mid),文件头:4D546864 2. JPEG (jpg),文件头:FFD8FF 3. PNG (png),文件头:89504E47 4. GIF (gif),文件头:47494 ...
- Three Families
http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&p ...
- Thrift的安装和简单演示样例
本文仅仅是简单的解说Thrift开源框架的安装和简单使用演示样例.对于具体的解说,后面在进行阐述. Thrift简述 ...
- How to face setbacks
I’ve been in a bad mood since I started on the American Accent. I became even more upset when I adde ...
- C++11中正則表達式測试
VC++2010已经支持regex了, 能够用来编译下述代码. #include <string> #include <regex> #include <iostream ...