回答編集履歴
2
運営による修正
test
CHANGED
@@ -1,14 +1,77 @@
|
|
1
1
|
そんなに詳しいわけではないのですが、読み解いてみました。なお、回答時点では元のCのソースコードを参照できなかったため、処理の詳細について認識違いがあるかもしれません。
|
2
|
+
```
|
3
|
+
1 804840b: 8d 4c 24 04 lea 0x4\(%esp\),%ecx
|
2
4
|
|
5
|
+
2 804840f: 83 e4 f0 and \$0xfffffff0,%esp #なぜこれをしないといけないのか?
|
3
6
|
```
|
4
|
-
|
5
|
-
2 804840f: 83 e4 f0 and $0xfffffff0,%esp #なぜこれをしないといけないのか?
|
7
|
+
こうする理由のひとつは、スタックの位置をキャッシュラインサイズに合わせるためだと思います。スタックがキャッシュラインサイズ \(x86では64バイト\) の境界をまたぐように配置されると、スタック操作の際にキャッシュミスが多発して速度が落ちます。スタックを16バイト境界に揃えれば、速度低下を回避しやすくなります。が、なぜ最大の64バイト境界で揃えないのかはわかりません \(メモリの節約のためでしょうか\)
|
6
8
|
|
9
|
+
もうひとつ。[SSE命令](https://ja\.wikipedia\.org/wiki/Streaming_SIMD_Extensions) では128ビットのレジスタを使う関係から、全てのデータが16バイト境界にそろえてある必要があります。スタックもこれに合わせて、4バイトや8バイトではなく16バイト境界で揃えているのだと考えられます。
|
10
|
+
|
11
|
+
いずれにせよ、スタックポインタを書き換えてしまうとretしたときに呼び出し元に戻れません。書き換える前に、スタック最上部に積んであるもの \(戻り先のアドレス\) のアドレス0x4\(%esp\)を、%ecxに保存しています。
|
7
12
|
```
|
13
|
+
3 8048412: ff 71 fc pushl -0x4\(%ecx\) ##これと対になるのはなにか?
|
14
|
+
```
|
15
|
+
これは何かを退避しているわけではありません。%ecxに入っているアドレスに入っているもの \(つまり、戻り先のアドレス\) を、%espを変更した後のスタックに積みなおしているだけです。
|
8
16
|
|
9
|
-
こ
|
17
|
+
ここまでは、スタックのアライメント調整のためのコードです。関数が呼び出されたときにスタックがかならず16バイト境界から始まっているようなアーキテクチャでは必要ないと思われます。
|
18
|
+
```
|
19
|
+
4 8048415: 55 push %ebp
|
10
20
|
|
11
|
-
|
21
|
+
5 8048416: 89 e5 mov %esp,%ebp
|
12
22
|
|
13
|
-
|
23
|
+
6 8048418: 51 push %ecx ##これと対になるのはなにか?
|
14
24
|
|
25
|
+
7 8048419: 83 ec 14 sub \$0x14,%esp ##なぜ10行目があるのに、これをしないといけないのか?
|
26
|
+
```
|
27
|
+
ここでスタックフレームを作っています。上記のコードを実行した後のスタックをスタックポインタの値を基準に示すと、次のようになっているはずです。
|
28
|
+
| アドレス\(相対\) | 内容 |
|
29
|
+
| ---- | -------------------------------- |
|
30
|
+
| 0x00 | スタックポインタ \(%esp\) が指す位置 |
|
31
|
+
| \.\.\. | \.\.\. |
|
32
|
+
| 0x10 | ベースポインタ \(%ebp\) が指す位置 |
|
33
|
+
| 0x14 | %ecxの値 |
|
34
|
+
| 0x18 | 呼び出し元のベースポインタ \(%ebp\) |
|
35
|
+
| 0x1C | 戻り先アドレス |
|
36
|
+
| \.\.\. | \.\.\. |
|
37
|
+
| \?\?\? | 調整前のスタックポインタが指す位置 |
|
38
|
+
| \?\?\? | 戻り先アドレス |
|
39
|
+
|
40
|
+
%espの指す位置より上位で%ebpの指す位置までの間の16バイトは、自動変数に使われます。16バイトの領域全てが使われるとは限りません \(下記の処理でも最下位の4バイトしか使っていません\)。
|
41
|
+
|
42
|
+
呼び出し元のベースポインタ%ebpをスタックに積んでおくのはわかりますが、%ecxを積む意味は私には分かりません。6行目、7行目の疑問についてはそれぞれ後述。
|
43
|
+
```
|
44
|
+
8 804841c: c7 45 f4 00 00 00 00 movl \$0x0,-0xc\(%ebp\)
|
45
|
+
|
46
|
+
9 8048423: eb 14 jmp 8048439 <main\+0x2e>
|
47
|
+
|
48
|
+
10 8048425: 83 ec 0c sub \$0xc,%esp
|
49
|
+
|
50
|
+
11 8048428: 68 d0 84 04 08 push \$0x80484d0
|
51
|
+
|
52
|
+
12 804842d: e8 ae fe ff ff call 80482e0 <puts@plt>
|
53
|
+
|
54
|
+
13 8048432: 83 c4 10 add \$0x10,%esp
|
55
|
+
##これをするとesp-0x14とesp-0x16の間にアドレス空間ができてしまう気がするのですが?
|
56
|
+
|
57
|
+
14 8048435: 83 45 f4 01 addl \$0x1,-0xc\(%ebp\)
|
58
|
+
|
59
|
+
15 8048439: 83 7d f4 09 cmpl \$0x9,-0xc\(%ebp\)
|
60
|
+
|
61
|
+
16 804843d: 7e e6 jle 8048425 <main\+0x1a>
|
62
|
+
```
|
63
|
+
処理の本体です。ここでスタックポインタを動かしています \(10行目\)。そしてスタックに引数を積み、putsを呼びます \(11-12行目\)。スタックポインタを動かす理由は、putsに入ったときにスタックが16バイト境界で揃っているようにするためではないでしょうか。
|
64
|
+
|
65
|
+
putsから戻ってきたら、pushした分をpopした上で動かしたスタックポインタを元に戻してもいいのですが、それらをまとめてスタックポインタの値を加算するだけですませています \(13行目\)。動かした分の12バイト \(0xc\) とpushした4バイトで、合わせて16バイト \(0x10\) です。
|
66
|
+
```
|
67
|
+
17 804843f: b8 00 00 00 00 mov \$0x0,%eax ##これはなにをしてるのか?
|
68
|
+
```
|
69
|
+
返り値を指定しているのでしょう。お使いのアーキテクチャでは、int型の関数では返り値を%eaxに入れて返すのだと思われます。
|
70
|
+
```
|
71
|
+
19 8048447: c9 leave
|
72
|
+
|
73
|
+
20 8048448: 8d 61 fc lea -0x4\(%ecx\),%esp
|
74
|
+
|
75
|
+
21 804844b: c3 ret
|
76
|
+
```
|
77
|
+
``leave``は``mov %ebp, %esp; pop %ebp``と同じ意味ですから、4-5行目で行ったスタックポインタの調整を元にもどします。これによってスタックフレームは破棄され、自動変数の領域が解放されることになります。またスタックポインタの調整以後にスタックに積まれたものも、popで元に戻す必要はありません。
|
1
若干修正
test
CHANGED
File without changes
|