Application Verifier for the programmer: testing Windows applications

    Perhaps they don’t write try { /* code */ } catch(...) { } to your project in order to avoid exceptions when working with memory, they can close handles and know about Windows Vista virtualization, and programs never crash for unknown or rarely repeated reasons.

    Then you are lucky, you can proceed to the next topic.

    But sometimes, it would seem, strange things happen. The program “crashes” out of the blue, the memory is leaking somewhere, and one more time they called you complaining about the strange behavior of the program running on the server 24/7, but of course you “wrapped” their problem, convincing that it is hardware-dependent and okay. Still, the development of programs for Windows is often tricky, and no one is safe from errors of carelessness or due to ignorance of the architecture. I won’t learn how to avoid these mistakes - I don’t know myself. But here I can advise one tool for effective debugging.

    It will be about Microsoft Application Verifier. But this is not a debugger. On the contrary, without a debugger, in itself, a thing is relatively useless. But in conjunction with it allows you to detect a number of important platform-specific problems. In addition, it will not be possible to obtain a “Compatible with windows 7” certificate without passing testing using the AppVerifier (actually for “Vista Certified” as well, but apparently this is not accepted). And this certificate is for the user some guarantee that the program that received it may not do better, but at least it will not hurt . Okay, the "water" is over, let's get down to business.

    Mode of application


    Download and install AppVerifier for Habrocheloveka, I am sure, not complexity. Let's run (from under the real-administrator, under Vista + it will not work in another way) its graphical shell:



    On the left is a list of applications for testing; on the right is a list of sections to check for the selected application. MSDN claims that AppVerifier is designed to test C ++ programs, but is generally applicable to any native code.

    The graphical shell does not produce any tests, only makes it possible to select the desired items. The checks themselves are implemented thanks to the so-called “layers”, dynamically connected libraries vfbasics, vfcompat, vfLuaPriv, vfprint(you can admire them in the folder system32). When the application under test is launched, they connect to it and intercept a call to system functions, such asHeapAlloc, GetTickCount, CloseHandleand many others. The interceptor performs a number of additional checks, then calls the original function, and therefore, with the exception of a few cases considered below , this will not affect the operation of the application under test. Unless some performance loss will be noticeable. Subjectively, in the worst case scenario, the program will “slow down” five times, and whether any specific numbers are needed or not, I will leave it up to you.

    There is an important feature here : despite the fact that when adding, we select the file of the application under test, the checks are attached only to its name without a path. On the one hand, you don’t have to worry in what configuration (and in which folder) to collect the project (usually the folders for Debug and Release are different), but on the other hand, you can forget about the installed checks, and when you start the program from the desktop, you’ll be surprised that it “ does not work".

    We’ll talk about the meaning of the test items a little later, but now we’ll add, for example, notepad.exe and install all the daws. Run the notebook, add a couple of lines, try to save. O-pa, failure:



    Not the only outcome of the situation, perhaps you will get another warning window, or even do without it. What happened? Let's look again at the AppVerifier graphic add-in. This time, select the Logs item from the main menu, see a list of log files associated with the tested applications. By the launch log.



    Physically, these log files are located in a folder AppVerifierLogsin the root of the user profile. It will be difficult to read them with your bare hands (binary format), so we poke the “View” button for the corresponding log. It will dump in xml and open the default viewer for xml:



    For those who have carefully watched: the error shown in this screenshot does not correspond to the error message (which is the normal behavior of the program) from the previous screenshot, but occurs a bit later.

    Here is a brief description of the problem, and stack trace. And from me a hint how to look for errors, not warnings. By the way, if errors are present, then the program does not receive certification for compatibility with Vista / Win7. Wait, but this is a notebook ?! Well, yes, only shhh.

    Patient treatment


    Now run the debugger. Let it be a debugger built into the studio, or a free WinDbg from Debugging Tools for Windows (it is certainly more sophisticated, but now it does not matter).

    And here is our patient:
    int _tmain(int argc, _TCHAR* argv[])
    {  
      int *p = new int();
       delete p;
      *p = 0; // p = 0 will be OK, but *p = 0 is error!
    }

    The potential danger of this fragment is easy to assess if the lines with deleteand overwriting the memory were stretched in time. But neither in the release nor in the release assembly is such a problem detected (Visual Studio, default configuration).

    Now we add the program for testing the Basics group in the Application Verifier. And run it from under the debugger (from the studio on F5, for example). AppVerifier spoke to us in the voice of the studio:



    And in Debug Output, the corresponding structural exception is shown :
    =======================================
    VERIFIER STOP 00000013: pid 0xB54: First chance access violation for current stack trace.

    02B59FF8 : Invalid address causing the exception.
    0082142F : Code address executing the invalid access.
    0013F670 : Exception record.
    0013F68C : Context record.

    =======================================

    It tells you what the exception is (00000013), with what memory address (02B59FF8) and at what code address (0082142F) it happened. The lucky ones who downloaded Windows Debug Symbols will also be shown the place in the source code where the problem occurred and Stack Trace, which led to the exception.

    Well, we found this problem, which means we fixed it. For other error classes, the operation algorithm is preserved, but the correction procedure may not be so trivial.

    Detectable problems


    Let's now figure out what problems AppVerifier will allow us to identify. All test options are divided into groups. With the exception of the Low Resource Simulation group and the TimeRollOver and HighVersionLie tests, the checks do not change the behavior of the application (in case no errors are detected).

    1. Distorting checks

    1.1. Low resource simulation

    Here it is the reason for the fall of the notebook. Tests of this group allow you to simulate the behavior of the system with a lack of resources. An application can easily be denied (using a random number sensor) memory allocation, file creation, Event'a, windows, entries in the registry. Usually there is some “calm” time of about 2-5 seconds, when the application is allowed to use the resources at full strength; this is done so that the application can even start up (it was invented not so long ago, it used to be sadder). The normal behavior of a program is stability; display warning dialogs, but not “crashes”. So in the code it would be necessary to provide these situations.

    1.2. TimeRollOver in the group Misc

    Consider the following code example that executes an action actionseveral times, but no more than one second:
    DWORD time_end = GetTickCount() + 1000; // 1s timelimit
    do { action(); } while (GetTickCount() < time_end);

    The catch is visible to the naked eye; if it is time_endvery close to DWORD_MAX, but less than DWORD_MAX-1000, and action()sometimes it takes more than a second, then the cycle will work a little longer than we would like. Namely, 50 days (DWORD_MAX / (1000 * 60 * 24)).

    And this is not the only case that you say about the next fragment?
    char buf[8];
    sprintf(buf, "%i", GetTickCount());

    To diagnose such problems, the TimeRollOver check “runs” the value of the GetTickCount () function faster. A full cycle before zeroing takes 5 minutes.

    1.3. HighVersionLie in the Compatibility group

    If you suddenly use the function GetVersionEx, then this test will help you detect code branches with incorrect verification of a valid OS version.

    OSVERSIONINFO osvi;
    ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx(&osvi);

    BOOL bIsWindowsXP_or_Later = (osvi.dwMajorVersion >= 5) && (osvi.dwMinorVersion >= 1);
    if (!bIsWindowsXP_or_Later)
        printf("Windows XP or later required.\n");


    There is a clear mistake in this fragment; In order to cut off Windows 2000 (5.0), an additional check is introduced for minor version XP (5.1), but the code also discards Windows Vista (6.0). On Windows 7 (6.1) it will work. Is this really the reason for poor compatibility with Windows Vista? Microsoft claims that 70% of incompatible with Vista programs do not work, including because of this problem .

    But diagnosing this situation on the developer's computer is difficult - he has one, fixed version of the OS. You can use a virtual machine with a different version of the OS, or you can just poke the daw HighVersionLie. Then the value GetVersionExwill be modified (usually by the rule dwMajorVersion += 3; dwMinorVersion = 0).

    2. Non-modifying checks

    2.1. Memory in the Basics group

    Validation of calls HeapAlloc, GlobalAllocand other Windows Heap Manager APIs. He does not monitor memory leaks, but this can be solved in other ways .

    2.2. TLS in the Basics group

    Monitors the correctness of calls to the Thread Local Storage API.

    2.3. Exceptions in the Basics Group

    It monitors the appropriateness of catching exceptions, in particular, attempts to “drown out” Access Violation exceptions, and “unmasks” exceptions in view stubs try { } catch(...) { } .

    2.4. Handles in the Basics group

    It monitors the admissibility of operations on handles, the correctness of handles and their lifetime. A little more in English .

    2.5. Locks in the group Basics

    Checks the correct use of critical sections, does not allow the critical section to be dropped from another stream relative to the critical section setting.

    2.6. DirtyStacks in the Misc group

    Periodically fills the unused part of the stack with the 0xCD pattern, which allows detecting uninitialized variables or function parameters.

    2.7. DangerousAPIs in the Misc group

    Alerts about the use and unwanted potentially dangerous functions of an API like TerminateThread.

    2.8. Luapriv

    Limited-user-account privileges test. Checks if the program needs administrative privileges, does the program perform actions that are valid only for the real-administrator.

    It consists of two parts: predictive (lists all program actions that can be performed only by the administrator) and diagnostic (refuses the program administrative actions with an error ACCESS_DENIED). Thus, the programmer does not have to test the program separately by logging in as a guest. It also checks a number of features related to virtualization under Windows Vista and later.

    Conclusion


    AppVerifier is an interesting tool that allows you to identify and solve a number of "floating" and "hidden" (and sometimes specially hidden) problems. Using it as a whole is not difficult, with certain skills it is convenient. And if you want to receive the certificate "Windows compatible", then acquaintance with it cannot be avoided. I personally helped already on two projects, I hope it will be useful to you too.

    * All source code was highlighted with Source Code Highlighter .

    Also popular now: