1. 工具
1.1 QEMU
MIT 6.828课程用到的模拟器为:QEMU,主要提供guest->host指令集的映射功能,通过动态二进制转换来模拟CPU,并提供硬件模型来让CPU进行交互;
1.2 KVM
Kernel-based Virtual Machine(KVM) is a virtualization infrastructure for the Linux kernel that it into a hypervisor.
KVM包含一个内核模块kvm.ko用来实现核心虚拟化功能,以及一个和处理器强相关的模块如kvm-intel.ko或kvm-amd.ko。 有了KVM以后,guest os的CPU指令不用再经过QEMU来转译便可直接运行,大大提高了运行速度。但KVM的kvm.ko本身只提供了CPU和内存的虚拟化,所以它必须结合QEMU才能构成一个完整的虚拟化技术。
1.3 GDB
gdb主要命令如下所示
- 单步调试使用si执行
-
断点相关
- b设置断点,b *addr,b function
- c执行至断点
-
信息查看
- 寄存器,info registers
- 数据,例如:x/4i 0xffff0
- 指令,例如:x/4x 0xfff0
参考资料:QEMU,KVM及QEMU-KVM介绍
2. 汇编语言
2.1 指令
MIT 6.828中gcc产生的汇编语言格式为AT&T格式,其中汇编语言格式如下:
[<label>:][<instruction or directive or pseudo-instruction>} @comment
# 分别对应:[<标号>:][<指令 or 伪操作 or 伪指令>} @注释
gcc生成的汇编语言代码在.asm中
2.2 Emu8086
汇编语言的体验可通过Emu8086进行,以下为Emu8086自带的例子,可以看到汇编语言的使用场景:
- HelloWorld
- 写LED
- 步进电机,stepper-motor
2.2 伪指令
常见伪指令如下:
# 汇编程序的缺省入口是_start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点。
_start = RELOC(entry)
# .long 定义4字节数据
.long 0x12345678,23876565
# .global/ .globl :用来定义一个全局的符号
.globl entry
# .text @代码段
.text
# .data @初始化数据段 .data Read-write initialized long data.
.data
# .align 对齐方式伪操作
.align 4
# .space <number_of_bytes> {,<fill_byte>},分配number_of_bytes字节的数据空间,并填充其值为fill_byte,若未指定该值,缺省填充0。
.space KSTKSIZE
2.3 内联汇编
在操作系统的代码中,内联汇编在CPU与操作系统之间起着桥梁作用,如获取寄存器值,读写端口等操作,格式如下:
asm ( "statements" : output_registers : input_registers : clobbered_registers);
其中 clobbered_registers ,易失寄存器,volatile;
如下的内联汇编代码可以在一个周期内完成乘以9的操作:
#define times9(arg1, arg2) \
__asm__ ( \
"leal (%0,%0,8),%0" \
: "=r" (arg2) \
: "0" (arg1) );
- 参数:(arg1),前面的0代表,入参传入由编译器决定的以0为标识的寄存器中
- 返回值:(arg2),=r代表,结果传入编译器决定的以r为标识的寄存器中
- 寄存器标识映射
a eax
b ebx
c ecx
d edx
S esi
D edi
I constant value (0 to 31)
q,r dynamically allocated register (see below)
g eax, ebx, ecx, edx or variable in memory
A eax and edx combined into a 64-bit integer (use long longs)
r edi,esi
number [0,1,2,etc]
cc condition code
参考资料:
3. C语言指针
指针操作中:*p+i=p+sizeof(p)i
#include <stdio.h>
#include <stdlib.h>
void
f(void)
{
int a[4];
int *b = malloc(16);
int *c;
int i;
printf("1: a = %p, b = %p, c = %p\n", a, b, c);
c = a;
for (i = 0; i < 4; i++)
a[i] = 100 + i;
c[0] = 200;
printf("2: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",
a[0], a[1], a[2], a[3]);
c[1] = 300;
*(c + 2) = 301;
3[c] = 302;
printf("3: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",
a[0], a[1], a[2], a[3]);
c = c + 1;
*c = 400;
printf("4: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",
a[0], a[1], a[2], a[3]);
//1.指针类型不同导致的异常
c = (int *) ((char *) c + 1);
*c = 500;
printf("5: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",
a[0], a[1], a[2], a[3]);
b = (int *) a + 1;
c = (int *) ((char *) a + 1);
printf("6: a = %p, b = %p, c = %p\n", a, b, c);
}
int
main(int ac, char **av)
{
f();
return 0;
}
其中第5部分,指针类型不同导致的异常部分,400的二进制表示为:0x00000190,301的二进制表示为:0x0000012d,500的二进制:0x000001f4
第5步的指针运算后,布局为:
最终a[1]为:0x0001f490,a[2]为:0x00000100