Constexpression's blog

操作系统课设

2023-02-28

Lab1_challenge1 打印用户程序调用栈

在main函数中调用了多层函数。需要打印栈的情况。

相关知识:

代理内核的启动过程

重点在于理解lab1中进程的定义。

1
2
3
4
5
6
19 typedef struct process {
20 // pointing to the stack used in trap handling.
21 uint64 kstack;
22 // trapframe storing the context of a (User mode) process.
23 trapframe* trapframe;
24 }process;

包含栈指针和指向trapframe的指针。trapframe中包含寄存器regs,里面存了我们所需要的fp。

elf_fpread函数需要一个ctx变量。ctx是什么?elf.h文件里有定义

1
2
3
4
typedef struct elf_ctx_t {
void *info;
elf_header ehdr;
} elf_ctx;

在elf_init函数里面对ctx进行了初始化。由此可以得到elf_header

然后就是根据手册里面的指导读取所有的section table。得到symtab和strtab

打印的symtab.value和其name在strtab偏移量中的字符串取值如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
st_value 00000000
systab名字
st_value 81000000
systab名字
st_value 81000398
systab名字
st_value 810003c4
systab名字
st_value 00000000
systab名字
st_value 00000000
systab名字
st_value 00000000
systab名字
st_value 00000000
systab名字
st_value 00000000
systab名字
st_value 00000000
systab名字
st_value 00000000
systab名字
st_value 00000000
systab名字
st_value 00000000
systab名字
st_value 00000000
systab名字
st_value 00000000
systab名字app_print_backtrace.c
st_value 00000000
systab名字user_lib.c
st_value 00000000
systab名字snprintf.c
st_value 81000190
systab名字vsnprintf
st_value 81000016
systab名字f7
st_value 8100003e
systab名字f5
st_value 8100016a
systab名字print_backtrace
st_value 81000066
systab名字f3
st_value 8100007a
systab名字f2
st_value 810000a2
systab名字main
st_value 810000ca
systab名字do_user_call
st_value 81000000
systab名字f8
st_value 81000052
systab名字f4
st_value 8100008e
systab名字f1
st_value 810000e2
systab名字printu
st_value 81000144
systab名字exit
st_value 8100002a
systab名字f6

此外,可以通过对current->trapframe->regs.s0循环查询函数调用栈。从中可以找到不同栈的fp和ra。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
regs.s0:810fff60
ra:8100000e
fp:810fff80
ra:81000036
fp:810fff90
ra:8100004a
fp:810fffa0
ra:8100005e
fp:810fffb0
ra:81000072
fp:810fffc0
ra:81000086
fp:810fffd0
ra:8100009a
fp:810fffe0
ra:810000ba

可以发现ra和value存在对应关系。

有一个问题,81000000对应的有一个空字符串…所以需要特判st_info字段是否为18(FUNC宏定义)。为18时代表这个字段是有意义的。至于为什么是18….百度..一下。很多信息藏在elf(5) - Linux manual page (man7.org)里面。

image-20230303205923034

Lab1_challenge2 打印异常代码行

目标:内核能够输出触发异常的用户程序的源文件名和对应代码行

elf.c中给出了debug_line段的解析函数make_addr_line。

1
make_addr_line(elf_ctx *ctx, char *debug_line, uint64 length)

elf文件中名为.debug_line的段保存到缓冲区中,然后将缓冲区指针传入这个参数;length为.debug_line段数据的长度。

函数调用结束后,process结构体的dir、file、line三个指针会各指向一个数组。

dir数组存储

所有代码文件的文件夹路径字符串指针

file数组存储所有代码文件的文件名字符串指针以及其文件夹路径在dir数组中的索引

file数组存储所有代码文件的文件名字符串指针以及其文件夹路径在dir数组中的索引

为完成该挑战,需要利用用户程序编译时产生的调试信息,目前最广泛使用的调试信息格式是DWARF

在lab1_challenge1中我们还可以得到elf文件中各个段的名字。可以发现.debug_line段就在其中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
section名字
section名字.text
section名字.rodata.str1.8
section名字.rodata
section名字.debug_info
section名字.debug_abbrev
section名字.debug_aranges
section名字.debug_line
section名字.debug_str
section名字.comment
section名字.riscv.attributes
section名字.debug_frame
section名字.debug_loc
section名字.debug_ranges
section名字.symtab
section名字.strtab
section名字.shstrtab

