c - 为什么 orig_ax 没有正确的系统调用号
问题描述
我编写了一个基本的 kprobe linux 内核模块,它将为 fork 注册一个处理程序,并在处理程序中打印寄存器“orig_ax”的值。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
MODULE_LICENSE("GPL");
static struct kprobe kp;
static char *name = "_do_fork";
module_param(name, charp, 0);
static int pre_handler(struct kprobe *p, struct pt_regs *regs)
{
printk("orig_ax regs:%lu \t ax:%lu\n", regs->orig_ax, regs->ax);
return 0;
}
static void post_handler(struct kprobe *p, struct pt_regs *regs,
unsigned long flags)
{
}
static int __init hello_init(void)
{
/* set the handler functions */
kp.pre_handler = pre_handler;
kp.post_handler = post_handler;
kp.symbol_name = name;
register_kprobe(&kp);
return 0;
}
static void __exit hello_exit(void)
{
unregister_kprobe(&kp);
}
module_init(hello_init);
module_exit(hello_exit);
我得到一个不同的值而不是 57
[ 9251.954392] orig_ax regs:0 ax:18446661681273651032
我犯错了吗
解决方案
发生了什么:
Kprobe 是通过将原始指令替换为int 3
会导致 CPU 产生软件中断的指令来实现的。在这种情况下,CPU 上下文必须保存在内核堆栈中,然后您的处理程序将被执行。所以这不是glibc用来触发内核系统调用的指令regs
上下文int 3
的上下文。syscall
您获得的值orig_ax
是发生 CPU 中断/异常时的错误代码。它的值为零,因为int 3
中断不会产生任何错误,因此内核将零压入堆栈作为占位符,这使整个实现更加通用。
你该怎么办:
如果要获取系统调用号,则应植入一个探针,在do_syscall_64
该探针上调用系统调用时执行的第一个 C 函数。或者您可以探测/汇编指令entry_SYSCALL_64
的中断处理程序。syscall
int 0x80
细节:
系统调用机制是使用 CPU 陷阱门实现的。当你fork()
用 C 语言调用时,glibc 将执行syscall
汇编指令,系统调用号就rax
如你所知存储在其中。CPU 将产生一个软件产生的中断,并开始执行系统调用的中断处理程序,其地址存储在 IDT 中。
以下代码是 x86_64 上系统调用的中断处理程序。
ENTRY(entry_SYSCALL_64)
UNWIND_HINT_EMPTY
/*
* Interrupts are off on entry.
* We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON,
* it is too small to ever cause noticeable irq latency.
*/
swapgs
/* tss.sp2 is scratch space. */
movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
/* Construct struct pt_regs on stack */
pushq $__USER_DS /* pt_regs->ss */
pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* pt_regs->sp */
pushq %r11 /* pt_regs->flags */
pushq $__USER_CS /* pt_regs->cs */
pushq %rcx /* pt_regs->ip */
GLOBAL(entry_SYSCALL_64_after_hwframe)
pushq %rax /* pt_regs->orig_ax */
PUSH_AND_CLEAR_REGS rax=$-ENOSYS
TRACE_IRQS_OFF
/* IRQs are off. */
movq %rax, %rdi
movq %rsp, %rsi
call do_syscall_64 /* returns with IRQs disabled */
该pushq %rax
指令将rax
(又名系统调用号)保存在内核堆栈上,然后调用do_syscall_64
.
__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
struct thread_info *ti;
enter_from_user_mode();
local_irq_enable();
ti = current_thread_info();
if (READ_ONCE(ti->flags) & _TIF_WORK_SYSCALL_ENTRY)
nr = syscall_trace_enter(regs);
if (likely(nr < NR_syscalls)) {
nr = array_index_nospec(nr, NR_syscalls);
regs->ax = sys_call_table[nr](regs);
#ifdef CONFIG_X86_X32_ABI
} else if (likely((nr & __X32_SYSCALL_BIT) &&
(nr & ~__X32_SYSCALL_BIT) < X32_NR_syscalls)) {
nr = array_index_nospec(nr & ~__X32_SYSCALL_BIT,
X32_NR_syscalls);
regs->ax = x32_sys_call_table[nr](regs);
#endif
}
syscall_return_slowpath(regs);
}
最重要的语句是regs->ax = sys_call_table[nr](regs);
将调用fork
-related 函数。_do_fork
调用时,信息regs
已经丢失,因此您无法获取与系统调用相关的任何信息。
推荐阅读
- heroku - heroku 如何处理网络流量并在 dyno 之间分配
- c# - C# 检查 ref 字段自定义属性
- gams-math - 在 GAMS 中使用 Excel 文件包含数据
- python - 关于模式的建议:将 numpy 数组广播到不同的进程。队列,ZMQ?
- android - ROLI JUCE Android CMake 错误,生成的空白演示应用无法在 Android Studio 中编译
- mysql - 自动向多个表添加属性
- redirect - MediaWiki:默认情况下如何取消选中在移动时创建重定向?
- javascript - !== 不比较字符串
- vue.js - 如何从 fontawesome 只导入一个图标?
- google-apps-script - 在 textInput 上单击输入后如何创建卡片