pop指令的功能是什么?如何借助其他指令实现其功能_在ES PLUS系列计算器上实现ROP——(四)ROM

0c68b87cd5cdc9538259f3f79dbd8b9a.png

作者:185264646

原链接:

在ES PLUS系列计算器上实现“编程”——(四)ROM的结构

来源:简书(185264646 - 简书)

本文经原作者同意转载至知乎。

前言

前文中已经讲解了CPU的相关知识,并且了解了汇编语言。然而,CASIO不会用汇编编写整个计算器程序,毕竟汇编太过底层,写起来很累。而是会使用 C 语言编写计算器中的程序,然后用编译器翻译成汇编语言,再汇编成程序写入计算器ROM。C 语言作为一种高级语言,具有很多高级特性,需要在汇编的层面予以支持,我们来讲一下这些特性在汇编中是如何实现的。读者最好能懂一些C语言知识。


程序入口

在给CPU加电的一瞬间,CPU就开始执行指令了。按照手册的说明,它会将 0:0000 中的值写入SP,将 0:0002(0:0004?,无所谓)中的值写入pc寄存器[1],然后开始执行。在C语言中,这个位置对应着程序的入口_start,它做了一些必要的工作(初始化栈、寄存器等)便会用b指令直接跳转到另一个函数。它跳往的函数则对应C中的main函数。


函数


学过C语言的人都知道,C语言中有一个叫做函数的概念,函数调用完后便会回到原来位置的下一条指令继续执行。在汇编里,我们使用 bl 指令来跳往某个函数,用 rt 指令返回,以实现函数调用的功能。在实际操作中,bl 指令先将当前的 pc 和 csr 备份到 lr 和 lcsr 中,然后将 pc 和 csr 更改为需要跳转到的地址。但是仔细想想就会发现,这样直接跳转有很大的问题,就是不能嵌套跳转。试想一下如果你跳转之后还未返回便再次跳转,之前存储的 lr 和 lcsr 就会被另外一个全新的值覆盖。因此,之后的 rt 会出现问题,造成死循环[2]。因此为了避免这种情况,只有没有调用子函数的函数才能用 rt 指令返回。但是,嵌套调用是非常重要、实用的特性,不可能去除。同时,我们也不想改变调用函数的方式,希望在 bl 之后总能返回到下一条指令。于是编译器采用了一个很巧妙的方法(实际上这个方法已经在手册中强制规定了),它将那些需要调用子函数的函数的结构做了改变,不再使用 rt 返回,而是在函数开头执行 push lr,将原来的lr值保存在栈中,在返回时也不用rt(现在lr的值已经“被破坏了”),而是直接执行 pop pc,将开头保存的 lr 转入 pc。(这用2条指令可表示为 pop lr; rt,相当于在开头时保存了 lr 的值,在返回前恢复。当然,直接用 pop pc 要更简洁)
总的来说,ROM中的函数大多数为这两种模型:

1.
 foo1:
   ……
   rt

2.
 foo2:
   push lr
   ……
   bl foo1
   ……
   pop pc


只有main函数(尽管它调用了子函数,但它在开头没有备份 lr 中的内容,因为这个函数永远不会返回)、shutdown函数(与main函数类似,都关机了还返回个锤子)、_start(严格意义上不算函数,只是程序的入口)以及极少数特殊函数不属于上面两种类型。


函数传参


C语言中,函数可以被传入任意个参数,也可以返回一个返回值。在汇编中,向函数传参可以通过寄存器进行,也可以寄存器和栈一起进行。具体采用哪种方式取决于参数的多少。
编译器首先使用寄存器进行传参,将参数按照从左到右的顺序放入寄存器中,直到用完 r0 - r3。例如 :strcpy函数的原型是

char *strcpy(char *dest, const char *src);

传参时 er0 存储了char *dest,而 er2 存储了const char *src,返回值存储在 er0 中。
如果参数太多,四个寄存器不够用,多余的参数通过栈进行传递。CPU按照从左到右的顺序将这些参数压入栈。然后再调用 bl 指令,清理栈的工作由调用者进行,即被调用函数返回后,调用者再将压入的值弹出,以保持栈平衡。例如:memset函数原型是

void * memset ( void * ptr, int value, size_t num )

(在该架构中size_t被定义为双字节无符号整数)。参数总共占用6字节,四个寄存器不够用,因此最后一个参数 num 通过栈进行传递。可以看看memzero函数调用memset的方式:

 memzero:
     push lr     ;lr入栈
     push er2    ;er2入栈以保护er2的内容
     mov er2,#0  ;er2置零,作为memset的参数num
     bl memset   ;调用memset
     pop er2     ;弹出er2,恢复er2的值
     pop pc      ;弹出pc(即之前的lr)以返回调用者


完全符合上述内容。
注意,要使栈平衡,使用 pop 指令只是方法之一。另一种方法是直接操作 sp 寄存器,例如 add sp, #imm7等。


数据


数据包括科学常数的值、各个字符的图像、各种字符串等。这些数据也是计算器正常工作所需要的。在ROM里,它们夹杂在上述的函数之间。这些数据大多都不能作为有效的指令被执行。


杂项


计算器ROM中大部分都用于存储以上两种东西,还有少部分用于存储其他一些杂项,它们只占了很少的空间,比如向量表(Vector Table)以及一些没用完的填充字节0xFF[3]。这些东西与我们目的关系很小,不用在意。如果有兴趣的话,可以查阅手册。



作者:185264646
链接:https://www.jianshu.com/p/4c7508aaa83f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

参考

  1. ^在上电复位时,PC的值来自于0:0002H(对应external reset input)
  2. ^这里的“出问题”是由于第二次调用bl时,备份寄存器组合lcsr:lr指向的位置是第二个bl后面的一条指令。执行到第一层函数的尾部时,rt把lcsr:lr复制到csr:pc,也就是跳回了第二个bl后面一条指令,因此产生死循环。
  3. ^在nX-U8/100架构中,FFh代表BReaK指令,可以重置整个CPU。这是一种通用的做法,目的是保证系统的安全和稳定
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值