当前位置: 首页 > news >正文

建设银行官方网站首页个人登录枣庄公司做网站

建设银行官方网站首页个人登录,枣庄公司做网站,做网盟行业网站的图片广告的销售,微站小程序段选择子#xff1a; 先直观认识一下GDT和段选择子在逻辑地址转换为线性地址中的作用#xff0c;例如#xff1a; 给出逻辑地址#xff1a;21h:12345678h#xff0c;需要将其转换为线性地址 a. 选择子SEL21h0000000000100 0 01b#xff0c;他代表的意思是#xff1a… 段选择子 先直观认识一下GDT和段选择子在逻辑地址转换为线性地址中的作用例如 给出逻辑地址21h:12345678h需要将其转换为线性地址 a. 选择子SEL21h0000000000100 0 01b他代表的意思是选择子的index4即100b选择GDT中的第4个描述符TI0代表选择子是在GDT选择左后的01b代表特权级RPL1因此有SELn3n是索引号 b. OFFSET12345678h若此时GDT第四个描述符中描述的段基址Base为11111111h则线性地址11111111h12345678h23456789h 保护模式笔记九 中断门和IDT(中断描述符表) https://www.52pojie.cn/thread-1455684-1-1.html (出处: 吾爱破解论坛) 前言 所有保护模式索引链接保护模式笔记一 保护模式介绍 前面学习了调用门之后继续学习中断门 中断门 中断门的作用 先前学习的调用门在实际的Windows中并没有被使用只是操作系统提供了调用门描述符给开发人员使用。相比之下Windows使用了中断门用于 系统调用老的CPU通过中断门进入RING内核0层新的CPU使用快速调用 调试常见的INT3 对应硬编码为0xCC 中断门执行流程 根据INT XXX的值 查IDT中断描述符表找到对应的段描述符 这个描述符是一个中断门描述符 在中断门描述符中存储另一个代码段的选择子 选择子指向的段 段.Base 偏移地址 就是真正要执行的地址 IDT IDT全称Interrupt Descriptor Table中断描述符表和GDT相似IDT也是由一系列描述符组成的。 IDT中存储的段描述符都是系统段描述符 IDT中的第一个元素不是NULL不为空 IDT可以包含三种门描述符①任务门描述符②中断门描述符③陷阱门描述符 使用windbg查看IDT的地址和长度 查看地址 复制代码 隐藏代码 r idtr查看长度 复制代码 隐藏代码 r idtl中断门描述符 对比调用门描述符 中断门描述符结构 当一个段描述符是一个调用门描述符时有以下特征 S位为0表示该段描述符为系统段描述符中断门描述符属于系统段描述符 Type域为1110表示该段描述符为32位中断门 低16位到31位存储一个段选择子该段选择子才和代码真正要调用的地址相关 真正要调用的地址 段选择子所指向的段.Base 32位的段中偏移 段中偏移分为两部分高位31-16位和低位15-0位 段.Base默认为0故真正要调用的地址 32位的段中偏移 给出调用门描述符和中断门描述符各部分的对比上半部分为调用门描述符下半部分为中断门描述符 可以发现中断门描述符和调用门描述符的结构基本一致只在Type域和参数计数处不同Type域是描述符的类型标识中断门不允许传参 构造中断门描述符 了解了中断门描述符的结构后尝试自己构造一个无参的中断门描述符如下 得到调用门描述符为0000EE0000080000 段中偏移暂时不明确要调用的代码段先置0 示例代码 接下来给出一段演示代码 复制代码 隐藏代码 #include Windows.h #include stdio.h int value;__declspec(naked) void INTGate(){_asm{pushadpushfd mov value,0x610popfdpopadiretd}}int main(){//使用 中断门_asm{int 0x20}printf(%X\n,value);return 0; }代码说明 代码十分简单主要分为两部分 INTGate中断门真正要调用的函数给全局变量赋值之后中断返回 main通过中断进入中断门最后输出全局变量观察是否通过中断门被修改 将门描述符写入IDT 中断索引和IDT地址的对应关系 在代码中索引的值为0x20其对应的IDT中的地址为8003f500 关于索引值和IDT地址的对应关系为 IDT地址 索引值 × 8 IDT首地址 代入当前的值即为IDT地址 0x20 × 8 0x8003f400 0x100 0x8003f400 0x8003f500 确定门描述符 在写入GDT前还需要确定要写入的值前面已经构造好了的门描述符为0x0000EE0000080000 但其段中偏移还未确定于是使用VC 6.0查看要调用的代码的地址 进入debug模式中断后选中INTGate函数然后右键→Go to Disassembly查看反汇编 可以得到要调用的函数的地址为0x00401020 将得到的要调用的函数地址填入门描述符中对应的offset得到 原0000EE0000080000 现0040EE0000081020 于是得到确定的门描述符为0040EE0000081020 确定中断索引并写入门描述符 确定中断索引其实就是确定要写入中断描述符的地址根据前面中断索引和IDT地址的对应关系不难倒推出 中断索引 要写入中断描述符的地址 - IDT首地址÷ 8 因此问题又转换为了确定要写入的中断描述符地址 流程如下图所示 用到的指令如下 1.查看IDT首地址 复制代码 隐藏代码 r idtr2.使用指令查看IDT内容 复制代码 隐藏代码 dq 8003f400 L30这里的L30代表要查看的长度为 0x30 个qword长度的数据即0x30个段描述符 3.找到要写入的地址后将构造好的中断门描述符写入 复制代码 隐藏代码 eq 8003f500 0040EE0000081020同时在确定了要写入的地址后就可以根据计算出中断索引 中断索引 要写入中断描述符的地址 - IDT首地址÷ 8 (8003f500 - 8003f400) ÷ 8 0x100 ÷ 8 0x20 4.最后再查看写入的地址确保已正确写入 dq 8003f500执行代码 执行结果如下 全局变量能够被修改说明中断门能够正常执行 对比执行前后寄存器和堆栈 执行前寄存器情况 在使用中断门语句处下断点断下后得到 得到此时的寄存器情况 寄存器 说明 值 有关段寄存器的详解可回顾保护模式笔记二 段寄存器 关于标志寄存器的详解可回顾逆向基础笔记五 标志寄存器 这里简单拆解一下标志寄存器 先将值转换为二进制得到 0x202→ 0000 0000 0000 0000 0000 0010 0000 0010 按对应的结构填入得到 此时IF标志位为1表示当前CPU允许响应INTR可屏蔽中断请求 若IF标志位为0则表示CPU不会响应可屏蔽中断请求 执行前堆栈情况 记录下此时的堆栈情况 执行后寄存器情况 为了查看执行后寄存器的情况在INTGate函数中加入了INT 3引发软中断但在中断门调用的代码中再引发软中断会引发错误这里仅作演示观察使用。修改后的INTGate函数如下 __declspec(naked) void INTGate(){_asm{int 3 //中断pushadpushfd mov value,0x610popfdpopadiretd}}之后INT3中断后查看寄存器情况如下 得到此时的寄存器情况 执行后堆栈情况 通过内存窗口观察此时的堆栈情况 得到此时的堆栈情况 对比执行前后寄存器 执行前后寄存器情况如下 主要关注到执行前后标志寄存器的变化 将执行后的EFL按对应的结构拆解得到 对比发现中断门调用后将标志寄存器的IF标志位置为0表明当前正在处理中断请求不再响应其它可屏蔽中断 对比执行前后堆栈 执行前后堆栈情况如下 不难发现中断门执行后向堆栈中压入了5个值SS、ESP、EFL、CS、返回地址 IRETD指令 为了研究IRETD指令干了什么观察IRETD执行前后堆栈和寄存器的变化情况 IRETD执行前 通过内存窗口观察执行前的堆栈情况 得到此时的堆栈情况 再观察此时的寄存器情况 IRETD执行后 通过内存窗口观察执行后的堆栈情况 查看寄存器情况 IRETD执行前后对比 堆栈对比 寄存器对比 IRETD返回的时候比RETF多了一个EFL的恢复关于RETF的内容可回顾保护模式笔记八 调用门提权无参有参 中断门使用RETF返回 了解了IRETD的原理后就可以尝试使用RETF来返回 示例代码 示例代码如下 __declspec(naked) void INTGate(){_asm{pushadpushfd //中断门会修改eflags的IF位为0 所以需要保存标志寄存器mov eax,[esp0x24] //retmov ebx,[esp0x28] //cs//中间少了个esp0x2c 为EFLmov ecx,[esp0x30] //espmov edx,[esp0x34] //ssmov [esp0x244],eaxmov [esp0x284],ebxmov [esp0x2c4],ecxmov [esp0x304],edxmov value,0x610popfdpopadadd esp,4retf}}执行结果 依旧可以正常返回并且执行正常 代码说明 代码也比较简短简单可以分为七个部分 保护现场pushad、pushfd 将堆栈中的数据取出存到寄存器 将取出来的数据覆盖到堆栈中 全局变量赋值 恢复现场popfd、popad 堆栈平衡add esp,4 返回retf 要理解堆栈数据的覆盖和平衡首先要了解IRETD和RETF的区别 IRETD 中断返回需要堆栈中按顺序存储返回地址、CS、EFL、ESP、SS 共5个数据 RETF返回需要堆栈中按顺序存储返回地址、CS、ESP、SS 共4个数据 因此将堆栈中的数据由原本的5个数据替换成4个数据即可 因此通过对堆栈中数据进行覆盖即可实现在中断门中使用RETF返回 总结 中断门执行后会将EFL标志位寄存器中的IF标志位 置0使CPU不再响应可屏蔽中断 执行中断门时分为两种情况 在没有权限切换时只向堆栈中压入3个值①CS②EFL③返回地址 在涉及权限切换时会向堆栈中压入5个值①SS②ESP③EFL④CS⑤返回地址 中断门不允许传递参数调用门允许传递参数 中断门通过INT N索引执行调用门通过远调用 CALL FAR CS:EIP执行 中断门一般使用IRET16位/IRETD32位返回调用门一般使用RETF返回 Windows并没有使用调用门但有使用中断门 【构建操作系统】全局描述符表GDT https://zhuanlan.zhihu.com/p/25867829 写在前面 添油加醋系列第二弹——剖析GDT 头文件https://github.com/bajdcc/MiniOS/blob/master/include/gdt.h 实现https://github.com/bajdcc/MiniOS/blob/master/src/kernel/gdt.c 话说C语言的话除了刷刷OJ外就是用来实现操作系统这个大头了。C语言比C少了很多很多臃肿的语法特性写起来非常优美至少写操作系统是这样的。虽说C有许多的奇技淫巧一个算法有N种实现方法但这会让选择恐惧症患者比如我难堪比如说一个类要怎样写啊等等抛开其他不谈假如一个语言的语法特性越少学起来可能越简单刚试过lua语法很简单。OK废话不多说进入本章主题涉及OS的资料很杂很偏如有错误望海涵。 GDT的构成 这个网址不错英文的Global Descriptor Table 首先根据网上资料GDT全局描述符表又叫段描述符表暂且就这样认为吧如有异议可以提出来。 一个GDT可能是这样的GDT与LDT - Lan’Sir - 博客频道 - CSDN.NET 同样也是这样的Global Descriptor Table 在代码中它又是这样 // 全局描述符表结构 http://www.cnblogs.com/hicjiajia/archive/2012/05/25/2518684.html // base: 基址注意base的byte是分散开的 // limit: 寻址最大范围 tells the maximum addressable unit // flags: 标志位 见上面的AC_AC等 // access: 访问权限 struct gdt_entry {uint16_t limit_low; uint16_t base_low;uint8_t base_middle;uint8_t access;unsigned limit_high: 4;unsigned flags: 4;uint8_t base_high; } __attribute__((packed));这时你的内心OS 答案是——它们都是GDT。。 关于C语言的问题首先可能有些童鞋不知道struct里那些冒号是神马意思。C语言 struct结构体的变量声明加冒号这里叫作“位域”就是占几个二进制位。同时它又涉及内存对齐的概念C语言 结构体的内存对齐问题与位域。涉及__attribute__((packed))的概念attribute 你知多少它是手动设置对齐大小。 众所周知一个字节byte是八个bit那么结构体中有两个4bit的成员不可能用16bit去容纳它们吧~让它们互相挤挤节省空间何乐而不为。 可能看到这里已经花了好多时间了……没办法OS的内容非常多同时GCC的一些怪异偏僻用法又不得不去领会所以只能一步步来慢慢理解急不得。 至于GDT为什么这样描述呢我自创行不行一个字——标准你想改可能你电脑里的硬件设施不答应…… GDT的存在意义 GDT 与 LDT - hicjiajia - 博客园描述得很清楚。 全局描述符表GDTGlobal Descriptor Table在整个系统中全局描述符表GDT只有一张(一个处理器对应一个GDT)GDT可以被放在内存的任何位置但CPU必须知道GDT的入口也就是基地址放在哪里Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址程序员将GDT设定在内存中某个位置之后可以通过LGDT指令将GDT的入口地址装入此寄存器从此以后CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。GDTR中存放的是GDT在内存中的基地址和其表长界限。 也就是说GDT是全局的存放在内存中的某个位置而这个位置是由你来指定给CPU的换句话说你来钦定 设置GDT 现在知道了GDT的struct构成就是一个个数组元素那么我们要给CPU的就是一个gdt_entry数组地址啦~ 那么设置gdt_entry的方法如下 void gdt_install(uint8_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t flags) {/* Setup the descriptor base address */gdt[num].base_low (base 0xffff);gdt[num].base_middle (base 16) 0xff;gdt[num].base_high (base 24) 0xff;/* Setup the descriptor limits */gdt[num].limit_low (limit 0xffff);gdt[num].limit_high ((limit 16) 0x0f);/* Finally, set up the granularity and access flags */gdt[num].flags flags;access | AC_RE; // 设置保留位为1gdt[num].access access; }通过实例认识它 // 宏定义#define AC_AC 0x1 // 可访问 access #define AC_RW 0x2 // [代码]可读[数据]可写 readable for code selector writeable for data selector #define AC_DC 0x4 // 方向位 direction #define AC_EX 0x8 // 可执行 executable, code segment #define AC_RE 0x10 // 保留位 reserve #define AC_PR 0x80 // 有效位 persent in memory// 特权位 01100000b #define AC_DPL_KERN 0x0 // RING 0 kernel level #define AC_DPL_USER 0x60 // RING 3 user level#define GDT_GR 0x8 // 页面粒度 page granularity, limit in 4k blocks #define GDT_SZ 0x4 // 大小位 size bt, 32 bit protect mode// gdt selector 选择子 #define SEL_KCODE 0x1 // 内核代码段 #define SEL_KDATA 0x2 // 内核数据段 #define SEL_UCODE 0x3 // 用户代码段 #define SEL_UDATA 0x4 // 用户数据段 #define SEL_TSS 0x5 // 任务状态段 task state segment http://wiki.osdev.org/TSS// RPL 请求特权等级 request privilege level #define RPL_KERN 0x0 #define RPL_USER 0x3// CPL 当前特权等级 current privilege level #define CPL_KERN 0x0 #define CPL_USER 0x3/* Setup the GDT pointer and limit */ gp.limit (sizeof(struct gdt_entry) * NGDT) - 1; gp.base (uint32_t)gdt;/* null descriptor */ gdt_install(0, 0, 0, 0, 0); /* kernel code segment type: code addr: 0 limit: 4G gran: 4KB sz: 32bit */ gdt_install(SEL_KCODE, 0, 0xfffff, AC_RW|AC_EX|AC_DPL_KERN|AC_PR, GDT_GR|GDT_SZ); /* kernel data segment type: data addr: 0 limit: 4G gran: 4KB sz: bit 32bit */ gdt_install(SEL_KDATA, 0, 0xfffff, AC_RW|AC_DPL_KERN|AC_PR, GDT_GR|GDT_SZ); /* user code segment type: code addr: 0 limit: 4G gran: 4KB sz: 32bit */ gdt_install(SEL_UCODE, 0, 0xfffff, AC_RW|AC_EX|AC_DPL_USER|AC_PR, GDT_GR|GDT_SZ); /* user code segment type: data addr: 0 limit: 4G gran: 4KB sz: 32bit */ gdt_install(SEL_UDATA, 0, 0xfffff, AC_RW|AC_DPL_USER|AC_PR, GDT_GR|GDT_SZ); 我的理解是gdt_install的参数段选择子索引号/见题图基址起始长度访问权限GDT flags。虽然上述例子中基址起始地址和长度都是一样的原项目https://github.com/SilverRainZ/OS677是这样写的可能有点问题但是访问权限中有AC_EX和AC_DPL_KERN(ring0)/AC_DPL_USER(ring3)的变化说明每个段的权限是不同的。这些段管理的是同一片内存只是由于当前索引号的不同访问/修改内存的权限也不同。 GDT 与 LDT - hicjiajia - 博客园讲述了分段管理和分页管理 分段管理可以把虚拟地址转换成线性地址而分页管理可以进一步将线性地址转换成物理地址。 根据段选择子找到段基指 偏移地址 线性地址 线性地址 通过页表 物理地址 通过将GDT告诉给CPU后CPU就知道了操作系统中段的设置从而可以通过段选择子得到线性地址在后面实现分页管理后可进一步将线性地址转换为物理地址不过当前连物理 址有多大都没法知道呢在后面会解决。 段选择子 GDT 与 LDT - hicjiajia - 博客园介绍 段选择子包括三部分描述符索引index、TI指示从GDT还是LDT中找、请求特权级RPL。 index部分表示所需要的段的描述符在描述符表的位置由这个位置再根据在GDTR中存储的描述符表基址就可以找到相应的描述符gdt_entry。然后用描述符gdt_entry中的段基址SEL加上逻辑地址OFFSET就可以转换成线性地址SEL:OFFSET看下面给的例子应该就是它们的和SELOFFSET 段选择子中的TI值只有一位0或10代表选择子是在GDT选择1代表选择子是在LDT选择。 请求特权级RPL则代表选择子的特权级共有4个特权级0级、1级、2级、3级0级最高。关于特权级的说明任务中的每一个段都有一个特定的级别。每当一个程序试图访问某一个段时就将该程序所拥有的特权级与要访问的特权级进行比较以决定能否访问该段。系统约定CPU只能访问同一特权级或级别较低特权级的段。 例如 给出逻辑地址21h:12345678h需要将其转换为线性地址 a. 选择子SEL21h0000000000100 0 01b他代表的意思是选择子的index4即100b选择GDT中的第4个描述符TI0代表选择子是在GDT选择左后的01b代表特权级RPL1因此有SELn3n是索引号 b. OFFSET12345678h若此时GDT第四个描述符中描述的段基址Base为11111111h则线性地址11111111h12345678h23456789h 任务状态段TSS 任务寄存器TR用于寻址一个特殊的任务状态段Task State SegmentTSS。TSS中包含着当前执行任务的重要信息。 TR寄存器用于存放当前任务TSS段的16位段选择符、32位基地址、16位段长度和描述符属性值。它引用GDT表中的一个TSS类型的描述符。指令LTR和STR分别用于加载和保存TR寄存器的段选择符部分。当使用LTR指令把选择符加载进任务寄存器时TSS描述符中的段基地址、段限长度以及描述符属性会被自动加载到任务寄存器中。当执行任务切换时处理器会把新任务的TSS的段选择符和段描述符自动加载进任务寄存器TR中。 它的初始化和设置 void tss_init() {gdt_install(SEL_TSS, (uint32_t)tss, sizeof(tss),AC_PR|AC_AC|AC_EX, GDT_GR); /* for tss, access_reverse bit is 1 */gdt[5].access ~AC_RE; }// 装载TSS void tss_install() {__asm__ volatile(ltr %%ax : : a((SEL_TSS 3))); }// 设置TSS void tss_set(uint16_t ss0, uint32_t esp0) {// 清空TSSmemset((void *)tss, 0, sizeof(tss));tss.ss0 ss0;tss.esp0 esp0;tss.iopb_off sizeof(tss); }跟GDT也差不了多少只是GDT_SZ没有了也指定了tss的地址并设置gdt_entry的保留位为1至于为啥我没有仔细查。至于__asm__ volatile的GCC在C语言中内嵌汇编 asm __volatile__我也没全部搞明白怎么用。SEL_TSS 3的话要参考选择子的构成它高13位是索引所以要乘8。 关于ltr指令设置TSS结构中堆栈信息的 ltr 指令 在任务内发生特权级变换时堆栈也随着自动切换外层堆栈指针保存在内层堆栈中而内层堆栈指针存放在当前任务的TSS中。所以在从外层向内层变换时要访问TSS(从内层向外层转移时不需要访问TSS而只需访问内层栈中保存的栈指针)。 LTR指令是专门用于装载任务状态段寄存器TR的指令。该指令的操作数是对应TSS段描述符的选择子。LTR指令从GDT中取出相应的TSS段描述符把TSS段描述符的基地址和界限等信息装入TR的高速缓冲寄存器中。 TSS的构成在https://github.com/bajdcc/MiniOS/blob/master/include/idt.h中看下面的英文注释/Task State Segment就是说SS0、ESP0比较重要。 // 任务状态段 task state segment http://wiki.osdev.org/TSS // The only interesting fields are SS0 and ESP0. // SS0 gets the kernel datasegment descriptor (e.g. 0x10 if the third entry in your GDT describes your kernel’s data) // ESP0 gets the value the stack-pointer shall get at a system call // IOPB may get the value sizeof(TSS) (which is 104) if you don’t plan to use this io-bitmap further (according to mystran in http://forum.osdev.org/viewtopic.php?t13678) // http://blog.csdn.net/huybin_wang/article/details/2161886 // TSS的使用是为了解决调用门中特权级变换时堆栈发生的变化 // http://www.kancloud.cn/wizardforcel/intel-80386-ref-manual/123838 /* TSS 状态段由两部分组成 1、 动态部分处理器在每次任务切换时会设置这些字段值 通用寄存器EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI 段寄存器ESCSSSDSFSGS 状态寄存器EFLAGS 指令指针EIP 前一个执行的任务的TSS段的选择子只有当要返回时才更新 2、 静态字段处理器读取但从不更改 任务的LDT选择子 页目录基址寄存器PDBR当启用分页时只读 内层堆栈指针特权级0-2 T-位指示了处理器在任务切换时是否引发一个调试异常 I/O 位图基址 */ struct tss_entry {uint32_t link;uint32_t esp0;uint32_t ss0;uint32_t esp1;uint32_t ss1;uint32_t esp2;uint32_t ss2;uint32_t cr3;uint32_t eip;uint32_t eflags;uint32_t eax;uint32_t ecx;uint32_t edx;uint32_t ebx;uint32_t esp;uint32_t ebp;uint32_t esi;uint32_t edi;uint32_t es;uint32_t cs;uint32_t ss;uint32_t ds;uint32_t fs;uint32_t gs;uint32_t ldtr;uint16_t padding1;uint16_t iopb_off; } __attribute__ ((packed));阶段性总结 涉及OS的内容真是庞大单单一个GDT就涉及巨量的知识包括结构体定义、汇编指令、GCC黑魔法、参数的使用等还涉及了TSS目标仅仅是实现分段管理。而后面还有中断管理、物理内存管理、虚拟内存管理等一系列内容篇幅绝对不比本文少真令人望洋兴叹。 原始项目OS67中也存在着一些错误有些错误像是单词拼写等我已经纠正了还有些如软盘访问我去参考了网上的资料与OS67的不一致但我没采用OS67的。毕竟OS67也是其作者自己摸索出来的让我跳过了许多坑。。不过我想后面的进程管理还是得自己写才能体会更深。 既然OS的内容很杂很多所以也只能挑一些重点的讲讲了不可能面面俱到在后面的编写/借鉴中还是要以查资料为主给源码附上参考文章的地址方便阅读。 Linux中断一网打尽(2) - IDT及中断处理的实现 https://zhuanlan.zhihu.com/p/106318141 通过阅读本文您可以了解到 IDT是什么 IDT如何被初始化什么是门传统系统调用是如何实现的硬件中断的实现; 如何设置IDT IDT 中断描述符表定义 中断描述符表简单来说说是定义了发生中断/异常时CPU按这张表中定义的行为来处理对应的中断/异常。 #define IDT_ENTRIES 256 gate_desc idt_table[IDT_ENTRIES] __page_aligned_bss;从上面我们可以知道其包含了256项它是一个gate_desc的数据其下标0-256就表示中断向量gate_desc我们在下面马上介绍。 中断描述符项定义 当中断发生cpu获取到中断向量后查找IDT中断描述符表得到相应的中断描述符再根据中断描述符记录的信息来作权限判断运行级别转换最终调用相应的中断处理程序 这里涉及到Linux kernel的分段式内存管理我们这里不详细展开有兴趣的同学可以自行学习。如下简述之 我们知道CPU只认识逻辑地址逻辑地址经分段处理转换成线性地址线性地址经分页处理最终转换成物理地址这样就可以从内存中读取了 逻辑地址你可以简单认为就是CPU执行代码时从CS(代码段寄存器) IP 指令计数寄存器中加载的代码实际上通过CS可以得到逻辑地址的基地址再加上IP这个相对于基地址的偏移量就得到真正的逻辑地址 CS寄存器16位它不会包含真正的基地址它一般被称为段选择子包括一个index索引指向GDT或 LDT的一项一个指示位指示index索引是属于GDT还是LDT; 还有CPL, 表明当前代码运行权限 GDT: 全局描述符表每一项记录着相应的段基址段大小段的访问权限DPL等到这里终于可以获取到段基地址了再加上之前IP寄存器里存放的偏移量真正的逻辑地址就有了。 附上简图 我们先看中断描述符的定义 struct gate_struct {u16 offset_low;u16 segment;struct idt_bits bits;u16 offset_middle;#ifdef CONFIG_X86_64u32 offset_high;u32 reserved;#endif} __attribute__((packed));其中 offset_high,offset_middle和offset_low合起来就是中断处理函数地址的偏移量 segment就是相应的段选择子根据它在GDT中查找可以最终获取到段基地址 bits是该中断描述符的一些属性值 struct idt_bits {u16 ist : 3,zero : 5,type : 5,dpl : 2,p : 1; } __attribute__((packed));ist表示此中断处理函数是使用pre-cpu的中断栈还是使用IST的中断栈; type表示所中断是何种类型目前有以下四种 enum {GATE_INTERRUPT 0xE, //中断门GATE_TRAP 0xF, // 陷入门GATE_CALL 0xC, // 调用门GATE_TASK 0x5, // 任务门};门的概念这里主要用作权限控制我们从一个区域进到另一个区域需要通过一扇门有门禁权限才可以通过因此 dpl就是这个权限实际中我们一般称为RPL 我们后面会通过一个例子来讲一下CPL,RPL和DPL三者之间的关系。 IDT 中断描述符表本身的存储 IDT 中断描述符表的物理地址存储在IDTR寄存器中这个寄存器存储了IDT的基地址和长度。查询时从 IDTR 拿到 base address 加上向量号 * IDT entry size即可以定位到对应的表项(gate)。 设置IDT 设置中断门类型的IDT描述符 static void set_intr_gate(unsigned int n, const void *addr){struct idt_data data;BUG_ON(n 0xFF);memset(data, 0, sizeof(data));data.vector n; // 中断向量data.addr addr; // 中断处理函数的地址data.segment __KERNEL_CS; // 段选择子data.bits.type GATE_INTERRUPT; // 类型data.bits.p 1;idt_setup_from_table(idt_table, data, 1, false);}上面的函数主要是填充好idt_data然后调用idt_setup_from_table; idt_setup_from_table: static voididt_setup_from_table(gate_desc *idt, const struct idt_data *t, int size, bool sys){gate_desc desc;for (; size 0; t, size--) {idt_init_desc(desc, t);write_idt_entry(idt, t-vector, desc);if (sys)set_bit(t-vector, system_vectors);}}首先使用 idt_data结构来填充中断描述符变量idt_init_desc, 然后将这个中断描述符变量copy进idt_table。 看就是这么简单~~~ gate_desc的多种初始化方法 因为gate_desc是通过ida_dat填充的所以这里关键是idt_data的初始化我们详细看一下 /* Interrupt gate 中断门DPL 0只能从内核调用*/#define INTG(_vector, _addr) \G(_vector, _addr, DEFAULT_STACK, GATE_INTERRUPT, DPL0, __KERNEL_CS)/* System interrupt gate系统中断门DPL 3可以从用户态调用比如系统调用*/#define SYSG(_vector, _addr) \G(_vector, _addr, DEFAULT_STACK, GATE_INTERRUPT, DPL3, __KERNEL_CS)/** Interrupt gate with interrupt stack. The _ist index is the index in* the tss.ist[] array, but for the descriptor it needs to start at 1.中断门, DPL 0只能从内核态调用使用TSS.IST[]作为中断栈 */#define ISTG(_vector, _addr, _ist) \G(_vector, _addr, _ist 1, GATE_INTERRUPT, DPL0, __KERNEL_CS)/* Task gate任务门 DPL 0只能作内核态调用 */#define TSKG(_vector, _gdt) \G(_vector, NULL, DEFAULT_STACK, GATE_TASK, DPL0, _gdt 3)我们再来看下G这个宏的实现 #define G(_vector, _addr, _ist, _type, _dpl, _segment) \{ \.vector _vector, \.bits.ist _ist, \.bits.type _type, \.bits.dpl _dpl, \.bits.p 1, \.addr _addr, \.segment _segment, \}实际上就是填充idt_data的各个字段。 传统系统调用的实现 这里所说的传统系统调用主要指旧的32位系统使用 int 0x80软件中断来进入内核态实现的系统调用。因为这种传统系统调用方式需要进入内核后作权限验证还要切换内核栈后作大量压栈方式调用结束后清理栈作恢复两个字太慢后来CPU从硬件上支持快速系统调用sysenter/sysexit, 再后来又发展到syscall/sysret 这两种都不需要通过中断方式进入内核态而是直接转换到内核态速度快了很多。 传统系统调用相关 IDT 的设置 Linux系统启动过程中内核压解后最终都调用到start_kernel, 在这里会调用trap_init, 然后又会调用idt_setup_traps: void __init idt_setup_traps(void){idt_setup_from_table(idt_table, def_idts, ARRAY_SIZE(def_idts), true);} 我们来看这里的def_idts的定义static const __initconst struct idt_data def_idts[] {....#if defined(CONFIG_IA32_EMULATION)SYSG(IA32_SYSCALL_VECTOR, entry_INT80_compat),#elif defined(CONFIG_X86_32)SYSG(IA32_SYSCALL_VECTOR, entry_INT80_32),#endif};​ 上面的SYSG(IA32_SYSCALL_VECTOR, entry_INT80_32)就是设置系统调用的异常中断处理程序其中 #define IA32_SYSCALL_VECTOR 0x80 再看一下SYSG的定义 #define SYSG(_vector, _addr) \G(_vector, _addr, DEFAULT_STACK, GATE_INTERRUPT, DPL3, __KERNEL_CS)它初始化一个中断门权限是DPL3, 因此从用户态是允许发起系统调用的。 我们调用系统调用不大可能自已手写汇编代码都是通过glibc来调用基本流程是保存参数到寄存器然后保存系统调用向量号到eax寄存器然后调用int 0x80进入内核态切换到内核栈将用户态时的ss/sp/eflags/cs/ip/error code依次压入内核栈。 entry_INT80_32系统调用对应的中断处理程序 assembly ENTRY(entry_INT80_32) ASM_CLAC pushl %eax / pt_regs-orig_ax /SAVE_ALL pt_regs_ax$-ENOSYS switch_stacks1 /* save rest */TRACE_IRQS_OFFmovl %esp, %eaxcall do_int80_syscall_32 .Lsyscall_32_done: ... .Lirq_return:INTERRUPT_RETURN ... ENDPROC(entry_INT80_32) 我们略去了中间的一些细节部分可以看到首先将中断向量号压栈再保存所有当前的寄存器值到pt_regs, 保存当前栈指针到%eax寄存器最后再调用 do_int80_syscall_32, 这个函数中就会执行具体的中断处理然后INTERRUPT_RETURN恢复栈作好返回用户态的准备。 do_int80_syscall_32调用 do_syscall_32_irqs_on,我们看一下其实现 static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs){struct thread_info *ti current_thread_info();unsigned int nr (unsigned int)regs-orig_ax;#ifdef CONFIG_IA32_EMULATIONti-status | TS_COMPAT;#endifif (READ_ONCE(ti-flags) _TIF_WORK_SYSCALL_ENTRY) {nr syscall_trace_enter(regs);}if (likely(nr IA32_NR_syscalls)) {nr array_index_nospec(nr, IA32_NR_syscalls);#ifdef CONFIG_IA32_EMULATIONregs-ax ia32_sys_call_table[nr](regs);#elseregs-ax ia32_sys_call_table[nr]((unsigned int)regs-bx, (unsigned int)regs-cx,(unsigned int)regs-dx, (unsigned int)regs-si,(unsigned int)regs-di, (unsigned int)regs-bp);#endif /* CONFIG_IA32_EMULATION */}syscall_return_slowpath(regs);}通过中断向量号nr从ia32_sys_call_table中断向量表中索引到具体的中断处理函数然后调用之其结果最终合存入%eax寄存器。 一图以蔽之 硬件中断的实现 硬件中断的IDT初始化和调用流程 这里我们不讲解具体的代码细节只关注流程 。 硬件中断相关IDT的初始化也是在Linux启动时完成在start_kernel中通过调用init_IRQ完成我们来看一下 void __init init_IRQ(void) { int i; for (i 0; i nr_legacy_irqs(); i) per_cpu(vector_irq, 0)[ISA_IRQ_VECTOR(i)] irq_to_desc(i); BUG_ON(irq_init_percpu_irqstack(smp_processor_id()));x86_init.irqs.intr_init(); // 即调用 native_init_IRQ} void __init native_init_IRQ(void) {/* Execute any quirks before the call gates are initialised: */x86_init.irqs.pre_vector_init();idt_setup_apic_and_irq_gates();lapic_assign_system_vectors();if (!acpi_ioapic !of_ioapic nr_legacy_irqs())setup_irq(2, irq2); }重点在于idt_setup_apic_and_irq_gates:c */ void __init idt_setup_apic_and_irq_gates(void) {int i FIRST_EXTERNAL_VECTOR;void *entry;idt_setup_from_table(idt_table, apic_idts, ARRAY_SIZE(apic_idts), true);for_each_clear_bit_from(i, system_vectors, FIRST_SYSTEM_VECTOR) {entry irq_entries_start 8 * (i - FIRST_EXTERNAL_VECTOR);set_intr_gate(i, entry);} }其中的set_intr_gate用来初始化硬件相关的调用门其对应的中断门处理函数在irq_entries_start中定义它位于arch/x86/entry/entry_64.S中 .align 8 ENTRY(irq_entries_start)vectorFIRST_EXTERNAL_VECTOR.rept (FIRST_SYSTEM_VECTOR - FIRST_EXTERNAL_VECTOR)UNWIND_HINT_IRET_REGSpushq $(~vector0x80) /* Note: always in signed byte range */jmp common_interrupt.align 8vectorvector1.endr END(irq_entries_start)这段汇编实现对不大熟悉汇编的同学可能看起来有点晕其实很简单它相当于填充一个中断处理函数的数组填充多少次呢? (FIRST_SYSTEM_VECTOR - FIRST_EXTERNAL_VECTOR)这就是次数数组的每一项都是一个函数 UNWIND_HINT_IRET_REGSpushq $(~vector0x80) /* Note: always in signed byte range */jmp common_interrupt即先将中断号压栈然后跳转到common_interrupt执行可以看到这个common_interrupt是硬件中断的通用处理函数它里面最主要的就是调用do_IRQ: __visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs) {struct pt_regs *old_regs set_irq_regs(regs);struct irq_desc * desc;/* high bit used in ret_from_ code */unsigned vector ~regs-orig_ax;entering_irq();/* entering_irq() tells RCU that were not quiescent. Check it. */RCU_LOCKDEP_WARN(!rcu_is_watching(), IRQ failed to wake up RCU);desc __this_cpu_read(vector_irq[vector]);if (likely(!IS_ERR_OR_NULL(desc))) {if (IS_ENABLED(CONFIG_X86_32))handle_irq(desc, regs);elsegeneric_handle_irq_desc(desc);} else {ack_APIC_irq();if (desc VECTOR_UNUSED) {pr_emerg_ratelimited(%s: %d.%d No irq handler for vector\n,__func__, smp_processor_id(),vector);} else {__this_cpu_write(vector_irq[vector], VECTOR_UNUSED);}}exiting_irq();set_irq_regs(old_regs);return 1; }首先根据中断向量号获取到对应的中断描述符irq_desc, 然后调用generic_handle_irq来处理 static inline void generic_handle_irq_desc(struct irq_desc *desc) {desc-handle_irq(desc); }这里最终会调用到中断描述符的handle_irq因此另一个重点就是这个中断描述符的设置了它可以单开一篇文章来讲我们暂不详述了。
http://www.zqtcl.cn/news/199605/

