寻找 ./output/qemu/bin/qemu-system-riscv64 的入口函数 main
首先,这个时代最好还是让 AI 帮忙做软件工程分析,没有必要再去看构建系统这些东西。
毕竟构建系统换了一代又一代 (Makefile, cmake, ninja, meson ...),我们还是最好关注一些原理、设计哲学的东西,而不是盯着构建系统不放。
推荐的顺序是:
1.先让AI帮你找入口代码
2.让AI在找的同时,把可复现的步骤依据给你,大概看看就行,不需要全记住甚至不用复现(要确认AI的结论对不对加个 printf("here"); exit(0); 就行)
3.等待AI确实找错,且多尝试几遍都没用的时候,再去钻研这个构建系统、钻研那些古老的 trick
不过,为了保持严谨度,这里我们还是复现一遍古法 trick 寻找入口函数 main 的流程。
古法软件工程 trick 开始 <------- !!!
首先,看 build.sh:
bash
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
cd qemu-6.0.0
if [ ! -d "$SHELL_FOLDER/output/qemu" ]; then
./configure --prefix=$SHELL_FOLDER/output/qemu --target-list=riscv64-softmmu --enable-gtk --enable-virtfs --disable-gio
fi
make -j16
make install
cd ..
可以看到有 make -j16 和 make install
根据经验 (是的,根据一种非常普遍的开源项目非成文惯例),同时有 make -j16 和 make install 说明 ./output/qemu/bin/qemu-system-riscv64 不是原始构建产物,原始构建产物在其它地方,./output/qemu/bin/qemu-system-riscv64 是被处理后放在这里的。
通常,原始构建产物包含调试信息,./output/qemu/bin/qemu-system-riscv64 这种最终构建产物不包含。
可以使用
bash
find . -name "qemu-system-riscv64"
去找原始构建产物,实际上很容易找到,在 qemu-6.0.0/build/qemu-system-riscv64。
运行
bash
file qemu-6.0.0/build/qemu-system-riscv64 \
output/qemu/bin/qemu-system-riscv64
会发现 qemu-6.0.0/build/qemu-system-riscv64 是 with debug_info, not stripped
而 output/qemu/bin/qemu-system-riscv64 是 stripped
也就是前者带调试信息,后者不带。
接下来,使用
bash
readelf -h qemu-6.0.0/build/qemu-system-riscv64 (output/qemu/bin/qemu-system-riscv64 也行,一样的) \
| grep -E 'Type:|Machine:|Entry point'
得到:
Type: DYN (Position-Independent Executable file)
Machine: Advanced Micro Devices X86-64
Entry point address: 0x2bf900
也就是程序入点是 0x2bf900,当然了,这是虚拟地址空间的地址。
对了,这里有个有趣的地方:你会发现 qemu-system-riscv64 的 Type 是 PIE。 根据我的理解,PIE 汇编指令会拖慢性能。Linux 本身为应用层程序提供了独立地址空间,按理来说不需要 PIE,直接 ET_EXEC 就行了,可以避免 PIE 带来的性能开销。可这里却把 qemu-system-riscv64 编译成了 PIE 类型,为什么呢? 原因是为了防御 ROP (return-oriented programming) 攻击,现代操作系统一般会采用 ASLR 防御机制,应用程序普遍使用 PIE 类型是为了配合 ASLR 机制。
接下来运行下面的命令:
bash
nm -an qemu-6.0.0/build/qemu-system-riscv64 (这里如果用 output/qemu/bin/qemu-system-riscv64 会显示没有符号,因为已经被 stripped 了) \
| grep -E ' (_start|main)$'
会得到结果:
00000000002be650 T main
00000000002bf900 T _start
说明一开始执行的是 _start 程序。
执行:
bash
objdump -d --disassemble=_start \
qemu-6.0.0/build/qemu-system-riscv64
得到:
qemu-6.0.0/build/qemu-system-riscv64
qemu-6.0.0/build/qemu-system-riscv64: file format elf64-x86-64
Disassembly of section .init:
Disassembly of section .plt:
Disassembly of section .plt.got:
Disassembly of section .plt.sec:
Disassembly of section .text:
00000000002bf900 <_start>:
2bf900: f3 0f 1e fa endbr64
2bf904: 31 ed xor %ebp,%ebp
2bf906: 49 89 d1 mov %rdx,%r9
2bf909: 5e pop %rsi
2bf90a: 48 89 e2 mov %rsp,%rdx
2bf90d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
2bf911: 50 push %rax
2bf912: 54 push %rsp
2bf913: 45 31 c0 xor %r8d,%r8d
2bf916: 31 c9 xor %ecx,%ecx
2bf918: 48 8d 3d 31 ed ff ff lea -0x12cf(%rip),%rdi # 2be650 <main>
2bf91f: ff 15 c3 f6 ad 00 call *0xadf6c3(%rip) # d9efe8 <__libc_start_main@GLIBC_2.34>
2bf925: f4 hlt
Disassembly of section .fini:
可以看到 _start 函数后面调用了 main 函数。
接下来运行下面的命令:
bash
addr2line -e qemu-6.0.0/build/qemu-system-riscv64 (这里不能用 output/qemu/bin/qemu-system-riscv64,因为没符号) \
-f -C 0x2be650
就能直接看到 main 符号来自具体哪个文件的第几行:
qemu-6.0.0/build/../softmmu/main.c:48
稍微补充一下,qemu-6.0.0 构建系统里用到了 meson,所以实际上可以在 quard_star_tutorial 文件夹下执行:
bash
meson introspect --installed qemu-6.0.0/build | grep qemu-system-riscv64
会得到非常长的一行,往上翻一翻,会看到红色高亮的:
"quard_star_tutorial/qemu-6.0.0/build/qemu-system-riscv64":
"quard_star_tutorial/output/qemu/bin/qemu-system-riscv64"
说明这两者被 meson 映射了,前者是原始构建产物,后者是 make install 后的安装产物。
我们做个实验,在 qemu-6.0.0/softmmu/main.c : main 函数开头加个日志 "hello from MEEEE!!!"
运行 ./output/qemu/bin/qemu-system-riscv64 时成功打印上述日志,说明我们找对了。
到此,qemu-system-riscv64 的入口 main 已经被我们找到,就是 qemu-6.0.0/build/.../softmmu/main.c:48