如何实现CALL& RET到汇编程序?

时间:2016-07-20 15:24:10

标签: c assembly machine-code

根据这个组成的微处理器架构的指令集: https://github.com/mertyildiran/DASM

在我们用C编写的玩具汇编程序中,我们已经实现了PUSH& POP说明如下所示:

PUSH:

这基本上是4 DEC和1 ST指令的组合。

        else if (strcmp(token,"push")==0) // PUSH instruction: combination of 4 DEC and 1 ST instruction on Stack Pointer (SP)
        {
            op1 = strtok(NULL,"\n\t\r ");
            op2[0] = sp; // Let's say address of SP is 9
            printf("\n\t%s\t%s\n",strupr(token),op1);
            ch = (op2[0]-48) | ((op2[0]-48)<<3); // Prepare bitwise instruction format for DEC instructions
            program[counter]=0x7800+((ch)&0x00ff); // Decrease Stack Pointer 4 times
            printf("> %d\t%04x\n",counter,program[counter]);
            counter++;
            program[counter]=0x7800+((ch)&0x00ff); // Decrease Stack Pointer 4 times
            printf("> %d\t%04x\n",counter,program[counter]);
            counter++;
            program[counter]=0x7800+((ch)&0x00ff); // Decrease Stack Pointer 4 times
            printf("> %d\t%04x\n",counter,program[counter]);
            counter++;
            program[counter]=0x7800+((ch)&0x00ff); // Decrease Stack Pointer 4 times
            printf("> %d\t%04x\n",counter,program[counter]);
            counter++;

            ch = ((op1[0]-48) << 2) | ((op2[0]-48) << 6); // Prepare bitwise instruction format for ST instruction
            program[counter]=0x3000+((ch)&0x00ff); // Store the value in Stack
            printf("> %d\t%04x\n",counter,program[counter]);
            counter++;
        }

POP:

这基本上是1 LD和4 INC指令的组合。

        else if (strcmp(token,"pop")==0) // POP instruction: combination of 1 LD and 4 INC instructions on Stack Pointer (SP)
        {
            op1 = strtok(NULL,"\n\t\r ");
            op2[0] = sp; // Let's say address of SP is 9
            printf("\n\t%s\t%s\n",strupr(token),op1);

            ch = (op1[0]-48) | ((op2[0]-48) << 3); // Prepare bitwise instruction format for LD instruction
            program[counter]=0x2000+((ch)&0x00ff); // Store the value in Stack
            printf("> %d\t%04x\n",counter,program[counter]);
            counter++;

            ch = (op2[0]-48) | ((op2[0]-48)<<3); // Prepare bitwise instruction format for INC instructions
            program[counter]=0x7700+((ch)&0x00ff); // Decrease Stack Pointer 4 times
            printf("> %d\t%04x\n",counter,program[counter]);
            counter++;
            program[counter]=0x7700+((ch)&0x00ff); // Decrease Stack Pointer 4 times
            printf("> %d\t%04x\n",counter,program[counter]);
            counter++;
            program[counter]=0x7700+((ch)&0x00ff); // Decrease Stack Pointer 4 times
            printf("> %d\t%04x\n",counter,program[counter]);
            counter++;
            program[counter]=0x7700+((ch)&0x00ff); // Decrease Stack Pointer 4 times
            printf("> %d\t%04x\n",counter,program[counter]);
            counter++;
        }

所以我的问题是我们如何实现CALL&amp;使用堆栈的RET指令?

我知道CALL指令会将PC的当前状态存储在堆栈中,这样程序就可以使用RET指令返回它所在的位置。但它引导我两个子问题:

  1. 执行CALL后,如果在子程序中,程序如何获取先前存储在堆栈中的地址,则指令将某些内容推送到堆栈或覆盖CALL的返回地址。
  2. 我们如何在机器代码级别将相关标签的地址传递给CALL?在我们的汇编JMP&amp;由于同样的原因,jZ指令也没有完成。
  3. 如果你想看看整个图片:https://github.com/mertyildiran/DASM/blob/master/assembler.c

    对于2. subquestion(JMP / CALL),您可以通过此示例解释它,可能jmp lpp行:

    .data
         count: 60
         array: .space 10
         char: 0xfe
    .code
            ldi 0 count
            ld  0 0
            ldi 1 array
            ldi 2 char
            ld  2 2
    lpp     st 1 2
            inc 1
            dec 0
            jz loop
            jmp lpp
    loop    sub 1 2 3
    lp1     jmp lp1  
    

1 个答案:

答案 0 :(得分:3)

&#34;汇编程序如何将标签地址传递给JMP指令?&#34;

从机器/ CPU架构中可以明显看出它是如何解码指令的。我对你的问题感到困惑,是否已经将最终的CPU作为最终版本(然后它应该描述,如何加载pc寄存器=这基本上是什么跳转,加上它可能是有条件的),或者你在一个中做两个项目,同时创建硬件机器规格和汇编程序。而且我懒得再读它,所以我只会告诉你现实世界的常用方法。

