mirror of
https://github.com/DragonOS-Community/DragonOS.git
synced 2025-06-18 12:16:31 +00:00
🆕 内核栈反向追踪
This commit is contained in:
21
Makefile
21
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:
|
||||
|
11
README.md
11
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] 内核栈反向跟踪
|
||||
|
||||
- [ ] 动态加载模块
|
||||
|
||||
|
12
README_EN.md
12
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
|
||||
|
||||
|
@ -18,9 +18,7 @@ LD_LIST := head.o
|
||||
OBJ_LIST := head.o
|
||||
|
||||
|
||||
kernel_subdirs := common driver process
|
||||
|
||||
|
||||
kernel_subdirs := common driver process debug
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -163,3 +178,8 @@ 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)
|
||||
@list='$(kernel_subdirs)'; for subdir in $$list; do \
|
||||
echo "Clean in dir: $$subdir";\
|
||||
cd $$subdir && $(MAKE) clean;\
|
||||
cd .. ;\
|
||||
done
|
2
kernel/debug/.gitignore
vendored
Normal file
2
kernel/debug/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
kallsyms
|
||||
kallsyms.S
|
24
kernel/debug/Makefile
Normal file
24
kernel/debug/Makefile
Normal file
@ -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
kernel/debug/kallsyms.c
Normal file
200
kernel/debug/kallsyms.c
Normal file
@ -0,0 +1,200 @@
|
||||
/**
|
||||
* @file kallsyms.c
|
||||
* @author longjin (longjin@RinGoTek.cn)
|
||||
* @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
kernel/debug/traceback/traceback.c
Normal file
69
kernel/debug/traceback/traceback.c
Normal file
@ -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
kernel/debug/traceback/traceback.h
Normal file
17
kernel/debug/traceback/traceback.h
Normal file
@ -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);
|
@ -10,3 +10,6 @@ all:
|
||||
$(MAKE) all CFLAGS="$(CFLAGS)";\
|
||||
cd ..;\
|
||||
done
|
||||
|
||||
clean:
|
||||
echo "Done."
|
@ -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();
|
||||
}
|
||||
|
@ -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."
|
@ -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)
|
||||
;
|
||||
|
Reference in New Issue
Block a user