目录预备知识1 .关于arm体系结构2 .关于汇编语言3 .草莓馅饼安装参考实验目的实验环境实验步骤1实验步骤1.Prologue序言2.Body函数本体3.Epilogue终端实验步骤3
预备知识1 .关于arm体系结构
ARM体系结构是高级指令集机器(高级RISC machine,以前称为Acorn指令集机器(AcornRISC Machine ) )、RISC (RISC )处理器体系结构系列由于节能的特点,其他领域也做了很多事情。 ARM处理器非常适合移动通信领域,主要设计目标符合低成本、高性能、低功耗的特性。 另一方面,超级计算机消耗大量电力,ARM也被认为是更高效的选择。 安谋控股公司开发这一框架,并授权其他公司使用,开发自主的系统单片机和系统模块(system-on-module,SoC ),以实现他们有ARM的框架
2 .汇编语言汇编语言(assembly language,英文)是用于电子计算机、微处理器、微控制器或其他可编程设备的低级语言。 汇编语言支持不同设备的机器语言指令集。 汇编语言不像许多高级语言那样可以在不同的系统平台之间移植,而是专门针对某种计算机系统结构。
3 .草莓派安装参考link
实验目的:通过该实验了解ARM汇编的基础语法。 这是学习在ARM中制定漏洞利用程序的基础。
实验环境服务器: Ubuntu IP地址:随机分配
测试文件请在实验机内下载使用。 http://tools.hetian lab.com/tools/t052.zip
启动草莓派命令:
$ QEMU-system-arm-kernel~~/QEMU _ VMs/QEMU-rpi-kernel/kernel-QEMU-4.34 -机灵的豌豆- CPU arm 1176 ras bian.img-redir TCP 3360502233603360336022-no-reboot ssh连接树派:
$ ssh pi@127.0.0.1 -p 5022 pi帐户密码raspberry。
可知实验步骤1函数利用堆栈保存局部变量,保存寄存器的状态等。 为了有条不紊地运行所有东西,函数使用堆栈帧,即堆栈中的本地化内存空间,专门用于特定函数。 堆栈框架是在函数prologue中创建的。 将帧指针(FP )设置在堆栈帧的底部,以打开分配给堆栈帧的堆栈缓存。 堆栈帧通常从末尾开始,包括返回地址(上一个LR )、上一个帧指针、要保存的寄存器、函数参数(如果允许函数大于4 )、本地变量等。 堆栈帧的实际存储内容可能不同,但前面概述的内容最常见。 最后,堆栈帧在函数运行到最后时被破坏。
以下是堆栈中堆栈帧的抽象示意图。
写c程序体验一下吧。
方案如下: pro.c :
int main () intRES=0; int a=1; int b=2; RES=max(a,b ); 返回RES; }intmax(inta,int b ) ) { do_nothing ); if(ab ) {返回b; } else { return a; }}int do_nothing () { return 0; 可以看到main调用了max。
直接编译进入调试:
break max在max下为断点、run,如下所示:
正如您可以结合c源代码看到的那样,两个参数传递给max。 这反映在汇编中,实际上是通过我在上图的红框中出现的两个指令来实现的。
查看max程序集:
可以看到c源代码中if语句的比较操作是在这里的cmp r2,r3中实现的,所以我们直接跳到这里来看看布局。
所以,运行到两个str运行后,输入nexti 9即可。
此时,FP(r11 )指向0xBEFFF234,这里是堆栈帧的底部。 堆栈上的地址(绿色地址)存储返回地址0x000010418。0xBEFFF234上方的4个字节(0xBEFFF230 )包含0xBEFFF24c的值,该值是上一帧指针的地址。 地址0xBEFFF22C中的0x1和0xBEFFF228中的0x2是执行函数max期间使用的局部变量。
实验步骤任务描述:学习ARM函数。
要理解ARM函数首先需要熟悉函数的结构和结构。 他们说:
1.Prologue序言Pologue序言的目的是将LR和R11的值存储在堆栈中,以保存程序的以前状态并打开函数的局部变量的堆栈区域。 序言的实现可能因所使用的编译器而异,但通常使用PUSH/ADD/SUB命令进行。 典型的使用方法如下。
从push {r11,LR} /*序言中,保存FP,并将lr放入堆栈*/add r11中
, sp, #0 /* 设置栈帧的底部*/sub sp, sp, #16 /* 序言的终止,在栈上分配一些缓存区,这样也为栈帧分配了一些内存空间*/ 2.Body 函数主体
函数的主体部分通常负责执行某种特殊的和特定的任务。函数的这一部分可以包含多种指令、分支(跳转)到其他函数等。典型用法如下:
mov r0,#1 /* setting up local variables (a=1).This also serves as setting up the first parameter for the function max */mov r1,#2 /* setting up local variables (b=2).This also serves as setting up the second parameter for the function max */bl max /* Calling/branchingto function max */
上面的片段设置局部变量,然后调用到另一个函数。这段代码还告诉我们,函数的参数(在这种情况下是函数max的参数)如何通过寄存器传递。在某些情况下,当有超过4个参数需要被传递时,我们将使用堆栈来存储剩余的参数。还需要注意的是,函数的结果是通过寄存器R0返回的。因此,无论函数(max)的结果究竟是什么,我们应该能够在函数返回之后,从寄存器R0中把它提取出来。还有一点要指出的是,在某些特定情况下,返回值的长度可能是64位的长度(超过32位寄存器的大小)。在这种情况下,我们可以使用R0与R1组合起来,来返回64位结果。
3.Epilogue 尾声
函数的最后一部分,尾声(epilogue),用来将程序恢复到初始状态(调用函数之前的状态),所以可以接着从函数被调用之前的位置继续往后执行。为了实现该目标,我们需要读取堆栈指针(SP)。这是通过使用帧指针寄存器(R11)作为参考并执行加法或者减法操作来完成的。当我们重新调整堆栈指针时,我们通过将它们从堆栈中弹出到各自的寄存器中来恢复先前(prologue)保存的寄存器值。POP指令可能是结尾部分的最后指令,这取决于函数的类型。但是,在恢复寄存器值之后,我们可能会使用BX指令来离开函数。尾声(Epilogue)的一个例子是这样的:
sub sp, r11, #0 /* 尾声开始,调整SP寄存器*/pop {r11, pc} /* 尾声的结束。从堆栈中恢复之前的FP,并把之前保存的LR载入PC,跳转到那里继续执行。函数的栈帧至此全部销毁完毕*/
简单的总结一下,就是:
序言部分建立了函数的运行环境;函数体部分实现函数的逻辑并将返回值存储进R0;尾声部分恢复了函数被调用之前的状态并继续运行。
实验步骤三
任务描述:函数的类型:叶和非叶。
叶函数是这样一类函数,它本身不调用/分支另一函数。非叶函数是一种函数,除了它自己的逻辑外,还得调用/分支到另一个函数。这两种函数的实现是相似的。然而,它们有一些不同之处。为了分析这些函数的差异,我们修改下之前的c源码。
源码如下所示,在pro1.c里面:
可以看出,main是非叶函数,max是叶函数,接下来编译链接生成Pro1。
使用objdump查看汇编:
直接看重点:main和max的。
叶函数和非叶函数在序言和尾声处的实现方式有差异,先来看我上图圈出的序言的差异。
非叶函数的序言需要将更多的寄存器保存在堆栈里。背后的原因在于,由于非叶函数的天然属性,在执行这样的函数期间LR被修改了,因此需要保存该寄存器的值,以便以后能够恢复。一般来说,如果必要的话,序言可以保存更多的寄存器。所以我们在上图可以看到main push了fp,lr;而max只push了fp。
再看看尾声的差异:
在max函数中可以看到,使用BL指令分支到叶函数。我们使用函数的标签作为参数来启动分支。在编译过程中,标签被替换为内存地址。在跳转到该位置之前,下一条指令的地址被保存到LR寄存器,这样我们就可以返回到函数max结束时离开的位置。
我们在max函数中看到:用于离开叶函数的BX指令以LR寄存器作为参数。如前所述,在跳转到函数max之前,BL指令将函数main的下一个指令的地址保存到LR寄存器中。由于叶函数不在执行期间改变LR寄存器的值,所以该寄存器现在可以用于返回父(main)函数。
至此,arm汇编教程已经结束。
极速赛车7码口诀器传递。在某些情况下,当有超过4个参数需要被传递时,我们将使用堆栈来存储剩余的参数。还需要注意的是,函数的结果是通过寄存器R0返回的。因此,无论函数(max)的结果究竟是什么,我们应该能够在函数返回之后,从寄存器R0中把它提取出来。还有一点要指出的是,在某些特定情况下,返回值的长度可能是64位的长度(超过32位寄存器的大小)。在这种情况下,我们可以使用R0与R1组合起来,来返回64位结果。
3.Epilogue 尾声
函数的最后一部分,尾声(epilogue),用来将程序恢复到初始状态(调用函数之前的状态),所以可以接着从函数被调用之前的位置继续往后执行。为了实现该目标,我们需要读取堆栈指针(SP)。这是通过使用帧指针寄存器(R11)作为参考并执行加法或者减法操作来完成的。当我们重新调整堆栈指针时,我们通过将它们从堆栈中弹出到各自的寄存器中来恢复先前(prologue)保存的寄存器值。POP指令可能是结尾部分的最后指令,这取决于函数的类型。但是,在恢复寄存器值之后,我们可能会使用BX指令来离开函数。尾声(Epilogue)的一个例子是这样的:
sub sp, r11, #0 /* 尾声开始,调整SP寄存器*/pop {r11, pc} /* 尾声的结束。从堆栈中恢复之前的FP,并把之前保存的LR载入PC,跳转到那里继续执行。函数的栈帧至此全部销毁完毕*/
简单的总结一下,就是:
序言部分建立了函数的运行环境;函数体部分实现函数的逻辑并将返回值存储进R0;尾声部分恢复了函数被调用之前的状态并继续运行。
实验步骤三
任务描述:函数的类型:叶和非叶。
叶函数是这样一类函数,它本身不调用/分支另一函数。非叶函数是一种函数,除了它自己的逻辑外,还得调用/分支到另一个函数。这两种函数的实现是相似的。然而,它们有一些不同之处。为了分析这些函数的差异,我们修改下之前的c源码。
源码如下所示,在pro1.c里面:
可以看出,main是非叶函数,max是叶函数,接下来编译链接生成Pro1。
使用objdump查看汇编:
直接看重点:main和max的。
叶函数和非叶函数在序言和尾声处的实现方式有差异,先来看我上图圈出的序言的差异。
非叶函数的序言需要将更多的寄存器保存在堆栈里。背后的原因在于,由于非叶函数的天然属性,在执行这样的函数期间LR被修改了,因此需要保存该寄存器的值,以便以后能够恢复。一般来说,如果必要的话,序言可以保存更多的寄存器。所以我们在上图可以看到main push了fp,lr;而max只push了fp。
再看看尾声的差异:
在max函数中可以看到,使用BL指令分支到叶函数。我们使用函数的标签作为参数来启动分支。在编译过程中,标签被替换为内存地址。在跳转到该位置之前,下一条指令的地址被保存到LR寄存器,这样我们就可以返回到函数max结束时离开的位置。
我们在max函数中看到:用于离开叶函数的BX指令以LR寄存器作为参数。如前所述,在跳转到函数max之前,BL指令将函数main的下一个指令的地址保存到LR寄存器中。由于叶函数不在执行期间改变LR寄存器的值,所以该寄存器现在可以用于返回父(main)函数。
至此,arm汇编教程已经结束。