ARM - 类RISC指令集,固定操作码大小:

在16b(拇指)和32b模式下,操作码的前几位指定指令(B, BL, BLX,B是纯跳转,BL类似于CALL,但不使用堆栈,&#34;链接& #34;寄存器用于存储返回地址),其余位指定寄存器保存目标地址(因此调用函数foo可以执行load r0,foo bl r0),或者立即值,相对于当前PC

这意味着,在16b模式下,您可以无条件地跳转+ -2kiB,或者有条件地跳转-252+258(条件变量在其他位中进行编码,从中立即取出一些)。< / p>

这有时会导致高级编译器使用寄存器变体,或者跳到足够接近另一个跳跃指令的情况,这种情况会进一步跳跃。

在32b模式下,为立即保留的剩余位为您提供更好的范围,对于所有变体约为+ -32MiB。 (还有一个模式,32b&#34;拇指&#34;它有一个不同的编码,但在这个例子中无关紧要。)

有趣的是,地址中的位0确实指定指令是否处于拇指模式或全32b模式,因为所有地址必须在ARM上对齐,因此跳转到地址0x00000001会通过CPU切换跳转到0x00000000到拇指模式。

x86 - 类似CISC的历史延伸超出了良好的品味

这个有几乎所有可能的变体(除了那个你真正需要的那个。你在过去3个月写过的。)指令编码具有可变长度,因此指令使用尽可能多的字节,由英特尔决定是否需要。

  • 相对跳跃(单字节短-128 ... +127) - 检查
  • 绝对跳转到地址(编码为jmp操作码字节,然后根据需要编码地址的字节数)
  • 注册跳转(到存储在寄存器中的地址)
  • 条件相对跳转(不仅仅是&#34;扩展&#34; jmp编码,如ARM案例,但具体的操作码)
  • loopjcxz/jecxz等额外内容,甚至还有一些我甚至忘了。

因此可以归结为将值加载到某个寄存器中,或者将立即编码到指令操作码中,然后将其视为绝对地址(例如,在32b平台上,您设法为地址编码25位) ,允许你处理32MiB的RAM;而不是4GiB),或作为相对地址(25bits =&gt; pc + - 16Mi)。

附录,这可能是你问题的一部分? 喜欢如何&#34;知道&#34;什么地址将在未来的标签上?

大多数汇编程序是双遍的,因此它们首先生成指令操作码(并且可以计算源的每个部分的长度),并收集符号表中的所有符号,然后根据操作码定义所有地址符号长度和org指令。然后在第二遍中,用特定的符号值填充操作码中的所有立即值。

这也表明,为什么双程组装者不能自动处理jmp rel8/rel16(由8b或16b立即定义的相对跳跃),但程序员必须指定他想要使用哪一个。 (或者使用多次传递汇编程序,它将首先尝试rel8,当失败时,它将使用rel16重新编码jmp并移动+重新编译超出该点的所有内容。)

查看push编码的示例来源,我觉得有一些工作可以帮助您改变汇编程序的工作方式......(无论如何,它都很丑陋,就像创建一样操作码和屏幕上的打印输出 - 不要再犹豫再写一次,我敢打赌它会变得更干净,更简单。)

编辑:最后,我可能更好地理解那个汇编程序。

所以programuint16_t[],对吧? (应该是问题的一部分)。

指令机器代码也是固定大小,16b。

但是你的counter计算16b字......那么目标架构的寻址方式是什么?

地址0存储16b字,地址1存储下一个16b字? (并且它没有重叠,如在x86,其中地址以8b字节计数=&gt;在这种情况下,第一个指令字将位于地址01,第二个字将是在地址23。确保您的counter遵循正确的寻址方案(或者在定义标签符号的值时必须转换它)。

你的asm正在产生类似0x0000 0x7802的输出:地址0,字7802,用params 2做一些指令78(不清楚的字节顺序!要么你很幸运在编译主机上有相同的一个,或者你会以错误的机器代码结束,每个单词中包含交换的字节),下一个操作码0x0001 0x7803,......等......

所以看起来你已经修复了16b大小的指令编码,不确定jmp是否会像存储/加载指令一样吃掉整个8b,或者那个是特殊的,保留更多的位用于立即编码。如果只有其他字节可用,则只有有意义的使用方式是签名8b -128..+127相对跳转。

如果寻址适用于16b字,那么你可以有效地向后/向前跳转-128 .. + 127条指令。如果寻址适用于8b字节,则范围仅为-64 .. + 63指令。很难说,因为我没有弄清楚目标平台的任何细节(你应该添加一些链接或其他东西,至少是如何编码jmp以及如何映射内存)。