setjmp和longjmp的实现原理与应用

2024-01-14 00:32

本文主要是介绍setjmp和longjmp的实现原理与应用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在讨论setjmp的实现原理之前,我们先看一个setjmp和longjmp的例子:
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

static jmp_buf jmpbuf_1;

int g_a = 0;

void test


int main



ret = setjmp;

if

else if

else if


test;

return 0;
}


下面是测试代码在不同的编译优化条件下的执行结果:
wangyao@wangyao-laptop:~/Test/sys$ gcc longjmp.c -O2
wangyao@wangyao-laptop:~/Test/sys$ ./a.out 1
Orig setjmp
Return From longjmp 1
Global_a: 1111
Local_a: 1
wangyao@wangyao-laptop:~/Test/sys$ ./a.out 2
Orig setjmp
Return From longjmp 2
Global_a: 1111
Local_a: 1
wangyao@wangyao-laptop:~/Test/sys$ gcc longjmp.c
wangyao@wangyao-laptop:~/Test/sys$ ./a.out 1
Orig setjmp
Return From longjmp 1
Global_a: 1111
Local_a: 2222
wangyao@wangyao-laptop:~/Test/sys$ ./a.out 2
Orig setjmp
Return From longjmp 2
Global_a: 1111
Local_a: 2222

man 3 longjmp中提到:
The values of automatic variables are unspecified after a call to longjmp if they meet all the following criteria:
  they are local to the function that made the corresponding setjmp call;
  their values are changed between the calls to setjmp and longjmp; and
  they are not declared as volatile.

posix中没有定义自动变量在调用longjmp后的动作。

在调用longjmp后,自动变量、全局变脸、寄存器变量、静态变量和易失变量的变化是不同的。全局变量、静态变量和易失变量的分配要么是在data段,要么是在bss段中,上下文切换对它们基本上没有什么影响;但是对于栈和寄存器来讲,上下文切换就有很大的影响。因此对于一些自动变量、寄存器变量就可能会在longjmp的时候发生变化,当然这个还与编译时候的优化条件有关。


下面再来讨论setjmp和longjmp的实现原理,这里我们只关注x86架构的实现,具体来讲是i386的实现。

首先,回想一下x86架构下面,函数调用过程栈的变化情况:
在x86架构下,调用函数的时候,首先自右向左将压栈,(早期的gcc版本是采用push $变量的方法;后期gcc使用的是sub $num,esp 然后mov $变量,esp+$num的方法)
接下来就是call func了,call func的动作是,将下一指令地址压栈,jmp到func的地址执行。
func刚开始就执行一个push ebp,将原先的栈基址保存下来。

setjmp具体的实现在:
glibc-2.7/sysdeps/i386/setjmp.S
将寄存器中的值保存到jmp_buf中。
ENTRY )
ENTER

movl JMPBUF, %eax
CHECK_BOUNDS_BOTH_WIDE , $JB_SIZE)

/ Save registers. /
movl %ebx,
movl %esi,
movl %edi,
leal JMPBUF, %ecx / Save SP as it will be after we return. /
#ifdef PTR_MANGLE
PTR_MANGLE
#endif
movl %ecx,
movl PCOFF, %ecx / Save PC we are returning to now. /
#ifdef PTR_MANGLE
PTR_MANGLE
#endif
movl %ecx,
LEAVE / pop frame pointer to prepare for tail-call. /
movl %ebp,  / Save caller‘s frame pointer. /

#if defined NOT_IN_libc && defined IS_IN_rtld
/ In ld.so we never save the signal mask. /
xorl %eax, %eax
ret
#else
/ Make a tail call to __sigjmp_save; it takes the same args. /
jmp __sigjmp_save
#endif


longjmp的具体实现在:
glibc-2.7/sysdeps/i386/__longjmp.S
将jmp_buf中的值恢复到相应的寄存器中,并将longjmp的第2个参数作为返回值返回,由于longjmp中将寄存器的eip设置回setjmp时候的值。longjmp的返回值在程序逻辑上就是setjmp的返回值了。
#ifdef PTR_DEMANGLE
movl JBUF, %eax / User‘s jmp_buf in %eax. /
CHECK_BOUNDS_BOTH_WIDE , $JB_SIZE)

/ Save the return address now. /
movl , %edx
/ Get the stack pointer. /
movl , %ecx
PTR_DEMANGLE
PTR_DEMANGLE
cfi_def_cfa
cfi_register
cfi_register
cfi_offset
cfi_offset
cfi_offset
cfi_offset
/ Restore registers. /
movl , %ebx
movl , %esi
movl , %edi
movl , %ebp
cfi_restore
cfi_restore
cfi_restore
cfi_restore

