diff --git a/CMakeLists.txt b/CMakeLists.txt index df85f28..9a7f78d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ set(LD "x86_64-elf-ld" CACHE STRING "Kernel linker") set(USER_CC "cc" CACHE STRING "User-space C compiler") set(USER_LD "ld" CACHE STRING "User-space linker") set(RUSTC "rustc" CACHE STRING "Rust compiler") +set(NM "nm" CACHE STRING "nm executable") set(XORRISO "xorriso" CACHE STRING "xorriso executable") set(TAR "tar" CACHE STRING "tar executable") @@ -87,6 +88,7 @@ set(RAMDISK_ROOT "${BUILD_ROOT}/ramdisk_root") set(KERNEL_ELF "${BUILD_ROOT}/clks_kernel.elf") set(RAMDISK_IMAGE "${BUILD_ROOT}/cleonos_ramdisk.tar") set(ISO_IMAGE "${CMAKE_SOURCE_DIR}/build/CLeonOS-x86_64.iso") +set(KERNEL_SYMBOLS_FILE "${BUILD_ROOT}/kernel.sym") set(USER_BUILD_ROOT "${BUILD_ROOT}/user") set(USER_OBJ_ROOT "${USER_BUILD_ROOT}/obj") @@ -99,6 +101,7 @@ resolve_tool_with_fallback(CC gcc cc clang) resolve_tool_with_fallback(LD ld.lld ld) resolve_tool_with_fallback(USER_CC cc gcc clang) resolve_tool_with_fallback(USER_LD ld.lld ld) +resolve_tool_with_fallback(NM llvm-nm x86_64-elf-nm nm) resolve_tool_with_fallback(OBJCOPY_FOR_TARGET llvm-objcopy x86_64-linux-gnu-objcopy objcopy) resolve_tool_with_fallback(OBJDUMP_FOR_TARGET llvm-objdump x86_64-linux-gnu-objdump objdump) resolve_tool_with_fallback(READELF_FOR_TARGET llvm-readelf x86_64-linux-gnu-readelf readelf) @@ -331,6 +334,21 @@ add_custom_command( add_custom_target(kernel DEPENDS "${KERNEL_ELF}") +add_custom_command( + OUTPUT "${KERNEL_SYMBOLS_FILE}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${BUILD_ROOT}" + COMMAND ${CMAKE_COMMAND} + "-DNM_TOOL=${NM}" + "-DKERNEL_ELF=${KERNEL_ELF}" + "-DOUT_SYMBOL_FILE=${KERNEL_SYMBOLS_FILE}" + -P "${CMAKE_SOURCE_DIR}/cmake/gen_kernel_symbols.cmake" + DEPENDS "${KERNEL_ELF}" "${CMAKE_SOURCE_DIR}/cmake/gen_kernel_symbols.cmake" + VERBATIM +) + +add_custom_target(kernel-symbols DEPENDS "${KERNEL_SYMBOLS_FILE}") +add_dependencies(kernel-symbols kernel) + set(USER_COMMON_OBJECTS) foreach(SRC IN LISTS USER_COMMON_SOURCES) add_user_c_object("${SRC}" OBJ_OUT) @@ -496,9 +514,10 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E make_directory "${RAMDISK_ROOT}" COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/ramdisk" "${RAMDISK_ROOT}" COMMAND ${CMAKE_COMMAND} -E make_directory "${RAMDISK_ROOT}/system" "${RAMDISK_ROOT}/shell" "${RAMDISK_ROOT}/driver" + COMMAND ${CMAKE_COMMAND} -E copy "${KERNEL_SYMBOLS_FILE}" "${RAMDISK_ROOT}/system/kernel.sym" ${RAMDISK_COPY_COMMANDS} COMMAND ${CMAKE_COMMAND} -E touch "${RAMDISK_ROOT_STAMP}" - DEPENDS ${RAMDISK_FILES} ${USER_APP_OUTPUTS} + DEPENDS ${RAMDISK_FILES} ${USER_APP_OUTPUTS} "${KERNEL_SYMBOLS_FILE}" VERBATIM ) @@ -526,6 +545,7 @@ add_custom_target(setup-tools "-DOBJCOPY_TOOL=${OBJCOPY_FOR_TARGET}" "-DOBJDUMP_TOOL=${OBJDUMP_FOR_TARGET}" "-DREADELF_TOOL=${READELF_FOR_TARGET}" + "-DNM_TOOL=${NM}" "-DUSER_CC_TOOL=${USER_CC}" "-DUSER_LD_TOOL=${USER_LD}" "-DRUSTC_TOOL=${RUSTC}" diff --git a/clks/include/clks/panic.h b/clks/include/clks/panic.h index 8ceffe8..c1e1b55 100644 --- a/clks/include/clks/panic.h +++ b/clks/include/clks/panic.h @@ -5,6 +5,11 @@ #include CLKS_NORETURN void clks_panic(const char *reason); -CLKS_NORETURN void clks_panic_exception(const char *name, u64 vector, u64 error_code, u64 rip); +CLKS_NORETURN void clks_panic_exception(const char *name, + u64 vector, + u64 error_code, + u64 rip, + u64 rbp, + u64 rsp); -#endif \ No newline at end of file +#endif diff --git a/clks/kernel/interrupts.c b/clks/kernel/interrupts.c index 7d85bcf..0c98224 100644 --- a/clks/kernel/interrupts.c +++ b/clks/kernel/interrupts.c @@ -260,7 +260,12 @@ void clks_interrupt_dispatch(struct clks_interrupt_frame *frame) { return; } - clks_panic_exception(clks_exception_names[vector], vector, frame->error_code, frame->rip); + clks_panic_exception(clks_exception_names[vector], + vector, + frame->error_code, + frame->rip, + frame->rbp, + frame->rsp); } if (vector == CLKS_IRQ_TIMER) { diff --git a/clks/kernel/panic.c b/clks/kernel/panic.c index 83d94b5..c79d210 100644 --- a/clks/kernel/panic.c +++ b/clks/kernel/panic.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -8,6 +9,11 @@ #define CLKS_PANIC_BG 0x00200000U #define CLKS_PANIC_FG 0x00FFE0E0U +#define CLKS_PANIC_BACKTRACE_MAX 20U +#define CLKS_PANIC_STACK_WINDOW_BYTES (128ULL * 1024ULL) +#define CLKS_PANIC_SYMBOL_FILE "/system/kernel.sym" +#define CLKS_PANIC_KERNEL_ADDR_BASE 0xFFFF800000000000ULL + struct clks_panic_console { u32 cols; u32 rows; @@ -18,6 +24,9 @@ struct clks_panic_console { }; static clks_bool clks_panic_active = CLKS_FALSE; +static clks_bool clks_panic_symbols_checked = CLKS_FALSE; +static const char *clks_panic_symbols_data = CLKS_NULL; +static u64 clks_panic_symbols_size = 0ULL; static inline void clks_panic_disable_interrupts(void) { #if defined(CLKS_ARCH_X86_64) @@ -41,6 +50,41 @@ static void clks_panic_u64_to_hex(u64 value, char out[19]) { out[18] = '\0'; } +static void clks_panic_u32_to_dec(u32 value, char *out, usize out_size) { + char tmp[11]; + usize len = 0U; + usize i; + + if (out == CLKS_NULL || out_size == 0U) { + return; + } + + if (value == 0U) { + if (out_size >= 2U) { + out[0] = '0'; + out[1] = '\0'; + } else { + out[0] = '\0'; + } + return; + } + + while (value != 0U && len < sizeof(tmp)) { + tmp[len++] = (char)('0' + (value % 10U)); + value /= 10U; + } + + if (len + 1U > out_size) { + len = out_size - 1U; + } + + for (i = 0U; i < len; i++) { + out[i] = tmp[len - 1U - i]; + } + + out[len] = '\0'; +} + static clks_bool clks_panic_console_init(struct clks_panic_console *console) { struct clks_framebuffer_info info; @@ -118,19 +162,40 @@ static void clks_panic_console_put_char(struct clks_panic_console *console, char } } -static void clks_panic_console_write(struct clks_panic_console *console, const char *text) { +static void clks_panic_console_write_n(struct clks_panic_console *console, const char *text, usize len) { usize i = 0U; if (console == CLKS_NULL || text == CLKS_NULL) { return; } - while (text[i] != '\0') { + while (i < len) { clks_panic_console_put_char(console, text[i]); i++; } } +static void clks_panic_console_write(struct clks_panic_console *console, const char *text) { + if (console == CLKS_NULL || text == CLKS_NULL) { + return; + } + + clks_panic_console_write_n(console, text, clks_strlen(text)); +} + +static void clks_panic_serial_write_n(const char *text, usize len) { + usize i = 0U; + + if (text == CLKS_NULL) { + return; + } + + while (i < len) { + clks_serial_write_char(text[i]); + i++; + } +} + static void clks_panic_serial_write_line(const char *line) { if (line == CLKS_NULL) { return; @@ -140,12 +205,343 @@ static void clks_panic_serial_write_line(const char *line) { clks_serial_write("\n"); } +static clks_bool clks_panic_is_hex(char ch) { + if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) { + return CLKS_TRUE; + } + + return CLKS_FALSE; +} + +static u8 clks_panic_hex_value(char ch) { + if (ch >= '0' && ch <= '9') { + return (u8)(ch - '0'); + } + + if (ch >= 'a' && ch <= 'f') { + return (u8)(10 + (ch - 'a')); + } + + return (u8)(10 + (ch - 'A')); +} + +static clks_bool clks_panic_parse_symbol_line(const char *line, + usize len, + u64 *out_addr, + const char **out_name, + usize *out_name_len) { + usize i = 0U; + u64 addr = 0ULL; + u32 digits = 0U; + usize name_start; + usize name_end; + + if (line == CLKS_NULL || out_addr == CLKS_NULL || out_name == CLKS_NULL || out_name_len == CLKS_NULL) { + return CLKS_FALSE; + } + + if (len == 0U) { + return CLKS_FALSE; + } + + if (len >= 2U && line[0] == '0' && (line[1] == 'X' || line[1] == 'x')) { + i = 2U; + } + + while (i < len && clks_panic_is_hex(line[i]) == CLKS_TRUE) { + addr = (addr << 4) | (u64)clks_panic_hex_value(line[i]); + digits++; + i++; + } + + if (digits == 0U) { + return CLKS_FALSE; + } + + while (i < len && (line[i] == ' ' || line[i] == '\t')) { + i++; + } + + if (i >= len) { + return CLKS_FALSE; + } + + name_start = i; + name_end = len; + + while (name_end > name_start && + (line[name_end - 1U] == ' ' || line[name_end - 1U] == '\t' || line[name_end - 1U] == '\r')) { + name_end--; + } + + if (name_end <= name_start) { + return CLKS_FALSE; + } + + *out_addr = addr; + *out_name = &line[name_start]; + *out_name_len = name_end - name_start; + return CLKS_TRUE; +} + +static clks_bool clks_panic_symbols_ready(void) { + u64 size = 0ULL; + const void *data; + + if (clks_panic_symbols_checked == CLKS_TRUE) { + return (clks_panic_symbols_data != CLKS_NULL && clks_panic_symbols_size > 0ULL) ? CLKS_TRUE : CLKS_FALSE; + } + + clks_panic_symbols_checked = CLKS_TRUE; + + if (clks_fs_is_ready() == CLKS_FALSE) { + return CLKS_FALSE; + } + + data = clks_fs_read_all(CLKS_PANIC_SYMBOL_FILE, &size); + + if (data == CLKS_NULL || size == 0ULL) { + return CLKS_FALSE; + } + + clks_panic_symbols_data = (const char *)data; + clks_panic_symbols_size = size; + return CLKS_TRUE; +} + +static clks_bool clks_panic_lookup_symbol(u64 addr, const char **out_name, usize *out_name_len, u64 *out_base) { + const char *data; + const char *end; + const char *line; + const char *best_name = CLKS_NULL; + usize best_len = 0U; + u64 best_addr = 0ULL; + clks_bool found = CLKS_FALSE; + + if (out_name == CLKS_NULL || out_name_len == CLKS_NULL || out_base == CLKS_NULL) { + return CLKS_FALSE; + } + + *out_name = CLKS_NULL; + *out_name_len = 0U; + *out_base = 0ULL; + + if (clks_panic_symbols_ready() == CLKS_FALSE) { + return CLKS_FALSE; + } + + data = clks_panic_symbols_data; + end = clks_panic_symbols_data + clks_panic_symbols_size; + + while (data < end) { + u64 line_addr; + const char *line_name; + usize line_name_len; + usize line_len = 0U; + + line = data; + + while (data < end && *data != '\n') { + data++; + line_len++; + } + + if (data < end && *data == '\n') { + data++; + } + + if (clks_panic_parse_symbol_line(line, line_len, &line_addr, &line_name, &line_name_len) == CLKS_FALSE) { + continue; + } + + if (line_addr <= addr && (found == CLKS_FALSE || line_addr >= best_addr)) { + best_addr = line_addr; + best_name = line_name; + best_len = line_name_len; + found = CLKS_TRUE; + } + } + + if (found == CLKS_FALSE) { + return CLKS_FALSE; + } + + *out_name = best_name; + *out_name_len = best_len; + *out_base = best_addr; + return CLKS_TRUE; +} + +static void clks_panic_emit_bt_entry(struct clks_panic_console *console, u32 index, u64 rip) { + char index_dec[12]; + char rip_hex[19]; + const char *sym_name = CLKS_NULL; + usize sym_name_len = 0U; + u64 sym_base = 0ULL; + clks_bool has_symbol; + + clks_panic_u32_to_dec(index, index_dec, sizeof(index_dec)); + clks_panic_u64_to_hex(rip, rip_hex); + has_symbol = clks_panic_lookup_symbol(rip, &sym_name, &sym_name_len, &sym_base); + + clks_serial_write("[PANIC][BT] #"); + clks_serial_write(index_dec); + clks_serial_write(" "); + clks_serial_write(rip_hex); + + if (has_symbol == CLKS_TRUE) { + char off_hex[19]; + u64 off = rip - sym_base; + + clks_panic_u64_to_hex(off, off_hex); + clks_serial_write(" "); + clks_panic_serial_write_n(sym_name, sym_name_len); + clks_serial_write("+"); + clks_serial_write(off_hex); + } + + clks_serial_write("\n"); + + if (console == CLKS_NULL) { + return; + } + + clks_panic_console_write(console, "BT#"); + clks_panic_console_write(console, index_dec); + clks_panic_console_write(console, " "); + clks_panic_console_write(console, rip_hex); + + if (has_symbol == CLKS_TRUE) { + char off_hex[19]; + u64 off = rip - sym_base; + + clks_panic_u64_to_hex(off, off_hex); + clks_panic_console_write(console, " "); + clks_panic_console_write_n(console, sym_name, sym_name_len); + clks_panic_console_write(console, "+"); + clks_panic_console_write(console, off_hex); + } + + clks_panic_console_write(console, "\n"); +} + +static clks_bool clks_panic_stack_ptr_valid(u64 ptr, u64 stack_low, u64 stack_high) { + if ((ptr & 0x7ULL) != 0ULL) { + return CLKS_FALSE; + } + + if (ptr < stack_low || ptr + 16ULL > stack_high) { + return CLKS_FALSE; + } + + if (ptr < CLKS_PANIC_KERNEL_ADDR_BASE) { + return CLKS_FALSE; + } + + return CLKS_TRUE; +} + +static void clks_panic_emit_backtrace(struct clks_panic_console *console, u64 rip, u64 rbp, u64 rsp) { + u64 current_rbp; + u64 stack_low; + u64 stack_high; + u32 frame = 0U; + + if (rip == 0ULL) { + return; + } + + clks_panic_serial_write_line("[PANIC][BT] BEGIN"); + + if (console != CLKS_NULL) { + clks_panic_console_write(console, "\nBACKTRACE:\n"); + } + + clks_panic_emit_bt_entry(console, frame, rip); + frame++; + + if (rbp == 0ULL || rsp == 0ULL || frame >= CLKS_PANIC_BACKTRACE_MAX) { + clks_panic_serial_write_line("[PANIC][BT] END"); + return; + } + + stack_low = rsp; + stack_high = rsp + CLKS_PANIC_STACK_WINDOW_BYTES; + + if (stack_high <= stack_low) { + clks_panic_serial_write_line("[PANIC][BT] END"); + return; + } + + current_rbp = rbp; + + while (frame < CLKS_PANIC_BACKTRACE_MAX) { + const u64 *frame_ptr; + u64 next_rbp; + u64 ret_rip; + + if (clks_panic_stack_ptr_valid(current_rbp, stack_low, stack_high) == CLKS_FALSE) { + break; + } + + frame_ptr = (const u64 *)(usize)current_rbp; + next_rbp = frame_ptr[0]; + ret_rip = frame_ptr[1]; + + if (ret_rip == 0ULL) { + break; + } + + clks_panic_emit_bt_entry(console, frame, ret_rip); + frame++; + + if (next_rbp <= current_rbp) { + break; + } + + current_rbp = next_rbp; + } + + clks_panic_serial_write_line("[PANIC][BT] END"); +} + +static void clks_panic_capture_context(u64 *out_rip, u64 *out_rbp, u64 *out_rsp) { + if (out_rip != CLKS_NULL) { + *out_rip = 0ULL; + } + + if (out_rbp != CLKS_NULL) { + *out_rbp = 0ULL; + } + + if (out_rsp != CLKS_NULL) { + *out_rsp = 0ULL; + } + +#if defined(CLKS_ARCH_X86_64) + if (out_rbp != CLKS_NULL) { + __asm__ volatile("mov %%rbp, %0" : "=r"(*out_rbp)); + } + + if (out_rsp != CLKS_NULL) { + __asm__ volatile("mov %%rsp, %0" : "=r"(*out_rsp)); + } + + if (out_rip != CLKS_NULL) { + *out_rip = (u64)(usize)__builtin_return_address(0); + } +#endif +} + static CLKS_NORETURN void clks_panic_halt_loop(void) { clks_cpu_halt_forever(); } CLKS_NORETURN void clks_panic(const char *reason) { struct clks_panic_console console; + u64 rip = 0ULL; + u64 rbp = 0ULL; + u64 rsp = 0ULL; clks_panic_disable_interrupts(); @@ -154,6 +550,7 @@ CLKS_NORETURN void clks_panic(const char *reason) { } clks_panic_active = CLKS_TRUE; + clks_panic_capture_context(&rip, &rbp, &rsp); clks_panic_serial_write_line("[PANIC] CLeonOS KERNEL PANIC"); @@ -173,13 +570,21 @@ CLKS_NORETURN void clks_panic(const char *reason) { clks_panic_console_write(&console, "\n"); } + clks_panic_emit_backtrace(&console, rip, rbp, rsp); clks_panic_console_write(&console, "\nSystem halted. Please reboot the VM.\n"); + } else { + clks_panic_emit_backtrace(CLKS_NULL, rip, rbp, rsp); } clks_panic_halt_loop(); } -CLKS_NORETURN void clks_panic_exception(const char *name, u64 vector, u64 error_code, u64 rip) { +CLKS_NORETURN void clks_panic_exception(const char *name, + u64 vector, + u64 error_code, + u64 rip, + u64 rbp, + u64 rsp) { struct clks_panic_console console; char hex_buf[19]; @@ -230,10 +635,13 @@ CLKS_NORETURN void clks_panic_exception(const char *name, u64 vector, u64 error_ clks_panic_u64_to_hex(rip, hex_buf); clks_panic_console_write(&console, "RIP: "); clks_panic_console_write(&console, hex_buf); - clks_panic_console_write(&console, "\n\n"); + clks_panic_console_write(&console, "\n"); - clks_panic_console_write(&console, "System halted. Please reboot the VM.\n"); + clks_panic_emit_backtrace(&console, rip, rbp, rsp); + clks_panic_console_write(&console, "\nSystem halted. Please reboot the VM.\n"); + } else { + clks_panic_emit_backtrace(CLKS_NULL, rip, rbp, rsp); } clks_panic_halt_loop(); -} \ No newline at end of file +} diff --git a/cmake/check_tools.cmake b/cmake/check_tools.cmake index b59f155..0fe4ddb 100644 --- a/cmake/check_tools.cmake +++ b/cmake/check_tools.cmake @@ -29,10 +29,11 @@ require_tool("${LD_TOOL}") require_tool("${OBJCOPY_TOOL}") require_tool("${OBJDUMP_TOOL}") require_tool("${READELF_TOOL}") +require_tool("${NM_TOOL}") require_tool("${USER_CC_TOOL}") require_tool("${USER_LD_TOOL}") require_tool("${RUSTC_TOOL}") require_tool("${MAKE_TOOL}") require_tool("${SH_TOOL}") -cl_log_info("required tools are available") \ No newline at end of file +cl_log_info("required tools are available") diff --git a/cmake/gen_kernel_symbols.cmake b/cmake/gen_kernel_symbols.cmake new file mode 100644 index 0000000..581c6ec --- /dev/null +++ b/cmake/gen_kernel_symbols.cmake @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 3.20) + +if(NOT DEFINED NM_TOOL OR "${NM_TOOL}" STREQUAL "") + message(FATAL_ERROR "NM_TOOL is required") +endif() + +if(NOT DEFINED KERNEL_ELF OR "${KERNEL_ELF}" STREQUAL "") + message(FATAL_ERROR "KERNEL_ELF is required") +endif() + +if(NOT DEFINED OUT_SYMBOL_FILE OR "${OUT_SYMBOL_FILE}" STREQUAL "") + message(FATAL_ERROR "OUT_SYMBOL_FILE is required") +endif() + +execute_process( + COMMAND "${NM_TOOL}" -n "${KERNEL_ELF}" + RESULT_VARIABLE _nm_result + OUTPUT_VARIABLE _nm_output + ERROR_VARIABLE _nm_error +) + +if(NOT _nm_result EQUAL 0) + message(FATAL_ERROR "failed to run nm (${_nm_result}): ${_nm_error}") +endif() + +string(REPLACE "\r\n" "\n" _nm_output "${_nm_output}") +string(REPLACE "\r" "\n" _nm_output "${_nm_output}") +string(REPLACE "\n" ";" _nm_lines "${_nm_output}") + +set(_out_text "CLEONOS_KERNEL_SYMBOLS_V1\n") + +foreach(_line IN LISTS _nm_lines) + string(STRIP "${_line}" _line) + + if(_line STREQUAL "") + continue() + endif() + + if(_line MATCHES "^([0-9A-Fa-f]+)[ \t]+([tTwW])[ \t]+(.+)$") + set(_addr "${CMAKE_MATCH_1}") + set(_name "${CMAKE_MATCH_3}") + + if(_name MATCHES "^\\.") + continue() + endif() + + string(TOUPPER "${_addr}" _addr_upper) + set(_out_text "${_out_text}0X${_addr_upper} ${_name}\n") + endif() +endforeach() + +file(WRITE "${OUT_SYMBOL_FILE}" "${_out_text}")