Visual Studio 发布模式阻止在调试模式下执行的代码.使用 WinHTTP 和多线程

Visual Studio release mode blocks code execution that is executed in debug mode. Using WinHTTP and Multi-Threading

本文关键字:模式 执行 代码 使用 多线程 WinHTTP 调试 布模式 Studio Visual      更新时间:2023-10-16

我正在使用WinHTTP发出GET请求,并且我正在使用WinHttpOpen与回调函数异步使用。

HINTERNET hSession = WinHttpOpen(L"", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC);
if (hSession) {
void* phSession_Callback = WinHttpSetStatusCallback(hSession, (WINHTTP_STATUS_CALLBACK)hSession_Callback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0);
if (phSession_Callback == WINHTTP_INVALID_STATUS_CALLBACK) {
//error handling code
}
}

在我的GET函数结束时,我使用位字段来存储数据。在该数据中,有一个标志指示请求何时成功完成(即 if 检查(。

while (1) {
std::this_thread::sleep_for(std::chrono::microseconds(1));
if ((g_nBitFlags >> 3) & 1) {
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnection);
WinHttpCloseHandle(hSession);
if ((g_nBitFlags >> 2) & 1) {
return -1;
} else if ((g_nBitFlags >> 1) & 1) {
return 0;
} else if ((g_nBitFlags >> 0) & 1) {
return g_nBitFlags >> 4;
}
}
}

这是我的hSession回调,它触发函数(作为单独的线程(,该函数设置标志是否成功接收。 (请注意,此线程不会等待加入,即使http请求在该worker函数之前完成,它也是完全独立的(

void WINAPI hSession_Callback(
IN HINTERNET hInternet,
IN DWORD_PTR dwContext,
IN DWORD dwInternetStatus,
IN LPVOID lpvStatusInformation,
IN DWORD dwStatusInformationLength
) {
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: {
std::thread(dataAvail_worker, &httplib::g_nBitFlags, lpvStatusInformation, hInternet).detach();
}
}

请求的编辑: dataAvail_worker:

#include "pch.h"
#include "dataAvail.h"
namespace httplib {
extern char* g_szDataBuffer;
}
void dataAvail_worker(unsigned short* pnBitfield, LPVOID lpvDataLength, HINTERNET hRequest) {
if (*(DWORD*)lpvDataLength > 0) {
if (httplib::g_szDataBuffer == nullptr) {
httplib::g_szDataBuffer = new char[*(DWORD*)lpvDataLength + 1];
httplib::g_szDataBuffer[*(DWORD*)lpvDataLength] = 0;
WinHttpReadData(hRequest, httplib::g_szDataBuffer, *(DWORD*)lpvDataLength, NULL);
} else {
char* temp_buffer = new char[strlen(httplib::g_szDataBuffer) + 1];
temp_buffer[strlen(httplib::g_szDataBuffer)] = 0;
memcpy(temp_buffer, httplib::g_szDataBuffer, strlen(httplib::g_szDataBuffer)); //-V575 (PVS-Studio FalseAlarm)
delete[] httplib::g_szDataBuffer;
httplib::g_szDataBuffer = new char[strlen(temp_buffer) + *(DWORD*)lpvDataLength + 1];
httplib::g_szDataBuffer[strlen(temp_buffer) + *(DWORD*)lpvDataLength] = 0;
memcpy(httplib::g_szDataBuffer, temp_buffer, strlen(temp_buffer));
WinHttpReadData(hRequest, httplib::g_szDataBuffer + strlen(temp_buffer), *(DWORD*)lpvDataLength, NULL);
delete[] temp_buffer;
}
} else *pnBitfield += (~(*pnBitfield << 12) & 0b1000);
}

请求的编辑: 获取.cpp:

#include "pch.h"
#include "get.hpp"
#include "callbacks/get_Session/Session.h"
void _appendToHeader(std::string& private_member, const char* szValue, const char* szHeaderName);
namespace httplib {
char* g_szDataBuffer = nullptr;
GetRequest::GetRequest(FILE* temp_pSTDOUT) {
*stdout = *temp_pSTDOUT;
m_szData = &g_szDataBuffer;
}
GetRequest::~GetRequest() {
delete[] *m_szData;
}
// XXXXXXXXXXXX0000 = X (12bit): Reserved for HTTP RETURN STATUS CODE
// XXXXXXXXXXXX0001 = request succsessfully finished + valid http return status code available
// XXXXXXXXXXXX0010 = return SendRequest() function with 0 (Ivalid URI)
// XXXXXXXXXXXX0100 = return SendRequest() function with -1 (SSL Certificate error)
// XXXXXXXXXXXX1000 = is the request finished / should the function return
USHORT g_nBitFlags = 0b0000'0000'0000'0000;
RESPONSE GetRequest::SendRequest(Address szURI, Port nPort) {
char* pStartEndpoint = (char*)strchr(szURI, L'/');
HINTERNET hSession = WinHttpOpen(L"", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS,     WINHTTP_FLAG_ASYNC);
if (hSession) {
void* phSession_Callback = WinHttpSetStatusCallback(hSession, (WINHTTP_STATUS_CALLBACK)hSession_Callback,   WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0);
if (phSession_Callback == WINHTTP_INVALID_STATUS_CALLBACK) {
// TODO: Display the error message in the gui/console of DiscordPP
printf("TEMPORARY: GetLastError() from GetRequest::SendRequest at WinHttpOpen() async at WinHttpSetStatusCallback   () (Cannot install callback function): %un", GetLastError()); // This is temporary. Should not be used.
}
} else {
// TODO: Display the error message in the gui/console of DiscordPP
printf("TEMPORARY: GetLastError() from GetRequest::SendRequest at WinHttpOpen() async: %un", GetLastError()); // This   is temporary. Should not be used.
}
wchar_t* szwConnectionUri;
if (pStartEndpoint != nullptr) {
szwConnectionUri = new wchar_t[pStartEndpoint - szURI + 1];
ZeroMemory(szwConnectionUri, (pStartEndpoint - szURI + 1) * sizeof(wchar_t));
mbstowcs(szwConnectionUri, szURI, pStartEndpoint - szURI);
} else {
szwConnectionUri = new wchar_t[strlen(szURI) + 1];
ZeroMemory(szwConnectionUri, (strlen(szURI) + 1) * sizeof(wchar_t));
mbstowcs(szwConnectionUri, szURI, strlen(szURI));
}

HINTERNET hConnection = WinHttpConnect(hSession, szwConnectionUri, nPort, 0);
if (!hConnection) {
// TODO: Display the error message in the gui/console of DiscordPP
printf("TEMPORARY: GetLastError() from GetRequest::SendRequest at WinHttpConnect() async: %un", GetLastError()); //    This is temporary. Should not be used.
if (GetLastError() == 12005)
return 0;
}
delete[] szwConnectionUri;
wchar_t* szwEndpointUri;
if (pStartEndpoint != nullptr) {
szwEndpointUri = new wchar_t[(strlen(szURI) - (pStartEndpoint - szURI)) + 1];
ZeroMemory(szwEndpointUri, ((strlen(szURI) - (pStartEndpoint - szURI)) + 1) * sizeof(wchar_t));
mbstowcs(szwEndpointUri, szURI + (pStartEndpoint - szURI), (strlen(szURI) - (pStartEndpoint - szURI)));
} else {
szwEndpointUri = (wchar_t*)L"/";
}
const wchar_t* szDataAcceptTypes[] = {L"", 0};
HINTERNET hRequest = WinHttpOpenRequest(hConnection, L"GET", szwEndpointUri, L"HTTP/2.0", WINHTTP_NO_REFERER,   szDataAcceptTypes, WINHTTP_FLAG_SECURE);
if (!hRequest) {
// TODO: Display the error message in the gui/console of DiscordPP
printf("TEMPORARY: GetLastError() from GetRequest::SendRequest at WinHttpOpenRequest() async: %un", GetLastError   ()); // This is temporary. Should not be used.
}
if (pStartEndpoint != nullptr) delete[] szwEndpointUri;
wchar_t* szwHeaders = strcmp(m_szHeadersOut.c_str(), "") ? new wchar_t[m_szHeadersOut.length() + 1] : nullptr;
if (szwHeaders != nullptr) {
ZeroMemory(szwHeaders, (m_szHeadersOut.length() + 1) * sizeof(wchar_t));
mbstowcs(szwHeaders, m_szHeadersOut.c_str(), m_szHeadersOut.length());
}
if (strcmp(m_szHeadersOut.c_str(), "") != 0 && szwHeaders != nullptr) {
if (!WinHttpAddRequestHeaders(hRequest, szwHeaders, wcslen(szwHeaders), WINHTTP_ADDREQ_FLAG_ADD |   WINHTTP_ADDREQ_FLAG_REPLACE))
// TODO: Display the error message in the gui/console of DiscordPP
printf("TEMPORARY: GetLastError() from GetRequest::SendRequest at WinHttpOpenRequest() async: %un", GetLastError   ()); // This is temporary. Should not be used.
}
if (szwHeaders != nullptr) {
if (!WinHttpSendRequest(hRequest, szwHeaders, wcslen(szwHeaders), WINHTTP_NO_REQUEST_DATA, NULL, 0, 0))
// TODO: Display the error message in the gui/console of DiscordPP
printf("TEMPORARY: GetLastError() from GetRequest::SendRequest at WinHttpOpenRequest() async: %un", GetLastError   ()); // This is temporary. Should not be used.
} else {
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, NULL, 0, 0))
// TODO: Display the error message in the gui/console of DiscordPP
printf("TEMPORARY: GetLastError() from GetRequest::SendRequest at WinHttpOpenRequest() async: %un", GetLastError   ()); // This is temporary. Should not be used.
}
DWORD dwOption = WINHTTP_DISABLE_REDIRECTS;
WinHttpSetOption(hRequest, WINHTTP_OPTION_DISABLE_FEATURE, &dwOption, sizeof(dwOption));
while (1) {
std::this_thread::sleep_for(std::chrono::microseconds(1));
if ((g_nBitFlags >> 3) & 1) {
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnection);
WinHttpCloseHandle(hSession);
if ((g_nBitFlags >> 2) & 1) {
return -1;
} else if ((g_nBitFlags >> 1) & 1) {
return 0;
} else if ((g_nBitFlags >> 0) & 1) {
return g_nBitFlags >> 4;
}
}
}
}
bool GetRequest::AddHeader(HeaderType Header, HeaderValue szValue) {
switch (Header) {
case HeaderType::Accept:
_appendToHeader(m_szHeadersOut, (char*)szValue, "Accept");
break;
case HeaderType::Accept_Encoding:
_appendToHeader(m_szHeadersOut, (char*)szValue, "Accept-Encoding");
break;
case HeaderType::Authorization:
_appendToHeader(m_szHeadersOut, (char*)szValue, "Authorization");
break;
case HeaderType::Connection:
_appendToHeader(m_szHeadersOut, (char*)szValue, "Connection");
break;
case HeaderType::Content_Encoding:
_appendToHeader(m_szHeadersOut, (char*)szValue, "Content-Encoding");
break;
case HeaderType::Content_Length:
_appendToHeader(m_szHeadersOut, (char*)szValue, "Content-Length");
break;
case HeaderType::Content_Type:
_appendToHeader(m_szHeadersOut, (char*)szValue, "Content-Type");
break;
case HeaderType::User_Agent:
_appendToHeader(m_szHeadersOut, (char*)szValue, "User-Agent");
break;
case HeaderType::Upgrade:
_appendToHeader(m_szHeadersOut, (char*)szValue, "Upgrade");
break;
case HeaderType::Referer:
_appendToHeader(m_szHeadersOut, (char*)szValue, "Referer");
break;
default:
return false;
}
return true;
}
const char* GetRequest::GetData() {
return *m_szData;
}
}
void _appendToHeader(std::string &private_member, const char* szValue, const char* szHeaderName) {
private_member += szHeaderName;
private_member += ":";
private_member += szValue;
private_member += "rn";
}

请求的编辑:回调.cpp:

#include "pch.h"
#include "Session.h"
#include "workers/dataAvail/dataAvail.h"
#include "workers/headersAvail/headersAvail.h"
namespace httplib {
extern USHORT g_nBitFlags;
extern char* g_szDataBuffer;
}
void WINAPI hSession_Callback(
IN HINTERNET hInternet,
IN DWORD_PTR dwContext,
IN DWORD dwInternetStatus,
IN LPVOID lpvStatusInformation,
IN DWORD dwStatusInformationLength
) {
switch (dwInternetStatus) {
case WINHTTP_CALLBACK_STATUS_REQUEST_SENT:
WinHttpReceiveResponse(hInternet, 0);
break;
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: {
std::thread(headersAvail_worker, &httplib::g_nBitFlags, hInternet).detach();
WinHttpQueryDataAvailable(hInternet, NULL);
}
break;
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: {
std::thread(dataAvail_worker, &httplib::g_nBitFlags, lpvStatusInformation, hInternet).detach();
}
break;
case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
httplib::g_nBitFlags = 0;
httplib::g_nBitFlags += (~(httplib::g_nBitFlags << 13) & 0b0100);
httplib::g_nBitFlags += (~(httplib::g_nBitFlags << 12) & 0b1000);
break;
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
httplib::g_nBitFlags = 0;
httplib::g_nBitFlags += (~(httplib::g_nBitFlags << 14) & 0b0010);
httplib::g_nBitFlags += (~(httplib::g_nBitFlags << 12) & 0b1000);
break;
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
WinHttpQueryDataAvailable(hInternet, NULL);
break;
case WINHTTP_CALLBACK_STATUS_REDIRECT:
printf("Session handler: Redirection was attempted, and canceled. Redirect Destination: %sn", (char*)  lpvStatusInformation);
break;
}
}

请求的编辑:主要.cpp:

#include "pch.h"
#include "../../httplib/src/get.hpp"
#include "../../httplib/src/post.hpp"
#ifdef _DEBUG
#pragma comment(lib, "../httplib/bin/Debug_x86/httplib_Debug_x86.lib")
#else
#pragma comment(lib, "../httplib/bin/Release_x86/httplib_Release_x86.lib")
#endif
int main() {
//ToDo: Switch to WINDOWS sub, make gui with console to the side.
httplib::GetRequest get(stdout);
get.AddHeader(HeaderType::Authorization, "token");
int nGet = get.SendRequest("discord.com/api/channels/678958544606724115/messages?limit=1");
printf("HTTP/GET Method: %dnnData: %snnnn", nGet, get.GetData());
}

所以我的问题是,如果该线程在 GET 函数结束时休眠丢失,那么它永远处于循环中,我使用了许多工具来调试它并查看它是如何做到这一点的,但我唯一有效的结论是它处于永无止境的循环中,因为 while 语句正在消耗所有资源,从而阻止进一步的进展(尽管该循环中的 CPU 使用率不是100%, 它在锐龙 9x 上只有大约 3600%(

编辑:为了澄清这是在没有thread_sleep的调试模式下工作,只有在 Relase 模式下它才会执行此行为。

您在g_nBitFlags上存在数据争用,因此存在未定义的行为。一个线程在g_nBitFlags上旋转,等待另一个线程更新它,但您没有使用原子变量或其他同步机制。因此,编译器有权假设变量不能更改。因此,在发布模式下,编译器只是决定优化重新加载g_nBitFlags,并且您的代码继续循环相同的(常量(值。

简单的解决方案:使g_nBitFlags成为原子。

因此,在程序集分析之后,问题是在发布模式下,编译器使用此代码来检查标志变量 (g_nBitFlags_Get(:

005B1500  test        al,8  
005B1502  je          httplib::GetRequest::SendRequest+350h (05B1500h)

我认为(这可能不正确(是假设标志变量将在EAX中(但是因为标志变量的值是在不同的线程中设置的,因此EAX寄存器对于每个线程都是不同的值(正如mpoeter所说:数据赛车(。我想编译器认为这是一种优化的方法,但它没有意识到该数据是在外部修改

的这是调试程序集:

; This is the ( while (1) )
009F1595  mov         eax,1  
009F159A  test        eax,eax  
009F159C  je          httplib::GetRequest::SendRequest+651h (09F1631h) ; never jumps
; Here begins the check ( (g_nBitFlags_Get >> 3) & 1) )
009F15A2  movzx       eax,word ptr [httplib::g_nBitFlags_Get (09FB2F4h)]  
009F15A9  sar         eax,3  
009F15AC  and         eax,1  ; This here is the important check
009F15AF  je          httplib::GetRequest::SendRequest+64Ch (09F162Ch)  ; if the flags variable is in a correct state it doesnt jump back to the start (if ZF=0 goes in the if statement)

编辑:编译器编译:D非常混乱,我添加了asm代码而不是线程暂停,它更改了代码以实际正常工作:

00CD1500  mov         edx,539h ; My asm
00CD1505  test        byte ptr [httplib::g_nBitFlags_Get (0CD53FCh)],8 ; ACTUAL working check :D
00CD150C  je          httplib::GetRequest::SendRequest+350h (0CD1500h) 

编辑2:使用NOP作为我的asm进行测试,它运行良好!