前提・実現したいこと
現在、コンピュータサイエンスの勉強のために書籍でアセンブラを勉強しています。
アセンブラの勉強を通してCPUやメモリの仕組みを理解したいと考えてます。
今までC, C++, C#, Java, Python, htmlなどプログラミング言語を触りましたが、
大学時代情報系ではなかったので全て独学で、コンピュータサイエンスについての知見は浅いです。
発生している問題・エラーメッセージ
書籍で理解できない部分があるのでどなたか詳しい人に教えていただきたいです。
以下のCのプログラムをアセンブルした結果について不明な点があります。
c
1/* アセンブル元のC言語のプログラム */ 2#include <string.h> 3 char *evil(char *a){ 4 char s[128]; 5 strcpy(s, a); 6 return s;
assembra
1/* アセンブル結果 */ 2push ebp 3mov ebp, esp 4sub esp, 136 //1 5sub esp, 8 // 6push dword [ebp+8] 7lea eax, [ebp-136] 8push eax 9call <strcpy> 10add esp, 16 11leave 12ret
不明な点を以下に示します
・sub esp, 136の136は値なのか、アドレスなのか、また、
・sub esp, 136はespに136を入れてるイメージでいいのか
・以下の部分では何をしているのか、なぜ136と8を分けて足してるのか、レジスタの中で何が起きているのか
sub esp, 136 //1
sub esp, 8 //
push dword [ebp+8]
lea eax, [ebp-136]
・call <strcpy>は関数strcpyのアドレスをどこに保存しているのか?
・最後らへんでなぜ"add esp, 16"をしているのか?
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答5件
0
sub esp, 136はespに136を入れてるイメージでいいのか
各命令の意味を知らずにアセンブラプログラムを読もうというのは無謀です。
sub は引き算です。
また、CPUのレジスタ構成を理解していますか?
スタックポインタ、ベースポインタの意味が分かりますか?
分からないとすると、そこから説明するのはこういう場では無理です。
どこまで理解したいのかにも依りますが、CPUアーキテクチャを学習した上で、コンパイラの作り方みたいな本を読んだ方がいいかと思います。
あと、
Cのプログラムをアセンブルした結果
「Cのプログラムをコンパイルした結果」ですね。
#追記
どこまで理解したいのかにも依りますが、
言葉だけで説明すると、
1.関数の入り口でスタック上にローカル変数(仮引数も含む)のエリアを確保します。つまりスタックポインタをそのエリアサイズだけずらします。引数のサイズはポインタ1つで8バイト、ローカル変数128バイトで合計136バイト
2.scrcpyを呼びます。Cの関数を呼び出す手順が決まっていて(コンパイラにも依りますが)、2つの引数をスタックに積んで呼び出し、戻ってきたら、積んだ引数を捨てます(スタックポンタを引数サイズここではポインタ2つで16バイトずらす)。
3.1でずらしたスタックポインタを戻します(LEAVE命令)。
4.呼び出し元に戻る(RET命令)。
ベースポンタの説明は省略。
136を引いたあとに、さらに8引いている8の意味は調べないと分からない。CじゃなくてJavaScriptのように関数の中で関数を定義できる言語だと、スタックフレームのリンク用だと思うのですが。
なぜ136と8を分けて足してるのか、
は、最適化すれば多分1つになるでしょう。
投稿2019/08/01 12:34
編集2019/08/01 13:07総合スコア84423
0
・sub esp, 136の136は値なのか、アドレスなのか、また、
ローカル変数、ワーク用エリアの確保サイズですな
・sub esp, 136はespに136を入れてるイメージでいいのか
ダメです。sub というのは引き算ですぜ
espから136を引いています
・call <strcpy>は関数strcpyのアドレスをどこに保存しているのか?
リンク時にアドレスが割り当てられます。
保存してあるわけではないです
・最後らへんでなぜ"add esp, 16"をしているのか?
冒頭でsub しているのでその分addしてますな
pushしている分をこれで戻してますね
それぞれの命令がどういう動作をするのかを理解しましょう。
投稿2019/08/01 12:34
編集2019/08/01 13:04総合スコア87719
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
ちょっと怪しいところが有るのですが・・・
asm
1push ebp ; bp退避 2mov ebp, esp ; bpをspに 3sub esp, 136 //1 ; 文字列(s)の領域確保 4sub esp, 8 // ;はじめは復帰値かと思ったんですが・・不明? 5push dword [ebp+8] ; 文字列のポインタ(*a)をpush 6lea eax, [ebp-136] ; 文字列のアドレスをaxに 7push eax ; axをpush 8call <strcpy> 9add esp, 16 ; 引数(スタック)を戻す 10leave 11ret
で、clangだと
asm
1 .cfi_startproc 2# %bb.0: 3 pushq %rbx 4 .cfi_def_cfa_offset 16 5 subq $128, %rsp 6 .cfi_def_cfa_offset 144 7 .cfi_offset %rbx, -16 8 movq %rdi, %rsi 9 movq %rsp, %rbx 10 movq %rbx, %rdi 11 callq strcpy 12 movq %rbx, %rax 13 addq $128, %rsp 14 .cfi_def_cfa_offset 16 15 popq %rbx 16 .cfi_def_cfa_offset 8 17 retq
こうなります(全部レジスタ渡ですね)
・・
他の方も言われてますがアセンブラを勉強しましょうd^^
で、コンパイラも色々癖が有りますから、展開のされ方も習得されたほうがいいと思います。
投稿2019/08/01 14:20
編集2019/08/01 14:23総合スコア6851
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/08/01 14:31 編集
2019/08/01 15:33 編集
2019/08/01 22:36
0
ベストアンサー
sub esp, 136の136は値なのか、アドレスなのか
値です。
sub esp, 136はespに136を入れてるイメージでいいのか
違います。代入ではありません。
sub は subtract 引き算という意味です。
sub esp, 136 は esp = esp - 136 という引き算です。
esp はスタックポインタです。esp はメモリのアドレスを値として持っています。esp に対して引き算や足し算をすると、スタック領域の中のポイントするメモリが変わります。それはスタック領域のトップ位置が変化することになる…と言ってわかるかな?図示できると良いのだけど今は…。
以下の部分では何をしているのか、なぜ136と8を分けて足してるのか、レジスタの中で何が起きているのか
繰り返しますが、足し算ではなく、引き算です。
asm
1sub esp, 136 // esp = esp - 136, s[128] を割り当てた 2sub esp, 8 // esp = esp - 8 3push dword [ebp+8] // この関数の引数 a の値を strcpy に渡そうとしている 4lea eax, [ebp-136] // eax に s[] の先頭アドレスを代入し、 5push eax // eax をスタックトップにpushして strcpy に渡す
- 136 は 128 + 8 です。主な目的は s[128] となるメモリ領域を確保(割当て)すること。加えて8バイトの領域(用途は今、不明)もスタック領域に確保すること。
- sub esp, 8 で、さらに8バイトの領域をスタックに確保した、ということ(たぶん、関数呼出しをする際の「作法」のようなもの、後述)。
136バイトの領域と8バイトの領域は、使う目的が違う=出処が違うので、別の命令が生成されたのです。でも、2つの連続した sub esp 命令は、次の一命令に置き換えることができます。
sub esp, 144 // esp = esp - 144
人間がプログラムするなら、容易に一命令にまとめられますが、おそらくコンパイラの最適化レベルが低い(最適化オプションを指定していない?)ので、まとめられなかったのでしょう。
call <strcpy>は関数strcpyのアドレスをどこに保存しているのか?
これをアセンブルする時点で strcpy のアドレスは不明です。アセンブラは、アドレスはわからないけど、call命令の機械語命令とバイト数は当然わかってる…
具体的に言うと call strcpy は 0xE8 0x?? 0x?? 0x?? 0x?? という5バイトの機械語命令にアセンブルされると思われ、4バイトの 0x?? は~~ strcpy のアドレス~~が書かれる所なのだけど、この時点で strcpy のアドレスは決まらないので、この4バイトを 0x?? のままにしている…アドレスに相当する部分の機械語コードを空欄にしてアセンブルしておくのです。
「保存」ではなく、後でアドレスを埋められるように「空欄」を作っておくということ。
コンパイル処理の中で、アセンブルの次(というか、最後)の処理はリンクです。
strcpy 関数の機械語コードはライブラリファイルの中にあります。それをこのプログラムと結合(linkage)します。結合した時点で strcpy のアドレスが確定するので、空欄を埋めることができる…先ほど空欄にしておいた 4バイトの 0x?? に確定したアドレスを書き込むことができて、実行可能なファイルができあがる、というわけです。
※上記、打ち消し線を引いた2ヵ所を「strcpy への距離」と訂正します。この距離は call 命令の、次の命令の先頭アドレスが基準です。それは今回 add 命令ですから
- 距離 = (strcpy のアドレス)-(add 命令の先頭アドレス)
と計算します。この値が4バイトの 0x?? に書かれます。以上、昨夜はアドレスか距離か、どちらか確信ないまま回答しましたが、いずれにしても、 strcpy のアドレス(と add 命令のアドレス)はリンク時に確定し、全ての機械語が確定する、という意味では大勢に違いありません(と言い訳するw)。
最後らへんでなぜ"add esp, 16"をしているのか?
call命令の直前にpush命令が2つ、それと上記の sub esp, 8 命令があります。
asm
1sub esp, 8 // esp = esp - 8 2push dword [ebp+8] // esp = esp - 4 3lea eax, [ebp-136] 4push eax // esp = esp - 4 5call <strcpy> 6add esp, 16 // esp = esp + 16
二つのpush命令は strcpy(s, a) の実引数 s と a をスタックに書いて strcpy() に渡しています。
ひとつのpush命令は esp の値を 4 減らします(esp = esp - 4)。上にコメントしたように、8, 4, 4 が esp から引かれているので、これらの分をまとめて元に戻すのが add esp, 16 です。
スタックの値を元に戻したということから、ここの sub esp, 8 から add esp, 16 までの6命令が strcpy(s, a); という関数呼出しをコンパイルした部分として、ひとまとまりのコードと見ることができます。
不明点はまだあるでしょうから、コメントもしくは新たな質問で聞いてください。
投稿2019/08/01 13:40
編集2019/08/02 11:03総合スコア1380
0
既に他の方の回答で尽くされている気もしますが、
書籍で理解できない部分があるので
どんな本で勉強したのでしょうか?
(アセンブラの解説があれば、subなんて必ず、説明がある筈)
また、アセンブラと言うと、CPUに依存しますが、CPUは何? (予想できますが)
コンパイル環境等はどうなっているか?
こんなのが明確で無いと、正確な答えは出ません。
[追記]
一応、大体の説明 (厳密にはちょっと自信無いが)
sub esp, 136 // esp レジスタの値を 136だけ減じる。 esp = esp - 136 (128 + 8(引数))
sub esp, 8 // esp = esp - 8 (8 => 64bitアドレス分か?)
push dword [ebp+8] // 多分、s の先頭アドレスを スタックに入れる
lea eax, [ebp-136] // 引数 aの値をスタックにいれる。
call <strcpy> // <strcpy> はリンク時に実際の値が設定される。
// さて、リンクとは? の問題が発生するが、こちらは分かりますか?
add esp, 16 // スタック(esp)に 16を加算 esp = esp + 16、
// esp, ebp はレジスタですが、その役割は?
// esp, ebpレジスタとか、アセンブラ命令から、Intel系 CPUでしょうか。
// あちこちに 8と言う数字が出ることから、 64bit CPUでしょうか?
// 関数呼び出し時の引数の指定方法は、CPUとコンパイラ依存です。
あ、
strcpy(s, a);
return s;
ここで、s を戻していますが、この char s[128]
はスタック上に領域を確保しているで、元の Cプログラムは正しく動かないと思います。
投稿2019/08/01 13:17
編集2019/08/01 14:27総合スコア6383
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/08/01 15:38
2019/08/01 22:40
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/08/01 13:16
2019/08/01 22:54
2019/08/02 01:33