引言
接下来的几个日志,我会写几篇关于如何学习X86指令集,也是帮助自己梳理和记忆知识点。
下面是我大概总结了一下,一个操作系统需要掌握的知识点,仅供参考欢迎斧正!
学习内容
1 | blk_userspace=>parallel: 用户态应用程序 |
上图用户层应用程序以及硬件部分暂不是本文考虑范围,中间的三个软件部分“Linux内核/模块”、“IA32e指令”以及“BIOS/UEFI部分指令”都可以通过本文的方法学习。需要说明的是,虽然这里划分了三个部分,并不是操作系统上的划分,只是一个建议的学习的阶段划分。
BIOS/UEFI 部分设计的指令
是指硬件上电后CPU执行的最早期的指令。通常包括BIOS、boot loader等。
这部分可以通过x86-bare-metal-examples来学习
IA32e指令
是指操作系统已经经过一些初始化操作,例如开启页表、开启32bit或者32e模式、段寄存器初始化、开启中断(APIC/X2APIC)、开启SMP支持等。
在这样的环境中,我们可以更专注于X86指令集的研究。上述初始化过程可以等日后展开讲述。
Linux内核/模块
基本上Linux 内核开发涵盖之前两个方面,只是上来就学习Linux内核有点复杂,代码量太大。并且,本系教程的重点在于学习X86指令,并不在Linux中复杂的功能实现。
如何利用QEMU学习
首先确保系统里安装了qemu,步骤略。大概有两种形式使用QEMU
编译
随便举个例子,来自kvm-unit-test:1
2
3
4
5
6
7
8
9
10gcc -m64 -g -Wall -fno-pic -no-pie -std=gnu99 -ffreestanding \
-I /home/works/kvm-unit-tests/lib -I /home/works/kvm-unit-tests/lib/x86 -I ./lib \
-c -o x86/tsc.o x86/tsc.c
gcc -I /home/works/kvm-unit-tests/lib -I /home/works/kvm-unit-tests/lib/x86 \
-I lib -T /home/works/kvm-unit-tests/x86/flat.lds -fno-pic -no-pie -nostdlib \
x86/tsc.c x86/cstart64.o lib/libcflat.a /usr/lib/gcc/x86_64-linux-gnu/5/libgcc.a \
-o x86/tsc.elf
objcopy -O elf32-i386 x86/tsc.elf x86/tsc.flat ##&& ./x86-run x86/tsc.flat
编译一个test case,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13int main(void)
{
u64 t1, t2;
asm volatile ("rdtsc" : "=a"(a), "=d"(d));
t1 = a | ((long long)d << 32);
asm volatile ("rdtsc" : "=a"(a), "=d"(d));
t2 = a | ((long long)d << 32);
printf("rdtsc latency %u\n", (unsigned)(t2 - t1));
return 0;
}
这样得到一个测试两次rdtsc指令执行的时间差的测试tsc.flat
。当然上面辣么麻烦的编译啊、链接啊都是为了得到最终的测试的binary,或者叫可执行文件?!我们后面有很多种方法以及机会得到这样的测试代码,如果读者一时没有成功,不要终止于此,不要气馁。
在虚拟机中作为内核直接运行(-kernel)
1 | qemu-system-x86_64 -vnc none -serial stdio -machine accel=kvm -kernel x86/tsc.flat |
这样运行的虚拟机,看上去并不真的像是一个“虚拟机”,因为没有窗口,仅仅是console端文字输出。但这样足够我们验证和尝试CPU 指令,而且非常的轻量。个人比较喜欢这样的运行方式。
制作一个镜像文件,并且使用QEMU启动
我们可以手动制作一个镜像,步骤如下。但最后一步安装grub的时候,需要的条件有点苛刻,需要本地装有较新版本的grub-x86_64-efi。如果本地没有环境的同学,可以直接跳过制作镜像,直接下载文末的制作好的镜像文件raw.img。
1 | $ dd if=/dev/zero of=raw.img bs=512 count=1048576 |
同样,这里附上UEFI/OVMF的build 方法,但同样可以直接下载文末的binary,毕竟这不是本文的主要内容。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29$ git clone git://github.com/tianocore/edk2.git
$ cd edk2
# Because the latest version is missing a file, switch to an older version
$ git checkout 984ba6a467
$ cd edk2
$ make -C BaseTools
$ . edksetup.sh
$ vi Conf/target.txt
#Find
# ACTIVE_PLATFORM = Nt32Pkg/Nt32Pkg.dsc
# and replace it with
# ACTIVE_PLATFORM = OvmfPkg/OvmfPkgX64.dsc
# Find
# TOOL_CHAIN_TAG = MYTOOLS
# and replace it with your version on GCC here for example GCC 4.6 will be used.
# TOOL_CHAIN_TAG = GCC5 //this is your gcc version
# Find
# TARGET_ARCH = IA32
# and replace it with 'X64' for 64bit or 'IA32 X64' to build both architectures.
# TARGET_ARCH = X64
#mode detail:https://wiki.ubuntu.com/UEFI/EDK2
$ build
$ find -name "OVMF.fd"
#./Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd
$ vim ~/.bashrc
# add this to bashrc "export OVMF_PATH=/home/huihuang/git/edk2/Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd"
$ source ~/.bashrc
1 | #!/bin/bash |
上面这个脚本是将之前生成的tsx.flat文件copy到预先准备好的镜像里面。同时,需要确保镜像中的grub.cfg文件中有正确的entry:1
2
3
4
5
6
7
8
9
10
11menuentry 'acrn_unit_test' {
recordfail
load_video
insmod gzio
if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
insmod part_gpt
insmod ext2
set root='hd0,gpt0'
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt0 --hint-efi=hd0,gpt0 --hint-baremetal=ahci0,gpt4 A807-B387
multiboot /tsx.elf
}
要启动这个镜像需要使用OVMF(EDK2项目中的软件模拟的UEFI)。下面是QEMU的启动参数
1 | DISPLAY=:0 qemu-system-x86_64 --bios OVMF_CODE.fd \ |
这里所需的所有文件都放在文末以备下载。
这样启动的虚拟机,就有种仪式感了,QEMU会创建一个窗口,同时因为有UEFI/OVMF,所以可以看到一个虚拟的BIOS画面,然后还有GRUB的选择菜单,选择刚刚创建的tsx.elf的入口。
OVMF 点击下载:OVMF
raw.img 点击下载: raw.img
调试虚拟机代码
小结
自此如何利用虚拟机学习底层编程的准备工作都已经搞定,我们会在后面的文章里陆续介绍X86的指令。可能不一定系统,甚至可以说的是零碎。因为Intel SDM实在是太长了,能吃透个一章半节的就挺开心的了。仅仅是为想学相关技术的同学们一点点思路,有不对的地方请留言指正!