【问题标题】:Is there a library available which easily can record and replay results of API calls?是否有可用的库可以轻松记录和重放 API 调用的结果?
【发布时间】:2011-01-09 09:44:33
【问题描述】:

我正在编写各种调用相对复杂的 Win32 API 函数的东西。这是一个例子:

//Encapsulates calling NtQuerySystemInformation buffer management.
WindowsApi::AutoArray NtDll::NtQuerySystemInformation( 
    SystemInformationClass toGet ) const
{
    AutoArray result;
    ULONG allocationSize = 1024;
    ULONG previousSize;
    NTSTATUS errorCheck;
    do 
    {
        previousSize = allocationSize;
        result.Allocate(allocationSize);
        errorCheck = WinQuerySystemInformation(toGet, 
            result.GetAs<void>(), allocationSize, &allocationSize);
        if (allocationSize <= previousSize)
            allocationSize = previousSize * 2;
    } while (errorCheck == 0xC0000004L);
    if (errorCheck != 0)
    {
        THROW_MANUAL_WINDOWS_ERROR(WinRtlNtStatusToDosError(errorCheck));
    }
    return result;
}
//Client of the above.
ProcessSnapshot::ProcessSnapshot()
{
    using Dll::NtDll;
    NtDll ntdll;
    AutoArray systemInfoBuffer = ntdll.NtQuerySystemInformation(
        NtDll::SystemProcessInformation);
    BYTE * currentPtr = systemInfoBuffer.GetAs<BYTE>();
    //Loop through the results, creating Process objects.
    SYSTEM_PROCESSES * asSysInfo;
    do
    {
        // Loop book keeping
        asSysInfo = reinterpret_cast<SYSTEM_PROCESSES *>(currentPtr);
        currentPtr += asSysInfo->NextEntryDelta;

        //Create the process for the current iteration and fill it with data.
        std::auto_ptr<ProcImpl> currentProc(ProcFactory(
            static_cast<unsigned __int32>(asSysInfo->ProcessId), this));
        NormalProcess* nptr = dynamic_cast<NormalProcess*>(currentProc.get());
        if (nptr)
        {
            nptr->SetProcessName(asSysInfo->ProcessName);
        }
        // Populate process threads
        for(ULONG idx = 0; idx < asSysInfo->ThreadCount; ++idx)
        {
            SYSTEM_THREADS& sysThread = asSysInfo->Threads[idx];
            Thread thread(
                currentProc.get(),
                static_cast<unsigned __int32>(sysThread.ClientId.UniqueThread),
                sysThread.StartAddress);
            currentProc->AddThread(thread);
        }
        processes.push_back(currentProc);
    } while(asSysInfo->NextEntryDelta != 0);
}

我的问题是在模拟 NtDll::NtQuerySystemInformation 方法 - 即返回的数据结构很复杂(嗯,这里实际上相对简单但它可能很复杂),并编写一个构建数据结构的测试,如API 调用所花费的时间可能是编写使用 API 的代码的 5-6 倍。

我想做的是调用 API,并以某种方式记录它,这样我就可以将记录的值返回给被测代码,而无需实际调用 API。返回的结构不能简单地进行 memcpy 处理,因为它们通常包含内部指针(指向同一缓冲区中其他位置的指针)。有问题的库需要检查这些类型的东西,并且能够在重放时将指针值恢复到类似的缓冲区。 (即检查每个指针大小的值是否可以被解释为缓冲区中的指针,将其更改为偏移量,并记住在重放时将其更改回指针 - 这里的误报率是可以接受的)

有什么东西可以做这样的事情吗?

【问题讨论】:

  • @Nicklamor:是的。 (无需删除评论——这是个好问题)
  • @NickL “API”在这种情况下是 Windows API。我会调用我想要的函数作为进行实际单元测试的一部分。
  • @Nick:我正在寻找一般的东西;也就是说,它不会关心调用了什么 API 函数。
  • @Billy:您是否尝试这样做,以便您拥有更容易读取的数据结构(AutoArray),或者因为您不想通过 api 调用更改内部变量/指针值确实如此,还是两者兼而有之? ps-谢谢你的耐心
  • @Nick:两者都不是。出于测试目的,我希望能够替换 API 调用本身。例如,在上面,如果我想编写测试,我不能简单地为客户端方法的输出编写测试,因为每次测试运行返回的进程都会不同。我想保存API返回的缓冲区,以后可以返回缓冲区。

标签: c++ unit-testing


【解决方案1】:

手工实现真的有那么难吗?

#include <stddef.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>

typedef char BYTE;

//Note: Buffer is MODIFIED after this call (hence why it's not const)! Don't re-use it!
void saveBuffer(FILE* stream, BYTE* buffer, size_t bufferSize)
{
    assert(bufferSize % sizeof(void*) == 0);
    fwrite(&bufferSize, sizeof(bufferSize), 1, stream);
    for (size_t i = 0; i < bufferSize - sizeof(void*) + 1; i += sizeof(void*))
    {
        void** pAsPointer = (void**)&buffer[i];
        if (*pAsPointer >= buffer && *pAsPointer < buffer + bufferSize)
        {
            fwrite(&i, sizeof(i), 1, stream);
            *pAsPointer = pAsPointer;
        }
    }
    void* null = NULL;
    fwrite(&null, sizeof(null), 1, stream); //Null-terminator
    fwrite(buffer, 1, bufferSize, stream);
}


