答自己问 pdf mobi txt 2024 电子版 下载
答自己问电子书下载地址
内容简介:
暂无相关简介,正在全力查找中!
书籍目录:
暂无相关目录,正在全力查找中!
作者介绍:
史铁生,1951年生于北京, 初中毕业后去延安插队,1972年双腿瘫痪转回北京在某街道工厂做工7年,后因病情加重回家休养。现为中国作协会员, 北京作协理事,合同制作家。主要作品有:《午餐半小时》、《我们的角落》、《在一个冬天的晚上》、《我的遥遥的清平湾》、《奶奶的星星》、《关于詹牧师的报告文学》、《插队的故事》、《我与地坛》等。其作品多次在全国获奖, 并有英、法、日本译本在国外出版。
出版社信息:
暂无出版社相关信息,正在全力查找中!
书籍摘录:
暂无相关书籍摘录,正在全力查找中!
原文赏析:
暂无原文赏析,正在全力查找中!
其它内容:
书籍介绍
精彩短评:
作者:木须龙 发布时间:2010-09-26 08:53:46
我当年读这本书时才了解什么叫逆境中的坚强,后来我的博客就以此命名为“答自己问”。
作者:- 发布时间:2012-12-25 13:54:38
史铁生脑子里想的那些傻不愣玩意儿好像我
作者:鱼深深深深深深 发布时间:2008-07-23 13:54:37
文字的力量。
作者:Molly 发布时间:2012-04-11 11:43:24
迟卉的风格应该说是我很喜欢的 细腻 感性 但似乎也缺少驾驭大场景的能力 虽然SFW的短篇都很好 但卡勒米安墓场里就比较明显了 这也许正是女作者的特点 ——总之 Hold your head high!
作者:xiaohanyu 发布时间:2011-01-01 12:06:41
我希望既有一个健美的躯体又有一个了悟了人生意义的灵魂,我希望二者兼得。但是, 前者可以祈望上帝的恩赐,后者去必须在千难万苦中靠自己去获取。
作者:罅隙 发布时间:2013-03-30 16:20:32
人生小哲学。要谈人生也得有资本吧。
深度书评:
把最重要的人生命题都自己搞明白
作者:都一样 发布时间:2022-05-22 23:43:20
史铁生《答自己问》中提到最多的一个词是“上帝”。到处可见。到处可见,从今天阅读的结尾部分信手拈来一则。
第214页:
上帝从来不对任何人施舍“最幸福”这三个字,他在所有人的欲望前面设下永恒的距离,公平地给每一个人以局限。
第225页:
上帝是一个聪明的幼儿园阿姨,让一代一代的孩子们玩同一个游戏,绝不让同一个孩子把这游戏永远玩下去,她懂得艺术的魅力在于新奇感。谢谢她为我们想得周到。这个游戏取名“人生”……
一个被命运彻底打击到底,一个已经顺从命运安排的人,无疑会信仰一个神秘的存在的,史铁生选择了上帝。在《神位 官位 心位》中,他对念佛之人世俗化、交易性的现象表示厌恶,同时他的思想是那么的西化,所以他念兹在兹的都是上帝,也很自然。
从还在居委工作时开始,我花了颇多时间在这本仅仅14万的小书上。它是一本枯燥的书吗?非也。它没有营养吗?非也。它字词粗糙吗?非也,相反,能有这样文字水平的人,当代作家中真不多。
之所以看得这么慢,一方面与我阅读速度慢(轻度的阅读障碍症),另一方面还是与它蕴含的内容过于沉重和真实,我要慢慢消化才行。
这本书无疑将纳入保留纸质书籍中,相伴始终。
Notes
作者:伊卡洛斯 发布时间:2011-05-17 16:26:07
英文名:Software Debugging 作者:张银奎
第1编 绪论
第1章 软件调试基础
一个完整的软件调试过程:
1)重现故障
2)定位根源(最困难也是最关键的步骤)
3)探索和实现解决方案
4)验证方案(又称回归测试)
导致软件调试复杂性的几个因素:
1)定位软件是一个很复杂的搜索问题,必须通过大量分析才能逐步接近真正的内在原因
2)很多时候必须深入到被调试模块或系统底层,研究内部的数据和代码
3)调试着必须有丰富的知识,熟悉问题域内的各个软硬件模块,以及他们的协作模式
4)每个软件调试任务都有特殊性,即很难找到两个调试任务是一样的
5)软件的大型化、层次的增多、多核和多处理器系统的普及都在增加软件调试的难度
第2编 CPU的调试支持
第2章 CPU的调试支持
很多软件工程师的一个弱点是对硬件了解的太少,甚至不愿意去学习硬件知识。事实上,了解必要的硬件知识对理解软件经常会有事半功倍的效果,扎实的硬件基础对于软件工程师来说也是非常重要的
第3章 中断和异常
中断:由CPU外部的输入输出设备(硬件)所触发的,供外部设备通知CPU“有事情要处理”,目的是使CPU暂时停止执行当前正在执行的程序,转去执行中断请求所对应的中断处理例程(ISR)
异常:CPU在执行指令时因为检测到预先定义的某个(或多个)条件而产生的同步事件,来源主要是三种;程序错误、特殊指令(如INT 3)和机器检查异常
异常来源于CPU本身,是CPU主动产生的;而中断来自于外部设备,是中断源发起的,CPU是被动的。
异常的分类:
1)错误(Fault)(开始执行导致异常的指令时,保存导致异常的指令)
2)陷阱(Trap)(执行完导致异常的指令时,保存导致异常的指令的下一条指令)
3)终止(Abort)(发生状态不确定,同时不可以恢复执行)
实模式下IA-32 CPU响应中断和异常的全过程:
1)将代码段寄存器CS和指令寄存器EIP的低16位压入堆栈
2)将标志寄存器EFLAGS的低16位压入堆栈
3)清除标志寄存器的IF标志,以禁止其他中断
4)清除标志寄存器的TF(Trap Flag)、RF(Resume Flag)、AC(Alignment Check)
5)使用向量号n作为索引,在IVT中找到对应的表项(n*4+IVT表基地址)
6)将表项中的段地址和偏移地址分别装入CS和EIP寄存器中,并开始执行对应的代码
7)中断例程总是以IRET指令结束。IRET指令会从对战中弹出前面保存的CS、EIP和标志寄存器值,然后返回执行被中断的程序
第4章 断点和单步执行
软件断点:
当CPU执行INT 3指令时,它会跳转到异常处理例程,让当前的程序接受调试,调试结束后,异常处理例程使用中断返回机制让CPU再继续执行原来的程序。
在Windows系统中,操作系统的断点异常处理函数(KiTrap03)对于x86 CPU的断点异常会有一个特殊处理,会将EIP减1。出于这个原因,我们在调试器看到的程序指针指向的依然是INT 3指令的位置,而不是它的下一条指令。主要是出于以下两个目的:
1)调试器在落实断点时,不管所在位置的指令是几个字节,它都只替换一个字节。因此,如果程序指针指向下一个指令位置,那么只想的可能是原来的多字节指令的第二个字节,不是一条完整的指令。
2)因为有断点在,所以被调试程序在断点位置的那条指令还没有执行。按照程序指针总是指向即将执行的那条指令的原则,应该把程序指针指向这条要执行的指令,也就是倒退回一个字节,指向本来指令的起始地址。
软件断点的局限性:
1)属于代码类断点,即可以让CPU执行到代码段内的某个地址时停下来,不适用于数据段和I/O空间
2)对于在ROM(如BIOS或其他固件程序)中执行的程序,由于目标是只读,无法动态写入断点指令
3)在中断向量表或中断描述表(IDT)没有准备好或遭到破坏的情况下,这类断点是无法或不能正常工作的,只能使用硬件级调试工具
硬件断点:
在保护模式下,我们不能使用调试寄存器来针对一个物理内存地址设置断点
三种调试断点的情况
1)读/写内存中的数据时中断
2)执行内存中的代码时中断
3)读写I/O端口时中断
高级语言的单步执行在大多数调试器下使用TF标志一步步地走过每条汇编指令,如果一条语句对应N条汇编指令,那就是产生N次调试异常,中间的N-1次都是简单地重新设置起TF标志,便恢复被调试程序执行,不中断给用户
第5章 分支记录和性能监视
使用寄存器的分支记录
LBR栈是一个环形堆栈,由数个用来记录分支地址的MSR寄存器(称为LBR MSR)和一个表示栈顶(Top Of Stack)指针的MSR寄存器(称为MSR_LASTBRANCH_TOS)构成。CPU在把新的分支记录放入这个堆栈前会先把TOS加1,当TOS达到最大值时,会自动归0
使用内存的分支记录
分支踪迹存储BTS(Branch Trace Store)机制,允许把分支记录保存在一个特定的被称为BTS的缓冲区的内存内。BTS缓冲区与用于记录性能监控信息的PEBS(Precise Event-Based Sampling,即精确的基于时间采样)缓冲区是使用类似的机制来管理的,这种机制被称为调试存储区(Debug Store),简称为DS存储区
DS存储区由管理信息区、BTS缓冲区、PEBS缓冲区组成,具有以下特点
1)全部在非分页内存中
2)包含DS缓冲区的内存页必须被映射到相同的物理地址
3)不与代码位于统一内存页面中,以防止CPU写分支记录时会触发防止保护代码页的动作
4)DS位于活动状态时,要防止进入A20M模式
5)DS应该仅用在启用了APIC的系统中
使用硬件和系统提供的性能监视工具,比使用调用系统时间计算差的方法要准确很多,对后续工作如性能监视和软件调优都有很重要的意义
第6章 机器检查架构(MCA)
IA-32处理器的机器检查(Machine Check)机制基本原理:CPU先收集好要记录的信息,并把它们存储到特定的寄存器或内存区域中,然后通过产生异常的方式把控制权交给软件,接下来,软件将这些信息懈怠外部存储器(如硬盘)上永久记录下来。
EDX寄存器的MCA(位14)和MCE(位7)分别表示处理器是否实现了机器检查架构和机器检查异常
Windows NT 6以后的系统设计了更完善的体制,称为WHEA(Windows Hardware Error Architecture)
第7章 JTAG调试
JTAG调试,即基于JTAG(Joint Test Action Group)技术的硬件调试工具。硬件调试工具的最大优点就是不需要在目标系统上运行任何软件,可以再目标系统还不具备基本的软件环境时进行调试,因此,JTAG调试非常适合调试BIOS、操作系统的加载程序,以及使用软件调试器南一条是的特殊软件。
JTAG的核心思想就是将测试点和测试设施集成在芯片内部(build test facilities/test points into chip),并通过一组标准的信号(借口)向外输出测试结果,这些标准信号被称为TAP(Test Access Port)信号。有了标准的TAP信号,那么基于JTAG技术的调试工具就可以与这个芯片通信,而不必关心它内部的实现细节。这样,一个JTAG调试工具就可以比较容易地调试很多种芯片。芯片内部通常需要实现一个边界扫描链路和一个TAP控制器。
第2编 操作系统的调试支持
第8章 Windows概要
每个WIndows进程除了虚拟地址空间,还有以下资源:
1)一个全局的文艺的进程ID(Client ID),简称为PID
2)一个可执行映像(image),也就是该进程的程序文件(可执行文件)在内存中的表示
3)一个或多个线程
4)一个位于内核空间中的名为EPROCESS(executive process block,即进程执行块)的数据架构,用以记录该进程的关键信息,包括进程的创建时间、映像文件名称等
5)一个位于内核空间中的对象句柄表,用以记录和索引该进程所创建/打开的内核对象。操作系统根据该表格将用户模式下的句柄翻译为志向内核对象的指针。
6)一个用于描述内存目录表起始位置的基地址,简称页目录基地址(DirBase),当CPU切换到该进程/任务时,会将该地址加载到CR3寄存器,这样当前进程的虚拟地址才会被翻译为正确的物理地址
7)一个位于用户空间中的进程环境块(Process Environment Block,简称PEB)
8)一个访问权限令牌(access token),用于表示该进程的用户、安全组,以及优先级别
第9章 用户态调试模型
Windows下进行用户态调试时,参与的角色有调试器进程(Debugger Process)、被调试进程(Debuggee Process)、调试子系统,调试API,以及位于NTDLL和内核中的支持函数
Debugger Process是调试过程的主导者,它负责发起调试对话,读取和处理调试事件,并通过用户界面接受调试人员下达的指令,然后执行。调试器进程通过调试API与系统的调试支持函数和调试子系统交互。
Debuggee Process是调试的目标。为降低海森伯效应,应尽可能少地向被调试进程中加入支持调试的设施,以免影响问题的重现和分析。
调试子系统是沟通被Debugger Process和Debuggee Process的桥梁,它的指责是帮助调试器完成各种调试功能,比如控制和访问被调试进程,管理和分发调试事件,接收和处理调试器的服务请求。
XP以后的系统是以内核对象DebugObject为核心,结构如下:
typedef struct _DEBUG_OBJECT
{
KEVENT EventsPresent; //+0x00 用于指示有调试事件发生的事件对象(WaitForDebugEvent所等待的对象)
FAST_MUTEX Mutex; //+0x10 用于同步的互斥对象(用于锁定对这个数据结构的访问)
LIST_ENTRY StateEventListEntry; //+0x30 保存调试时间的链表(调试消息队列)
ULONG Flags; //+0x30 标志
} DEBUG_OBJECT, *PDEBUG_OBJECT;
建立程序调试对话的两周情况:
1)在调试器中启动被调试程序。系统在创建进程时,会把调试器线程的TEB结构的DbgSsReserved[1]字段中保存的调试对象句柄传递给创建进程的内核服务,然后内核中的进程穿件函数会将这个句柄所对应的对象指针赋给新创建进程的EPROCESS结构的DebugPort字段
2)把调试器附加到一个已经运行的进程中。系统调用内核中的DbgkpSetProcessDebugObject函数来将一个创建好的调试对象附加到其参数所指定的被调试进程中。除此之外还会调用DbgkpMarkProcessPeb函数设置进程环境块(PEB)的BeingDebugged字段。
第10章 用户态调试过程
调试器的主要功能分成如下两个方面:
1)人机接口(UI线程):以某种界面的形式将调试功能呈现给用户,并坚挺和接受用户的输入(命令),在收到用户收入后,进行解析和执行,然后把执行结果显示给用户。
2)与被调试进程交互(Debugger's Worker Thread线程,简称DWT):包括与被调试进程建立调试关系,然后坚挺和处理调试事件,根据需要将被调试进程中断到调试器,读取和修改被调试进程的数据,或者操纵它的其他行为
处于被调试状态的Windows进程与普通进程相比,有如下差异:
1)EPB的DebugPort字段不为空(志向DebugObject对象)
2)PEB的BeingDebugged字段不等于0
3)可能会存在一个由调试器远程启动的线程,这个线程的作用是将被调试进程中断到调试器,称之为RemoteBreakin线程
4)响应调试热键(F12),按调试热键可以将处于被调试状态的进程中断到调试器
系统在创建进程时,会检查创建报纸是否包含DEBUG_PROCESS或DEBUG_ONLY_THIS_PROCESS标志,如果包含,那么系统会把调用进程当做Debugger进程,吧新创建的进程当做Debuggee,为二者建立起调试关系,主要执行以下3个动作。
1)在进程创建的早期(执行内核服务NtCreatProcess/NtCreatProcessEx)之前,调用DbgUiConnectToDbg()使调用线程与调试子系统建立连接。在Windows XP以后,DbgUiConnectToDbg()内部会调用ZwCreateDebugEvent创建DEBUG_OBJECT内核对象,并将其保存在TEB的DbgSsReserved[1]字段中。
完成这一步以后,调用线程便由普通线程晋级为DWT了。
2)当调用进程创建内核服务NtCreatProcess或NtCreatProcessEx时,将DbgSsReserved[1]字段中记录的对象句柄以参数(第7个参数)形式传递给内核中的进程管理器。接下来,内核中的进程创建函数(PspCreateProcess)会检查这个句柄是否为空,如果不为空,会取得它的对象指针,然后设置到EPROCESS的DebugPort字段中。
完成这一步以后,新创建进程便由普通进程晋升为调试自启动里的Debuggee了
3)当PspCreateProcess调用MmCreatePeb函数创建新进程的PEB时,MmCreatPeb函数内部会根据EPROCESS结构的DebugPort字段设置BeingDebugged字段。如果DebugPort不为空,那么BeingDebugged会被设置为1(为真)。
当新进程的初始线程在自己上下文中初始化时,作为进程初始化的一个步骤,NTDLL.DLL中的LdrpInitializeProcess函数会见车正在初始化的进程是否处于被调试状态(查询进程环境块的BeingDebugged字段),如果是,它会调用DbgBreakPoint()触发一个断点异常,目的是中断到调试器。这相当于系统在新进程中为我们设置了一个断点,这个断点通常被称为初始断点。
另外一种Attach到已经运行的进程的调试方法,主要是通过DebugActiveProcess()API来完成的,其内部工作过程如下:
1)通过DbgUiConnectToDbg()使调用进程与调试子系统建立连接,实质上就是活的一个调试通信对象并存放在当前线程TEB结构的DbgSsReserved数组中。
这与调试一个新进程的第一步相同。
2)调用ProcessIdToHandle函数,获得指定进程ID的进程句柄,这个函数内部会调用OpenProcess()API,进而调用NtOpenProcess内核服务。
这一步需要调用进程与目标进程有同样或更高的权限,否则会出现“Access is denied”错误
3)调用NTDLL中的DbgUiDebugActiveProcess。这个函数内部主要调用NtDebugActiveProcess内核服务,并将要调试进程的句柄(参数1)和调试对象的句柄(参数2)作为参数传递给这个内核服务。
NtDebugActiveProcess内部主要执行以下三个动作:
1、根据参数中指定的句柄去的被调试进程的EPROCESS结构和调试对象的指针
2、向调试对象发送杜撰的调试事件
3、调用DbgkpSetProcessDebugObject函数,这个函数内部会将调试对象设置到被调试进程的调试端口(DebugPort字段),并调用DbgkpMarkProcessPeb来设置BeingDebugged字段
以上操作都成功后DebugActiveProcess()会返回真,通知调用进程已经成功建立调试对话。接下来调试器便进入调试事件循环开始接收和处理调试事件了。它首先会受到一系列杜撰的调试事件,包括进程创建、模块加载等。最后受到远程中断线程产生的断电事件,接收器受到这一事件后,通常会停下来报告给用户。
中断到调试器的几种方法:
1)初始断点DbgBreakPoint
2)编程时加入断点DebugBreak(x86下,等价于INT 3)
3)通过调试器设置断点(动态插入)
4)通过远程线程触发异常DebugBreakProcess
5)在线程当前执行位置设置断点
6)动态调用远程函数
7)挂起中断
8)调试热键(F12)SrvActiveDebugger
9)窗口更新
Windows下,使用OutputDebugString()API来输出调试字符串。这个函数利用RaiseException()API产生一个调试打印异常(DBG_PRINTEXCEPTION_C),RaiseException()被调用以后,会产生一个标准的异常结构EXCEPTION_RECORD,然后调用内核中的异常分发函数KiDispatchException,此函数会调用支持用户态调试的内核例程DbgForwardException向调试子系统同胞异常。
调试器的DWT通过WaitForDebugEvent调用NTDLL中的DbgUiWaitStateChange来等待调试事件。在接收到异常事件后,如果异常代码等于DBG_RPINTEXCEPTION_C,那么它们都会将事件代码字段(dwDebugEventCode)设置为OUTPUT_DEBUG_STRING_EVENT,而不是EXCEPTION_DEBUG_EVENT,并将异常参数中的调试信息填写到DEBUG_EVENT结构的DebugString子结构中。
调试器收到OUTPUT_DEBUG_STRING_EVENT事件后,会从其参数中得到调试信息字符串的地址和长度,然后使用内存访问函数从被调试进程的空间中读取调试信息字符串,然后显示出来。显示后,调试器默认会立即调用ContinueDebugEvent回复此事件,表明自己处理了该异常,于是KiDispathException函数结束异常分发,被调试程序便继续正常运行了。
终止调试回话的4种方式
1)被调试进程退出:PspExitThread来退出线程,PspProcessDelete做最后的进程清理和删除工作
2)调试器进程退出:执行以下7个步骤
1、[WinDBG]调用系统服务NtTerminateProcess,开始终止WinDBG进程
2、[WinDBG]执行PspExitThread函数,WinDBG的DWT退出
3、[WinDBG]执行PspExitThread函数,WinDBG的UI线程退出
4、[WinDBG]执行ObKillProcess函数,开始清理WinDBG进程的句柄表
5、[WinDBG]执行DbgkpCloseObject函数,关闭调试对象,将被调试进程的DebugPort设为空,并调用DbgkpMarkProcessPeb设置PEB的BeingDebugged字段,并引发终止Debuggee
6、[Debuggee]执行PspExitThread,Debuggee退出
7、执行PspProcessDelete和MmDeleteProcessAddressSpace函数,删除Debuggee和Debugger的内核对象和进程空间
每一步骤前的方括号内是函数执行的进程上下文,最后一步可能是在系统服务进程(svchost.exe)环境内执行的,也可能是在系统进程内执行的。
3)分离(detach)被调试进程,使用DebugActiveProcessStop,内部是调用NTDLL中的DbgUiStopDebugging函数,此函数调用内核服务NtRemoveProcessDebug。NtRemoveProcessDebug内部调用DbgkClearProcessDebugObject。后者就是去除Debugger的DebugPort字段对调试对象的引用,并将其设置为NULL。将DebugPort设为空后,DbgkClearProcessDebugObject会便利调试对象的调试事件队列,并删除有关这个Debuggee的事件。
4)退出时分离:在调用终止进程的函数前,DbgkpCloseObject会检查调试对象的Flags是否包含KillOnExit标志,如果清除了这个标志,那么便不会退出Debuggee。使用DebugSetProcessKillOnExit来设置这个标志。
第11章 中断和异常管理
在保护模式下,当有中断或一场发生时,CPU是通过中断描述表(Interrupt Descriptor Table,简称IDT)来寻找处理函数。IDT表是一张位于物理内存的线性表,共有256个表项,在32位模式下,每个IDT表项长度是8个字节。其位置和长度是由CPU的IDTR寄存器来描述的。IDTR共有48位,高32位是IDT表的基地址,低16位是IDT表的长度(limit)。
IDT表的每一个表项是一个所谓的门描述符(Gate Descriptor)结构。一共有三种门描述符:任务门、中断门和陷阱门。其高字节的6~12位来区分描述符的种类
大多数中断和异常都是利用中断门或陷阱门来处理的,大致流程如些:
首先,CPU会根据门描述符中的段选择子定位到段描述符,然后惊醒一系列检查,如果检查通过后,CPU就判断是否需要切换栈。如果目标代码段的特权级别比当前特权级别高(级别数值小),那么CPU需要切换栈,其方法是从当前任务的任务状态段(TSS)中读取新堆栈的段选择子(SS)和堆栈指针(ESP),并将其加载到SS和ESP寄存器。然后,CPU会把被中断过程(旧的)的堆栈段选择子(SS)和堆栈指针(ESP)压入堆栈。接下来,CPU执行以下两项操作:
1)把EFLAGS、CS和EIP的指针压入堆栈。CS和EIP指针代表了转到处理例程钱CPU正在执行代码的位置
2)如果发生一场,而且该异常有错误代码,那么把该错误代码也压入堆栈
在Windows中TSS个数只与CPU个数有关。在启动期间,Windows会为每个CPU创建3~4个TSS,一共用于处理NMI,一个用于处理#DF一场,一个处理机器检查异常,另一个供所有Windows线程共享。当Windows切换线程时,它把当前线程的状态复制到共享的TSS中。所以,普通的线程切换并不会切换TSS,只有当NMI或#DF异常发生时才会切换TSS。这也就是所谓的以软件方式切换线程(任务)
Windows使用EXCEPTION_RECORD结构来描述异常,其结构如下:
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode; //异常代码
DWORD ExceptionFlags; //异常标志
struct _EXCEPTION_RECORD* ExceptionRecord; //相关的另一个异常
PVOID ExceptionAddress; //异常发生地址
DWORD NumberParameters; //参数数组中的元素个数
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; //参数数组
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;
登记CPU异常:CPU会通过IDT表寻找异常处理函数入口KiTrapXX,该例程在完成针对本异常的特别动作后,通常会调用CommonDispathException函数,并通过寄存器将如下信息传递给这个函数
1)将唯一标识该异常的一个异常代码放入EAX
2)将导致一场的指令地址放入EBX
3)将其他信息作为附带参数(最多3个)分别放入EDX(参数1),ESI(参数2)和EDI(参数3)寄存器,并将参数个数放入ECX
CommonDispathException被调用后,它会在栈中分配一个EXCEPTION_RECORD架构,并把以上异常信息存储到该结构中。
登记软件异常:软件中最后都是通过调用KiRaiseException函数来完成,此函数内部会通过KeContextToKframes例程把ContextRecord结构中的信息复制到当前线程的内核栈,然后把ExceptionRecord中的异常代码的最高位清0,一边把软件产生的异常与CPU异常区分开。
对于Visual C++程序抛出的异常,ExceptionCode参数固定为0xe06d7363(对应ASCII码为.msc)
对于.NET程序抛出的异常(CLR)异常,ExceptionCode参数固定为0xe0434f4d(对应ASCII码为.COM)
无论是来自用户态的异常,还是内核态的异常,(如果需要分发)系统都会使用KiDispathException函数来分发异常。对于每个异常系统最多给它两轮被处理机会,对于每轮机会,KiDispathException有都是尝试最先让调试器来处理,如果调试器没有处理,那么KiDispathException会寻找代码中的异常处理块来处理该异常。对于来自内核态的异常,KiDispathException会直接调用RtlDispathException来寻找异常处理块;对于用户态的异常,KiDispathException会通过设置TrapFrame让KiUserExceptionDispatcher来寻找用户空间中的异常处理块。
VEH的基本思想是注册VectoredHandler函数来接受和处理异常,只能用于用户态程序中。使用AddVectoredExceptionHandler和RemoveVectoredExceptionHandler来分别注册和注销回调函数
关于VEH和SEH的区别和联系:
1)从应用范围来说,SEH可用于Ring3和Ring0,但是VEH只能用于Ring3,同时只能在NT5.0以上才能使用
2)从优先级角度看,VEH比SEH先得到处理权
3)从登记方式来看,SEH的注册信息是以固定结构存储在线程栈,不同层次的各个SEH的注册信息一次被压入到栈中,分布在不同的位置上,依靠结构体内指针相联系,因为人们经常将一个函数所对应的栈区称之为栈帧(Stack Frame),所以SEH的异常处理器又被称为基于帧的异常处理去(frame-based exception handler);
VEH的注册信息是存储在进程的内存堆中
4)从作用域的角度来看,VEH处理器相对于整个进程都有效,具有全局性;SEH处理器是动态建立在所在函数的栈帧上,会锁着函数的反悔而被注销,因此SEH只对当前函数或这个函数所调用的子函数有效
5)从编译角度说,SEH的登记和注销是依赖编译器编译时所生成的数据结构和代码的,VEH的注册和注销都是通过调用系统API显式完成的,不需要编译器的特别处理
第12章 未处理异常和JIT调试
对于用户态的未处理异常,Windows的策略是使用系统登记的默认异常处理器来处理。事实上,Windows为应用程序的每个线程都设置了默认的SEH异常处理器。当应用程序内的代码没有处理异常时,系统会使用这些默认的异常处理器来处理异常。
对于内核态的未处理异常,如果有内核调试期存在,则系统(KiExceptionDispatch)会给调试器第二轮处理机会,如果调试器没有处理该异常,或者根本没有内核调试期,则系统会调用KeBugCheckEx发起蓝屏机制,报告错误并停止整个系统,其停止代码为KMODE_EXCEPTION_NOT_HANDLED
对于典型的Windows程序,系统会为它的每个线程登记默认的结构化异常处理器(SEH)。此外,编译器在编译时插入的启动函数通常也会注册一个SEH处理器。对于使用C运行库的程序,C运行库包含了基于信号的异常处理器机制(主要是为了兼容来自UNIX的软件,实际上在现在基本没什么作用了)。这些SEH异常处理器的过滤表达式都直接或间接调用了UnhandledExceptionFilter函数。
Window创建进程(启动一个程序)的大体过程:
1)打开要执行的程序映像文件,创建section对象用于将文件映射到内存中
2)建立进程运行所需的各种数据结构(EPROCESS、KPROCESS及PEB)和地址空间
3)调用NtCreateThread,创建出于挂起状态的初始线程,将用户态的起始地址存储在ETHREAD结构中
4)通知Windows子系统注册新的进程
5)开始执行初始线程
6)在新进程的上下文(context)中对进程做最后的初始化工作
在创建Windows子系统进程时,系统通常并不把PE文件的入口地址用作新线程的起始地址,而是把起始地址指向kernel32.dll中的进程启动函数BaseProcessStart。这样做的一个原因是要注册一个默认的SEH处理器(最后得到处理机会)。
在执行这些函数前,需要做大量的初始化工作,编译器通常会把自身提供的一个启动函数登记为程序的入口,让系统先执行这个启动函数(XXXmainCRTStartup),这个启动函数内部再调用用户编写的main或WinMain函数(源代码叫crt0.cpp),这个入口函数也包含一个SEH处理器,它的保护范围包含了对main或WinMain函数的调用(倒数第二个得到处理机会)。
对于初始线程以外的其他线程,其用户态的起始地址是系统提供一个位于kernel32.dll中的函数BaseThreadStart,系统在该函数也提供了一个SEH处理器。
系统弹出“应用程序错误”对话框(Application Fault Dailog,或者交General Protection Fault)的目的有二:
1)告知用户,该应用程序已经发生严重错误(未处理异常)而无法继续运行即将被终止
2)征求用户的处理意见:是立即终止、启动JIT调试器调试该程序,还是先发送错误报告然后再终止(Windows XP 引入)
“应用程序错误”对话框由UnhandledExceptionFilter函数触发,由操作系统的其他程序弹出并维护(与用户交互),系统本身使用其他的进程来现实“应用程序错误”对话框。不同系统使用不同的办法(Windows 2000:HardError,Windows XP:ReportFault API(使用DWWIN,增加Send Error Report),Windows Vista:WER 2.0 API(使用WerFault)),新办法不能用时就会调用HardError。
JIT调试(Just-In-Time Debugging),就是指在应用程序出现严重错误后面启动的紧急调试。因为JIT调试建立时,被调试的应用程序内已经发生了严重的错粗,通常都无法再恢复正常运行,所以JIT调试又被称为事后调试(Postmortem Debugging),其主要目的是分析和定位错误原因,或者手机和记录错误发生时的县城数据供事后分析。DrWatson是Windows系统中默认的JIT调试器。
顶层过滤函数(Top Level Exception Filter):如果应用程序不希望使用这些默认逻辑来处理未处理异常,那么可以注册一个自己的未处理异常的过滤函数,并通过SetUnhandledExceptionFilter API进行注册,这就使顶层过滤函数,只有在有未处理异常发生时才可能被调用。(例如C运行库将当前进程的顶层过滤器设置为msvcrt!_CxxUnhandledExceptionFilter)。顶层过滤函数被调用的条件是有未处理异常发生,同事所在程序不再被调试。
注意系统是使用一个全局变量而不是一个链表来记录顶层过滤函数的,所以系统(UnhandledExceptionFilter函数)只会调用最后注册成功的那个顶层过滤函数。同事在Windows XP SP2以后的版本中,SetUnhandledExceptionFilter函数会先将要设置的顶层过滤函数地址进行编码,然后再保存到BaseCurrentTopLevelFilter变量中,UnhandledExceptionFilter函数在调用顶层过滤函数时会对其进行解码。
第13章 硬错误和蓝屏
任何软件都可能因为自身设计缺欠或外部环境变化等而发生错误。既然错误是不可避免的,那么制定完善的错误处理机制显然要比试图发生错误更明智。
一套好的错误处理方案通常应该考虑以下3个方面:
1)即时提示(instant notification):即当错误情况需要立刻提示给用户时,将错误情况以可靠的方式、以用户可以理解的语言及时提示给用户
2)永久记录(persistent recording):即当错误情况需要永久记录的条件时,将错误描述永久记录在文件或数据库中供事后分析
3)自动报告(automatic reporting):即自动收集错误现场的详细情况并生成错误报告,让用户可以通过简单的方式(如网络)发送到专门用来收集错误报告的服务器。(Windows Error Reporting)
利用系统提供的MessageBox API,弹出一个图形化的消息框。这个优点是简单易用。但是存在3个局限:
1)MessageBox 是一个用户态的API,内核代码无法直接使用
2)MessageBox是工作在调用者(通常是错误发生地)的进程和线程上下文中的,如果当前进程/线程的数据结构(比如消息队列)已经由于严重错误而遭到破坏,那么MessageBox可能无法工作
3)对于系统启动关闭等待特殊情况,MessageBox是无法工作的
HardError:使用核心函数ExpRaiseHardError,系统中默认的硬错误提示进程就是CSRSS
BSOD:蓝屏机制的设计思想是将系统终止在导致错误的第一现场,并且把这个现场的信息显示给用户或永远保存下来,比如保存到转储文件,这样有利于更快地发现问题根源,去除软件错误(Bug)使用的核心函数是BugCheck2(具体有11个步骤,详见P368)
采用蓝屏方式提示错误的几种常见情况:
1)系统捕捉到内核代码中的未处理异常,或者检测到违反操作系统规则的情况
2)系统监测到操作系统的数据结构、模块或进程遭到破坏
3)在系统安装、启动或退出等边缘状态时发生错误,这时由于还不具备以其他方式提示错误的能力,所以只能以蓝屏提示
蓝屏包含以下几部分内容:
1)错误信息。用以描述错误情况和错误原因的文字信息
2)解决错误的建议。对于同一版本的Windows,这段信息只要出现就是相同的
3)技术信息。其格式为“STOP:停止码(参数1,参数2,参数3,参数4)”
4)现实内存转储(Dump)的过程和结果。
对于蓝屏错误,可以通过如下步骤逐步分析其原因:
1)根据蓝屏的停止码和蓝屏参数做初步判断(WinDBG的帮助文件中以停止码为顺序列出了系统定义的所有蓝屏错误,包括错误码和每个参数的含义,以及解决问题的建议,这个建议是针对停止码的,要不蓝屏画面中的建议有用的多)
2)可以在我iruande知识库(supports.microsoft.com)中或使用其他搜索引擎搜索蓝屏的停止码和参数,以了解更多信息
3)分析转储文件。系统会默认为蓝屏产生小型的转储文件(Small memory dump),默认位置为Windows系统目录的MiniDump文件夹,文件名是MiniMMDDYY-XX.dmp的格式,MMDDYY是发生蓝屏的日期(月日年),XX是序号,从01开始,依次递增。
4)如果经过以上的步骤还没有找到问题的原因,那么应该考虑通过内核调试做进一步的调试和分析。使用内核调试,可以设置断点,跟踪内核代码的执行过程,这样更容易准确地定位到错误根源。
System Mode Dump(与User Mode Dump不同,User Mode Dump也被称为MiniDump,位于Data目录中,可以通过MiniDumpReadDump API来读取,或者使用任何的调试工具,而System Mode Dump文件格式未公开,只能通过WinDBG查看)
Windows定义了3种类型的系统转储文件:完整转储(Complete memory dump)、内核转储(Kernel memory dump)、小型内存转储(Small memory dump)。其大小和信息聊依次递减。由于其是以内存页面(4KB)为单位来组织数据的,因此它的大小是内存页大小(4KB)的整数倍。
当有异常发生时,系统会将当时的状态保存到一个KTRAP_FRAME结构中,称为陷阱帧。因为很多崩溃与异常有关,所以转储文件中经常包含着陷阱帧数据(通过kv可以查看当前栈回溯序列中是否有关联的陷阱帧)。
可以通过Windbg的!analyze -v命令来自动分析Dump文件
辅助调试是错误提示机制的目的之一,但绝不能用错误提示机制取代其他正常的调试手段。应该采用合适的调试机制来帮助调试,比如使用ASSERT语句进行参数检查,使用TRACE语句或调用OutputDebugString之类的调试信息打印函数来追踪变量和执行路径。实际上,在开发期间应该把调试器作为最主要的调试手段。在调试器中运行,可以大大缩短我们熟悉代码执行情况和发现问题的时间。
设计优秀的错误提示机制应该具有可控性,可以按照严重程度、错误类别等属性定制错误提示的方式和次数。
第14章 错误报告
错误报告(reporting)和错误提示(notification)的区别:
1)从时间角度来看。错误提示是即时的,其目的是让用户立刻知晓所提示的信息;错误报告往往没有如此强的时间要求。
2)从目的性来看。错误提示的是让用户得知错误情况,选择处理方法;错误报告是记录错误详情以便找到错误原因
3)从信息(数据)量角度来看。错误报告往往包含更全面、更多的信息
WER采用C/S结构,客户端负责收集、生成和发送错误报告;服务端负责接收、存储、分类和自动寻找解决方案等任务
WER 1.0客户端包括以下几个部分:
1)FAULTREP.DLL,导出ReportFault和AddERExcludedApplication等函数
2)DWWIN.EXE,WER的客户端主程序,负责显示WER风格的“应用程序错误”对话框和发送错误报告,包括应用程序崩溃后的错误报告和系统崩溃后的错误报告。
3)DW.EXE和DW20.EXE,是DWWIN.EXE的两个遍体,分别用于报告Visual Studio与Office的应用程序错误(通常是通过SetUnhandledExceptionHandler注册自己的顶层未处理异常过滤器来启动的)
4)DUMPREP.EXE,用于检查是否有等待发送的错误报告。如果有,则会通过动态加载FAULTREP.DLL中的ReportFault函数启动DWWIN.EXE程序发送报告
5)修改了的KERNEL32.DLL,WER修改了KERNEL32.DLL中的UnhandledExceptionFilter函数。当应用程序出现未处理异常时,调用FAULTREP.DLL中的ReportFault。这时WER与系统的一个重要接入点
6)配置界面
WER的报告模式(Reporting Modes):共享内存模式(Shared memory mode,也称之为异常模式)、清单模式(Manifest mode)、排队模式(Queued mode,也称之为Headless mode)
WER的传输模式:互联网模式(Internet mode,默认方式)和企业方式(Corporate mode)
WER 2.0引新模块,均位于system32目录下:Wer.dll、Wercon.exe、Wercplusupport.dll、Werdiagcontroller.dll、WerFault.exe、WerFaultSecure.exe、Wemgr.exe、Wersvc.dll
WER 2.0比1.0更加灵活强大,允许程序员做更多定制,以用于我们自己开发的软件
第15章 日志
一条日志记录通常包含如下几个要素:
1)时间:所记录事件发生的时间,通常至少精确到分钟级别
2)地点:用来定位所记录事件发生的“位置信息”,通常包括机器名,进程ID、线程ID等
3)主体(来源):即该事件的实施者,根据需要可以是服务名称、模块名称或类名和函数名
4)事件:对所发生事件的描述
5)类型(严重程度):该事件的严重程度,可以分为信息(information)、警告(warning)和错误(error)三种,也可以根据需要分得更细
Windows的日志机制:
1)ELF(Event Log File):使用磁盘文件记录,每一类事件放在一个文件中。XP下就是AppEvent.Evt、SecEvent.Evt和SysEvent.Evt。均位于config目录中,运行在Services.exe中,自动启动且不可停止。内部使用RPC实现
2)CLFS(Common Log File System,Vista以后引用):每一个CLFS Log由一个BLF和多个Container文件组成,用LSN(Log Sequence No)来唯一标识一条日志记录,增加了HardwareEvents和DFS Replication等类别,并且为所有的日志文件建立了一个单独的目录,即winevtLogs目录,日志扩展名也改成.Evtx。内部使用CLFS.SYS和CLFSW32.DLL实现
CLFS的使用方法:创建日志文件——>添加CLFS 容器(Cantainer)——>创建编组区(Marshalling Area)——>添加日志记录
第16章 事件追踪
事件追踪(Event Tracing):记录软件运行的动态轨迹,包括代码执行轨迹和变量的变化轨迹。事件追踪机制更关心软件的“变化和运动过程”,通常以二进制方式而不是文本来传输和记录信息。包含更多的技术细节,比如函数名称、变量取值等。在软件正常运行时通常是关闭的,只在观察和分析时才会开启。
事件追踪通常在性能分析、产品期调试和单元测试或自动测试等任务(领域)中有重要作用
事件追踪机制应满足的要求:高效性、动态性、灵活性、选择性、易用性
Windows提供了一套完整的时间追踪机制(包括内部实现、API和辅助工具),称为ETW(Event Tracing for Windows)。使用ETW,程序员可以非常简单地将这一强大的时间追踪机制插入到自己的软件应用中。
1)ETW将追踪消息先输出到由系统来管理和维护缓冲区中,然后异步写入到追踪文件或送给观察器。如果系统崩溃,崩溃前没有写入到文件中的信息会记录到转储文件(Dump)中
2)ETW的追踪信息是以二进制形式传输和存储的,所有格式信息存放在私有的消息文件中,这样可以防止软件本身的保密技术泄露
3)ETW机制支持动态开启,没有开启时,开销几乎可以忽略(只需判断一个标志)
ETW使用经典的Provider/Consumer/Controller设计模式
ETW提供3种方式来描述格式信息,1)MOF(Managed Object Format),2)WPP(Windows Software Trace Preprocessor),3)Manifest(XML)和TDH API(Vista以后)
第17章 WHEA(Windows Hardware Error Architecture)
WHEA(Windows硬件错误架构)是Windows操作系统中用于处理硬件错误的基本框架,它定义了系统中的硬件、固件、以及软件应该如何相互写作来报告、处理和记录各种硬件错误,并且提供了一系列基础设施和机制来实现以上目标
蓝屏(BSOD)崩溃的2个不足:
1)对于可以纠正的错误,BSOD显然处置过度,导致不必要的系统中止
2)BSOD通常只能记录崩溃前的CPU和内存状态,无法记录与硬件错误直接相关的硬件状态,这让发现硬件的错误根源和修正错误很困难
WHEA框架所定义的基础设施包括:
1)通用的错误来源(Error Source)发现机制
2)统一的硬件错误记录格式
3)统一的硬件错误处理流程
4)可靠的错误记录持久化机制
5)基于ETW的硬件错误事件模型,管理程序可以通过这种模型接收到硬件错误事件并采取进一步措施
WHEA要实现的目标:
1)借助WHEA的错误记录机制所记录下的错误信息,可以更快地发现错误根源,缩短系统从硬件错误中恢复运行的平均时间
2)通过纠正可纠正错误和健康状况监视(Health Monitoring),可减少因为硬件错误而导致的系统崩溃
3)为应用软件开发者提供支持,以便可以开发出强大的硬件错误报告和管理软件
4)更好地利用硬件已经提供的和将来可能提供的错误报告机制,比如CPU的MCA机制,以及PCI Express总线标准中定义的AER(Advance Error Reporting)机制
第18章 内核调试引擎
目前主要有3中方法来进行内核调试:
1)使用硬件调试器,它通过特定的接口(如JTAG)与CPU建立连接并读取它的状态,比如ITP调试器,相对于后两种方法,这种方法能够调试引擎初始化之前的状态。比如加在NTLDR或WinLoad程序夹在内核的过程,或者调试内核开始工作的最初过程。
2)在内核中插入专门用于调试的终端处理函数和驱动程序,当操作系统被中断时,这些终端处理函数和驱动程序接管系统的硬件,营造一个供调试器可以运行的简单环境,这个环境使用自己的驱动程序来接受用户输入,显示输出(窗口)。SoftICE和Syser调试器使用的就是这种方法。
3)在系统内核中加入调试支持,当需要中断到调试器中时,只保留这部分支持调试的代码还在运行,内核的其它部分都停止了,包括任务调度、用户输入和显示输出部分。因为正常的内核服务都已经停止,所以调试器程序是不可能运行在同一个系统中的。、因此这种方法需要调试器运行在另一个系统中,二者通过通信电缆交流信息。使用串行口(native通信方式)、1394和USB 2.0来建立连接。
Windows操作系统推荐的内核调试方式使用的是第3种方法。内建在操作系统内核中负责调试的那个部分通常被称为内核调试引擎(Kernel Debug Engine)。
从访问内核的角度来看,内核调试引擎为内核调试期提供了一套特殊的API,我们将其称为内核调试API,简称KdAPI。通过它调试器可以以一种类似远程调用的方式访问到内核,这与应用程序通过Win32 API访问内核(服务)类似。
目前流行使用虚拟机管道技术来实现内核调试,优点是简单方便,只需要一台主机,缺点如下:
1)难以调试硬件相关的驱动程序
2)当对某些设计底层操作(中断、异常或者I/O)的函数或指令设置断点时,可能导致虚拟机以外重新启动
3)当将目标系统终端到调试器中时,目前的虚拟机管理软件会占用很高的CPU(超过90%,在多核系统上问题不那么严重)
内核调试引擎的第一次调试机会,是在内核的入口函数KiSystemStartup调用HalInitialzeProcessor初始化CPU后,在电泳KiInitializeKernel之前。也就是KdInitSystem函数第一次被调用的时候。
第19章 Windows的验证机制
软件调试的主要任务就是寻找软件瑕疵(defect)的根源,其前提是通常已经知道了有瑕疵存在。
发现软件下次的最普通方法就是测试。常见的测试手段有以下几种:
1)黑盒测试(Black box testing),是指测试人员根据软件需求规约和测试文档对软件的运行行为进行检查。其基本思想是将被测试软件当作一个不透明的黑盒子,给其一个输入,看其输出是否符合要求,只要输出结果正确,便认为测试通过,不检查盒子内部的变化过程。
2)白盒测试(White box testing),是指根据程序的结构和代码逻辑来编写测试用例并进行测试。比黑盒测试更有针对性,但是对测试人员的要求更高。
3)内建自检,又称为BIST(Built-In Self-Test),是指在软件代码内部构建一些测试功能,这些功能(函数)可以在某些情况下执行,或者被自动测试工具所调用以发现问题。
4)压力测试(Stress testing),用于测试目标程序在高负载(入频繁访问和大量要处理的任务)和低资源(如低可用内存和低硬件配置)情况下的工作情况。
虽然以上每种测试都有它的优势和侧重点,但即使使用了以上所有测试手段,也不能保证会发现所有问题。主要原因就是测试时的运行环境和条件不足以将错误触发并暴露出来。所以当测试时,我们通常希望系统做严格的检查,发现问题就立刻报告,并且最好能模拟极端的和苛刻的运行环境,以便让错误更容易暴露出来。Windows操作系统的验证机制(Verifier)就是为了满足这个需求而设计的。验证机制的主要目标是检查被测试软件,或者说是为被测试软件提供一个验证器(Verifier)。
驱动程序验证器(Driver Verifier)的主体实现在内核文件(NOTSKRNL.EXE)中的一系列内核函数和全局变量,其名字中大多都包含Verifier字样,或者是以Vi和Vf开头。其基本设计思想是在驱动程序调用设备驱动接口(DDI)函数时,对驱动程序执行各种检查,看其是否符合系统定义的设计要求,特别是DDK文档所定义的调用条件和规范。
一个驱动程序如果想取得Windows徽标和签名,则一定要通过驱动验证器的验证(WHQL,Windows Hardware Quality Labs)。这种强制性有利于提高内核模块的质量和整个系统的稳定性。
Windows采用的是通过修改被验证驱动程序的输入地址表(Import Address Table,IAT)来挂接(HOOK)驱动程序DDI调用,即通常所说的IAT HOOK方法。简单的说,系统会将被验证驱动程序IAT表中的DDI函数地址替换为验证函数的地址。这样,当这个驱动程序调用DDI函数时,便会调用对应的验证函数。验证函数与原来的函数具有完全一致的函数原型,所以不会影响被验证程序的执行。
在验证函数得到调用后,它执行的典型操作如下:
1)更新计数器,或者全局变量
2)检测调用参数,或者做其他检查,如果检测到异常情况,那么调用KeBugCheckEx(DRIVER_VERIFIER_DETECTED_VIOLATION,...)函数,即通过蓝屏(Bug Check)机制来报告验证失败
3)如果没有发现问题,那么验证函数会调用原来的函数,并返回原函数的返回值(如果有)
驱动验证器的主要验证项目:自动检查(唯一一个不可以单独禁止和取消的项目)、特殊内存池、强制IRQL检查、低资源模拟、内存池跟踪、I/O验证、死锁检测、增强I/O验证、SCSI验证、IRP记录、驱动滞留探测(Driver Hand Detection)、安全检查、强制I/O请求等待解决、零散检查等
应用程序验证机制的实现分为连个部分,一部分是是现在NTDLL.DLL中的一系列函数,这些函数都是以AVrf开头的;另一部分是一个名为应用程序验证器(Microsoft Application Verifier)的工具包,其设计思想是通过监视应用程序与操作系统之间的交互来发现应用程序中隐藏的设计问题(如内存分配、内核对象使用和API调用等)。其设计原理也是通过IAT HOOK的方式
第4编 编译器的调试支持
第20章 编译和编译期检查
编译器的调试支持主要为以下几个方面:编译期检查、运行期检查、调试符号、内存分配和释放、异常处理、映射(MAP)文件
构建并执行一个C语言程序的典型过程:源程序经过编译器(Compiler)被编译为等价的汇编语言模块,再经过汇编器(Assembler)产生出于目标平台CPU一致的机器码模块。尽管机器码模块中包含的指令已经可以被目标CPU所执行,但其中可能还包含没有解决(unresolved)的名称和地址引用,因此需要链接器(Linker)解决这些问题,并产生出符合目标平台操作系统所要求格式的可执行模块。当用户执行程序时,操作系统的加载器(Loader)会解读链接器记录在可执行模块中的格式信息,将程序中的代码和数据“布置”在内存中,成为真正可以运行的内存映像。
链接器的主要职责是将编译期产生的多个目标文件和成为一个可以在目标平台下执行的执行映像。
在Windows系统下,链接器应该根据Windows操作系统定义的可执行文件格式来产生可执行文件,也就是产生PE(Portable Exectuable)格式的执行映像文件。要产生一个PE格式的可执行文件,链接器要完成的典型任务如下:
1)解决目标文件中的外部符号,包括函数调用和变量引用。如果调用的函数是Windows API或其他位于DLL模块中的函数,那么须要为这些调用建立输入目录表(Import Directorty Table,简称IDT)和输入地址表(Import Address Table,简称IAT),IDT用来描述被引用的文件,IAT用来记录或重定位被引用函数的地址,链接器会把IDT和IAT放在PE文件的输入数据段(.idata)中
2)生成代码段(.text),放入已经解决了外部引用的目标代码
3)生成包含只读数据的数据段(.data)
4)生成包含资源数据的资源段(.rsrc)
5)如果定义了输出函数和变量,则产生包含输出表的.edata段。输出表通常表现在DLL文件中,EXE文件一般不包含.edata段,但NTOSKRNL.EXE是个例外
6)生成PE文件头,文件头描述了文件的构成和程序的基本信息
加载器(Loader)是操作系统的一个部分,它负责将可执行程序从外部存储器(如硬盘)加载到内存中,并做好执行准备,包括遍历输入目录表加载依赖模块,遍历IAT表绑定动态调用函数,对基地址发生冲突的模块执行调整工作等。NTDLL中包含了一系列以Ldr开头的函数,用于完成以上任务。
编译器的基本功能就是将使用一种语言编写的程序(源程序)翻译成用另一种语言表示的等价程序(目标)。现实中,通常是从高层次的语言翻译到底层次的语言。基本结构都是由前端和后端两个部分组成。前端主要是负责理解源代码的含义,即分析(analysis)功能,后端负责产生等价的目标程序,即合成(synthesis)功能。前端和后端之间的媒介是中间代码,又称为中间表示(Intermediate Representation),即IR。编译器前端对于源程序进行词法分析(Lexical Analysis)、语法分析(Syntax Analysis)、语义分析(Semantic Analysis),并将其映射到中间表示,后端对中间表示进行优化处理,再将其映射到机器码表示的目标程序中。而后,链接器再将目标程序经链接成为可以执行的执行映像。
编译器的前端(Front End)负责扫描和分析源程序并产生中间表示(IR),它主要完成如下几项任务:
1)词法分析(Lexical Analysis):读入并小苗源程序文件的字符流,剔除其中的空格和注释内容,并根据构成词规则识别出单词,将源代码中的字节流转换成记号流(token stream)。实现该功能的部分通常被称为扫描器(Scanner)
2)语法分析(Syntax Analysis):对词法分析产生的记号流进行层次分析,根据语法规则(syntax rules)把单词序列组成语法词语,并表示为语法树(syntax tree)或推导树(derivation tree)的形式
3)语义分析(Semantic Analysis):对语法树中的语句进行语义处理,审查数据类型的正确性,以及运算符使用是否符合语言规范。因为编译阶段的语义分析无法分析程序运行时才确定的动态语义,所以编译时的语义分析又被称为静态语义分析(Static Semantic Analysis),简称SSA
4)中间代码生成:产生编译器和后端交流所使用的中间表示(IR),有时也被称为中间代码。中间表示的具体形式因编译期的不同而不同,常见的有三元式(three-address code)、四元式(4-tuple code)等
编译器的后端(Back End)负责对前端产生的中间代码进行优化处理,并阐释使用目标代码表示的目标程序。优化后的代码仍然是使用中间代码表示的。目标程序经过联建最终成为可以在特定平台上运行的可执行程序。
编译器前端-后端设计使它们分工明确,前端与被编译的语言相关,不必关心目标平台(CPU),而后端与目标平台相关,不必关心源程序语言。这样的好处是编译器的前端和后端松耦合,更容易支持新的语言和新的目标平台。
编译器编译过程的8个主要步骤:词法分析、语法分析、语义分析、中间代码生成、优化代码生成、目标代码生成、符号表管理、错误处理
编译期检查可以发现代码中的词法,语法以及少量语义方面的问题,主要有以下5个方法:
1)未初始化的局部变量
2)类型不匹配
3)使用编译器指令(compiler directives)手工加入检查
4)标准标注语言(Standard Annotation Language,SAL)向代码中加入标注(仅限于VC8以上)
5)使用静态分析工具,如驱动程序静态验证器(Static Driver Verifier,SDV)、PREfas
网站评分
书籍多样性:7分
书籍信息完全性:8分
网站更新速度:8分
使用便利性:6分
书籍清晰度:9分
书籍格式兼容性:4分
是否包含广告:3分
加载速度:5分
安全性:6分
稳定性:5分
搜索功能:4分
下载便捷性:5分
下载点评
- epub(191+)
- 体验好(222+)
- 内容完整(368+)
- 体验还行(494+)
- 赚了(614+)
- 盗版少(96+)
- 速度慢(117+)
- 中评多(521+)
- 差评(612+)
- 书籍完整(206+)
- 好评(293+)
下载评价
- 网友 车***波: ( 2025-01-03 19:01:13 )
很好,下载出来的内容没有乱码。
- 网友 宫***凡: ( 2024-12-21 00:16:37 )
一般般,只能说收费的比免费的强不少。
- 网友 马***偲: ( 2024-12-12 06:23:19 )
好 很好 非常好 无比的好 史上最好的
- 网友 屠***好: ( 2025-01-02 00:08:36 )
还行吧。
- 网友 家***丝: ( 2024-12-15 04:48:15 )
好6666666
- 网友 冯***丽: ( 2024-12-26 18:35:20 )
卡的不行啊
- 网友 权***波: ( 2024-12-14 13:39:03 )
收费就是好,还可以多种搜索,实在不行直接留言,24小时没发到你邮箱自动退款的!
- 网友 国***舒: ( 2024-12-28 21:43:33 )
中评,付点钱这里能找到就找到了,找不到别的地方也不一定能找到
- 网友 谢***灵: ( 2024-12-30 02:58:00 )
推荐,啥格式都有
- 楚辞(中华国学经典精粹·诗词文论必读本) pdf mobi txt 2024 电子版 下载
- 国家网络文艺战略研究--中国文化强国新时代(精)/互联网+新文艺 pdf mobi txt 2024 电子版 下载
- 9787545024340 pdf mobi txt 2024 电子版 下载
- 燎原教育(20142015)同步辅导考研复变函数辅导及习题精解(钟玉泉第三版) pdf mobi txt 2024 电子版 下载
- 初中3年-男孩如何管教 pdf mobi txt 2024 电子版 下载
- 新东方 剑桥少儿英语2级 冲刺指南 pdf mobi txt 2024 电子版 下载
- 苏轼十讲 pdf mobi txt 2024 电子版 下载
- 四川泡菜大全 pdf mobi txt 2024 电子版 下载
- 2021春少年班九年级数学—北师版(下) pdf mobi txt 2024 电子版 下载
- 企业税收风险管理理论与实务研究 pdf mobi txt 2024 电子版 下载
书籍真实打分
故事情节:4分
人物塑造:3分
主题深度:8分
文字风格:8分
语言运用:9分
文笔流畅:7分
思想传递:5分
知识深度:7分
知识广度:7分
实用性:3分
章节划分:9分
结构布局:8分
新颖与独特:8分
情感共鸣:3分
引人入胜:5分
现实相关:3分
沉浸感:9分
事实准确性:6分
文化贡献:9分