文章目录
前言
本文继续翻译 Intel vol3 chapter 2 :2.3章节,有些英文单词翻译成中文别扭的我会保留英文,不翻译。
一、System Flags and Fields in the EFLAGS Register
1.1 IA-32 architecture
EFLAGS 寄存器的系统标志和 IOPL 字段控制 I/O、可屏蔽硬件中断、调试、任务切换和virtual-8086 mode。 只有特权代码(通常是操作系统或执行代码)才能修改这些位。系统标志和 IOPL 如下图所示:
(1)TF Trap (bit 8):设置启用单步模式进行调试; 清除以禁用单步模式。 在单步模式下,处理器在每条指令后生成一个调试异常。 这允许在每条指令之后检查程序的执行状态。如果应用程序使用 POPF、POPFD 或 IRET 指令设置 TF 标志,则会在 POPF、POPFD 或 IRET 之后的指令之后生成调试异常。
备注:该标志位就是x86架构下gdb单步调试的原理。
(2)IF Interrupt enable (bit 9):控制处理器对可屏蔽硬件中断请求的响应,该标志设置为响应可屏蔽的硬件中断; 清除以禁止可屏蔽的硬件中断。 IF 标志不影响异常或不可屏蔽中断(NMI 中断)的生成。控制寄存器 CR4 中的 CPL、IOPL 和 VME 标志的状态决定了 IF 标志是否可以被 CLI、STI、POPF、POPFD 和 IRET 修改。
(3)IOPL I/O privilege level field (bits 12 and 13):指示当前运行的程序或任务的 I/O 特权级别 (IOPL)。 当前运行的程序或任务的 CPL 必须小于或等于 IOPL 才能访问 I/O 地址空间。 只有当 CPL 为 0 时,POPF 和 IRET 指令才能修改该字段。
当虚拟模式扩展生效时(当 CR4.VME = 1 时),IOPL 也是控制 IF 标志的修改和 virtual-8086 mode下的中断处理的机制之一。
(4)NT Nested task (bit 14):控制中断的和被调用的任务的链接。处理器在调用由 CALL 指令、中断或异常启动的任务时设置此标志。当由IRET指令启动的任务返回时,它检查并修改这个标志。该标志可以通过POPF/POPFD指令显式地设置或清除;但是,更改此标志的状态可能会在应用程序中产生意外异常。
(5)RF Resume (bit 16):控制处理器对指令断点条件的响应。 设置后,此标志会暂时禁止为指令断点生成调试异常 (#DB)(尽管其他异常条件可能会导致生成异常)。 清除后,指令断点将产生调试异常。
RF 标志的主要功能是允许在由指令断点条件引起的调试异常之后重新启动指令,在此,调试软件必须在使用 IRETD 返回中断程序之前在堆栈上的 EFLAGS image中设置此标志(以防止指令断点导致另一个调试异常)。然后,在返回的指令成功执行后,处理器自动清除该标志,再次启用指令断点错误。
(6)VM Virtual-8086 mode (bit 17):设置启用 virtual-8086 mode; 清除以返回 protected mode。
(7)AC Alignment check or access control (bit 18):如如果在 CR0 寄存器中设置了 AM 位,当且仅当此标志为 1 时,才启用user-mode数据访问的对齐检查。当引用未对齐的操作数时会产生对齐检查异常,例如奇数字节地址处的字或地址不是四的整数倍的双字(doubleword)。 对齐检查异常仅在用户模式(权限级别 3)中生成。 默认为特权级别 0 的内存引用(例如段描述符加载)不会生成此异常,即使是由在用户模式下执行的指令引起的。
备注:对于Intel处理器中,术语字(word)表示16位数据类型,双字( doubleword)表示32位数据类型。
对齐检查( alignment-check)异常可用于检查数据的对齐情况。这在与需要对齐所有数据的处理器交换数据时很有用。解释器也可以使用对齐检查异常,通过 misaligning the pointer 来将一些指针标记为特殊指针。这消除了检查每个指针的开销,并且仅在使用时处理特殊指针。
如果在CR4寄存器中设置了SMAP位,当且仅当该位为1时,允许对用户模式页面进行显式的 supervisor-mode数据访问。
(8)VIF Virtual Interrupt (bit 19):包含 IF 标志的virtual image。此标志与 VIP 标志结合使用。 当控制寄存器 CR4 中的 VME 标志或 PVI 标志被设置且 IOPL 小于 3 时,处理器才识别 VIF 标志。(VME 标志启用虚拟 8086 模式扩展;PVI 标志启用保护模式虚拟中断。)
(9)VIP Virtual interrupt pending (bit 20):软件置位以指示有中断待处理; 清零表示没有待处理的中断。此标志与 VIF 标志结合使用。处理器读取此标志但从不修改它。仅在控制寄存器 CR4 中的 VME 标志或 PVI 标志被设置且 IOPL 小于 3 时,处理器才识别该标志位(VIP)。VME 标志启用虚拟 8086 模式扩展; PVI 标志启用保护模式虚拟中断。
(10)ID Identification (bit 21):表示处理器是否支持 CPUID 指令。
The ability of a program or procedure to set or clear this flag indicates support for the CPUID instruction.
1.2 Intel 64 architecture
在 64 位模式下,RFLAGS 寄存器扩展为 64 位,保留高 32 位。
在 IA-32e 模式下,处理器不允许设置 VM 位,因为不支持虚拟 8086 模式(忽略设置该位的尝试)。
此外,处理器不会设置 NT 位。 然而,处理器确实允许软件设置 NT 位。(注意,IRET在IA-32e模式下,如果设置了NT位,会导致 a general protection fault)
在 IA-32e 模式下,SYSCALL/SYSRET 指令具有一种可编程的方法来指定在 RFLAGS/EFLAGS 中清除哪些位。 这些指令保存/恢复 EFLAGS/RFLAGS。
二、内核源码解析
以 EFLAGS Register 标志位中的 TF Trap (bit 8) 为例:
struct pt_regs 结构体的成员 unsigned long flags 用来表示 EFLAGS Register:
// linux-3.10/arch/x86/include/asm/ptrace.h
struct pt_regs {
unsigned long r15;
unsigned long r14;
unsigned long r13;
unsigned long r12;
unsigned long bp;
unsigned long bx;
/* 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 ax;
unsigned long cx;
unsigned long dx;
unsigned long si;
unsigned long di;
unsigned long orig_ax;
/* end of arguments */
/* cpu exception frame or undefined */
unsigned long ip;
unsigned long cs;
unsigned long flags;
unsigned long sp;
unsigned long ss;
/* top of stack page */
};
// linux-3.10/arch/x86/include/uapi/asm/processor-flags.h
/*
* EFLAGS bits
*/
#define X86_EFLAGS_CF 0x00000001 /* Carry Flag */
#define X86_EFLAGS_BIT1 0x00000002 /* Bit 1 - always on */
#define X86_EFLAGS_PF 0x00000004 /* Parity Flag */
#define X86_EFLAGS_AF 0x00000010 /* Auxiliary carry Flag */
#define X86_EFLAGS_ZF 0x00000040 /* Zero Flag */
#define X86_EFLAGS_SF 0x00000080 /* Sign Flag */
#define X86_EFLAGS_TF 0x00000100 /* Trap Flag */
#define X86_EFLAGS_IF 0x00000200 /* Interrupt Flag */
#define X86_EFLAGS_DF 0x00000400 /* Direction Flag */
#define X86_EFLAGS_OF 0x00000800 /* Overflow Flag */
#define X86_EFLAGS_IOPL 0x00003000 /* IOPL mask */
#define X86_EFLAGS_NT 0x00004000 /* Nested Task */
#define X86_EFLAGS_RF 0x00010000 /* Resume Flag */
#define X86_EFLAGS_VM 0x00020000 /* Virtual Mode */
#define X86_EFLAGS_AC 0x00040000 /* Alignment Check */
#define X86_EFLAGS_VIF 0x00080000 /* Virtual Interrupt Flag */
#define X86_EFLAGS_VIP 0x00100000 /* Virtual Interrupt Pending */
#define X86_EFLAGS_ID 0x00200000 /* CPUID detection flag */
设置任务 struct task_struct 为单步模式:
// linux-3.10/arch/x86/include/asm/thread_info.h
struct thread_info {
......
__u32 flags;
......
};
// linux-3.10/include/linux/thread_info.h
/*
* flag set/clear/test wrappers
* - pass TIF_xxxx constants to these functions
*/
static inline void set_ti_thread_flag(struct thread_info *ti, int flag)
{
set_bit(flag, (unsigned long *)&ti->flags);
}
/* set thread flags in other task's structures
* - see asm/thread_info.h for TIF_xxxx flags available
*/
static inline void set_tsk_thread_flag(struct task_struct *tsk, int flag)
{
set_ti_thread_flag(task_thread_info(tsk), flag);
}
/*
* thread information flags
* - these are process state flags that various assembly files
* may need to access
* - pending work-to-be-done flags are in LSW
* - other flags in MSW
*/
......
#define TIF_SINGLESTEP 4 /* reenable singlestep on user return*/
......
#define TIF_FORCED_TF 24 /* true if TF in eflags artificially */
......
/*
* Enable single-stepping. Return nonzero if user mode is not using TF itself.
*/
static int enable_single_step(struct task_struct *child)
{
struct pt_regs *regs = task_pt_regs(child);
unsigned long oflags;
/*
* If we stepped into a sysenter/syscall insn, it trapped in
* kernel mode; do_debug() cleared TF and set TIF_SINGLESTEP.
* If user-mode had set TF itself, then it's still clear from
* do_debug() and we need to set it again to restore the user
* state so we don't wrongly set TIF_FORCED_TF below.
* If enable_single_step() was used last and that is what
* set TIF_SINGLESTEP, then both TF and TIF_FORCED_TF are
* already set and our bookkeeping is fine.
*/
if (unlikely(test_tsk_thread_flag(child, TIF_SINGLESTEP)))
regs->flags |= X86_EFLAGS_TF;
/*
* Always set TIF_SINGLESTEP - this guarantees that
* we single-step system calls etc.. This will also
* cause us to set TF when returning to user mode.
*/
set_tsk_thread_flag(child, TIF_SINGLESTEP);
oflags = regs->flags;
/* Set TF on the kernel stack.. */
regs->flags |= X86_EFLAGS_TF;
/*
* ..but if TF is changed by the instruction we will trace,
* don't mark it as being "us" that set it, so that we
* won't clear it by hand later.
*
* Note that if we don't actually execute the popf because
* of a signal arriving right now or suchlike, we will lose
* track of the fact that it really was "us" that set it.
*/
if (is_setting_trap_flag(child, regs)) {
clear_tsk_thread_flag(child, TIF_FORCED_TF);
return 0;
}
/*
* If TF was already set, check whether it was us who set it.
* If not, we should never attempt a block step.
*/
if (oflags & X86_EFLAGS_TF)
return test_tsk_thread_flag(child, TIF_FORCED_TF);
set_tsk_thread_flag(child, TIF_FORCED_TF);
return 1;
}
/*
* Enable single or block step.
*/
static void enable_step(struct task_struct *child, bool block)
{
/*
* Make sure block stepping (BTF) is not enabled unless it should be.
* Note that we don't try to worry about any is_setting_trap_flag()
* instructions after the first when using block stepping.
* So no one should try to use debugger block stepping in a program
* that uses user-mode single stepping itself.
*/
if (enable_single_step(child) && block)
set_task_blockstep(child, true);
else if (test_tsk_thread_flag(child, TIF_BLOCKSTEP))
set_task_blockstep(child, false);
}
void user_enable_single_step(struct task_struct *child)
{
enable_step(child, 0);
}
void user_enable_block_step(struct task_struct *child)
{
enable_step(child, 1);
}
// linux-3.10/kernel/ptrace.c
static int ptrace_resume(struct task_struct *child, long request,
unsigned long data)
{
if (!valid_signal(data))
return -EIO;
if (request == PTRACE_SYSCALL)
set_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
else
clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
......
if (is_singleblock(request)) {
if (unlikely(!arch_has_block_step()))
return -EIO;
user_enable_block_step(child);
} else if (is_singlestep(request) || is_sysemu_singlestep(request)) {
if (unlikely(!arch_has_single_step()))
return -EIO;
user_enable_single_step(child);
} else {
user_disable_single_step(child);
}
child->exit_code = data;
wake_up_state(child, __TASK_TRACED);
return 0;
}
// linux-3.10/include/uapi/linux/ptrace.h
#define PTRACE_CONT 7
#define PTRACE_KILL 8
#define PTRACE_SINGLESTEP 9
#define PTRACE_SYSCALL 24
// linux-3.10/kernel/ptrace.c
int ptrace_request(struct task_struct *child, long request,
unsigned long addr, unsigned long data)
{
......
switch (request) {
// 处理器再执行被跟踪期间,置于单步模式
// tracer(跟踪者进程) 再 tracee(被跟踪者进程) 每执行一条汇编指令后,tracer都可以访问 tracee ,收集 tracee 进程状态的详细信息
#ifdef PTRACE_SINGLESTEP
case PTRACE_SINGLESTEP:
#endif
case PTRACE_SYSCALL:
// 恢复 tracee 的执行
case PTRACE_CONT:
return ptrace_resume(child, request, data);
case PTRACE_KILL:
if (child->exit_state) /* already dead */
return 0;
return ptrace_resume(child, request, SIGKILL);
}
}
总结
该小章节翻译完毕,结束了。。。