Cortex-M单片机中 setjmp、longjmp原理探究及实现

一、原理

C语言中包含头文件 <setjump.h> 即可实现跨函数跳转,通常用于异常处理,在运行代码出现异常时可以自动跳转到调用setjump函数的位置。

1、实验

#include "setjmp.h"

jmp_buf g_jmp_buf;

int div__(int a,int b)
{
    if(b==0)
    {
        longjmp(g_jmp_buf,1);
    }
    return a/b;
}


int main(void)
{
	void init (void);
	init();
    int a=10;
    int b=0;
    if(setjmp(g_jmp_buf)==0)
    {
        printf("start calc %d/%d\r\n",a,b);
        printf("%d/%d=%d\r\n",a,b,div__(a,b));
    }
    else
    {
        printf("something wrong\r\n");
    }
    
}

输出为
setjmp输出
可见,当b==0时产生了异常,程序从div__()函数跳转回setjmp()函数的位置输出了"something wrong",而不是计算结果。
setjmp和longjmp函数的第一个参数是jmp_buf,这个变量用于存储上下文,setjmp.h中的描述为:

/* size as defined in the ATEPCS /
/
an array type suitable for holding the data /
/
needed to restore a calling environment. */

2、猜想

猜测实现方式是在setjmp函数中保存当前的上下文,也就是cpu的寄存器值,然后在调用longjmp函数时跳转到setjmp函数中再恢复上下文,这与多线程的实现方法类似,只是多线程是把上下文保存到栈中,setjmp可以保存到全局变量或堆中,只需要保证jmp_buf的生命周期在调用setjm和longjmp期间是有效的即可。

二、实现

以下代码在Keil5环境下可以编译通过。
定义jmp_buf数据类型:

typedef u32 my_jmp_buf[48];

定义setjmp和longjmp函数:

    AREA |.text|, CODE, READONLY, ALIGN=2
    THUMB
    REQUIRE8
    PRESERVE8


my_setjmp PROC
	EXPORT	my_setjmp
	STR		r0,[r0]
	STR		r1,[r0,#4]
	STR		r2,[r0,#8]
	STR		r3,[r0,#12]
	STR		r4,[r0,#16]
	STR		r5,[r0,#20]
	STR		r6,[r0,#24]
	STR		r7,[r0,#28]
	STR		r8,[r0,#32]
	STR		r9,[r0,#36]
	STR		r10,[r0,#40]
	STR		r11,[r0,#44]
	STR		r12,[r0,#48]
	STR		r13,[r0,#52]
	STR		r14,[r0,#56]
	;存入PC指针
	LDR		r1,=return_from_longjmp
	STR		r1,[r0,#60]
	MOV		r0,#0
	BX		r14
	
	;从longjmp返回时会从这里开始执行
return_from_longjmp
	STR		r1,[r0]
	LDR		r1,[r0,#4]
	LDR		r2,[r0,#8]
	LDR		r3,[r0,#12]
	LDR		r4,[r0,#16]
	LDR		r5,[r0,#20]
	LDR		r6,[r0,#24]
	LDR		r7,[r0,#28]
	LDR		r8,[r0,#32]
	LDR		r9,[r0,#36]
	LDR		r10,[r0,#40]
	LDR		r11,[r0,#44]
	LDR		r12,[r0,#48]
	LDR		r13,[r0,#52]
	LDR		r14,[r0,#56]
	LDR		r0,[r0]
	BX		r14
	
	ENDP



my_longjmp	PROC
	EXPORT	my_longjmp
	LDR		r2,[r0,#60]
	BX		r2
	ENDP



    ALIGN   4

    END

三、调用


typedef u32 my_jmp_buf[48];


extern int my_setjmp(my_jmp_buf);

extern void my_longjmp(my_jmp_buf,int);

my_jmp_buf g_jmp_buf;

int div__(int a,int b)
{
    if(b==0)
    {
        my_longjmp(g_jmp_buf,1);
    }
    return a/b;
}


int main(void)
{
	void init (void);
	init();
    int a=10;
    int b=0;
    if(my_setjmp(g_jmp_buf)==0)
    {
        printf("start calc %d/%d\r\n",a,b);
        printf("%d/%d=%d\r\n",a,b,div__(a,b));
    }
    else
    {
        printf("something wrong\r\n");
    }
    
}

输出为:
my_setjmp输出
可以看到现象与使用库函数的现象相同。

四、总结

标准库中规定longjmp不能使setjmp返回0:

After longjmp is completed, program execution continues as if
the corresponding call to setjmp had just returned the value
specified by val. The longjmp function cannot cause setjmp to
return the value 0; if val is 0, setjmp returns the value 1.

而在下写的函数my_longjmp是可以使my_setjmp返回0的,这会造成一个奇怪的无限循环,例如:

typedef u32 my_jmp_buf[48];


extern int my_setjmp(my_jmp_buf);

extern void my_longjmp(my_jmp_buf,int);

my_jmp_buf g_jmp_buf;

int div__(int a,int b)
{
    if(b==0)
    {
        my_longjmp(g_jmp_buf,0);
    }
    return a/b;
}


int main(void)
{
	void init (void);
	init();
    int a=10;
    int b=0;
    if(my_setjmp(g_jmp_buf)==0)
    {
        printf("start calc %d/%d\r\n",a,b);
        printf("%d/%d=%d\r\n",a,b,div__(a,b));
    }
    else
    {
        printf("something wrong\r\n");
    }
    
}

输出为:
setjmp无限循环

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
setjmplongjmpC语言提供的两个非常重要的函数,目的是实现函数间的跳转和状态保存。 setjmp函数用于保存当前函数执行的上下文,同时返回一个int类型的值。这个值由setjmp函数自动生成,用于之后调用longjmp函数时确定跳转目标。 longjmp函数则用于恢复之前setjmp所保存下来的上下文,使得程序能够直接跳转到指定的目标。 下面是setjmplongjmp在mips汇编语言实现setjmp: ``` #setjmp implementation in MIPS assembly language .text .align 2 .globl setjmp .ent setjmp setjmp: addi sp, sp, -32 # allocate stack frame sw ra, 28(sp) # save return address sw fp, 24(sp) # save frame pointer addi fp, sp, 32 # set new frame pointer sw a0, 0(fp) # save jmp_buf li v0, 0 # return 0 jr ra .end setjmp ``` longjmp: ``` #longjmp implementation in MIPS assembly language .text .align 2 .globl longjmp .ent longjmp longjmp: lw fp, 24(sp) # restore frame pointer lw ra, 28(sp) # restore return address addi sp, sp, 32 # deallocate stack frame lw t0, 0(fp) # restore jmp_buf lw ra, 4(t0) # restore return address lw sp, 8(t0) # restore stack pointer lw s0, 12(t0) # restore frame pointer jr ra .end longjmp ``` 以上是setjmplongjmp在MIPS汇编语言实现。需要注意的是,这里使用了MIPS体系结构的调用约定,即函数返回值在v0寄存器,函数参数在a0-a3寄存器,被调用者保存的寄存器在栈帧。在实际使用,应根据特定的编译器和操作系统的要求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值