为什么 orig_ax 没有正确的系统调用号
Why is orig_ax not having proper system call number
我写了一个基本的 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 上下文必须保存在内核堆栈中,然后您的处理程序才会被执行。所以 regs
是 int 3
的上下文,而不是 glibc 用来触发内核系统调用的 syscall
指令的上下文。你得到的值orig_ax
是CPU interrupt/exception时的错误码。它的值为零,因为 int 3
中断不会产生任何错误,因此内核将零压入堆栈作为占位符,这使整个实现更加通用。
你应该怎么做:
如果你想获取系统调用号,你应该在 do_syscall_64
上放置一个探测器,这是调用系统调用时执行的第一个 C 函数。或者您可以探测 entry_SYSCALL_64
,它是 syscall
/int 0x80
汇编指令的中断处理程序。
详情:
系统调用机制是使用CPU陷阱门实现的。当你在 C 语言中调用 fork()
时,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
相关的函数。当_do_fork
被调用时,regs
信息已经丢失,所以你无法得到系统调用相关的任何信息。
我写了一个基本的 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 上下文必须保存在内核堆栈中,然后您的处理程序才会被执行。所以 regs
是 int 3
的上下文,而不是 glibc 用来触发内核系统调用的 syscall
指令的上下文。你得到的值orig_ax
是CPU interrupt/exception时的错误码。它的值为零,因为 int 3
中断不会产生任何错误,因此内核将零压入堆栈作为占位符,这使整个实现更加通用。
你应该怎么做:
如果你想获取系统调用号,你应该在 do_syscall_64
上放置一个探测器,这是调用系统调用时执行的第一个 C 函数。或者您可以探测 entry_SYSCALL_64
,它是 syscall
/int 0x80
汇编指令的中断处理程序。
详情:
系统调用机制是使用CPU陷阱门实现的。当你在 C 语言中调用 fork()
时,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
相关的函数。当_do_fork
被调用时,regs
信息已经丢失,所以你无法得到系统调用相关的任何信息。