# デバッグ with QEMU
## QEMUのインストール
```bash
$ sudo apt install qemu-system # qemu-system-x86_64
$ sudo apt install qemu-utils # qemu-img
```
## Buildroot
QEMUでLinuxカーネルを起動するには、ルートFSが必要である。今回は軽量なルートFSとしてBuildrootを使用する。Buildrootのビルド方法は別の場所でまとめた。
リンク:https://hackmd.io/x9oKoEXUSIGKwXFIhrU0vw?view#Buildroot%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9
## ルートFSの修正
手元でビルドしたagentやアプリケーションは、ルートFSイメージの中に置いておく必要がある。また、実行時に必要な共有ライブラリと動的リンカも一緒に置いておくために、以下のようなmake_image.shというスクリプトを作成した。
```sh=
#!/bin/bash
sudo mount -o loop rootfs.ext2 mnt
# ======== ghost binary ========
sudo cp ~/ghost-userspace/bazel-bin/fifo_per_cpu_agent \
mnt/root/fifo_per_cpu_agent
sudo cp ~/ghost-userspace/bazel-bin/simple_exp \
mnt/root/simple_exp
# set rpath and interpreter
set_rpath_and_interpreter() {
sudo patchelf --set-rpath . "$1"
sudo patchelf --set-interpreter ./ld-linux-x86-64.so.2 "$1"
}
set_rpath_and_interpreter mnt/root/fifo_per_cpu_agent
set_rpath_and_interpreter mnt/root/simple_exp
# ======= shared objects =======
cp_shared_library () {
so_name="$1"
so_path=/lib/x86_64-linux-gnu/"$so_name"
if [ -h "$so_path" ]; then
# symbolic link
linked_name=$(readlink "$so_path")
linked_path=/lib/x86_64-linux-gnu/"$linked_name"
echo "@$so_name --> $linked_name"
sudo cp /lib/x86_64-linux-gnu/"$linked_name" \
mnt/root/"$so_name"
else
echo "@$so_name is a file"
sudo cp /lib/x86_64-linux-gnu/"$so_name" \
mnt/root/"$so_name"
fi
}
cp_shared_library libelf.so.1
cp_shared_library libz.so.1
cp_shared_library libnuma.so.1
cp_shared_library libcap.so.2
cp_shared_library libstdc++.so.6
cp_shared_library libm.so.6
cp_shared_library libgcc_s.so.1
cp_shared_library libpthread.so.0
cp_shared_library libc.so.6
# ======= interpreter ========
sudo cp /lib/x86_64-linux-gnu/ld-2.31.so mnt/root/ld-linux-x86-64.so.2
sudo umount mnt
```
※ 3行目のrootfs.ext2とは、Buildrootでビルドしたディスクイメージを手元にコピーしてきたものである。
※ 9-15行では、使用する共有ライブラリや動的リンカを見つけられるようにするための処理である。これを記述することで、共有ライブラリ及び動的リンカは、カレントディレクトリにあるものを選択するようになる。
※ 17-40行では、実行に必要な共有ライブラリをコピーしている。コピー先は/rootディレクトリ配下にしている。
## QEMUの起動
qemu起動用のシェルスクリプトを以下のように作成した。
```bash
#!/bin/bash
KERNEL_IMAGE=/home/ruth/ghost-kernel/arch/x86_64/boot/bzImage
ROOTFS_IMAGE=./rootfs.ext2
qemu-system-x86_64 \
-smp 4 -m 2G \
-monitor stdio \
-kernel ${KERNEL_IMAGE} \
-drive file=${ROOTFS_IMAGE},format=raw \
-append "console=ttyS0 root=/dev/sda oops=panic panic=-1 nopti nokaslr" \
-s
```
# GDB解析
## 特定の関数への呼び出し経路をビジュアル化する
以下のスクリプトは、カーネル内の特定の関数の呼び出し経路をグラフに描画するスクリプトである。5行目に__scheduleを指定しているが、ここを好きな関数に置き換えれば解析対象を変更することもできる。
```python
import gdb
gdb.execute("target remote :1234")
gdb.execute("file ~/ghost-kernel/vmlinux")
gdb.execute("b __schedule", to_string=True)
gdb.execute("c", to_string=True)
def bt(depth: int = 3) -> str:
command = f"bt {depth}"
output = gdb.execute(command, to_string=True)
lines = list(output.split("\n"))[:-1]
retval = []
for line in lines:
if line[0] != "#":
break
lst = list(line.split())
if lst[1][0:2] == "0x":
retval.append(lst[3])
else:
retval.append(lst[1])
return retval
# ここの値を変えることで、取得するデータの数を変えることができる。
# 多ければ多いほどいいが、データ取得に時間もかかるので、いい感じの配分がいい。
NLOOP = 3
nodes = []
edges = dict()
for i in range(NLOOP):
# 5という値は、呼び出し経路の深さを指定している
# 好きな値に変更して良い。
flist = bt(5)
print(flist)
for i in range(1, len(flist)):
fname = flist[i]
if fname not in nodes:
nodes.append(fname)
if (flist[i], flist[i - 1]) in edges.keys():
edges[(flist[i], flist[i - 1])] += 1
else:
edges[(flist[i], flist[i - 1])] = 1
gdb.execute("c", to_string=True)
import networkx as nx
import matplotlib.pyplot as plt
# 有向グラフを作成
G = nx.DiGraph()
# ノードを追加
G.add_nodes_from(nodes)
# エッジ(有向辺)を追加
for edge, weight in edges.items():
G.add_edge(edge[0], edge[1], weight=weight)
# 有向グラフを可視化
# 呼び出しグラフは基本的にはDAGとなるので、DAG形式で見やすいようにしている。
for layer, nodes in enumerate(nx.topological_generations(G)):
for node in nodes:
G.nodes[node]["layer"] = layer
pos = nx.multipartite_layout(G, subset_key="layer")
fig, ax = plt.subplots()
nx.draw_networkx(G, pos=pos, ax=ax,
node_size=15000, node_color="SkyBlue",
width=2.0, edge_color="Gray",
font_color="Gray", font_size=10, font_family="Monospace")
ax.set_title("DAG layout in topological order")
fig.tight_layout()
# グラフを表示
plt.show()
```
このスクリプトはgdbに読み込ませて使う。以下のような感じ。
```
$ gdb -q -x script.py
```