所以我们先参照前面的方法读取出debuf_line段的地址,再调用解析函数make_addr_line。

得到的三个数组按道理是存在process结构体里面,但是我查找process.h文件没有发现有这些东西。实际上该函数里面的做法如下:

1
2
3
4
5
6
7
8
9
void make_addr_line(elf_ctx *ctx, char *debug_line, uint64 length) {
process *p = ((elf_info *)ctx->info)->p;
p->debugline = debug_line;
// directory name char pointer array
p->dir = (char **)((((uint64)debug_line + length + 7) >> 3) << 3); int dir_ind = 0, dir_base;
// file name char pointer array
p->file = (code_file *)(p->dir + 64); int file_ind = 0, file_base;
// table array
p->line = (addr_line *)(p->file + 64); p->line_ind = 0;

所以我们仿照这个做法试试。

注意,还是要把两个函数声明在elf.h头文件中供其他调用。

1
2
3
uint64 elf_fpread(elf_ctx *ctx, void *dest, uint64 nb, uint64 offset);
void make_addr_line(elf_ctx *ctx, char *debug_line, uint64 length);

问题:make_addr_line的第二个参数是 char *指针类型,而我们定义的指向.debug_line段数据的指针是Elf64_Shdr类型的…所以强转看看。(错误理解)

不是强转,而是缓冲区,开一个char数组即可。

还有elf_info这个结构体也没有定义。定义在elf.c中。把它移到.h中。

1
2
3
4
typedef struct elf_info_t {
spike_file_t *f;
process *p;
} elf_info;

不能直接移。需要链接到spike.h。。。。md

发现一个问题。如果直接运行,结果是illegal instruction,这是正常的。

换成访问process里面的东西的时候就变成了Load access fault!

原来process_t里面的结构被改了,变成下面的样子

1
2
3
4
5
6
7
8
9
10
// the extremely simple definition of process, used for begining labs of PKE
typedef struct process_t {
// pointing to the stack used in trap handling.
uint64 kstack;
// trapframe storing the context of a (User mode) process.
trapframe* trapframe;

// added @lab1_challenge2
char *debugline; char **dir; code_file *file; addr_line *line; int line_ind;
}process;

其中code_file以及addr_line的定义又如下:

1
2
3
4
5
6
7
8
9
// code file struct, including directory index and file name char pointer
typedef struct {
uint64 dir; char *file;
} code_file;

// address-line number-file name table
typedef struct {
uint64 addr, line, file;
} addr_line;

在读到缓冲区的时候出现misaligned load的错误…要定义在.h文件里面…

…数组要开8000+ “那么这个数组必须足够大”

打开文件的时候不能用fopen,而应该用spike_file里面的spike_file_open。相应的,FILE文件应该用spike_file_t*

1
2
3
4
typedef struct file {
int kfd; // file descriptor of the host file
uint32 refcnt;
} spike_file_t;

没有strcat函数…

…麻

lab2_challenge2 堆空间管理

这是碰都不能碰的滑梯.jpg

申请100和50个字节的一个物理页的内存,然后使用better_free释放掉100个字节,向50个字节中复制一串字符串,进行输出。原本的pke中malloc的实现是非常简化的(一次直接分配一个页面),你的挑战任务是修改内核(包括machine文件夹下)的代码,使得应用程序的malloc能够在一个物理页中分配,并对各申请块进行合理的管理

首先要做的当然是在顺着系统调用的次序逐步跟随malloc和free找到userlib和syscall中对应的代码,然后将前者改为better_malloc和better_free。改完之后自然是实现。原来的实现是直接调用user_vm_map和user_vm_unmap进行映射,所以我们找到vmm.h,在其中加入user_better_free和user_better_malloc函数。

执行过程中要用到current,所以还要include process.h

lab3_challenge2 实现信号量

简单来说就是在user_lib和syscall里面实现sem_new, sem_P, sem_V操作。

sem_new(n):找到一个未被占用的信号量,初值设置为n

sem_P(n):对编号为n的信号量执行P操作。还需要进行进程调度。维护一个等待队列。

sem_V(n):同理。

需要添加的工作是:

在process中添加init_sem_pool(参考线程池),并声明信号灯数组。注意,信号池和线程池的初始化需要在process.c里面一起实现。

然后参照系统调用的步骤在userlib、kernel和syscall里面加上三个函数的调用实现。

Tags: 笔记
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章