Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions kernel/src/process/execve.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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, &param, &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();
}
Expand Down
1 change: 1 addition & 0 deletions kernel/src/process/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
36 changes: 36 additions & 0 deletions kernel/src/process/trace.rs
Original file line number Diff line number Diff line change
@@ -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],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 保持 sched_process_exec 的 filename ABI

这里把事件 payload 定义成 comm 会让 /tracing/events/sched/sched_process_exec/format 暴露为 comm,pid,old_pid,而 Linux 6.6 的同名 tracepoint ABI 是 filename,pid,old_pidfilename 还是第一项,见 https://codebrowser.dev/linux/linux/include/trace/events/sched.h.html#431)。因此按 Linux format 编译或按 filename 过滤/解析的 eBPF/trace 工具会在 DragonOS 上找不到字段,raw offset 也会读错,恰好破坏本次为 agentsight 兼容该 tracepoint 的目的;即使暂时不能实现动态 __string,也应保留 filename 字段语义/名称并从 exec 参数填充。

Useful? React with 👍 / 👎.

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)
})
);
191 changes: 191 additions & 0 deletions user/apps/tests/dunitest/suites/normal/sched_tracepoint.cc
Original file line number Diff line number Diff line change
@@ -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 <gtest/gtest.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

#include <string>

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<size_t>(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<ssize_t>(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();
}
1 change: 1 addition & 0 deletions user/apps/tests/dunitest/whitelist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,4 @@ normal/virtiofs_dax
normal/af_packet_sockopt
normal/af_packet_e2e
normal/af_packet_mcast
normal/sched_tracepoint
Loading