首页 > 其他分享 >Xv6 Lab4

Xv6 Lab4

时间:2023-07-13 19:22:26浏览次数:50  
标签:fp uint64 printf Xv6 trapframe 地址 ra Lab4

RISC-V assembly

Which registers contain arguments to functions? For example, which register holds 13 in main's call to printf?

  • a2 寄存器,函数调用时,参数从左到右会依次保存在 a0, a1, a2, a3 寄存器,似乎是一直到寄存器 a7 的。

Where is the call to function f in the assembly code for main? Where is the call to g? (Hint: the compiler may inline functions.)

  • 这里的调用都被内联了

At what address is the function printf located?

  • auipc(Add Upper Immediate PC)指令是将一个立即数左移 $12$ 位加上当前指令的地址(pc)中,得到一个绝对地址。

  • 例如 auipc a0, 0x0 就是将 $\text{0x0}$ 左移 $12$ 位加上当前指令的地址 (pc) 中(pc 的值我们一般认为是在指令执行完成时才发生递增,从而指向下一条指令,使得处理器能够按顺序顺利执行指令序列),因此,执行 auipc a0, 0x0 时,加的就是当前指令的地址,即 $\text{0x28}$。

  • Ep4uySNWrac6nwQ

  • auipc 常常会与 jalr(Jump and Link Register) 指令组合在一起,在 jalr 1526(ra) 中,它把 pc 设置为寄存器 ra 的值加上 offset,然后把原先的 $pc + 4$ 的值写入寄存器 ra,即 ra 的值就是函数的返回值,即正常序列里,jalr 的下一条指令,这里 ra 的值应该是 $\text{0x38}$, pc 的值则为 $\text{0x626}$。

unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);
  • 上面这段代码的输出结果为 He110 World

Backtrace

这里的关键在于弄懂 stack frame 的结构,参考这张图,其实基本原理与 x86 下 C 语言的 stack frame 是一致的,我们通过 uint64 fp = r_fp() 函数拿到的是栈基地址(联想 x86 中的 rbp),因此 fp + 8 就是 return address 的地址,因此 uint64 ra = *(uint64 *)(fp + 8);,注意 fp + 8 是地址的地址,不是我们要输出的 ra!

我们需要遍历 stack frame,上一个 stack frame 的栈基地址就存放在 fp + 16 处,因此 fp = *(uint64 *)(fp + 16);

循环的终止条件,可以先计算出 fp 的上下界 PGROUNDDOWN(fp)PGROUNDUP(fp)(按照 PGSIZE 大小对齐),又或者当 PGROUNDUP(fp) != p->kstack + PGSIZE 时退出循环。

void backtrace(void) {
    uint64 fp = r_fp();
    printf("backtrace\n");
    uint64 upb = PGROUNDUP(fp);
    uint64 db = PGROUNDDOWN(fp);
    // struct proc *p = myproc();
    while (fp < upb && fp > db) {
        uint64 ra = *(uint64 *)(fp - 8);
        printf("%p\n", ra);
        fp = *(uint64 *)(fp - 16);
    }
}

Alarm

这个实验在了解了 trap 机制的整个流程,如何陷入到内核代码,又如何返回用户代码之后,其实并不难,但是还是有两个地方比较难想:

  1. sigalarm 的调用其实只起到一个开关的作用,当开启 sigalarm 之后,还是要在 usertrap 的外部定时中断部分来执行定时响应函数,我们可以通过 argaddr 函数来获取 sigalarm 传入的作为函数地址的第二个参数,但是,我们并不能直接在 usertrap 中把函数地址强制类型转化为函数指针来调用函数,这是因为 kernel page tableuser page table 是不一样的,我们需要把 p->trapframe->epc 修改为 periodic 的地址,在返回用户态时,pc 会被更新为 p->trapframe->epc 的值,这样返回到用户态时就会马上执行 periodic 函数;

  2. 经过上面的操作,我们返回时会直接执行 periodic 函数,但是执行完函数之后,机器就不知道接下来该执行哪一条指令来,因此提供了 sigreturn 函数,使得可以恢复到发生定时器中断时的状态,这里提到了需要保存很多寄存器的值,因此直接在 struct proc 中添加一个 struct trapframe *save_reg; 字段,在 usertrap 的定时中断响应中,利用 memmovep->trapframe 复制到 p->save_reg,而在 sys_return 中将 p->save_reg 恢复到 p->trapframe

几处关键的代码:

trap.c 中:

void usertrap(void) {
    int which_dev = 0;

    if ((r_sstatus() & SSTATUS_SPP) != 0)
        panic("usertrap: not from user mode");

    // send interrupts and exceptions to kerneltrap(),
    // since we're now in the kernel.
    w_stvec((uint64)kernelvec);

    struct proc *p = myproc();

    // save user program counter.
    p->trapframe->epc = r_sepc();

    if (r_scause() == 8) {
        // system call

        if (p->killed)
            exit(-1);

        // sepc points to the ecall instruction,
        // but we want to return to the next instruction.
        p->trapframe->epc += 4;

        // an interrupt will change sstatus &c registers,
        // so don't enable until done with those registers.
        intr_on();

        syscall();
    } else if ((which_dev = devintr()) != 0) {
        // ok
        if (which_dev == 2 && p->interval != 0) {
            // 说明启用了 alarm
            ++p->tick_num;
            if (p->tick_num == p->interval) {
                if (p->flag == 0) {
                    // typedef void (*Handler)();
                    // printf("p->handler: %p\n", p->handler);
                    memmove(p->save_reg, p->trapframe, PGSIZE);
                    p->flag = 1;
                    p->trapframe->epc = p->handler;
                }
                p->tick_num = 0;
            }
        }
    } else {
        printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
        printf("            sepc=%p stval=%p\n", r_sepc(), r_stval());
        p->killed = 1;
    }

    if (p->killed)
        exit(-1);

    // give up the CPU if this is a timer interrupt.
    if (which_dev == 2) {
        yield();
    }
    usertrapret();
}

