pt_regs小笔记

struct pt_regs用以在堆栈中保存异常发生时的现场寄存器信息,其具体定义与CPU架构相关。此外使用eBPF的uprobe和uretprobe时,上下文信息就是使用该结构体类型。

关于使用eBPF对pt_regs进行操作需要注意的是:你永远不能修改寄存器的值,但是如果寄存器的值是一个对象的地址,你可以顺着地址去修改那块地址的内容~

1. 上下文切换与pt_regs

pt_regs里其实就存储了各种寄存器的值。https://unix.stackexchange.com/questions/696743/how-is-the-execution-state-saved-at-context-switch-in-x86-64-linux-kernel

上下文切换时,cpu会将通用寄存器的值保存在内核栈中,存储为数据结构pt_regs(注意CPU不只是这么一些寄存器,pt_regs只保留重要的、通用的寄存器)。另一方面,其余的CPU信息(如进程和线程等)就保存在内核数据结构thread_struct中。

2. pt_regs定义:

arch/x86/include/asm/ptrace.h中通过宏定义了32位/64位下的pt_regs定义。如果想知道自己系统中pt_regs的定义,从vmlinux.h中获取即可。

用户态下可包含头文件uapi/linux/ptrace.h。(uapi指代userspace API,向用户态提供内核中的数据结构)

2.1 32位系统下pt_regs定义:

注意:在该头文件中以下pt_regs的定义是只有#ifndef __KERNEL__成立时才会存在,目前根据查找的资料,__KERNEL__宏的存在,是为了编译一些只有内核才会只用到的代码。因此我们只需要关注__KERNEL__宏不存在的情况即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
int xfs;
int xgs;
long orig_eax;
long eip;
int xcs;
long eflags;
long esp;
int xss;
};

2.2 64位系统下pt_regs定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct pt_regs {
unsigned long r15;
unsigned long r14;
unsigned long r13;
unsigned long r12;
unsigned long rbp;
unsigned long rbx;
/* arguments: non interrupts/non tracing syscalls only save up to here*/
unsigned long r11;
unsigned long r10;
unsigned long r9;
unsigned long r8;
unsigned long rax;
unsigned long rcx;
unsigned long rdx;
unsigned long rsi;
unsigned long rdi;
unsigned long orig_rax;
/* end of arguments */
/* cpu exception frame or undefined */
unsigned long rip;
unsigned long cs;
unsigned long eflags;
unsigned long rsp;
unsigned long ss;
/* top of stack page */
};

3. 使用BCC对pt_regs结构体进行访问和修改:

PT_REGS_RC(ctx)保存的就是返回值

  如果寄存器太多实在记不清楚,也可以使用BCC提供的一些宏来帮助使用:https://github.com/iovisor/bcc/blob/master/src/cc/export/helpers.h

  下面是x64系统下普通函数相关宏的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
// ...
#elif defined(bpf_target_x86)
#define PT_REGS_PARM1(ctx) ((ctx)->di)
#define PT_REGS_PARM2(ctx) ((ctx)->si)
#define PT_REGS_PARM3(ctx) ((ctx)->dx)
#define PT_REGS_PARM4(ctx) ((ctx)->cx)
#define PT_REGS_PARM5(ctx) ((ctx)->r8)
#define PT_REGS_PARM6(ctx) ((ctx)->r9)
#define PT_REGS_RET(ctx) ((ctx)->sp)
#define PT_REGS_FP(ctx) ((ctx)->bp) /* Works only with CONFIG_FRAME_POINTER */
#define PT_REGS_RC(ctx) ((ctx)->ax)
#define PT_REGS_IP(ctx) ((ctx)->ip)
#define PT_REGS_SP(ctx) ((ctx)->sp)

  下面是syscall的相关宏定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Helpers for syscall params. Pass in a ctx returned from PT_REGS_SYSCALL_CTX.
*/
#define PT_REGS_PARM1_SYSCALL(ctx) PT_REGS_PARM1(ctx)
#define PT_REGS_PARM2_SYSCALL(ctx) PT_REGS_PARM2(ctx)
#define PT_REGS_PARM3_SYSCALL(ctx) PT_REGS_PARM3(ctx)
#if defined(bpf_target_x86)
#define PT_REGS_PARM4_SYSCALL(ctx) ((ctx)->r10) /* for syscall only */
#else
#define PT_REGS_PARM4_SYSCALL(ctx) PT_REGS_PARM4(ctx)
#endif
#define PT_REGS_PARM5_SYSCALL(ctx) PT_REGS_PARM5(ctx)
#ifdef PT_REGS_PARM6
#define PT_REGS_PARM6_SYSCALL(ctx) PT_REGS_PARM6(ctx)
#endif

3.1 对函数参数和返回值进行修改:

相关文档:https://www.man7.org/linux/man-pages/man7/bpf-helpers.7.html

注意,寄存器的值是不可修改的,但是如果寄存器的值是一个地址,就可以修改地址所指向内存的内容。

long bpf_probe_read(void *dst, u32 size, const void *unsafe_ptr)

Description

For tracing programs, safely attempt to read size bytes from kernel space address unsafe_ptr and store the data in dst.

Generally, use bpf_probe_read_user() or bpf_probe_read_kernel() instead.

Return 0 on success, or a negative error in case of failure.

long bpf_probe_write_user(void *dst, const void *src, u32 len)

Description
Attempt in a safe way to write len bytes from the buffer src to dst in memory. It only works for threads that are in user context, and dst must be a valid user space address.

This helper should not be used to implement any kind of security mechanism because of TOC-TOU attacks, but rather to debug, divert, and manipulate execution of emi-cooperative processes.

Keep in mind that this feature is meant for experiments, and it has a risk of crashing the system and running programs. Therefore, when an eBPF program using this helper is attached, a warning including PID and process name is printed to kernel logs.

Return 0 on success, or a negative error in case of failure.

下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from bcc import BPF

program = r"""
#include <uapi/linux/ptrace.h>

struct Test {
int a;
};

int change_param(struct pt_regs *ctx) {
int pid = bpf_get_current_pid_tgid() & 0xffffffff;
struct Test* param = (struct Test*)PT_REGS_PARM1(ctx);

bpf_trace_printk("%d: %d", pid, param->a);

int a = 22;
u64 success = bpf_probe_write_user(&(param->a), &a, sizeof(a));
bpf_trace_printk("%d - success: %ld", param->a, success);
return 0;
}
"""

bpf = BPF(text=program)

bpf.attach_uprobe(name="/home/fanqiliang/project/ebpf/change_param/test", sym_re=".*say.*", fn_name="change_param")

bpf.trace_print()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <unistd.h>

using namespace std;

class Test {
public:
int a;
};

int saySomething(Test *test) {
cout << "Param: " << (*test).a << endl;

return 100;
}

int main() {
struct Test test1;
test1.a = 2;
while (true) {
cout << "Ret: " << saySomething(&test1) << endl;
sleep(1);
}
return 0;
}

pt_regs小笔记
https://www.torch-fan.site/2023/05/03/pt-regs小笔记/
作者
Torch-Fan
发布于
2023年5月3日
更新于
2023年5月9日
许可协议