diff --git a/arch/arm/cortex_m/link.x b/arch/arm/cortex_m/link.x index 5c055560..a97e2172 100644 --- a/arch/arm/cortex_m/link.x +++ b/arch/arm/cortex_m/link.x @@ -207,6 +207,14 @@ SECTIONS __bss_end = .; } > RAM AT > RAM + .coredump_bss (NOLOAD) : ALIGN(4096) + { + __coredump_buf_start = .; + *(.coredump_bss .coredump_bss.*) + . = ALIGN(4096); + __coredump_buf_end = .; + } > RAM + .heap (COPY) : { . = ALIGN(8); diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index 3e356a74..011a6bc8 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -2,6 +2,10 @@ menu "RISCV Configuration" +config 32BIT + bool "32-bit RISC-V" + default n + config NUM_IRQS int diff --git a/kconfig/BUILD.gn b/kconfig/BUILD.gn index 6e7616d9..8dbad393 100644 --- a/kconfig/BUILD.gn +++ b/kconfig/BUILD.gn @@ -19,6 +19,7 @@ _kconfig_files = [ "//kernel/arch/Kconfig", "//kernel/kernel/src/boards/${board}/Kconfig", "//kernel/kernel/src/Kconfig", + "//kernel/kernel/src/coredump/Kconfig", "//kernel/kernel/src/allocator/Kconfig", "//kernel/kernel/src/scheduler/Kconfig", "//kernel/kernel/src/vfs/Kconfig", diff --git a/kconfig/config/gd32e507_eval/debug/defconfig b/kconfig/config/gd32e507_eval/debug/defconfig index 471fe56a..fed374e9 100644 --- a/kconfig/config/gd32e507_eval/debug/defconfig +++ b/kconfig/config/gd32e507_eval/debug/defconfig @@ -22,6 +22,7 @@ CONFIG_ROUND_ROBIN=y CONFIG_ROBIN_SLICE=10 CONFIG_OVERFLOW_CHECK=y CONFIG_STACK_HIGHWATER_CHECK=y +CONFIG_ENABLE_COREDUMP=y #CONFIG_DEBUGGING_SCHEDULER=y CONFIG_MAIN_THREAD_STACK_SIZE=12288 CONFIG_IDLE_THREAD_STACK_SIZE=2048 diff --git a/kconfig/config/gd32e507_eval/release/defconfig b/kconfig/config/gd32e507_eval/release/defconfig index 70ca60f5..34888786 100644 --- a/kconfig/config/gd32e507_eval/release/defconfig +++ b/kconfig/config/gd32e507_eval/release/defconfig @@ -29,6 +29,7 @@ CONFIG_TIMER_THREAD_STACK_SIZE=2048 # CONFIG_FDT is not set # CONFIG_VIRTIO is not set CONFIG_PROCFS=y +CONFIG_ENABLE_COREDUMP=y CONFIG_ENABLE_NET=n # diff --git a/kconfig/config/gd32vw553_eval/coverage/defconfig b/kconfig/config/gd32vw553_eval/coverage/defconfig index 863ae100..441c578e 100644 --- a/kconfig/config/gd32vw553_eval/coverage/defconfig +++ b/kconfig/config/gd32vw553_eval/coverage/defconfig @@ -41,6 +41,7 @@ CONFIG_ENABLE_SYSCALL=y CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 CONFIG_ENABLE_VFS=y +CONFIG_ENABLE_COREDUMP=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y CONFIG_NETWORK_STACK_SIZE=24576 diff --git a/kconfig/config/gd32vw553_eval/debug/defconfig b/kconfig/config/gd32vw553_eval/debug/defconfig index 8dee11a2..0fd6790a 100644 --- a/kconfig/config/gd32vw553_eval/debug/defconfig +++ b/kconfig/config/gd32vw553_eval/debug/defconfig @@ -41,6 +41,7 @@ CONFIG_ENABLE_SYSCALL=y CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 CONFIG_ENABLE_VFS=y +CONFIG_ENABLE_COREDUMP=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=n CONFIG_NETWORK_STACK_SIZE=24576 diff --git a/kconfig/config/gd32vw553_eval/release/defconfig b/kconfig/config/gd32vw553_eval/release/defconfig index 8dee11a2..0fd6790a 100644 --- a/kconfig/config/gd32vw553_eval/release/defconfig +++ b/kconfig/config/gd32vw553_eval/release/defconfig @@ -41,6 +41,7 @@ CONFIG_ENABLE_SYSCALL=y CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 CONFIG_ENABLE_VFS=y +CONFIG_ENABLE_COREDUMP=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=n CONFIG_NETWORK_STACK_SIZE=24576 diff --git a/kconfig/config/qemu_mps2_an385/coverage/defconfig b/kconfig/config/qemu_mps2_an385/coverage/defconfig index 8a2d1f7e..1d07ce51 100644 --- a/kconfig/config/qemu_mps2_an385/coverage/defconfig +++ b/kconfig/config/qemu_mps2_an385/coverage/defconfig @@ -40,6 +40,7 @@ CONFIG_ENABLE_SYSCALL=y CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 CONFIG_ENABLE_VFS=y +CONFIG_ENABLE_COREDUMP=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y CONFIG_NETWORK_STACK_SIZE=24576 diff --git a/kconfig/config/qemu_mps2_an385/debug/defconfig b/kconfig/config/qemu_mps2_an385/debug/defconfig index 8a2d1f7e..40146f8a 100644 --- a/kconfig/config/qemu_mps2_an385/debug/defconfig +++ b/kconfig/config/qemu_mps2_an385/debug/defconfig @@ -39,6 +39,7 @@ CONFIG_ENABLE_SYSCALL=y # CONFIG_VIRTIO is not set CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 +CONFIG_ENABLE_COREDUMP=y CONFIG_ENABLE_VFS=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y diff --git a/kconfig/config/qemu_mps2_an385/release/defconfig b/kconfig/config/qemu_mps2_an385/release/defconfig index 8a2d1f7e..1d07ce51 100644 --- a/kconfig/config/qemu_mps2_an385/release/defconfig +++ b/kconfig/config/qemu_mps2_an385/release/defconfig @@ -40,6 +40,7 @@ CONFIG_ENABLE_SYSCALL=y CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 CONFIG_ENABLE_VFS=y +CONFIG_ENABLE_COREDUMP=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y CONFIG_NETWORK_STACK_SIZE=24576 diff --git a/kconfig/config/qemu_mps3_an547/coverage/defconfig b/kconfig/config/qemu_mps3_an547/coverage/defconfig index e77a66b6..d9537f8a 100644 --- a/kconfig/config/qemu_mps3_an547/coverage/defconfig +++ b/kconfig/config/qemu_mps3_an547/coverage/defconfig @@ -39,6 +39,7 @@ CONFIG_ENABLE_SYSCALL=y # CONFIG_VIRTIO is not set CONFIG_SERIAL_RX_FIFO_SIZE=256 CONFIG_SERIAL_TX_FIFO_SIZE=256 +CONFIG_ENABLE_COREDUMP=y # CONFIG_ENABLE_VFS is not set # CONFIG_ENABLE_NET is not set diff --git a/kconfig/config/qemu_mps3_an547/debug/defconfig b/kconfig/config/qemu_mps3_an547/debug/defconfig index c03bcafb..c1875c9e 100644 --- a/kconfig/config/qemu_mps3_an547/debug/defconfig +++ b/kconfig/config/qemu_mps3_an547/debug/defconfig @@ -21,6 +21,7 @@ CONFIG_ROUND_ROBIN=y CONFIG_ROBIN_SLICE=10 CONFIG_OVERFLOW_CHECK=y CONFIG_STACK_HIGHWATER_CHECK=y +CONFIG_ENABLE_COREDUMP=y # CONFIG_DEBUGGING_SCHEDULER is not set CONFIG_MAIN_THREAD_STACK_SIZE=12288 CONFIG_IDLE_THREAD_STACK_SIZE=2048 diff --git a/kconfig/config/qemu_mps3_an547/release/defconfig b/kconfig/config/qemu_mps3_an547/release/defconfig index da39d25f..d2f7d8eb 100644 --- a/kconfig/config/qemu_mps3_an547/release/defconfig +++ b/kconfig/config/qemu_mps3_an547/release/defconfig @@ -40,6 +40,7 @@ CONFIG_ENABLE_SYSCALL=y # CONFIG_VIRTIO is not set CONFIG_SERIAL_RX_FIFO_SIZE=256 CONFIG_SERIAL_TX_FIFO_SIZE=256 +CONFIG_ENABLE_COREDUMP=y # CONFIG_ENABLE_VFS is not set # CONFIG_ENABLE_NET is not set diff --git a/kconfig/config/qemu_riscv32/coverage/defconfig b/kconfig/config/qemu_riscv32/coverage/defconfig index 62389c31..394c16ed 100644 --- a/kconfig/config/qemu_riscv32/coverage/defconfig +++ b/kconfig/config/qemu_riscv32/coverage/defconfig @@ -41,6 +41,8 @@ CONFIG_ENABLE_SYSCALL=y CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 CONFIG_ENABLE_VFS=y +CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y CONFIG_NETWORK_STACK_SIZE=24576 diff --git a/kconfig/config/qemu_riscv32/debug/defconfig b/kconfig/config/qemu_riscv32/debug/defconfig index b230376a..c3e7356a 100644 --- a/kconfig/config/qemu_riscv32/debug/defconfig +++ b/kconfig/config/qemu_riscv32/debug/defconfig @@ -40,6 +40,8 @@ CONFIG_ENABLE_SYSCALL=y # CONFIG_VIRTIO is not set CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 +CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y CONFIG_ENABLE_VFS=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y diff --git a/kconfig/config/qemu_riscv32/release/defconfig b/kconfig/config/qemu_riscv32/release/defconfig index 62389c31..394c16ed 100644 --- a/kconfig/config/qemu_riscv32/release/defconfig +++ b/kconfig/config/qemu_riscv32/release/defconfig @@ -41,6 +41,8 @@ CONFIG_ENABLE_SYSCALL=y CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 CONFIG_ENABLE_VFS=y +CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y CONFIG_NETWORK_STACK_SIZE=24576 diff --git a/kconfig/config/qemu_riscv64/coverage/defconfig b/kconfig/config/qemu_riscv64/coverage/defconfig index 6fd85c96..463e8214 100644 --- a/kconfig/config/qemu_riscv64/coverage/defconfig +++ b/kconfig/config/qemu_riscv64/coverage/defconfig @@ -37,6 +37,8 @@ CONFIG_ENABLE_SYSCALL=y CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 CONFIG_ENABLE_VFS=y +CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y CONFIG_NETWORK_STACK_SIZE=24576 diff --git a/kconfig/config/qemu_riscv64/debug/defconfig b/kconfig/config/qemu_riscv64/debug/defconfig index be55b99f..60db10b4 100644 --- a/kconfig/config/qemu_riscv64/debug/defconfig +++ b/kconfig/config/qemu_riscv64/debug/defconfig @@ -36,6 +36,8 @@ CONFIG_ENABLE_SYSCALL=y # CONFIG_VIRTIO is not set CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 +CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y CONFIG_ENABLE_VFS=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y diff --git a/kconfig/config/qemu_riscv64/release/defconfig b/kconfig/config/qemu_riscv64/release/defconfig index 6fd85c96..463e8214 100644 --- a/kconfig/config/qemu_riscv64/release/defconfig +++ b/kconfig/config/qemu_riscv64/release/defconfig @@ -37,6 +37,8 @@ CONFIG_ENABLE_SYSCALL=y CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 CONFIG_ENABLE_VFS=y +CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y CONFIG_NETWORK_STACK_SIZE=24576 diff --git a/kconfig/config/qemu_virt64_aarch64/coverage/defconfig b/kconfig/config/qemu_virt64_aarch64/coverage/defconfig index bdf207bf..5ab80bae 100644 --- a/kconfig/config/qemu_virt64_aarch64/coverage/defconfig +++ b/kconfig/config/qemu_virt64_aarch64/coverage/defconfig @@ -37,6 +37,8 @@ CONFIG_VIRTIO=y CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 CONFIG_ENABLE_VFS=y +CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y CONFIG_NETWORK_STACK_SIZE=32768 diff --git a/kconfig/config/qemu_virt64_aarch64/debug/defconfig b/kconfig/config/qemu_virt64_aarch64/debug/defconfig index 44ddb252..74dba3d7 100644 --- a/kconfig/config/qemu_virt64_aarch64/debug/defconfig +++ b/kconfig/config/qemu_virt64_aarch64/debug/defconfig @@ -37,6 +37,8 @@ CONFIG_FDT=y CONFIG_VIRTIO=y CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 +CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y CONFIG_ENABLE_VFS=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y diff --git a/kconfig/config/qemu_virt64_aarch64/release/defconfig b/kconfig/config/qemu_virt64_aarch64/release/defconfig index 44ddb252..d6a76795 100644 --- a/kconfig/config/qemu_virt64_aarch64/release/defconfig +++ b/kconfig/config/qemu_virt64_aarch64/release/defconfig @@ -38,6 +38,8 @@ CONFIG_VIRTIO=y CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 CONFIG_ENABLE_VFS=y +CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y CONFIG_NETWORK_STACK_SIZE=32768 diff --git a/kconfig/config/raspberry_pico2_cortexm/debug/defconfig b/kconfig/config/raspberry_pico2_cortexm/debug/defconfig index cd0ae877..7c649ee1 100644 --- a/kconfig/config/raspberry_pico2_cortexm/debug/defconfig +++ b/kconfig/config/raspberry_pico2_cortexm/debug/defconfig @@ -22,6 +22,7 @@ CONFIG_ROUND_ROBIN=y CONFIG_ROBIN_SLICE=10 CONFIG_OVERFLOW_CHECK=y CONFIG_STACK_HIGHWATER_CHECK=y +CONFIG_ENABLE_COREDUMP=y #CONFIG_DEBUGGING_SCHEDULER=y CONFIG_MAIN_THREAD_STACK_SIZE=12288 CONFIG_IDLE_THREAD_STACK_SIZE=2048 diff --git a/kconfig/config/raspberry_pico2_cortexm/release/defconfig b/kconfig/config/raspberry_pico2_cortexm/release/defconfig index 7e33595c..3696f160 100644 --- a/kconfig/config/raspberry_pico2_cortexm/release/defconfig +++ b/kconfig/config/raspberry_pico2_cortexm/release/defconfig @@ -26,6 +26,7 @@ CONFIG_STACK_HIGHWATER_CHECK=y CONFIG_MAIN_THREAD_STACK_SIZE=12288 CONFIG_IDLE_THREAD_STACK_SIZE=2048 CONFIG_TIMER_THREAD_STACK_SIZE=2048 +CONFIG_ENABLE_COREDUMP=y # CONFIG_FDT is not set # CONFIG_VIRTIO is not set CONFIG_PROCFS=y diff --git a/kconfig/config/rk3568/coverage/defconfig b/kconfig/config/rk3568/coverage/defconfig index 630ec794..a961248f 100644 --- a/kconfig/config/rk3568/coverage/defconfig +++ b/kconfig/config/rk3568/coverage/defconfig @@ -41,6 +41,8 @@ CONFIG_VIRTIO=y CONFIG_SERIAL_RX_FIFO_SIZE=512 CONFIG_SERIAL_TX_FIFO_SIZE=512 CONFIG_ENABLE_VFS=y +CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y CONFIG_PROCFS=y CONFIG_ENABLE_NET=y CONFIG_NETWORK_STACK_SIZE=32768 diff --git a/kconfig/config/rk3568/debug/defconfig b/kconfig/config/rk3568/debug/defconfig index 193f2722..b0311ccb 100644 --- a/kconfig/config/rk3568/debug/defconfig +++ b/kconfig/config/rk3568/debug/defconfig @@ -38,6 +38,8 @@ CONFIG_ENABLE_SYSCALL=y CONFIG_SERIAL_RX_FIFO_SIZE=256 CONFIG_SERIAL_TX_FIFO_SIZE=256 CONFIG_ENABLE_VFS=y + CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y CONFIG_PROCFS=y # CONFIG_ENABLE_NET is not set diff --git a/kconfig/config/rk3568/release/defconfig b/kconfig/config/rk3568/release/defconfig index 193f2722..5cdcda07 100644 --- a/kconfig/config/rk3568/release/defconfig +++ b/kconfig/config/rk3568/release/defconfig @@ -38,6 +38,8 @@ CONFIG_ENABLE_SYSCALL=y CONFIG_SERIAL_RX_FIFO_SIZE=256 CONFIG_SERIAL_TX_FIFO_SIZE=256 CONFIG_ENABLE_VFS=y +CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y CONFIG_PROCFS=y # CONFIG_ENABLE_NET is not set diff --git a/kconfig/config/seeed_xiao_esp32c3/debug/defconfig b/kconfig/config/seeed_xiao_esp32c3/debug/defconfig index 6e5739bf..4f1ff751 100644 --- a/kconfig/config/seeed_xiao_esp32c3/debug/defconfig +++ b/kconfig/config/seeed_xiao_esp32c3/debug/defconfig @@ -16,6 +16,8 @@ CONFIG_ROUND_ROBIN=n CONFIG_ROBIN_SLICE=10 CONFIG_OVERFLOW_CHECK=y CONFIG_STACK_HIGHWATER_CHECK=y +CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y CONFIG_MAIN_THREAD_STACK_SIZE=12288 CONFIG_IDLE_THREAD_STACK_SIZE=2048 CONFIG_TIMER_THREAD_STACK_SIZE=2048 diff --git a/kconfig/config/seeed_xiao_esp32c3/release/defconfig b/kconfig/config/seeed_xiao_esp32c3/release/defconfig index c29c801c..3d91566f 100644 --- a/kconfig/config/seeed_xiao_esp32c3/release/defconfig +++ b/kconfig/config/seeed_xiao_esp32c3/release/defconfig @@ -25,6 +25,8 @@ CONFIG_THREAD_PRIORITY_MAX=32 CONFIG_MAIN_THREAD_PRIORITY=32 CONFIG_ENABLE_SYSCALL=y +CONFIG_ENABLE_COREDUMP=y +CONFIG_COREDUMP_MEM_BACKEND=y # CONFIG_FDT is not set # CONFIG_VIRTIO is not set CONFIG_SERIAL_RX_FIFO_SIZE=256 diff --git a/kernel/BUILD.gn b/kernel/BUILD.gn index d7b6e9b8..ce21df6c 100644 --- a/kernel/BUILD.gn +++ b/kernel/BUILD.gn @@ -24,6 +24,7 @@ _kernel_default_cfgs = [ group("check_kernel") { testonly = true deps = [ + ":run_coredump_test", ":run_integration_test", ":run_unittest", ] @@ -221,6 +222,69 @@ run_qemu_check("run_integration_test") { } } +build_rust("kernel_coredump_test") { + testonly = true + crate_type = "bin" + sources = [ "tests/coredump_test.rs" ] + edition = "2021" + deps = [ + ":blueos", + "//external/vendor/semihosting-0.1.20:semihosting", + "//kernel/rsrt", + "//libc", + ] + board_deps + if (coverage || profile) { + deps += [ "//external/vendor/minicov-0.3.7:minicov" ] + } + cfgs = _kernel_default_cfgs + linker_script = default_linker_script + if (defined(chip)) { + rustflags += [ + "--cfg", + "target_chip=\"${chip}\"", + ] + } +} + +if (defined(qemu_use_esp32_loader) && qemu_use_esp32_loader) { + gen_esp32_qemu_runner("coredump_test_runner") { + testonly = true + elf = ":kernel_coredump_test" + chip = "${chip}" + bootloader = qemu_esp32_bootloader_bin + partition_table = qemu_esp32_partition_table_bin + qemu = qemu_exe + machine = qemu_machine + qemu_args = qemu_extra_args + net_args = qemu_net_args + block_args = qemu_block_args + semihosting = true + } +} else { + gen_qemu_runner("coredump_test_runner") { + testonly = true + semihosting = true + img = ":kernel_coredump_test" + qemu = qemu_exe + machine = qemu_machine + qemu_args = qemu_extra_args + net_args = qemu_net_args + block_img = "coredump_test_block.img" + block_args = qemu_block_args + } +} + +run_qemu_check("run_coredump_test") { + testonly = true + runner = ":coredump_test_runner" + if (coverage) { + img = ":kernel_coredump_test" + checker = "src/coverage.checker" + } else { + checker = "tests/coredump.checker" + } +} + if (defined(use_defmt) && use_defmt) { gen_probe_runner("unittest_runner") { testonly = true diff --git a/kernel/src/Kconfig b/kernel/src/Kconfig index a576f08b..9d3c2bbc 100644 --- a/kernel/src/Kconfig +++ b/kernel/src/Kconfig @@ -65,3 +65,5 @@ menu "os adapter configuration" endchoice endmenu # os adapter configuration + +rsource "coredump/Kconfig" diff --git a/kernel/src/arch/aarch64/exception.rs b/kernel/src/arch/aarch64/exception.rs index c8f22f09..06ab3626 100644 --- a/kernel/src/arch/aarch64/exception.rs +++ b/kernel/src/arch/aarch64/exception.rs @@ -201,6 +201,14 @@ extern "C" fn trap_fiq(context: &mut Context) -> usize { } fn show_exception(ec: u64, context: &mut Context) { + #[cfg(enable_coredump)] + crate::coredump::dump_current(&crate::coredump::elf::CoredumpReason { + signo: crate::coredump::signal::aarch64_ec_to_signo(ec), + code: ec as i32, + fault_addr: ESR_EL1.get() as usize, + arch_specific: ec as usize, + }); + match ec { 0x00 => panic!("Unknown reason Exceptions\n======== error stack ======== \n{}",context), 0x01 => panic!("WFI or WFE instruction\n======== error stack ======== \n{}",context), diff --git a/kernel/src/arch/arm/hardfault.rs b/kernel/src/arch/arm/hardfault.rs index d3f668a1..1d737641 100644 --- a/kernel/src/arch/arm/hardfault.rs +++ b/kernel/src/arch/arm/hardfault.rs @@ -216,6 +216,15 @@ impl fmt::Display for HardFaultRegs { pub extern "C" fn panic_on_hardfault(ctx: &IsrContext) { super::disable_local_irq(); let fault_regs: HardFaultRegs = HardFaultRegs::from_scb(); + + #[cfg(enable_coredump)] + crate::coredump::dump_current(&crate::coredump::elf::CoredumpReason { + signo: crate::coredump::signal::arm_cfsr_to_signo(fault_regs.cfsr), + code: fault_regs.cfsr as i32, + fault_addr: fault_regs.mmfar as usize, + arch_specific: fault_regs.cfsr as usize, + }); + let xpsr = xpsr::read(); panic!( " diff --git a/kernel/src/arch/riscv/trap.rs b/kernel/src/arch/riscv/trap.rs index 30dc8725..09e5f201 100644 --- a/kernel/src/arch/riscv/trap.rs +++ b/kernel/src/arch/riscv/trap.rs @@ -258,6 +258,14 @@ extern "C" fn handle_trap(ctx: &mut Context, mcause: usize, mtval: usize, cont: might_switch_context(ctx, cont) } _ => { + #[cfg(enable_coredump)] + crate::coredump::dump_current(&crate::coredump::elf::CoredumpReason { + signo: crate::coredump::signal::riscv_mcause_to_signo(mcause), + code: mcause as i32, + fault_addr: mtval, + arch_specific: mcause, + }); + let t = scheduler::current_thread_ref(); panic!( "[C#{}:0x{:x}] Unexpected trap: context: {:?}, mcause: 0x{:x}, mtval: 0x{:x}", diff --git a/kernel/src/boards/gd32vw553_eval/Kconfig b/kernel/src/boards/gd32vw553_eval/Kconfig index e03bbcc8..822d5db7 100644 --- a/kernel/src/boards/gd32vw553_eval/Kconfig +++ b/kernel/src/boards/gd32vw553_eval/Kconfig @@ -2,6 +2,7 @@ config SOC_GD32VW553 bool "Gd32vw553" default y select RISCV + select 32BIT select HAS_MIE select HAS_MTIME select HAS_PLIC diff --git a/kernel/src/boards/gd32vw553_eval/link.x b/kernel/src/boards/gd32vw553_eval/link.x index 49f4b7ff..f7e7cdb3 100644 --- a/kernel/src/boards/gd32vw553_eval/link.x +++ b/kernel/src/boards/gd32vw553_eval/link.x @@ -92,6 +92,14 @@ SECTIONS __bss_end = .; } >RAM + .coredump_bss (NOLOAD) : { + . = ALIGN(4096); + __coredump_buf_start = .; + *(.coredump_bss .coredump_bss.*) + . = ALIGN(4096); + __coredump_buf_end = .; + } >RAM + .heap : { . = ALIGN(16); __heap_start = .; diff --git a/kernel/src/boards/qemu_riscv32/Kconfig b/kernel/src/boards/qemu_riscv32/Kconfig index 9c7e1f95..ceecde4c 100644 --- a/kernel/src/boards/qemu_riscv32/Kconfig +++ b/kernel/src/boards/qemu_riscv32/Kconfig @@ -23,6 +23,7 @@ config SOC_QEMU_VIRT_RISCV32 bool "Qemu Virt Riscv32" default y select RISCV + select 32BIT select HAS_MIE select HAS_MTIME select HAS_PLIC diff --git a/kernel/src/boards/qemu_riscv32/link.x b/kernel/src/boards/qemu_riscv32/link.x index 72d23e01..911153d3 100644 --- a/kernel/src/boards/qemu_riscv32/link.x +++ b/kernel/src/boards/qemu_riscv32/link.x @@ -49,6 +49,14 @@ SECTIONS __bss_end = .; } + .coredump_bss (NOLOAD) : { + . = ALIGN(4096); + __coredump_buf_start = .; + *(.coredump_bss .coredump_bss.*) + . = ALIGN(4096); + __coredump_buf_end = .; + } + /* Initialize C runtime. */ /* .ctors and .dtors should not appear since we don't have C++ code at present. */ .init_array : { diff --git a/kernel/src/boards/qemu_riscv64/link.x b/kernel/src/boards/qemu_riscv64/link.x index 7c25dd9c..2e4c3d46 100644 --- a/kernel/src/boards/qemu_riscv64/link.x +++ b/kernel/src/boards/qemu_riscv64/link.x @@ -49,6 +49,14 @@ SECTIONS __bss_end = .; } + .coredump_bss (NOLOAD) : { + . = ALIGN(4096); + __coredump_buf_start = .; + *(.coredump_bss .coredump_bss.*) + . = ALIGN(4096); + __coredump_buf_end = .; + } + /* Initialize C runtime. */ /* .ctors and .dtors should not appear since we don't have C++ code at present. */ .init_array : { diff --git a/kernel/src/boards/qemu_virt64_aarch64/link.x b/kernel/src/boards/qemu_virt64_aarch64/link.x index f9cf619e..97046cc4 100644 --- a/kernel/src/boards/qemu_virt64_aarch64/link.x +++ b/kernel/src/boards/qemu_virt64_aarch64/link.x @@ -55,7 +55,15 @@ SECTIONS __bss_end = .; } > DRAM :data - .init_array : AT(ADDR(.init_array) - KERNEL_OFFSET) { + .coredump_bss (NOLOAD) : ALIGN(4096) + { + __coredump_buf_start = .; + *(.coredump_bss*) + . = ALIGN(4096); + __coredump_buf_end = .; + } > DRAM :data + + .init_array : { . = ALIGN(16); PROVIDE_HIDDEN (__init_array_start = .); KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*))) diff --git a/kernel/src/boards/rk3568/link.x b/kernel/src/boards/rk3568/link.x index 220b2c78..21f65afa 100644 --- a/kernel/src/boards/rk3568/link.x +++ b/kernel/src/boards/rk3568/link.x @@ -56,6 +56,14 @@ SECTIONS __bss_end = .; } > DRAM :data + .coredump_bss (NOLOAD) : ALIGN(4096) + { + __coredump_buf_start = .; + *(.coredump_bss*) + . = ALIGN(4096); + __coredump_buf_end = .; + } > DRAM :data + .init_array : { . = ALIGN(16); PROVIDE_HIDDEN (__init_array_start = .); diff --git a/kernel/src/boards/seeed_xiao_esp32c3/Kconfig b/kernel/src/boards/seeed_xiao_esp32c3/Kconfig index da2125d0..10cb42c2 100644 --- a/kernel/src/boards/seeed_xiao_esp32c3/Kconfig +++ b/kernel/src/boards/seeed_xiao_esp32c3/Kconfig @@ -23,6 +23,7 @@ config SOC_ESP32C3 bool "Esp32c3" default y select RISCV + select 32BIT config NUM_IRQS default 31 \ No newline at end of file diff --git a/kernel/src/boards/seeed_xiao_esp32c3/link.x b/kernel/src/boards/seeed_xiao_esp32c3/link.x index caf34196..f8116798 100644 --- a/kernel/src/boards/seeed_xiao_esp32c3/link.x +++ b/kernel/src/boards/seeed_xiao_esp32c3/link.x @@ -143,6 +143,14 @@ SECTIONS { . = ALIGN(4); } > RWDATA + .coredump_bss (NOLOAD) : ALIGN(4096) + { + __coredump_buf_start = ABSOLUTE(.); + *(.coredump_bss .coredump_bss.*) + . = ALIGN(4096); + __coredump_buf_end = ABSOLUTE(.); + } > RWDATA + .noinit (NOLOAD) : ALIGN(4) { . = ALIGN(4); diff --git a/kernel/src/boot.rs b/kernel/src/boot.rs index e8a54a15..eedee56a 100644 --- a/kernel/src/boot.rs +++ b/kernel/src/boot.rs @@ -134,6 +134,8 @@ extern "C" fn init() { } scheduler::init(); + #[cfg(enable_coredump)] + crate::coredump::init(); logger::logger_init(); time::timer::init(); #[cfg(kernel_async)] diff --git a/kernel/src/coredump/Kconfig b/kernel/src/coredump/Kconfig new file mode 100644 index 00000000..8f453583 --- /dev/null +++ b/kernel/src/coredump/Kconfig @@ -0,0 +1,30 @@ +config ENABLE_COREDUMP + bool "Enable ELF coredump on fatal faults" + default n + help + Generate an ELF ET_CORE coredump when the kernel hits a fatal + exception or panic. Output goes to the configured backend + (file, logging, or memory). + +config COREDUMP_BUF_SIZE + int "Coredump ELF buffer size in bytes" + default 65536 if CORTEX_M + default 262144 if !CORTEX_M && 32BIT + default 4194304 + depends on ENABLE_COREDUMP + help + Size of the static buffer used to construct the ELF coredump + in memory before output. Cortex-M devices with limited RAM + should use a smaller value (e.g. 65536). 64-bit platforms + with large BSS segments may need 4 MB. + +config COREDUMP_MEM_BACKEND + bool "Store coredump in a static memory buffer" + default n + depends on ENABLE_COREDUMP + help + Store the coredump ELF in a static buffer (COREDUMP_STORAGE) + for post-mortem retrieval by a subsequent boot or debugger. + The buffer size matches COREDUMP_BUF_SIZE. + If disabled, the coredump goes to FileBackend (when VFS is + available) or LoggingBackend (serial output). \ No newline at end of file diff --git a/kernel/src/coredump/README.md b/kernel/src/coredump/README.md new file mode 100644 index 00000000..04131725 --- /dev/null +++ b/kernel/src/coredump/README.md @@ -0,0 +1,121 @@ +# Coredump Module + +## Overview + +Generates an ELF ET_CORE coredump on-device covering register state, memory regions, and thread information for post-mortem fault analysis. + +## Triggering + +Coredumps are triggered automatically through: + +1. **Exception handling** — When the CPU traps a fault (SIGSEGV, SIGBUS, illegal instruction), `dump_current()` is called from the exception handler +2. **Kernel panic** — The `panic_handler` calls `dump_current()` to generate a coredump before halting +3. **Manual invocation** — Code can directly call `coredump::dump_current(&reason)` or `coredump::dump(&reason, mode)` + +`dump_current()` dumps the current thread; `dump()` supports `DumpMode::Current` and `DumpMode::All`. + +## Signal Mapping + +Architecture-specific exception codes are mapped to POSIX signals: + +| Architecture | Mapping function | Input source | +|---|---|---| +| RISC-V | `riscv_mcause_to_signo(mcause)` | mcause CSR | +| AArch64 | `aarch64_ec_to_signo(ec)` | ESR_EL1 EC field | +| Cortex-M | `arm_cfsr_to_signo(cfsr)` | CFSR (MMFSR/BFSR/UFSR) | + +See the mapping tables in [signal.rs](signal.rs). + +## Backend Selection (compile-time) + +Backends are selected at compile time via Kconfig and `#[cfg]` chains, in priority order: + +1. **`CONFIG_COREDUMP_MEM_BACKEND=y`** → MemoryBackend (static buffer) +2. **`CONFIG_ENABLE_VFS=y` and not MemoryBackend** → FileBackend (VFS file) +3. **Not MemoryBackend and not VFS** → LoggingBackend (hex-encoded serial/semihosting) + +### FileBackend + +Output path: `/tmp/blueos.core.` where pid is the current thread ID. + +### LoggingBackend + +Outputs hex-encoded ELF data line-by-line via semihosting, at most 64 hex characters per line. +Appends a trailer: +``` +[BLUEOS_COREDUMP_END] mode=Current chunks=N size=NKB +``` + +### MemoryBackend + +Writes the coredump to a static kernel buffer (`COREDUMP_STORAGE`) placed in the `.coredump_bss` linker section, which is excluded from BSS memory region collection. Sets `COREDUMP_VALID` after completion. + +## Kconfig Options + +| Option | Type | Default | Description | +|---|---|---|---| +| `ENABLE_COREDUMP` | bool | n | Global coredump switch | +| `COREDUMP_MEM_BACKEND` | bool | n | Use MemoryBackend (depends on ENABLE_COREDUMP) | +| `COREDUMP_BUF_SIZE` | int | 64KB (Cortex-M) / 256KB (32-bit) / 4MB | ELF buffer size in bytes | + +Default values: +- Cortex-M (resource-constrained): 64KB +- Other 32-bit platforms: 256KB +- 64-bit platforms: 4MB + +## Integration Test + +```bash +# Run the coredump integration test +ninja -C out/qemu_riscv64.debug.swi run_coredump_test +``` + +The test constructs a synthetic `CoredumpReason { signo: 6, code: 0, fault_addr: 0, arch_specific: 0 }`, +calls `dump_current()`, and verifies successful output. + +Test assertions are defined in [tests/coredump.checker](../tests/coredump.checker): +``` +// ASSERT-SUCC: Coredump integration test end. +// ASSERT-FAIL: Backtrace in Panic.* +``` + +## Serial Log Parser Tool + +When using LoggingBackend, coredump data is output as hex-encoded text over serial/semihosting. Use the parser tool to reassemble it into a `.core` ELF file: + +```bash +# Parse from QEMU serial log +python3 tools/coredump_parser.py < qemu_log.txt -o blueos.core + +# Specify input file +python3 tools/coredump_parser.py --input qemu_log.txt -o blueos.core + +# Validate the generated core file +python3 tools/coredump_parser.py --input qemu_log.txt -o blueos.core --validate + +# Verbose output +python3 tools/coredump_parser.py --input qemu_log.txt -o blueos.core --verbose +``` + +Options: +- `--input` / `-i`: Input file (default stdin) +- `--output` / `-o`: Output core file path +- `--validate`: Run `readelf` to validate the generated core file +- `--verbose`: Print metadata and size information + +## Architecture Support + +| Architecture | Status | Boards | +|---|---|---| +| RISC-V 64 | Supported | qemu_riscv64 | +| RISC-V 32 | Supported | qemu_riscv32, gd32vw553_eval | +| AArch64 | Supported | qemu_virt64_aarch64 | +| Cortex-M | Supported | qemu_mps2_an385 | +| ESP32-C3 | Supported | seeed_xiao_esp32c3 (RISC-V 32) | + +## Notes + +1. **Buffer size**: `COREDUMP_BUF_SIZE` must be large enough to hold the complete ELF output, or `write()` will return an error. +2. **Re-entrancy**: `COREDUMP_IN_PROGRESS` prevents recursive coredump triggers. +3. **Interrupt state**: Coredump executes with interrupts disabled (guaranteed by the exception handler). +4. **`.coredump_bss` section**: The ELF buffer and MemoryBackend storage are placed in this section so that BSS memory dumps do not include the buffers themselves. \ No newline at end of file diff --git a/kernel/src/coredump/arch.rs b/kernel/src/coredump/arch.rs new file mode 100644 index 00000000..454dc033 --- /dev/null +++ b/kernel/src/coredump/arch.rs @@ -0,0 +1,40 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Architecture dispatch for coredump register capture. +//! +//! Each arch module provides: +//! - `capture_regs(ctx: &arch::Context) -> [u8; N]` +//! - `capture_current_regs() -> [u8; N]` + +#[cfg(target_arch = "arm")] +mod arm; +#[cfg(target_arch = "arm")] +pub use arm::*; + +#[cfg(target_arch = "aarch64")] +mod aarch64; +#[cfg(target_arch = "aarch64")] +pub use aarch64::*; + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +mod riscv; +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +pub use riscv::*; + +/// Return the register dump size in bytes for the current architecture. +#[inline] +pub fn regset_size() -> usize { + capture_current_regs().len() +} diff --git a/kernel/src/coredump/arch/aarch64.rs b/kernel/src/coredump/arch/aarch64.rs new file mode 100644 index 00000000..9b51f777 --- /dev/null +++ b/kernel/src/coredump/arch/aarch64.rs @@ -0,0 +1,66 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Register capture for AArch64 coredump. +//! +//! Register order follows Linux AArch64 user_regs_struct: +//! x0..x30, sp, pc, pstate (34 × 8 = 272 bytes) + +use crate::arch; + +/// Register count matching Linux AArch64 user_regs_struct. +const REGS: usize = 34; + +#[inline(never)] +pub fn capture_regs(ctx: &arch::Context) -> [u8; REGS * 8] { + let mut buf = [0u8; REGS * 8]; + + // x0..x28 + let gpr: [usize; 29] = [ + ctx.x0, ctx.x1, ctx.x2, ctx.x3, ctx.x4, ctx.x5, ctx.x6, ctx.x7, ctx.x8, ctx.x9, ctx.x10, + ctx.x11, ctx.x12, ctx.x13, ctx.x14, ctx.x15, ctx.x16, ctx.x17, ctx.x18, ctx.x19, ctx.x20, + ctx.x21, ctx.x22, ctx.x23, ctx.x24, ctx.x25, ctx.x26, ctx.x27, ctx.x28, + ]; + for (i, &r) in gpr.iter().enumerate() { + buf[i * 8..(i + 1) * 8].copy_from_slice(&r.to_ne_bytes()); + } + + // x29 = fp, x30 = lr + buf[29 * 8..30 * 8].copy_from_slice(&ctx.fp.to_ne_bytes()); + buf[30 * 8..31 * 8].copy_from_slice(&ctx.lr.to_ne_bytes()); + + // SP = ctx pointer base address + let sp = ctx as *const arch::Context as usize; + buf[31 * 8..32 * 8].copy_from_slice(&sp.to_ne_bytes()); + + // PC = ctx.elr + buf[32 * 8..33 * 8].copy_from_slice(&ctx.elr.to_ne_bytes()); + + // PSTATE = ctx.spsr + buf[33 * 8..34 * 8].copy_from_slice(&ctx.spsr.to_ne_bytes()); + + buf +} + +#[inline(never)] +pub fn capture_current_regs() -> [u8; REGS * 8] { + let thread = crate::scheduler::current_thread_ref(); + let sp = thread.saved_sp(); + if sp != 0 { + if let Some(ctx) = unsafe { (sp as *const arch::Context).as_ref() } { + return capture_regs(ctx); + } + } + [0u8; REGS * 8] +} diff --git a/kernel/src/coredump/arch/arm.rs b/kernel/src/coredump/arch/arm.rs new file mode 100644 index 00000000..878248ff --- /dev/null +++ b/kernel/src/coredump/arch/arm.rs @@ -0,0 +1,70 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Register capture for ARM Cortex-M coredump. +//! +//! Register order follows Linux ARM user_regs_struct: +//! r0..r15, cpsr, orig_r0 (18 × 4 = 72 bytes) + +use crate::arch; + +/// Register count matching Linux ARM user_regs_struct. +const REGS: usize = 18; + +#[inline(never)] +pub fn capture_regs(ctx: &arch::Context) -> [u8; REGS * 4] { + let mut buf = [0u8; REGS * 4]; + + // On Cortex-M, saved_sp points to where r4 was stored (callee-saved block). + // The hardware auto-saves r0-r3,r12,lr,pc,xpsr below that. + // Calculate PSP = saved_sp + 4*8 (r4-r11) + 4*8 (r0-r3,r12,lr,pc,xpsr). + let psp = ctx as *const arch::Context as usize + 64; + + let regs: [u32; REGS] = [ + ctx.r0 as u32, + ctx.r1 as u32, + ctx.r2 as u32, + ctx.r3 as u32, + ctx.r4 as u32, + ctx.r5 as u32, + ctx.r6 as u32, + ctx.r7 as u32, + ctx.r8 as u32, + ctx.r9 as u32, + ctx.r10 as u32, + ctx.r11 as u32, + ctx.r12 as u32, + psp as u32, // SP = R13 + ctx.lr as u32, // LR = R14 + ctx.pc as u32, // PC = R15 + ctx.xpsr as u32, // CPSR + 0, // orig_r0 + ]; + for (i, &r) in regs.iter().enumerate() { + buf[i * 4..(i + 1) * 4].copy_from_slice(&r.to_ne_bytes()); + } + buf +} + +#[inline(never)] +pub fn capture_current_regs() -> [u8; REGS * 4] { + let thread = crate::scheduler::current_thread_ref(); + let sp = thread.saved_sp(); + if sp != 0 { + if let Some(ctx) = unsafe { (sp as *const arch::Context).as_ref() } { + return capture_regs(ctx); + } + } + [0u8; REGS * 4] +} diff --git a/kernel/src/coredump/arch/riscv.rs b/kernel/src/coredump/arch/riscv.rs new file mode 100644 index 00000000..87cf48c1 --- /dev/null +++ b/kernel/src/coredump/arch/riscv.rs @@ -0,0 +1,90 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Register capture for RISC-V coredump. +//! +//! Register order follows Linux riscv_user_regs_struct: +//! pc, ra, sp, gp, tp, t0..t6, fp, a0..a7, s1..s11, (s0=fp) +//! Total: 32 registers × usize width. + +use crate::arch; + +/// RISC-V has 32 general-purpose registers in the Linux user_regs_struct order. +const REGS: usize = 32; + +/// Register dump size: 32 × usize (4 bytes on riscv32, 8 bytes on riscv64). +const REGSET_SZ: usize = REGS * core::mem::size_of::(); + +#[inline(never)] +pub(crate) fn capture_regs(ctx: &arch::Context) -> [u8; REGSET_SZ] { + let mut buf = [0u8; REGSET_SZ]; + + // Linux RISCV user_regs_struct order: + // [0] pc, [1] ra, [2] sp, [3] gp, [4] tp + // [5-7] t0-t2, [8] fp(s0), [9-16] a0-a7 + // [17-19] t3-t5, [20] t6, [21-31] s1-s11 + let sp = ctx as *const arch::Context as usize; + + let regs: [usize; REGS] = [ + ctx.mepc, // pc + ctx.ra, // ra + sp, // sp + ctx.gp, // gp + ctx.tp, // tp + ctx.t0, // t0 + ctx.t1, // t1 + ctx.t2, // t2 + ctx.fp, // fp (s0) + ctx.a0, + ctx.a1, + ctx.a2, + ctx.a3, + ctx.a4, + ctx.a5, + ctx.a6, + ctx.a7, + ctx.t3, + ctx.t4, + ctx.t5, + ctx.t6, + ctx.s1, + ctx.s2, + ctx.s3, + ctx.s4, + ctx.s5, + ctx.s6, + ctx.s7, + ctx.s8, + ctx.s9, + ctx.s10, + ctx.s11, + ]; + for (i, &r) in regs.iter().enumerate() { + let off = i * core::mem::size_of::(); + buf[off..off + core::mem::size_of::()].copy_from_slice(&r.to_ne_bytes()); + } + buf +} + +#[inline(never)] +pub fn capture_current_regs() -> [u8; REGSET_SZ] { + let thread = crate::scheduler::current_thread_ref(); + let sp = thread.saved_sp(); + if sp != 0 { + if let Some(ctx) = unsafe { (sp as *const arch::Context).as_ref() } { + return capture_regs(ctx); + } + } + [0u8; REGSET_SZ] +} diff --git a/kernel/src/coredump/backend.rs b/kernel/src/coredump/backend.rs new file mode 100644 index 00000000..ff025b62 --- /dev/null +++ b/kernel/src/coredump/backend.rs @@ -0,0 +1,193 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Coredump backends. +//! +//! Backends receive the finished ELF coredump buffer for output. +//! Two built-in backends: +//! - `FileBackend`: writes via the VFS (QEMU/GDB scenarios) +//! - `LoggingBackend`: hex-encodes and prints (serial/log scenarios) +//! - `MemoryBackend`: stores in a static buffer (for post-mortem retrieval) + +use crate::coredump::DumpMode; +use crate::vfs::FileOps; + +/// Maximum coredump buffer size. +/// +/// Controlled by Kconfig `CONFIG_COREDUMP_BUF_SIZE` (int, in bytes). +/// Falls back to an arch-appropriate default if Kconfig is absent. +/// 64-bit → 4 MB, 32-bit (non-Cortex-M) → 256 KB, Cortex-M → 64 KB. +#[cfg(enable_coredump)] +pub const COREDUMP_BUF_SIZE: usize = + blueos_kconfig::CONFIG_COREDUMP_BUF_SIZE as usize; +#[cfg(not(enable_coredump))] +pub const COREDUMP_BUF_SIZE: usize = 4096; + +/// Backend trait for coredump output. +pub trait CoredumpBackend { + /// Write a chunk of the finished coredump. + fn write(&mut self, buf: &[u8]); + + /// Signal that the coredump is complete. + fn finalize(&mut self, mode: DumpMode); + + /// Return a descriptive name for this backend. + fn name(&self) -> &str; +} + +// ── FileBackend ───────────────────────────────────────────────────────── + +/// Writes the coredump to a file via VFS. +/// +/// Used in QEMU/GDB scenarios where a filesystem is available. +/// The file path is typically `/tmp/blueos.core.`. +#[derive(Debug)] +pub struct FileBackend { + path: &'static str, + written: usize, +} + +impl FileBackend { + pub const fn new(path: &'static str) -> Self { + Self { path, written: 0 } + } +} + +impl CoredumpBackend for FileBackend { + fn write(&mut self, buf: &[u8]) { + #[cfg(enable_vfs)] + { + // Append to the coredump file via VFS. + if let Ok(file) = crate::vfs::path::open_path(self.path, libc::O_WRONLY | libc::O_CREAT | libc::O_APPEND, 0o666) { + let _ = file.write(buf); + } + } + #[cfg(not(enable_vfs))] + { + let _ = buf; + } + self.written += buf.len(); + } + + fn finalize(&mut self, _mode: DumpMode) { + // File is already flushed on each write; nothing extra needed. + } + + fn name(&self) -> &str { + "file" + } +} + +// ── LoggingBackend ────────────────────────────────────────────────────── + +/// Hex-encodes the coredump and outputs via the kernel logger. +/// +/// Used in embedded/no-GDB scenarios where only a serial/console +/// output channel is available. The host can reassemble the hex stream. +#[derive(Debug)] +pub struct LoggingBackend { + chunks: usize, +} + +impl LoggingBackend { + pub const fn new() -> Self { + Self { chunks: 0 } + } +} + +/// Hex-encode a byte slice into the given buffer (2× input size). +fn hex_encode(src: &[u8], dst: &mut [u8]) -> usize { + let n = core::cmp::min(src.len(), dst.len() / 2); + for (i, &b) in src[..n].iter().enumerate() { + dst[i * 2] = hex_nib(b >> 4); + dst[i * 2 + 1] = hex_nib(b & 0x0f); + } + n * 2 +} + +#[inline] +fn hex_nib(v: u8) -> u8 { + if v < 10 { + b'0' + v + } else { + b'a' + v - 10 + } +} + +impl CoredumpBackend for LoggingBackend { + fn write(&mut self, buf: &[u8]) { + // Output in small hex-encoded lines via console + const LINE_MAX: usize = 64; // 32 bytes per line → 64 hex chars + let mut hex_buf = [0u8; LINE_MAX * 2 + 1]; // +1 for newline + for chunk in buf.chunks(LINE_MAX) { + let n = hex_encode(chunk, &mut hex_buf); + hex_buf[n] = b'\n'; + // Use semihosting eprint which is always available + let s = unsafe { core::str::from_utf8_unchecked(&hex_buf[..n + 1]) }; + semihosting::eprint!("{}", s); + } + self.chunks += 1; + } + + fn finalize(&mut self, mode: DumpMode) { + // Emit a trailer line with metadata + let size_kb = self.chunks * 32 / 1024; + semihosting::eprint!( + "[BLUEOS_COREDUMP_END] mode={:?} chunks={} size={}KB\n", + mode, + self.chunks, + size_kb, + ); + } + + fn name(&self) -> &str { + "logging" + } +} + +// ── MemoryBackend ─────────────────────────────────────────────────────── + +/// Stores the coredump in a fixed static buffer. +/// +/// Used for post-mortem retrieval by a subsequent boot or +/// by a debugger attached after the fact. +#[derive(Debug)] +pub struct MemoryBackend { + buf: &'static mut [u8], + pos: usize, +} + +impl MemoryBackend { + pub fn new(buf: &'static mut [u8]) -> Self { + Self { buf, pos: 0 } + } +} + +impl CoredumpBackend for MemoryBackend { + fn write(&mut self, data: &[u8]) { + let remaining = self.buf.len().saturating_sub(self.pos); + let n = core::cmp::min(data.len(), remaining); + self.buf[self.pos..self.pos + n].copy_from_slice(&data[..n]); + self.pos += n; + } + + fn finalize(&mut self, _mode: DumpMode) { + #[cfg(coredump_mem_backend)] + crate::coredump::COREDUMP_VALID.store(true, core::sync::atomic::Ordering::Release); + } + + fn name(&self) -> &str { + "memory" + } +} diff --git a/kernel/src/coredump/elf.rs b/kernel/src/coredump/elf.rs new file mode 100644 index 00000000..59cff0fb --- /dev/null +++ b/kernel/src/coredump/elf.rs @@ -0,0 +1,248 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! ELF ET_CORE structure definitions and builder. +//! +//! Hand-written `#[repr(C)]` structs for ELF32/ELF64. +//! Zero third-party ELF dependencies. + +use core::mem; + +// ── ELF identification constants ────────────────────────────────────── + +const ELFCLASS32: u8 = 1; +const ELFCLASS64: u8 = 2; +const ELFDATA2LSB: u8 = 1; +const EV_CURRENT: u8 = 1; + +#[cfg(target_pointer_width = "32")] +const ELF_CLASS: u8 = ELFCLASS32; +#[cfg(target_pointer_width = "64")] +const ELF_CLASS: u8 = ELFCLASS64; + +const ELF_IDENT: [u8; 16] = [ + 0x7f, b'E', b'L', b'F', // magic + ELF_CLASS, // class + ELFDATA2LSB, // data encoding (little-endian) + EV_CURRENT, // version + 0, // OS/ABI + 0, // ABI version + 0, 0, 0, 0, 0, 0, 0, // padding +]; + +const ET_CORE: u16 = 4; +const PT_LOAD: u32 = 1; +const PT_NOTE: u32 = 4; + +pub const NT_PRSTATUS: u32 = 1; +pub const NT_PRPSINFO: u32 = 3; +pub const NT_SIGINFO: u32 = 0x53494749; // 'SIGI' + +// ── ELF machine types ───────────────────────────────────────────────── +#[cfg(target_arch = "arm")] +const EM_MACHINE: u16 = 40; // EM_ARM +#[cfg(target_arch = "aarch64")] +const EM_MACHINE: u16 = 183; // EM_AARCH64 +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +const EM_MACHINE: u16 = 243; // EM_RISCV + +// ── ELF structures ──────────────────────────────────────────────────── + +/// ELF header. `usize` fields expand to 4 or 8 bytes matching ELFCLASS. +#[repr(C)] +pub struct ElfHeader { + pub e_ident: [u8; 16], + pub e_type: u16, + pub e_machine: u16, + pub e_version: u32, + pub e_entry: usize, + pub e_phoff: usize, + pub e_shoff: usize, + pub e_flags: u32, + pub e_ehsize: u16, + pub e_phentsize: u16, + pub e_phnum: u16, + pub e_shentsize: u16, + pub e_shnum: u16, + pub e_shstrndx: u16, +} + +/// Program header (PT_LOAD or PT_NOTE). +#[repr(C)] +pub struct Phdr { + pub p_type: u32, + pub p_flags: u32, + pub p_offset: usize, + pub p_vaddr: usize, + pub p_paddr: usize, + pub p_filesz: usize, + pub p_memsz: usize, + pub p_align: usize, +} + +/// ELF note header. +#[repr(C)] +pub struct Nhdr { + pub n_namesz: u32, + pub n_descsz: u32, + pub n_type: u32, +} + +// ── Memory segment descriptor ───────────────────────────────────────── + +#[derive(Clone, Copy, Debug)] +pub struct MemSegment { + pub vaddr: usize, + pub size: usize, + pub data: &'static [u8], + pub flags: u32, +} + +impl MemSegment { + pub fn align(&self) -> usize { + core::mem::size_of::() + } +} + +// ── Coredump reason ─────────────────────────────────────────────────── + +#[derive(Clone, Debug)] +#[repr(C)] +pub struct CoredumpReason { + pub signo: i32, + pub code: i32, + pub fault_addr: usize, + pub arch_specific: usize, +} + +// ── Helpers ─────────────────────────────────────────────────────────── + +pub fn write_struct(buf: &mut [u8], offset: &mut usize, val: &T) { + let sz = mem::size_of::(); + let ptr = val as *const T as *const u8; + unsafe { + buf[*offset..*offset + sz].copy_from_slice(core::slice::from_raw_parts(ptr, sz)); + } + *offset += sz; +} + +pub fn align4(offset: &mut usize) { + *offset = (*offset + 3) & !3; +} + +pub fn align_to(offset: &mut usize, align: usize) { + *offset = (*offset + align - 1) & !(align - 1); +} + +pub fn write_phdr_at(buf: &mut [u8], phoff: usize, phdr: Phdr) { + let sz = mem::size_of::(); + let ptr = &phdr as *const Phdr as *const u8; + unsafe { + buf[phoff..phoff + sz].copy_from_slice(core::slice::from_raw_parts(ptr, sz)); + } +} + +// ── ELF builder ─────────────────────────────────────────────────────── + +pub fn build_elf(notes: &[u8], segments: &[MemSegment], buf: &mut [u8]) -> Result { + let mut offset = 0; + let phnum = 1 + segments.len(); + let ehdr_size = mem::size_of::(); + let phdr_size = mem::size_of::(); + + // Phase 1: write ELF Header + let total_needed = ehdr_size + + phnum * phdr_size + + notes.len() + + segments.iter().map(|s| s.data.len() + s.align()).sum::(); + if buf.len() < total_needed { + return Err(()); + } + + let ehdr = ElfHeader { + e_ident: ELF_IDENT, + e_type: ET_CORE, + e_machine: EM_MACHINE, + e_version: 1, + e_entry: 0, + e_phoff: ehdr_size as usize, + e_shoff: 0, + e_flags: 0, + e_ehsize: ehdr_size as u16, + e_phentsize: phdr_size as u16, + e_phnum: phnum as u16, + e_shentsize: 0, + e_shnum: 0, + e_shstrndx: 0, + }; + write_struct(buf, &mut offset, &ehdr); + + // Phase 2: reserve space for Program Headers + let phdr_notes_off = offset; + offset += phdr_size; + let mut load_phdr_offs = [0usize; 32]; + for i in 0..segments.len() { + load_phdr_offs[i] = offset; + offset += phdr_size; + } + + // Phase 3: write Notes (4-byte aligned) + align4(&mut offset); + let notes_file_off = offset; + buf[offset..offset + notes.len()].copy_from_slice(notes); + offset += notes.len(); + + // Phase 4: write Segment data + let mut load_file_offs = [0usize; 32]; + for (i, seg) in segments.iter().enumerate() { + align_to(&mut offset, seg.align()); + load_file_offs[i] = offset; + buf[offset..offset + seg.data.len()].copy_from_slice(seg.data); + offset += seg.data.len(); + } + + // Phase 5: backfill Program Headers + write_phdr_at( + buf, + phdr_notes_off, + Phdr { + p_type: PT_NOTE, + p_flags: 0, + p_offset: notes_file_off, + p_vaddr: 0, + p_paddr: 0, + p_filesz: notes.len(), + p_memsz: 0, + p_align: 4, + }, + ); + for (i, seg) in segments.iter().enumerate() { + write_phdr_at( + buf, + load_phdr_offs[i], + Phdr { + p_type: PT_LOAD, + p_flags: seg.flags, + p_offset: load_file_offs[i], + p_vaddr: seg.vaddr, + p_paddr: seg.vaddr, + p_filesz: seg.data.len(), + p_memsz: seg.size, + p_align: seg.align(), + }, + ); + } + + Ok(offset) +} diff --git a/kernel/src/coredump/mod.rs b/kernel/src/coredump/mod.rs new file mode 100644 index 00000000..5e74b047 --- /dev/null +++ b/kernel/src/coredump/mod.rs @@ -0,0 +1,188 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! BlueKernel coredump module. +//! +//! Generates an ELF ET_CORE file on-device, covering registers and memory. +//! Supports multiple backends (file, logging, memory) and dump modes. + +pub mod arch; +pub mod backend; +pub mod elf; +pub mod notes; +pub mod regions; +pub mod signal; + +use crate::coredump::arch::capture_current_regs; +use crate::coredump::backend::{CoredumpBackend, COREDUMP_BUF_SIZE}; +use crate::coredump::elf::{build_elf, CoredumpReason}; +use crate::coredump::notes::build_notes; +use crate::coredump::regions::{collect_regions, DumpMode}; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::fmt::Write; + +/// Recursion guard: prevents re-entrant coredumps. +static COREDUMP_IN_PROGRESS: AtomicBool = AtomicBool::new(false); + +/// Static buffer for building the ELF coredump. +/// +/// Must be large enough to hold the ELF header, program headers, notes, +/// and all segment data (BSS, stacks). On 64-bit platforms where BSS can +/// be 2+ MB and system stacks 512 KB, 4 MB is required. On 32-bit +/// platforms 256 KB suffices. +/// +/// Using a static buffer instead of a stack allocation avoids stack +/// overflow since thread stacks (64 KB) are much smaller than the +/// coredump buffer. +#[link_section = ".coredump_bss"] +static mut ELF_BUF: [u8; COREDUMP_BUF_SIZE] = [0u8; COREDUMP_BUF_SIZE]; + +/// Maximum combined size of all notes (must match notes.rs). +const NOTES_BUF_SIZE: usize = 2048; + +/// Static buffer for formatting the coredump file path at runtime. +/// Used by FileBackend when VFS is enabled. +#[cfg(all(enable_vfs, not(coredump_mem_backend)))] +static mut COREDUMP_PATH_BUF: [u8; 64] = [0u8; 64]; + +/// Helper: writes a formatted path into a `&mut [u8]` buffer. +#[cfg(all(enable_vfs, not(coredump_mem_backend)))] +#[derive(Debug)] +struct PathWriter { + buf: &'static mut [u8], + pos: usize, +} + +#[cfg(all(enable_vfs, not(coredump_mem_backend)))] +impl Write for PathWriter { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + let remaining = self.buf.len().saturating_sub(self.pos); + let n = core::cmp::min(s.len(), remaining); + self.buf[self.pos..self.pos + n].copy_from_slice(s.as_bytes()); + self.pos += n; + Ok(()) + } +} + +/// Generate a coredump with the given reason and dump mode. +/// +/// Returns `true` if the coredump was successfully generated. +/// +/// # Safety +/// +/// Must be called with interrupts disabled. The coredump buffer is +/// a large stack allocation — ensure the stack is large enough. +#[inline(never)] +pub fn dump(reason: &CoredumpReason, mode: DumpMode) -> bool { + // Recursion guard + if COREDUMP_IN_PROGRESS.swap(true, Ordering::AcqRel) { + return false; + } + + // Static buffer for the ELF output. + // SAFETY: coredump runs with interrupts disabled and COREDUMP_IN_PROGRESS + // prevents re-entry, so exclusive access is guaranteed. + let elf_buf = unsafe { &mut *core::ptr::addr_of_mut!(ELF_BUF) }; + + // ── Step 1: Capture registers ────────────────────────────────────── + let regs = capture_current_regs(); + + // ── Step 2: Build notes ──────────────────────────────────────────── + let tid = crate::scheduler::current_thread_id(); + let (notes_buf, notes_written) = build_notes(tid, ®s, reason); + + // ── Step 3: Collect memory regions ───────────────────────────────── + let collector = collect_regions(mode); + let segments = collector.segments(); + + // ── Step 4: Build ELF ────────────────────────────────────────────── + let notes_slice = ¬es_buf[..notes_written]; + match build_elf(notes_slice, segments, &mut elf_buf[..]) { + Ok(written) => { + // ── Step 5: Output via backend ───────────────────────────── + // Backend selection priority: MemoryBackend > FileBackend > LoggingBackend + #[cfg(coredump_mem_backend)] + let mut backend = { + // SAFETY: COREDUMP_STORAGE is a static mut buffer; coredump runs + // with interrupts disabled and COREDUMP_IN_PROGRESS guard prevents + // re-entry, so exclusive write access is guaranteed. + let buf = unsafe { &mut COREDUMP_STORAGE[..] }; + backend::MemoryBackend::new(buf) + }; + #[cfg(all(not(coredump_mem_backend), enable_vfs))] + let mut backend = { + let pid = crate::scheduler::current_thread_id(); + let path: &'static str = unsafe { + let mut writer = PathWriter { buf: &mut COREDUMP_PATH_BUF, pos: 0 }; + let _ = write!(writer, "/tmp/blueos.core.{}", pid); + let len = writer.pos; + core::str::from_utf8_unchecked(&COREDUMP_PATH_BUF[..len]) + }; + backend::FileBackend::new(path) + }; + #[cfg(all(not(coredump_mem_backend), not(enable_vfs), not(cortex_m)))] + let mut backend = backend::LoggingBackend::new(); + #[cfg(all(not(coredump_mem_backend), not(enable_vfs), cortex_m))] + let mut backend = backend::LoggingBackend::new(); + + backend.write(&elf_buf[..written]); + backend.finalize(mode); + + COREDUMP_IN_PROGRESS.store(false, Ordering::Release); + true + } + Err(()) => { + // Buffer too small — log and abort + #[cfg(not(use_defmt))] + semihosting::eprintln!("[COREDUMP] ELF buffer exhausted ({})", COREDUMP_BUF_SIZE); + #[cfg(use_defmt)] + defmt::error!("[COREDUMP] ELF buffer exhausted"); + COREDUMP_IN_PROGRESS.store(false, Ordering::Release); + false + } + } +} + +/// Generate a coredump using the default backend for the current platform. +/// +/// Convenience wrapper that calls `dump()` with `DumpMode::Current`. +pub fn dump_current(reason: &CoredumpReason) -> bool { + dump(reason, DumpMode::Current) +} + +/// Reserved coredump buffer for the MemoryBackend (256 KB). +#[cfg(coredump_mem_backend)] +#[link_section = ".coredump_bss"] +pub static mut COREDUMP_STORAGE: [u8; COREDUMP_BUF_SIZE] = [0u8; COREDUMP_BUF_SIZE]; + +/// Valid flag for MemoryBackend: set to `true` after a coredump is written. +#[cfg(coredump_mem_backend)] +pub(crate) static COREDUMP_VALID: AtomicBool = AtomicBool::new(false); + +/// Initialize the coredump subsystem. +/// +/// When the MemoryBackend is enabled, clears the storage buffer and +/// resets the valid flag so a fresh coredump can be detected. +#[cfg(coredump_mem_backend)] +pub fn init() { + // SAFETY: called once at boot, single-threaded, before any coredump. + unsafe { + core::ptr::write_bytes(COREDUMP_STORAGE.as_mut_ptr(), 0, COREDUMP_BUF_SIZE); + } + COREDUMP_VALID.store(false, Ordering::Release); +} + +/// Initialize the coredump subsystem (no-op when MemoryBackend is disabled). +#[cfg(not(coredump_mem_backend))] +pub fn init() {} diff --git a/kernel/src/coredump/notes.rs b/kernel/src/coredump/notes.rs new file mode 100644 index 00000000..bd3f7b9a --- /dev/null +++ b/kernel/src/coredump/notes.rs @@ -0,0 +1,191 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! NT_PRSTATUS, NT_PRPSINFO, NT_SIGINFO note builders. +//! +//! Uses a fixed-size staging buffer — no heap, no alloc. + +use crate::coredump::elf::*; +use core::mem; + +/// Maximum combined size of all notes. +const NOTES_BUF_SIZE: usize = 2048; + +/// Write cursor over a `&mut [u8]`. +struct NoteCursor<'a> { + buf: &'a mut [u8], + pos: usize, +} + +impl<'a> NoteCursor<'a> { + fn new(buf: &'a mut [u8]) -> Self { + Self { buf, pos: 0 } + } + + fn remaining(&self) -> usize { + self.buf.len().saturating_sub(self.pos) + } + + fn push(&mut self, data: &[u8]) { + let end = self.pos + data.len(); + if end <= self.buf.len() { + self.buf[self.pos..end].copy_from_slice(data); + self.pos = end; + } + } + + fn pad4(&mut self) { + let pad = (4 - (self.pos & 3)) & 3; + if pad > 0 { + self.push(&[0u8; 3][..pad]); + } + } + + fn written(&self) -> &[u8] { + &self.buf[..self.pos] + } +} + +/// Append a single ELF note (Nhdr + name + desc, 4-byte aligned) into `cursor`. +fn push_note(cur: &mut NoteCursor, n_type: u32, name: &[u8], desc: &[u8]) { + let nhdr = Nhdr { + n_namesz: name.len() as u32, + n_descsz: desc.len() as u32, + n_type, + }; + let nhdr_bytes = unsafe { + core::slice::from_raw_parts(&nhdr as *const Nhdr as *const u8, mem::size_of::()) + }; + cur.push(nhdr_bytes); + cur.push(name); + cur.pad4(); + cur.push(desc); + cur.pad4(); +} + +/// Build all notes (NT_PRSTATUS + NT_PRPSINFO + NT_SIGINFO) into `notes_buf`. +/// +/// Returns the buffer and the actual number of bytes written. +pub fn build_notes(tid: usize, regs: &[u8], reason: &CoredumpReason) -> ([u8; NOTES_BUF_SIZE], usize) { + let mut buf = [0u8; NOTES_BUF_SIZE]; + let mut cur = NoteCursor::new(&mut buf); + + // ── NT_PRSTATUS ───────────────────────────────────────────────── + // Layout: siginfo-like header + pid/ppid/pgrp/sid + registers + let prstatus_desc = build_prstatus_descriptor(tid, regs, reason); + push_note(&mut cur, NT_PRSTATUS, b"CORE", &prstatus_desc); + + // ── NT_PRPSINFO ───────────────────────────────────────────────── + struct Prpsinfo { + state: u8, + sname: i8, + zombie: u8, + nice: u8, + _pad: [u8; 4], + uid: u16, + gid: u16, + pid: u32, + ppid: u32, + pgrp: u32, + sid: u32, + fname: [u8; 16], + psargs: [u8; 80], + } + let mut fname = [0u8; 16]; + fname[..7].copy_from_slice(b"blueos\x00"); + let prpsinfo = Prpsinfo { + state: 0, + sname: b'?' as i8, + zombie: 0, + nice: 0, + _pad: [0u8; 4], + uid: 0, + gid: 0, + pid: tid as u32, + ppid: 0, + pgrp: 0, + sid: 0, + fname, + psargs: [0u8; 80], + }; + let prpsinfo_bytes = unsafe { + core::slice::from_raw_parts( + &prpsinfo as *const Prpsinfo as *const u8, + mem::size_of::(), + ) + }; + push_note(&mut cur, NT_PRPSINFO, b"CORE", prpsinfo_bytes); + + // ── NT_SIGINFO ────────────────────────────────────────────────── + let reason_bytes = unsafe { + core::slice::from_raw_parts( + reason as *const CoredumpReason as *const u8, + mem::size_of::(), + ) + }; + push_note(&mut cur, NT_SIGINFO, b"CORE", reason_bytes); + + // Record the actual written length, zero the rest + let written = cur.pos; + if written < NOTES_BUF_SIZE { + buf[written..].fill(0); + } + (buf, written) +} + +/// Return the maximum notes buffer size. +/// The actual written length is returned alongside the buffer by `build_notes`. +pub fn notes_len() -> usize { + NOTES_BUF_SIZE +} + +/// Build NT_PRSTATUS descriptor: siginfo (12/16 bytes) + pad + pid/ppid/pgrp/sid + regs. +fn build_prstatus_descriptor(tid: usize, regs: &[u8], reason: &CoredumpReason) -> [u8; 1100] { + let mut desc = [0u8; 1100]; + let mut pos = 0usize; + + // si_signo (4 bytes) + desc[pos..pos + 4].copy_from_slice(&reason.signo.to_ne_bytes()); + pos += 4; + // si_code (4 bytes) + desc[pos..pos + 4].copy_from_slice(&reason.code.to_ne_bytes()); + pos += 4; + // si_errno (4 bytes) + let zero32: [u8; 4] = 0u32.to_ne_bytes(); + desc[pos..pos + 4].copy_from_slice(&zero32); + pos += 4; + + // padding: 2 × pointer-sized int + let pad_sz = 2 * mem::size_of::(); + pos += pad_sz; + + // pid + desc[pos..pos + 4].copy_from_slice(&(tid as u32).to_ne_bytes()); + pos += 4; + // ppid + desc[pos..pos + 4].copy_from_slice(&zero32); + pos += 4; + // pgrp + desc[pos..pos + 4].copy_from_slice(&zero32); + pos += 4; + // sid + desc[pos..pos + 4].copy_from_slice(&zero32); + pos += 4; + + // registers + let regs_len = core::cmp::min(regs.len(), desc.len().saturating_sub(pos)); + desc[pos..pos + regs_len].copy_from_slice(®s[..regs_len]); + + desc +} diff --git a/kernel/src/coredump/regions.rs b/kernel/src/coredump/regions.rs new file mode 100644 index 00000000..c8b1ed08 --- /dev/null +++ b/kernel/src/coredump/regions.rs @@ -0,0 +1,173 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Memory region collection for coredump. +//! +//! Gathers PT_LOAD segments from linker symbols and thread stacks. +//! Uses only static slices — no heap allocation. + +use crate::coredump::elf::MemSegment; + +// ── Linker symbols ────────────────────────────────────────────────────── + +extern "C" { + static __bss_start: u8; + static __bss_end: u8; + static __sys_stack_start: u8; + static __sys_stack_end: u8; +} + +/// Dump mode: controls how much memory is collected. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum DumpMode { + /// Current thread only (registers + current thread stack). + Current, + /// All thread stacks + system regions. + Threads, + /// Full RAM dump (all regions defined by the linker). + Full, +} + +/// Number of segment slots reserved in the segment array. +const MAX_SEGMENTS: usize = 32; + +/// Collected memory segments and their count. +#[derive(Debug)] +pub struct RegionCollector { + segments: [MemSegment; MAX_SEGMENTS], + count: usize, +} + +impl RegionCollector { + pub const fn new() -> Self { + Self { + segments: [MemSegment { + vaddr: 0, + size: 0, + data: &[], + flags: 0, + }; MAX_SEGMENTS], + count: 0, + } + } + + fn push(&mut self, seg: MemSegment) { + if self.count < MAX_SEGMENTS { + self.segments[self.count] = seg; + self.count += 1; + } + } + + pub fn segments(&self) -> &[MemSegment] { + &self.segments[..self.count] + } +} + +/// Collect BSS region as a zero-fill segment. +fn collect_bss(col: &mut RegionCollector) { + let start = unsafe { &__bss_start as *const u8 as usize }; + let end = unsafe { &__bss_end as *const u8 as usize }; + let size = end.saturating_sub(start); + if size > 0 { + col.push(MemSegment { + vaddr: start, + size, + data: unsafe { core::slice::from_raw_parts(start as *const u8, size) }, + flags: 2, // PF_W + }); + } +} + +/// Collect system stack region. +fn collect_sys_stack(col: &mut RegionCollector) { + let start = unsafe { &__sys_stack_start as *const u8 as usize }; + let end = unsafe { &__sys_stack_end as *const u8 as usize }; + let size = end.saturating_sub(start); + if size > 0 { + col.push(MemSegment { + vaddr: start, + size, + data: unsafe { core::slice::from_raw_parts(start as *const u8, size) }, + flags: 3, // PF_R | PF_W + }); + } +} + +/// Collect the current thread's stack region. +fn collect_current_stack(col: &mut RegionCollector) { + let thread = crate::scheduler::current_thread_ref(); + let base = thread.stack_base(); + let size = thread.stack_size(); + if size > 0 { + col.push(MemSegment { + vaddr: base, + size, + data: unsafe { core::slice::from_raw_parts(base as *const u8, size) }, + flags: 3, + }); + } +} + +/// Collect all thread stacks using the GlobalQueueVisitor API. +/// +/// Iterates over every thread in the global queue, skipping RETIRED +/// threads (their stacks are freed). Each live thread's entire stack +/// region is added as a PT_LOAD segment. +fn collect_all_stacks(col: &mut RegionCollector) { + use crate::thread::GlobalQueueVisitor; + use crate::thread::RETIRED; + + let mut visitor = GlobalQueueVisitor::new(); + while let Some(thread_node) = visitor.next() { + let thread = thread_node.lock(); + if thread.state() == RETIRED { + continue; + } + let base = thread.stack_base(); + let size = thread.stack_size(); + if size > 0 && base != 0 { + col.push(MemSegment { + vaddr: base, + size, + data: unsafe { core::slice::from_raw_parts(base as *const u8, size) }, + flags: 3, + }); + } + } +} + +/// Build the segment list for the given dump mode. +pub fn collect_regions(mode: DumpMode) -> RegionCollector { + let mut col = RegionCollector::new(); + + // System regions are always included. + collect_bss(&mut col); + collect_sys_stack(&mut col); + + match mode { + DumpMode::Current => { + collect_current_stack(&mut col); + } + DumpMode::Threads => { + collect_all_stacks(&mut col); + } + DumpMode::Full => { + collect_all_stacks(&mut col); + // Full mode also collects BSS fully (already done above), + // plus any additional heap regions (future). + } + } + + col +} diff --git a/kernel/src/coredump/signal.rs b/kernel/src/coredump/signal.rs new file mode 100644 index 00000000..1cfb8e43 --- /dev/null +++ b/kernel/src/coredump/signal.rs @@ -0,0 +1,150 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Fault-type to signal number mapping. +//! +//! Each architecture provides a function that maps its fault exception +//! class/code to a POSIX signal number (from libc). + +// libc signal numbers +const SIGILL: i32 = 4; +const SIGTRAP: i32 = 5; +const SIGABRT: i32 = 6; +const SIGFPE: i32 = 8; +const SIGBUS: i32 = 10; +const SIGSEGV: i32 = 11; + +/// Map AArch64 ESR Exception Class (EC) to a signal number. +pub fn aarch64_ec_to_signo(ec: u64) -> i32 { + match ec { + // Instruction abort (MMU) + 0x20 | 0x21 => SIGSEGV, + // Data abort (MMU) + 0x24 | 0x25 => SIGSEGV, + // SP alignment fault + 0x26 => SIGBUS, + // SError + 0x2F => SIGBUS, + // Breakpoint, watchpoint, etc. + 0x30..=0x35 => SIGTRAP, + // Unknown EC + 0x00 => SIGILL, + // Everything else + _ => SIGILL, + } +} + +/// Map RISC-V mcause exception code to a signal number. +/// +/// `mcause` exception codes (interrupt bit masked out already): +/// 0: instruction address misaligned +/// 1: instruction access fault +/// 2: illegal instruction +/// 3: breakpoint +/// 4: load address misaligned +/// 5: load access fault +/// 6: store address misaligned +/// 7: store access fault +/// 8: ecall from U-mode +/// 9: ecall from S-mode +/// 10: reserved +/// 11: ecall from M-mode +/// 12: instruction page fault +/// 13: load page fault +/// 14: reserved +/// 15: store page fault +pub fn riscv_mcause_to_signo(mcause: usize) -> i32 { + match mcause { + 1..=3 => SIGBUS, + 4 => SIGSEGV, + 5 => SIGSEGV, + 6 => SIGILL, + 8 => SIGILL, + 12 => SIGSEGV, + 13 => SIGSEGV, + 15 => SIGSEGV, + _ => SIGILL, + } +} + +/// Map ARM Cortex-M CFSR fault type to a signal number. +/// +/// CFSR is a 32-bit register formed from: +/// [31:16] MMFSR (MemManage Fault Status) +/// [15:8] BFSR (Bus Fault Status) +/// [7:0] UFSR (Usage Fault Status) +pub fn arm_cfsr_to_signo(cfsr: u32) -> i32 { + // Usage Fault status (low 8 bits) + let ufsr = (cfsr & 0xFF) as u8; + // Bus Fault status (bits 15:8) + let bfsr = ((cfsr >> 8) & 0xFF) as u8; + // MemManage Fault status (bits 31:16) + let mmfsr = ((cfsr >> 16) & 0xFF) as u8; + + // Memory management faults take precedence + if arm_mmfsr_has_fault(mmfsr) { + // IACCVIOL (instruction access) → SIGSEGV + // DACCVIOL (data access) → SIGSEGV + return SIGSEGV; + } + + // Bus faults + if arm_bfsr_has_fault(bfsr) { + // IBUSERR, PRECISERR, IMPRECISERR, UNSTKERR, STKERR → SIGBUS + return SIGBUS; + } + + // Usage faults + if ufsr & 0x01 != 0 { + // UNDEFINSTR → SIGILL + return SIGILL; + } + if ufsr & 0x02 != 0 { + // INVSTATE → SIGILL + return SIGILL; + } + if ufsr & 0x04 != 0 { + // INVPC → SIGILL + return SIGILL; + } + if ufsr & 0x08 != 0 { + // NOCP → SIGILL + return SIGILL; + } + if ufsr & 0x10 != 0 { + // UNALIGNED → SIGBUS + return SIGBUS; + } + if ufsr & 0x20 != 0 { + // DIVBYZERO → SIGFPE + return SIGFPE; + } + + SIGILL +} + +fn arm_mmfsr_has_fault(mmfsr: u8) -> bool { + // Bits: IACCVIOL(0), DACCVIOL(1), MLSPERR(2), MSTKERR(3), MUNSTKERR(4) + mmfsr & 0x1F != 0 +} + +fn arm_bfsr_has_fault(bfsr: u8) -> bool { + // Bits: IBUSERR(0), PRECISERR(1), IMPRECISERR(2), UNSTKERR(3), STKERR(4) + bfsr & 0x1F != 0 +} + +/// Signal number for a generic kernel panic. +pub fn panic_signo() -> i32 { + SIGABRT +} diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index c53a46bd..57eee555 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -64,6 +64,8 @@ pub mod boards; pub(crate) mod boot; pub mod config; pub mod console; +#[cfg(enable_coredump)] +pub mod coredump; #[cfg(coverage)] pub mod coverage; pub(crate) mod devices; @@ -239,6 +241,15 @@ mod tests { #[panic_handler] fn oops(info: &PanicInfo) -> ! { let _guard = DisableInterruptGuard::new(); + + #[cfg(enable_coredump)] + crate::coredump::dump_current(&crate::coredump::elf::CoredumpReason { + signo: crate::coredump::signal::panic_signo(), + code: 0, + fault_addr: 0, + arch_specific: 0, + }); + #[cfg(not(use_defmt))] { semihosting::println!("{}", info); diff --git a/kernel/src/vfs/mod.rs b/kernel/src/vfs/mod.rs index 272549ce..4af9f358 100644 --- a/kernel/src/vfs/mod.rs +++ b/kernel/src/vfs/mod.rs @@ -36,7 +36,7 @@ mod fs; mod inode; mod inode_mode; mod mount; -mod path; +pub mod path; #[cfg(procfs)] mod procfs; #[cfg(procfs)] @@ -48,7 +48,7 @@ pub mod syscalls; mod tmpfs; mod utils; use alloc::string::String; -pub use file::AccessMode; +pub use file::{AccessMode, FileOps}; #[cfg(enable_net)] pub use sockfs::{alloc_sock_fd, free_sock_fd, get_sock_by_fd, sock_attach_to_fd}; diff --git a/kernel/tests/coredump.checker b/kernel/tests/coredump.checker new file mode 100644 index 00000000..81ee510b --- /dev/null +++ b/kernel/tests/coredump.checker @@ -0,0 +1,3 @@ +// TOTAL-TIMEOUT: 30 +// ASSERT-SUCC: Coredump integration test end. +// ASSERT-FAIL: Backtrace in Panic.* \ No newline at end of file diff --git a/kernel/tests/coredump_test.rs b/kernel/tests/coredump_test.rs new file mode 100644 index 00000000..1a375e08 --- /dev/null +++ b/kernel/tests/coredump_test.rs @@ -0,0 +1,53 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Coredump integration test. +//! +//! Calls dump_current() with a synthetic CoredumpReason and validates +//! that the coredump machinery completes without panic. + +#![no_main] +#![no_std] + +extern crate rsrt; +use semihosting::println; + +#[no_mangle] +fn main() -> i32 { + println!("Coredump integration test start..."); + + #[cfg(enable_coredump)] + { + let reason = blueos::coredump::elf::CoredumpReason { + signo: 6, // SIGABRT + code: 0, + fault_addr: 0, + arch_specific: 0, + }; + let ok = blueos::coredump::dump_current(&reason); + if ok { + println!("Coredump generated successfully."); + } else { + println!("Coredump skipped (already in progress or disabled)."); + } + } + + #[cfg(not(enable_coredump))] + { + println!("Coredump not enabled in this configuration."); + } + + println!("Coredump integration test end."); + 0 +} \ No newline at end of file