size_t loadBuffer(FILE* stream, BYTE* buffer, size_t bufferCapacity) //Buffer size is in stream
{
    size_t bufferSize;
    fread(&bufferSize, sizeof(bufferSize), 1, stream);
    if (bufferSize >= bufferCapacity)
    {
        memset(buffer, 0, bufferSize);
        for (;;)
        {
            size_t pointerOffset;
            fread(&pointerOffset, sizeof(pointerOffset), 1, stream);
            if (pointerOffset != 0)
            { *(size_t*)&buffer[pointerOffset] = (size_t)buffer + pointerOffset; }
            else { break; }
        }
        for (size_t i = 0; i < bufferSize; i += sizeof(void*))
        {
            if (*(void**)&buffer[i] == NULL)
            { fread(&buffer[i], sizeof(size_t), 1, stream); }
        }
    }
    return bufferSize;
}

(抱歉,我还没有测试它,但它应该非常接近工作。)

唯一的麻烦是假设所有可能是指针的值实际上都是指针,但除此之外,它似乎很简单。

【讨论】:

  • 当然也可以手工实现。但我尽量避免重新发明轮子。
  • 避免重新发明轮子是很好的,但如果它比实际找到一个轮子花费的时间更少,您可能需要考虑它! :)
  • 但这绝对不总是关于“不重新发明轮子”,而且非常重要的是关于“不重新发明轮子并使其弯曲 b>".
【解决方案2】:

这个问题至少是一个完全一般性的研究问题的一个子集,这表明不会有现成的系统适合你。一篇关于 API 调用记录和重放主题的论文仅在两年前出现在一个重要的操作系统会议 (OSDI 2008) 上。在他们的网站上查看显示没有可用的代码——尽管如此,代码(如果有的话)可能不是生产质量的。

R2:用于记录和回放的应用级内核 郭振宇、王曦、唐健、刘学正、徐志雷、吴明、M. Frans Kaashoek 和张正,OSDI 2008

http://www.usenix.org/event/osdi08/tech/full_papers/guo/guo_html/

http://research.microsoft.com/en-us/people/zhenyug/r2-osdi08.pdf

摘要

基于库的记录和回放工具旨在通过在日志中记录所选函数的结果并在回放期间从日志中返回结果而不是执行函数来重现应用程序的执行。这些工具必须确保回放运行与记录运行相同。这样做的挑战是只应记录应用程序对函数的调用,记录函数调用的副作用可能很困难,并且在重放、多线程和工具的存在期间不执行函数调用可能会改变应用程序从录制到重放的行为。这些问题限制了此类工具的使用。

R2 允许开发人员选择可以正确记录和回放的功能。开发人员使用简单的关键字注释选择的函数,以便 R2 可以处理具有副作用和多线程的调用。 R2 从模板生成用于记录和回放的代码,允许开发人员避免手动为数百个函数实现存根。为了跟踪调用是代表应用程序还是代表所选函数的实现,R2 维护了一个模式位,用于存根保存和恢复。

我们已经在 Windows 上实现了 R2,并对 Win32 API 的大部分(1,300 个函数)和两个更高级别的接口(MPI 和 SQLite)进行了注释。 R2 可以重放以前基于库的工具无法重放的多线程 Web 和数据库服务器。通过允许开发人员选择高级接口,R2 还可以保持较小的记录开销;实验表明,它对 Apache 的记录开销约为 10%,在 SQLite 接口上记录和重放可以将日志大小减少多达 99%(与在 Win32 API 上这样做相比),并且使用 BitTorrent 和 MPI 的优化注释应用程序实现了从 13.7% 到 99.4% 的日志大小减少。

【讨论】:

  • 这是关于重放程序的整个执行,即用于调试目的,类似于 VMWare 的功能。这不是我真正想要做的......
【解决方案3】:

(不期待代表投票,只是想帮助/与社区讨论代码) 你说你想要一个更容易阅读的数据结构,但如果你使用 api(我认为这是你说你想做的……间接地),你就会被它给你的那个困住。那么你只是希望能够调用 api 函数,但让它恢复它的指针/变量值,就好像你没有调用它一样?\n 我认为您正在寻找几乎不可能的我的朋友(或非常复杂!)。您必须更改 api 代码才能恢复它的指针值,但您已经知道这一点。程序结束时这些值会恢复吗?如果是这样,您可能可以将此 api 调用放在另一个解决方案/程序中并从您的程序中调用它?

【讨论】:

  • 我不想要更容易阅读的数据结构。我希望能够保存 API 返回的缓冲区并稍后重新加载。可以通过检查缓冲区的每个指针大小的块并查看它是否可以解释为指向该缓冲区中其他位置的指针来检测指针。我已经通过虚函数处理了 API 调用本身的重定向;我只需要为我的模拟生成示例测试数据即可返回。
  • 啊。那么好吧..大声笑。您不能将缓冲区中的值(不是地址)复制到以后可以访问的某种动态存储数组(可能是某种自制的向量或列表)吗?你可以检测给定的值是否是一个地址,去那个位置并拉取它的值,等等?还是我应该停止说话 XD
【解决方案4】:

有几种方法:

  1. 将顶级类更改为模板,具有一个模板参数。对于您的实际实现,您将使用 NtDll,但对于测试,您将使用模拟记录调用

  2. 为 NtDll 类创建一个带有接口的包装器。您可以使用此类将这种类型的对象传递给实际代码中的构造函数,并在测试中进行模拟。

  3. 尝试使用 NtDll 类将您的类重构为单个类。

所有方法都假定您使用的是dependency injection

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-15
    • 2021-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多