65.9K
CodeProject 正在变化。 阅读更多。
Home

我使用 Visual Studio 进行远程调试的思路

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2016年3月20日

CPOL

4分钟阅读

viewsIcon

19974

我对远程调试的构想及我的实现。

引言

你肯定尝试过使用 Visual Studio 进行远程调试。以下是我的一些想法,我遇到的问题,我提出的解决方案,以及一个免费工具,如果你不想自己实现所有功能,可以使用它。

远程调试配置

远程调试并不难。你将二进制文件及其 pdb 文件部署到目标计算机,然后从 Visual Studio 连接到远程调试器 (msvsmon.exe) 并开始调试。但是,有一些重要的问题,解决这些问题将使你的生活更加轻松,尤其是目标计算机用户是新手的情况下。

即使你可以控制远程计算机(例如,通过 TeamViewer 或我自己的 HMN 会话),此处介绍的构想也将帮助你提高远程调试的效率。

问题 #1:是否安装了 Visual Studio 远程工具?

这很简单。远程调试器的安装路径可以在以下位置找到:

  • HKLM\SOFTWARE\Microsoft\VisualStudio\14.0 - InstallDir,或
  • HKLM\SOFTWARE\Microsoft\VisualStudio\14.0\EnterpriseTools\QualityTools - InstallDir

将以下内容附加到此目录:

  • \Remote Debugger\x64\msvsmon.exe 或
  • \Remote Debugger\x86\msvsmon.exe

如果在此位置找不到可执行文件,则需要安装远程工具。截至撰写本文时,你可以从以下位置获取它们:

  • https://download.microsoft.com/download/E/7/A/E7AEA696-A4EB-48DD-BA4A-9BE41A402400/rtools_setup_x64.exe
  • https://download.microsoft.com/download/E/7/A/E7AEA696-A4EB-48DD-BA4A-9BE41A402400/rtools_setup_x86.exe

它们具有用于静默/无人值守安装的不错的命令行参数。

 

问题 #2:MSVSMON 需要传入连接

目标 PC 通常位于某些愚蠢的非 UPnP NAT 或某些限制性防火墙或杀毒软件之后,因此你将很难指导你的客户端配置所有这些内容,或者你将被迫通过远程会话操作未知的路由器。

如果可以反转连接,以便 msvsmon 连接到你的 PC,那就容易多了。但是 msvsmon 不支持这样做。

解决方案是使用我的连接反转方法(一个简单的中间人)。假设你的客户端监听端点为 A1 (msvsmon),你希望从你的 B1 (Visual Studio) 连接,但 NAT 阻止了它。然后你启动以下设置:

  • 一个可通过你的 NAT 访问的 B2 监听点。
  • 一个在你的局域网内使用的 B3 监听点,并与 B2 内部通信。
  • 远程计算机中的一个 A2 点。

然后,会发生什么?

  • A2 连接到 B2
  • A2 连接到 A1
  • B1 连接到 B3。B3 收到的所有流量都会转发到 B2,B2 会将其转发到 A2,A2 会将其转发到 A1。A1 和 B1(你无法控制的端点,msvsmon 和 Visual Studio)不知道此设置,并认为它们直接通信。

 

这是我对上述设置的实现

class CREV
    {
    public:

    XSOCKET s2;
    XSOCKET c1;
    bool Compression = false;

#pragma pack(push,1)
    enum P2PTYPE
        {
        CONNECT,
        CONNECTRESULT,
        DATA,
        CDATA,
        TERMINATE,
        };

    struct P2P
        {
        int Type = 0;
        unsigned long long ID = 0;
        int sz = 0;
        int res = 0;
        };
#pragma pack(pop)

    CREV()
        {
        __nop();
        }

    int Port1 = 0;
    int Port2 = 0;

    unordered_map<unsigned long long, tuple<XSOCKET>> sessions;

    size_t EraseSessionFromThread(unsigned long long id) 
        { 
        sessions.erase(id); 
        return 0; 
        };

    void CompressB(vector<char>& d)
        {
        ZIPUTILS::ZIP z(d.data(), d.size());
        vector<char> dd;
        unsigned long long beforecompression = d.size();
        z.MemCompress(d.data(), d.size(), dd);
        dd.resize(dd.size() + 8);
        memcpy(dd.data() + dd.size() - 8, &beforecompression, 8);
        d = dd;
        }

    void UncompressB(vector<char>& d)
        {
        if (d.size() <= 8)
            return;
        unsigned long long bc = 0;
        memcpy(&bc, d.data() + d.size() - 8, 8);
        d.resize(d.size() - 8);
        ZIPUTILS::ZIP z(d.data(), d.size());
        vector<char> dd;
        dd.resize(bc + 100);
        z.MemUncompress(d.data(), d.size(), dd);
        d = dd;
        }

    unsigned long long TotalBytesTransmitted = 0;
    void EstablishedConnection(unsigned long long sid, int Mode)
        {
        auto& s = sessions[sid];
        auto& x = get<0>(s);
        vector<char> b(10000);
        for (;;)
            {
            b.resize(10000);
            int rv = x.receive(b.data(), 10000);
            if (rv == 0 || rv == -1)
                break;

            b.resize(rv);
            if (Compression)
                CompressB(b);

            // Notify of data
            XSOCKET& aa = (Mode == 0) ? s2 : c1;
            P2P p;
            p.Type = DATA;
            if (Compression)
                p.Type = CDATA;
            p.ID = sid;
            p.sz = b.size();
            if (aa.transmit((char*)&p, sizeof(p), true) != sizeof(p))
                break;
            if (aa.transmit((char*)b.data(), b.size(), true) != b.size())
                break;
            TotalBytesTransmitted += b.size();
            }

        // Notify of termination
        XSOCKET& aa = (Mode == 0) ? s2 : c1;
        P2P p;
        p.Type = TERMINATE;
        p.ID = sid;
        p.sz = 0;
        aa.transmit((char*)&p, sizeof(p), true);

        // Kill session
        x.Close();
        UWL::InterlockedCall2<size_t, CREV*,unsigned long long>(std::forward<std::function<size_t(CREV*,unsigned long long)>>(&CREV::EraseSessionFromThread), this,std::forward<unsigned long long>(sid));
        }

    void ServerListen1(int p)
        {
        UWL::XSOCKET s;
        if (!s.BindAndListen(p))
            return;
        for (;;)
            {
            auto y2 = s.Accept();
            if (y2 == INVALID_SOCKET)
                break;
            XSOCKET y = y2;

            // We order s2 to send a "connect request"
            if (!s2.Valid())
                {
                y.Close();
                continue;
                }

            //    UWL::debugprint("s1 accepted client, we will order a connect request\r\n");

            P2P p;
            p.Type = CONNECT;
            p.ID = GetTickCount64();
            p.sz = 0;

            // Create the session
            sessions[p.ID] = make_tuple<>(y);
            if (s2.transmit((char*)&p, sizeof(p), true) != sizeof(p))
                {
                y.Close();
                continue;
                }
            }
        }

    void ServerListen2(int port)
        {
        // This accepts connections from remote peer (control)
        UWL::XSOCKET s;
        if (!s.BindAndListen(port))
            return;
        s2 = s.Accept();
        if (s2.operator SOCKET() == INVALID_SOCKET)
            return;
        s.Close();

        P2P p;
        vector<char> b;
        for (;;)
            {
            int rv = s2.receive((char*)&p, sizeof(P2P));
            if (rv == 0 || rv == -1)
                break;

            if (p.sz)
                {
                b.resize(p.sz);
                rv = s2.receive((char*)b.data(), p.sz, true);
                if (rv != p.sz)
                    break;
                }

            if (p.Type == TERMINATE)
                {
                // Server orders us to terminate
                get<0>(sessions[p.ID]).Close();
                }

            if (p.Type == DATA)
                {
                // Send the received data from the server to our client
                auto& sock = get<0>(sessions[p.ID]);
                sock.transmit((char*)b.data(), b.size(), true);
                }

            if (p.Type == CDATA)
                {
                // Send the received data from the server to our client
                UncompressB(b);
                auto& sock = get<0>(sessions[p.ID]);
                sock.transmit((char*)b.data(), b.size(), true);
                }

            if (p.Type == CONNECTRESULT)
                {
                // Client notifies us that connection succeeded or failed
                if (p.res == 0)
                    {
                    // OK
                    std::thread t(&CREV::EstablishedConnection, this,p.ID, 0);
                    t.detach();
                    }
                else
                    {
                    // Kill session
                    UWL::InterlockedCall2<size_t, CREV*, unsigned long long>(std::forward<std::function<size_t(CREV*, unsigned long long)>>(&CREV::EraseSessionFromThread), this, std::forward<unsigned long long>(p.ID));
                    }
                }

            }
        s2.Close();

        }

    void Server(int p1,int p2)
        {
        std::thread t1(&CREV::ServerListen1,this, p1);
        t1.detach();
        std::thread t2(&CREV::ServerListen2,this, p2);
        t2.detach();
        }

    void Client(string server, int sport, string lserver, int lport)
        {
        // Connect to server
        XSOCKET Link;
        if (!Link.ConnectTo(server.c_str(), sport))
            return;
        c1 = Link;
        Link.CloseIf();

        P2P p;
        vector<char> b;
        for (;;)
            {
            int rv = c1.receive((char*)&p, sizeof(P2P));
            if (rv == 0 || rv == -1)
                break;

            if (p.sz)
                {
                b.resize(p.sz);
                rv = c1.receive((char*)b.data(), p.sz, true);
                if (rv != p.sz)
                    break;
                }

            if (p.Type == CONNECT)
                {
                // Server orders us to connect
                XSOCKET a;
                if (!a.ConnectTo(lserver.c_str(), lport))
                    {
                    // Failed ....
                    P2P p2;
                    p2.Type = CONNECTRESULT;
                    p2.ID = p.ID;
                    p2.sz = 0;
                    p2.res = -1;
                    if (c1.transmit((char*)&p2, sizeof(p2), true) != sizeof(p2))
                        break;
                    continue;
                    }
                // Success
                sessions[p.ID] = make_tuple<>(a);
                P2P p2;
                p2.Type = CONNECTRESULT;
                p2.ID = p.ID;
                p2.sz = 0;
                p2.res = 0;
                if (c1.transmit((char*)&p2, sizeof(p2), true) != sizeof(p2))
                    break;
                std::thread t(&CREV::EstablishedConnection, this,p.ID, 1);
                t.detach();
                }

            if (p.Type == TERMINATE)
                {
                // Server orders us to terminate
                get<0>(sessions[p.ID]).Close();
                }

            if (p.Type == DATA)
                {
                // Send the received data from the server to our client
                auto& sock = get<0>(sessions[p.ID]);
                sock.transmit((char*)b.data(), b.size(), true);
                }

            if (p.Type == CDATA)
                {
                // Send the received data from the server to our client
                UncompressB(b);
                auto& sock = get<0>(sessions[p.ID]);
                sock.transmit((char*)b.data(), b.size(), true);
                }

            }
        c1.Close();
        }

    };

 

