2016-05-29 4 views
6

アセンブリで、画面上に正方形を描画するサブプログラムを作成しようとしています。私はC++で行うのと同じように、サブプログラムにパラメータを渡すことはできないと思うので、スタックを使用してパラメータを格納してアクセスすることができました(共通データレジスタを使用できません。パス)。x86アセンブリ:スタックを介して関数にパラメータを渡す

問題は、現在の「プログラム」のアドレスにcallコマンドを使用するとスタックに保存されるため、「ret」コマンドを使用したときにどこの場所を知るかという問題です復帰する。しかし、スタックに何かを格納してから関数を呼び出すと、スタックの最上部にあるアドレスのどこかに保存してから、パラメータを安全にポップする必要があります。その後、コードが終了し、 "ret"を呼び出す前に、私は住所を押し戻す必要があります。

私は正しいですか?そして、もしそうなら、どこにアドレスを格納することができますか(私はアドレスがAXやBXあるいは他のデータレジスタに収まるように1バイト長ではないと思います)。これを行うためにIPを使用することはできますか(これは他の目的に使用されています)。

[BITS 16] 
.... 
main: 
    mov ax,100b 
    push ax 
    call rectangle ;??--pushes on the stack the current address? 

jml $ 

rectangle: 
    pop ax ;??--this is the addres of main right(where the call was made)? 
    pop bx ;??--this is the real 100b, right? 
    .... 
    push ax 
ret ;-uses the address saved in stack 

答えて

8

は通常、あなたはパラメータや地元の人々を参照するために(16ビット、ebpビット32上にbp)ベースポインタを使用します。

は、これは私が想像するものです。

基本的な考え方は、関数の実行中に関数が "固定参照点"として呼び出されたときに、スタックポインタをベースポインタの中に保存する関数を入力するたびに呼び出されることです。このスキーマでは、[ebp-something]は通常ローカル、[ebp+something]はパラメータです。あなたはこのように行うことができます呼び出し規約典型的な32ビット、呼び出し先のクリーンアップをトランスポーズ

、発信者:

push param1 
push param2 
call subroutine 

サブルーチン:これは、ことを除いて、うまくいく

push bp  ; save old base pointer 
mov bp,sp  ; use the current stack pointer as new base pointer 
; now the situation of the stack is 
; bp+0 => old base pointer 
; bp+2 => return address 
; bp+4 => param2 
; bp+6 => param1 
mov ax,[bp+4] ; that's param2 
mov bx,[bp+6] ; that's param1 
; ... do your stuff, use the stack all you want, 
; just make sure that by when we get here push/pop have balanced out 
pop bp  ; restore old base pointer 
ret 4   ; return, popping the extra 4 bytes of the arguments in the process 
+0

サブプログラムではBPが使用されていませんが、どのアドレスを知っていますか? '16ビットBPレジスタは、主にサブルーチンに渡されるパラメータ変数の参照に役立ちます。 SSレジスタ内のアドレスはBPのオフセットと組み合わされ、パラメータの位置を取得します。 BPは特別なアドレッシングのためのベースレジスタとしてDIとSIを組み合わせることもできます。 (http://www.tutorialspoint.com/assembly_programming/assembly_registers.htm) –

+0

[BP + 6]はなぜですか?私は[]がアドレスを参照するときに使用されることを知っています。もしBPがサブルーチンのアドレスであれば、[BP + 6]はサブルーチンの右側のコマンドを指しますか? (私はちょっと新しいので、私は間違っている可能性があります)。そしてなぜ6? (私が知っているのは、例えば、varや他のものを指している次のアドレスを意味しているということです) –

+0

実際には..2は2バイトを意味しますか?バイト単位の長さであれば、アドレス全体にジャンプするということです。 –

3

呼び出し元の視点から、関数はspを変更します。 32bitほとんどの呼び出し規則では、関数はeax/ecx/edxを変更することのみが許され、それらを使用したい場合は他のregsを保存/復元する必要があります。私は16bitが似ていると仮定します。 (もちろん、asmではあなたが好きなカスタム呼び出し規約を使って関数を書くことができます)。

呼び出し規約によっては、呼び出し元がプッシュしたargsをポップすることが予想されるため、実際にはこのように動作します。マッテオの回答のret 4はそれを行います。 (規則、および他の良いリンクのトンを呼び出す上の情報のためタグのwikiを参照してください。)


それは、それが正常に使用されていない理由である、物事を行うための最善の方法スーパー奇妙な、とはありません。 最大の問題は、ランダムアクセスではなく、パラメータにアクセスできることです。最初の6つまでのargsにアクセスできるのは、レジスタを使い果たしてポップするためです。

リターンアドレスを保持するレジスタも結び付けています。 x86(x86-64より前)はレジスタが非常に少ないので、これは本当に悪いです。あなたはレジスタに他の関数argをポップした後にリターンアドレスをプッシュすることができます。

jmp axは、技術的にpush/retの代わりに動作しますが、これは将来のret指示が遅く、リターンアドレス予測を破ります。それは安いですし、あなたにスタックへのランダムアクセスを与えるのでpush bp/mov bp, spとスタックフレームを作る


しかし、いずれにせよ、は普遍16ビットコードで使用されています。 ([sp +/- constant]は16ビットで有効なアドレッシングモードではありませんが(32ビットと64ビット)

32ビットと64ビットのコードでは、命令を浪費してebpを縛るのではなく、[esp + 8]などのアドレッシングモードを使用するのが普通です(がデフォルトです)。つまり、同じデータに対して正しいオフセットを計算するために、espへの変更を追跡する必要があります。実際のコードでは、効率を犠牲にしてもCコンパイラを使うだけなので、最も効率的なのは明らかです。

関連する問題