liang's profile阿斯提亚神殿——梦幻天子's skyPhotosBlogLists Tools Help
    January 21

    Some tips about Conficker.B

    Something 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”.

    clip_image002

    4.       In the “Services.msc”, we could see this service, but the status of it is not running:

            clip_image002[5]

    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:

           clip_image001

    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:

           clip_image002[7]

           clip_image002[9]

           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.

            clip_image001[5]

    That is all about Conficker. Hope we could kill it soonerJ

    July 29

    (转载)Hindsight analysis of the infamous DNS bug

    If you read Dan Kaminsky's researchs over the past few years, you'd probably
    that Dan know many DNS tricks. One of these is the CNAME trick that Dan
    mentioned in the Wired
    interview<http://blog.wired.com/27bstroke6/2008/07/kaminsky-on-how.html>.
    He has talked about this trick back to
    2007<http://www.doxpara.com/slides/DMK_BO2K7_Web.ppt>as below:

    1. CNAME Records: DNS Aliases

    - Instead of returning an address, return what the "Canonical", or Official
    Name was, and then the address of that Canonical Name

    - If you are allowed to be the resolver for that canonical name, your
    additional record overrides whatever's already in the cache, even if the TTL
    hasn't expired yet

    * It's not a bug.

    * Works against most, but not actually all name servers

    2. Demo

    $ dig 1.foo.notmallory.com
    ;; ANSWER SECTION:
    1.foo.notmallory.com. 120 IN CNAME bar.foo.notmallory.com
    bar.foo.notmallory.com. 120 IN A 10.0.0.0

    $ dig bar.foo.notmallory.com
    bar.foo.notmallory.com. 111 IN A 10.0.0.0

    $ dig 2.foo.notmallory.com
    2.foo.notmallory.com. 120 IN CNAME bar.foo.notmallory.com.
    bar.foo.notmallory.com. 120 IN A 10.0.0.1

    $ dig bar.foo.notmallory.com
    bar.foo.notmallory.com. 118 IN A 10.0.0.1

    As you can see, this trick, Dan called it "CNiping", can be used to
    overwrite whatever is www.yourwebsite.com. I guess when Dan talked to Paul
    Vixie, Paul told him that not only CNAME can be used to overwrite cached
    data but also NS, and probably other type of RRs as well. This is not new.
    RFC3833 has told us about this DNS *feature* for years.

    Okie so now, we have some ways to overwrite cached data in a DNS resolver,
    the remained problem is how to force it to accept our packet. And you
    probably know this already, so I don't repeat here.

    The rest of this post to to emphasize on how easy to guess the QID if we
    have a fast Internet connection. Much of the following part is derived from
    this study (
    http://www.faqs.org/ftp/internet-drafts/draft-hubert-dns-anti-spoofing-00.txt
    ).

    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
    around 2.5)

    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
    time of the authoritative servers (often 0.1s)

    D: Average number of identical outstanding questions of a resolver
    (typically 1, see Section 5)

    A: Number of attempts, one for each window of opportunity

    The probability of spoofing a resolver is equal to amount of fake packets
    that arrive within the window of opportunity, divided by the size of the
    problem space.

    When the resolver has 'D' multiple identical outstanding questions, each
    fake packet has a proportionally higher chance of matching any of these
    questions. This assumption only holds for small values of 'D'.

    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
    to packet rate, which can easily be converted to bandwidth if needed.

    If the Window of opportunity length is 'W' and the attacker can send 'R'
    packets per second, the number of fake packets 'F' that are candidates to be
    accepted is:

    F= R * W ---> P_s = D*R*W/N*P*I

    To calculate the combined chance of at least one success, the following
    formula holds:

    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,
    this formula reduces to:

    P_cs = 1 - (1 -R/1638400)^A

    The different between this attack and the one described in the original
    study (
    http://www.faqs.org/ftp/internet-drafts/draft-hubert-dns-anti-spoofing-00.txt)
    is in the number A.

    In the original study, A = T/TTL, where T is the time taken to successful
    poison target resolvers, and TTL is the number of seconds when the target
    hostname expires. In other words, each time we fail to poison, we must have
    to wait for TTL second before trying again.

    In this case, A = N, which is the number of queries we send to target
    resolvers. This is because we don't have to wait any second even if we fail
    to poison in all previous attempts. Remember the CNAME trick above?

    So all you must do now is to increase R and A. Suppose for each query, you
    send F = 30 fake responses to target resolvers in W = 0.1, hence your
    bandwidth should be R = F/W = 300 packet/s. If A = 4000, then you stand a
    51% chance to be sucessful! It's that easy.

    If you use Metasploit, you can use the following formula to calculate your
    probability:

    P_cs = 1 - (1 - xids/2^16)^A

    where xids is the number of fake responses you want to send for each query.
    Remember to consider your bandwidth capacity when choosing xids. If you
    choose the default value, i.e. xids=10, then you can expect to poison
    successful after A>4000.

    One final note: this attack is not a birthday attack. because we only have
    one opening query to guess its QID.

    May 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.
     
       
    March 13

    [原创]让VS2008支持WDM驱动的编写

    为了毕设的需要,今天写了一个VS2008下编写WDM驱动的template,主要通过VS提供的custom wizard工程,编辑一个wizard的htm以及相关的js文件来实现。
    目前还存在以下缺陷:第一,不支持c++,强大的面向对象设计思想暂时还不能应用。第二,必须事先手动设置一个环境变量,指定DDK的路径。
    现在终于可以在VS中新建驱动工程了。相比以前在记事本里写驱动代码,VS下看起来还是很舒服的。
    有兴趣的朋友可以私下交流具体细节。
     
    下一阶段打算研究一下毕设的细节。毕竟这样的机会不多,很可能一生就这一次,今后可能就不会再碰研究领域了,所以呢,要珍惜啦。本次毕设的Code Name暂时命名为:Zion,软件开发部分与MSRA实习生——老BOSS合作完成,目前的计划是:首先合作完成Rootkit底层功能模块,然后我做攻击工具,他做检测工具。
    最后,祝愿我一切顺利。
    February 05

    调和数与小提琴

    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 中的每一项对应。”
     
    长见识了,原来这都可以……
    January 12

    终于结束了

    也许大学所有的考试就这么结束了,纪念一下。
    看了下毕业所修的所有课,总学分达到255分了。
    这大学念的苦啊~~~~
    December 19

    第二、第三张offer

    这周真是喜事不断,首先是郁闷了将近两周的微软面试,终于有了结果,非常感觉在面试中给我鼓励的Samuel, Daniel,Chao,以及所有Security Support Group的同事们和同学们,没有你们的帮助,就没有今天这样的结果。期间经历了痛苦的等待,看着别人陆续拿到口头offer而自己却没有结果。。。汗,看来我的心态的确还需要磨练。好的心态的走向成功的基础。
    今天上午Symantec给了口头offer,北京的Security Response Engineer,也是我特别喜欢的职位。不过地方比较远,因此就当个保底吧。
    December 16

    无题

    汗,犯了个低级错误,贴一下正确的,以此为纪念
    #pragma comment(linker, "/ENTRY:wmain")
    int _tmain(int argc, _TCHAR* argv[])
    {
     return 0;
    }
    December 02

    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 IoCancel­Irp 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, IoCom­pleteRequest 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 Io­FreeIrp 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 Cancel­Routine 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 IoMark­Irp­Pending 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;
    }
    November 13

    第一张offer

    终于拿到人生的第一张offer,腾讯的软件工程师,不管怎么样,先庆祝一下,这年头拿offer真累
    October 07

    PTE/PDE(转载)

      虚拟地址如何转换到物理地址?必须用到PDE,PTE的虚拟地址吗?

    按照以前学习的分页机制,一个虚拟地址转换成物理地址的计算过程应该是:

    1 处理器通过CR3找到当前页目录所在的物理页地址a
    2 计算PDE的物理地址:a+ 虚拟地址高十位左移两位 (因为每个页目录项四个字节),取出该地址处的PDEPDE的前20位+低12位0是这个虚拟地址对应页表的物理地址b
    3 计算PTE的物理地址:b+ 虚拟地址的中间10位左移两位,取出该地址处的PTEPTE的前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
    2 计算PDE的物理地址:0e238000 + 00000600= 0e238600

    取出该地址处的PDE

    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
    取出该地址处的PTE

    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
    4 计算虚拟地址对应的物理地址:0dece000 + 00000020 = 0dece020
    取出该物理地址处的数据:

    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 ................
    实验结果表明:虚拟地址0x60010020对应的物理地址为:0dece020,并不需要用到PDE,PTE的虚拟地址,上面的计算方法是正确的

    通过基址c0300000c0000000计算出的PDEPTE的虚拟地址有什么用?


    cpu在转换地址的过程中,并不需要页表,页目录的虚拟地址,那为什么还需要计算PDE,PTE的虚拟地址呢?是因为在分页标志设置后,cpu把指令中的虚拟地址转换成物理地址,程序中只能使用虚拟地址,因此要把页目录和页表映射到虚拟地址空间中,页表被映射到了从0xC00000000xC03FFFFF4M地址空间,页目录被映射到了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


    PTE的虚拟地址为:  c0000000 + 00180040 = c0180040
    取出该虚拟地址处的PTE,和上面用PTE的物理地址取出来的结果相同

    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

    September 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是一个不错的建议。如果有哪位兄弟知道有什么好办法,希望能告诉我。当然也很欢迎各位和我讨论。 
     
    September 16

    沙加与佛的对话(转载)

    佛说:沙加,沙加,有什么事情让你如此悲伤?只有六岁的你,为什么每天这么坐着?
           什么事情让你如此有心忡忡?
    沙加:今天又看到冈底斯河中浮着好几具尸体,在河岸上有好多来自印度各地的巡礼者在那里沐浴,
           看他们的样子,与其说是求生,不如说是希望求死一样。我所降生的这个国家,为什么会这么贫穷?
           难道人们就是为了受苦受难而来到这个世界的吗?
    佛说:沙加,这就是你悲伤的原因吗?
    沙加:当然了,谁会希望致意个只有痛苦的人生呢?
    佛说:那是不对的...
           因为有痛苦,所以快乐也一定相应的存在,反过来一样...
           美丽的花开了,可它也有一天会凋谢,在这个世界上,生命一瞬也不会停止的,它一直在动着,变着,这就是无常...
           人的一生也是这样...
    沙加:但是,最后还是只有一死... ...
           这难道不可以说,人生还是被悲伤所支配着吗?
           活着的时候,无论是克服痛苦还是追求爱,追求喜悦...
           最终死亡还是把一切都化为虚无...
           那,人是为什么而生?想要和死亡这种永恒的东西对抗根本就是不可能...
    佛说:沙加,你忘记了?
    沙加:忘记了?
    佛说:那是... ...
    沙加:沙罗双树的花...也凋谢了吗...
    佛说:沙加啊,你忘记了吧?
    沙加:忘记了?
    佛说:死并不是一切的终结,即使是死也只不过是变化的一种...

    佛说:沙加,沙加,你一定不能忘记啊!死亡决不是最后...
           曾经活在这个世上的圣人们,都超越了死的境界!
           沙加啊!如果你也能领悟这一点的话,那你也就成为了人类中最接近神的人!
    沙加:花开了,然后又会凋零,星星市璀璨的,可那光芒也会消失!
           这个地球,太阳,这整个银河系,甚至连这个宇宙,也会有死亡 的时候。
           人的一生,和这些东西相比,简直就象是刹那间的事情...
           在这样的一个瞬间,人降生了,笑着,哭着,战斗,伤害,喜悦,悲伤,憎恶,爱情,一切都知识刹那间的邂逅,而最后都要归入死的永眠!
    September 02

    [汇编]条件条状与PSW(转载)

    一、状态寄存器

    PSW(Program Flag)程序状态字寄存器,是一个16位寄存器,由条件码标志(flag)和控制标志构成,如下所示:

    15
    14
    13
    12
    11
    10
    9
    8
    7
    6
    5
    4
    3
    2
    1
    0
     
     
     
     
    OF
    DF
    IF
    TF
    SF
    ZF
     
    AF
     
    PF
     
    CF


    条件码:
    ①OF(Overflow Flag)溢出标志。溢出时为1,否则置0。
    ②SF(Sign Flag)符号标志。结果为负时置1,否则置0.
    ③ZF(Zero Flag)零标志,运算结果为0时ZF位置1,否则置0.
    ④CF(Carry Flag)进位标志,进位时置1,否则置0.
    ⑤AF(Auxiliary carry Flag)辅助进位标志,记录运算时第3位(半个字节)产生的进位置。有进位时1,否则置0.
    ⑥PF(Parity Flag)奇偶标志。结果操作数中1的个数为偶数时置1,否则置0.

    控制标志位:
    ⑦DF(Direction Flag)方向标志,在串处理指令中控制信息的方向。

    ⑧IF(Interrupt Flag)中断标志。
    ⑨TF(Trap Flag)陷井标志。

    二、 直接标志转移(8位寻址)

    指令格式
    机器码
    测试条件
    如...则转移
     
     
    指令格式
    机器码
    测试条件
    如...则转移
    JC
    72
    C=1
    有进位
    JNS
    79
    S=0
    正号
    JNC
    73
    C=0
    无进位
    JO
    70
    O=1
    有溢出
    JZ/JE
    74
    Z=1
    零/等于
    JNO
    71
    O=0
    无溢出
    JNZ/JNE
    75
    Z=0
    不为零/不等于
    JP/JPE
    7A
    P=1
    奇偶位为偶
    JS
    78
    S=1
    负号
    JNP/IPO
    7B
    P=0
    奇偶位为奇

    三、间接标志转移(8位寻址)

    指令格式
    机器码
    测试格式
    如...则转移
    JA/JNBE(比较无符号数)
    77
    C或Z=0
    >  高于/不低于或等于
    JAE/JNB(比较无符号数)
    73
    C=0
    >=  高于或等于/不低于
    JB/JNAE(比较无符号数)
    72
    C=1
    <  低于/不高于或等于
    JBE/JNA(比较无符号数)
    76
    C或Z=1
    <=  低于或等于/不高于
    JG/JNLE(比较带符号数)
    7F
    (S异或O)或Z=0
    >  大于/不小于或等于
    JGE/JNL(比较带符号数)
    7D
    S异或O=0
    >=  大于或等于/不小于
    JL/JNGE(比较带符号数)
    7C
    S异或O=1
    <  小于/不大于或等于
    JLE/JNG(比较带符号数)
    7E
    (S异或O)或Z=1
    <=  小于或等于/不大于
     

    August 23

    RSA与大数运算(转载)

    此文重要,特转载之,路人以鉴之

    RSA依赖大数运算,目前主流RSA算法都建立在512位到1024位的
    大数运算之上,所以我们在现阶段首先需要掌握1024位的大数
    运算原理。

    大多数的编译器只能支持到64位的整数运算,即我们在运算中
    所使用的整数必须小于等于64位,即:0xffffffffffffffff
    也就是18446744073709551615,这远远达不到RSA的需要,于是
    需要专门建立大数运算库来解决这一问题。

    最简单的办法是将大数当作字符串进行处理,也就是将大数用
    10进制字符数组进行表示,然后模拟人们手工进行“竖式计算”
    的过程编写其加减乘除函数。但是这样做效率很低,因为1024
    位的大数其10进制数字个数就有数百个,对于任何一种运算,
    都需要在两个有数百个元素的数组空间上做多重循环,还需要
    许多额外的空间存放计算的进位退位标志及中间结果。当然其
    优点是算法符合人们的日常习惯,易于理解。

    另一种思路是将大数当作一个二进制流进行处理,使用各种移
    位和逻辑操作来进行加减乘除运算,但是这样做代码设计非常
    复杂,可读性很低,难以理解也难以调试。

    于是俺琢磨了一种介于两者之间的思路:

    将大数看作一个n进制数组,对于目前的32位系统而言n可以取
    值为2的32次方,即0x10000000,假如将一个1024位的大数转
    化成0x10000000进制,它就变成了32位,而每一位的取值范围
    就不是0-1或0-9,而是0-0xffffffff。我们正好可以用一个无
    符号长整数来表示这一数值。所以1024位的大数就是一个有32
    个元素的unsigned long数组。而且0x100000000进制的数组排列
    与2进制流对于计算机来说,实际上是一回事,但是我们完全
    可以针对unsigned long数组进行“竖式计算”,而循环规模
    被降低到了32次之内,并且算法很容易理解。

    例如大数18446744073709551615,等于“ffffffff ffffffff”,
    它就相当于10进制的“99”:有两位,每位都是ffffffff。
    而大数18446744073709551616,等于“00000001 00000000
    00000000”,它就相当于10进制的“100”:有三位,第一位是
    1,其它两位是0。如果我们要计算18446744073709551616
    -18446744073709551615,就类似于100-99:
     
      00000001 00000000 00000000
    -           ffffffff ffffffff
    -----------------------------
    =        0         0        1


    所以,可以声明大数类如下:
    /****************************************************************/
    //大数运算库头文件:BigInt.h
    //作者:afanty@vip.sina.com
    //版本:1.0 (2003.4.26)
    //说明:适用于MFC
    /****************************************************************/

    #define BI_MAXLEN 40
    #define DEC 10
    #define HEX 16

    class CBigInt
    {
    public:
       int m_nSign;    //记录大数的符号,支持负值运算
       int m_nLength;  //记录0x10000000进制的位数,0-40之间,相当于2进制的0-1280位
       unsigned long m_ulvalue[BI_MAXLEN];   //记录每一位的“数字”

       CBigInt();
       ~CBigInt();


    //将大数赋值为另一个大数    
       CBigInt& Mov(CBigInt& A);

    //将大数赋值为编译器能够理解的任何整形常数或变量  
       CBigInt& Mov(unsigned __int64 A);

    //比较两个大数大小
       int Cmp(CBigInt& A);

    //计算两个大数的和
       CBigInt Add(CBigInt& A);

    //重载函数以支持大数与普通整数相加
       CBigInt Add(long A);

    //计算两个大数的差
       CBigInt Sub(CBigInt& A);

    //重载函数以支持大数与普通整数相减
       CBigInt Sub(long A);

    //计算两个大数的积
       CBigInt Mul(CBigInt& A);

    //重载函数以支持大数与普通整数相乘
       CBigInt Mul(long A);

    //计算两个大数的商
       CBigInt Div(CBigInt& A);

    //重载函数以支持大数与普通整数相除
       CBigInt Div(long A);

    //计算两个大数相除的余数
       CBigInt Mod(CBigInt& A);

    //重载函数以支持大数与普通整数相除求模
       long Mod(long A);

    //将输入的10进制或16进制字符串转换成大数
       int InPutFromStr(CString& str, const unsigned int system);

    //将大数按10进制或16进制格式输出到字符串
       int OutPutToStr(CString& str, const unsigned int system);

    //欧几里德算法求:Y=X.Euc(A),使满足:YX mod A = 1
       CBigInt Euc(CBigInt& A);

    //蒙哥马利算法求:Y=X.Mon(A,B),使满足:X^A mod B = Y
       CBigInt Mon(CBigInt& A, CBigInt& B);
    };


    注意以上函数的声明格式,完全遵循普通整数运算的习惯,例如大数
    Y=X+Z 相当于 Y.Mov(X.(Add(Z)),这样似乎没有Mov(Y,Add(X,Z))
    看起来舒服,但是一旦我们重载运算符“=”为“Mov”,“+”为“Add”,
    则Y.Mov(X.(Add(Z))的形式就等价于 Y=X+Z。

    俺不知道其他编程语言里是否支持运算浮重载,至少这样定义函数格式
    在C++里可以带来很大的方便。


    下面让我们来实现大数类的主要成员函数:
    /****************************************************************/
    //大数运算库源文件:BigInt.cpp
    //作者:afanty@vip.sina.com
    //版本:1.0 (2003.4.26)
    //说明:适用于MFC
    /****************************************************************/

    #include "stdafx.h"
    #include "BigInt.h"

    //初始化大数为0
    CBigInt::CBigInt()
    {
    m_nSign=1;
    m_nLength=1;
    for(int i=0;i<BI_MAXLEN;i++)m_ulvalue[i]=0;
    }

    //采用缺省的解构函数
    CBigInt::~CBigInt()
    {
    }

    //大数比较,如果大数A位数比大数B多,当然A>B
    //如果位数相同,则从高位开始比较,直到分出大小
    int CBigInt::Cmp(CBigInt& A)
    {
    if(m_nLength>A.m_nLength)return 1;
    if(m_nLength<A.m_nLength)return -1;
    for(int i=m_nLength-1;i>=0;i--)
    {
    if(m_ulvalue[i]>A.m_ulvalue[i])return 1;
    if(m_ulvalue[i]<A.m_ulvalue[i])return -1;
    }
    return 0;
    }

    //照搬参数的各属性
    CBigInt& CBigInt::Mov(CBigInt& A)
    {
    m_nLength=A.m_nLength;
    for(int i=0;i<BI_MAXLEN;i++)m_ulvalue[i]=A.m_ulvalue[i];
    return *this;
    }

    //大数相加
    //调用形式:N.Add(A),返回值:N+A
    //若两大数符号相同,其值相加,否则改变参数符号再调用大数相减函数
    /******************************************************************/
    例如:
         A  B  C
    +       D  E
    --------------
    = S  F  G  H

    其中,若C+E<=0xffffffff,则H=C+E,carry(进位标志)=0
         若C+E>0xffffffff,则H=C+E-0x100000000,carry=1

         若B+D+carry<=0xfffffff,则G=B+D,carry=0      
         若B+D+carry>0xfffffff,则G=B+D+carry-0x10000000,carry=1

         若carry=0,则F=A,S=0
         若carry=1,A<0xfffffff,则F=A+1,S=0
         若carry=1,A=0xfffffff,则F=0,S=1
    /*****************************************************************/
    CBigInt CBigInt::Add(CBigInt& A)
    {
    CBigInt X;
    if(X.m_nSign==A.m_nSign)
    {
    X.Mov(*this);
    int carry=0;
           unsigned __int64 sum=0;
           if(X.m_nLength<A.m_nLength)X.m_nLength=A.m_nLength;
    for(int i=0;i<X.m_nLength;i++)
    {
    sum=A.m_ulvalue[i];
    sum=sum+X.m_ulvalue[i]+carry;
    X.m_ulvalue[i]=(unsigned long)sum;
    if(sum>0xffffffff)carry=1;
    else carry=0;
    }
    if(X.m_nLength<BI_MAXLEN)
    {
    X.m_ulvalue[X.m_nLength]=carry;
       X.m_nLength+=carry;
    }
    return X;
    }
    else{X.Mov(A);X.m_nSign=1-X.m_nSign;return Sub(X);}
    }

    //大数相减
    //调用形式:N.Sub(A),返回值:N-A
    //若两大数符号相同,其值相减,否则改变参数符号再调用大数相加函数
    /******************************************************************/
    例如:
         A  B  C
    -       D  E
    --------------
    =    F  G  H

    其中,若C>=E,则H=C-E,carry(借位标志)=0
         若C<E,则H=C-E+0x100000000,carry=1

         若B-carry>=D,则G=B-carry-D,carry=0      
         若B-carry<D,则G=B-carry-D+0x10000000,carry=1

         若carry=0,则F=A
         若carry=1,A>1,则F=A-1
         若carry=1,A=1,则F=0
    /*****************************************************************/
    CBigInt CBigInt::Sub(CBigInt& A)
    {
    CBigInt X;
    if(m_nSign==A.m_nSign)
    {
    X.Mov(*this);
    int cmp=X.Cmp(A);
    if(cmp==0){X.Mov(0);return X;}
    int len,carry=0;
    unsigned __int64 num;
    unsigned long *s,*d;
           if(cmp>0)
                   {
                           s=X.m_ulvalue;
                           d=A.m_ulvalue;
                           len=X.m_nLength;
                   }
           if(cmp<0)
                   {
                           s=A.m_ulvalue;
                           d=X.m_ulvalue;
                           len=A.m_nLength;
                           X.m_nSign=1-X.m_nSign;
                   }
           for(int i=0;i<len;i++)
    {
    if((s[i]-carry)>=d[i])
    {
    X.m_ulvalue[i]=s[i]-carry-d[i];
    carry=0;
    }
    else
    {
    num=0x100000000+s[i];
    X.m_ulvalue[i]=(unsigned long)(num-carry-d[i]);
    carry=1;
    }
    }
    while(X.m_ulvalue[len-1]==0)len--;
    X.m_nLength=len;
    return X;
    }
    else{X.Mov(A);X.m_nSign=1-X.m_nSign;return Add(X);}
    }

    //大数相乘
    //调用形式:N.Mul(A),返回值:N*A
    /******************************************************************/
    例如:
            A  B  C
    *          D  E
    ----------------
    =    S  F  G  H
    + T  I  J  K
    ----------------
    = U  V  L  M  N

    其中,SFGH=ABC*E,TIJK=ABC*D

    而对于:
         A  B  C
    *          E
    -------------
    = S  F  G  H    

    其中,若C*E<=0xffffffff,则H=C*E,carry(进位标志)=0
         若C*E>0xffffffff,则H=(C*E)&0xffffffff
           carry=(C*E)/0xffffffff
         若B*E+carry<=0xffffffff,则G=B*E+carry,carry=0
         若B*E+carry>0xffffffff,则G=(B*E+carry)&0xffffffff
           carry=(B*E+carry)/0xffffffff
         若A*E+carry<=0xffffffff,则F=A*E+carry,carry=0
         若A*E+carry>0xffffffff,则F=(A*E+carry)&0xffffffff
           carry=(A*E+carry)/0xffffffff
         S=carry
    /*****************************************************************/
    CBigInt CBigInt::Mul(CBigInt& A)
    {
    CBigInt X,Y;
    unsigned __int64 mul;
           unsigned long carry;
           for(int i=0;i<A.m_nLength;i++)
    {
    Y.m_nLength=m_nLength;
    carry=0;
    for(int j=0;j<m_nLength;j++)
    {
    mul=m_ulvalue[j];
    mul=mul*A.m_ulvalue[i]+carry;
    Y.m_ulvalue[j]=(unsigned long)mul;
    carry=(unsigned long)(mul>>32);
    }
    if(carry&&(Y.m_nLength<BI_MAXLEN))
                   {
                           Y.m_nLength++;
                           Y.m_ulvalue[Y.m_nLength-1]=carry;
                   }
    if(Y.m_nLength<BI_MAXLEN-i)
    {
    Y.m_nLength+=i;
           for(int k=Y.m_nLength-1;k>=i;k--)Y.m_ulvalue[k]=Y.m_ulvalue[k-i];
           for(k=0;k<i;k++)Y.m_ulvalue[k]=0;
    }
    X.Mov(X.Add(Y));
    }
    if(m_nSign+A.m_nSign==1)X.m_nSign=0;
    else X.m_nSign=1;
    return X;
    }

    //大数相除
    //调用形式:N.Div(A),返回值:N/A
    //除法的关键在于“试商”,然后就变成了乘法和减法
    //这里将被除数与除数的试商转化成了被除数最高位与除数最高位的试商
    CBigInt CBigInt::Div(CBigInt& A)
    {
    CBigInt X,Y,Z;
    int len;
    unsigned __int64 num,div;
    unsigned long carry=0;
    Y.Mov(*this);
    while(Y.Cmp(A)>0)
    {      
    if(Y.m_ulvalue[Y.m_nLength-1]>A.m_ulvalue[A.m_nLength-1])
    {
    len=Y.m_nLength-A.m_nLength;
    div=Y.m_ulvalue[Y.m_nLength-1]/(A.m_ulvalue[A.m_nLength-1]+1);
    }
    else if(Y.m_nLength>A.m_nLength)
    {
    len=Y.m_nLength-A.m_nLength-1;
    num=Y.m_ulvalue[Y.m_nLength-1];
    num=(num<<32)+Y.m_ulvalue[Y.m_nLength-2];
    if(A.m_ulvalue[A.m_nLength-1]==0xffffffff)div=(num>>32);
    else div=num/(A.m_ulvalue[A.m_nLength-1]+1);
    }
    else
    {
                           X.Mov(X.Add(1));
    break;
    }
                   Z.Mov(div);
    Z.m_nLength+=len;
    for(int i=Z.m_nLength-1;i>=len;i--)Z.m_ulvalue[i]=Z.m_ulvalue[i-len];
    for(i=0;i<len;i++)Z.m_ulvalue[i]=0;
    X.Mov(X.Add(Z));
    Z.Mov(Z.Mul(A));
    Y.Mov(Y.Sub(Z));
    }
    if(Y.Cmp(A)==0)X.Mov(X.Add(1));
    if(m_nSign+A.m_nSign==1)X.m_nSign=0;
    else X.m_nSign=1;
    return X;
    }

    //大数求模
    //调用形式:N.Mod(A),返回值:N%A
    //求模与求商原理相同
    CBigInt CBigInt::Mod(CBigInt& A)
    {
    CBigInt X,Y;
    int len;
    unsigned __int64 num,div;
    unsigned long carry=0;
    X.Mov(*this);
    while(X.Cmp(A)>0)
    {      
    if(X.m_ulvalue[X.m_nLength-1]>A.m_ulvalue[A.m_nLength-1])
    {
    len=X.m_nLength-A.m_nLength;
    div=X.m_ulvalue[X.m_nLength-1]/(A.m_ulvalue[A.m_nLength-1]+1);
    }
    else if(X.m_nLength>A.m_nLength)
    {
    len=X.m_nLength-A.m_nLength-1;
    num=X.m_ulvalue[X.m_nLength-1];
    num=(num<<32)+X.m_ulvalue[X.m_nLength-2];
    if(A.m_ulvalue[A.m_nLength-1]==0xffffffff)div=(num>>32);
    else div=num/(A.m_ulvalue[A.m_nLength-1]+1);
    }
    else
    {
    X.Mov(X.Sub(A));
    break;
    }
                   Y.Mov(div);
    Y.Mov(Y.Mul(A));
    Y.m_nLength+=len;
    for(int i=Y.m_nLength-1;i>=len;i--)Y.m_ulvalue[i]=Y.m_ulvalue[i-len];
    for(i=0;i<len;i++)Y.m_ulvalue[i]=0;
    X.Mov(X.Sub(Y));
    }
    if(X.Cmp(A)==0)X.Mov(0);
    return X;
    }


    //暂时只给出了十进制字符串的转化
    int CBigInt::InPutFromStr(CString& str, const unsigned int system=DEC)
    {
           int len=str.GetLength();
    Mov(0);
    for(int i=0;i<len;i++)
           {
                 Mov(Mul(system));
    int k=str[i]-48;
    Mov(Add(k));
     }
     return 0;
    }

    //暂时只给出了十进制字符串的转化
    int CBigInt::OutPutToStr(CString& str, const unsigned int system=DEC)
    {
    str="";
    char ch;
    CBigInt X;
    X.Mov(*this);
    while(X.m_ulvalue[X.m_nLength-1]>0)
    {
    ch=X.Mod(system)+48;
    str.Insert(0,ch);
           X.Mov(X.Div(system));
    }
    return 0;
    }

    //欧几里德算法求:Y=X.Euc(A),使满足:YX mod A=1
    //相当于对不定方程ax-by=1求最小整数解
    //实际上就是初中学过的辗转相除法
    /********************************************************************/
    例如:11x-49y=1,求x

               11 x  -  49 y  =   1      a)
    49%11=5 ->  11 x  -   5 y  =   1      b)
    11%5 =1 ->     x  -   5 y  =   1      c)

    令y=1  代入c)式  得x=6
    令x=6  代入b)式  得y=13
    令y=13 代入a)式  得x=58  
    /********************************************************************/
    CBigInt CBigInt::Euc(CBigInt& A)
    {
    CBigInt X,Y;
    X.Mov(*this);
    Y.Mov(A);
    if((X.m_nLength==1)&&(X.m_ulvalue[0]==1))return X;
    if((Y.m_nLength==1)&&(Y.m_ulvalue[0]==1)){X.Mov(X.Sub(1));return X;}
    if(X.Cmp(Y)==1)X.Mov(X.Mod(Y));
    else Y.Mov(Y.Mod(X));
    X.Mov(X.Euc(Y));
           Y.Mov(*this);
    if(Y.Cmp(A)==1)
    {
    X.Mov(X.Mul(Y));
    X.Mov(X.Sub(1));
    X.Mov(X.Div(A));
    }
    else
    {
    X.Mov(X.Mul(A));
    X.Mov(X.Add(1));
    X.Mov(X.Div(Y));
    }
    return X;
    }

    //蒙哥马利算法求:Y=X.Mon(A,B),使满足:X^A mod B=Y
    //俺估计就是高中学过的反复平方法
    CBigInt CBigInt::Mon(CBigInt& A, CBigInt& B)
    {
    CBigInt X,Y,Z;
    X.Mov(1);
    Y.Mov(*this);
           Z.Mov(A);
    while((Z.m_nLength!=1)||Z.m_ulvalue[0])
    {
    if(Z.m_ulvalue[0]&1)
    {
    Z.Mov(Z.Sub(1));
    X.Mov(X.Mul(Y));
    X.Mov(X.Mod(B));
    }
    else
    {
    Z.Mov(Z.Div(2));
    Y.Mov(Y.Mul(Y));
    Y.Mov(Y.Mod(B));
    }
    }
           return X;
    }


    最后需要说明的是因为在VC里面存在一个__int64类型可以
    用来计算进位与借位值,所以将大数当作0x100000000进制
    进行运算是可能的,而在其他编译系统中如果不存在64位
    整形,则可以采用0x40000000进制,由于在0x40000000
    进制中,对任何两个“数字”进行四则运算,结果都在
    0x3fffffff*03fffffff之间,小于0xffffffff,都可以用
    一个32位无符号整数来表示。事实上《楚汉棋缘》采用的
    freelip大数库正是运用了0x40000000进制来表示大数的,
    所以其反汇编后大数的值在内存中表现出来有些“奇怪”。
     

    June 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可以正常啟動
    May 27

    我也成上班族了,呵呵

    下周就要去上班了,心情激动+紧张...
    开始新的挑战,加油!!!
    我要学会处理生活中的每一个难题,不光是技术,更是一种"被各种类型的事务烦扰而又能心无杂念的去做完每一件事"的能力.
    April 15

    java多线程同步(转载)

    下面是我原来在CSDN论坛上看到的一个贴子,涉及到同步,wait(),notify()等概念的理解,我试着根据原来的一些回复和Think in Java上的相关概念将wait()和notify()这两个方法剖析了一下,欢迎指教.

    问题如下:

    file://分析这段程序,并解释一下,着重讲讲synchronized、wait(),notify 谢谢!
    class ThreadA
    {
      public static void main(String[] args)
      {
        ThreadB b=new ThreadB();
        b.start();
        System.out.println("b is start....");
        synchronized(b)//括号里的b是什么意思,起什么作用?
        {
          try
          {
     System.out.println("Waiting for b to complete...");
     b.wait();//这一句是什么意思,究竟让谁wait?
            System.out.println("Completed.Now back to main thread");
          }catch (InterruptedException e){}
        }
        System.out.println("Total is :"+b.total);
       }
    }


    class ThreadB extends Thread
    {
      int total;
      public void run()
      {
        synchronized(this)
        {
          System.out.println("ThreadB is running..");
          for (int i=0;i<100;i++ )
          {
            total +=i;
            System.out.println("total is "+total);
          }
          notify();
        }
      }
    }

    要分析这个程序,首先要理解notify()和wait(),为什么在前几天纪录线程的时候没有纪录这两个方法呢,因为这两个方法本来就不属于Thread类,而是属于最底层的object基础类的,也就是说不光是Thread,每个对象都有notify和wait的功能,为什么?因为他们是用来操纵锁的,而每个对象都有锁,锁是每个对象的基础,既然锁是基础的,那么操纵锁的方法当然也是最基础了.

     再往下看之前呢,首先最好复习一下Think in Java的14.3.1中第3部分内容:等待和通知,也就是wait()和notify了.

    按照Think in Java中的解释:"wait()允许我们将线程置入“睡眠”状态,同时又“积极”地等待条件发生改变.而且只有在一个notify()或notifyAll()发生变化的时候,线程才会被唤醒,并检查条件是否有变."

      我们来解释一下这句话.
      "wait()允许我们将线程置入“睡眠”状态",也就是说,wait也是让当前线程阻塞的,这一点和sleep或者suspend是相同的.那和sleep,suspend有什么区别呢?

       区别在于"(wait)同时又“积极”地等待条件发生改变",这一点很关键,sleep和suspend无法做到.因为我们有时候需要通过同步(synchronized)的帮助来防止线程之间的冲突,而一旦使用同步,就要锁定对象,也就是获取对象锁,其它要使用该对象锁的线程都只能排队等着,等到同步方法或者同步块里的程序全部运行完才有机会.在同步方法和同步块中,无论sleep()还是suspend()都不可能自己被调用的时候解除锁定,他们都霸占着正在使用的对象锁不放.
       而wait却可以,它可以让同步方法或者同步块暂时放弃对象锁,而将它暂时让给其它需要对象锁的人(这里应该是程序块,或线程)用,这意味着可在执行wait()期间调用线程对象中的其他同步方法!在其它情况下(sleep啊,suspend啊),这是不可能的.
       但是注意我前面说的,只是暂时放弃对象锁,暂时给其它线程使用,我wait所在的线程还是要把这个对象锁收回来的呀.wait什么?就是wait别人用完了还给我啊!
       好,那怎么把对象锁收回来呢?
       第一种方法,限定借出去的时间.在wait()中设置参数,比如wait(1000),以毫秒为单位,就表明我只借出去1秒中,一秒钟之后,我自动收回.
       第二种方法,让借出去的人通知我,他用完了,要还给我了.这时,我马上就收回来.哎,假如我设了1小时之后收回,别人只用了半小时就完了,那怎么办呢?靠!当然用完了就收回了,还管我设的是多长时间啊.

       那么别人怎么通知我呢?相信大家都可以想到了,notify(),这就是最后一句话"而且只有在一个notify()或notifyAll()发生变化的时候,线程才会被唤醒"的意思了.
       因此,我们可将一个wait()和notify()置入任何同步方法或同步块内部,无论在那个类里是否准备进行涉及线程的处理。而且实际上,我们也只能在同步方法或者同步块里面调用wait()和notify().

       这个时候我们来解释上面的程序,简直是易如反掌了.

       synchronized(b){...};的意思是定义一个同步块,使用b作为资源锁。b.wait();的意思是临时释放锁,并阻塞当前线程,好让其他使用同一把锁的线程有机会执行,在这里要用同一把锁的就是b线程本身.这个线程在执行到一定地方后用notify()通知wait的线程,锁已经用完,待notify()所在的同步块运行完之后,wait所在的线程就可以继续执行.

     

     

    java多线程设计wait/notify机制

    多线程之间需要协调工作。例如,浏览器的一个显示图片的线程displayThread想要执行显示图片的任务,必须等待下载线程downloadThread将该图片下载完毕。如果图片还没有下载完,displayThread可以暂停,当downloadThread完成了任务后,再通知displayThread“图片准备完毕,可以显示了”,这时,displayThread继续执行。

    以上逻辑简单的说就是:如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于wait/notify。等待机制与锁机制是密切关联的。例如:

    synchronized(obj) {
        while(!condition) {
            obj.wait();
        }
        obj.doSomething();
    }

    当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait()。

    在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A:

    synchronized(obj) {
        condition = true;
        obj.notify();
    }

    需要注意的概念是:

    # 调用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){
            synchronized(a1) {
              //一次只能有一个线程进入
            }
          }

        3.synchronized后面括号里是一对象,此时,线程获得的是对象锁.例如:

    public class MyThread implements Runnable {
      public static void main(String args[]) {
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt, "t1");
        Thread t2 = new Thread(mt, "t2");
        Thread t3 = new Thread(mt, "t3");
        Thread t4 = new Thread(mt, "t4");
        Thread t5 = new Thread(mt, "t5");
        Thread t6 = new Thread(mt, "t6");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
      }

      public void run() {
        synchronized (this) {
          System.out.println(Thread.currentThread().getName());
        }
      }
    }


     
        对于3,如果线程进入,则得到对象锁,那么别的线程在该类所有对象上的任何操作都不能进行.在对象级使用锁通常是一种比较粗糙的方法。为什么要将整个对象都上锁,而不允许其他线程短暂地使用对象中其他同步方法来访问共享资源?如果一个对象拥有多个资源,就不需要只为了让一个线程使用其中一部分资源,就将所有线程都锁在外面。由于每个对象都有锁,可以如下所示使用虚拟对象来上锁:

    class FineGrainLock {

       MyMemberClass x, y;
       Object xlock = new Object(), ylock = new Object();

       public void foo() {
          synchronized(xlock) {
             //access x here
          }

          //do something here - but don't use shared resources

          synchronized(ylock) {
             //access y here
          }
       }

       public void bar() {
          synchronized(this) {
             //access both x and y here
          }
          //do something here - but don't use shared resources
       }
    }

     

        4.synchronized后面括号里是类.例如:

    class ArrayWithLockOrder{
      private static long num_locks = 0;
      private long lock_order;
      private int[] arr;

      public ArrayWithLockOrder(int[] a)
      {
        arr = a;
        synchronized(ArrayWithLockOrder.class) {//-----------------------------------------这里
          num_locks++;             // 锁数加 1。
          lock_order = num_locks;  // 为此对象实例设置唯一的 lock_order。
        }
      }
      public long lockOrder()
      {
        return lock_order;
      }
      public int[] array()
      {
        return arr;
      }
    }

    class SomeClass implements Runnable
    {
      public int sumArrays(ArrayWithLockOrder a1,
                           ArrayWithLockOrder a2)
      {
        int value = 0;
        ArrayWithLockOrder first = a1;       // 保留数组引用的一个
        ArrayWithLockOrder last = a2;        // 本地副本。
        int size = a1.array().length;
        if (size == a2.array().length)
        {
          if (a1.lockOrder() > a2.lockOrder())  // 确定并设置对象的锁定
          {                                     // 顺序。
            first = a2;
            last = a1;
          }
          synchronized(first) {              // 按正确的顺序锁定对象。
            synchronized(last) {
              int[] arr1 = a1.array();
              int[] arr2 = a2.array();
              for (int i=0; i
                value += arr1[i] + arr2[i];
            }
          }
        }
        return value;
      }
      public void run() {
        //...
      }
    }

     

        对于4,如果线程进入,则线程在该类中所有操作不能进行,包括静态变量和静态方法,实际上,对于含有静态方法和静态变量的代码块的同步,我们通常用4来加锁.

    以上4种之间的关系:

        锁是和对象相关联的,每个对象有一把锁,为了执行synchronized语句,线程必须能够获得synchronized语句中表达式指定的对象的锁,一个对象只有一把锁,被一个线程获得之后它就不再拥有这把锁,线程在执行完synchronized语句后,将获得锁交还给对象。
        在方法前面加上synchronized修饰符即可以将一个方法声明为同步化方法。同步化方法在执行之前获得一个锁。如果这是一个类方法,那么获得的锁是和声明方法的类相关的Class类对象的锁。如果这是一个实例方法,那么此锁是this对象的锁。


      下面谈一谈一些常用的方法:

      wait(),wait(long),notify(),notifyAll()等方法是当前类的实例方法,
       
            wait()是使持有对象锁的线程释放锁;
            wait(long)是使持有对象锁的线程释放锁时间为long(毫秒)后,再次获得锁,wait()和wait(0)等价;
            notify()是唤醒一个正在等待该对象锁的线程,如果等待的线程不止一个,那么被唤醒的线程由jvm确定;
            notifyAll是唤醒所有正在等待该对象锁的线程.
            在这里我也重申一下,我们应该优先使用notifyAll()方法,因为唤醒所有线程比唤醒一个线程更容易让jvm找到最适合被唤醒的线程.

        对于上述方法,只有在当前线程中才能使用,否则报运行时错误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了。如下例:

    /**
     * Title:        Jdeveloper's Java Projdect
     * Description:  n/a
     * Copyright:    Copyright (c) 2001
     * Company:      soho 
    http://www.ChinaJavaWorld.com
     * @author jdeveloper@21cn.com
     * @version 1.0
     */
    import java.lang.Runnable;
    import java.lang.Thread;

    public class DemoThread
        implements Runnable {

      public DemoThread() {
        TestThread testthread1 = new TestThread(this, "1");
        TestThread testthread2 = new TestThread(this, "2");

        testthread2.start();
        testthread1.start();

      }

      public static void main(String[] args) {
        DemoThread demoThread1 = new DemoThread();

      }

      public void run() {

        TestThread t = (TestThread) Thread.currentThread();
        try {
          if (!t.getName().equalsIgnoreCase("1")) {
            synchronized (this) {
              wait();
            }
          }
          while (true) {

            System.out.println("@time in thread" + t.getName() + "=" +
                               t.increaseTime());

            if (t.getTime() % 10 == 0) {
              synchronized (this) {
                System.out.println("****************************************");
                notify();
                if (t.getTime() == 100)
                  break;
                wait();
              }
            }
          }
        }
        catch (Exception e) {
          e.printStackTrace();
        }
      }

    }

    class TestThread
        extends Thread {
      private int time = 0;
      public TestThread(Runnable r, String name) {
        super(r, name);
      }

      public int getTime() {
        return time;
      }

      public int increaseTime() {
        return++time;
      }

    }

        下面我们用生产者/消费者这个例子来说明他们之间的关系:

        public class test {
      public static void main(String args[]) {
        Semaphore s = new Semaphore(1);
        Thread t1 = new Thread(s, "producer1");
        Thread t2 = new Thread(s, "producer2");
        Thread t3 = new Thread(s, "producer3");
        Thread t4 = new Thread(s, "consumer1");
        Thread t5 = new Thread(s, "consumer2");
        Thread t6 = new Thread(s, "consumer3");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
      }
    }

    class Semaphore
        implements Runnable {
      private int count;
      public Semaphore(int n) {
        this.count = n;
      }

      public synchronized void acquire() {
        while (count == 0) {
          try {
            wait();
          }
          catch (InterruptedException e) {
            //keep trying
          }
        }
        count--;
      }

      public synchronized void release() {
        while (count == 10) {
          try {
            wait();
          }
          catch (InterruptedException e) {
            //keep trying
          }
        }
        count++;
        notifyAll(); //alert a thread that's blocking on this semaphore
      }

      public void run() {
        while (true) {
          if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("consumer")) {
            acquire();
          }
          else if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("producer")) {
            release();
          }
          System.out.println(Thread.currentThread().getName() + " " + count);
        }
      }
    }

           生产者生产,消费者消费,一般没有冲突,但当库存为0时,消费者要消费是不行的,但当库存为上限(这里是10)时,生产者也不能生产.请好好研读上面的程序,你一定会比以前进步很多.

          上面的代码说明了synchronized和wait,notify没有绝对的关系,在synchronized声明的方法、代码块中,你完全可以不用wait,notify等方法,但是,如果当线程对某一资源存在某种争用的情况下,你必须适时得将线程放入等待或者唤醒.

    April 10

    [转贴]Java媒体架构(JMF)

     
    Java媒体架构(JMF)是一个令人激动的通用的API,它允许Java开发者用许多不同的方法处理媒体。本指南主要通过使用工作的例子提供一个JMF的一些主要的特征的概述。阅读完本指南后,你将会明白JMF体系结构中的主要播放功能。你同样能正确的使用JMF,使用现存的例子和可为更多特殊功能扩展的源代码。

    本指南包含着以下主题:
    · 下载和安装JMF
    · 主要的JMF类以及它们在JMF体系结构中的应用
    · 播放本地的媒体文件
    · 为媒体的存取和操作制作以和图形用户界面(GUI)
    · 通过网络传播媒体
    · 通过网络接收媒体

    几乎所有的媒体类型的操作和处理都可以通过JMF来实现。全面的讨论JMF所提供的所有特征已经超过了本指南的范围,我们将使用三个简单的媒体应用程序来学习此框架的构建模块。通过这个方法,本指南将为你未来学习和实施更多特殊的应用提供准备。
    我应该使用此指南吗?

    本指南会带你学习使用JMF工作的基础。为完成这些,我们会创建三个的独立工作的例程序。每个例子都会建立前一个例子的基础上,显示JMF功能性的不同方面。
    在本指南中的例子假定你曾经使用过并且已经熟悉了Java程序语言。除了Java核心和JMF的类之外,我们会使用一些Java AWT和Swing类(用于创建GUI),也会有一些Java网络类(用于在网络中传输媒体)。对GUI和网络类一些熟悉有助于你更快的明白观点和这里的例子,但并非是阅读本指南必须的。

    我们将学习的例程序如下
    · 一个简单的音频播放器(JMF的HelloWorld应用):这个字符界面的播放器通过在命令行中简单的输入媒体文件的名字就可以播放大多数的音频类型。此音频播放器的演示大体上显示了JMF的特有的类。
    · 一个图形界面的媒体播放器:我们将使用JMF内置的接口组件来建立图形界面,所以在此练习中必须有一些图形界面的编程经验。这个媒体阅览器演示使用了一些Java AWT和Swing类来为用户显示图形组件。
    · 一个媒体广播应用:此应用程序允许一个本地媒体文件通过网络传播。此程序能灵活的使媒体只传输到指定的网络节点,或者传输到一个子网络中的所有节点。此演示使用了一些Java的网络APIs来在网络中传输媒体。
    作为第三个练习的一部分,我们将修改图形界面的播放器,让其能接收并且播放媒体。
    跳至23页观看Resources,文章,指南,和其他参考书目的列表,这会帮助你学习到更到关于此指南包括的主题。

    安装需求
    要运行此指南中的例程序,你需要如下的工具和组件:
    ·  Java 2 平台,标准版,编译和运行演示程序
    ·  Java媒体框架,版本2.1.1a或者更高
    · 一块已经安装并且配置号的适当的声卡
    · 一台或者多台测试机器
    · 演示的源代码文件在mediaplayer.jar中
    最后的一个演示应用显示了JMF在网络中的应用。如果需要,此演示能运行在一个独立的机器上,使用此机器即是传输方也是接收方。可是要观察到在网络中使用JMF的所有功能,你仍然需要至少两台联网的机器。
    在23页中的Resources可下载Java 2平台,完整的源代码文件,以及其他一些完成本指南所需要的工具。

    下载安装文件
    将JMF安装到你的计算机中的第一步是在JMF的主页中下载安装文件,它同样包括了JMF源代码和API文档的链接。23页的Resources中有下载JMF的链接。
    目前,JMF有Windows, Solaris, Linux等版本,以及可运行在任何装有虚拟机的计算机上一个纯Java版本。为了增加性能,你需要下载一个与你操作系统所适应的版本。任何在一个操作系统JMF版本下书写和编译的代码都可以方便的移植到另外的操作系统上。例如,如果你下载了一个Solaris版本的JMF并且编译了一个类,这些类就可以在Linux上使用,不会有任何问题。
    作为选择,你可以选择下载纯Java版本,或者跨平台版本的JMF。这些版本没有使用操作系统特有的库文件。如果没有合适的JMF版本适合的操作系统,那么跨平台版本就是一个不错的选择。

    安装JMF
    下载完JMF安装程序后,双击安装程序的图标。
    大部分安装程序都会有个选项,安装本地库到系统目录中;例如,Windows版本安装程序会有一个选项“Move DLLs to Windows/System directory.”。最好将此选项选中,因为它能确保这些操作系统的库文件能正确的安装
    在安装的过程中,你还需要选择项目来更新系统的CLASSPATH和PATH变量。如果这些选项被关闭,那么在你编译和运行本指南的例程序的时候就需要在classpath中引入JMF的jar文件。

    关于作者
    Eric Olson在Retek Inc工作的软件工程师。它在Java平台上有四年的工作经验,并且在不同的基于Java的技术上富有经验,包括JMF, Jini, Jiro, JSP, servlets, and EJBs。Eric毕业于St. Paul, MN的St. Thomas大学,获得计算机科学的学位。他在IBM的SanFrancisco项目组工作,负责WebSphere商业组件。他同时再为Imation Corp.工作,负责存储应用。现在,他正在开发零售行业的基于web的软件解决方案。再业余的时间,Eric和Paul
    Monday在Stereo Beacon上合作—一个分布式的点对点的基于JMF的媒体播放器。联系
    Eric zpalffy@yahoo.com.

    第二节. 一个简单的音频播放器
    浏览
    在本节中,我们将进行创建一个简单的音频播放器的第一个练习。本例将介绍Manager类和Player接口,中两个都是建立大多数基于JMF应用的重要部分。
    本例的功能目标是在字符界面下播放本地的音频文件。我们将学习此源代码,并了解每一行所做的任务。完成本节后,你将会有一个基于JMF的可播放包括MP3, WAV, AU等多种音频文件的演示程序。
    在本练习后的源代码分类种可查询文件SimpleAudioPlayer.java。

    引入必要的类
    SimpleAudioPlayer类中包括了一些调用,在其前几行中需要引入所有必要的类:
    import javax.media.*;
    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    import java.net.MalformedURLException;

    The javax.media包是由JMF定义的多个包之一。javax.media是一个核心包,包括了定义Manager类和Player接口等。本节中,我们主要学习Manager类和Player接口,其余的javax.media类放在后面的章节中。
    除了引入javax.media声明外,以上的代码片断引入了一些创建媒体播放器的输入的声明。

    Player接口
    在下面的代码片断中,创建一个公共类SimpleAudioPlayer并举例定义一个Player变量:
    public class SimpleAudioPlayer {
    private Player audioPlayer = null;

    术语Player听起来由点熟悉,因为它是建立在我们公用的音频或者视频播放器的基础上的。事实上,这个接口的例子就像是当作它们的真实的副本。 Players揭示了一个实体上的媒体播放器(如立体音箱系统或者VCR)涉及到功能上的方法。例如,一个JMF媒体播放器可以开始和结束一个媒体流。在本节种,我们将使用Player的开始和结束功能。

    在一个文件上创建一个Player
    使用JMF获得一个特定媒体文件的Player实例非常简单。Manager类在JMF中如同一个工厂制作许多的特殊接口类型,包括Player接口。因此,Manager类的责任就是创建Player实例,如下例:
    public SimpleAudioPlayer(URL url) throws IOException,
    NoPlayerException,
    CannotRealizeException {
    audioPlayer = Manager.createRealizedPlayer(url);
    }
    public SimpleAudioPlayer(File file) throws IOException,
    NoPlayerException,
    CannotRealizeException {
    this(file.toURL());
    }


    如果你看完本节的代码,你可以注意到Manager类包含了创建一个Player实例的其他方法。我们会研究其中的一些,如在后面的章节中的DataSource或者MediaLocator的实例化。

    Player的状态
    JMF定义了大量的一个Player实例可能存在的不同状态。如下:
    · Prefetched
    · Prefetching
    · Realized
    · Realizing
    · Started
    · Unrealized

    使用这些状态
    因为使用媒体常常是资源非常密集的,由JMF对象揭示的许多方法都是不闭塞的,允许一系列事件监听的状态改变的异步通知。例如,一个Player在它可以启动之前,必须经过Prefetched和Realized状态。由于这些状态的改变都需要一些时间来完成,JMF媒体应用可以分配一个线程来初始化创建 Player实例,然后再继续其他的操作。当Player准备就绪的时候,它会通知应用程序其状态已经改变。

    在一个如同我们的这样简单的程序中,多功能性的类型并不是很重要。处于这个原因,Manager类也提供了一些创建Realized player的有用方法。调用一个createRealizedPlayer()方法来阻塞调用线程,直到player达到Realized状态。为了调用一个无阻塞的创建player的方法,我们在Manager类中使用了一个createPlayer()方法。下面的一行代码中创建了一个我们需要在例程序中使用的
    Realized player:
    audioPlayer = Manager.createRealizedPlayer(url);


    启动和停止Player
    设定一个Player实例的启动或是停止就如同调用Player的一个简单的认证方法,如下所示:
    public void play() {
    audioPlayer.start();
    }
    public void stop() {
    audioPlayer.stop();
    audioPlayer.close();
    }

    调用SimpleAudioPlayer类中的play()方法来实现调用Player实例的start()方法。调用此方法后,你能听到本地的喇叭的声音文件。同样的,stop()方法使player停止并且关闭掉Player对象。

    对于读取和或者播放本地媒体文件来说,关闭Player实例释放所有资源是一个有用的方法。因为这是一个简单的例子,关闭Player是终止一个会话可接受的方法。但是在实际的应用中,你需要小心的确认在除掉Player之前必须要关闭掉。一但你已经关闭掉player,在再次播放一个媒体之前你必须要创建一个新的Player实例(等待它的状态改变)。

    建立一个SimpleAudioPlayer

    最后,这个媒体播放应用程序要包含一个可以从命令提示行中输入命令而调用的main()方法。在此main()方法中,我们将调用创建SimpleAudioPlayer的方法:
    File audioFile = new File(args[0]);
    SimpleAudioPlayer player = new SimpleAudioPlayer(audioFile);


    在播放音频文件之前的唯一的一些事情就是调用已经创建的音频player的方法play(),如下所示:

    player.play();


    要停止和清除掉音频player,在main()方法中也应该有如下调用:
    player.stop();


    编译和运行SimpleAudioPlayer
    通过在命令提示行输入javac SimpleAudioPlayer.java来编译例程序。所创建的文件SimpleAudioPlayer.class在当前工作目录中。
    然后在命令提示行中键入如下命令来运行例程序:
    java SimpleAudioPlayer audioFile

    将audioFile替换成你本地机器上的音频文件。所有的相对文件名都试相对于当前的工作目录。你会看到一些当前正在播放文件的标志信息。要终止播放,按下回车键。
    如果编译失败,确认JMF的jar文件已经正确的包含在CLASSPATH环境变量中。


    第三节. JMF用户界面组件
    播放视频
    在前一节中,我们学习了建立一个通过字符界面播放音频文件的应用程序。JMF中一个最重要的特点就是你不需要为了配置媒体播放器而去了解媒体文件的格式;一切都内置了。举一个例子,再我们前面的例子中,需要使用MP3格式的时候,我们不需要让应用程序为一个MP3文件建立一个特殊的Player。
    如同你将会再本节所见到的,对于视频文件的操作同样有效。JMF有所有媒体文件类型接口的详细资料。
    处理视频媒体与音频最大的不同就是,我们必须建立一个能播放视频的显示屏幕。幸运的是,JMF能处理许多的这些资料。如同再上例一样我们会建立一个Player对象,并且使用很多的可视组件来直接从JMF对象中创建我们的可视的媒体浏览器。
    本节中,我们将学习两个例程序。In this section, we&aposll walk through the second example application. 请再后面的练习的源代码分布中查阅MediaPlayerFrame.java。

    关于例子
    在本节中,我们将创建一个能显示和运行本地音频和视频媒体的应用程序。作为练习的一部分,我们将研究JMF内置的一些GUI组件。熟悉AWT和Swing将有助于你理解本例,但这并不是必须的。除非需要直接涉及到JMF的GUI组件,或者我们是不会详细介绍源代码的。你可以在源代码的注释中找到这里未涉及的详细说明。
    本例中我们使用的许多概念,类和方法都和第一个例子的类似。建立Player的基本操作大都一样。最大的不同就是我们需要对Player对象专研更深一点,特别当需要从Player获取媒体信息的时候。

    如何开始
    视频播放器例子被设计得如同音频播放例子一样通过命令行来运行,但是本例需要建立在GUI基础上。如同在上节一样,我们先通过媒体文件名调用应用。然后,应用程序显示一个带有可操作媒体组件的窗体。
    在MediaPlayerFrame开始的一行中我们定义了类并扩展自,javax.swing.Jframe类。这就是使媒体播放器如同一个在桌面上的单独窗体的方法。任何客户机程序创建了本媒体播放对象后都可以通过调用Jframe类中定义的show()方法来显示。
    下面是一个MediaPlayerFrame正在播放MPEG电影的屏幕截图:

    获取GUI组件
    Player界面有一些方法来获取已选择可视组件的涉及。在MediaPlayerFrame中,我们使用如下组件:
    · player.getVisualComponent()是一个播放所有视频媒体的可视组件。
    · player.getControlPanelComponent() 是一个操作时间轴的可视组件(包括开始,停止,回放),也包含了一些媒体流的有用信息。
    · player.getGainControl().getControlComponent() 是操作音量(增加)的可视组件。getGainControl()方法返回一个GainControl实例,可用于改变节目的增加等级。

    使用可视化组件
    上面的界面方法都返回一个java.awt.Component类的实例。没个实例都视可加载到我们窗体上的可视组件。这些组件都与Player有直接的联系,所以在这些组件上的所有可视元素的处理都会产生Player播放媒体后相应的变化。
    在我们将这些组件加入到我们的窗体的之前,必须要保证它们不为空。因为并不是所有的媒体播放器包括每一种可视组件,我们只需添加相关播放器类型的组件。比如,一般来说一个音频播放器没有可视组件,所以getVisualComponent()就要返回空。你不会想在音频播放器窗体上添加可视组件的。

    获得媒体的特殊控制
    一个Player实例也可以通过getControl()和getControls()方法来暴露其控制,getControls()返回一个控制对象集,而getControl()返回一个控制。不同的播放器类型可选择为特殊的操作来暴露控制集去指定的媒体类型,或者用于获取该媒体的传输机制。如果你在写一个只支持某些媒体类型的播放器,你需要依靠某些在Player实例中可用Control对象。
    由于我们的播放器是非常抽象的,被设计于播放多种不同媒体类型,我们简单的为用户暴露所有的Control对象。如果找到任何扩展的控制集,我们就可使用getControlComponent()方法来增加相应的可视控件到标签面板上。通过这个办法,用户就可以观察播放器上的所有组件。以下代码片断将所有的控制对象暴露给用户:
    Control[] controls = player.getControls();
    for (int i = 0; i< controls.length; i++) {
    if (controls[i].getControlComponent() != null) {
    tabPane.add(controls[i].getControlComponent());
    }
    }

    为了使一个真实的应用程序能用Control实例做一些有用的事(除了能显示可视组件之外),应用程序需要知道该Control的特殊类型,并分配它。此后,应用程序就可使用这些control来控制媒体节目了。例如,如果你知道你经常使用的媒体暴露 javax.media.control.QualityControl类型的Control,你能使用QualityControl界面,之后在 QualityControl界面上通过调用各种方法来改变性质设定。

    使用一个MediaLocator
    在我们新的基于GUI的媒体播放器和我们的第一个简单播放器之间最大的不同就是,我们使用一个MediaLocator对象而不是URL来创建Player实例,如下所示:
    public void setMediaLocator(MediaLocator locator) throws IOException,
    NoPlayerException, CannotRealizeException {
    setPlayer(Manager.createRealizedPlayer(locator));
    }

    我们将在稍后的章节中讨论这个变化的原因。目前,在网络上资源站点上,关于MediaLocator对象和URL的描述被认为是非常相似的。事实上,你可以从一个URL创建一个MediaLocator,也可以从MediaLocator获取到URL。我们的新媒体播放器一个URL中创建一个 MediaLocator,并使用该MediaLocator通过文件创建了一个Player。

    编译和运行MediaPlayerFrame
    通过在命令提示行输入javac MediaPlayerFrame.java来编译例程序。在工作目录下将创建一个名为MediaPlayerFrame.class的文件。
    在命令提示行中键入如下来运行例程序:
    java MediaPlayerFrame mediaFile

    你需要用你本机上的一个媒体文件来替换掉mediaFile(音频或者视频文件都可以)。所有的相对文件名都是相对于当前工作目录。你会看见一个显示控制媒体文件的GUI控制集的窗口。欲了解JMF支持的音频和视频文件列表,在23页的资源。
    如果初始编译时失败,请确认JMF的jar文件已经包含在当前的CLASSPATH环境变量中。

    MediaPlayerFrame在行动
    在本节前你看见的一个视频播放器正在播放MPEG视频文件的屏幕截图。下面的屏幕截图显示了一个音频播放器正在播放一个MP3文件:
    要更多的学习本练习中的例子,查看完成的MediaPlayerFrame源代码。

    第四节. JMF概念
    JMF体系结构
    你曾见过了使用JMF播放本地媒体文件是多么的容易,现在我们将后退一步,来看看一幅是如何通过JMF创建了如此成熟的基于媒体的应用程序的大的画面,是如何通过JMF创建了如此成熟的基于媒体的应用程序。全面的了解JMF体系结构是没有意义的,本节将给你一个大体的概念,关于高级的JMF组件是如何组合起来创建想得到的东西。
    JMF的组件结构非常的灵活,它的组件一般可以分成三个部分:
    · Input描述某种被用于在进程休息的时候作为一个输入的媒体。
    · process执行某些输入上的活动。一个过程有一个明确的输入和输出。大量的过程可用, 能被用于一个输入或者一批输入。这些过程能被联系起来,一个过程的输出被用于另外一个过程的输入。在这种风格中,大量的过程可能被应用于一个输入。(这段期间是可选择的——我们开始的两个例子没有包含真正的数据过程,只有一个来自文件的输入和一个通过Player的输出。)
    · Output 描述了媒体的某些目的地。

    从这些描述中,你可以想象到JMF组件体系结构听起来就好像在一个典型的立体声系统或者VCR之后。很容易设想到,使用JMF就如同打开电视或者在立体声音箱系统下调节声音的风格。例如,录制喜爱的电视节目的简单的动作能在这些组件的基础中:
    · Input 是电视广播流,在同一个频道运输音频和视频。
    · Process 是一个记录设备(就是,一个VCR或者许多的数字设备)转换模拟或者数字音频视频广播流成适合复制到磁带或其他媒体上的格式。
    · Output 是记录已格式化轨迹(音频和视频)到某些类型的媒体上。

    JMF资料处理模式
    以下图片说明了JMF数据处理模块并对每个类型给出了例子:
    使用此模式,很容易明白我们前面的两个例子,从文件中输入音频和视频并输出到本地计算机上。在后面的章节中,我们也会谈论一些通过传播和接收音频媒体的JMF网络功能。

    处理模型例子
    将JMF 的输入,处理和输出模式联系起来,我们能开始想象许多基于媒体的操作都可能通过JMF完成。一个例子,转换一种媒体类型为其他类型并将其输出存储到一个新的文件。举一个例子,我们想要在不损坏原始文件的前提下转化一个WAV格式的音频文件为MP3格式。以下的过程模式插图,就是我们将开始执行转换的步骤:
    本例的输入是一个WAV文件。它被一个媒体格式转换工具加工,并输出到一个新的文件。现在,让我们看看JMF API中的这个模式的每一步。我们使用输入,处理和输出模式作为概念上的路标。

    JMF输入
    再JMF中,一般由一个MediaLocator对象来描述一个输入。如先前规定的,
    MediaLocator的外观和行为都非常象一个URL,这样它可以唯一确定网络上的一个资源。事实上,使用一个URL来创建一个MediaLocator是完全可能的;我们在前面的两个例子中就是这样做的。
    为了我们的媒体转换例子,我们需要建立一个MediaLocator来描述最初的WAV文件。如同我们将在后面的章节中见到的,一个 MediaLocator也可以用于描述一个跨越网络中媒体流。在这个案例中,MediaLocator会描述传播的URL――很像一个被URL指定的在 Web上的资源,用于取代指定一个本地文件系统的文件来建立MediaLocator。

    一个MediaLocator和一个URL之间的不同
    要成功的建立一个URL对象,需要适当的java.net.URLStreamHandler安装于系统中。这个流处理的用途是能够处理被URL描述的流类型。一个MediaLocator对象并没有这个需要。例如,我们的下个应用程序将使用实时传输协议(RTP)在网络上传输音频。由于多数的系统都未为 RTP协议安装一个URLStreamHandler,所以创建一个URL对象会失败。在这个应用中,只有MediaLocator对象会成功。
    要理解更多关于URL对象以及创建和注册一个URLStreamHandler的信息,查阅JDK帮助文档(查看23页资源)。

    JMF处理机
    当我们使用JMF的时候,应用程序的处理机组件被Processor接口实例描述。你需要已有些熟悉Processor,它扩展至Player接口。由于 Processor继承直Player接口,它同样也从Player继承所有可用属性。另外,Processor增加了两个属性:Configuring 和Configured。这些扩展的属性(和与之关联的方法)用于Processor从输入流收集信息时的通信。
    在我们的最后的例程序中,我们将建立一个Processor用于将MP3编码格式的音频转换成适合在网络上传播的格式。在稍后的板块中我们会讨论创建一个简单的Processor的步骤。

    JMF输出
    有少许的方法用于描述JMF中处理模式的输出状态。最简单的(并且我们将在最后一个例子中使用的)是javax.media.DataSink接口。一个 DataSink读取媒体内容并且将其传送到一些目的地。本节中最开始的音频格式转换过程中,MP3(输出)文件将被DataSink描述。在我们最后一个例子中,我们将使用一个DataSink在实际上完成网络中传播音频媒体的工作。一个DataSink是在Manager类中,由指定一个 DataSource(输入到DataSink)和一个MediaLocator(输出到DataSink)完成的。
    一个DataSource实例描述可用于Players,Processors和DataSinks的输入数据。一个处理机的输出也被描述成一个DataSource对象。
    这就是为什么处理器能彼此联系起来,在同一媒体数据中完成多种操作。这也是来自Processor的输出能作为输入被Player或者DataSink使用的原因(它可将媒体传递到输出目的地)。
    一个DataSink的最后目的文件由一个MediaLocator对象说明。如同前面一样,MediaLocator描述一个网络资源;这就是媒体流将被传递的地方。

    第五节.传播接收媒体
    JMF和实时传输协议(RTP)
    许多的友善网络的特征直接建立在JMF中,这些使为客户端程序通过网络传输和接收媒体非常容易。当在一个网络上的一个用户想要接收任何种类的媒体流的时候,它不需要在观看媒体前等待全部的广播下载到机器上;用户可以实时的观看广播。在流媒体中些提出了这个概念。通过流媒体,一个网络客户端能接收到其他机器上广播的音频,甚至获取正在发生的实况视频广播。
    在IETF RFC 1889中定义了实时传输协议(RTP)。发展在快速和可靠的状态下通过网络传输时间极其敏感的数据,RTP在JMF中用于提供给用户向其他网络节点中传输媒体流的方法。
    在本节中,我们将学习我们的最后一个例程序。这里,你将学习到如何传输一个存储在一台机器上的MP3文件到另外的在同一个网络的机器上去。实际的MP3源文件并不从主计算机上移除,它也不使复制到其他机器上去;事实上它将会转换成能使用RTP传输的文件格式并通过网络发送。一旦被一个客户端接收到,源文件(现在是RTP信息包的形式)可以再次传输,这一次是在接收机器上可播放的一种格式。
    在MediaTransmitter.java文件中源代码查看学习以下练习。

    设置处理模式
    我们可以在前面的章节中定义的处理模式的基础下来讨论我们的最终的例子。在传输机器上,处理模式看起来像这样:
    事实上,MediaTransmitter对象源代码包括了以下三行:
    private MediaLocator mediaLocator = null;
    private DataSink dataSink = null;
    private Processor mediaProcessor = null;


    这三个实例变量可以直接映射到前面的处理模式图表,如下:
    ·  mediaProcessor变量是我们的处理器;它将负责转换音频文件从MP3文件模式到一个适合通过RTP协议传输的格式。
    ·  dataSink变量是我们的输出块。
    · 当我们建立DataSink时我们需要指定一个MediaLocator,它是DataSink的目的文件。

    当我们通过运行DataSink我们的处理过的媒体,它将传输到我们在MediaLocator中指定的地点。

    RTP MediaLocator
    在前面的两个练习中,我们通过从文件中获得的一个URL建立了MediaLocator实例。在本练习中,我们必须建立一个MediaLocator来描述网络上媒体传播输出流;换句话说,我们必须创建一个能我们的音频传播的目的地的 MediaLocator。一个RTP MediaLocator符合如下规则,看起来就像一个典型的URL:
    rtp://address:port/content-type


    让我们看看上面URL规范的每一段:
    · address 是将传输的媒体的地址。以单播的模式传输(一个专用IP地址),地址将会是有意接收的机器的IP地址。以广播的模式传播(到子网中的所有机器),地址将会是以255作为最后的一块的子网地址。举个例子,如果我再子网中可指定地址为192.168.1和想要传播到子网中的所有节点,我可以指定 192.168.1.255作为地址;这样允许子网中的每个节点监听广播媒体。
    · port 必须是被传输者和接收者都允许的一个端口。
    · content-type 是媒体流类型。在我们的案子中,这个将会是音频。
    下面的一个简单的RTP传播MediaLocator例子会让所有在指定网络中的机器接收到媒体流:
    rtp://192.168.1.255:49150/audio


    创建一个处理机
    在setDataSource()方法中我们首先要做的就是创建一个Processor实例。
    下面的Processor的职责是转换MP3音频媒体为一个RTP来表示:
    public void setDataSource(DataSource ds) throws IOException,
    NoProcessorException, CannotRealizeException, NoDataSinkException {
    mediaProcessor = Manager.createRealizedProcessor(
    new ProcessorModel(ds, FORMATS, CONTENT_DESCRIPTOR));
    在Manager类中,我们能创建一个Processor对象,通过两种方法中的一种:
    createProcessor ()或者createRealizedProcessor()。你很可能会注意到这两个方法样式的显示和前面例子中创建一个Player的方法很相似。在目前的例子中,我们将创建一个已实现的Processor。我们这样做是因为我们使用的应用非常简单,在Processo处于Realized状态时我们不需要关心任何真实的工作。

    创建一个ProcessorModel
    创建一个已实现的Processor,我们需要创建一个为Processor描述输入和输出媒体类型的ProcessorModel实例。为了创建ProcessorModel,我们需要下面的一些:
    · 一个DataSource,将被处理的媒体(输入文件)。
    · 一个javax.media.Format数组,描述输入媒体的格式。
    · 一个javax.media.protocol.ContentDescriptor实例,为我们的处理机描述输出格式。传送者的DataSource是通过一个参数传递到此方法。

    定义输入和输出格式
    因为我们的MediaTransmitter类会被时常用于将输入媒体格式(MP3)转换成一种输出格式(音频RTP),中学对象被定义成静态。我们创建一个新的javax.media.format.AudioFormat实例用于描述媒体输入类型(在java帮助文档中查看可用格式)。这就是我们的处理机可以获取MP3音频文件的原因。
    我们也创建一个javax.media.protocol.ContentDescriptor实例来描述想要处理机输出的。在我们的案子中,这是一个RTP媒体流。

    这就是为什么我们的处理机可以只制造RTP流。
    下面的代码片断显示了我们如何设置格式和内容描述符变量,用于创建ProcessorModel对象:
    private static final Format[] FORMATS = new Format[] {
    new AudioFormat(AudioFormat.MPEG_RTP)};
    private static final ContentDescriptor CONTENT_DESCRIPTOR =
    new ContentDescriptor(ContentDescriptor.RAW_RTP);


    连接输入,处理机和输出
    现在我们有一个处于Realized状态的Processor,我们需要设置DataSink以能实际上传播RTP媒体。创建DataSink是简单的大概使用另外一个调用给Manager对象,如下所示:

    dataSink = Manager.createDataSink(mediaProcessor.getDataOutput(),
    mediaLocator);


    createDataSink ()方法获取新Processor的输出(作为一个DataSource参数)和MediaLocator对象,我们和MediaTransmitter 对象同时建立的。通过这样,你能开始我们的不同的组件是如何在处理模式中联系起来的:我们从一个Processor中获取输出并使用他们作为输入到其他组件。在这个特殊的应用中,Processor输出用于传输媒体的DataSink的一个输入。

    创建一个DataSource实例
    在这点上,我们全部都是做和设置我们的媒体播放器的广播传输。
    我们需要创建DataSource对象,我们用于创建处理机(就是,在我们的MediaTransmitter中,参数传递到setDataSource()方法)。下面是创建一个DataSource实例的代码:
    File mediaFile = new File(args[1]);
    DataSource source = Manager.createDataSource(new MediaLocator(
    mediaFile.toURL()));


    这段代码是在MediaTransmitter对象中的vmain()方法。这里我们通过从命令行输入的第二个参数创建一个File对象。我们通过文件创建一个MediaLocator,而后通过位置创建一个DataSource。这个新近创建的DataSource是一个涉及到传送者的输入文件。我们能使用这个DataSource初始化传输者。

    开始和停止MediaTransmitter
    我们通过调用其中的startTransmitting()方法来开始MediaTransmitter,如下所示:
    public void startTransmitting() throws IOException {
    mediaProcessor.start();
    dataSink.open();
    dataSink.start();
    }


    这个方法首先开启处理机,然后打开并启动DataSink。在这个调用后,接收机器就可在媒体传送者上监听。
    停止传输者是非常简单的。以下代码将DataSink和Processor都停止和关闭掉:
    public void stopTransmitting() throws IOException {
    dataSink.stop();
    dataSink.close();
    mediaProcessor.stop();
    mediaProcessor.close();
    }


    编译和运行MediaTransmitter
    通过在命令行中输入javac MediaTransmitter.java来编译例程序,可在你的工作目录中生成一个同名的.class文件。
    要运行例程序,在命令提示行中输入以下代码:
    java MediaTransmitter rtpMediaLocator audioFile


    此例将创建一个myAudio.mp3文件的媒体广播。不要忘记将rtpMediaLocator替换成一个媒体传输的RTP URL,如同先前讨论的。
    你同样也需要将audioFile替换成你本机的音频文件名。
    所有的相对文件名都是相对于当前工作目录的。你会看见一些信息标志正在播放的文件。按下Enter键来停止播放。

    为传送者的一个例命令行交互如下:
    java MediaTransmitter rtp://192.168.1.255:49150/audio myAudio.mp3
    如果初始编辑失败,确定JMF的jar文件包含CLASSPATH环境变量中。要近一步探索本程序和练习,请查阅MediaTransmitter源代码。

    接收传输的媒体
    现在你可能会问,“如果没有人可以看或者收听的话,这个传播媒体有什么好的?”
    幸运的是,设定一个接收传播媒体的客户端只需要对我们在第二个例程序的MediaPlayerFrame源代码做很小的改动。
    MediaPlayerFrame类需要一个很小的调节来接收和播放音频文件。在main()方法中,你需要注释掉如下的一行:
    mpf.setMediaLocator(new MediaLocator(new File(args[0]).toURL()));

    并且输入如下的一行:
    mpf.setMediaLocator(new MediaLocator(args[0]));

    这个简单的改动允许我们通过String来创建一个MediaLocator对象,而不是通过创建一个File来创建MediaLocator。
    其他代码都一样。

    指定RTP URL
    在12页的说明编译和运行MediaPlayerFrame介绍了如何编译和运行MediaPlayerFrame例程序。这唯一的不同就是你需要为传输者指定RTP URL。为接收者的例命令行交互如下:
    java MediaPlayerFrame rtp://192.168.1.255:49150/audio


    运行网络媒体传送者的注意事项
    如果你在网络上只有权使用一台机器,你仍然可以运行传输程序。当你启动传送程序的时候,你可以即使用RTP URL传输地址,也可指定你工作的机器的机器地址。为了能够调节传输,在开始前接收者必须使用精确的同样的RTP URL。
    如果你运行本例真实的网络版本,每台你使用的机器都需要安装JMF,不论是传输还是接收媒体流。这是必须的,因为不论是传送程序还是接收程序都大量的使用了JMF的API。
    在任一个案子中,确认在指定的RTP URL中使用了相同的地址和端口;否则媒体传输是不会工作的。

    第六节. 约束和资源
    摘要
    我希望本指南能给你提供如何使用JMF的API的有用的浏览。
    我们建立了三个小的应用程序来播放本地的音频和视频,也通过网络传播和接收媒体。这些应用程序的源代码中包含了很多的javadoc样式的注释。这就有助于你理解你剩余的问题。
    许多JMF的主题在本指南中并没有涉及。实际上,我们更关注JMF的基本概念和应用;在此基础上,我们能轻易地扩展学习的其他范围。要更深入JMF的应用程序,你可能想要学习下面的面板中所提到的主题。更近一步的阅读本指南中的主题,查阅23页的资源。

    高级主题
    大量的值得做的练习在本指南的范围之上。在简单的说明之下自己更进一步的学习,你可以扩展我们的应用程序代码,也可以反展你的JMF相关知识。使用以下的练习开始:
    · 媒体捕获:JMF包含了丰富的API来捕获媒体数据。如果你对使用JMF捕获媒体感兴趣,你可以使用 javax.media.CaptureDeviceManager类和javax.media.protocol.CaptureDevice接口的 API来学习。对于一个高级的练习,考虑使用CaptureDeviceManager和CaptureDevice接口来增加媒体捕获功能到媒体播放应用程序的GUI版本上。
    · 会话管理:由于本指南是一个JMF的说明,我们使输出表现非常的简单,仅仅实现了javax.media.DataSink输出。
    另外的输出表示是使用javax.media.rtp.SessionManager。这个管理类允许客户端创建并监视他们的RTP流和连接。通过 SessionManager并随后创建流,它可能非常的接近监视RTP会话。作为一个高级的练习,转换我们的地三个演示程序来使用 SessionManager,然后监听流出的RTP流已经哪些客户端在收听。
    · 使用JMF的多点传送:我们的广播演示应用程序说明了如何传送一个网络的媒体到另外一个网络的一或多台机器上去。它也可能使用JMF中的多点传输协议来提供给更复杂,多用户的网络。
    JMF用户指南提供了一个使用JMF的多播协议的更深入的探讨。更进一步追踪本主题查看23页资源。
    · 传输视频: 我们的最后一个演示应用程序着眼于如何传输一个MP3音频文件,但是JMF也能够通过网络传递视频。关注API文档中的Format和ContentDescriptor类获得如何使用的更好的方法。
    · 导入/导出RTP媒体流: JMF同样允许将RTP流保存为文件以便将来使用。举一个实例,一个远程电信会议可以保存下来以后再看。
    由于流已经保存再RTP格式中,已经不需要再次转换,这样可导致传输程序的性能改进。通过一个文件而不是URL来设置DataSink对象中输入/输出MediaLocator。你会再JMF用户指南中发现更深层次的主题探讨。

    资源
    JMF
    · 下载mediaplayer.jar,本指南中使用的完整的例源代码。
    ·  JMF主页 (http://www.javasoft.com/jmf)是最好的探讨JMF更多信息的资源。
    · 你可以找到JMF说明书(http://java.sun.com/products/java- media/jmf/2.1.1/specdownload.html),再Java开发者联盟上包括API文档和JMF用户指南。你必须有权使用所有的这些资源,如果你想做任何更深入的JMF编程的话。
    · 官方的JMF支持文件格式 页面
    (http://java.sun.com/products/java-media/jmf/2.1.1/formats.html) 列出了所有可为JMF辨识并播放的文件格式。此文件格式页面也包括了学习更多关于捕获设备和RTP格式的参考。
    · MPEG-4 Video for JMF (http://www.alphaworks.ibm.com/tech/mpeg-4), 来自IBM
    alphaWorks, 是一个JMF的视频编解码器。
    RTP
    ·  IETF RTP RFC (http://www.ietf.org/rfc/rfc1889.txt) 非常详细的描述了RTP协议。
    · 查看 JMF API Guide
    (http://java.sun.com/products/java-media/jmf/2.1.1/specdownload.html) ,有许多有关于RTP协议和描述以及它是如何在JMF上应用的。
    · 哥伦比亚大学有一个比较有用的RTP FAQ(http://www.cs.columbia.edu/~hgs/rtp/faq.html).
    Java技术
    ·  Java 2 Platform, Standard Edition (http://java.sun.com/j2se/) 可从sun公司获得。
    · sun的指南关于JFC/Swing (http://java.sun.com/docs/books/tutorial/uiswing/index.html)
    和 AWT (http://java.sun.com/docs/books/tutorial/information/download.html#OLDui) 是非常好的能学习到很多关于Java程序语言中GUI编程的好地方。
    · 另外一个sun指南学习network programming 基础
    (http://java.sun.com/docs/books/tutorial/networking/index.html)。
    多点传输协议
    · Explicit Multicast (XCAST)
    (http://oss.software.ibm.com/developerworks/opensource/xcast/) 是IP多点传输的一种形式,为非常多的多点传输组设计提供可升级的支持,这些组有些少量的参与者代表。XCAST 代码得到了IBM Common Public License的认可。
    · Todd Montgomery 的 MTP page (http://www.nard.net/~tmont/rm-links.html),
    在这里你能找到一个广泛的涉及到多点传输协议的列表。
    附加资源
    · 你可以在
    developerWorks Java technology zone (http://www-106.ibm.com/developerworks/java/)中找到许多的关于Java各方面的内容。
    · 查看 developerWorks tutorials page
    (http://www-105.ibm.com/developerworks/education.nsf/dw/java-onlinecourse-bytitle?OpenDocument&Count=for a complete listing of free tutorials.