⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 apcs 简介.htm

📁 ARM指令集
💻 HTM
📖 第 1 页 / 共 2 页
字号:
  {
     printf("main...one...two\n");
     return;
  }

  void zero(void)
  {
     return;
  }


  当它在屏幕上输出消息的时候,
  APCS 回溯结构将是:

      fp ----> two_structure
               return link
               return sp
               return fp  ----> one_structure
               ...              return link
                                return sp
                                return fp  ----> main_structure
                                ...              return link
                                                 return sp
                                                 return fp  ----> 0
                                                 ...
</PRE>所以,我们可以检查 fp 
并参看给函数‘two’的结构,它指向给函数‘one’的结构,它指向给‘main’的结构,它指向零来终结。在这种方式下,我们可以反向追溯整个程序并确定我们是如何到达当前的崩溃点的。值得指出‘zero’函数,因为它已经被执行并退出了,此时我们正在做它后面的打印,所以它曾经在回溯结构中,但现在不在了。值得指出的还有对于给定代码不太可能总是生成象上面那样的一个 
APCS 结构。原因是不调用任何其他函数的函数不要求完全的 APCS 头部。 
<P><BR>为了更细致的理解,下面是代码是 Norcroft C v4.00 为上述代码生成的...</P><PRE>        AREA |C$$code|, CODE, READONLY

        IMPORT  |__main|
|x$codeseg|
        B       |__main|

        DCB     &amp;6d,&amp;61,&amp;69,&amp;6e
        DCB     &amp;00,&amp;00,&amp;00,&amp;00
        DCD     &amp;ff000008

        IMPORT  |x$stack_overflow|
        EXPORT  one
        EXPORT  main
main
        MOV     ip, sp
        STMFD   sp!, {fp,ip,lr,pc}
        SUB     fp, ip, #4
        CMPS    sp, sl
        BLLT    |x$stack_overflow|
        BL      one
        MOV     a1, #0
        LDMEA   fp, {fp,sp,pc}^

        DCB     &amp;6f,&amp;6e,&amp;65,&amp;00
        DCD     &amp;ff000004

        EXPORT  zero
        EXPORT  two
one
        MOV     ip, sp
        STMFD   sp!, {fp,ip,lr,pc}
        SUB     fp, ip, #4
        CMPS    sp, sl
        BLLT    |x$stack_overflow|
        BL      zero
        LDMEA   fp, {fp,sp,lr}
        B       two

        IMPORT  |_printf|
two
        ADD     a1, pc, #L000060-.-8
        B       |_printf|
L000060
        DCB     &amp;6d,&amp;61,&amp;69,&amp;6e
        DCB     &amp;2e,&amp;2e,&amp;2e,&amp;6f
        DCB     &amp;6e,&amp;65,&amp;2e,&amp;2e
        DCB     &amp;2e,&amp;74,&amp;77,&amp;6f
        DCB     &amp;0a,&amp;00,&amp;00,&amp;00

zero
        MOVS    pc, lr

        AREA |C$$data|

|x$dataseg|

        END
</PRE>这个例子不遵从 32 为体系。APCS-32 规定只是简单的说明了标志不需要被保存。所以删除 LDM 的‘^’后缀,并在函数 zero 中删除 
MOVS 的‘S’后缀。则代码就与遵从 32-bit 的编译器生成的一样了。 
<P>保存代码指针包含这条设置回溯结构的指令(STMFD ...)的地址再加上 12 字节。记住,对于 26-bit 代码,你需要去除其中的 PSR 
来得到实际的代码地址。 
<P>现在我们查看刚进入函数的时候: 
<UL>
  <LI><B>pc</B> 总是包含下一个要被执行的指令的位置。 
  <LI><B>lr</B> (总是)包含着退出时要装载到 <B>pc</B> 中的值。在 26-bit 位代码中它还包含着 PSR。 
  <LI><B>sp</B> 指向当前的栈块(chunk)限制,或它的上面。这是用于复制临时数据、寄存器和类似的东西到其中的地方。在 RISC OS 
  下,你有可选择的至少 256 字节来扩展它。 
  <LI><B>fp</B> 要么是零,要么指向回溯结构的最当前的部分。 
  <LI>函数实参布置成(下面)描述的那样。 </LI></UL>
<P>  
<H2><A name=07>实际参数</A></H2>APCS 
没有定义记录、数组、和类似的格局。这样语言可以自由的定义如何进行这些活动。但是,如果你自己的实现实际上不符合 APCS 
的精神,那么将不允许来自你的编译器的代码与来自其他编译器的代码连接在一起。典型的,使用 C 语言的惯例。 
<UL>
  <LI>前 4 个整数实参(或者更少!)被装载到 a1 - a4。 
  <LI>前 4 个浮点实参(或者更少!)被装载到 f0 - f3。 
  <LI>其他任何实参(如果有的话)存储在内存中,用进入函数时紧接在 sp 的值上面的字来指向。换句话说,其余的参数被压入栈顶。所以要想简单。最好定义接受 4 
  个或更少的参数的函数。 </LI></UL>
<P>  
<H2><A name=08>函数退出</A></H2>通过把返回连接值传送到程序计数器中来退出函数,并且: 
<UL>
  <LI>如果函数返回一个小于等于一个字大小的值,则把这个值放置到 a1 中。 
  <LI>如果函数返回一个浮点值,则把它放入 f0 中。 
  <LI>sp、fp、sl、v1-v6、和 f4-f7 
  应当被恢复(如果被改动了)为包含在进入函数时它所持有的值。<BR>我测试了故意的破坏寄存器,而结果是(经常在程序完全不同的部分)出现不希望的和奇异的故障。 
  <LI>ip、lr、a2-a4、f1-f3 和入栈的这些实参可以被破坏。 </LI></UL>在 32 位模式下,不需要对 PSR 
标志进行跨越函数调用的保护。在 26 位模式下必须这样,并通过传送 lr 到 pc 中(MOVS、或 LDMFD xxx^)来暗中恢复。必须从 lr 重新装载 
N、Z、C 和 V,跨越函数保护这些标志不是足够的。 <PRE>&nbsp;
</PRE>
<H2><A name=09>建立栈回溯结构</A></H2>对于一个简单函数(固定个数的参数,不可重入),你可以用下列指令建立一个栈回溯结构: <PRE>function_name_label
        MOV     ip, sp
        STMFD   sp!, {fp,ip,lr,pc}
        SUB     fp, ip, #4
</PRE>这个片段(来自上述编译后的程序)是最基本的形式。如果你要破坏其他不可破坏的寄存器,则你应该在这个 STMFD 指令中包含它们。 
<P>下一个任务是检查栈空间。如果不需要很多空间(小于 256 字节)则你可以使用: <PRE>        CMPS    sp, sl
        BLLT    |x$stack_overflow|


这是 C 版本 4.00 处理溢出的方式。在以后的版本中,你要调用 |__rt_stkovf_split_small|。
</PRE>
<P>接着做你自己的事情... 
<P>通过下面的指令完成退出: <PRE>        LDMEA   fp, {fp,sp,pc}^</PRE>还有,如果你入栈了其他寄存器,则也在这里重新装载它们。选择这个简单的 LDM 
退出机制的原因是它比分支到一个特殊的函数退出处理器(handler)更容易和更合理。 
<P>用在回溯中的对这个协议的一个扩展是把函数名字嵌入到代码中。紧靠在函数(和 <CODE>MOV ip, sp</CODE>)的前面的应该是: <PRE>        DCD     &amp;ff0000xx</PRE>这里的‘xx’是函数名字符串的长度(包括填充和终结符)。这个字符串是字对齐、尾部填充的,并且应当被直接放置在 
DCD &amp;ff....的前面。 
<P>所以一个完整的栈回溯代码应当是: <PRE>        DCB     "my_function_name", 0, 0, 0, 0
        DCD     &amp;ff000010
my_function_name
        MOV     ip, sp
        STMFD   sp!, {fp, ip, lr, pc}
        SUB     fp, ip, #4

        CMPS    sp, sl                    ; 如果你不使用栈
        BLLT    |x$stack_overflow|        ; 则可以省略

        ...处理...

        LDMEA   fp, {fp, sp, pc}^
</PRE>要使它遵从 32-bit 体系,只须简单的省略最后一个指令的‘^’。注意你不能在一个编译的 26-bit 
代码中使用这个代码。实际上,你可以去除它,但这不是我愿意打赌的事情。&nbsp; 
<P>如果你不使用栈,并且你不需要保存任何寄存器,并且你不调用任何东西,则没有必要设置 APCS 块(但在调试阶段对跟踪问题仍是有用的)。在这种情况下你可以: <PRE>my_simple_function

        ...处理...

        MOVS    pc, lr
</PRE>(再次,对 32 位 APCS 使用 MOV 而不是 MOVS,但是不要冒险与 26 位代码连接)。 
<P>  
<H2><A name=10>APCS 标准</A></H2>总的来说,有多个版本的 APCS (实际上是 16 个)。我们只关心在 RISC OS 
上可能遇到的。 
<P><B>APCS-A</B><BR>就是 APCS-Arthur;由早期的 Arthur 所定义。它已经被废弃,原因是它有不同的寄存器定义(对于熟练的 
RISC OS 程序员它是某种异类)。它用于在 USR 模式下运行的 Arthur 应用程序。不应该使用它。 
<UL>
  <LI><CODE>sl = R13, fp = R10, ip = R11, sp = R12, lr = R14, pc = R15。</CODE> 
  <LI>PRM (p4-411) 中说“用 <CODE>r12</CODE> 作为 <CODE>sp</CODE>,而不是在体系上更自然的 
  <CODE>r13</CODE>,是历史性的并先于 Arthur 和 RISC OS 二者。” 
  <LI>栈是分段的并可按需要来扩展。 
  <LI>26-bit 程序计数器。 
  <LI>不在 FP 寄存器中传递浮点实参。 
  <LI>不可重入。标志必须被恢复。 </LI></UL>