此实现使用了各种组件:Zip Utils,我自己的原子调用和一个简单的 SOCKET 包装器。

 

问题 #3:MSVSMON 流量未压缩

这可以通过我的上述设置轻松解决。A2 和 B2 之间的流量(网络瓶颈)可以被压缩,然后在将其传递到 A1 和 B3 之前解压缩。这样,未压缩的流量只通过本地网络。

在上面的代码中,你只需要启用压缩标志。压缩对于 A1 或 B1 是透明的。

 

问题 #4:部署非常慢。

一个简单的可执行文件可能约为 1MB,以及一个 6MB 的 pdb 文件。较大的二进制文件可能有大量的 MB 需要传输,即使代码发生最细微的更改(调试中非常常见),也需要部署整个 PDB。

你已经通过压缩节省了很多,但是,仍然必须做一些事情来避免仅在存在少量更改时重新部署整个文件。

这次我的DIFF 库可以帮助你。它基于远程差分压缩 API,仅部署二进制文件的更改,从而节省大量的带宽和时间。

以下是针对你想要部署的每个文件的算法:

  • 服务器请求客户端提供现有文件的签名。
  • 如果找不到该文件,服务器会将整个文件上传到客户端。
  • 如果客户端找到该文件,它将计算签名并将其传输到服务器。
  • DIFFLIB::DIFF diff;
    HANDLE hX = CreateFile(LastFile.c_str() ,
       GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
    DIFFLIB::FileRdcFileReader fi(hX);
    DIFFLIB::MemoryDiffWriter wr;
    diff.GenerateSignature(&fi, wr); // wr.p() contains the signature in a vector  of char.
    CloseHandle(hX);
  • 服务器生成本地(较新文件)的签名,然后计算差异并将其发送到客户端。
  •   HANDLE hX = CreateFile(a.local.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
      DIFFLIB::FileRdcFileReader MyFile(hX);
      DIFFLIB::MemoryDiffWriter MySignature;
      // say it was stored in some buffer d
      DIFFLIB::MemoryRdcFileReader RemoteSig(d.data(), d.size()); 
      DIFFLIB::MemoryDiffWriter diffw;
      diff.GenerateDiff(&RemoteSig, MySignature.GetReader(), &MyFile, diffw);
    
  • 然后,客户端使用旧文件和差异重建文件。
  •   HANDLE hY = CreateFile(File.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
      DIFFLIB::FileRdcFileReader r1(hY);
      DIFFLIB::MemoryRdcFileReader diffx(d.data(), d.size());
      DIFFLIB::MemoryDiffWriter reco;
      diff.Reconstruct(&r1, &diffx, 0, reco);
      CloseHandle(hY);
      // And now save reco to the file
      hY = CreateFile(LastFile.c_str(), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
      DWORD A = 0;
      WriteFile(hY, reco.p().data(), (int)reco.sz(), &A, 0);
      CloseHandle(hY);
    

然后你就可以完成了。你已成功修补了文件。

 

我的工具

我已经为个人程序员实现了一个免费工具,远程调试轻松。欢迎使用!

祝你好运。

 

历史

2016 年 3 月 20 日:首次发布。

© . All rights reserved.