sysproc.c 中:


uint64 sys_sigalarm() {
    struct proc *p = myproc();
    int interval;
    if (argint(0, &interval) < 0) {
        return -1;
    }
    uint64 addr;
    if (argaddr(1, &addr) < 0) {
        return -1;
    }
    p->interval = interval;
    p->handler = addr;

    return 0;
}

uint64 sys_sigreturn() {
    struct proc *p = myproc();
    memmove(p->trapframe, p->save_reg, PGSIZE);
    p->flag = 0;
    return 0;
}

同时注意在 proc.callocproc 中给 save_reg 分配空间,在 freeproc 中释放。

以及 kernel/param.h 中,FSSIZE 要修改为至少 $1088$,否则 alarmtest 能过,但是 usertests 过不了,会卡在 writebig 处,报错提示为 "test writebig: panic: balloc: out of blocks",不知道有没有大佬能解惑。

标签:fp,uint64,printf,Xv6,trapframe,地址,ra,Lab4
From: https://www.cnblogs.com/zwyyy456/p/17551880.html

相关文章

  • 【cs 50】lab4 & problemset4 -ing
    (1)lab4-Smileyhelpers.c#include"helpers.h"voidcolorize(intheight,intwidth,RGBTRIPLEimage[height][width]){//Changeallblackpixelstoacolorofyourchoosingfor(inti=0;i<height;++i){for(intj=0;j<width;++......
  • Xv6 Lab2
    系统调用Lab1主要是基于提供的系统调用接口来编写一些小工具程序,而Lab2则是要我们自己实现系统调用,并提供系统调用的接口。以本次Lab要我们实现的trace调用为例,说明一下系统调用的流程:在user/trace.c的第$15$行,调用了属于systemcall的trace函数,当前执行makeq......
  • Xv6 操作系统组织架构
    进程概述64位的RISC-V的VAS是39位的,即VA只有39位,而Xv6则只有38位,最大虚拟地址为#defineMAXVA0x3fffffffff。VAS的顶端,即最高位存放了两个page,一个是用于trampoline,一个用于mappingtheprocess'strapframe。Xv6使用这两个page来切换到内核以及返回。......
  • buaa os lab4-challenge 信号系统的实现
    buaaoslab4-challenge信号系统的实现信号是什么生活中我们会收到各种各样的信号,比如老师在群里布置了一个新的ddl,或者肚子发出咕咕的叫声提醒我们该吃饭了,接收到信号之后我们并不是马上处理,需要等到一些合适的时机并前横利弊,比如对于人来说肯定是吃饭重要,所以我们会忽略ddl......
  • CS144 计算机网络 Lab4:TCP Connection
    前言经过前面几个实验的铺垫,终于到了将他们组合起来的时候了。Lab4将实现TCPConnection功能,内部含有TCPReceiver和TCPSender,可以与TCP连接的另一个端点进行数据交换。实验要求简单来说,这次实验就是要在TCPConnection类中实现下图所示的有限状态机:这些状态对应T......
  • xv6页表
    一级页表地址64bit。虚拟地址使用low39位,物理地址使用low56位。虚拟地址的low39中,高27位是index,用来索引页表中的具体"一行"页表项。一个页表项在物理内存中是4096Byte,offset用来索引具体一个Byte。物理地址的low56中,高44位是物理页号(PPN),低12位等同于虚拟地址中的offset的1......
  • 《操作系统原型--xv6分析与实验》第一章:qemu启动xv6问题记录
    最近在学习《操作系统原型--xv6分析与实验》,第一章安装qemu和启动xv6就遇到很多障碍,特此记录一下解决办法。版本信息系统:Ubuntu22.04.1LTSxv6:rev9qemu:6.2gcc:11.2.0操作步骤ubuntu勾选了完整安装,默认自带gcc、make等构建工具。首先将用到的xv6下载下来解压,我下载的是re......
  • xv6 exec系统调用
    exec是一个创建用户地址空间的系统调用。根据一个存储在文件系统中的file来创建用户地址空间。具体来说,intexec(char*path,char**argv)exec使用namei打开指定的二进制文件路径(kernel/exec.c:26)。exec读取ELF(可执行与可链接格式)文件头。xv6应用程序遵循广泛使用的......
  • xv6 labs Copy-On-Write fork
    虚拟内存提供了一个中间层:内核可以通过将PTE标记为invalid或者read-only来拦截内存引用,这会导致pagefault,此时,你可以通过修改PTE来改变地址的含义。在计算机系统中有一种说法,任何系统问题都可以通过中间层解决。lazyallocation实验提供了一个例子,本次实验将探索另一个例子:copy-o......
  • xv6 如何进入系统调用,并设置参数
    #Initialprocessthatexecs/init.#Thiscoderunsinuserspace.#include"syscall.h"#exec(init,argv).globlstartstart:laa0,initlaa1,argvlia7,SYS_exececall#for(;;)exit();exit:lia......