<P><B>APCS-R</B><BR>就是 APCS-RISC OS。用于 RISC OS 应用程序在 USR 模式下进行操作;或在 SVC 
模式下的模块/处理程序。 
<UL>
  <LI><CODE>sl = R10, fp = R11, ip = R12, sp = R13, lr = R14, pc = R15。</CODE> 
  <LI>它是唯一的最通用的 APCS 版本。因为所有编译的 C 程序都使用 APCS-R。 
  <LI>显式的栈限制检查。 
  <LI>26-bit 程序计数器。 
  <LI>不在 FP 寄存器中传递浮点实参。 
  <LI>不可重入。标志必须被恢复。 </LI></UL>
<P><B>APCS-U</B><BR>就是 APCS-Unix,Acorn 的 RISCiX 使用它。它用于 RISCiX 应用程序(USR 
模式)或内核(SVC 模式)。 
<UL>
  <LI><CODE>sl = R10, fp = R11, ip = R12, sp = R13, lr = R14, pc = R15。</CODE> 
  <LI>隐式的栈限制检查(使用 sl)。 
  <LI>26-bit 程序计数器。 
  <LI>不在 FP 寄存器中传递浮点实参。 
  <LI>不可重入。标志必须被恢复。 </LI></UL>
<P><B>APCS-32</B><BR>它是 APCS-2(-R 和 -U)的一个扩展,允许 32-bit 程序计数器,并且从执行在 USR 
模式下的一个函数中退出时,允许标志不被恢复。其他事情同于 APCS-R。<BR>Acorn C 版本 5 支持生成 32-bit 代码;在用于广域调试的 32 
位工具中,它是最完整的开发发行。一个简单的测试是要求你的编译器导出汇编源码(而不是制作目标代码)。你不应该找到:<BR><CODE>MOVS PC, 
R14</CODE><BR>或者<BR><CODE>LDMFD R13!, {Rx-x, PC}<B><I>^</I></B></CODE> 
<P>  
<H2><A name=11>对编码有用的东西</A></H2>首先要考虑的是该死的 26/32 位问题。 简单的说,不转弯抹角绝对没有方法为两个版本的 
APCS 汇编同一个通用代码。但是幸运的这不是问题。APCS 标准不会突然改变。RISC OS 的 32 
位版本也不会立刻变异。所以利用这些,我们可以设计一种支持两种版本的方案。这将远远超出 APCS,对于 RISC OS 的 32 位版本你需要使用 MSR 
来处理状态和模式位,而不是使用 TEQP。许多现存的 API 实际上不需要保护标志位。所以在我们的 32 版本中可以通过把 <CODE>MOVS 
PC,...</CODE> 变成 <CODE>MOV PC,...</CODE>,和把 <CODE>LDM {...}^</CODE> 变成 <CODE>LDM 
{...}</CODE>,并重新建造来解决。objasm 汇编器(v3.00 和以后)有一个 <CODE>{CONFIG}</CODE> 变量可以是 
<CODE>26</CODE> 或 <CODE>32</CODE>。可以使用它建造宏... <PRE>my_function_name
        MOV     ip, sp
        STMFD   sp!, {fp, ip, lr, pc}
        SUB     fp, ip, #4

        ...处理...

        [ {CONFIG} = 26
          LDMEA   fp, {fp, sp, pc}^
        |
          LDMEA   fp, {fp, sp, pc}
        ]
</PRE>我未测试这个代码。它(或类似的东西)好象是保持与两个版本的 APCS 相兼容的最佳方式,也是对 RISC OS 的不同版本,26 位版本和将来的 
32 位版本的最佳方法。 
<P>测试是否处于 32 位? 如果你要求你的代码有适应性,有一个最简单的方法来确定处理器的 PC 状态: <PRE>   TEQ     PC, PC     ; 对于 32 位是 EQ;对于 26 位是 NE</PRE>
<P>使用它你可以确定:</P>
<UL>
  <LI>26 位 PC,可能是 APCS-R 或 APCS-32。 
  <LI>32 位 PC,不能 APCS-R。所有 26-bit 代码(TEQP 等)面临着失败! </LI></UL>
<P> </P>
<HR SIZE=3>
<A href="http://www.linuxforum.net/books/mhss/arm/index.html">Return to 
assembler index</A> 
<HR SIZE=3>

<ADDRESS>Copyright &copy; 2001 Richard Murray </ADDRESS></BODY></HTML>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -