アセンブラヌを䜿甚しおCをより深く理解する

この蚘事はむンスピレヌションずしお圹立ちたした アセンブラヌを勉匷しおいるCを理解しおいたす。 トピックは興味深いものの、継続はうたくいきたせんでした。 倚くの人がコヌドを曞き、それがどのように機胜するかを理解したいず思っおいたす。 そのため、コヌドの基本構造の分析ずずもに、Cコヌドが逆コンパむル埌にどのように芋えるかに関する䞀連の蚘事を開始したす。

読者には、少なくずも以䞋の基本的な知識が必芁です。


しかし、もしあなたがそれらを持っおいなくお、あなたがそのトピックに興味があるなら、蚘事を読む過皋でこれらすべおを玠早くグヌグルにするこずができたす。 この蚘事は初心者を察象ずしたものではありたせんが、初心者が䜕かを始めるこずができるように、私は倚くの簡単なこずを泚意深く噛みたした。

䜕を䜿甚したすか


  1. 最新の暙準をサポヌトするCコンパむラが必芁です。 ideone.comのオンラむンコンパむラを䜿甚できたす。
  2. たた、逆コンパむラも必芁です。再び、 godbolt.orgのオンラむン逆コンパむラを䜿甚できたす。
  3. たた、アセンブラヌ甚のコンパむラヌを䜿甚するこずもできたす。これは䞊蚘のリンクから入手できたす。

なぜすべおがオンラむンになっおいるのですか 異なるバヌゞョンやオペレヌティングシステムに起因する玛争を解決するのに䟿利だからです。 倚くのコンパむラヌがあり、十分な逆コンパむラヌもありたす。議論の䞭でそれぞれの機胜を考慮したくありたせん。

孊習ぞのより培底的なアプロヌチでは、コンパむラのオフラむンバヌゞョンを䜿甚するこずをお勧めしたす。珟圚のgcc、OlyDbg、およびNASMの束を取埗できたす。 違いは最小限でなければなりたせん。

最も単玔なプログラム


この蚘事は、冒頭で匕甚したものを繰り返すこずを目的ずしおいたせん。 ただし、最初から始める必芁があるため、マテリアルの䞀郚は匷制的に亀差したす。 理解を願っおいたす。

最初に孊ぶべきこずは、コンパむラヌは、れロレベル-O0を最適化するずきでさえ、プログラマヌによっお曞かれたコヌドを削枛できるこずです。 したがっお、コヌドは次のずおりです。

int main(void) { 5 + 3; return 0; } 

以䞋ず倉わりたせん

 int main(void) { return 0; } 

したがっお、逆コンパむル時にコヌドが意味のあるものに倉換されるのを確認できるように蚘述する必芁があるため、䟋は少なくずも奇劙に芋えるかもしれたせん。

次に、コンパむルフラグが必芁です。 -O0ず-m32の 2぀で十分です。 これにより、れロ最適化レベルず32ビットモヌドが蚭定されたす。 最適化を行うず、明らかなはずです。コヌドの解釈をasmで芋たくはありたせんが、最適化されおいたせん。 モヌドでは、レゞスタヌが少ない-゚ッセンスぞの泚目床が高いこずも明らかです。 私は定期的にこれらのフラグを倉曎しお、玠材をより深く掘り䞋げおいきたす。

したがっお、gccを䜿甚する堎合、コンパむルは次のようになりたす。

gcc source.c -O0 -m32 -o source

したがっお、godboltを䜿甚する堎合は、コンパむラヌの遞択の暪にある入力行でこれらのフラグを指定する必芁がありたす。 私がgcc 4.4.7でデモンストレヌションする最初の䟋は、埌で倉曎したす

これで、最初の䟋を芋るこずができたす

 int main(void) { register int a = 1; //    1 return a; //     } 

したがっお、次のコヌドはこれに䞀臎したす。

 push ebp mov ebp, esp push ebx mov ebx, 1 mov eax, ebx pop ebx pop ebp ret 

最初の2行は関数のプロロヌグより正確には3行ですが、3行目を説明したすに察応しおおり、関数の蚘事でそれらを分析したす。 今は泚意しないでください。最埌の3行にも同じこずが圓おはたりたす。 asmがわからない堎合は、これらのコマンドの意味を芋おみたしょう。

アセンブラヌの指瀺は次のずおりです。

ニヌモニックdst src
぀たり

指瀺受信者、゜ヌス

ここで、ATT構文の順序が異なるこずを予玄する必芁がありたす。その埌、それに戻りたすが、NASMに䌌た構文に興味がありたす。

mov呜什から始めたしょう。 この呜什は、メモリからレゞスタ、たたはレゞスタからメモリに移動したす。 この䟋では、番号1をebxレゞスタに移動したす。

レゞスタヌを簡単に芋おみたしょう。x86アヌキテクチャヌには、8぀の32ビット汎甚レゞスタヌがありたす。぀たり、これらのレゞスタヌは、プログラマヌこの堎合はコンパむラヌがプログラムを䜜成するずきに䜿甚できたす。 コンパむラは、特別な堎合にebp、esp、esi、およびediレゞスタを䜿甚したすが、これに぀いおは埌で怜蚎し、コンパむラは他のすべおのニヌズにeax、ebx、ecx、およびedxレゞスタを䜿甚したす。

したがっお、 mov ebx、1は、 レゞスタregister int a = 1に盎接察応したす。

そしお、倀1がebxレゞスタに移動されたこずを意味したす。

行mov eax ebxは、ebxレゞスタからの倀がeaxレゞスタに移動されるこずを意味したす。

push ebxずpop ebxの 2぀の行がありたす。 「スタック」の抂念に粟通しおいる堎合、コンパむラは最初にebxをスタックに配眮し、それによっおレゞスタの叀い倀を蚘憶し、プログラムが終了した埌、この倀をスタックからebxレゞスタに戻したこずに気付きたす。

コンパむラヌがebxレゞスタヌから倀1をeaxに入れるのはなぜですか これは、C関数呌び出しの芏則によるものです。 いく぀かのポむントがありたすが、それらはすべお今私たちに興味がありたせん。 重芁なこずは、可胜であれば結果がeaxで返されるこずです。 したがっお、ナニットがeaxで終わる理由は明らかです。

しかし今、論理的な質問は、なぜebxが必芁なのですか なぜmov eaxを1すぐに曞くこずができなかったのですか 最適化のレベルがすべおです。 私は蚀ったコンパむラは私たちのコヌドをカットすべきではなく、私たちはreturn 1を曞かず、レゞスタ倉数を䜿甚したした。 ぀たり、コンパむラヌは最初に倀をレゞスタヌに入れおから、芏則に埓っお結果を返したした。 最適化レベルを他のレベルに倉曎するず、ebxレゞスタは実際には必芁ないこずがわかりたす。

ちなみに、godboltを䜿甚する堎合、Cの行にマりスを合わせるず、この行が匷調衚瀺されおいれば、asmのこの行に察応するコヌドが匷調衚瀺されたす。

スタック


䟋を耇雑にしお、レゞスタ倉数の䜿甚を停止したしょうあたり䜿甚したせんか。 このコヌドがどうなるか芋おみたしょう

 int main(void) { int a = 1; //   1 int b = a + 5; //  'a' 5    'b' return b; //    } 

ASM

 push ebp mov ebp, esp sub esp, 16 mov DWORD PTR [ebp-8], 1 mov eax, DWORD PTR [ebp-8] add eax, 5 mov DWORD PTR [ebp-4], eax mov eax, DWORD PTR [ebp-4] leave ret 

繰り返したすが、䞊の3行ず䞋の2行をスキップしたす。ロヌカル倉数ができたので、スタック䞊のメモリにメモリが割り圓おられたす。 したがっお、次の魔法がありたす DWORD PTR [ebp-8] 、それはどういう意味ですか DWORD PTRはダブルワヌド倉数です。 ワヌドは16ビットです。 この甚語は16ビットプロセッサの時代に広たり、正確に16ビットがレゞスタに配眮されたした。 このような倧量の情報は蚀葉ず呌ばれるようになりたした。 ぀たり、この堎合、dwordダブルワヌド2 * 16 = 32ビット= 4バむト通垞のintです。

ebpレゞスタには、珟圚の関数のスタックの最䞊郚のアドレスが含たれおいたす埌でこれに戻りたす。したがっお、アドレス自䜓を䞊曞きしないように4バむトシフトされ、倉数の倀を远加したす。 この堎合、倉数aに察しお8バむトだけシフトされたす。 しかし、次のコヌドを芋るず、倉数bが4バむトのオフセットであるこずがわかりたす。 角括匧は䜏所を瀺したす。 ぀たり、この行は次のように機胜したす。ebpに栌玍されおいるアドレスに基づいお、コンパむラは倀1をサむズ4バむトのebp-8アドレスに配眮したす。 なぜプラス8ではなくマむナス8。 この関数に枡されるパラメヌタヌはプラスに察応するため、これに぀いおは埌で説明したす。

次の行は、倀1をeaxレゞスタに移動したす。 詳现な説明は必芁ないず思いたす。

次に、远加远加する新しいaddステヌトメントがありたす。 ぀たり、eax1の倀に5が加算され、倀6がeaxになりたす。

その埌、倀6を倉数bに移動する必芁がありたす。これは次の行で行われたす倉数bはスタック䞊のオフセット4にありたす。

最埌に、倉数bの倀を返す必芁があるため、移動する必芁がありたす
eaxレゞスタの倀 mov eax、DWORD PTR [ebp-4] 。

前のものですべおが明確であれば、より耇雑なものに進むこずができたす。

興味深く、あたり明癜ではないもの。


次のように曞くずどうなりたすかint var = 2.5;

みなさんは、varには2の倀があるず正しく答えるず思いたすが、小数郚はどうなりたすか それは砎棄され、無芖され、型倉換が行われたすか 芋おみたしょう

ASM
 mov DWORD PTR [ebp-4], 2 

コンパむラ自䜓は、小数郚分を䞍芁なものずしお砎棄したした。

このように曞くずどうなりたすかint var = 2 + 3;

ASM
 mov DWORD PTR [ebp-4], 5 

そしお、コンパむラ自䜓が定数を蚈算できるこずを孊びたす。 そしおこの堎合2ず3は定数なので、それらの合蚈はコンパむル段階で蚈算できたす。 したがっお、そのような定数の蚈算に煩わされるこずはありたせん;コンパむラがあなたのために仕事をするこずができたす。 たずえば、時間から秒ぞの倉換は、時間* 60 * 60ず曞くこずができたす。しかし、ここでの䟋は、コヌドで宣蚀されおいる定数に操䜜を眮くこずです。

このコヌドを曞くずどうなりたすか

 int a = 1; int b = a * 2; 

 mov DWORD PTR [ebp-8], 1 mov eax, DWORD PTR [ebp-8] add eax, eax mov DWORD PTR [ebp-4], eax 

面癜いですね。 コンパむラヌは乗算の挔算を䜿甚しないこずにしたしたが、2を乗算した2぀の数倀を単玔に加算したしたこれらの行に぀いおは詳しく説明したせん。前の資料から理解する必芁がありたす

オペレヌション「乗算」はオペレヌション「加算」よりも時間がかかるず聞いたこずがあるかもしれたせん。 これらの理由により、コンパむラはこのような単玔なこずを最適化したす。

しかし、私たちは圌のために仕事を耇雑にし、これを曞きたす

 int a = 1; int b = a * 3; 

ASM

 mov DWORD PTR [ebp-8], 1 mov edx, DWORD PTR [ebp-8] mov eax, edx add eax, eax add eax, edx mov DWORD PTR [ebp-4], eax 

新しいedxレゞスタの䜿甚にだたされないでください; eaxやebxほど悪くはありたせん。 少し時間がかかるかもしれたせんが、ナニットがedxレゞスタに入り、次にeaxレゞスタに入り、その埌eax倀がそれ自䜓で远加され、その埌edxから別のナニットが既に远加されおいるこずがわかりたす。 したがっお、1 + 1 + 1になりたした。

ご存知のように、圌はこれを際限なく行うこずはありたせん。すでに* 4で、コンパむラヌは以䞋を生成したす。

 mov DWORD PTR [ebp-8], 1 mov eax, DWORD PTR [ebp-8] sal eax, 2 mov DWORD PTR [ebp-4], eax mov eax, 0 

それで、新しいsal呜什がありたす、それは䜕をしたすか これは巊ぞのバむナリシフトです。 次のCコヌドず同等

 int a = 1; int b = a << 2; 

この挔算子がどのように機胜するか本圓に理解しおいない人のために

0001は、2぀のれロによっお巊にシフトたたは右に远加されたす0100぀たり、10番目の数倀システムでは4。 その䞭心では、巊ぞの2桁のシフトは4の乗算です。

5を掛けるず、コンパむラが1぀のsalず1぀のaddを䜜成し、異なる数倀を自分でテストできるこずは面癜いです。

22時にgodbolt.orgのコンパむラヌは降䌏しお乗算を䜿甚したすが、この数たでさたざたな方法で抜け出そうずしおいたす。 枛算でも、ただ説明しおいないいく぀かの呜什を䜿甚したす。

さお、それらは花であり、次のコヌドに぀いおどう思いたすか

 int a = 2; int b = a / 2; 

あなたが枛算を期埅するなら、悲しいかな、いいえ。 コンパむラは、より掗緎されたメ゜ッドを生成したす。 挔算「陀算」は乗算よりもさらに遅いため、コンパむラヌもねじれたす。

 mov DWORD PTR [ebp-4], 2 mov eax, DWORD PTR [ebp-4] mov edx, eax shr edx, 31 add eax, edx sar eax mov DWORD PTR [ebp-8], eax 

このコヌドでは、gcc 4.4.7を䟋ずしおあげる前に、かなり新しいバヌゞョンgcc 7.2のコンパむラヌを遞択したず蚀わなければなりたせん。 初期の䟋には倧きな違いはありたせんでした;この䟋では、5行目のコヌドで異なる呜什を䜿甚しおいたす。 そしお、7.2によっお生成された䟋は、私にずっお説明しやすくなりたした。

倉数aが8ではなくオフセット4でスタック䞊にあり、このわずかな違いをすぐに忘れるこずに泚意しおください。 キヌポむントはmov edx、eaxで始たりたす。 ただし、珟時点では、この行の倀をスキップしたす。 shr呜什は、右ぞのバむナリシフトを実行したす぀たり、 shr edxがあった堎合は2で陀算したす、1 。 そしお、ここで、䞀郚の人が考えるこずができるでしょう、なぜ実際にshr edx、1を曞いおはいけないのですか しかし、それほど単玔ではありたせん。

少し最適化しお、それが䜕に圱響するかを芋おみたしょう。 実際、コヌドで敎数陀算を行いたす。 倉数「a」は敎数型であり、2はint型の定数であるため、Cロゞックに埓っお結果が小数になるこずはありたせん。 敎数の陀算はより高速で簡単なので、これは良いこずですが、笊号付きの数倀がありたす。぀たり、shr呜什で陀算したずきの負の数倀は正解ずは異なる堎合がありたす。 これはすべお、笊号付きタむプの範囲の䞭倮で0がブレヌクするずいう事実によるものです。 眲名された郚門を眲名なしに眮き換えた堎合

 unsigned int a = 2; unsigned int b = a / 2; 

その埌、期埅どおりになりたす。 godboltはshr呜什でナニットを省略し、これはNASMでコンパむルされないこずを考慮する䟡倀がありたすが、それはそこで暗瀺されおいたす。 2を4に倉曎するず、2番目のオペランドが2ずしお衚瀺されたす。

前のコヌドを芋おください。 sar eaxを芋るず、これはshrず同じですが、笊号付きの数倀に぀いおのみです。 残りのコヌドでは、負の数を陀算するずきたたはコヌドはわずかに倉曎されたすが、負の数で陀算するずきにこの単䜍を単玔に考慮したす。 コンピュヌタヌ䞊で負の数倀がどのように衚されるかを知っおいれば、なぜ31ビットず぀右にシフトしおこの倀を元の数倀に加算するのかを掚枬するのは難しくありたせん。

倧きい数で割るず、さらに簡単です。 そこでは、陀算が乗算に眮き換えられ、定数が第2オペランドずしお蚈算されたす。 あなたがその方法に興味があるなら、あなたは自分で頭を打ち砎るこずができたす、耇雑なこずは䜕もありたせん。 メモリで実数がどのように衚珟されるかを理解する必芁がありたす。

おわりに


最初の蚘事では、すでに十分な資料がありたす。 それは切り䞊げお圚庫を取る時間です。 アセンブラの基本的な構文に粟通し、コンパむラが蚈算で最も単玔な最適化を匕き受けるこずができるこずがわかりたした。 レゞスタ倉数ずスタック倉数の違いを芋たした。 他にもいく぀かありたす。 これは入門蚘事であり、明らかなこずに倚くの時間を費やさなければなりたせんでしたが、それらは誰にずっおも明癜ではありたせん。将来、C蚀語のより埮劙な点を理解するでしょう。

パヌト2

Source: https://habr.com/ru/post/J344896/


All Articles