diff --git a/kernel/src/process/execve.rs b/kernel/src/process/execve.rs index 7827aec25..0cc93ed8f 100644 --- a/kernel/src/process/execve.rs +++ b/kernel/src/process/execve.rs @@ -1,3 +1,4 @@ +use super::trace::trace_sched_process_exec; use crate::arch::CurrentIrqArch; use crate::exception::InterruptArch; use crate::filesystem::vfs::fcntl::AtFlags; @@ -128,6 +129,11 @@ fn do_execve_internal( let old_vm = do_execve_switch_user_vm(address_space.clone()); + // 捕获 sched_process_exec 的 old_pid:必须在 load_binary_file_with_context 之前, + // 因为该函数内的 begin_new_exec → de_thread 会在「非 leader 线程 execve」时 + // 交换 current 与旧 thread-group leader 的 raw_pid(对齐 Linux fs/exec.c:1770)。 + let old_pid = ProcessManager::current_pcb().raw_pid().data() as i32; + // 尝试加载二进制文件 let load_result = load_binary_file_with_context(&mut param, &ctx); @@ -214,6 +220,26 @@ fn do_execve_internal( let vfork_done = pcb.thread.write_irqsave().vfork_done.take(); let exec_ret = Syscall::arch_do_execve(regs, ¶m, &result, user_sp, argv_ptr); if exec_ret.is_ok() { + // sched_process_exec:arch_do_execve 成功、用户态寄存器就绪后触发, + // 对齐 Linux fs/exec.c:1803(trace 在 start_thread 之后、所有失败点之后)。 + let pid = pcb.raw_pid().data() as i32; + // 先把 comm 复制到栈缓冲并释放 basic 读锁:trace 默认回调内部的 + // trace_cmdline_push 会再次获取 basic 读锁,持锁重入有 deadlock 风险。 + let mut comm_buf = [0u8; 16]; + let mut comm_len; + { + let basic_guard = pcb.basic(); + let name = basic_guard.name(); + comm_len = name.len().min(15); + // 回退到 UTF-8 字符边界,避免在多字节字符中间截断导致 from_utf8 失败。 + while comm_len > 0 && !name.is_char_boundary(comm_len) { + comm_len -= 1; + } + comm_buf[..comm_len].copy_from_slice(&name.as_bytes()[..comm_len]); + } + let comm = core::str::from_utf8(&comm_buf[..comm_len]).unwrap_or(""); + trace_sched_process_exec(comm, pid, old_pid); + if let Some(completion) = vfork_done { completion.complete_all(); } diff --git a/kernel/src/process/mod.rs b/kernel/src/process/mod.rs index 847ab86c3..31c7df7ca 100644 --- a/kernel/src/process/mod.rs +++ b/kernel/src/process/mod.rs @@ -24,6 +24,7 @@ pub mod signal; pub mod stdio; pub mod syscall; pub mod timer; +pub mod trace; pub mod utils; pub mod wait; diff --git a/kernel/src/process/trace.rs b/kernel/src/process/trace.rs new file mode 100644 index 000000000..718b2d9e8 --- /dev/null +++ b/kernel/src/process/trace.rs @@ -0,0 +1,36 @@ +//! 进程/调度类 tracepoint 声明。 +//! +//! 字段参考 Linux `include/trace/events/sched.h`。 +//! 注意:DragonOS 的 `define_event_trace!` 宏不支持 Linux 的 `__string` 动态字符串, +//! 且当前未实现 `bpf_get_current_comm()` helper,故 `comm` 直接放入 payload。 + +use crate::define_event_trace; + +define_event_trace!( + sched_process_exec, + TP_system(sched), + TP_PROTO(comm: &str, pid: i32, old_pid: i32), + TP_STRUCT__entry { + comm: [u8; 16], + pid: i32, + old_pid: i32, + }, + TP_fast_assign { + comm: { + // 对齐 Linux TASK_COMM_LEN=16(15 字符 + NUL)。 + let mut buf = [0u8; 16]; + let bytes = comm.as_bytes(); + let len = bytes.len().min(15); + buf[..len].copy_from_slice(&bytes[..len]); + buf + }, + pid: pid, + old_pid: old_pid, + }, + TP_ident(__entry), + TP_printk({ + let nul = __entry.comm.iter().position(|&b| b == 0).unwrap_or(16); + let comm = core::str::from_utf8(&__entry.comm[..nul]).unwrap_or("invalid utf8"); + format!("comm={} pid={} old_pid={}", comm, __entry.pid, __entry.old_pid) + }) +); diff --git a/user/apps/tests/dunitest/suites/normal/sched_tracepoint.cc b/user/apps/tests/dunitest/suites/normal/sched_tracepoint.cc new file mode 100644 index 000000000..138052198 --- /dev/null +++ b/user/apps/tests/dunitest/suites/normal/sched_tracepoint.cc @@ -0,0 +1,191 @@ +// sched_process_exec tracepoint 语义测试。 +// +// 验证: +// 1. sched_process_exec 事件在 debugfs 下正确导出(enable/format/id 文件 + format 字段)。 +// 2. enable 后执行 execve 会触发该 tracepoint,并在 trace 文件中留下记录。 +// +// 对应 issue #2149。 + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +constexpr char kHelperExec[] = "--sched-tp-helper-exit0"; + +// 读 path 全部内容(非阻塞,读到 EOF 为止)。 +std::string read_all(const char* path) { + int fd = open(path, O_RDONLY); + EXPECT_GE(fd, 0) << "open(" << path << ") failed: errno=" << errno << " (" + << strerror(errno) << ")"; + if (fd < 0) { + return {}; + } + std::string out; + char buf[256]; + while (true) { + ssize_t n = read(fd, buf, sizeof(buf)); + if (n <= 0) { + break; + } + out.append(buf, static_cast(n)); + } + close(fd); + return out; +} + +// 向 path 写 data,返回是否写成功。 +bool write_file(const char* path, const char* data) { + int fd = open(path, O_WRONLY); + if (fd < 0) { + return false; + } + size_t len = strlen(data); + ssize_t n = write(fd, data, len); + close(fd); + return n == static_cast(len); +} + +// mount debugfs 到 root。 +void mount_debugfs(const char* root) { + ASSERT_EQ(0, mount("none", root, "debugfs", 0, nullptr)) + << "mount debugfs failed: errno=" << errno << " (" << strerror(errno) << ")"; +} + +// 子模式:被 execve 进来后立即退出 0。 +[[noreturn]] void helper_exec_exit0() { + char arg0[] = "/proc/self/exe"; + char arg1[] = "--sched-tp-helper-exit0"; + char* const argv[] = {arg0, arg1, nullptr}; + char* const envp[] = {nullptr}; + execve("/proc/self/exe", argv, envp); + _exit(127); +} + +} // namespace + +// 事件文件存在且 format 含全部字段。 +TEST(SchedProcessExecTp, EventFilesExist) { + char root[128] = {}; + snprintf(root, sizeof(root), "/tmp/sched_tp_events_%d", getpid()); + ASSERT_EQ(0, mkdir(root, 0755)) << strerror(errno); + + mount_debugfs(root); + + const char* base_rel = "/tracing/events/sched/sched_process_exec"; + char base[256] = {}; + snprintf(base, sizeof(base), "%s%s", root, base_rel); + + struct stat st = {}; + ASSERT_EQ(0, stat(base, &st)) << "missing event dir " << base << ": " << strerror(errno); + EXPECT_TRUE(S_ISDIR(st.st_mode)); + + char file[320] = {}; + for (const char* leaf : {"enable", "format", "id"}) { + snprintf(file, sizeof(file), "%s/%s", base, leaf); + ASSERT_EQ(0, stat(file, &st)) + << "missing " << file << ": " << strerror(errno); + } + + // format 文件应含事件名与全部字段。 + snprintf(file, sizeof(file), "%s/format", base); + std::string fmt = read_all(file); + ASSERT_FALSE(fmt.empty()); + for (const char* needle : + {"sched_process_exec", "common_pid", "comm", "pid", "old_pid"}) { + EXPECT_NE(std::string::npos, fmt.find(needle)) + << "format missing \"" << needle << "\"\n" + << fmt; + } + + // enable 默认为 "0"(未启用)。 + snprintf(file, sizeof(file), "%s/enable", base); + std::string enable = read_all(file); + EXPECT_NE(std::string::npos, enable.find("0")) << "enable not '0' by default: " << enable; + + // id 应为非负整数(DragonOS tracepoint id 从 0 开始递增分配)。 + snprintf(file, sizeof(file), "%s/id", base); + std::string id = read_all(file); + EXPECT_FALSE(id.empty()); + char* end = nullptr; + long idval = strtol(id.c_str(), &end, 10); + // 确认整个字符串都是数字(允许尾部换行),而非仅前缀可解析。 + ASSERT_NE(end, id.c_str()) << "id not numeric: " << id; + while (end != nullptr && (*end == '\n' || *end == '\r' || *end == ' ')) ++end; + EXPECT_EQ(end != nullptr && *end == '\0', true) << "id has trailing garbage: " << id; + EXPECT_GE(idval, 0) << "invalid id: " << id; + + EXPECT_EQ(0, umount(root)) << strerror(errno); + EXPECT_EQ(0, rmdir(root)) << strerror(errno); +} + +// enable 后 execve 应触发事件,trace 文件留下记录。 +TEST(SchedProcessExecTp, FiresOnExecve) { + char root[128] = {}; + snprintf(root, sizeof(root), "/tmp/sched_tp_fire_%d", getpid()); + ASSERT_EQ(0, mkdir(root, 0755)) << strerror(errno); + + mount_debugfs(root); + + const char* base_rel = "/tracing/events/sched/sched_process_exec"; + char base[256] = {}; + snprintf(base, sizeof(base), "%s%s", root, base_rel); + + // 启用事件。 + char enable_path[320] = {}; + snprintf(enable_path, sizeof(enable_path), "%s/enable", base); + ASSERT_TRUE(write_file(enable_path, "1")) << "enable write failed"; + + // 清空 ring buffer:向 trace 写任意字节触发 clear。 + char trace_path[256] = {}; + snprintf(trace_path, sizeof(trace_path), "%s/tracing/trace", root); + ASSERT_TRUE(write_file(trace_path, "1")) << "trace clear write failed"; + + // fork + execve 自身触发 sched_process_exec。 + pid_t child = fork(); + ASSERT_GE(child, 0) << "fork failed: " << strerror(errno); + if (child == 0) { + helper_exec_exit0(); + } + + int status = 0; + ASSERT_EQ(child, waitpid(child, &status, 0)) << "waitpid failed: " << strerror(errno); + ASSERT_TRUE(WIFEXITED(status)) << "child did not exit normally, status=" << status; + EXPECT_EQ(0, WEXITSTATUS(status)) << "helper exit code != 0"; + + // 读 trace 快照,断言含 sched_process_exec 记录。 + std::string trace = read_all(trace_path); + ASSERT_FALSE(trace.empty()) << "trace empty after execve"; + EXPECT_NE(std::string::npos, trace.find("sched_process_exec(")) + << "no sched_process_exec record in trace:\n" + << trace; + // TP_printk 输出的字段。 + EXPECT_NE(std::string::npos, trace.find("comm=")) + << "trace missing comm= field:\n" + << trace; + + // 关闭事件并清理。 + write_file(enable_path, "0"); + EXPECT_EQ(0, umount(root)) << strerror(errno); + EXPECT_EQ(0, rmdir(root)) << strerror(errno); +} + +int main(int argc, char** argv) { + // 子模式:被 execve 进来后立即退出 0。 + if (argc >= 2 && strcmp(argv[1], kHelperExec) == 0) { + _exit(0); + } + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/user/apps/tests/dunitest/whitelist.txt b/user/apps/tests/dunitest/whitelist.txt index fbfc9c3af..4143c0979 100644 --- a/user/apps/tests/dunitest/whitelist.txt +++ b/user/apps/tests/dunitest/whitelist.txt @@ -85,3 +85,4 @@ normal/virtiofs_dax normal/af_packet_sockopt normal/af_packet_e2e normal/af_packet_mcast +normal/sched_tracepoint