相关文章:

  • 青岛企业网站制作seo排名优化培训网站
  • 2018做网站还是app上海搜索seo
  • 网站建设用模板好吗罗湖网站制作费用
  • 网站图片延时加载app推广视频
  • 郑州设计师网站个人搭建网站要多少钱
  • 网站制作成品下载wordpress怎么更改样式
  • 河北省城乡和建设厅网站首页网站维护属于什么部门
  • 西安建网站公司哪家好网站导航条设计欣赏
  • 张家港网站网络优化济南网站建设0531soso
  • 关于网站的建设深圳搜索优化排名
  • 网站建设的布局建设通破解vip
  • 怎样做公司网站介绍网站百度排名优化
  • 广州网站建设工作室招聘wordpress在哪里设置编辑器
  • 苏州网站建设功能大宗交易平台软件
  • 无域名网站 能否被百度品牌营销优化
  • 做爰全过程免费的网站视频做网站视频背景
  • 网站布局设计分析特点手机网站设计欣赏网站
  • 建设网站对服务器有什么要求灌南县规划局网站一品嘉苑规划建设
  • 常平镇仿做网站wordpress教程 菜单
  • 大气的企业网站做网站服务怎么赚钱
  • 如何用网站做淘宝客网易企业邮箱怎么修改密码
  • 白酒网站设计wordpress增加网址大全
  • 网站上图片可以做商业作品吗成都十大景观设计公司
  • 自助建站网站哪个好2017织梦网站怎么做seo
  • 佛山新网站建设咨询做业精灵官方网站
  • 大庆网站设计费用asp网站仿制
  • 革吉网站建设网页游戏中心大全
  • 好的网站特点京东物流网站建设特点
  • 昆明企业自助建站系统网站建设技术交流
  • 卖网站模板网站哪家做的比较好