コンパイルを無視しないでください C、C++、Javaなど、日常生活で慣れ親しんでいる高級言語と比較すると、アセンブリ言語は機械語に近いです。その一般的な操作は、値(即値、レジスタ番号、またはメモリデータ)をレジスタにロードするのと同じくらい簡単です。したがって、アセンブリにプログラムタスクを完了させるプロセスは比較的わかりにくく、高級言語は多くの機械の詳細(手順(関数)スタックフレームの初期化、手順終了時のスタックフレームの復元など)を隠しており、コードは明確で理解しやすいです。 私は 1960 年代や 1970 年代の偉大な人たちがいかにしてそれを乗り越えたかを本当に尊敬しています。彼らを崇拝しています。 100 以内の整数の合計を記述することは、十分なアセンブリ ドキュメントがあっても、しばらくの間は私を悩ませます。うんざりです。しかし、アセンブリの動作とその重要な詳細のいくつかを理解することは、コンピューターのソフトウェアとハードウェアがどのように機能するかを理解するのに役立ちます。簡単なアルゴリズムでアセンブリを紹介します。 プロセスアセンブリプレリュード プロセスは C の関数として理解できます。呼び出し元が呼び出し先を呼び出すと、システムは呼び出し先のためにスタック内にスペースを割り当てます。このスペースはスタック フレームと呼ばれます。スタックの構造はおおよそ次のようになります。 プログラム スタックは、データ構造のスタック構造と同様に、後入れ先出しの特性を持ち、下位アドレスに向かって大きくなるスタックです。レジスタ %esp (スタック ポインタ) にはスタック トップ ポインタのアドレスが格納され、レジスタ %ebp (** ポインタ) にはフレーム ポインタのアドレスが格納されます。 プログラムを実行すると、スタック ポインターを移動してプログラム スタックのスペースを増減できますが、プログラム スタックに格納されるデータのほとんどはフレーム ポインター (フレーム ポインター + オフセット) を基準とするため、フレーム ポインターは固定されます。 呼び出し元が別のプロシージャを呼び出す場合: まず、呼び出されたプロシージャにパラメータがある場合、これらのパラメータは呼び出しスタック フレーム内に構築され、呼び出し元のスタック フレームに格納されます (このため、上の図ではパラメータ n...パラメータ 1 が表示されています)。 戻りアドレスをスタックにプッシュします。戻りアドレスは、呼び出されたプロシージャが完了した後に呼び出し元が引き続き実行する必要がある命令のアドレスです。これは呼び出し元のスタック フレームの一部であり、呼び出し元のスタック フレームの末尾を形成します。 このステップでは、呼び出し先のスタック フレーム、いわゆる現在のスタック フレームに入ります。呼び出し元のプログラム スタックを後で取得できるように、呼び出し元のフレーム ポインターを保存します。 最後に、プログラムが実行されます。通常、サブ 0xNh %esp は、現在のプログラム スタックのサイズを割り当てるために使用され、一時変数にアクセスしたり、レジスタ値を一時的に保存したりするために使用されます。 呼び出し先が別のプロシージャを呼び出す場合は、最初のステップに戻るだけです。 プロセスが終了すると、スタック ポインターとフレーム ポインターが復元され、逆アセンブリで次のような内容が表示されることがよくあります。 移動 %esp,%ebp ポップ%ebp 同時に、返信先アドレスが PC に復元されます。 この時点で、呼び出し元が実行を継続するはずだった場所に戻ります。 上記のテキストは、さらに要約することができます。プロセス (関数) を逆アセンブルすると、確立 (初期化)、本体 (実行)、終了 (戻り) が行われます。以前はスタックとヒープ (データ構造ではない) を混同しやすかったので、皆さんと共有したい良い記事を見つけました: スタックとヒープの違い。何度も転送されているとのことで、よく書かれていることが分かります。 プロシージャの呼び出しと戻りは、それぞれ call と ret (戻り) を使用してアセンブリ言語で実装されます。 call メソッドと ret メソッドはあまり透過的ではありません。 呼び出しは戻りアドレスをスタックにプッシュし、PC を呼び出されたプロシージャの開始アドレスにジャンプします。 ret は call の反対で、スタックから戻りアドレスをポップし、PC をジャンプします。 詳細は画像をご覧ください: アセンブリコード形式について 最も一般的なアセンブリ コード形式は、ATT と Intel アセンブリ コードです。ATT は古いものですが、GCC と OBJDUMP のデフォルトの形式です。複数のオペランドを持つ命令の場合は、オペランドの並び順が逆になるため、考えが混乱しやすいので注意が必要です。例えば、%esp→%eaxを実装する場合、以下のような違いがあります。
書籍の影響で、レジスタの前に「%」を追加することに慣れており、ATT 形式のアセンブリ コードを好みます。 分解特有の分析 (次のプログラム スタック図では、パラメータをスタックにプッシュしてから、「パラメータ i = ?」とマークしています。これは少しわかりにくいかもしれません。「パラメータ x = ?」の方が良いでしょう :)) 簡単なプログラムがあります。どのような機能を実装しているかに関係なく、これを読めば必ず何かが得られます。与えられた C コードは次のとおりです。
Visual Studio 2008 でアセンブリ コードをデバッグして表示すると、次の逆アセンブリ コードが取得されます。わかりにくいため、次のようにコピーしました。
上記のコードでは、最初の文で fun のアドレスが間接的に記述されています。 fun を呼び出す前に準備があることがわかります。
00411445h の命令は、fun のパラメータ (この時点では i=6、上の図を思い出してください、パラメータ n - パラメータ 1) と戻りアドレスをスタックにプッシュし、その後 PC は 004110E6h にジャンプします。このとき、main のスタック フレームは次のようになります。 jmp を使用して 004113A0h にジャンプし、正式に fun 関数に入ります。まず、フレーム ポインタ、呼び出し先の保存されたレジスタ、およびその他の関連データが fun に保存されます。関数は、パラメーター x==0 の場合にのみ終了します。したがって、fun の再帰呼び出し (呼び出しではなく再帰呼び出しであることに注意してください) の前 (つまり、fun を呼び出す前) には、次のものがあります。 写真 したがって、再帰を続けると次のようになります。 x==0 になるまで、if 分岐実行ステップに入ります。
アセンブリでは、XOR ロジック演算を使用してレジスタをクリアします (アドレス 004113C4h の命令)。x==0 なので、PC は 004113E8h にジャンプし、実行が戻ります。
ここでは、保存されたレジスタ値がすべてポップアウトされ、スタックが復元され、%esp と %ebp に対する操作に注意が払われ、ret 操作が実行されて戻ります。 プログラムは実行を続けます:
ご覧のとおり、プロセッサはスタック上のメモリ (%esp+4、スタックは下位アドレスに向かって大きくなることに注意してください) を解放します。これは、呼び出し前、つまりアドレス 00411448h で、呼び出し元、つまりメイン関数が %eax パラメータをスタックにプッシュし、その後 fun が終了した後にパラメータ メモリが当然解放されるためです。考えてみてください。パラメータが多数ある場合、呼び出し前に複数のプッシュが行われ、それに応じて、呼び出し後にそれらを解放するための「add %esp n」操作が行われます。そして、%eax の値 (レジスタの使用習慣では、%eax は戻り値レジスタとしてよく使用されます) が rv に渡され、rv は自然に fun の戻り値を取得します。次:
単に x&0x01+rv を入力して、%eax に送信します (%eax は戻り値レジスタとしてよく使用されることに注意してください)。この時点で、x がどこから来るのか疑問に思うかもしれません。答えは、x は関数のパラメータであるため、呼び出し先のスタック フレームではなく、呼び出し元のスタック フレームに存在するということです。dword ptr [x] は、呼び出し元のスタック フレームにある x パラメータを読み取る必要があります。スタックを復元する時が来ました:
図に示すように、スタック フレームを復元し、ret を実行します。 Fun は再び正常に復帰し、プログラムは続行されます。
先ほどの状態に戻りましたが、データは異なります。次に、プログラムは return を実行して終了します。
スタックフレームの構造は次のとおりです。 あと 1 回残っているので、プログラムは戻った後も実行を続けます。
次に、プログラムは終了に戻ります (面倒なことはもうありません)。
この時点で、プログラムは fun の再帰プロセスを完全に終了し、メイン関数 main に戻ります。main も関数であるため、main には独自のスタック フレームもあります。下に:
0x0041144E では、%esp,4 を追加します。これは、最初にスタックにプッシュされた fun のパラメータを解放することが目的で、main 関数は 0 を返し (return 0)、排他的論理和演算 xor も使用して %eax をクリアします。 この時点で、再帰呼び出しプロセス中にプログラム スタックがどのように変化するか、および上記の関数がパラメーター i のビットの合計を計算する方法について理解が深まったと思います。 利益 私はこのような小さな再帰プログラムを見つけ、その逆アセンブルを分析することで自然に戻ったような感覚を得ることができ、「再帰呼び出し」をより明確に理解することができました。上記の分析を見ると、再帰呼び出しはアルゴリズムで問題を解決するための一般的な方法ですが、再帰回数が多いプログラムでは大量のメモリを消費します(分析のため、上記で選択した再帰回数は比較的少ないです)。 したがって、プログラムを作成するときは、時間とスペースの消費を慎重に決定する必要があります。アセンブリ コードと逆アセンブリ コードの分析を学習することで、マシンの動作をより深く理解し、より効率的なコードを記述できるようになります。 記事は少し長いですが、議論を歓迎します。 オリジナルリンク: http://www.cnblogs.com/daoluanxiaozi/archive/2012/02/08/2340530.html 【編集者のおすすめ】
|
<<: ベイジアンアルゴリズムは「アプリチケット詐欺」を打破する良い方法となるだろう
>>: JVM チューニングの概要: 新世代のガベージ コレクション アルゴリズム
この論文は浙江大学CAD&CG国家重点実験室の視覚化と視覚分析グループが特別にまとめたもので...
この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI)より許可を得て転載...
簡単に言えば、機械学習とは、非常に複雑なアルゴリズムと技術に基づいて、人間の行動を無生物、機械、また...
多くの人が人工知能技術の導入に非常に興味を持っていることは間違いありません。しかし、世界的な調査によ...
モノのインターネットは急速に「あらゆるもののインターネット」になりつつあります。ガートナーは、202...
Google DeepMind、論文を提出してください!ちょうど今、ジェフ・ディーン氏とハサビス氏は...
最近、イスラエルを拠点とするスマート物流ロボットのスタートアップであるCAJA Roboticsは、...
人工知能 (AI) とモノのインターネット (IoT) の技術トレンドが融合し始めており、業界ではこ...
新しい研究(ETH チューリッヒによる)では次のことがわかりました。大規模モデルの「人間による検索」...
ロボットの学習能力と IoT アプリケーションの相互接続性は、実りある未来を約束します。モノのインタ...
この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI)より許可を得て転載...
ChatGPTは今年9月末に音声チャットと画像認識機能を追加しました。テキスト駆動型と比較して、C...