回答編集履歴

2

運営による修正

2024/07/02 03:26

投稿

ikedas
ikedas

スコア4443

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
- 1 804840b: 8d 4c 24 04 lea 0x4(%esp),%ecx
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
- うする理由のひとつは、スタックの位置をキャッシュラインサイズに合わせるためだと思います。スタックキャッシュラインサイズ (x86では64バイト) の境界をまたぐように配置されスタック操作の際にキャッシュミス多発して速度が落ちます。スタックを16バイト境界に揃えれば、速度低下を回避しやすくなりす。が、ぜ最大の64バイト境界揃えないのかはかりせん (メモリの節約のためでしょうか)
17
+ こまでは、スタックのライト調整のためのコードです。関数呼び出されきにスタックがかならず16バイト境界から始っているようアーキテクチャは必要ないと思
18
+ ```
19
+ 4 8048415: 55 push %ebp
10
20
 
11
- もうひとつ。[SSE命令](https://ja.wikipedia.org/wiki/Streaming_SIMD_Extensions)では128ビットのレジスタを使う関係から、全てのデータが16バイト境界にそろえてある必要があります。スタックもこれに合わせて、4バイトや8バイトではなく16バイト境界で揃えているのだと考えられます。
21
+ 5 8048416: 89 e5 mov %esp,%ebp
12
22
 
13
- いずれにせよ、スタックポインタを書き換えてしまうとretしたときに呼び出し元に戻れません。書き換える前に、スタック最上部に積んであるもの (戻り先のアドレス) のアドレス0x4(%esp)を、%ecxに保存しています。
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

若干修正

2017/03/27 05:08

投稿

ikedas
ikedas

スコア4443

test CHANGED
File without changes