使用stack为程序8086中的过程获取参数/数组地址

时间:2017-07-08 11:44:37

标签: assembly x86 parameter-passing x86-16

我有一个任务,我告诉我写一个程序,并且有3个阵列'使用堆栈给我的地址,但是他们没有告诉我它们是如何放入堆栈的。有没有办法让我知道如何在程序中检索这些地址?

1 个答案:

答案 0 :(得分:2)

您需要知道的第一件事是传递参数的 order 。没有它,您将能够获得三个值,但您不会知道如何解释它们。但是,让我们继续......

因为8086是16位微处理器,所以指针的长度为16位。在汇编语言术语中,这将是WORD大小,或2个字节。

通常,当调用者想要将参数传递给函数时,它会在PUSH函数之前CALL将它们放到堆栈中。所以,这里有一个例子,说明如何将三个WORD大小的值压入堆栈,然后调用foo函数:

push  3
push  2
push  1
call  foo
add   sp, 6     ; clean up stack after call, adding 6 because we pushed 3 2-byte values
                ; (could also do POP+POP+POP, but that trashes a register and is slower)

非常简单,对吧?请注意,我显然已按相反的顺序推送参数。这样,当它们在foo函数中被检索时,它们看起来将是正确的顺序(1,2,3)。当使用汇编语言调用函数时,这是传统的,这是C编译器在生成调用函数的代码时将始终执行的操作,因此您可以假设(如果没有另行说明)这就是你的参数传递方式。

现在,关于你的问题 - foo函数如何从堆栈中检索这些参数?

首先,这里是foo函数的启动方式(这称为 prologue 代码,它基本上用于需要与堆栈交互的每个函数的顶部):

foo:
    push  bp       ; save original value of BP
    mov   bp, sp   ; copy current value of SP (stack pointer) into BP (base pointer)
    ...

要检索第一个参数(并且,比如在AX中加载它),您将执行以下操作:

mov  ax, WORD PTR [bp+4]

为什么bp+4?好吧,在序言代码之后,bp包含指向堆栈顶部的指针。什么在堆栈上?请考虑以下图表:

Low            |====================|
addresses      | Unused space       |
               |                    |
               |====================|    ← SP points here
   ↑           | Function's         |
   ↑           | local variables    |
   ↑           |                    |    ↑ BP - x
direction      |--------------------|    ← BP points here
of stack       | Original/saved BP  |    ↓ BP + x
growth         |--------------------|
   ↑           | Return pointer     |
   ↑           |--------------------|
   ↑           | Function's         |
               | parameters         |
               |                    |
               |====================|
               | Parent             |
               | function's data    |
               |====================|
               | Grandparent        |
High           | function's data    |
addresses      |====================|

从该图中,您可以看到:

  • BP+0 ==您在BP函数的序言中保存的foo的原始值,将其推送到堆栈的顶部(push bp
  • BP+2 ==返回指针(由CALL隐式推送,RET结束时foo将隐式使用该指针
  • BP+4 ==第一个由调用者在堆栈中传递给foo函数的参数
  • BP+6 ==第二个参数

同样,因为你在16位8086上,所有这些值都是WORD大小的,所以它们每个都是2个字节。

如果已在堆栈上分配空间以在{{1}内部存储一些局部变量,则将使用“函数的局部变量”部分(偏离bp)功能。但是为了简单起见,我们会在这个答案中忽略它。

所以,让我们把它们放在一起,看看foo的可能实现:

foo

请注意在执行“有趣”操作后出现的结尾代码 - 特别是foo: push bp ; save original value of BP mov bp, sp ; set base pointer to top of stack mov ax, WORD PTR [bp+4] ; get first parameter mov bx, WORD PTR [bp+6] ; get second parameter mov cx, WORD PTR [bp+8] ; get third parameter ; do something interesting leave ret 指令。这颠倒了我们之前看到的序言代码。 LEAVE相当于:

LEAVE

但字节更少,因此它通常用作8086上的优化(不是在较新的处理器上,因为它比至少386及更高版本的扩展形式慢)。

这就是你从堆栈中访问参数的方法!

还有一件事。您说这些参数是指针(地址),因此您需要确保了解如何在汇编语言中取消引用指针。当我们之前加载寄存器时,我们加载的是指针。如果你想获得第一个数组的第一个元素,你可以这样做:

mov  sp, bp
pop  bp

请注意,在16位模式下,您非常restricted to certain addressing modes。您可以用来访问内存的唯一寄存器是mov bx, WORD PTR [bp+4] ; get first parameter (pointer to array) mov ax, WORD PTR [bx] ; dereference pointer to array, ; putting value of first element in BX BPBXSI。这就是为什么我们必须将堆栈指针放在DI中(而不是直接从BP使用它),为什么在最后一个例子中,我将指针加载到SP所以我可以从BX加载。