movl VAL, %eax / Second argument is return value. /
movl %ecx, %esp
#else
movl JBUF, %ecx / User‘s jmp_buf in %ecx. /
CHECK_BOUNDS_BOTH_WIDE , $JB_SIZE)

movl VAL, %eax / Second argument is return value. /
/ Save the return address now. /
movl , %edx
/ Restore registers. /
movl , %ebx
movl , %esi
movl , %edi
movl , %ebp
movl , %esp
#endif
/ Jump to saved PC. /
jmp %edx
END )


-----------------------------------------------------------------------------
对于setjmp和longjmp的使用还应该注意一些问题:
setjmp和longjmp函数,这两个函数在跳转时会带信号屏蔽字跳转,在信号处理程序中使用longjmp会导致后来产生的这种信号被屏蔽,无法调用此种信号的信号处理函数。
POSIX.1 也没有具体说明setjmp和longjmp对信号屏蔽字的作用,而是定义了两个新函数: sigsetjmp和siglongjmp。
sigsetjmp在参数为非0的时候,会保存进程的当前信号屏蔽字;在调用siglongjmp的时候,再恢复保存的信号屏蔽字。

一段演示siglongjmp的代码:
#include <signal.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>

/ Jump buffer /
static sigjmp_buf jmpbuf;

/ Signal handler /
static void myfunc


int main

else


/ JUMP /
printf;

return 0;
}
运行结果为:
wangyao@wangyao-laptop:~/Test/sys$ ./a.out
SIGQUIT
I‘m jumped
I‘m here

这篇关于setjmp和longjmp的实现原理与应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/603331

相关文章

golang版本升级如何实现

《golang版本升级如何实现》:本文主要介绍golang版本升级如何实现问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录golanwww.chinasem.cng版本升级linux上golang版本升级删除golang旧版本安装golang最新版本总结gola

PostgreSQL的扩展dict_int应用案例解析

《PostgreSQL的扩展dict_int应用案例解析》dict_int扩展为PostgreSQL提供了专业的整数文本处理能力,特别适合需要精确处理数字内容的搜索场景,本文给大家介绍PostgreS... 目录PostgreSQL的扩展dict_int一、扩展概述二、核心功能三、安装与启用四、字典配置方法

SpringBoot中SM2公钥加密、私钥解密的实现示例详解

《SpringBoot中SM2公钥加密、私钥解密的实现示例详解》本文介绍了如何在SpringBoot项目中实现SM2公钥加密和私钥解密的功能,通过使用Hutool库和BouncyCastle依赖,简化... 目录一、前言1、加密信息(示例)2、加密结果(示例)二、实现代码1、yml文件配置2、创建SM2工具

Mysql实现范围分区表(新增、删除、重组、查看)

《Mysql实现范围分区表(新增、删除、重组、查看)》MySQL分区表的四种类型(范围、哈希、列表、键值),主要介绍了范围分区的创建、查询、添加、删除及重组织操作,具有一定的参考价值,感兴趣的可以了解... 目录一、mysql分区表分类二、范围分区(Range Partitioning1、新建分区表:2、分

MySQL 定时新增分区的实现示例

《MySQL定时新增分区的实现示例》本文主要介绍了通过存储过程和定时任务实现MySQL分区的自动创建,解决大数据量下手动维护的繁琐问题,具有一定的参考价值,感兴趣的可以了解一下... mysql创建好分区之后,有时候会需要自动创建分区。比如,一些表数据量非常大,有些数据是热点数据,按照日期分区MululbU

MySQL中查找重复值的实现

《MySQL中查找重复值的实现》查找重复值是一项常见需求,比如在数据清理、数据分析、数据质量检查等场景下,我们常常需要找出表中某列或多列的重复值,具有一定的参考价值,感兴趣的可以了解一下... 目录技术背景实现步骤方法一:使用GROUP BY和HAVING子句方法二:仅返回重复值方法三:返回完整记录方法四:

IDEA中新建/切换Git分支的实现步骤

《IDEA中新建/切换Git分支的实现步骤》本文主要介绍了IDEA中新建/切换Git分支的实现步骤,通过菜单创建新分支并选择是否切换,创建后在Git详情或右键Checkout中切换分支,感兴趣的可以了... 前提:项目已被Git托管1、点击上方栏Git->NewBrancjsh...2、输入新的分支的

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语