RustでCコンパイラ その05 STEP12まで(if else, for, whileの制御文)
2025-11-29 11:01:44
- Tags:
- Rust
下記をRustで実装する続き。
低レイヤを知りたい人のためのCコンパイラ作成入門
Githubは下記。
commitコメントに、learnt step N って書いているから、該当のところをみれば変更が分かると思う。
ifはそこまで苦労しなかった。
だけど、Blockや空行がまだ実装していないので、ワンライナーのifとかelseしか書けない。
しかし、whileやforが上手く行かず、
かつアセンブリの量も多くなるので、どうして上手くいかないのかがアセンブリを見てもわからない。
なので、比較のために、実際のc言語の結果を逆アセンブルしてみるようにした。
例えば下記のような簡単なC言語 (戻り値は10になる)は、
int main() {
int i = 0;
while (i<10) i=i+1;
return i;
}
下記のようにobjdumpで逆アセンブルして、main関数だけ表示させることができる。
$ objdump -d -M intel --disassemble=main test1
test1: file format elf64-x86-64
Disassembly of section .init:
Disassembly of section .plt:
Disassembly of section .plt.got:
Disassembly of section .text:
0000000000001129 <main>:
1129: f3 0f 1e fa endbr64
112d: 55 push rbp
112e: 48 89 e5 mov rbp,rsp
1131: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
1138: eb 04 jmp 113e <main+0x15>
113a: 83 45 fc 01 add DWORD PTR [rbp-0x4],0x1
113e: 83 7d fc 09 cmp DWORD PTR [rbp-0x4],0x9
1142: 7e f6 jle 113a <main+0x11>
1144: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
1147: 5d pop rbp
1148: c3 ret
Disassembly of section .fini:
最初は自分で作っているアセンブリと全然違うやん!って思ったけど、
よく読むとなんとなく分かってくる。
jmpやjle命令で制御していて、その先は具体的なアドレス位置であり、
自分で作っているようにわざわざブロックを作ったりはしていないみたい。
また、値はポインター?なのかPTRというところに入れているらしい。
しかも数値は0x1とか0x9とかの16進数。
また、raxレジスタは64bitだからか使っておらず、eaxレジスタになっている。
とまあ、違いはあるにせよ。そう大きな違いではないので、
自分が作っているものが、実際のC言語に近いものになっていくことが知れて嬉しかった。
なお、これを参考にしても自分のwhileとforが上手く動かない理由は分からなかったけど(笑)
なので、次にgdbのデバッガを使ってみた。
(tmpがプログラム名)
# ファイルを指定してデバッガ起動
$ gdb tmp
# intel形式に変更
(gdb) set disassembly-flavor intel
(gdb) disass
Dump of assembler code for function main:
0x0000000000001129 <+0>: push rbp
0x000000000000112a <+1>: mov rbp,rsp
0x000000000000112d <+4>: sub rsp,0xd0
0x0000000000001134 <+11>: mov rax,rbp
0x0000000000001137 <+14>: sub rax,0x8
0x000000000000113b <+18>: push rax
0x000000000000113c <+19>: push 0x0
0x000000000000113e <+21>: pop rdi
0x000000000000113f <+22>: pop rax
0x0000000000001140 <+23>: mov QWORD PTR [rax],rdi
0x0000000000001143 <+26>: push rdi
0x0000000000001144 <+27>: pop rax
0x0000000000001145 <+28>: mov rax,rbp
0x0000000000001148 <+31>: sub rax,0x8
0x000000000000114c <+35>: push rax
0x000000000000114d <+36>: pop rax
0x000000000000114e <+37>: mov rax,QWORD PTR [rax]
0x0000000000001151 <+40>: push rax
0x0000000000001152 <+41>: push 0xa
0x0000000000001154 <+43>: pop rdi
0x0000000000001155 <+44>: pop rax
0x0000000000001156 <+45>: cmp rax,rdi
0x0000000000001159 <+48>: setl al
0x000000000000115c <+51>: movzx rax,al
0x0000000000001160 <+55>: push rax
0x0000000000001161 <+56>: pop rax
0x0000000000001162 <+57>: cmp rax,0x0
0x0000000000001166 <+61>: je 0x118d <main+100>
0x0000000000001168 <+63>: mov rax,rbp
0x000000000000116b <+66>: sub rax,0x8
0x000000000000116f <+70>: push rax
0x0000000000001170 <+71>: mov rax,rbp
0x0000000000001173 <+74>: sub rax,0x8
0x0000000000001177 <+78>: push rax
0x0000000000001178 <+79>: pop rax
0x0000000000001179 <+80>: mov rax,QWORD PTR [rax]
0x000000000000117c <+83>: push rax
0x000000000000117d <+84>: push 0x1
0x000000000000117f <+86>: pop rdi
0x0000000000001180 <+87>: pop rax
0x0000000000001181 <+88>: add rax,rdi
0x0000000000001184 <+91>: push rax
0x0000000000001185 <+92>: pop rdi
0x0000000000001186 <+93>: pop rax
0x0000000000001187 <+94>: mov QWORD PTR [rax],rdi
0x000000000000118a <+97>: push rdi
0x000000000000118b <+98>: je 0x1145 <main+28>
0x000000000000118d <+100>: pop rax
0x000000000000118e <+101>: mov rax,rbp
0x0000000000001191 <+104>: sub rax,0x8
0x0000000000001195 <+108>: push rax
0x0000000000001196 <+109>: pop rax
0x0000000000001197 <+110>: mov rax,QWORD PTR [rax]
0x000000000000119a <+113>: push rax
0x000000000000119b <+114>: pop rax
0x000000000000119c <+115>: mov rsp,rbp
0x000000000000119f <+118>: pop rbp
0x00000000000011a0 <+119>: ret
0x00000000000011a1 <+120>: pop rax
0x00000000000011a2 <+121>: mov rsp,rbp
0x00000000000011a5 <+124>: pop rbp
0x00000000000011a6 <+125>: ret
ああ、こっちのほうが全然見やすい。
のだが、runで起動しても上手く進まずに落ちてしまう。
調べてみたら、下記がみつかった。
Input/output error message when running programs in GDB - Stack Overflow
Unable to debug amd64 binaries on apple silicon · Issue #6921 · docker/for-mac
そのため、Docker起動のためのcompose.ymlに、cap_addとsecurity_optを追加する
services:
tvcc:
container_name: tvcc
platform: linux/amd64
build:
context: .
dockerfile: Dockerfile
volumes:
- ${PWD}:/tvcc
working_dir: /tvcc
command: ./test.sh
cap_add:
- SYS_PTRACE
security_opt:
- seccomp=unconfined
で、下記のようなmygdb.shを用意。
上記githubのissueの途中ぐらいにあるbashスクリプトを少し改造した感じ。
最後の2つ。でもここは便利なように変えていくかも。
#!/bin/bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
if [ "$#" != "1" ]; then
echo "Usage: $0 <path/to/program to debug>" >&2
exit 1
fi
prog="$1"
# Start program in background
ROSETTA_DEBUGSERVER_PORT=1234 "$prog" &
# Run real gdb and tell it to attach
/usr/bin/gdb \
-iex "set architecture i386:x86-64" \
-iex "file $prog" \
-iex "target remote localhost:1234" \
-iex "set history save on" \
-iex "set disassembly-flavor intel" \
-iex 'disp/i $pc'
で、./mygdb.sh ./test1 のように対象プログラムを引数にして動かせば、
あとは、b mainとかでブレイクポイントおいて、
cで実行を開始(runは効かない)
info registersとか、display $raxとかで値を確認しながらsiでステップ実行していく。
なお、gdbの使い方は下記を参考にした。
gdb超入門 #入門 - Qiita
GDBの使い方メモ – GitHub 出張所 – プログラム関係のブログはここに
そんなこんなでデバッグしていると、
ループのためのjmp命令が、 je命令になっていて、いつでもスルーされており、
ループ内が一回しか実行されていないことが分かった。
je命令をjmpになおして実行してみると上手くいった。
デバッグの方法覚えたのはかなりの収穫だな。
次はSTEP 13のブロック


There are currently no comments on this article, be the first to add one below
Add a Comment
Note that I may remove comments for any reason, so try to be civil. If you are looking for a response to your comment, either leave your email address or check back on this page periodically.