6.828学习准备工作
本部分是学习6.828需要做的准备工作,虽说是预备知识,但是最佳的学习效果应该是与lab1同时进行,应该该部分非常的基础且重要,于是单独成一篇文章。
环境搭建
本人使用的环境用VMware Station跑ubuntu 16.04的虚拟机。
IDE开发环境VS code和里面的插件remote ssh。
课程使用了MIT patch版本的QEMU和cgdb进行调试,cgdb安装调试可以参考MIT 6.828 2017版本环境配置
并且想要查看更具体的开发效果,可以看看我上传的视频。
汇编语言
本部分的内容主要来自该链接
并且下面所有的讲解都建立在AT&T
语法的基础之上,之所以不对比,是为了防止混淆。
- 寄存器命名规则
%eax
,带有一个%
- 源地址、目标地址顺序:
AT&T: movl src, dst
- 寄存赋值立即数带有
$
:movl $0xd00d, %ebx
- 寄存器后缀:
b
,w
,l
分别占1, 2, 4Bytes - 寻址方式:
immed32(basepointer,indexpointer,indexscale)
,其中basepointer
是必须的。 (%eax)
带有一个框号是表示eax存的值对应的地址中存放的值,间接索引。
有些指令虽然没有涉及具体的寄存器,但是会隐含的涉及一个寄存器的值和操作。
比如call address
就是暗含着压栈的操作。
stos
: 还会使用eax
和edi
寄存器,将eax
的值复制到edi
中。
stosl
: long word
stos WORD PTR ES:[EDI]
: 和stos
功能一致。
内联汇编
一般使用的格式为
asm ("cld\n\t"
"rep\n\t"
"stosl"
: /* no output registers */ //输出部分
: "c" (count), "a" (fill_value), "D" (dest) //输入部分
: "%ecx", "%edi" ); //告诉寄存器,哪些寄存器被修改,不能再设用这些寄存器里面的值了
缩略字母的含义:
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)
关于变量的序号,是从上到下,从左到右,0,1,2...n赋予序号。
c语言指针
可以仔细看看这个链接
这一部分主要讲讲c语言里面的一些用到的语法技巧。
例子1:指针强制类型转化
int *x;
*((char *) (x)+1)='a';
就是对x指针强制类型转换,然后在该地址进行赋值。
例子2:数组初始化
pde_t entry_pgdir[NPDENTRIES] = {
[0] = ((uintptr_t)entry_pgtable - KERNBASE) + PTE_P,
[960] = ((uintptr_t)entry_pgtable - KERNBASE) + PTE_P + PTE_W
};
//仅仅对其中的部分值进行初始化,需要有前面的[x]的索引来进行。
//后面会发现中断向量表也是这样进行的。
例子3:钩子函数
// 定义钩子函数
struct Command {
const char *name;
const char *desc;
// return -1 to force monitor to exit
int (*func)(int argc, char** argv, struct Trapframe* tf);
};
//实例化
static struct Command commands[] = {
{ "help", "Display this list of commands", mon_help },
{ "kerninfo", "Display information about the kernel", mon_kerninfo },
{"traceback", "traceback info", mon_kerninfo},
};
//具体的需要挂载到钩子函数上的实际函数
int
mon_kerninfo(int argc, char **argv, struct Trapframe *tf)
{
extern char _start[], entry[], etext[], edata[], end[];
cprintf("Special kernel symbols:\n");
cprintf(" _start %08x (phys)\n", _start);
cprintf(" entry %08x (virt) %08x (phys)\n", entry, entry - KERNBASE);
cprintf(" etext %08x (virt) %08x (phys)\n", etext, etext - KERNBASE);
cprintf(" edata %08x (virt) %08x (phys)\n", edata, edata - KERNBASE);
cprintf(" end %08x (virt) %08x (phys)\n", end, end - KERNBASE);
cprintf("Kernel executable memory footprint: %dKB\n",
ROUNDUP(end - entry, 1024) / 1024);
return 0;
}
//调用
for (i = 0; i < ARRAY_SIZE(commands); i++) {
if (strcmp(argv[0], commands[i].name) == 0)
return commands[i].func(argc, argv, tf);
}
例子4:函数可变长参数
可变参数函数实现的步骤如下:
- 1.在函数中创建一个va_list类型变量
- 2.使用va_start对其进行初始化
- 3.使用va_arg访问参数值
- 4.使用va_end完成清理工作
//来源:公众号【编程珠玑】
#include <stdio.h>
/*要使用变长参数的宏,需要包含下面的头文件*/
#include <stdarg.h>
/*
* getSum:用于计算一组整数的和
* num:整数的数量
*
* */
int getSum(int num,...)
{
va_list ap;//定义参数列表变量
int sum = 0;
int loop = 0;
va_start(ap,num);
/*遍历参数值*/
for(;loop < num ; loop++)
{
/*取出并加上下一个参数值*/
sum += va_arg(ap,int);
}
va_end(ap);
return sum;
}
int main(int argc,char *argv[])
{
int sum = 0;
sum = getSum(5,1,2,3,4,5);
printf("%d\n",sum);
return 0;
}
16位向32位系统转换
一般有下面的套路:
flush_gdt:
lgdt [gdtr]
ljmp 0x08:complete_flush
complete_flush:
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
ret
具体解释为什么使用ljmp
,为什么段寄存器都赋值为0x10
,可以参考该链接。
GDB常用命令
si
: 执行一个汇编指令
s
:执行一个c语言语句,并且进入到函数中。
n
:与上面的相对应,执行一个c语句,不进入到函数执行中。
b *address
: 在某个地址打断点,这个地址前记得一定要带*
。当然也可以是函数名,文件的行号。
p /x var
:查看某个变量的值,可以是寄存器,或变量。
x/Nx address
:查看从address开始的若干内存的内容。
x/s address
:查看字符串的内容。
bt
: 查看栈的内容,如lab 1 exercise 12的实现功能一致。
建议有不会的内容可以查看该手册
ELF解析
建议直接参考这张图,由于这张图上有丰富的信息,因此页面展示非常模糊,直接原图下载查看。