Browse Source

:new: 内核栈反向追踪

fslongjin 2 years ago
parent
commit
1ab51cb334

+ 12 - 9
Makefile

@@ -14,16 +14,19 @@ GLOBAL_CFLAGS += -g
 endif
 
 .PHONY: all
-all:
+all: kernel user
+	
+
+
+.PHONY: kernel
+kernel:
 	mkdir -p bin/kernel/
-	mkdir -p bin/user/
-	mkdir -p bin/tmp/
-	@list='$(SUBDIRS)'; for subdir in $$list; do \
-    		echo "make all in $$subdir";\
-    		cd $$subdir;\
-    		 $(MAKE) all;\
-    		cd ..;\
-    done
+	@list='./kernel'; for subdir in $$list; do \
+				echo "make all in $$subdir";\
+				cd $$subdir;\
+				$(MAKE) all;\
+				cd ..;\
+		done
 
 .PHONY: user
 user:

+ 8 - 3
README.md

@@ -6,6 +6,11 @@
 
 这是一个运行于x86_64平台的64位操作系统。目前正在开发之中!
 
+## 网站
+- 项目官网  **[DragonOS.org](https://dragonos.org)**
+- 项目文档  **[docs.DragonOS.org](https://docs.dragonos.org)**
+- 开源论坛  **[bbs.DragonOS.org](https://bbs.dragonos.org)**
+ 
 ## 开发环境
 
 GCC>=8.0
@@ -72,11 +77,11 @@ grub==2.06
 
 - [x] 浮点数支持
 
-- [ ] 基于POSIX实现系统调用库
+- [x] 基于POSIX实现系统调用库
 
-- [ ] Shell
+- [x] Shell
 
-- [ ] 内核栈反向跟踪
+- [x] 内核栈反向跟踪
 
 - [ ] 动态加载模块
 

+ 9 - 3
README_EN.md

@@ -6,6 +6,12 @@
 
 This project is a operating system running on computer which is in X86_ 64 Architecture . The DragonOS is currently under development!
 
+## Websites
+- Home Page  **[DragonOS.org](https://dragonos.org)**
+- Documentation  **[docs.DragonOS.org](https://docs.dragonos.org)**
+- BBS  **[bbs.DragonOS.org](https://bbs.dragonos.org)**
+ 
+
 ## Development Environment
 
 GCC>=8.0
@@ -72,11 +78,11 @@ grub==2.06
 
 - [x] Floating point support
 
-- [ ] Implementation of system call library based on POSIX
+- [x] Implementation of system call library based on POSIX
 
-- [ ] Shell
+- [x] Shell
 
-- [ ] Kernel stack backtracking
+- [x] Kernel stack backtracking
 
 - [ ] Dynamic loading module
 

+ 26 - 6
kernel/Makefile

@@ -18,12 +18,10 @@ LD_LIST := head.o
 OBJ_LIST := head.o
 
 
-kernel_subdirs := common driver process
+kernel_subdirs := common driver process debug
 	
 
 
-
-
 head.o: head.S
 	gcc -E head.S > head.s # 预处理
 	as $(ASFLAGS) -o head.o head.s
@@ -144,17 +142,34 @@ uart.o: driver/uart/uart.c
 	gcc $(CFLAGS) -c driver/uart/uart.c -o driver/uart/uart.o
 
 
-all: kernel
 
+all: kernel
+	echo "Linking kernel..."
 	ld -b elf64-x86-64 -z muldefs -o kernel head.o main.o $(shell find . -name "*.o") -T link.lds
+# 生成kallsyms
+	current_dir=$(pwd)
+	
+	@dbg='debug';for x in $$dbg; do \
+		cd $$x;\
+		$(MAKE) generate_kallsyms kernel_root_path="$(shell pwd)";\
+		cd ..;\
+	done
+	
+# 重新链接
+	echo "Re-Linking kernel..."
+	ld -b elf64-x86-64 -z muldefs -o kernel head.o main.o $(shell find . -name "*.o") ./debug/kallsyms.o -T link.lds
+	echo "Generating kernel ELF file..."
+# 生成内核文件
 	objcopy -I elf64-x86-64 -O elf64-x86-64 -R ".comment" -R ".eh_frame" kernel ../bin/kernel/kernel.elf
+	echo "Done."
+
 
 kernel: head.o entry.o main.o printk.o trap.o mm.o slab.o irq.o pic.o sched.o syscall.o multiboot2.o cpu.o acpi.o ps2_keyboard.o ps2_mouse.o ata.o pci.o ahci.o smp.o apu_boot.o rtc.o HPET.o softirq.o timer.o fat32.o MBR.o VFS.o $(OBJ_LIST)
 	
 	@list='$(kernel_subdirs)'; for subdir in $$list; do \
     		echo "make all in $$subdir";\
     		cd $$subdir;\
-    		$(MAKE) all CFLAGS="$(CFLAGS)" ASFLAGS="$(ASFLAGS)";\
+    		$(MAKE) all CFLAGS="$(CFLAGS)" ASFLAGS="$(ASFLAGS)" kernel_root_path="$(shell pwd)";\
     		cd ..;\
 	done
 
@@ -162,4 +177,9 @@ kernel: head.o entry.o main.o printk.o trap.o mm.o slab.o irq.o pic.o sched.o sy
 
 
 clean: 
-	rm -rf $(GARBAGE)
+	rm -rf $(GARBAGE)
+	@list='$(kernel_subdirs)'; for subdir in $$list; do \
+		echo "Clean in dir: $$subdir";\
+		cd $$subdir && $(MAKE) clean;\
+		cd .. ;\
+	done

+ 2 - 0
kernel/debug/.gitignore

@@ -0,0 +1,2 @@
+kallsyms
+kallsyms.S

+ 24 - 0
kernel/debug/Makefile

@@ -0,0 +1,24 @@
+
+all: traceback.o
+
+CFLAGS += -I .
+
+kallsyms.o: kallsyms.c
+	gcc -o kallsyms kallsyms.c 
+	rm -rf kallsyms.o
+
+traceback.o: traceback/traceback.c
+	gcc $(CFLAGS) -c traceback/traceback.c -o traceback/traceback.o
+
+
+# 生成内核栈符号表的汇编文件
+generate_kallsyms: kallsyms.o 
+	echo "Generating kallsyms..."
+	
+	nm -n $(kernel_root_path)/kernel | ./kallsyms > kallsyms.S
+	gcc -c kallsyms.S -o kallsyms.o
+	echo "Kallsyms generated."
+
+
+clean:
+	rm -rf kallsyms

+ 200 - 0
kernel/debug/kallsyms.c

@@ -0,0 +1,200 @@
+/**
+ * @file kallsyms.c
+ * @author longjin ([email protected])
+ * @brief 内核栈跟踪
+ * @version 0.1
+ * @date 2022-06-22
+ *
+ * @copyright Copyright (c) 2022
+ *
+ */
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * @brief 判断符号是否需要被输出(只输出text段内的符号)
+ *
+ */
+#define symbol_to_write(vaddr, tv, etv) \
+    ((vaddr < tv || vaddr > etv) ? 0 : 1)
+
+/**
+ * @brief 使用nm命令提取出来的信息存到这个结构体之中
+ *
+ */
+struct kernel_symbol_entry_t
+{
+    uint64_t vaddr;
+    char type;
+    char *symbol;
+    int symbol_length;
+};
+
+struct kernel_symbol_entry_t *symbol_table;
+// 符号表最大能容纳的entry数量
+uint64_t table_size = 0;
+// 符号表当前的entry数量
+uint64_t entry_count = 0;
+// 符号表中,text和etext的下标
+uint64_t text_vaddr, etext_vaddr;
+
+/**
+ * @brief 读取一个符号到entry之中
+ *
+ * @param filp stdin的文件指针
+ * @param entry 待填写的entry
+ * @return int 返回码
+ */
+int read_symbol(FILE *filp, struct kernel_symbol_entry_t *entry)
+{
+    // 本函数假设nm命令输出的结果中,每行最大512字节
+    char str[512] = {0};
+    int retval = fscanf(filp, "%llx %c %510s\n", &entry->vaddr, &entry->type, str);
+
+    // 如果当前行不符合要求
+    if (retval != 3)
+    {
+        if (retval != EOF)
+        {
+            // 如果不是输入流的结尾,说明该行不符合要求,将其过滤
+            fgets(str, 512, filp);
+        }
+
+        return -1;
+    }
+    // malloc一块内存,然后把str的内容拷贝进去,接着修改symbol指针
+    entry->symbol = strdup(str);
+    entry->symbol_length = strlen(str) + 1; // +1的原因是.asciz指令会在字符串末尾自动添加结束符\0
+    return 0;
+}
+
+/**
+ * @brief 接收标准输入流的数据,解析nm命令输出的内容
+ *
+ * @param filp
+ */
+void read_map(FILE *filp)
+{
+    // 循环读入数据直到输入流结束
+    while (!feof(filp))
+    {
+        // 给符号表扩容
+        if (entry_count >= table_size)
+        {
+            table_size += 100;
+            // 由于使用了realloc,因此符号表原有的内容会被自动的copy过去
+            symbol_table = (struct kernel_symbol_entry_t *)realloc(symbol_table, sizeof(struct kernel_symbol_entry_t) * table_size);
+        }
+
+        // 若成功读取符号表的内容,则将计数器+1
+        if (read_symbol(filp, &symbol_table[entry_count]) == 0)
+            ++entry_count;
+    }
+
+    // 查找符号表中的text和etext标签
+    for (uint64_t i = 0; i < entry_count; ++i)
+    {
+        if (strcmp(symbol_table[i].symbol, "_text")==0)
+            text_vaddr = symbol_table[i].vaddr;
+        if (strcmp(symbol_table[i].symbol, "_etext")==0)
+            etext_vaddr = symbol_table[i].vaddr;
+    }
+}
+
+/**
+ * @brief 输出最终的kallsyms汇编代码文件
+ * 直接输出到stdout,通过命令行的 > 命令,写入文件
+ */
+void generate_result()
+{
+    printf(".section .rodata\n\n");
+    printf(".global kallsyms_address\n");
+    printf(".align 8\n\n");
+
+    printf("kallsyms_address:\n"); // 地址数组
+
+    uint64_t last_vaddr = 0;
+    uint64_t total_syms_to_write = 0; // 真正输出的符号的数量
+
+    // 循环写入地址数组
+    for (uint64_t i = 0; i < entry_count; ++i)
+    {
+        // 判断是否为text段的符号
+        if (!symbol_to_write(symbol_table[i].vaddr, text_vaddr, etext_vaddr))
+            continue;
+
+        if (symbol_table[i].vaddr == last_vaddr)
+            continue;
+
+        // 输出符号地址
+        printf("\t.quad\t%#llx\n", symbol_table[i].vaddr);
+        ++total_syms_to_write;
+
+        last_vaddr = symbol_table[i].vaddr;
+    }
+
+    putchar('\n');
+
+    // 写入符号表的表项数量
+    printf(".global kallsyms_num\n");
+    printf(".align 8\n");
+    printf("kallsyms_num:\n");
+    printf("\t.quad\t%lld\n", total_syms_to_write);
+
+    putchar('\n');
+
+    // 循环写入符号名称的下标索引
+    printf(".global kallsyms_names_index\n");
+    printf(".align 8\n");
+    printf("kallsyms_names_index:\n");
+    uint64_t position = 0;
+    last_vaddr = 0;
+    for (uint64_t i = 0; i < entry_count; ++i)
+    {
+        // 判断是否为text段的符号
+        if (!symbol_to_write(symbol_table[i].vaddr, text_vaddr, etext_vaddr))
+            continue;
+
+        if (symbol_table[i].vaddr == last_vaddr)
+            continue;
+
+        // 输出符号名称的偏移量
+        printf("\t.quad\t%lld\n", position);
+        position += symbol_table[i].symbol_length;
+        last_vaddr = symbol_table[i].vaddr;
+    }
+
+    putchar('\n');
+
+    // 输出符号名
+    printf(".global kallsyms_names\n");
+    printf(".align 8\n");
+    printf("kallsyms_names:\n");
+
+    last_vaddr = 0;
+    for (uint64_t i = 0; i < entry_count; ++i)
+    {
+        // 判断是否为text段的符号
+        if (!symbol_to_write(symbol_table[i].vaddr, text_vaddr, etext_vaddr))
+            continue;
+
+        if (symbol_table[i].vaddr == last_vaddr)
+            continue;
+
+        // 输出符号名称
+        printf("\t.asciz\t\"%s\"\n", symbol_table[i].symbol);
+        
+        last_vaddr = symbol_table[i].vaddr;
+    }
+
+    putchar('\n');
+    
+}
+int main(int argc, char **argv)
+{
+    read_map(stdin);
+
+    generate_result();
+}

+ 69 - 0
kernel/debug/traceback/traceback.c

@@ -0,0 +1,69 @@
+#include "traceback.h"
+#include <common/printk.h>
+#include <process/process.h>
+
+static int lookup_kallsyms(uint64_t addr, int level)
+{
+    const char *str = (const char *)&kallsyms_names;
+
+    // 暴力查找符合要求的symbol
+    // todo: 改用二分搜索。
+    // 由于符号表使用nm -n生成,因此是按照地址升序排列的,因此可以二分
+    uint64_t index = 0;
+    for (index = 0; index < kallsyms_num - 1; ++index)
+    {
+        if (addr > kallsyms_address[index] && addr <= kallsyms_address[index + 1])
+            break;
+    }
+
+    if (index < kallsyms_num) // 找到对应的函数
+    {
+        // 依次输出函数名称、rip离函数起始处的偏移量、函数执行的rip
+        printk("function:%s() \t(+) %04d address:%#018lx\n", &str[kallsyms_names_index[index]], addr - kallsyms_address[index], addr);
+        return 0;
+    }
+    else
+        return -1;
+}
+
+/**
+ * @brief 追溯内核栈调用情况
+ *
+ * @param regs 内核栈结构体
+ */
+void traceback(struct pt_regs *regs)
+{
+    // 先检验是否为用户态出错,若为用户态出错,则直接返回
+    if (verify_area(regs->rbp, 0))
+    {
+        printk_color(YELLOW, BLACK, "Kernel traceback: Fault in userland. pid=%ld, rbp=%#018lx\n", current_pcb->pid, regs->rbp);
+        return;
+    }
+
+    uint64_t *rbp = (uint64_t *)regs->rbp;
+    printk_color(YELLOW, BLACK, "======== Kernel traceback =======\n");
+    // printk("&kallsyms_address:%#018lx,kallsyms_address:%#018lx\n", &kallsyms_address, kallsyms_address);
+    // printk("&kallsyms_syms_num:%#018lx,kallsyms_syms_num:%d\n", &kallsyms_num, kallsyms_num);
+    // printk("&kallsyms_index:%#018lx\n", &kallsyms_names_index);
+    // printk("&kallsyms_names:%#018lx,kallsyms_names:%s\n", &kallsyms_names, &kallsyms_names);
+
+    uint64_t ret_addr = regs->rip;
+    // 最大追踪10层调用栈
+    for (int i = 0; i < 10; ++i)
+    {
+        printk_color(ORANGE, BLACK, "rbp:%#018lx,*rbp:%#018lx\n", rbp, *rbp);
+        if (lookup_kallsyms(ret_addr, i) != 0)
+            break;
+
+        // 由于内核栈大小32K,因此当前rbp的值为按照32K对齐时,表明调用栈已经到头了,追踪结束。
+        if (((*rbp) & (~STACK_SIZE)) == 0)
+            break;
+
+        // 由于x86处理器在执行call指令时,先将调用返回地址压入栈中,然后再把函数的rbp入栈,最后将rsp设为新的rbp。
+        // 因此,此处的rbp就是上一层的rsp,那么,*(rbp+1)得到的就是上一层函数的返回地址
+        ret_addr = *(rbp + 1);
+        rbp = (uint64_t *)(*rbp);
+        printk("\n");
+    }
+    printk_color(YELLOW, BLACK, "======== Kernel traceback end =======\n");
+}

+ 17 - 0
kernel/debug/traceback/traceback.h

@@ -0,0 +1,17 @@
+#pragma once
+#include <common/glib.h>
+#include<process/ptrace.h>
+
+// 使用弱引用属性导出kallsyms中的符号表。
+// 采用weak属性是由于第一次编译时,kallsyms还未链接进来,若不使用weak属性则会报错
+extern const uint64_t kallsyms_address[] __attribute__((weak));
+extern const uint64_t kallsyms_num __attribute__((weak));
+extern const uint64_t kallsyms_names_index[] __attribute__((weak));
+extern const char* kallsyms_names __attribute__((weak));
+
+/**
+ * @brief 追溯内核栈调用情况
+ * 
+ * @param regs 内核栈结构体
+ */
+void traceback(struct pt_regs * regs);

+ 3 - 0
kernel/driver/Makefile

@@ -10,3 +10,6 @@ all:
     		$(MAKE) all CFLAGS="$(CFLAGS)";\
     		cd ..;\
 	done
+
+clean:
+	echo "Done."

+ 5 - 3
kernel/exception/trap.c

@@ -3,12 +3,14 @@
 #include "../process/ptrace.h"
 #include "../common/kprint.h"
 #include <process/process.h>
+#include <debug/traceback/traceback.h>
 
 // 0 #DE 除法错误
 void do_divide_error(struct pt_regs *regs, unsigned long error_code)
 {
     // kerror("do_divide_error(0)");
     kerror("do_divide_error(0),\tError Code:%#18lx,\tRSP:%#18lx,\tRIP:%#18lx\t CPU:%d\t pid=%d\n", error_code, regs->rsp, regs->rip, proc_current_cpu_id, current_pcb->pid);
+    traceback(regs);
     current_pcb->state = PROC_STOPPED;
     while (1)
         hlt();
@@ -75,8 +77,8 @@ void do_bounds(struct pt_regs *regs, unsigned long error_code)
 void do_undefined_opcode(struct pt_regs *regs, unsigned long error_code)
 {
 
-    kerror("do_undefined_opcode(6),\tError Code:%#18lx,\tRSP:%#18lx,\tRIP:%#18lx\t CPU:%d", error_code, regs->rsp, regs->rip, proc_current_cpu_id);
-
+    kerror("do_undefined_opcode(6),\tError Code:%#18lx,\tRSP:%#18lx,\tRIP:%#18lx\t CPU:%d, pid:%ld", error_code, regs->rsp, regs->rip, proc_current_cpu_id, current_pcb->pid);
+    traceback(regs);
     while (1)
         hlt();
 }
@@ -98,7 +100,7 @@ void do_double_fault(struct pt_regs *regs, unsigned long error_code)
     printk("[ ");
     printk_color(RED, BLACK, "Terminate");
     printk(" ] do_double_fault(8),\tError Code:%#18lx,\tRSP:%#18lx,\tRIP:%#18lx\t CPU:%d\n", error_code, regs->rsp, regs->rip, proc_current_cpu_id);
-
+    traceback(regs);
     while (1)
         hlt();
 }

+ 3 - 0
kernel/process/Makefile

@@ -14,3 +14,6 @@ process.o: process.c
 
 wait_queue.o: wait_queue.c
 	gcc $(CFLAGS) -c wait_queue.c -o wait_queue.o
+
+clean:
+	echo "Done."

+ 1 - 0
user/apps/shell/shell.c

@@ -73,6 +73,7 @@ int main()
     int kb_fd = open(kb_file_path, 0);
     // printf("keyboard fd = %d\n", kb_fd);
     print_ascii_logo();
+    int a = 1/0;
     main_loop(kb_fd);
     while (1)
         ;