# デバッグ 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 ```