私のタイトルから示唆されているように、C配列を割り当て、それに応じてESPレジスタを指すことでスタックのように動作させることができるかどうかに興味があります。
いくつかのコード例...
void foo(){
int x = 99;
int y = 89;
return;
}
char myStack[1024];
void main(){
int main_num = 66;
__asm volatile("movl %0, %%esp": : "rm" (&myStack+1)); //Move ESP to the end of the array
foo();
return 0;
}
このコードの背後にある考え方は、最初にESPをmyStack配列の最後にポイントし(スタックは下位アドレスに向かって成長するため)、次にfoo()を呼び出して、特にfoo()用に別のスタックを作成することです。この新しいスタック(C配列)に格納されているリターンアドレスとローカル変数。
そのようなアプローチも可能だろうか?もしそうなら、それを達成する方法は?
上記のコードを実装しようとしているときに、スタックに関する情報を確認するためにGDBを実行しました(例:GDBのinfo stackコマンド)。「スタックなし」が表示され続けました。これは、スタックポインタが深淵に送信されたことを意味します。
PS:私はこれをカーネルレベルのコードとして実装しています
たぶん、これはおもちゃの実験でのみ機能する巨大な危険なハックとして機能する可能性があります。新しいスタックを設定する場合は、C関数を呼び出す前に、手書きのasmで設定してください。
このハックはへの呼び出しで機能する可能性がありますがfoo()
、どうreturn 0;
ですか?コンパイラで生成されたコードは、現在の%espからリターンアドレスをポップしようとします。
(または、最適化が無効になっている場合は、保存されたEBPをポップする前にleave
どのセットを使用しESP = EBP
ます。これにより、初期スタックに戻ります。したがって、動作は最適化レベルによって異なります。これは望ましくありません。)
GDBを使用してコードをシングルステップし、実際にreg値が変化するのを監視します(例:layout reg
。
しかし、はい、&myStack + 1
は配列の最後の1つのアドレスでありmovl $myStack+1024, %eax
、asmステートメントのセットアップとして生成されます(コンパイラがオペランドにそのレジスタを選択したため、テンプレート内で%0
展開さ%eax
れ"rm"
ます。あなたはしませんでした即時定数のオプションを指定するか、)を使用してそれを実行しmovl $myStack+1024, %esp
ます。
https://godbolt.org/z/Nz6DgAは、それが「機能」し、最適化を有効にしてretに到達するとすぐにクラッシュすることを示しています。これは、pop
ESPがの最後の1つを指しているためですmyStack
。
私は現在、カーネルレベルでのスレッドの実装をいじっています。したがって、スレッドごとに個別のスタックを割り当て、それらの間でシフトするというアイデアがあります。
特にmain
実際return
に必要な場合は、はい、スタックを設定してから何かに使用する必要がありますcall
。そうしないと、その最終的なリターンアドレスが間違ったスタックになります。
たとえば、Linuxでは、pthreadライブラリは、に割り当てて、mmap()
そのスタックアドレスをオペランドとしてに渡すことにより、新しいスタックで新しいスレッドを作成しclone()
ます。したがって、新しいスレッドは親のスタックを使用することはなく、独自のスタックのみを使用します。新しいタスクのカーネル側のスレッドスタックの作成も同様だと思います。あなたは、新しいスタックを割り当て、その後、新しいスレッドコンテキストのためにそれを使用します。
新しいスレッドで呼び出された最初の関数が実際にスレッド出口/クリーンアップ関数に戻るように、先頭に「リターンアドレス」を置くことができます。おそらくそのためのいくつかのasmで。または、実際のスレッドエントリポイントを返さない関数にします。代わりに、スレッドコンテキストをクリーンアップして別のスレッドに切り替えるか、スケジューラなどを呼び出します。
それは私が目指していることの一種であり、メインのスタックではなく独自のスタックを使用するスレッドを持っています。ただし、ページングを有効にしていないため、今のところスタック作成をできるだけ簡単に実装したいと思います(したがって、C配列は簡単な解決策のように見えました)
残念ながら、これは単純すぎて実際には機能しません。
はい、スレッドスタックにC配列を使用できます(余分なスレッドが1つしかない場合は...)、問題はそれに切り替える方法です。
ある時点で、あるレジスタコンテキストを保存して別のレジスタコンテキストをロードするコンテキストスイッチ関数を作成する必要があります。(たとえば、Googleは、Stack Overflowでいくつか見つけることができ、おそらくhttps://www.osdev.org/で何かを見つけることができます。)
スタックポインタがスレッドスタックの最上位を指し、EIPがスレッドエントリポイントを指すように、メモリ内に新しいスレッドコンテキスト構造体を作成します。コンテキストスイッチ関数を呼び出して、その新しいコンテキストに切り替えます。
CコンパイラのPOVからは、コンテキストスイッチ関数は他の関数呼び出しと同じように見えます。最終的には戻り、グローバルに到達可能なCオブジェクトが変更された可能性があります。一時的にESPが別の場所を指していることは問題ではありません。「他の関数呼び出しと同様に」には、call-clobberedレジスタのクラバリングが含まれるため、EAX / ECX / EDXを保存/復元する必要はありません。コンテキストスイッチ関数の呼び出し元は、すでにそれらが破棄されていると想定しています。
通常、インラインasmではなくasmで手書きする必要があります。インラインasmからESPを変更することは危険を伴い、GCCでサポートされていないものとして公式に文書化されています。
これは、コンパイラが、asmステートメントの後のスタックポインタの値が、ステートメントへのエントリ時と同じである必要があるためです。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加