Matthew van Eerde's web log

  • Matthew van Eerde's web log

    How to validate and log a WAVEFORMATEX

    • 4 Comments

    Last time I wrote about how to query audio endpoint properties.

    This time, a few words about WAVEFORMATEX structures.  WAVEFORMATEX is a variable-sized structure - it can be as small as a PCMWAVEFORMAT or larger than a WAVEFORMATEXTENSIBLE.

    If the wFormatTag member (the first field) is WAVE_FORMAT_PCM, then you should assume that it is a PCMWAVEFORMAT.  In particular you should not touch the cbSize field since that may not be memory you own!

    If wFormatTag is anything else, you can look at the cbSize to see how much bigger than a WAVEFORMATEX the structure is.  For example, if the wFormatTag is WAVE_FORMAT_EXTENSIBLE, you have the right to expect that cbSize be at least sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX).

    I updated the attachment to the post linked above.  Here's the new output:

    PKEY_AudioEngine_DeviceFormat VT_BLOB of size 40
        WAVEFORMATEXTENSIBLE
        Format (
            wFormatTag: 65534 (WAVE_FORMAT_EXTENSIBLE)
            nChannels: 4
            nSamplesPerSec: 16000
            nAvgBytesPerSec: 128000
            nBlockAlign: 8
            wBitsPerSample: 16
            cbSize: 22
        )
        Samples (
            wValidBitsPerSample / wSamplesPerBlock / wReserved: 16
        )
        dwChannelMask: 0x0
        SubFormat: {00000001-0000-0010-8000-00AA00389B71} (KSDATAFORMAT_SUBTYPE_PCM)

    And here's the code that produces it:

    HRESULT LogWaveFormat(LPCWAVEFORMATEX pWfx, UINT nBytes) {
        if (NULL == pWfx) {
            ERR(L"LogWaveFormat called with a NULL pointer");
            return E_POINTER;
        }
        
        if (nBytes < sizeof(PCMWAVEFORMAT)) {
            ERR(L"Wave format is only %u bytes, smaller even than a PCMWAVEFORMAT (%Iu bytes)", nBytes, sizeof(PCMWAVEFORMAT));
            return E_INVALIDARG;
        } else if (nBytes == sizeof(PCMWAVEFORMAT)) {
            const PCMWAVEFORMAT *pPcm = reinterpret_cast<const PCMWAVEFORMAT *>(pWfx);
            
            // PCMWAVEFORMAT must have a wFormatTag of WAVE_FORMAT_PCM
            if (WAVE_FORMAT_PCM != pPcm->wf.wFormatTag) {
                ERR(L"PCMWAVEFORMAT has invalid format tag %u (expected %u)", pPcm->wf.wFormatTag, WAVE_FORMAT_PCM);
                return E_INVALIDARG;
            }
            
            LOG(
                L"    PCMWAVEFORMAT\n"
                L"        wf.wFormatTag: %u (%s)\n"
                L"        wf.nChannels: %u\n"
                L"        wf.nSamplesPerSec: %u\n"
                L"        wf.nAvgBytesPerSec: %u\n"
                L"        wf.nBlockAlign: %u\n"
                L"        wBitsPerSample: %u",
                pPcm->wf.wFormatTag, StringFromWaveFormatTag(pPcm->wf.wFormatTag),
                pPcm->wf.nChannels,
                pPcm->wf.nSamplesPerSec,
                pPcm->wf.nAvgBytesPerSec,
                pPcm->wf.nBlockAlign,
                pPcm->wBitsPerSample
            );
            return S_OK;
        } else if (nBytes < sizeof(WAVEFORMATEX)) {
            ERR(
                L"Wave format is %u bytes, "
                L"bigger than a PCMWAVEFORMAT (%Iu bytes) "
                L"but smaller than a WAVEFORMATEX (%Iu bytes)",
                nBytes,
                sizeof(PCMWAVEFORMAT),
                sizeof(WAVEFORMATEX)
            );
            return E_INVALIDARG;
        } else if (nBytes != sizeof(WAVEFORMATEX) + pWfx->cbSize) {
            ERR(
                L"Wave format takes up %u bytes "
                L"but sizeof(WAVEFORMATEX) + pWfx->cbSize is %Iu bytes",
                nBytes,
                sizeof(WAVEFORMATEX) + pWfx->cbSize
            );
            return E_INVALIDARG;
        } else {
            // nBytes matches cbSize
            switch (pWfx->wFormatTag) {
               
                case WAVE_FORMAT_EXTENSIBLE:
                    {
                        if (pWfx->cbSize < sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) {
                            ERR(
                                L"cbSize for a WAVE_FORMAT_EXTENSIBLE format must be at least "
                                L"sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) (%Iu), "
                                L"not %u",
                                sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX),
                                pWfx->cbSize
                            );
                            return E_INVALIDARG;
                        }
                    
                        const WAVEFORMATEXTENSIBLE *pWfxExt = reinterpret_cast<const WAVEFORMATEXTENSIBLE *>(pWfx);
                        
                        LOG(
                            L"    WAVEFORMATEXTENSIBLE\n"
                            L"    Format (\n"
                            L"        wFormatTag: %u (%s)\n"
                            L"        nChannels: %u\n"
                            L"        nSamplesPerSec: %u\n"
                            L"        nAvgBytesPerSec: %u\n"
                            L"        nBlockAlign: %u\n"
                            L"        wBitsPerSample: %u\n"
                            L"        cbSize: %u\n"
                            L"    )\n"
                            L"    Samples (\n"
                            L"        wValidBitsPerSample / wSamplesPerBlock / wReserved: %u\n"
                            L"    )\n"
                            L"    dwChannelMask: 0x%x\n"
                            L"    SubFormat: {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X} (%s)",
                            pWfxExt->Format.wFormatTag, StringFromWaveFormatTag(pWfxExt->Format.wFormatTag),
                            pWfxExt->Format.nChannels,
                            pWfxExt->Format.nSamplesPerSec,
                            pWfxExt->Format.nAvgBytesPerSec,
                            pWfxExt->Format.nBlockAlign,
                            pWfxExt->Format.wBitsPerSample,
                            pWfxExt->Format.cbSize,
                            pWfxExt->Samples.wValidBitsPerSample,
                            pWfxExt->dwChannelMask,
                            pWfxExt->SubFormat.Data1,
                            pWfxExt->SubFormat.Data2,
                            pWfxExt->SubFormat.Data3,
                            pWfxExt->SubFormat.Data4[0],
                            pWfxExt->SubFormat.Data4[1],
                            pWfxExt->SubFormat.Data4[2],
                            pWfxExt->SubFormat.Data4[3],
                            pWfxExt->SubFormat.Data4[4],
                            pWfxExt->SubFormat.Data4[5],
                            pWfxExt->SubFormat.Data4[6],
                            pWfxExt->SubFormat.Data4[7],
                            StringFromSubFormat(pWfxExt->SubFormat)
                        );

                        if (pWfx->cbSize > sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) {
                            LOG(L"    Trailing bytes:\n");
                            return LogBytes(
                                reinterpret_cast<const BYTE *>(pWfx) + sizeof(WAVEFORMATEXTENSIBLE),
                                pWfx->cbSize - (sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX))
                            );
                        }
                        
                        return S_OK;
                    }
                    
                case WAVE_FORMAT_PCM:
                    if (pWfx->cbSize > 0) {
                        ERR(L"cbSize for a WAVE_FORMAT_PCM format must be 0, not %u", pWfx->cbSize);
                        return E_INVALIDARG;
                    }
                    // intentionally fall through
                default:
                    LOG(
                        L"    WAVEFORMATEX\n"
                        L"        wFormatTag: %u (%s)\n"
                        L"        nChannels: %u\n"
                        L"        nSamplesPerSec: %u\n"
                        L"        nAvgBytesPerSec: %u\n"
                        L"        nBlockAlign: %u\n"
                        L"        wBitsPerSample: %u\n"
                        L"        cbSize: %u",
                        pWfx->wFormatTag, StringFromWaveFormatTag(pWfx->wFormatTag),
                        pWfx->nChannels,
                        pWfx->nSamplesPerSec,
                        pWfx->nAvgBytesPerSec,
                        pWfx->nBlockAlign,
                        pWfx->wBitsPerSample,
                        pWfx->cbSize
                    );
                    
                    if (pWfx->cbSize > 0) {
                        LOG(L"    Trailing bytes:");
                        return LogBytes(reinterpret_cast<const BYTE *>(pWfx) + sizeof(WAVEFORMATEX), pWfx->cbSize);
                    }

                    return S_OK;
            }
        }
    }


  • Matthew van Eerde's web log

    How to install unsigned drivers

    • 4 Comments

    Most device drivers these days have signatures which allow the operating system to validate that the driver files have not been tampered with since the author sent them out into the world.

    It is still possible to run into an unsigned driver. If you try to install an unsigned driver, Windows will warn you at install time:

    IMPORTANT: if you see this warning, ask yourself this question - DO I TRUST THE SOURCE OF THIS DRIVER? If the answer is anything but a resounding YES, you should click "Don't install this software."

    Windows XP was, by and large, a 32-bit operating system. A 64-bit version of Windows XP was developed but it was not broadly released. At that time it was still fairly common to have unsigned drivers.

    Windows Vista was the first client release of Windows to have a widely deployed 64-bit release. This gave an opportunity to be more strict about enforcing driver signatures. For backwards compatibility, it was still possible to run unsigned drivers on the 32-bit version of Windows Vista (so the XP drivers would still work) but there was no such burden on Windows Vista 64-bit; so Windows Vista 64-bit was the first time that there was really a strong incentive to run with only signed drivers.

    Even so, there are still workarounds provided:

    • If you want to run an unsigned driver for one particular boot, you can hit F8 during boot1 and choose "Disable Driver Signature Enforcement". This allows unsigned drivers to load for this boot only.
    • If you're a device driver developer, it is expected that you will want to iterate on your particular driver; it is also expected that you will probably also have a kernel debugger attached. So driver signature enforcement is disabled if you have a kernel debugger attached.

    The most reliable method, however, and the one with the fewest side effects, is to... well... sign the driver!

    To sign a driver:

    1. Download the Windows Driver Kit.
    2. Make sure the driver .inf has a CatalogFile=MyCatalogFile.cat line (specify your own value for MyCatalogFile.cat). If one is missing you can add it to the [Version] section.
    3. Point inf2cat to the driver .inf file and it will make a .cat file for you. This .cat file will have an entry for every file pulled in by the .inf.
    4. Use SignTool to sign the .cat file.

    At this point you can see who says this driver is OK. Double-click the .cat file | View Signature | View Certificate | Certification Path and note the highest certificate in the chain:

    If you're a professional driver developer and you want your driver to ship on Windows systems, your next step would very likely be to use the Windows Logo Kit to run the Microsoft logo tests, and get an official "Microsoft says this driver has passed the logo tests" signature for your .cat file. (The difference is that the top certificate in the chain will be "Microsoft Root Authority" rather than "Microsoft Test Root Authority.") A full description of this process lies outside the scope of this blog post.

    But if you're just developing a driver for your personal use, you would probably skip this step. Instead of getting a "Microsoft says this driver is OK" signature, you can add the root certificate for your signing certificate to the "trusted root certificates" certificate store on the machine you want to load the driver on.

    If the root certificate in question is the "Microsoft Test Root Authority", you can run "bcdedit -set testsigning on" and reboot.

    If it's some other certificate (for example, if you generated a self-signed certificate) then you need to add it to the trusted certificate store by using certmgr.exe or by going to Internet Explorer | Internet Options | Content | Certificates.

    If you have everything right, then installing the driver should not pop up the "driver is unsigned" warning. If you still get the warning, looking at C:\windows\inf \setupapi.dev.log can give some clues as to what is wrong... whether there is a driver file that is not listed in the catalog file, or the catalog file's signature does not lead back to a trusted root certificate, or something else.

    1 In Windows 8 Developer Preview, the F8 experience has been reworked. Hit Shift-F8 instead.2
    2 Some BIOSes have an issue where Shift is not sent to Windows this early. If you have Windows 8 Developer Preview installed one of these machines there's another way - hit F10 during boot to get to the "Edit Boot Options" screen. Add /DISABLE_INTEGRITY_CHECKS to the boot flags.

  • Matthew van Eerde's web log

    Anand vs. Gelfand world chess championship 2012 oldest pair of contenders since 1886

    • 2 Comments

    In 2012, World Chess Champion Viswanathan Anand will attempt to defend his title aqainst challenger Boris Gelfand. This is a very unusual match in that both players are fairly old by World Chess Champion contender standards. I decided to see just how unusual it was, and do so with some degree of rigor.

    One tricky bit is that chess championships (usually) have (at least) two players, so we have to define an age metric for pairs of people. Creating a well-ordering on tuples is sometimes controversial. I chose to have the comparison routine be:
        age(contenders) = min(age(contender) : contender ∈ contenders)
    which is to say, the age of the youngest contender.

    Another tricky bit was deciding which matches were definitively "world chess championship matches." I pulled the list of world chess championship matches from chessgames.com. For time periods where the organizational ownership of the title is in question, this includes matches sponsored by all contending organizations.

    As a naïve first pass, I looked up the birth years for all the contenders and subtracted that from the year of the championship to get an estimated age. This could be off by a year if the youngest contender's birthday comes after (or during?) the match. Nevertheless, this was accurate enough to give me a short list of matches to investigate further:.

    Year Player 1 Estimated Age Player 2 Estimated Age Minimum
    2012 Viswanathan Anand 43 Boris Gelfand 44 43
    2010 Viswanathan Anand 41 Veselin Topalov 35 35
    2008 Viswanathan Anand 39 Vladimir Kramnik 33 33
    2006 Vladimir Kramnik 31 Veselin Topalov 31 31
    2005 Veselin Topalov 30 Many N/A1 30
    2004 Vladimir Kramnik 29 Peter Leko 25 25
    2004 Rustam Kasimdzhanov 25 Michael Adams 33 25
    2001 Ruslan Ponomariov 18 Vassily Ivanchuk 32 18
    2000 Vladimir Kramnik 25 Garry Kasparov 37 25
    2000 Viswanathan Anand 31 Alexey Shirov 28 28
    1999 Alexander Khalifman 33 Vladimir Akopian 28 28
    1998 Anatoly Karpov 47 Viswanathan Anand 29 29
    1996 Anatoly Karpov 45 Gata Kamsky 22 22
    1995 Garry Kasparov 32 Viswanathan Anand 26 26
    1993 Garry Kasparov 30 Nigel Short 28 28
    1993 Anatoly Karpov 42 Jan Timman 42 42
    1990 Garry Kasparov 27 Anatoly Karpov 39 27
    1987 Garry Kasparov 24 Anatoly Karpov 36 24
    1986 Garry Kasparov 23 Anatoly Karpov 35 23
    1985 Garry Kasparov 22 Anatoly Karpov 34 22
    1984 Anatoly Karpov 33 Garry Kasparov 21 21
    1981 Anatoly Karpov 30 Viktor Korchnoi 50 30
    1978 Anatoly Karpov 27 Viktor Korchnoi 47 27
    1972 Bobby Fischer 29 Boris Spassky 35 29
    1969 Boris Spassky 32 Tigran Petrosian 40 32
    1966 Tigran Petrosian 37 Boris Spassky 29 29
    1963 Tigran Petrosian 34 Mikhail Botvinnik 52 34
    1961 Mikhail Botvinnik 50 Mikhail Tal 25 25
    1960 Mikhail Tal 24 Mikhail Botvinnik 49 24
    1958 Mikhail Botvinnik 47 Vasily Smyslov 37 37
    1957 Vasily Smyslov 36 Mikhail Botvinnik 46 36
    1954 Mikhail Botvinnik 43 Vasily Smyslov 33 33
    1951 Mikhail Botvinnik 40 David Bronstein 27 27
    1948 Mikhail Botvinnik 37 Vasily Smyslov 27 27
    1937 Alexander Alekhine 45 Max Euwe 36 36
    1935 Max Euwe 34 Alexander Alekhine 43 34
    1934 Alexander Alekhine 42 Efim Bogolyubov 45 42
    1929 Alexander Alekhine 37 Efim Bogolyubov 40 37
    1927 Alexander Alekhine 35 José Raúl Capablanca 39 35
    1921 José Raúl Capablanca 33 Emanuel Lasker 53 33
    1910 Emanuel Lasker 42 Dawid Janowski 42 42
    1910 Emanuel Lasker 42 Carl Schlecter 36 36
    1908 Emanuel Lasker 40 Siegbert Tarrasch 46 40
    1907 Emanuel Lasker 39 Frank Marshall 30 30
    1896 Emanuel Lasker 28 Wilhelm Steinitz 60 28
    1894 Emanuel Lasker 26 Wilhelm Steinitz 58 26
    1892 Wilhelm Steinitz 56 Mikhail Chigorin 42 42
    1890 Wilhelm Steinitz 54 Isidor Gunsberg 36 36
    1889 Wilhelm Steinitz 53 Mikhail Chigorin 39 39
    1886 Wilhelm Steinitz 50 Johannes Zukertort 44 44

    Closer investigation of each of the highlighted matches revealed that, astonishingly, in every case the youngest contender's birthday came after the match:

    • 2012 Viswanathan Anand (43?) vs. Boris Gelfand: Anand's birthday (December 11) comes after the match (starts in May) so he will still be 42.
    • 1993 Anatoly Karpov (42?) vs. Jan Timman (42?): Timman's birthday (December 14) came after the match (finished November 1) so he was still 41.
    • 1934 Alexander Alekhine (42?) vs. Efim Bogolyubov: Alekhine's birthday (October 31) came after the match (April to June) so he was still 41.
    • 1910 Emanuel Lasker (42?) vs. Dawid Janowski (42?): Janowski was born May 25; Lasker December 24. Lasker's birthday came after the match (finished December 8) so he was still 41.
    • 1892 Wilhelm Steinitz vs. Mikhail Chigorin (42?): Chigorin's birthday November 12 (October 31 old style) came after the match (finished February 28) so he was still 41.
    • 1886 Wilhelm Steinitz vs. Johannes Zukertort (44?): Zukertort's birthday (September 7) came after the match (finished March 29) so he was still 43.

    We conclude that Anand vs. Gelfand (2012) features the oldest contenders since the very first World Chess Championship Steinitz vs. Zukertort (1886) - and is within a year of even that! If the 2014 championship is a rematch, it will set the record.

    1 Topalov was the clear winner of the 2005 FIDE World Championship Tournament so there was no need for a runoff.

  • Matthew van Eerde's web log

    Mark your variadic logging function with __format_string to have PREfast catch format specifier errors

    • 0 Comments

    There are a handful of Problems (with a capital P) which occur over and over again in programming. One of them is Logging.

    It is incredibly convenient to use the variadic printf function to log strings with values of common types embedded in them:

    // spot the bug
    LOG(L"Measurement shows %lg% deviation", 100.0 * abs(expected - actual) / expected);

    However, printf is very error prone. It is very easy to use the wrong format specifier like %d instead of %Id, or to forget to escape a special character like % or \.
    In particular, the above line contains a bug.

    Static code analysis tools like PREfast are quite good at catching these kinds of errors. If my LOG macro was something like this, PREfast would catch the bug:

    #define LOG(fmt, ...) wprintf(fmt L"\n", __VA_ARGS__)

    This works because PREfast knows that the first argument to wprintf is a format string, and can match up the format specifiers with the trailing arguments and verify that they match.

    If you implement your own variadic logger function, though, PREfast doesn't necessarily know that the last explicit argument is a format specifier - you have to tell it. For example, PREfast will NOT catch format specifier issues if the LOG macro is defined like this:

    // PREfast doesn't know Format is a format string
    interface IMyLogger { virtual void Log(LPCWSTR Format, ...) = 0; };
    extern IMyLogger *g_Logger;
    #define LOG(fmt, ...) g_Logger->Log(fmt, __VA_ARGS__)

    How do you tell it? Well, let's look at the declaration of wprintf. It's in (SDK)\inc\crt\stdio.h:

    _CRTIMP __checkReturn_opt int __cdecl wprintf(__in_z __format_string const wchar_t * _Format, ...);

    The relevant part here is __format_string. So the fixed IMyLogger declaration looks like this:

    // Now PREfast can catch format specifier issues
    interface IMyLogger { virtual void Log(__format_string LPCWSTR Format, ...) = 0; };
    extern IMyLogger *g_Logger;
    #define LOG(fmt, ...) g_Logger->Log(fmt, __VA_ARGS__)

Page 1 of 1 (4 items)

September, 2011