2

用語が正しくない場合はご容赦ください。

x86 gnu c ベースのシステムを実装しようとしていますが、コマンドライン引数をプログラムに渡すことができます。プログラム内でそれらにアクセスすることと混同しないでください。実際には、実行をユーザー プログラムに渡す前にスタックを設定します。

私が収集したものから、argc と argv はスタックにプッシュされますが、何かが欠けているのはその構築プロセスです。以下は、別のプログラムを実行する方法です。

__asm__ __volatile__ ("pushl %%ds\n" /* save data and extra segment registers */
        "pushl %%es\n" 
        "movl %%esp, %%ebx\n" 
        "movl %%ebx, oldsp\n" 
        "movl %%ss, %%ebx\n" 
        "movl %%ebx, oldss\n" 
        "movl %0, %%ds\n"   /* set data segment to new user base */

        "movl %0, %%ss\n" 
        "movl $0xfff0, %%ebx\n" /* start of the new user stack pointer */
        "movl %%ebx, %%esp\n"
        "movl %2, %%eax\n"  /* place i into eax - push it onto the stack*/
        "pushl %%eax\n"
        "pushl %%eax\n"
        "lcallw  *%%fs:(%1)\n" 
        "movl %%fs:oldss, %%ebx\n" 
        "movl %%ebx, %%ss\n" 
        "movl %%fs:oldsp, %%ebx\n" 
        "movl %%ebx, %%esp\n" 
        "popl  %%es\n"  /* restore old segment registers */
        "popl  %%ds\n"
        :
        :"a" (userbase), "d" (&useg), "r" (i)
        :"%ebx", "eax", "memory");  /* prevents gcc from optimizing useg away*/

スタック ポインターが更新された後、値をスタックにプッシュできるという印象を受けました。スタックにプッシュした値を取得していないことは明らかなので、正しい方法で行っているかどうかさえよくわかりません。

以下は、argc print を画面に読み込もうとした単純なテスト プログラムです。

.file    "prog3.c"
#APP
.code16gcc

call main

lretw

.section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string    "\r\nstring: %u"
#NO_APP
.section    .text.startup,"ax",@progbits
.globl    main
.type    main, @function
main:
.LFB0:
.cfi_startproc
pushl    %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl    %esp, %ebp
.cfi_def_cfa_register 5
andl    $-16, %esp
subl    $16, %esp
movl    8(%ebp), %eax
movl    %eax, 4(%esp)
movl    $.LC0, (%esp)
call    printf
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size    main, .-main
.ident    "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section    .note.GNU-stack,"",@progbits

同様のプロセスである可能性があると考えて、スタックが関数呼び出しに対してどのように準備されているかを見てきましたが、まだ切断されています。何かご意見は?

4

1 に答える 1

0

プログラムが引数を読み取ろうとしたときのスタックと、すべてがそこに到達した方法を見てみましょう。

mov    %ebx, %esp # 0xfff0
push   %eax       # 0xffec i
push   %eax       # 0xffe8 i
lcallw *%fs:useg  # 0xffe4 return address 1
call   main       # 0xffe0 return address 2
push   %ebp       # 0xffdc old ebp
mov    %esp, %ebp # ebp = 0xffdc

GCC は、関数の引数がスタックの戻りアドレスのすぐ上に配置されることを想定しています。これは、最初の引数が にあることを意味しebp + 8ます。ただし、ここでわかるように、その位置の値はlcallw命令からの戻りアドレスであるため、メイン関数は最初の引数としてそれを認識します。そこに配置した引数を取得するには、呼び出すユーザーコードmain( と呼びましょうstart) で 2 つの戻りアドレス間でそれをコピーするか、そのコードでスタックのサイズをまったく変更しない必要があります。

start現在、スタック上の引数の長さは一定で小さいため、単純にデータをコピーすることは難しくありません。2 つの値を再プッシュする必要があるだけなので、次のようstartになります。

push   8(%esp) # Copy the high value
push   8(%esp) # Copy the low value
call   main
add    $8,%esp
lretw

より長い引数または一定でない引数の場合、コピーする量を決定し、そのすべてをコピーする必要があるため、これは遅くなります。

startを呼び出す前にスタックのサイズを変更しない場合は、コピーせずにこれを行うことができますmain。スタックに独自の戻りアドレスを配置する必要があるため、これは、他の戻りアドレスを別の場所に移動する必要があることを意味します。これを行う最善の方法は、通常保存されているレジスタの 1 つが保存されていないとシステムに想定させることです。これにより、startそこに戻りアドレスを保存できます。ebpプログラムを実行するコードで、esi、またはを使用していないように見えるediので、インラインアセンブリの破棄されたレジスタのリストに追加するだけでそれらのいずれかを使用できますstart。そこに最初の返送先住所。

pop    %esi  # Pop first return address
call   main
push   %esi  # Restore first return address
lretw
于 2012-11-01T03:46:40.040 に答える