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
  • 寄存器后缀: bwl分别占1, 2, 4Bytes
  • 寻址方式:immed32(basepointer,indexpointer,indexscale),其中basepointer是必须的。
  • (%eax)带有一个框号是表示eax存的值对应的地址中存放的值,间接索引。

有些指令虽然没有涉及具体的寄存器,但是会隐含的涉及一个寄存器的值和操作。

比如call address就是暗含着压栈的操作。

stos: 还会使用eaxedi寄存器,将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解析

建议直接参考这张,由于这张图上有丰富的信息,因此页面展示非常模糊,直接原图下载查看。

results matching ""

    No results matching ""