Linux的系统调用是怎么样运行的
一、辅助函数
1. entry文件概述
- 作用:处理系统调用、中断、异常(如缺页、除零错误等),以及任务切换(如信号处理、调度)。
- 关键特性
- 保存/恢复寄存器状态。
- 系统调用入口(
system_call
、sysenter_entry
)。 - 中断处理(
common_interrupt
)。 - 异常处理(如
divide_error
、page_fault
)。 - 返回用户空间的逻辑(
ret_from_exception
、resume_userspace
)。
2. 核心数据结构
栈布局(Stack Layout)
在 ret_from_system_call
时,栈的结构如下(偏移量从 %esp
开始):
0(%esp) - %ebx | 通用寄存器保存顺序(SAVE_ALL 宏)
4(%esp) - %ecx |
8(%esp) - %edx |
C(%esp) - %esi |
10(%esp) - %edi |
14(%esp) - %ebp |
18(%esp) - %eax | 返回值或系统调用号
1C(%esp) - %ds | 段寄存器
20(%esp) - %es |
24(%esp) - orig_eax | 原始系统调用号(可能被修改)
28(%esp) - %eip | 用户态返回地址
2C(%esp) - %cs | 代码段寄存器
30(%esp) - %eflags | CPU 标志寄存器
34(%esp) - %oldesp | 用户态栈指针
38(%esp) - %oldss | 用户态栈段寄存器
3. 关键宏定义
3.1.寄存器保存与恢复
3.1.1.SAVE_ALL
#define SAVE_ALL \cld; \pushl %es; \pushl %ds; \pushl %eax; \pushl %ebp; \pushl %edi; \pushl %esi; \pushl %edx; \pushl %ecx; \pushl %ebx; \movl $(__USER_DS), %edx; \movl %edx, %ds; \movl %edx, %es;
这段代码是 Linux 内核(x86 架构)中用于保存进程上下文的宏定义,通常出现在系统调用、中断或异常处理的入口处。它的核心作用是将寄存器状态保存到内核栈,并设置正确的段寄存器,以确保内核代码能安全访问用户空间数据。
cld
- 作用:清除方向标志位(Direction Flag,DF)。
- 内核要求:DF 必须为 0(默认递增),避免字符串操作反向执行导致错误。
- 用户态可能设置 DF=1:因此在进入内核时需显式清除。
保存段寄存器 es
和 ds
pushl %es;
pushl %ds;
- 作用:将
es
和ds
寄存器的值压入内核栈。ds
和es
是数据段寄存器。- 内核需要保存用户态的段寄存器值,以便后续恢复(如返回用户态时)。
保存通用寄存器
pushl %eax;
pushl %ebp;
pushl %edi;
pushl %esi;
pushl %edx;
pushl %ecx;
pushl %ebx;
- 作用:按顺序保存
eax
、ebp
、edi
、esi
、edx
、ecx
、ebx
到内核栈。
设置段寄存器
movl $(__USER_DS), %edx; // 加载用户数据段选择子到 edx
movl %edx, %ds; // 设置 ds = 用户数据段
movl %edx, %es; // 设置 es = 用户数据段
- 作用:将
ds
和es
设置为用户数据段选择子(__USER_DS
)。 - 此处设置
__USER_DS
是为了支持从用户态访问内存的场景(如系统调用参数传递)。
3.1.2.RESTORE_ALL
RESTORE_INT_REGS
#define RESTORE_INT_REGS \popl %ebx; \popl %ecx; \popl %edx; \popl %esi; \popl %edi; \popl %ebp; \popl %eax
从栈中恢复通用整数寄存器(ebx
, ecx
, edx
, esi
, edi
, ebp
, eax
)。
RESTORE_REGS
#define RESTORE_REGS \RESTORE_INT_REGS; \
1: popl %ds; \
2: popl %es; \
.section .fixup,"ax"; \
3: movl $0,(%esp); \jmp 1b; \
4: movl $0,(%esp); \jmp 2b; \
.previous; \
.section __ex_table,"a";\.align 4; \.long 1b,3b; \.long 2b,4b; \
.previous
- 功能:在
RESTORE_INT_REGS
基础上,额外恢复段寄存器(ds
,es
)。 - 关键部分
- 段寄存器恢复:
popl %ds
和popl %es
可能触发异常(如无效段选择子)。 - 异常处理
.fixup
段:定义异常修复代码(将栈顶值设为 0,跳过出错的pop
指令)。__ex_table
:异常表,记录可能出错的指令地址(1b
,2b
)和对应的修复代码(3b
,4b
)。
- 段寄存器恢复:
- 机制
- 若
popl %ds
失败,CPU 会触发异常,内核通过__ex_table
跳转到3b
,将栈顶(%esp
指向的值)设为 0(避免后续pop
继续出错),然后跳回1b
下一条指令(即popl %es
)。 - 同理处理
popl %es
的异常。
- 若
RESTORE_ALL
#define RESTORE_ALL \RESTORE_REGS \addl $4, %esp; \
1: iret; \
.section .fixup,"ax"; \
2: sti; \movl $(__USER_DS), %edx; \movl %edx, %ds; \movl %edx, %es; \movl $11,%eax; \call do_exit; \
.previous; \
.section __ex_table,"a";\.align 4; \.long 1b,2b; \
.previous
- 功能:恢复所有寄存器(包括
RESTORE_REGS
)并执行iret
返回用户态。 - 关键部分
addl $4, %esp
:跳过栈中的错误码(某些异常会压入错误码,如缺页异常)。iret
:从中断返回- 异常处理
- 若
iret
失败,跳转到2b
的修复代码:sti
:允许中断(避免死锁)。- 恢复段寄存器:将
ds
和es
设为用户态数据段(__USER_DS
)。 - 调用
do_exit
:终止进程(eax=11
可能是信号编号或错误码)。
__ex_table
:记录iret
的地址(1b
)和修复代码(2b
)。
- 若
4. 核心机制解析
4.1.异常表(__ex_table
)
-
作用:将可能出错的指令地址映射到修复代码,实现内核的“异常安全”。
-
格式
.long <出错指令地址>, <修复代码地址>
-
流程
- CPU 执行出错指令(如
popl %ds
)。 - 内核通过
__ex_table
找到修复代码(如3b
)。 - 执行修复逻辑(如跳过错误指令)。
- CPU 执行出错指令(如
4.2..fixup
段
- 作用:存储修复代码,标记为可执行(
"ax"
表示分配+可执行)。 - 修复逻辑
- 对于
popl %ds
失败:将栈顶值设为 0(避免后续pop
继续出错)。 - 对于
iret
失败:恢复段寄存器并退出进程。
- 对于
5.从fork系统调用返回用户空间
ENTRY(ret_from_fork)pushl %eaxcall schedule_tailGET_THREAD_INFO(%ebp)popl %eaxjmp syscall_exit
这个代码片段处理新创建的子进程在完成fork后,如何正确返回到用户空间继续执行。
5.1. 入口标签
ENTRY(ret_from_fork)
- 定义函数入口点:
ret_from_fork
是子进程开始执行的地方 - 调用场景:当
fork()
系统调用创建新进程后,子进程从这里开始执行
5.2. 保存系统调用返回值
pushl %eax
- 保存
eax
寄存器:将eax
的值压栈保护 eax
的作用:在系统调用中,eax
存放返回值。对于fork:- 父进程:
eax
= 子进程的PID - 子进程:
eax
= 0
- 父进程:
5.3. 调用调度尾处理
call schedule_tail
asmlinkage void schedule_tail(task_t *prev)__releases(rq->lock)
{finish_task_switch(prev);if (current->set_child_tid)put_user(current->pid, current->set_child_tid);
}
关键函数:schedule_tail()
完成进程切换的清理工作:
- 清理前一个任务的相关资源
5.4. 获取线程信息
GET_THREAD_INFO(%ebp)
宏展开:这个宏获取当前进程的 thread_info
结构
#define GET_THREAD_INFO(reg) \movl $-THREAD_SIZE, reg; \andl %esp, reg
$-THREAD_SIZE
- 相当于
~(THREAD_SIZE - 1)
,得到内核栈地址的掩码 - 如果是4KB的栈,即
0xFFFFE000
andl %esp, reg
-
将栈地址和掩码相与,可以得到栈的基地址,即thread_info结构地址
-
结果:
ebp
寄存器指向当前进程的thread_info结构
5.5. 恢复系统调用返回值
popl %eax
- 恢复
eax
值:将之前压栈的系统调用返回值弹出 - 对于子进程,
eax
= 0(fork的返回值)
5.6. 跳转到系统调用退出
jmp syscall_exit
最终跳转:进入通用的系统调用退出路径,包括:
- 检查是否需要信号处理
- 恢复用户空间寄存器
- 执行
iret
指令返回用户空间
二、system_call
系统调用进入
ENTRY(system_call)pushl %eax # save orig_eaxSAVE_ALLGET_THREAD_INFO(%ebp)# system call tracing in operationtestb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)jnz syscall_trace_entrycmpl $(nr_syscalls), %eaxjae syscall_badsys
syscall_call:call *sys_call_table(,%eax,4)movl %eax,EAX(%esp) # store the return value:
1.ENTRY宏的定义和作用
#define ENTRY(name) \.globl name; \ALIGN; \name:
2.代码详细解析
2.1.第一行:保存原始系统调用号
pushl %eax # save orig_eax
%eax
包含系统调用号- 保存到栈上,因为后面会修改EAX
2.2.第二行:保存所有寄存器
SAVE_ALL
2.3.第三行:获取线程信息
GET_THREAD_INFO(%ebp)
GET_THREAD_INFO
宏:获取当前线程的 thread_info
2.4.第四-五行:系统调用跟踪检查
testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
jnz syscall_trace_entry
- 检查是否需要系统调用跟踪或审计
- 如果需要,跳转到跟踪处理代码
2.5.第六-七行:系统调用号验证
cmpl $(nr_syscalls), %eax
jae syscall_badsys
- 比较系统调用号是否超出范围
- 如果超出,跳转到错误处理
2.6.第八-九行:调用系统调用处理函数
syscall_call:
call *sys_call_table(,%eax,4)
movl %eax,EAX(%esp) # store the return value
关键操作:
sys_call_table(,%eax,4)
:系统调用表基址 + EAX*4call *...
:间接调用对应的系统调用函数movl %eax,EAX(%esp)
:保存返回值到栈上的pt_regs
中
ENTRY(sys_call_table).long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */.long sys_exit.long sys_fork.long sys_read.long sys_write.long sys_open /* 5 */...
该表保存了指定系统调用号的函数地址,一个地址四个字节,所以4 x 系统调用号 + 系统调用表基址 就是要调用的函数地址
3.实际执行流程示例
普通系统调用:
1. 用户程序:mov eax, 1 (exit), int 0x80
2. 进入system_call:- push %eax, SAVE_ALL- GET_THREAD_INFO- 检查跟踪 → 不需要- 检查系统调用号 → 有效- call sys_exit- 保存返回值
被跟踪的系统调用:
1. strace附加到进程
2. 设置_TIF_SYSCALL_TRACE标志
3. 进入system_call:- 检查跟踪标志 → 设置- 跳转到syscall_trace_entry- 通知strace- 执行实际系统调用
三、syscall_exit
系统调用退出
#define TI_flags 8 /* offsetof(struct thread_info, flags) */
#define _TIF_ALLWORK_MASK 0x0000FFFF /* work to do on any return to u-space */
work_notifysig_v86:pushl %ecx # save ti_flags for do_notify_resumecall save_v86_state # %eax contains pt_regs pointerpopl %ecxmovl %eax, %espxorl %edx, %edxcall do_notify_resumejmp restore_all
work_notifysig: # deal with pending signals and# notify-resume requeststestl $VM_MASK, EFLAGS(%esp)movl %esp, %eaxjne work_notifysig_v86 # returning to kernel-space or# vm86-spacexorl %edx, %edxcall do_notify_resumejmp restore_all
work_pending:testb $_TIF_NEED_RESCHED, %cljz work_notifysig
ENTRY(resume_userspace)cli # make sure we don't miss an interrupt# setting need_resched or sigpending# between sampling and the iretmovl TI_flags(%ebp), %ecxandl $_TIF_WORK_MASK, %ecx # is there any work to be done on# int/exception return?jne work_pendingjmp restore_all
syscall_exit_work:testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SINGLESTEP), %cljz work_pendingsti # could let do_syscall_trace() call# schedule() insteadmovl %esp, %eaxmovl $1, %edxcall do_syscall_tracejmp resume_userspace
syscall_exit:cli # make sure we don't miss an interrupt# setting need_resched or sigpending# between sampling and the iretmovl TI_flags(%ebp), %ecxtestw $_TIF_ALLWORK_MASK, %cx # current->workjne syscall_exit_work
1.代码流程解析
1.1.入口点:syscall_exit
syscall_exit:cli # 禁用中断movl TI_flags(%ebp), %ecx # 加载线程标志到ECXtestw $_TIF_ALLWORK_MASK, %cx # 检查是否有工作需要做jne syscall_exit_work # 有工作,跳转到工作处理# 否则直接恢复执行
1.2.系统调用工作处理:syscall_exit_work
syscall_exit_work:testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SINGLESTEP), %cljz work_pending # 没有跟踪/审计/单步,跳转到通用工作sti # 启用中断(允许调度)movl %esp, %eax # 传递pt_regs指针movl $1, %edx # entryexit=1(系统调用退出)call do_syscall_trace # 处理系统调用跟踪jmp resume_userspace # 返回用户空间检查
1.3.通用工作调度:work_pending
work_pending:testb $_TIF_NEED_RESCHED, %cl # 检查是否需要重新调度jz work_notifysig # 不需要调度,处理信号# 需要调度时会调用schedule()
1.4.信号通知处理:work_notifysig
work_notifysig:testl $VM_MASK, EFLAGS(%esp) # 检查是否从VM86模式返回movl %esp, %eaxjne work_notifysig_v86 # VM86模式,特殊处理xorl %edx, %edx # oldset = NULLcall do_notify_resume # 处理信号和通知jmp restore_all # 恢复执行
1.5.VM86模式特殊处理:work_notifysig_v86
work_notifysig_v86:pushl %ecx # 保存线程标志call save_v86_state # 保存VM86状态,返回pt_regspopl %ecx # 恢复线程标志movl %eax, %esp # 使用新的栈指针xorl %edx, %edx # oldset = NULLcall do_notify_resume # 处理信号和通知jmp restore_all # 恢复执行
1.6.用户空间恢复:resume_userspace
ENTRY(resume_userspace)cli # 禁用中断movl TI_flags(%ebp), %ecx # 重新加载线程标志andl $_TIF_WORK_MASK, %ecx # 检查工作掩码jne work_pending # 有工作,继续处理jmp restore_all # 没有工作,恢复执行
2.完整的执行流程
2.1.场景1:普通系统调用(无特殊工作)
1. syscall_exit:- 检查TIF_ALLWORK_MASK → 无工作- 直接restore_all返回用户空间
2.2.场景2:系统调用跟踪(strace
)
1. syscall_exit:- 检查TIF_ALLWORK_MASK → 有工作
2. syscall_exit_work:- 检查TIF_SYSCALL_TRACE → 设置- 调用do_syscall_trace(regs, 1)- 跳转到resume_userspace
3. resume_userspace:- 重新检查工作标志- 如果没有其他工作,restore_all
2.3.场景3:信号递送
1. syscall_exit:- 检查TIF_ALLWORK_MASK → 有工作
2. syscall_exit_work:- 检查跟踪标志 → 未设置- 跳转到work_pending
3. work_pending:- 检查TIF_NEED_RESCHED → 未设置- 跳转到work_notifysig
4. work_notifysig:- 检查VM86模式 → 不是- 调用do_notify_resume(regs, NULL, flags)- restore_all返回用户空间
四、VM86状态保存save_v86_state
struct pt_regs * fastcall save_v86_state(struct kernel_vm86_regs * regs)
{struct tss_struct *tss;struct pt_regs *ret;unsigned long tmp;/** This gets called from entry.S with interrupts disabled, but* from process context. Enable interrupts here, before trying* to access user space.*/local_irq_enable();if (!current->thread.vm86_info) {printk("no vm86_info: BAD\n");do_exit(SIGSEGV);}set_flags(regs->eflags, VEFLAGS, VIF_MASK | current->thread.v86mask);tmp = copy_to_user(¤t->thread.vm86_info->regs,regs, VM86_REGS_SIZE1);tmp += copy_to_user(¤t->thread.vm86_info->regs.VM86_REGS_PART2,®s->VM86_REGS_PART2, VM86_REGS_SIZE2);tmp += put_user(current->thread.screen_bitmap,¤t->thread.vm86_info->screen_bitmap);if (tmp) {printk("vm86: could not access userspace vm86_info\n");do_exit(SIGSEGV);}tss = &per_cpu(init_tss, get_cpu());current->thread.esp0 = current->thread.saved_esp0;current->thread.sysenter_cs = __KERNEL_CS;load_esp0(tss, ¤t->thread);current->thread.saved_esp0 = 0;put_cpu();loadsegment(fs, current->thread.saved_fs);loadsegment(gs, current->thread.saved_gs);ret = KVM86->regs32;return ret;
}
1.函数功能
save_v86_state()
负责从虚拟8086模式切换回保护模式,并保存VM86状态到用户空间
2.背景:虚拟8086模式(VM86)
什么是VM86模式?
- x86处理器的一种特殊模式
- 允许在保护模式下运行实模式(8086)程序
- 用于DOS模拟器、16位应用程序兼容性
VM86 vs 保护模式:
特性 | VM86模式 | 保护模式 |
---|---|---|
内存访问 | 1MB实模式 | 4GB保护模式 |
特权级 | 环3(用户态) | 环0-3 |
分段 | 64KB段 | 4GB段 |
用途 | 16位程序 | 32/64位程序 |
3.函数详细解析
3.1.第一阶段:启用中断并检查状态
local_irq_enable();if (!current->thread.vm86_info) {printk("no vm86_info: BAD\n");do_exit(SIGSEGV);
}
关键操作:
local_irq_enable()
:启用中断(在entry.S
中中断被禁用)- 检查
vm86_info
:确保有有效的VM86上下文结构 - 如果没有,进程终止(SIGSEGV)
3.2.第二阶段:保存标志寄存器
set_flags(regs->eflags, VEFLAGS, VIF_MASK | current->thread.v86mask);
标志处理:
VEFLAGS
:VM86有效的标志位VIF_MASK
:虚拟中断标志v86mask
:进程特定的VM86掩码- 作用:过滤和保存相关的EFLAGS位
3.3.第三阶段:复制寄存器状态到用户空间
tmp = copy_to_user(¤t->thread.vm86_info->regs, regs, VM86_REGS_SIZE1);
tmp += copy_to_user(¤t->thread.vm86_info->regs.VM86_REGS_PART2,®s->VM86_REGS_PART2, VM86_REGS_SIZE2);
tmp += put_user(current->thread.screen_bitmap, ¤t->thread.vm86_info->screen_bitmap);
if (tmp) {printk("vm86: could not access userspace vm86_info\n");do_exit(SIGSEGV);
}
错误处理:如果复制到用户空间失败,终止进程。
3.4.第四阶段:恢复保护模式栈
tss = &per_cpu(init_tss, get_cpu());
current->thread.esp0 = current->thread.saved_esp0;
current->thread.sysenter_cs = __KERNEL_CS;
load_esp0(tss, ¤t->thread);
current->thread.saved_esp0 = 0;
put_cpu();
TSS(任务状态段)操作:
init_tss
:每CPU的TSS结构esp0
:环0栈指针(内核栈)saved_esp0
:保存的保护模式栈指针load_esp0()
:更新TSS中的ESP0字段
作用:从VM86栈切换回正常的保护模式内核栈
3.5.第五阶段:恢复段寄存器
loadsegment(fs, current->thread.saved_fs);
loadsegment(gs, current->thread.saved_gs);
段寄存器恢复:
- 在VM86模式中,FS/GS可能被修改
- 恢复为保护模式的值
- 确保正常的内存访问
3.6.第六阶段:返回保护模式寄存器状态
ret = KVM86->regs32;
return ret;
KVM86
结构:内核内部的VM86管理结构,包含保护模式的寄存器状态
4.完整的VM86切换流程
4.1.进入VM86模式:
1. 用户程序调用vm86()系统调用
2. 内核设置VM86环境:- 保存保护模式寄存器- 设置VM86段描述符- 初始化VM86信息结构
3. 切换到VM86模式执行16位代码
4.2.退出VM86模式(本函数):
1. VM86程序触发异常或中断
2. 进入内核VM86处理程序
3. 调用save_v86_state():- 保存VM86寄存器状态到用户空间- 恢复保护模式栈和段寄存器- 返回保护模式寄存器状态
4. 继续正常的保护模式执行
五、系统调用跟踪和审计do_syscall_trace
__attribute__((regparm(3)))
void do_syscall_trace(struct pt_regs *regs, int entryexit)
{if (unlikely(current->audit_context)) {if (!entryexit)audit_syscall_entry(current, regs->orig_eax,regs->ebx, regs->ecx,regs->edx, regs->esi);elseaudit_syscall_exit(current, regs->eax);}if (!test_thread_flag(TIF_SYSCALL_TRACE) &&!test_thread_flag(TIF_SINGLESTEP))return;if (!(current->ptrace & PT_PTRACED))return;/* the 0x80 provides a way for the tracing parent to distinguishbetween a syscall stop and SIGTRAP delivery */ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) &&!test_thread_flag(TIF_SINGLESTEP) ? 0x80 : 0));/** this isn't the same as continuing with a signal, but it will do* for normal use. strace only continues with a signal if the* stopping signal is not SIGTRAP. -brl*/if (current->exit_code) {send_sig(current->exit_code, current, 1);current->exit_code = 0;}
}
1.函数属性解析
__attribute__((regparm(3)))
x86性能优化:
- 前3个参数通过寄存器传递(EAX, EDX, ECX)
- 减少栈操作,提高性能
2.函数参数
void do_syscall_trace(struct pt_regs *regs, int entryexit)
regs
:包含系统调用参数的寄存器状态entryexit
:0=系统调用入口,1=系统调用出口
3.函数详细解析
3.1.第一阶段:系统调用审计
if (unlikely(current->audit_context)) {if (!entryexit)audit_syscall_entry(current, regs->orig_eax,regs->ebx, regs->ecx,regs->edx, regs->esi);elseaudit_syscall_exit(current, regs->eax);
}
审计子系统:
current->audit_context
:进程的审计上下文
入口审计(entryexit=0
):
- 系统调用号:
regs->orig_eax
- 参数:
ebx, ecx, edx, esi
(x86系统调用参数)
出口审计(entryexit=1
):
- 返回值:
regs->eax
3.2.第二阶段:跟踪条件检查
if (!test_thread_flag(TIF_SYSCALL_TRACE) &&!test_thread_flag(TIF_SINGLESTEP))return;
if (!(current->ptrace & PT_PTRACED))return;
跟踪标志:
TIF_SYSCALL_TRACE
:系统调用跟踪启用TIF_SINGLESTEP
:单步调试启用PT_PTRACED
:进程被ptrace
跟踪
快速返回:如果没有启用跟踪,立即返回避免性能损失。
3.3.第三阶段:通知调试器
ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) &&!test_thread_flag(TIF_SINGLESTEP) ? 0x80 : 0));
条件分析:
PT_TRACESYSGOOD
:调试器要求区分系统调用陷阱和普通SIGTRAP!TIF_SINGLESTEP
:不是单步调试模式- 结果:如果条件满足,信号值 =
SIGTRAP | 0x80
设计目的:
- 普通SIGTRAP:信号值 = 5
- 系统调用SIGTRAP:信号值 = 133 (5 | 0x80)
- 帮助调试器区分不同类型的停止
3.4.第四阶段:处理退出代码
if (current->exit_code) {send_sig(current->exit_code, current, 1);current->exit_code = 0;
}
退出代码机制:
current->exit_code
:调试器设置的信号编号- 当调试器希望进程继续执行但附带一个信号时使用
4.完整工作流程
4.1.场景1:strace系统调用跟踪
1. strace attach到目标进程
2. 设置 TIF_SYSCALL_TRACE 和 PT_PTRACED
3. 目标进程执行系统调用:- 入口:do_syscall_trace(regs, 0)→ 审计记录(如果有)→ 通知strace:SIGTRAP|0x80→ strace显示系统调用和参数- 执行系统调用- 出口:do_syscall_trace(regs, 1)→ 审计记录返回值→ 通知strace:SIGTRAP|0x80→ strace显示返回值
4.2.场景2:GDB单步调试
1. GDB单步执行
2. 设置 TIF_SINGLESTEP 和 PT_PTRACED
3. 遇到系统调用:- 入口:do_syscall_trace(regs, 0)→ 通知GDB:普通SIGTRAP(无0x80)- GDB显示汇编指令- 用户单步继续