| liang 的个人资料阿斯提亚神殿——梦幻天子's sky照片日志列表 | 帮助 |
|
1月21日 Some tips about Conficker.BSomething about virus loading limitation ============================ 1. This virus will check the VM by using some unique instructions for VMWare or VPC. So this virus could not be run in VMWare or VPC unless Hardware-level virtualization has been enabled. 2. The victim computer will use “Rundll32.exe <virus dll>, <random parameter>, or “rundll32.exe <virus rename>” . 1) For the first kind, the random parameter is calculated by hashing the Computer Name of the victim machine, so if the hash is incorrect. The virus failed to load. 2) For the second kind, although it does not use the random parameter is not needed, the virus name SHOULD not be the dll extension, otherwise the virus failed to load.
Something about Hiding Tricks for Conficker.B ======================================= Conficker is far away from the professional Rootkits: 1. For Register Entry Hiding, the virus just modifies the permission of the specific service. 2. The virus uses undocumented method to create the service(Do not call CreateService API), instead it create the service entry by modifying the registry. In this case, the service could run properly, but we could not see this service in “Services.msc” before a reboot for creating this service. After the reboot, we could see this virus in “Services.msc”. 3. For file hiding, it just sets the virus file attribute to “RHSA”.
4. In the “Services.msc”, we could see this service, but the status of it is not running:
And for Windows 2k, the status is always “Starting”, this is because the virus is running in the DLLMain Function, and Windows Service Manager will treat it as initializing status. 5. If the virus is running, we could still see the handle in Process Explorer:
This virus is running unstable and sometimes itself will crash. In this case, you may not find the handle in Process Explorer, but this not means it is hidden. 6. This virus will continuously sending mal-formed packets to the LAN network by enumerate all the local IP addresses:
After finishing sending the packets, the virus will wait for a long time, and during this period, you will not see any suspicious packets. 7. The driver’s file name is always 0*.tmp(01.tmp,02.tmp, etc). It is extracted from the dll file when the dll virus loaded for the first time of each reboot. This .tmp driver will loaded to memory just to modify the memory of the tcpip.sys module to add the limitation of TCP Maximum Half-connection attempts number. And the dll will delete this file, unload the driver, and delete the service entry immediately.
That is all about Conficker. Hope we could kill it soonerJ 7月29日 (转载)Hindsight analysis of the infamous DNS bugIf you read Dan Kaminsky's researchs over the past few years, you'd probably 1. CNAME Records: DNS Aliases - Instead of returning an address, return what the "Canonical", or Official - If you are allowed to be the resolver for that canonical name, your * It's not a bug. * Works against most, but not actually all name servers 2. Demo $ dig 1.foo.notmallory.com $ dig bar.foo.notmallory.com $ dig 2.foo.notmallory.com $ dig bar.foo.notmallory.com As you can see, this trick, Dan called it "CNiping", can be used to Okie so now, we have some ways to overwrite cached data in a DNS resolver, The rest of this post to to emphasize on how easy to guess the QID if we Assume the following symbols are used: I: Number distinct IDs available (maximum 65536) P: Number of ports used (maximum around 64000, but often 1) N: Number of authoritative nameservers for a domain (averages F: Number of 'fake' packets sent by the attacker R: Number of packets sent per second by the attacker W: Window of opportunity, in seconds. Bounded by the response D: Average number of identical outstanding questions of a resolver A: Number of attempts, one for each window of opportunity The probability of spoofing a resolver is equal to amount of fake packets When the resolver has 'D' multiple identical outstanding questions, each In symbols, if the probability of being spoofed is denoted as P_s: P_s = D*F/N*P*I It is more useful to reason not in terms of aggregate packets but to convert If the Window of opportunity length is 'W' and the attacker can send 'R' F= R * W ---> P_s = D*R*W/N*P*I To calculate the combined chance of at least one success, the following P_cs = 1 - (1 - P_s)^A = 1 - (1 - D*R*W/N*P*I)^A When common numbers (as listed above) for D, W, N, P and I are inserted, P_cs = 1 - (1 -R/1638400)^A The different between this attack and the one described in the original In the original study, A = T/TTL, where T is the time taken to successful In this case, A = N, which is the number of queries we send to target So all you must do now is to increase R and A. Suppose for each query, you If you use Metasploit, you can use the following formula to calculate your P_cs = 1 - (1 - xids/2^16)^A where xids is the number of fake responses you want to send for each query. One final note: this attack is not a birthday attack. because we only have 5月10日 The ancient IIS vulnerability(MS01-044)It's a famous IIS vulnerability whose Exploit code is called "the red code". Due to its great reputation, I did a simple analysis with the bug code(between the lines):
idq!CVariableSet::AddExtensionControlBlock: 6e90065c mov eax,0x6e906af8 6e900661 call idq!_EH_prolog (6e905c30) 6e900666 sub esp,0x1d0 6e90066c push ebx 6e90066d xor eax,eax 6e90066f push esi 6e900670 push edi 6e900671 mov [ebp-0x24],ecx 6e900674 mov [ebp-0x2c],eax 6e900677 mov [ebp-0x28],eax 6e90067a mov [ebp-0x4],eax 6e90067d mov eax,[ebp+0x8]; the parameter "EXTENSION_CONTROL_BLOCK" . . . 6e9006b7 mov esi,[eax+0x64];offset "lpszQueryString" 6e9006ba or ecx,0xffffffff 6e9006bd mov edi,esi . . . 6e9007b7 push 0x3d;ascii '=' 6e9007b9 push edi 6e9007ba mov [ebp-0x18],edi 6e9007bd call dword ptr [idq!_imp__strchr (6e8f111c)] 6e9007c3 mov esi,eax 6e9007c5 pop ecx 6e9007c6 test esi,esi 6e9007c8 pop ecx 6e9007c9 je 6e9008d2 6e9007cf sub eax,edi 6e9007d1 push 0x26 6e9007d3 push edi 6e9007d4 mov [ebp-0x20],eax ;store the address of '=' 6e9007d7 inc esi 6e9007d8 call dword ptr [idq!_imp__strchr (6e8f111c)] 6e9007de mov edi,eax 6e9007e0 pop ecx 6e9007e1 test edi,edi 6e9007e3 pop ecx 6e9007e4 jz 6e9007fa 6e9007e6 cmp edi,esi 6e9007e8 jnb 6e9007f0 6e9007ea inc edi 6e9007eb jmp 6e9008e4 6e9007f0 mov eax,edi 6e9007f2 sub eax,esi 6e9007f4 inc edi 6e9007f5 mov [ebp-0x14],eax 6e9007f8 jmp 6e900804 6e9007fa mov eax,[ebp-0x10] 6e9007fd sub eax,esi 6e9007ff add eax,ebx 6e900801 mov [ebp-0x14],eax 6e900804 cmp dword ptr [ebp-0x20],0x190 ; key comparison //it shows that the distance between the beginning of lpszQueryString and the address of '=' must not greater than 0x190(400) 6e90080b jb 6e900828 /* case greater than 0x190, throw the exception 6e90080d mov eax,0x80040e14 6e900812 xor ecx,ecx 6e900814 mov [ebp-0x3c],eax 6e900817 lea eax,[ebp-0x3c] 6e90081a push 0x6e9071b8 6e90081f push eax 6e900820 mov [ebp-0x38],ecx 6e900823 call idq!_CxxThrowException (6e905c36) */ /* case lower than 0x190 6e900828 mov eax,[ebp+0x8] 6e90082b push dword ptr [eax+0x8] 6e90082e lea eax,[ebp-0x1dc];about 476-bytes long from the returning address 6e900834 push eax 6e900835 lea eax,[ebp-0x20] 6e900838 push eax 6e900839 push dword ptr [ebp-0x18] 6e90083c call idq!DecodeURLEscapes (6e9060be); this function convert the original ANSI string to the Unicode version and stored in [ebp-0x1dc] { query!DecodeURLEscapes: 68cc697e mov eax,0x68d667cc 68cc6983 call query!_EH_prolog (68d4b250) 68cc6988 sub esp,0x30 68cc698b push ebx 68cc698c push esi 68cc698d xor eax,eax 68cc698f push edi 68cc6990 mov edi,[ebp+0x10] 68cc6993 mov [ebp-0x3c],eax 68cc6996 mov [ebp-0x38],eax 68cc6999 mov ecx,[ebp+0xc] 68cc699c mov [ebp-0x4],eax 68cc699f mov [ebp-0x18],eax 68cc69a2 mov ecx,[ecx];distance 68cc69a4 cmp ecx,eax 68cc69a6 mov [ebp-0x10],ecx; store the "distance" varible to the local stack 68cc69a9 jz query!DecodeURLEscapes+0x99 (68cc6a17) 68cc69ab mov esi,[ebp+0x8]; the address of lpszQueryString 68cc69ae mov eax,ecx 68cc69b0 inc eax 68cc69b1 mov [ebp-0x14],eax ;distance+1 68cc69b4 movzx bx,byte ptr [esi] 68cc69b8 and dword ptr [ebp-0x34],0x0; set 4 bytes of the local stack to 0 68cc69bc cmp bx,0x2b 68cc69c0 jne query!DecodeURLEscapes+0xdf (68cc6a5d) 68cc69c6 push 0x20 68cc69c8 pop ebx 68cc69c9 inc esi 68cc69ca xor eax,eax 68cc69cc cmp [ebp-0x34],eax 68cc69cf jnz query!DecodeURLEscapes+0x79 (68cc69f7) 68cc69d1 cmp bx,0x80 68cc69d6 jb query!DecodeURLEscapes+0x79 (68cc69f7) 68cc69d8 cmp [ebp-0x18],eax 68cc69db jnz query!DecodeURLEscapes+0x79 (68cc69f7) 68cc69dd cmp [ebp-0x3c],eax 68cc69e0 jnz query!DecodeURLEscapes+0x73 (68cc69f1) 68cc69e2 mov eax,[ebp-0x14] 68cc69e5 push eax 68cc69e6 mov [ebp-0x38],eax 68cc69e9 call query!ciNew (68d4a977) 68cc69ee mov [ebp-0x3c],eax 68cc69f1 mov eax,[ebp-0x3c] 68cc69f4 mov [ebp-0x18],eax 68cc69f7 mov eax,[ebp-0x18] 68cc69fa test eax,eax 68cc69fc jz query!DecodeURLEscapes+0x88 (68cc6a06) 68cc69fe mov [eax],bl 68cc6a00 inc eax 68cc6a01 mov [ebp-0x18],eax 68cc6a04 jmp query!DecodeURLEscapes+0x8d (68cc6a0b) 68cc6a06 mov [edi],bx 68cc6a09 inc edi 68cc6a0a inc edi 68cc6a0b dec dword ptr [ebp-0x10] ; decrease the length(400 bytes in maximum) 68cc6a0e dec dword ptr [ebp-0x14] 68cc6a11 cmp dword ptr [ebp-0x10],0x0 ; loop until [ebp-0x10] is 0 68cc6a15 jnz query!DecodeURLEscapes+0x36 (68cc69b4) 68cc6a17 test eax,eax 68cc6a19 jz query!DecodeURLEscapes+0xb4 (68cc6a32) 68cc6a1b sub eax,[ebp-0x3c] 68cc6a1e push eax 68cc6a1f push edi 68cc6a20 push eax 68cc6a21 push dword ptr [ebp-0x3c] 68cc6a24 push 0x1 68cc6a26 push dword ptr [ebp+0x14] 68cc6a29 call dword ptr [query!_imp__MultiByteToWideChar (68c61264)] 68cc6a2f lea edi,[edi+eax*2] 68cc6a32 and word ptr [edi],0x0 68cc6a36 sub edi,[ebp+0x10] 68cc6a39 mov eax,[ebp+0xc] 68cc6a3c push dword ptr [ebp-0x3c] 68cc6a3f or dword ptr [ebp-0x4],0xffffffff 68cc6a43 sar edi,1 68cc6a45 mov [eax],edi 68cc6a47 call query!ciDelete (68d4a9ae) 68cc6a4c mov ecx,[ebp-0xc] 68cc6a4f pop edi 68cc6a50 pop esi 68cc6a51 mov fs:[00000000],ecx 68cc6a58 pop ebx 68cc6a59 leave 68cc6a5a ret 0x10 } Now, the whole thing comes to light: We could see that DecodeURLEscapes function calculates the source buffer from the ANSI version string to Unicode version string(also do some filtering work), and then copy the Unicode version to the local stack in AddExtensionControlBlock function, which is 476 bytes faraway from ebp. Unfortunately, in this case DecodeURLEscapes converts at most 400 bytes ANSI chars to Unicode version, which means the output buffer should be as big as 800 bytes, otherwise a buffer overflow attacking could happen. An attack could sent an HTTP request in which contains a crafted URL to the IIS Server, the flawed module of IIS then parses it and results in buffer overflow.
One key problem is: How to build a special shellcode which could be executed successfully after it is converted to unicode version?
Did you see a shellcode look like this "XX 00 YY 00 ZZ 00", or every byte in it is greate than 0x80? I have never seen. :(
However, the man called "yuange" realized this. See his article "widechar的字符串缓冲溢出攻击技术" for reference.
3月13日 [原创]让VS2008支持WDM驱动的编写为了毕设的需要,今天写了一个VS2008下编写WDM驱动的template,主要通过VS提供的custom wizard工程,编辑一个wizard的htm以及相关的js文件来实现。
目前还存在以下缺陷:第一,不支持c++,强大的面向对象设计思想暂时还不能应用。第二,必须事先手动设置一个环境变量,指定DDK的路径。
现在终于可以在VS中新建驱动工程了。相比以前在记事本里写驱动代码,VS下看起来还是很舒服的。
有兴趣的朋友可以私下交流具体细节。
下一阶段打算研究一下毕设的细节。毕竟这样的机会不多,很可能一生就这一次,今后可能就不会再碰研究领域了,所以呢,要珍惜啦。本次毕设的Code Name暂时命名为:Zion,软件开发部分与MSRA实习生——老BOSS合作完成,目前的计划是:首先合作完成Rootkit底层功能模块,然后我做攻击工具,他做检测工具。
最后,祝愿我一切顺利。 2月5日 调和数与小提琴Knuth大牛真是博学多才,在讲到Harmonic number时,对其这般定义:
Hn is a harmonic number, so called because the kth harmonic produced by a violin string is the fundamental tone produced by a string that is 1/k times as long.
这段话始终看不懂。但可以肯定的是,调和数必然和小提琴有关,google了下,终于豁然开朗:
“小提琴的泛音是通过手指搭在弦上不按到指板(就是琴弦下面那块黑色的木板)时运弓发出的。通常手指按在弦上但不压到指板运弓的话发不出什么声音,但在如果搭在靠近演奏者的琴弦长度的 1/2, 1/3, 1/4 等位置却能发出比较清脆的类似笛子的声音,音调比空弦高,1/2 处比空弦高八度。这些位置发出的声音就叫自然泛音。(另外还有人工泛音,我自己从来没有试过,就不说了。)注意到这些泛音的位置了吗?空弦算作 1/1, 泛音的位置 1/2, 1/3, 1/4 正好与 Hn 中的每一项对应。”
长见识了,原来这都可以…… 12月19日 第二、第三张offer这周真是喜事不断,首先是郁闷了将近两周的微软面试,终于有了结果,非常感觉在面试中给我鼓励的Samuel, Daniel,Chao,以及所有Security Support Group的同事们和同学们,没有你们的帮助,就没有今天这样的结果。期间经历了痛苦的等待,看着别人陆续拿到口头offer而自己却没有结果。。。汗,看来我的心态的确还需要磨练。好的心态的走向成功的基础。
今天上午Symantec给了口头offer,北京的Security Response Engineer,也是我特别喜欢的职位。不过地方比较远,因此就当个保底吧。 12月16日 无题汗,犯了个低级错误,贴一下正确的,以此为纪念
#pragma comment(linker, "/ENTRY:wmain")
int _tmain(int argc, _TCHAR* argv[]) { return 0; } 12月2日 Cancelling I/O Requests[How to]这几天写了一个在内核级模糊检测一类病毒的工具,程序本身很简单,可对于WDM的生疏,使我碰到了许多莫名的问题,程序在以一定概率导致BSOD的状况下运行,这显然是定时炸弹,必须想办法解决,以下看到一篇好文章,对我解决DRIVER_UNLOADED_WITHOUT_CANCELLING_PENDING_OPERATION的错误有帮助:
Just as happens with people in real life, programs sometimes change their mind about the I/O requests they’ve asked you to perform for them. We’re not talking about simple fickleness here. Applications might terminate after issuing requests that will take a long time to complete, leaving requests outstanding. Such an occurrence is especially likely in the WDM world, where the insertion of new hardware might require you to stall requests while the Configuration Manager rebalances resources or where you might be told at any moment to power down your device. To cancel a request in kernel mode, someone calls IoCancelIrp. The operating system automatically calls IoCancelIrp for every IRP that belongs to a thread that’s terminating with requests still outstanding. A user-mode application can call CancelIo to cancel all outstanding asynchronous operations issued by a given thread on a file handle. IoCancelIrp would like to simply complete the IRP it’s given with STATUS_CANCELLED, but there’s a hitch: IoCancelIrp doesn’t know where you have salted away pointers to the IRP, and it doesn’t know for sure whether you’re currently processing the IRP. So it relies on a cancel routine you provide to do most of the work of cancelling an IRP. It turns out that a call to IoCancelIrp is more of a suggestion than a demand. It would be nice if every IRP that somebody tried to cancel really got completed with STATUS_CANCELLED. But it’s OK if a driver wants to go ahead and finish the IRP normally if that can be done relatively quickly. You should provide a way to cancel I/O requests that might spend significant time waiting in a queue between a dispatch routine and a StartIo routine. How long is significant is a matter for your own sound judgment; my advice is to err on the side of providing for cancellation because it’s not that hard to do and makes your driver fit better into the operating system. If It Weren’t for Multitasking… An intricate synchronization problem is associated with cancelling IRPs. Before I explain the problem and the solution, I want to describe the way cancellation would work in a world where there was no multitasking and no concern with multiprocessor computers. In that utopia, several pieces of the I/O Manager would fit together with your StartIo routine and with a cancel routine you’d provide, as follows: When you queue an IRP, you set the CancelRoutine pointer in the IRP to the address of your cancel routine. When you dequeue the IRP, you set CancelRoutine to NULL. IoCancelIrp unconditionally sets the Cancel flag in the IRP. Then it checks to see whether the CancelRoutine pointer in the IRP is NULL. While the IRP is in your queue, CancelRoutine will be non-NULL. In this case, IoCancelIrp calls your cancel routine. Your cancel routine removes the IRP from the queue where it currently resides and completes the IRP with STATUS_CANCELLED. Once you dequeue the IRP, IoCancelIrp finds the CancelRoutine pointer set to NULL, so it doesn’t call your cancel routine. You process the IRP to completion with reasonable promptness (a concept that calls for engineering judgment), and it doesn’t matter to anyone that you didn’t actually cancel the IRP. Synchronizing Cancellation Unfortunately for us as programmers, we write code for a multiprocessing, multitasking environment in which effects can sometimes appear to precede causes. There are many possible race conditions between the queue insertion, queue removal, and cancel routines in the naive scenario I just described. For example, what would happen if IoCancelIrp called your cancel routine to cancel an IRP that happened to be at the head of your queue? If you were simultaneously removing an IRP from the queue on another CPU, you can see that your cancel routine would probably conflict with your queue removal logic. But this is just the simplest of the possible races. In earlier times, driver programmers dealt with the cancel races by using a global spin lock―the cancel spin lock. Because you shouldn’t use this spin lock for synchronization in your own driver, I’ve explained it briefly in the sidebar. Read the sidebar for its historical perspective, but don’t plan to use this lock. The Global Cancel Spin Lock The original Microsoft scheme for synchronizing IRP cancellation revolved around a global cancel spin lock. Routines named IoAcquireCancelSpinLock and IoReleaseCancelSpinLock acquire and release this lock. The Microsoft queuing routines IoStartPacket and IoStartNextPacket acquire and release the lock to guard their access to the cancel fields in an IRP and to the CurrentIrp field of the device object. IoCancelIrp acquires the lock before calling your cancel routine but doesn’t release the lock. Your cancel routine runs briefly under the protection of the lock and must call IoReleaseCancelSpinLock before returning. In this scheme, your own StartIo routine must also acquire and release the cancel spin lock to safely test the Cancel flag in the IRP and to reset the CancelRoutine pointer to NULL. Hardly anyone was able to craft queuing and cancel logic that approached being bulletproof using this original scheme. Even the best algorithms actually have a residual flaw arising from a coincidence in IRP pointer values. In addition, the fact that every driver in the system needed to use a single spin lock two or three times in the normal execution path created a measurable performance problem. Consequently, Microsoft now recommends that drivers either use the cancel-safe queue routines or else copy someone else’s proven queue logic. Neither Microsoft nor I would recommend that you try to design your own queue logic with cancellation because getting it right is very hard. Nowadays, we handle the cancel races in one of two ways. We can implement our own IRP queue (or, more probably, cut and paste someone else’s). Or, in certain kinds of drivers, we can use the IoCsqXxx family of functions. You don’t need to understand how the IoCsqXxx functions handle IRP cancellation because Microsoft intends these functions to be a black box. I’ll discuss in detail how my own DEVQUEUE handles cancellation, but I first need to tell you a bit more about the internal workings of IoCancelIrp. Some Details of IRP Cancellation Here is a sketch of IoCancelIrp. You need to know this to correctly write IRP-handling code. (This isn’t a copy of the Windows XP source code―it’s an abridged excerpt.) BOOLEAN IoCancelIrp(PIRP Irp) { IoAcquireCancelSpinLock(&Irp->CancelIrql); Irp->Cancel = TRUE; PDRIVER_CANCEL CancelRoutine = IoSetCancelRoutine(Irp, NULL); if (CancelRoutine) { PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); (*CancelRoutine)(stack->DeviceObject, Irp); return TRUE; } else { IoReleaseCancelSpinLock(Irp->CancelIrql); return FALSE; } } IoCancelIrp first acquires the global cancel spin lock. As you know if you read the sidebar earlier, lots of old drivers contend for the use of this lock in their normal IRP-handling path. New drivers hold this lock only briefly while handling the cancellation of an IRP. Setting the Cancel flag to TRUE alerts any interested party that IoCancelIrp has been called for this IRP. IoSetCancelRoutine performs an interlocked exchange to simultaneously retrieve the existing CancelRoutine pointer and set the field to NULL in one atomic operation. IoCancelIrp calls the cancel routine, if there is one, without first releasing the global cancel spin lock. The cancel routine must release the lock! Note also that the device object argument to the cancel routine comes from the current stack location, where IoCallDriver is supposed to have left it. If there is no cancel routine, IoCancelIrp itself releases the global cancel spin lock. Good idea, huh? How the DEVQUEUE Handles Cancellation As I promised, I’ll now show you how the major DEVQUEUE routines work so you can see how they safely cope with IRP cancellation. DEVQUEUE Internals―Initialization The DEVQUEUE object has this declaration in my DEVQUEUE.H and GENERIC.H header files: typedef struct _DEVQUEUE { LIST_ENTRY head; KSPIN_LOCK lock; PDRIVER_START StartIo; LONG stallcount; PIRP CurrentIrp; KEVENT evStop; NTSTATUS abortstatus; } DEVQUEUE, *PDEVQUEUE; InitializeQueue initializes one of these objects like this: VOID NTAPI InitializeQueue(PDEVQUEUE pdq, PDRIVER_STARTIO StartIo) { InitializeListHead(&pdq->head); KeInitializeSpinLock(&pdq->lock); pdq->StartIo = StartIo; pdq->stallcount = 1; pdq->CurrentIrp = NULL; KeInitializeEvent(&pdq->evStop, NotificationEvent, FALSE); pdq->abortstatus = (NTSTATUS) 0; } We use an ordinary (noninterlocked) doubly-linked list to queue IRPs. We don’t need to use an interlocked list because we’ll always access it within the protection of our own spin lock. This spin lock guards access to the queue and other fields in the DEVQUEUE structure. It also takes the place of the global cancel spin lock for guarding nearly all of the cancellation process, thereby improving system performance. Each queue has its own associated StartIo function that we call automatically in the appropriate places. The stall counter indicates how many times somebody has requested that IRP delivery to StartIo be stalled. Initializing the counter to 1 means that the IRP_MN_START_DEVICE handler must call RestartRequests to release an IRP. I’ll discuss this issue more fully in Chapter 6. The CurrentIrp field records the IRP most recently sent to the StartIo routine. Initializing this field to NULL indicates that the device is initially idle. We use this event when necessary to block WaitForCurrentIrp, one of the DEVQUEUE routines involved in handling PnP requests. We’ll set the event inside StartNextPacket, which should always be called when the current IRP completes. We reject incoming IRPs in two situations. The first situation occurs after we irrevocably commit to removing the device, when we must start causing new IRPs to fail with STATUS_DELETE_PENDING. The second situation occurs during a period of low power, when, depending on the type of device we’re managing, we might choose to cause new IRPs to fail with the STATUS_DEVICE_POWERED_OFF code. The abortstatus field records the status code we should use in rejecting IRPs in these situations. In the steady state after all PnP initialization finishes, each DEVQUEUE will have a zero stallcount and abortstatus. DEVQUEUE Internals―Queuing and Cancellation Here is the complete implementation of the three DEVQUEUE routines whose usage I just showed you. I cut and pasted the source code directly from GENERIC.SYS and did some minor formatting for the sake of readability on the printed page. I also removed some power management code from StartNextPacket because it would just confuse this presentation. VOID StartPacket(PDEVQUEUE pdq, PDEVICE_OBJECT fdo, PIRP Irp, PDRIVER_CANCEL cancel) { KIRQL oldirql; KeAcquireSpinLock(&pdq->lock, &oldirql); NTSTATUS abortstatus = pdq->abortstatus; if (abortstatus) { KeReleaseSpinLock(&pdq->lock, oldirql); Irp->IoStatus.Status = abortstatus; IoCompleteRequest(Irp, IO_NO_INCREMENT); } else if (pdq->CurrentIrp ││ pdq->stallcount) { IoSetCancelRoutine(Irp, cancel); if (Irp->Cancel && IoSetCancelRoutine(Irp, NULL)) { KeReleaseSpinLock(&pdq->lock, oldirql); Irp->IoStatus.Status = STATUS_CANCELLED; IoCompleteRequest(Irp, IO_NO_INCREMENT); } else { InsertTailList(&pdq->head, &Irp->Tail.Overlay.ListEntry); KeReleaseSpinLock(&pdq->lock, oldirql); } } else { pdq->CurrentIrp = Irp; KeReleaseSpinLockFromDpcLevel(&pdq->lock); (*pdq->StartIo)(fdo, Irp); KeLowerIrql(oldirql); } } VOID StartNextPacket(PDEVQUEUE pdq, PDEVICE_OBJECT fdo) { KIRQL oldirql; KeAcquireSpinLock(&pdq->lock, &oldirql); pdq->CurrentIrp = NULL; while (!pdq->stallcount && !pdq->abortstatus && !IsListEmpty(&pdq->head)) { PLIST_ENTRY next = RemoveHeadList(&pdq->head); PIRP Irp = CONTAINING_RECORD(next, IRP, Tail.Overlay.ListEntry); if (!IoSetCancelRoutine(Irp, NULL)) { InitializeListHead(&Irp->Tail.Overlay.ListEntry); continue; } pdq->CurrentIrp = Irp; KeReleaseSpinLockFromDpcLevel(&pdq->lock); (*pdq->StartIo)(fdo, Irp); KeLowerIrql(oldirql); } KeReleaseSpinLock(&pdq->lock, oldirql); } VOID CancelRequest(PDEVQUEUE pdq, PIRP Irp) { KIRQL oldirql = Irp->CancelIrql; IoReleaseCancelSpinLock(DISPATCH_LEVEL); KeAcquireSpinLockAtDpcLevel(&pdq->lock); RemoveEntryList(&Irp->Tail.Overlay.ListEntry); KeReleaseSpinLock(&pdq->lock, oldirql); Irp->IoStatus.Status = STATUS_CANCELLED; IoCompleteRequest(Irp, IO_NO_INCREMENT); } Now I’ll explain in detail how these functions work together to provide cancel-safe queuing. I’ll do this by describing a series of scenarios that involve all of the code paths. 1. The Normal Case for StartPacket The normal case for StartPacket occurs in the steady state when an IRP, which we assume has not been cancelled, arrives after all PnP processing has taken place and at a time when the device was fully powered. In this situation, stallcount and abortstatus will both be 0. The path through StartPacket depends on whether the device is busy, as follows: We first acquire the spin lock associated with the queue. (See point 1.) Nearly all the DEVQUEUE routines acquire this lock (see points 8 and 15), so we can be sure that no other code on any other CPU can do anything to the queue that would invalidate the decisions we’re about to make. If the device is busy, the if statement at point 3 will find CurrentIrp not set to NULL. The if statement at point 5 will also fail (I’ll explain later exactly why), so we’ll get to point 6 to put the IRP in the queue. Releasing the spin lock is the last thing we do in this code path. If the device is idle, the if statement at point 3 will find CurrentIrp set to NULL. I’ve already assumed that stallcount is 0, so we’ll get to point 7 in order to process this IRP. Note how we manage to call StartIo at DISPATCH_LEVEL after releasing the spin lock. 2. The Normal Case for StartNextPacket The normal case for StartNextPacket is similar to that for StartPacket. The stallcount and abortstatus members are 0, and the IRP at the head of the queue hasn’t been cancelled. StartNextPacket executes these steps: Acquires the queue spin lock (point 8). This protects the queue from simultaneous access by other CPUs trying to execute StartPacket or CancelRequest. No other CPU can be trying to execute StartNextPacket because the only caller of StartNextPacket is someone who has just finished processing some other IRP. We allow only one IRP to be active at a time, so there should never be more than one such entity. If the list is empty, we just release the spin lock and return. If StartPacket had been waiting for the lock, it will now find that the device isn’t busy and will call StartIo. If the list isn’t empty, the if test at point 10 will succeed, and we’ll enter a loop looking for the next uncancelled IRP. The first step in the loop (point 11) is to remove the next IRP from the list. Note that RemoveHeadList returns the address of a LIST_ENTRY built into the IRP. We use CONTAINING_RECORD to get the address of the IRP. IoSetCancelRoutine (point 12) will return the non-NULL address of the cancel routine originally supplied to StartPacket. This is because nothing, least of all IoCancelIrp, has changed this pointer since StartPacket set it. Consequently, we’ll get to point 13, where we’ll send this IRP to the StartIo routine at DISPATCH_LEVEL. 3. IRP Cancelled Prior to StartPacket; Device Idle Suppose StartPacket receives an IRP that was cancelled some time ago. At the time IoCancelIrp executed, there wouldn’t have been a cancel routine for the IRP. (If there had been, it would have belonged to a driver higher up the stack than us. That other driver would have completed the IRP instead of sending it down to us.) All that IoCancelIrp would have done, therefore, is to set the Cancel flag in the IRP. If the device is idle, the if test at point 3 fails and we once again go directly to point 7, where we send the IRP to StartIo. In effect, we’re going to ignore the Cancel flag. This is fine so long as we process the IRP “relatively quickly,” which is an engineering judgment. If we won’t process the IRP with reasonable dispatch, StartIo and the downstream logic for handling the IRP should have code to detect the Cancel flag and to complete the IRP early. 4. IRP Cancelled During StartPacket; Device Idle In this scenario, someone calls IoCancelIrp while StartPacket is running. Just as in scenario 3, IoCancelIrp will set the Cancel flag and return. We’ll ignore the flag and send the IRP to StartIo. 5. IRP Cancelled Prior to StartPacket; Device Busy The initial conditions are the same as in scenario 3 except that now the device is busy and the if test at point 3 succeeds. We’ll set the cancel routine (point 4) and then test the Cancel flag (point 5). Because the Cancel flag is TRUE, we’ll go on to call IoSetCancelRoutine a second time. The function will return the non-NULL address we just installed, whereupon we’ll complete the IRP with STATUS_CANCELLED. 6. IRP Cancelled During StartPacket; Device Busy This is the first sticky wicket we encounter in the analysis. Assume the same initial conditions as scenario 3, but now the device is busy and someone calls IoCancelIrp at about the same time StartPacket is running. There are several possible situations now: Suppose we test the Cancel flag (point 5) before IoCancelIrp manages to set that flag. Since we find the flag set to FALSE, we go to point 6 and queue the IRP. What happens next depends on how IoCancelIrp, CancelRequest, and StartNextPacket interact. StartPacket is in a not-my-problem field at this point, however, and needn’t worry about this IRP any more. Suppose we test the Cancel flag (point 5) after IoCancelIrp sets the flag. We have already set the cancel pointer (point 4). What happens next depends on whether IoCancelIrp or we are first to execute the IoSetCancelRoutine call that changes the cancel pointer back to NULL. Recall that IoSetCancelRoutine is an atomic operation based on an InterlockedExchangePointer. If we execute our call first, we get back a non-NULL value and complete the IRP. IoCancelIrp gets back NULL and therefore doesn’t call any cancel routine. On the other hand, if IoCancelIrp executes its IoSetCancelRoutine first, we will get back NULL from our call. We’ll go on to queue the IRP (point 6) and to enter that not-my-problem field I just referred to. IoCancelIrp will call our cancel routine, which will block (point 15) until we release the queue spin lock. Our cancel routine will eventually complete the IRP. 7. Normal IRP Cancellation IRPs don’t get cancelled very often, so I’m not sure it’s really right to use the word normal in this context. But if there were a normal scenario for IRP cancellation, this would be it: someone calls IoCancelIrp to cancel an IRP that’s in our queue, but the cancel process runs to conclusion before StartNextPacket can possibly try to reach it. The potential race between StartNextPacket and CancelRequest therefore can’t materialize. Events will unfold this way: IoCancelIrp acquires the global cancel spin, sets the Cancel flag, and executes IoSetCancelRoutine to simultaneously retrieve the address of our cancel routine and set the cancel pointer in the IRP to NULL. (Refer to the earlier sketch of IoCancelIrp.) IoCancelIrp calls our cancel routine without releasing the lock. The cancel routine locates the correct DEVQUEUE and calls CancelRequest. CancelRequest immediately releases the global cancel spin lock (point 14). CancelRequest acquires the queue spin lock (point 15). Past this point, there can be no more races with other DEVQUEUE routines. CancelRequest removes the IRP from the queue (point 16) and then releases the spin lock. If StartNextPacket were to run now, it wouldn’t find this IRP on the queue. CancelRequest completes the IRP with STATUS_CANCELLED (point 17). 8. Pathological IRP Cancellation The most difficult IRP cancellation scenario to handle occurs when IoCancelIrp tries to cancel the IRP at the head of our queue while StartNextPacket is active. At point 12, StartNextPacket will nullify the cancel pointer. If the return value from IoSetCancelRoutine is not NULL, we’ve beaten IoCancelIrp to the punch and can go on to process the IRP (point 13). If the return value from IoSetCancelRoutine is NULL, however, it means that IoCancelIrp has gotten there first. CancelRequest is probably waiting right now on another CPU for us to release the queue spin lock, whereupon it will dequeue the IRP and complete it. The trouble is, we’ve already removed the IRP from the queue. I’m a bit proud of the trick I devised for coping with the situation: we simply initialize the linking field of the IRP as if it were the anchor of a list! The call to RemoveEntryList at point 16 in CancelRequest will perform several motions with no net result to “remove” the IRP from the degenerate list it now inhabits. 9. Things That Can’t Happen or Won’t Matter The preceding list exhausts the possibilities for conflict between these DEVQUEUE routines and IoCancelIrp. (There is still a race between IRP_MJ_CLEANUP and IRP cancellation, but I’ll discuss that a bit later in this chapter.) Here is a list of things that might be causing you needless worry: Could CancelRoutine be non-NULL when StartPacket gets control? It better not be, because a driver is supposed to remove its cancel routine from an IRP before sending the IRP to another driver. StartPacket contains an ASSERT to this effect. If you engage the Driver Verifier for your driver, it will verify that you nullify the cancel routine pointer in IRPs that you pass down the stack, but it will not verify that the drivers above you have done this for IRPs they pass to you. Could the cancel argument to StartPacket be NULL? It better not be: you might have noticed that much of the cancel logic I described hinges on whether the IRP’s CancelRoutine pointer is NULL. StartPacket contains an ASSERT to test this assumption. Could someone call IoCancelIrp twice? The thing to think about is that the Cancel flag might be set in an IRP because of some number of primeval calls to IoCancelIrp and that someone might call IoCancelIrp one more time (getting a little impatient, are we?) while StartPacket is active. This wouldn’t matter because our first test of the Cancel flag occurs after we install our cancel pointer. We would find the flag set to TRUE in this hypothetical situation and would therefore execute the second call to IoSetCancelRoutine. Either IoCancelIrp or we win the race to reset the cancel pointer to NULL, and whoever wins ends up completing the IRP. The residue from the primeval calls is simply irrelevant. Cancelling IRPs You Create or Handle Sometimes you’ll want to cancel an IRP that you’ve created or passed to another driver. Great care is required to avoid an obscure, low-probability problem. Just for the sake of illustration, suppose you want to impose an overall 5-second timeout on a synchronous I/O operation. If the time period elapses, you want to cancel the operation. Here is some naive code that, you might suppose, would execute this plan: SomeFunction() { KEVENT event; IO_STATUS_BLOCK iosb; KeInitializeEvent(&event, ...); PIRP Irp = IoBuildSynchronousFsdRequest(..., &event, &iosb); NTSTATUS status = IoCallDriver(DeviceObject, Irp); if (status == STATUS_PENDING) { LARGE_INTEGER timeout; timeout.QuadPart = -5 * 10000000; if (KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout) == STATUS_TIMEOUT) { IoCancelIrp(Irp); // <== don\'t do this! KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); } } } The first call (A) to KeWaitForSingleObject waits until one of two things happens. First, someone might complete the IRP, and the I/O Manager’s cleanup code will then run and signal event. Alternatively, the timeout might expire before anyone completes the IRP. In this case, KeWaitForSingleObject will return STATUS_TIMEOUT. The IRP should now be completed quite soon in one of two paths. The first completion path is taken when whoever was processing the IRP was really just about done when the timeout happened and has, therefore, already called (or will shortly call) IoCompleteRequest. The other completion path is through the cancel routine that, we must assume, the lower driver has installed. That cancel routine should complete the IRP. Recall that we have to trust other kernel-mode components to do their jobs, so we have to rely on whomever we sent the IRP to complete it soon. Whichever path is taken, the I/O Manager’s completion logic will set event and store the IRP’s ending status in iosb. The second call (B) to KeWaitForSingleObject makes sure that the event and iosb objects don’t pass out of scope too soon. Without that second call, we might return from this function, thereby effectively deleting event and iosb. The I/O Manager might then end up walking on memory that belongs to some other subroutine. The problem with the preceding code is truly minuscule. Imagine that someone manages to call IoCompleteRequest for this IRP right around the same time we decide to cancel it by calling IoCancelIrp. Maybe the operation finishes shortly after the 5┸\second timeout terminates the first KeWaitForSingleObject, for example. IoCompleteRequest initiates a process that finishes with a call to IoFreeIrp. If the call to IoFreeIrp were to happen before IoCancelIrp was done mucking about with the IRP, you can see that IoCancelIrp could inadvertently corrupt memory when it touched the CancelIrql, Cancel, and CancelRoutine fields of the IRP. It’s also possible, depending on the exact sequence of events, for IoCancelIrp to call a cancel routine, just before someone clears the CancelRoutine pointer in preparation for completing the IRP, and for the cancel routine to be in a race with the completion process. It’s very unlikely that the scenario I just described will happen. But, as someone (James Thurber?) once said in connection with the chances of being eaten by a tiger on Main Street (one in a million, as I recall), “Once is enough.” This kind of bug is almost impossible to find, so you want to prevent it if you can. I’ll show you two ways of cancelling your own IRPs. One way is appropriate for synchronous IRPs, the other for asynchronous IRPs. Don’t Do This… A once common but now deprecated technique for avoiding the tiger-on-main-street bug described in the text relies on the fact that, in earlier versions of Windows, the call to IoFreeIrp happened in the context of an APC in the thread that originates the IRP. You could make sure you were in that same thread, raise IRQL to APC_LEVEL, check whether the IRP had been completed yet, and (if not) call IoCancelIrp. You could be sure of blocking the APC and the problematic call to IoFreeIrp. You shouldn’t rely on future releases of Windows always using an APC to perform the cleanup for a synchronous IRP. Consequently, you shouldn’t rely on boosting IRQL to APC_LEVEL as a way to avoid a race between IoCancelIrp and IoFreeIrp. Cancelling Your Own Synchronous IRP Refer to the example in the preceding section, which illustrates a function that creates a synchronous IRP, sends it to another driver, and then wants to wait no longer than 5 seconds for the IRP to complete. The key thing we need to accomplish in a solution to the race between IoFreeIrp and IoCancelIrp is to prevent the call to IoFreeIrp from happening until after any possible call to IoCancelIrp. We do this by means of a completion routine that returns STATUS_MORE_PROCESSING_REQUIRED, as follows: SomeFunction() { KEVENT event; IO_STATUS_BLOCK iosb; KeInitializeEvent(&event, ...); PIRP Irp = IoBuildSynchronousFsdRequest(..., &event, &iosb); IoSetCompletionRoutine(Irp, OnComplete, (PVOID) &event, TRUE, TRUE, TRUE); NTSTATUS status = IoCallDriver(...); if (status == STATUS_PENDING) { LARGE_INTEGER timeout; timeout.QuadPart = -5 * 10000000; if (KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout) == STATUS_TIMEOUT) { IoCancelIrp(Irp); // <== okay in this context KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); } } IoCompleteRequest(Irp, IO_NO_INCREMENT); } NTSTATUS OnComplete(PDEVICE_OBJECT junk, PIRP Irp, PVOID pev) { if (Irp->PendingReturned) KeSetEvent((PKEVENT) pev, IO_NO_INCREMENT, FALSE); return STATUS_MORE_PROCESSING_REQUIRED; } The new code in boldface prevents the race. Suppose IoCallDriver returns STATUS_PENDING. In a normal case, the operation will complete normally, and a lower-level driver will call IoCompleteRequest. Our completion routine gains control and signals the event on which our mainline is waiting. Because the completion routine returns STATUS_MORE_PROCESSING_REQUIRED, IoCompleteRequest will then stop working on this IRP. We eventually regain control in our SomeFunction and notice that our wait (the one labeled A) terminated normally. The IRP hasn’t yet been cleaned up, though, so we need to call IoCompleteRequesta second time to trigger the normal cleanup mechanism. Now suppose we decide we want to cancel the IRP and that Thurber’s tiger is loose so we have to worry about a call to IoFreeIrp releasing the IRP out from under us. Our first wait (labeled A) finishes with STATUS_TIMEOUT, so we perform a second wait (labeled B). Our completion routine sets the event on which we’re waiting. It will also prevent the cleanup mechanism from running by returning STATUS_MORE_PROCESSING_REQUIRED. IoCancelIrp can stomp away to its heart’s content on our hapless IRP without causing any harm. The IRP can’t be released until the second call to IoCompleteRequest from our mainline, and that can’t happen until IoCancelIrp has safely returned. Notice that the completion routine in this example calls KeSetEvent only when the IRP’s PendingReturned flag is set to indicate that the lower driver’s dispatch routine returned STATUS_PENDING. Making this step conditional is an optimization that avoids the potentially expensive step of setting the event when SomeFunction won’t be waiting on the event in the first place. I want to mention one last fine point in connection with the preceding code. The call to IoCompleteRequest at the very end of the subroutine will trigger a process that includes setting event and iosb so long as the IRP originally completed with a success status. In the first edition, I had an additional call to KeWaitForSingleObject at this point to make sure that event and iosb could not pass out of scope before the I/O Manager was done touching them. A reviewer pointed out that the routine that references event and iosb will already have run by the time IoCompleteRequest returns; consequently, the additional wait is not needed. Cancelling Your Own Asynchronous IRP To safely cancel an IRP that you’ve created with IoAllocateIrp or IoBuildAsynchronousFsdRequest, you can follow this general plan. First define a couple of extra fields in your device extension structure: typedef struct _DEVICE_EXTENSION { PIRP TheIrp; ULONG CancelFlag; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; Initialize these fields just before you call IoCallDriver to launch the IRP: pdx->TheIrp = IRP; pdx->CancelFlag = 0; IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE) CompletionRoutine, (PVOID) pdx, TRUE, TRUE, TRUE); IoCallDriver(..., Irp); If you decide later on that you want to cancel this IRP, do something like the following: VOID CancelTheIrp(PDEVICE_EXENSION pdx) { PIRP Irp = (PIRP) InterlockedExchangePointer((PVOID*)&pdx->TheIrp, NULL); if (Irp) { IoCancelIrp(Irp); if (InterlockedExchange(&pdx->CancelFlag, 1) IoFreeIrp(Irp); } } This function dovetails with the completion routine you install for the IRP: NTSTATUS CompletionRoutine(PDEVICE_OBJECT junk, PIRP Irp, PDEVICE_EXTENSION pdx) { if (InterlockedExchangePointer(&pdx->TheIrp, NULL) ││ InterlockedExchange(&pdx->CancelFlag, 1)) IoFreeIrp(Irp); return STATUS_MORE_PROCESSING_REQUIRED; } The basic idea underlying this deceptively simple code is that whichever routine sees the IRP last (either CompletionRoutine or CancelTheIrp) will make the requisite call to IoFreeIrp, at point 3 or 6. Here’s how it works: The normal case occurs when you don’t ever try to cancel the IRP. Whoever you sent the IRP to eventually completes it, and your completion routine gets control. The first InterlockedExchangePointer (point 4) returns the non-NULL address of the IRP. Since this is not 0, the compiler short-circuits the evaluation of the Boolean expression and executes the call to IoFreeIrp. Any subsequent call to CancelTheIrp will find the IRP pointer set to NULL at point 1 and won’t do anything else. Another easy case to analyze occurs when CancelTheIrp is called long before anyone gets around to completing this IRP, which means that we don’t have any actual race. At point 1, we nullify the TheIrp pointer. Because the IRP pointer was previously not NULL, we go ahead and call IoCancelIrp. In this situation, our call to IoCancelIrp will cause somebody to complete the IRP reasonably soon, and our completion routine runs. It sees TheIrp as NULL and goes on to evaluate the second half of the Boolean expression. Whoever executes the InterlockedExchange on CancelFlag first will get back 0 and skip calling IoFreeIrp. Whoever executes it second will get back 1 and will call IoFreeIrp. Now for the case we were worried about: suppose someone is completing the IRP right about the time CancelTheIrp wants to cancel it. The worst that can happen is that our completion routine runs before we manage to call IoCancelIrp. The completion routine sees TheIrp as NULL and therefore exchanges CancelFlag with 1. Just as in the previous case, the routine will get 0 as the return value and skip the IoFreeIrp call. IoCancelIrp can safely operate on the IRP. (It will presumably just return without calling a cancel routine because whoever completed this IRP will undoubtedly have set the CancelRoutine pointer to NULL first.) The appealing thing about the technique I just showed you is its elegance: we rely solely on interlocked operations and therefore don’t need any potentially expensive synchronization primitives. Cancelling Someone Else’s IRP To round out our discussion of IRP cancellation, suppose someone sends you an IRP that you then forward to another driver. Situations might arise where you’d like to cancel that IRP. For example, perhaps you need that IRP out of the way so you can proceed with a power-down operation. Or perhaps you’re waiting synchronously for the IRP to finish and you’d like to impose a timeout as in the first example of this section. To avoid the IoCancelIrp/IoFreeIrp race, you need to have your own completion routine in place. The details of the coding then depend on whether you’re waiting for the IRP. Canceling Someone Else’s IRP on Which You’re Waiting Suppose your dispatch function passes down an IRP and waits synchronously for it to complete. (See usage scenario 7 at the end of this chapter for the cookbook version.) Use code like this to cancel the IRP if it doesn’t finish quickly enough to suit you: NTSTATUS DispatchSomething(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; KEVENT event; KeInitializeEvent(&event, NotificationEvent, FALSE); IoSetCompletionRoutine(Irp, OnComplete, (PVOID) &event, TRUE, TRUE, TRUE); NTSTATUS status = IoCallDriver(...); if (status == STATUS_PENDING) { LARGE_INTEGER timeout; timeout.QuadPart = -5 * 10000000; if (KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout) == STATUS_TIMEOUT) { IoCancelIrp(Irp); KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); } } status = Irp->IoStatus.Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; } NTSTATUS OnComplete(PDEVICE_OBJECT junk, PIRP Irp, PVOID pev) { if (Irp->PendingReturned) KeSetEvent((PKEVENT) pev, IO_NO_INCREMENT, FALSE); return STATUS_MORE_PROCESSING_REQUIRED; } This code is almost the same as what I showed earlier for canceling your own synchronous IRP. The only difference is that this example involves a dispatch routine, which must return a status code. As in the earlier example, we install our own completion routine to prevent the completion process from running to its ultimate conclusion before we get past the point where we might call IoCancelIrp. You might notice that I didn’t say anything about whether the IRP itself was synchronous or asynchronous. This is because the difference between the two types of IRP only matters to the driver that creates them in the first place. File system drivers must make distinctions between synchronous and asynchronous IRPs with respect to how they call the system cache manager, but device drivers don’t typically have this complication. What matters to a lower-level driver is whether it’s appropriate to block a thread in order to handle an IRP synchronously, and that depends on the current IRQL and whether you’re in an arbitrary or a nonarbitrary thread. Canceling Someone Else’s IRP on Which You’re Not Waiting Suppose you’ve forwarded somebody else’s IRP to another driver, but you weren’t planning to wait for it to complete. For whatever reason, you decide later on that you’d like to cancel that IRP. typedef struct _DEVICE_EXTENSION { PIRP TheIrp; ULONG CancelFlag; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; NTSTATUS DispatchSomething(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE) OnComplete, (PVOID) pdx, TRUE, TRUE, TRUE); pdx->CancelFlag = 0; pdx->TheIrp = Irp; IoMarkIrpPending(Irp); IoCallDriver(pdx->LowerDeviceObject, Irp); return STATUS_PENDING; } VOID CancelTheIrp(PDEVICE_EXTENSION pdx) { PIRP Irp = (PIRP) InterlockedExchangePointer( (PVOID*) &pdx->TheIrp, NULL); if (Irp) { IoCancelIrp(Irp); if (InterlockedExchange(&pdx->CancelFlag, 1)) IoCompleteRequest(Irp, IO_NO_INCREMENT); } } NTSTATUS OnComplete(PDEVICE_OBJECT fdo, PIRP Irp, PDEVICE_EXTENSION pdx) { if (InterlockedExchangePointer((PVOID*) &pdx->TheIrp, NULL) ││ InterlockedExchange(&pdx->CancelFlag, 1)) return STATUS_SUCCESS; return STATUS_MORE_PROCESSING_REQUIRED; } This code is similar to the code I showed earlier for cancelling your own asynchronous IRP. Here, however, allowing IoCompleteRequest to finish completing the IRP takes the place of the call to IoFreeIrp we made when we were dealing with our own IRP. If the completion routine is last on the scene, it returns STATUS_SUCCESS to allow IoCompleteRequest to finish completing the IRP. If CancelTheIrp is last on the scene, it calls IoCompleteRequest to resume the completion processing that the completion routine short-circuited by returning STATUS_MORE_PROCESSING_REQUIRED. One extremely subtle point regarding this example is the call to IoMarkIrpPending in the dispatch routine. Ordinarily, it would be safe to just do this step conditionally in the completion routine, but not this time. If we should happen to call CancelTheIrp in the context of some thread other than the one in which the dispatch routine runs, the pending flag is needed so that IoCompleteRequest will schedule an APC to clean up the IRP in the proper thread. The easiest way to make that true is simple―always mark the IRP pending. Handling IRP_MJ_CLEANUP Closely allied to the subject of IRP cancellation is the I/O request with the major function code IRP_MJ_CLEANUP. To explain how you should process this request, I need to give you a little additional background. When applications and other drivers want to access your device, they first open a handle to the device. Applications call CreateFile to do this; drivers call ZwCreateFile. Internally, these functions create a kernel file object and send it to your driver in an IRP_MJ_CREATE request. When the entity that opened the handle is done accessing your driver, it will call another function, such as CloseHandle or ZwClose. Internally, these functions send your driver an IRP_MJ_CLOSE request. Just before sending you the IRP_MJ_CLOSE, however, the I/O Manager sends you an IRP_MJ_CLEANUP so that you can cancel any IRPs that belong to the same file object but that are still sitting in one of your queues. From the perspective of your driver, the one thing all the requests have in common is that the stack location you receive points to the same file object in every instance. Figure 5-10 illustrates your responsibility when you receive IRP_MJ_CLEANUP. You should run through your queues of IRPs, removing those that are tagged as belonging to the same file object. You should complete those IRPs with STATUS_CANCELLED. Figure 5-10. Driver responsibility for IRP_MJ_CLEANUP. File Objects Ordinarily, just one driver (the function driver, in fact) in a device stack implements all three of the following requests: IRP_MJ_CREATE, IRP_MJ_CLOSE, and IRP_MJ_CLEANUP. The I/O Manager creates a file object (a regular kernel object) and passes it in the I/O stack to the dispatch routines for all three of these IRPs. Anybody who sends an IRP to a device should have a pointer to the same file object and should insert that pointer into the I/O stack as well. The driver that handles these three IRPs acts as the owner of the file object in some sense, in that it’s the driver that’s entitled to use the FsContext and FsContext2 fields of the object. So your DispatchCreate routine can put something into one of these context fields for use by other dispatch routines and for eventual cleanup by your DispatchClose routine. It’s easy to get confused about IRP_MJ_CLEANUP. In fact, programmers who have a hard time understanding IRP cancellation sometimes decide (incorrectly) to just ignore this IRP. You need both cancel and cleanup logic in your driver, though: IRP_MJ_CLEANUP means a handle is being closed. You should purge all the IRPs that pertain to that handle. The I/O Manager and other drivers cancel individual IRPs for a variety of reasons that have nothing to do with closing handles. One of the times the I/O Manager cancels IRPs is when a thread terminates. Threads often terminate because their parent process is terminating, and the I/O Manager will also automatically close all handles that are still open when a process terminates. The coincidence between this kind of cancellation and the automatic handle closing contributes to the incorrect idea that a driver can get by with support for just one concept. In this book, I’ll show you two ways of painlessly implementing support for IRP_MJ_CLEANUP, depending on whether you’re using one of my DEVQUEUE objects or one of Microsoft’s cancel-safe queues. Cleanup with a DEVQUEUE If you’ve used a DEVQUEUE to queue IRPs, your IRP_MJ_DISPATCH_CLEANUP routine will be astonishingly simple: NTSTATUS DispatchCleanup(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT fop = stack->FileObject; CleanupRequests(&pdx->dqReadWrite, fop, STATUS_CANCELLED); return CompleteRequest(Irp, STATUS_SUCCESS, 0); } CleanupRequests will remove all IRPs from the queue that belong to the same file object and will complete those IRPs with STATUS_CANCELLED. Note that you complete the IRP_MJ_CLEANUP request itself with STATUS_SUCCESS. CleanupRequests contains a wealth of detail: VOID CleanupRequests(PDEVQUEUE pdq, PFILE_OBJECT fop, NTSTATUS status) { LIST_ENTRY cancellist; InitializeListHead(&cancellist); KIRQL oldirql; KeAcquireSpinLock(&pdq->lock, &oldirql); PLIST_ENTRY first = &pdq->head; PLIST_ENTRY next; for (next = first->Flink; next != first; ) { PIRP Irp = CONTAINING_RECORD(next, IRP, Tail.Overlay.ListEntry); PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); PLIST_ENTRY current = next; next = next->Flink; if (fop && stack->FileObject != fop) continue; if (!IoSetCancelRoutine(Irp, NULL)) continue; RemoveEntryList(current); InsertTailList(&cancellist, current); } KeReleaseSpinLock(&pdq->lock, oldirql); while (!IsListEmpty(&cancellist)) { next = RemoveHeadList(&cancellist); PIRP Irp = CONTAINING_RECORD(next, IRP, Tail.Overlay.ListEntry); Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); } } Our strategy will be to move the IRPs that need to be cancelled into a private queue under protection of the queue’s spin lock. Hence, we initialize the private queue and acquire the spin lock before doing anything else. This loop traverses the entire queue until we return to the list head. Notice the absence of a loop increment step―the third clause in the for statement. I’ll explain in a moment why it’s desirable to have no loop increment. If we’re being called to help out with IRP_MJ_CLEANUP, the fop argument is the address of a file object that’s about to be closed. We’re supposed to isolate the IRPs that pertain to the same file object, which requires us to first find the stack location. If we decide to remove this IRP from the queue, we won’t thereafter have an easy way to find the next IRP in the main queue. We therefore perform the loop increment step here. This especially clever statement comes to us courtesy of Jamie Hanrahan. We need to worry that someone might be trying to cancel the IRP that we’re currently looking at during this iteration. They could get only as far as the point where CancelRequest tries to acquire the spin lock. Before getting that far, however, they necessarily had to execute the statement inside IoCancelIrp that nullifies the cancel routine pointer. If we find that pointer set to NULL when we call IoSetCancelRoutine, therefore, we can be sure that someone really is trying to cancel this IRP. By simply skipping the IRP during this iteration, we allow the cancel routine to complete it later on. Here’s where we take the IRP out of the main queue and put it in the private queue instead. Once we finish moving IRPs into the private queue, we can release our spin lock. Then we cancel all the IRPs we moved. Cleanup with a Cancel-Safe Queue To easily clean up IRPs that you’ve queued by calling IoCsqInsertIrp, simply adopt the convention that the peek context parameter you use with IoCsqRemoveNextIrp, if not NULL, will be the address of a FILE_OBJECT. Your IRP_MJ_CANCEL routine will look like this (compare with the Cancel sample in the DDK): NTSTATUS DispatchCleanup(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT fop = stack->FileObject; PIRP qirp; while ((qirp = IoCsqRemoveNextIrp(&pdx->csq, fop))) CompleteRequest(qirp, STATUS_CANCELLED, 0); return CompleteRequest(Irp, STATUS_SUCCESS, 0); } Implement your PeekNextIrp callback routine this way: PIRP PeekNextIrp(PIO_CSQ csq, PIRP Irp, PVOID PeekContext) { PDEVICE_EXTENSION pdx = GET_DEVICE_EXTENSION(csq); PLIST_ENTRY next = Irp ? Irp->Tail.Overlay.ListEntry.Flink : pdx->IrpQueueAnchor.Flink; while (next != &pdx->IrpQueueAnchor) { PIRP NextIrp = CONTAINING_RECORD(next, IRP, Tail.Overlay.ListEntry); PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(NextIrp); if (!PeekContext ││ (PFILE_OBJECT) PeekContext == stack->FileObject) return NextIrp; next = next->Flink; } return NULL; } 10月7日 PTE/PDE(转载)一 虚拟地址如何转换到物理地址?必须用到PDE,PTE的虚拟地址吗? 按照以前学习的分页机制,一个虚拟地址转换成物理地址的计算过程应该是: 1 处理器通过CR3找到当前页目录所在的物理页地址a 2 计算PDE的物理地址:a+ 虚拟地址高十位左移两位 (因为每个页目录项四个字节),取出该地址处的PDE,PDE的前20位+低12位0是这个虚拟地址对应页表的物理地址b 3 计算PTE的物理地址:b+ 虚拟地址的中间10位左移两位,取出该地址处的PTE,PTE的前20位是这个虚拟地址+低12位0是所对应物理页的地址c 4 计算该虚拟地址对应的物理地址:c + 虚拟地址最低12位 是这样的吗? 实验代码://和老师上课给的代码相同,只是把虚拟地址改成了60010020 int main() { char buf[100] = "Hello world"; VirtualAlloc( (void *)0x60010020, 0x3000, MEM_RESERVE, PAGE_READWRITE ); //保留三页 VirtualAlloc( (void *)0x60010020, 0x2000, MEM_COMMIT, PAGE_READWRITE ); //提交两页 printf( "Accessing memory...\n" ); getchar(); memcpy( (void *)0x60010020, buf, 100 ); printf( "Releasing memory...\n"); getchar(); VirtualFree( (void *)0x60010020, 0, MEM_RELEASE ); printf( "Exiting memory...\n" ); getchar(); return 0; } 1 查寄存器CR3中的值 kd> r cr3 cr3=0e238000
kd> !dd 0e238600 # e238600 0dfc9867 00000000 00000000 00000000 # e238610 00000000 00000000 00000000 00000000 # e238620 00000000 00000000 00000000 00000000 # e238630 00000000 00000000 00000000 00000000 # e238640 00000000 00000000 00000000 00000000 # e238650 00000000 00000000 00000000 00000000 # e238660 00000000 00000000 00000000 00000000 # e238670 00000000 00000000 00000000 00000000 3计算PTE的物理地址:0dfc9000 + 00000040 = 0dfc9040 kd> !dd 0dfc9040 # dfc9040 0dece867 00000080 00000080 00000000 # dfc9050 00000000 00000000 00000000 00000000 # dfc9060 00000000 00000000 00000000 00000000 # dfc9070 00000000 00000000 00000000 00000000 # dfc9080 00000000 00000000 00000000 00000000 # dfc9090 00000000 00000000 00000000 00000000 # dfc90a0 00000000 00000000 00000000 00000000 # dfc90b0 00000000 00000000 00000000 00000000 kd> !db 0dece020 # dece020 48 65 6c 6c 6f 20 77 6f-72 6c 64 00 00 00 00 00 Hello world..... # dece030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ # dece040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ # dece050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ # dece060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ # dece070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ # dece080 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ # dece090 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 二 通过基址c0300000和c0000000计算出的PDE和PTE的虚拟地址有什么用? cpu在转换地址的过程中,并不需要页表,页目录的虚拟地址,那为什么还需要计算PDE,PTE的虚拟地址呢?是因为在分页标志设置后,cpu把指令中的虚拟地址转换成物理地址,程序中只能使用虚拟地址,因此要把页目录和页表映射到虚拟地址空间中,页表被映射到了从0xC0000000到0xC03FFFFF的4M地址空间,页目录被映射到了0xC0300000开始处的4K地址空间 因此PDE的虚拟地址为: c0300000 + 00000600 = c0300600 取出该虚拟地址处的PDE,可以看到和上面用PDE的物理地址取出来的结果相同
kd> dd c0300600
c0300600 0dfc9867 00000000 00000000 00000000
c0300610 00000000 00000000 00000000 00000000
c0300620 00000000 00000000 00000000 00000000
c0300630 00000000 00000000 00000000 00000000
c0300640 00000000 00000000 00000000 00000000
c0300650 00000000 00000000 00000000 00000000
c0300660 00000000 00000000 00000000 00000000
c0300670 00000000 00000000 00000000 00000000
kd> dd c0180040
c0180040 0dece867 00000080 00000080 00000000
c0180050 00000000 00000000 00000000 00000000
c0180060 00000000 00000000 00000000 00000000
c0180070 00000000 00000000 00000000 00000000
c0180080 00000000 00000000 00000000 00000000
c0180090 00000000 00000000 00000000 00000000
c01800a0 00000000 00000000 00000000 00000000 c01800b0 00000000 00000000 00000000 00000000 9月18日 如何对抗硬件断点之一---调试寄存器(转载)1.前言 在我跨入ollydbg的门的时候,就对ollydbg里面的各种断点充满了疑问,以前我总是不明白普通断点,内存断点,硬件断点有什么区别,他们为什么有些时候不能混用,他们的原理是什么,在学习了前辈们的文章以后,终于明白了一些东西。希望这篇文章能让你对硬件断点的原理和使用有一些帮助 2.正文 -------------------------------------------------- i.硬件断点的原理 在寄存器中,有这么一些寄存器,它们用于调试。人们把他们称为调试寄存器,调试寄存器一共有8个名字分别从Dr0-Dr7。所以我们也把调试寄存器简单的称为Drx。 对于Dr0-Dr3的四个调试寄存器,他们的作用是存放中断的地址,例如:401000 对于Dr4,Dr5这两个寄存器我们一般不使用他们,保留 对于Dr6,Dr7这两个寄存器的作用是用来记录你在Dr0-Dr3中下断的地址的属性,比如:对这个401000是硬件读还是写,或者是执行;是对字节还是对字,或者是双字。 好了,从这里你可能明白一些东西。 1. 为什么在OD里面只能下4个硬件断点? 2. 为什么下硬件断点有byte,word,dword只分? 3. 为什么下硬件断点有读,写,执行只分? ii.关于F4,F8,F7,F2的区别 在ollydbug的help里面只是提到如何使用F7和F8的使用,并没说明他们的实现原理 现在我们来做一个实验 实验一(F4的原理) 1.随便找一个程序,载入OD,构造一个死循环 就象这样: 00400154 > 90 nop //EP停在这里 00400155 90 nop 00400156 90 nop 00400157 90 nop 00400158 ^ EB FA jmp short 天2国际. //构造一个死循环 0040015A 61 popad 0040015B 94 xchg eax,esp 2.对0040015A这一行按下F4,由于死循环,程序一直运行 3.调试器的窗口里,右键--查看调试寄存器 结果在Drx里面显示: DR0 0040015A //地址 DR1 00000000 DR2 00000000 DR3 00000000 DR6 FFFF0FF0 //断点属性 DR7 00000401 实验二(F8原理) 1.随便找一个程序,载入OD,构造一个子程序的死循环 就像这样 00400154 t> E8 0100D03F call 4010015A //EP,停在这里 00400159 90 nop 0040015A 90 nop 0040015B 90 nop 0040015C 90 nop //对这里下F2断点 0040015D C3 retn // 返回 2.按下F8,由于INT3断点,程序中断在0040015C 3.调试器的窗口里,右键--查看调试寄存器 结果在Drx里面显示: DR0 00400159 //call的返回地址 DR1 00000000 DR2 00000000 DR3 00000000 DR6 FFFF4FF1 //断点属性 DR7 00000401 实验三(F7原理) 1.随便找一个程序,载入OD 2.双击调试器的窗口里的T标志,将TF从原来的0变成1 3.F9运行 结果程序断在了下面的一行 实验四(F2的原理) 1.用98的notepad吧,载入OD,构造一个死循环 004010CC N> 90 nop //EP,挺在这里 004010CD 90 nop 004010CE ^ EB FC jmp short NOTEPAD. //死循环 004010D0 90 nop //在这里按下F2,普通断点 004010D1 90 nop 2.按下F9,由于死循环,程序一直运行着 3.使用LordPE(不要用ollydump)将这个程序dump下来 4.重新载入OD 来看看成什么样子了 004010CC d> $ 90 nop 004010CD . 90 nop 004010CE .^ EB FC jmp short dumped. 004010D0 CC int3 //这里变成了CC了 004010D1 90 nop -------------------------------------------------- 3.总结 从实验一和实验二我们能清楚的看到,F4是直接将该行的地址放入drx里面,F8是将下一行的地址放入到drx里面,他们都使用了调试寄存器。从实验三中我们知道对于F7来说很可能使用的是将TF置一的办法,也就是说当我们按下F7的时候OD把TF置一。对于F2来说他是将,第一个字节悄悄的修改成了CC,虽然并没有显示给我看到这个是一个CC,当我们按下F2的时候,OD还没有运行,只是把这个表示记录下来,当运行的时候他就把所有标记的字节修改了,尽管还是显示原来的代码,当然当他一暂停下来就又修改回来了。 上面的是实验中,F7的原理只是猜测,还没有很好的办法能证明他就是使用TF,下面我继续猜测一下内存断点的原理 1.将设置的内存断点的地址记录下来 2.对这个地址的内存页面修改其属性 如果是内存写断点,就修改为RE(可读,可执行) 如果是内存访问断点,就修改为NO ACCESS(不可访问) 3.只要访问到这个页面就会产生相应的异常,然后由OD来判断是否与记录的断点一致,从而是否中断下来 -------------------------------------------------- 4.后话 对于上面的F7和内存断点的原理,我还没想出什么好的办法去找出OD的原理,或许去调试一下ollydbg.exe是一个不错的建议。如果有哪位兄弟知道有什么好办法,希望能告诉我。当然也很欢迎各位和我讨论。 9月16日 沙加与佛的对话(转载)佛说:沙加,沙加,有什么事情让你如此悲伤?只有六岁的你,为什么每天这么坐着? 什么事情让你如此有心忡忡? 沙加:今天又看到冈底斯河中浮着好几具尸体,在河岸上有好多来自印度各地的巡礼者在那里沐浴, 看他们的样子,与其说是求生,不如说是希望求死一样。我所降生的这个国家,为什么会这么贫穷? 难道人们就是为了受苦受难而来到这个世界的吗? 佛说:沙加,这就是你悲伤的原因吗? 沙加:当然了,谁会希望致意个只有痛苦的人生呢? 佛说:那是不对的... 因为有痛苦,所以快乐也一定相应的存在,反过来一样... 美丽的花开了,可它也有一天会凋谢,在这个世界上,生命一瞬也不会停止的,它一直在动着,变着,这就是无常... 人的一生也是这样... 沙加:但是,最后还是只有一死... ... 这难道不可以说,人生还是被悲伤所支配着吗? 活着的时候,无论是克服痛苦还是追求爱,追求喜悦... 最终死亡还是把一切都化为虚无... 那,人是为什么而生?想要和死亡这种永恒的东西对抗根本就是不可能... 佛说:沙加,你忘记了? 沙加:忘记了? 佛说:那是... ... 沙加:沙罗双树的花...也凋谢了吗... 佛说:沙加啊,你忘记了吧? 沙加:忘记了? 佛说:死并不是一切的终结,即使是死也只不过是变化的一种... 佛说:沙加,沙加,你一定不能忘记啊!死亡决不是最后... 曾经活在这个世上的圣人们,都超越了死的境界! 沙加啊!如果你也能领悟这一点的话,那你也就成为了人类中最接近神的人! 沙加:花开了,然后又会凋零,星星市璀璨的,可那光芒也会消失! 这个地球,太阳,这整个银河系,甚至连这个宇宙,也会有死亡 的时候。 人的一生,和这些东西相比,简直就象是刹那间的事情... 在这样的一个瞬间,人降生了,笑着,哭着,战斗,伤害,喜悦,悲伤,憎恶,爱情,一切都知识刹那间的邂逅,而最后都要归入死的永眠! 9月2日 [汇编]条件条状与PSW(转载)一、状态寄存器 PSW(Program Flag)程序状态字寄存器,是一个16位寄存器,由条件码标志(flag)和控制标志构成,如下所示:
控制标志位: 二、 直接标志转移(8位寻址)
三、间接标志转移(8位寻址)
8月23日 RSA与大数运算(转载)此文重要,特转载之,路人以鉴之 RSA依赖大数运算,目前主流RSA算法都建立在512位到1024位的
6月13日 UBOOT添加命令(转载)这个应该对我有帮助,使我能够更好的了解uboot命令的执行流程。 具体内容如下: U-Boot的命令为用户提供了交互功能,并且已经实现了几十个常用的命令。如果开发板需要很特殊的操作,可以添加新的U-Boot命令。
U-Boot的每一个命令都是通过U_Boot_CMD宏定义的。这个宏在include/command.h头文件中定义,每一个命令定义一个cmd_tbl_t结构体。
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
这样每一个U-Boot命令有一个结构体来描述。结构体包含的成员变量:命令名称、最大参数个数、重复数、命令执行函数、用法、帮助。
从控制台输入的命令是由common/command.c中的程序解释执行的。(这就是我要找的)find_cmd()负责匹配输入的命令,从列表中找出对应的命令结构体。
基于U-Boot命令的基本框架,来分析一下简单的icache操作命令,就可以知道添加新命令的方法。
(1)定义CACHE命令。在include/cmd_confdefs.h中定义了所有U-Boot命令的标志位。
#define CFG_CMD_CACHE 0x00000010ULL /* icache, dcache */
如果有更多的命令,也要在这里添加定义。
(2)实现CACHE命令的操作函数。下面是common/cmd_cache.c文件中icache命令部分的代码。
#if (CONFIG_COMMANDS & CFG_CMD_CACHE)
static int on_off (const char *s)
{ //这个函数解析参数,判断是打开cache,还是关闭cache
if (strcmp(s, "on") == 0) { //参数为“on”
return (1);
} else if (strcmp(s, "off") == 0) { //参数为“off”
return (0);
}
return (-1);
}
int do_icache ( cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{ //对指令cache的操作函数
switch (argc) {
case 2: /* 参数个数为1,则执行打开或者关闭指令cache操作 */
switch (on_off(argv[1])) {
case 0: icache_disable(); //打开指令cache
break;
case 1: icache_enable (); //关闭指令cache
break;
}
/* FALL TROUGH */
case 1: /* 参数个数为0,则获取指令cache状态*/
printf ("Instruction Cache is %s\n",
icache_status() ? "ON" : "OFF");
return 0;
default: //其他缺省情况下,打印命令使用说明
printf ("Usage:\n%s\n", cmdtp->usage);
return 1;
}
return 0;
}
……
U_Boot_CMD( //通过宏定义命令
icache, 2, 1, do_icache, //命令为icache,命令执行函数为do_icache()
"icache - enable or disable instruction cache\n", //帮助信息
"[on, off]\n"
" - enable or disable instruction cache\n"
);
……
#endif
U-Boot的命令都是通过结构体__U_Boot_cmd_##name来描述的。根据U_Boot_CMD在include/command.h中的两行定义可以明白。
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
还有,不要忘了在common/Makefile中添加编译的目标文件。
(3)打开CONFIG_COMMANDS选项的命令标志位。这个程序文件开头有#if语句需要预处理是否包含这个命令函数。CONFIG_COMMANDS选项在开发板的配置文件中定义。例如:SMDK2410平台在include/configs/smdk2410.h中有如下定义。
/***********************************************************
* Command definition
***********************************************************/
#define CONFIG_COMMANDS \
(CONFIG_CMD_DFL | \
CFG_CMD_CACHE | \
CFG_CMD_REGINFO | \
CFG_CMD_DATE | \
CFG_CMD_ELF)
按照这3步,就可以添加新的U-Boot命令。 一篇uboot的移植文档(转载)1 uboot的介绍及体系结构... 2
1.1 uboot的介绍... 2
1.2 uboot的体系结构... 2
2 uboot的运行过程分析... 3
2.1 启动模式介绍... 3
2.2 运行过程... 3
2.3 本开发板的地址分布(leopard2a)... 5
2.4 运行代码分析... 5
2.4.1 stage 1. 5
2.4.2 stage 2. 5
2.4.2.1 调用一系列初始化函数... 5
2.4.2.2 初始化网络设备... 7
2.4.2.3 进入主UBOOT 命令行... 7
3 uboot的移植和测试... 7
3.1 移植的过程... 7
3.2 移植主要修改的文件... 8
3.3 uboot网络下载功能的添加和RAM调试... 8
1 uboot的介绍及体系结构
1.1 uboot的介绍
Uboot是德国DENX小组的开发用于多种嵌入式CPU的bootloader程序, UBoot不仅仅支持嵌入式Linux系统的引导,当前,它还支持NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS嵌入式操作系统。UBoot除了支持PowerPC系列的处理器外,还能支持MIPS、 x86、ARM、NIOS、XScale等诸多常用系列的处理器。
1.2 uboot的体系结构
目录树
|--board
|--common
|--cpu
|--disk
|--doc
|--drivers
|--dtt
|--examples
|--fs
|--include
|--lib_arm
|--lib_generic
|--net
|--post
|--rtc
|--tools
2. board:和一些已有开发板有关的文件. 每一个开发板都以一个子目录出现在当前目录中,比如说: leopard2a子目录中存放与我们开发板相关的配置文件.
3. common:实现uboot命令行下支持的命令,每一条命令都对应一个文件。例如bootm命令对应就是cmd_bootm.c。
4. cpu:与特定CPU架构相关目录,每一款Uboot下支持的CPU在该目录下对应一个子目录,比如有子目录arm926ejs就是我们开发板上使用的cpu架构目录。
5. disk:对磁盘的支持。
5. doc:文档目录。Uboot有非常完善的文档,推荐大家参考阅读。
6. drivers:Uboot支持的设备驱动程序都放在该目录,比如各种网卡、支持CFI的Flash、串口和USB等。
7. fs: 支持的文件系统,Uboot现在支持cramfs、fat、fdos、jffs2和registerfs。
8. include:Uboot使用的头文件,还有对各种硬件平台支持的汇编文件,系统的配置文件和对文件系统支持的文件。该目录下configs目录有与开发板相关的配置头文件,如leopard2a.h。该目录下的asm目录有与CPU体系结构相关的头文件,asm对应的是asmarm.
9. lib_xxxx: 与体系结构相关的库文件。如与ARM相关的库放在lib_arm中。
10. net:与网络协议栈相关的代码,BOOTP协议、TFTP协议、RARP协议和NFS文件系统的实现。
11. tools:生成Uboot
的工具,如:mkimage, crc等等。
2 uboot的运行过程分析
2.1 启动模式介绍 大多数 Boot Loader 都包含两种不同的操作模式:"启动加载"模式和"下载"模式,这种区别仅对于开发人员才有意义。但从最终用户的角度看,Boot Loader 的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。 启动加载(Boot loading)模式:这种模式也称为"自主"(Autonomous)模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。这种模式是 BootLoader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader 显然必须工作在这种模式下。
下载(Downloading)模式:在这种模式下,目标机上的 Boot Loader 将通过串口连接或网络连接等通信手段从主机(Host)下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 BootLoader 保存到目标机的 RAM 中,然后再被 BootLoader 写到目标机上的FLASH 类固态存储设备中。BootLoader 的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用 BootLoader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的命令行接口。
UBoot这样功能强大的 Boot Loader 同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。
2.2 运行过程
大多数bootloader都分为阶段1(stage1)和阶段2(stage2)两大部分,uboot也不例外。依赖于CPU体系结构的代码(如CPU初始化代码等)通常都放在阶段1中且通常用汇编语言实现,而阶段2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。 U - Boot 编译后的代码定义一般不超过100kB ,并且这100 kB 又分成两个阶段来执行. 第一阶段的代码在start . s 中定义,大小不超过10 kB ,它包括从系统上电后在0x00000000 地址开始执行的部分. 这部分代码运行在Flash 中,它包括对arm926ejs的一些寄存器的初始化和将U - Boot 的第二阶段代码从Flash 拷贝到SDRAM 中. 除去第一阶段的代码,剩下的部分都是第二阶段的代码. 第二阶段的起始地址是在第一阶段代码中指定的,被复制到SDRAM后,就从第一阶段跳到这个入口地址开始执行剩余部分代码. 第二阶段主要是进行一些BSS 段设置,堆栈的初始化等工作,最后会跳转到main -loop 函数中,接受命令并进行命令处理. 图1 给出了U - Boot 的详细的运行过程包括对内核的设置、装载及调用过程.
系统复位进入u-boot stage l的入口点
硬件设备的初始化 为加载uboot stage 2准备ram空间 设置好堆栈 跳转到stage 2的C入口点 初始化本阶段要用到的设备 检查内存映射 将kernel映像和文件映像从flash中读到ram中 为内核设定启动参数 调用内核 图1
2.3 本开发板的地址分布(leopard2a)
本目标板是RAM 16M, Flash 8M,具体空间如图所示:
1F12FFFF
Uboot Uboot_env
Kernel
Rootfs
1F000000 1F020000 1F01FFFF 1F02FFFF 1F030000 1F130000 1F7EFFFF Kernel
Rootfs
FLASH SDRAM 0x00030000 0x00130000 0x00430000 可以根据变换后的分区结构,设置
uboot_addr,uboot_addr_end,kernel_addr,kernel_addr_end,rootfs_addr,rootfs_addr_end,
config_addr, config_addr_end等环境变量,调整bootloader。
SDRAM的调整修改linux-2.4.20_mvl31/drivers/mtd/maps/physmap.c
2.4 运行代码分析
2.4.1 stage 1 uboot的stage1代码通常放在start.s文件中,它用汇编语言写成,其主要代码包括定义入口,设置异常向量,设置cpu的模式和频率,配置内存区控制寄存器,安装uboot的栈空间,关闭看门狗等。由于本人对ram的汇编不太熟悉,所以这一部分不作具体分析。
2.4.2 stage 2
lib_arm/board.c中的start armboot是C语言开始的函数,也是整个启动代码中C语言的主函数,同时还是整个uboot(armboot)的主函数,该函数主要完成如下操作: 2.4.2.1 调用一系列初始化函数
1. 指定初始函数表:
init_fnc_t *init_sequence[] = {
cpu_init, /* cpu的基本设置 */
board_init, /* 开发板的基本初始化 */
interrupt_init, /* 初始化中断 */
env_init, /* 初始化环境变量 */
init_baudrate, /* 初始化波特率 */
serial_init, /* 串口通讯初始化 */
console_init_f, /* 控制台初始化第一阶段 */
display_banner, /* 通知代码已经运行到该处 */
dram_init, /* 配制可用的内存区 */
display_dram_config,
#if defined(CONFIG_VCMA9) || defined (CONFIG_CMC_PU2)
checkboard,
#endif
NULL,
};
执行初始化函数的代码如下:
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
2. 配置可用的Flash区
flash_init ()
3. 初始化内存分配函数
mem_malloc_init()
4. nand flash初始化
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
puts ("NAND:");
nand_init(); /* go init the NAND */
#endif
5. 初始化环境变量
env_relocate ();
6. 外围设备初始化
devices_init()
7. I2C总线初始化
i2c_init();
8. LCD初始化
drv_lcd_init();
9. VIDEO初始化
drv_video_init();
10. 键盘初始化
drv_keyboard_init();
11. 系统初始化
drv_system_init();
2.4.2.2 初始化网络设备
初始化相关网络设备,填写IP、MAC地址等。 1. 设置IP地址
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
2. 设置mac地址
{
int i;
ulong reg;
char *s, *e;
uchar tmp[64];
i = getenv_r ((uchar*)("ethaddr"), tmp, sizeof (tmp));
s = (i > 0) ? (char*)tmp : NULL;
for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
}
2.4.2.3 进入主UBOOT 命令行
进入命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。 for (;;) {
main_loop ();
}}
3 uboot的移植和测试
3.1 移植的过程 ① 在宿主机上建立交叉编译开发环境 ② 修改cpu/arm926ejst目录中的文件内容,
主要包含cpu.C,start.S,interrupts.C以及seria1.C,speed.C等文件
③ 在board目录下创建自己的目标板(开发板)目录leopard2a
在目录下创建leopard2a.C,flash.C,memsetup.S
以及Makefile,u-bot.1ds,config.mk文件
④在include/configs目录下创建leopard2a.h
⑤ 打开u-bot目录下Makefile文件,加入如下两行:
leopard2a_config : unconfig
@./mkconfig $(@:_config=) arm arm926ejs leopard2a
⑨ 编译。运行命令:
1. make leopard2a_config
2. make
编译成功.生成基本的u—b00t.
⑦ 烧写.把编译成的u-bot.bin
至此移植u-bot过程结束.
3.2 移植主要修改的文件
移植u—boot到开发板上只需要修改和硬件相关的代码即可。这首先就联想到cpu目录下的启动代码,另外参考u—boot/readme文件可知其他还需要修改的主要文件有: Makefile文件,和include目录下的目标板.h头文件(leopard2a.h),board目录下的目标板.C文件(leopard2a.c),flash.C文件,u-boot.1ds链接文件,以及cpu目录下的串口驱动文件。
具体修改如下:
① cpu/arm926ejst目录下
◆ start.S启动代码。
② board/leopard2a
◆ leopard2a.C文件。这个文件主要是SDRAM 的驱动程
序,主要完成SDRAM 的UPM 表设置,上电初始化。暂时不
改。
◆ flash.C文件。Flash的驱动程序就在此文件中。
◆ memsetup.S文件。
◆ config.mk文件.此文件用于设置程序链接的起始地
址.
◆u-boot.Ids文件。
③ include/configs目录下leopard2a.h文件.此文件是
leopard2a目标板头文件,大多数寄存器参数是在这一文件中
设置完成的.
3.3 uboot网络下载功能的添加和RAM调试
在commom/main.c 中的main_loop函数中添加tftp下载的函数,可以通过按钮触发下载rimage,kimage。 在烧录u-boot.bin之前,需要进行ram调试,保证uboot可以在EVB上正常运行。
先下載U-boot 到SDRAM上, 然後執行SDRAM 上的U-boot 程序, 以確認U-boot可以正常執行,
Command 如下:
1. “tftp a00000 u-boot.bin” <= 下載程序到SDRAM
2. “go a00000” <= 從SDRAM 執行程序
如果U-boot 可以相容於目前的硬件, 5VT EVB 會重新正常啟動
要是不能正常啟動,表示U-boot 不相容於目前的硬件, 請更換新的u-boot.
再重新測試, 直到被測試的U-boot可以正常啟動 5月27日 我也成上班族了,呵呵下周就要去上班了,心情激动+紧张...
开始新的挑战,加油!!!
我要学会处理生活中的每一个难题,不光是技术,更是一种"被各种类型的事务烦扰而又能心无杂念的去做完每一件事"的能力. 4月15日 java多线程同步(转载)下面是我原来在CSDN论坛上看到的一个贴子,涉及到同步,wait(),notify()等概念的理解,我试着根据原来的一些回复和Think in Java上的相关概念将wait()和notify()这两个方法剖析了一下,欢迎指教. 问题如下: file://分析这段程序,并解释一下,着重讲讲synchronized、wait(),notify 谢谢!
要分析这个程序,首先要理解notify()和wait(),为什么在前几天纪录线程的时候没有纪录这两个方法呢,因为这两个方法本来就不属于Thread类,而是属于最底层的object基础类的,也就是说不光是Thread,每个对象都有notify和wait的功能,为什么?因为他们是用来操纵锁的,而每个对象都有锁,锁是每个对象的基础,既然锁是基础的,那么操纵锁的方法当然也是最基础了. 再往下看之前呢,首先最好复习一下Think in Java的14.3.1中第3部分内容:等待和通知,也就是wait()和notify了. 按照Think in Java中的解释:"wait()允许我们将线程置入“睡眠”状态,同时又“积极”地等待条件发生改变.而且只有在一个notify()或notifyAll()发生变化的时候,线程才会被唤醒,并检查条件是否有变." 我们来解释一下这句话. 区别在于"(wait)同时又“积极”地等待条件发生改变",这一点很关键,sleep和suspend无法做到.因为我们有时候需要通过同步(synchronized)的帮助来防止线程之间的冲突,而一旦使用同步,就要锁定对象,也就是获取对象锁,其它要使用该对象锁的线程都只能排队等着,等到同步方法或者同步块里的程序全部运行完才有机会.在同步方法和同步块中,无论sleep()还是suspend()都不可能自己被调用的时候解除锁定,他们都霸占着正在使用的对象锁不放. 那么别人怎么通知我呢?相信大家都可以想到了,notify(),这就是最后一句话"而且只有在一个notify()或notifyAll()发生变化的时候,线程才会被唤醒"的意思了. 这个时候我们来解释上面的程序,简直是易如反掌了. synchronized(b){...};的意思是定义一个同步块,使用b作为资源锁。b.wait();的意思是临时释放锁,并阻塞当前线程,好让其他使用同一把锁的线程有机会执行,在这里要用同一把锁的就是b线程本身.这个线程在执行到一定地方后用notify()通知wait的线程,锁已经用完,待notify()所在的同步块运行完之后,wait所在的线程就可以继续执行.
java多线程设计wait/notify机制 多线程之间需要协调工作。例如,浏览器的一个显示图片的线程displayThread想要执行显示图片的任务,必须等待下载线程downloadThread将该图片下载完毕。如果图片还没有下载完,displayThread可以暂停,当downloadThread完成了任务后,再通知displayThread“图片准备完毕,可以显示了”,这时,displayThread继续执行。 以上逻辑简单的说就是:如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于wait/notify。等待机制与锁机制是密切关联的。例如: synchronized(obj) { 当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait()。 在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A: synchronized(obj) { 需要注意的概念是: # 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {...} 代码段内。 # 调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj) {...} 代码段内唤醒A。 # 当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。 # 如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。 # obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。 # 当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。
synchronized的4种用法 1.方法声明时使用,放在范围操作符(public等)之后,返回类型声明(void等)之前.即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入. public synchronized void synMethod() { 2.对某一代码块使用,synchronized后跟括号,括号里是变量,这样,一次只有一个线程进入该代码块.例如: public int synMethod(int a1){ public class MyThread implements Runnable { public void run() {
class FineGrainLock { MyMemberClass x, y; public void foo() { //do something here - but don't use shared resources synchronized(ylock) { public void bar() {
4.synchronized后面括号里是类.例如: class ArrayWithLockOrder{ public ArrayWithLockOrder(int[] a) class SomeClass implements Runnable
对于4,如果线程进入,则线程在该类中所有操作不能进行,包括静态变量和静态方法,实际上,对于含有静态方法和静态变量的代码块的同步,我们通常用4来加锁. 以上4种之间的关系: 锁是和对象相关联的,每个对象有一把锁,为了执行synchronized语句,线程必须能够获得synchronized语句中表达式指定的对象的锁,一个对象只有一把锁,被一个线程获得之后它就不再拥有这把锁,线程在执行完synchronized语句后,将获得锁交还给对象。
下面谈一谈一些常用的方法: wait(),wait(long),notify(),notifyAll()等方法是当前类的实例方法, 对于上述方法,只有在当前线程中才能使用,否则报运行时错误java.lang.IllegalMonitorStateException: current thread not owner.
下面,我谈一下synchronized和wait()、notify()等的关系: 1.有synchronized的地方不一定有wait,notify 2.有wait,notify的地方必有synchronized.这是因为wait和notify不是属于线程类,而是每一个对象都具有的方法,而且,这两个方法都和对象锁有关,有锁的地方,必有synchronized。 另外,请注意一点:如果要把notify和wait方法放在一起用的话,必须先调用notify后调用wait,因为如果调用完wait,该线程就已经不是current thread了。如下例: /** public class DemoThread public DemoThread() { testthread2.start(); } public static void main(String[] args) { } public void run() { TestThread t = (TestThread) Thread.currentThread(); System.out.println("@time in thread" + t.getName() + "=" + if (t.getTime() % 10 == 0) { } class TestThread public int getTime() { public int increaseTime() { } 下面我们用生产者/消费者这个例子来说明他们之间的关系: public class test { class Semaphore public synchronized void acquire() { public synchronized void release() { public void run() { 生产者生产,消费者消费,一般没有冲突,但当库存为0时,消费者要消费是不行的,但当库存为上限(这里是10)时,生产者也不能生产.请好好研读上面的程序,你一定会比以前进步很多. 上面的代码说明了synchronized和wait,notify没有绝对的关系,在synchronized声明的方法、代码块中,你完全可以不用wait,notify等方法,但是,如果当线程对某一资源存在某种争用的情况下,你必须适时得将线程放入等待或者唤醒. 4月10日 [转贴]Java媒体架构(JMF)
|
|
|