winapi — при двойном вызове EnumServicesStatusEx () я все еще получаю EROR_MORE_DATA в переполнении стека

Я вызываю EnumServicesStatusEx () дважды в моем коде, первый раз должен произойти сбой и поместить правильный размер буфера в dwBuffNeeded, чтобы при втором вызове размер буфера был правильным. Но иногда я не всегда получаю ERROR_MORE_DATA после второго звонка. Есть идеи почему? Спасибо

DWORD pId=GetCurrentProcessId();
SC_HANDLE hSCM    = NULL;
PUCHAR  pBuf    = NULL;
ULONG  dwBufSize   = 0x00;
ULONG  dwBufNeed   = 0x00;
ULONG  dwNumberOfService = 0x00;
LPENUM_SERVICE_STATUS_PROCESS pInfo = NULL;

hSCM = OpenSCManager( NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_CONNECT );

if (hSCM == NULL)
{
GetCustomLog().Log( SV_ERROR, 10004807, "Could not open Service Control Manager: %s", GetLastOSErrorString().c_str() );
return;
}

//Query services once to get correct buffer size, always fails
if ( EnumServicesStatusEx(
hSCM,
SC_ENUM_PROCESS_INFO,
SERVICE_WIN32,
SERVICE_ACTIVE,
NULL,
dwBufSize,
&dwBufNeed,
&dwNumberOfService,
NULL,
NULL) == 0 )
{

DWORD err = GetLastError();
if ( ERROR_MORE_DATA == err )
{
dwBufSize = dwBufNeed + 0x10;
pBuf  = (PUCHAR) malloc(dwBufSize);

//Query services again with correct buffer size
if ( EnumServicesStatusEx(
hSCM,
SC_ENUM_PROCESS_INFO,
SERVICE_WIN32,
SERVICE_ACTIVE,
pBuf,
dwBufSize,
&dwBufNeed,
&dwNumberOfService,
NULL,
NULL ) == 0 )

{
//FAILS HERE
GetCustomLog().Log( SV_ERROR, 10004808, "Could not enumerate services, error: %s", GetLastOSErrorString().c_str() );
free(pBuf);
return;
}
}
else
{
GetCustomLog().Log( SV_ERROR, 10004809, "Could not enumerate services, error: %s", GetLastOSErrorString().c_str() );
return;
}

1

Решение

У меня тоже была такая же проблема, но при запросе SERVICE_STATE_ALL, который, как я думал, потребовал бы одинакового объема памяти при каждом вызове (если служба не была установлена ​​/ удалена). Недостаточно просто повторить попытку с буфером размера, возвращенного в аргументе pcbBytesNeeded:

BOOL WINAPI EnumServicesStatusEx(
_In_         SC_HANDLE hSCManager,
_In_         SC_ENUM_TYPE InfoLevel,
_In_         DWORD dwServiceType,
_In_         DWORD dwServiceState,
_Out_opt_    LPBYTE lpServices,
_In_         DWORD cbBufSize,
_Out_        LPDWORD pcbBytesNeeded,
_Out_        LPDWORD lpServicesReturned,
_Inout_opt_  LPDWORD lpResumeHandle,
_In_opt_     LPCTSTR pszGroupName
);

В отличие от других вызовов API WIN32, это не возвращает абсолютное количество необходимых байтов, но дополнительный байтов, необходимых относительно параметра cbBufSize. В качестве эксперимента я предоставил преднамеренно небольшой буфер и просто удваивал его каждый раз, чтобы выяснить, что система будет возвращать в ответ pcbBytesNeeded. В этой системе размер ENUM_SERVICE_STATUS_PROCESS составляет 56 байт. Последняя строка — это самый маленький буфер, который приводит к успешному вызову.

+-----------+----------------+
| cbBufSize | pcbBytesNeeded |
+-----------+----------------+
|      112  |         37158  |
|      224  |         37013  |
|      448  |         36766  |
|      896  |         36374  |
|     1792  |         35280  |
|     3584  |         33202  |
|     7168  |         28972  |
|    14336  |         20765  |
|    28672  |          4215  |
|    32032  |             0  |
+-----------+----------------+

Вы можете видеть, что каждая строка приблизительно складывается до требуемого размера буфера, а также что требуемый размер буфера для системы не очень предсказуем. Число служебных записей, возвращаемых при успешном вызове, в этом случае составляло 233, что требует только 13048 байтов. Оказывается, строки, на которые указывают указатели ENUM_SERVICE_STATUS_PROCESS lpDisplayName и lpServiceName, просто хранятся в хвостовой части предоставленного буфера (мне всегда было интересно, где была их поддержка, и почему она была стабильной и не нуждалась в отдельности освобожден). В любом случае это объясняет несколько недетерминированные ответы, а также нечетные числа: система, скорее всего, имеет текущую запись, которая не подходит и точно знает, сколько ей нужно для этого, но догадывается об оставшейся части.

Код ниже надежен и обычно занимает ровно два вызова, но иногда может занять три (даже с SERVICE_STATE_ALL). Я не знаю, зачем нужны три, а недооценка системой задерживается на несколько минут за раз, но в конце концов решает сама. Я никогда не видел, что нужно четыре звонка.

int EnumerateAllServices(SC_HANDLE hSCM) {
void* buf = NULL;
DWORD bufSize = 0;
DWORD moreBytesNeeded, serviceCount;
for (;;) {
printf("Calling EnumServiceStatusEx with bufferSize %d\n", bufSize);
if (EnumServicesStatusEx(
hSCM,
SC_ENUM_PROCESS_INFO,
SERVICE_WIN32,
SERVICE_STATE_ALL,
(LPBYTE)buf,
bufSize,
&moreBytesNeeded,
&serviceCount,
NULL,
NULL)) {
ENUM_SERVICE_STATUS_PROCESS* services = (ENUM_SERVICE_STATUS_PROCESS*)buf;
for (DWORD i = 0; i < serviceCount; ++i) {
printf("%s\n", services[i].lpServiceName);
}
free(buf);
return 0;
}
int err = GetLastError();
if (ERROR_MORE_DATA != err) {
free(buf);
return err;
}
bufSize += moreBytesNeeded;
free(buf);
buf = malloc(bufSize);
}
}

«+ =» — это «трюк», и он не очень четко задокументирован (но, оглядываясь назад, теперь я понимаю нюанс в объяснении MSDN параметра pcbBytesNeeded):

pcbBytesNeeded [out]

A pointer to a variable that receives the number of bytes
needed to return the remaining service entries, if the buffer
is too small.
2

Другие решения

Поскольку служебная информация не является статичной, и теоретически все может произойти между двумя вызовами EnumServicesStatusEx()этот API лучше всего использовать итеративно.

Как предполагает Кэт Марсен, вы можете, конечно, многократно пытаться получить все сервисы (зацикливание и увеличение буфера, пока API возвращает ERROR_MORE_DATA).
Но это имеет 2 недостатка:

  1. Вы говорите СКМ постоянно заполнять информацию, которую он вам уже отослал (даже это довольно редко, я на самом деле хочу этого избежать).
  2. Согласно документации MSDN, существует жесткий верхний предел 256 Кбайт для буфера. Как я упоминал в комментарии выше, последнее практически маловероятно; Тем не менее вы хотите быть осторожным, как программист.

Итерационный подход

Напротив, мы можем только предположить, что IMO, у автора, ответственного за EnumServicesStatusEx (), была причина спроектировать его следующим образом:

  • Буфер, который заполняется немедленно, если он действителен и достаточно велик хотя бы для одной службы.
  • Выходной параметр для оставшихся байтов, который SCM предполагает для служб, которые еще предстоит повторить.
  • Наиболее важно, что есть выходной параметр, сообщающий вам точку возобновления.

И точка возобновления, и оставшаяся часть обеспечивают гораздо более простой итеративный подход.

Позвольте мне продемонстрировать полные реальные функции, работающие достаточно хорошо и показывающие некоторые вкусности C ++.
chunk_fetch_all_win32_services предварительно выделяет некоторый объем памяти, вызывает обратный вызов для каждого чанка и позволяет обратному вызову решить, потребляет ли он память или оставляет его для повторного использования.
enum_all_win32_services потребляет каждый кусок и в свою очередь вызывает обратный вызов для каждого отдельного сервиса.

#include <type_traits>
#include <memory>
#include <stddef.h>
#include <Windows.h>

using std::unique_ptr;
using std::error_code;
using std::system_error;
using std::system_category;
using std::function;
using std::make_unique;/** @short Fetch all win32 services in chunks.

This function fetches all services of type `SERVICE_WIN32=(SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS)`,
in chunks as decided by the SCM (refer to EnumServicesStatusEx()).

@param scmHandle Handle to the SCM with access right SC_MANAGER_ENUMERATE_SERVICE
@param stateMask One of SERVICE_ACTIVE, SERVICE_INACTIVE, SERVICE_STATE_ALL
In the callback you can decide whether you want to consume the passed memory or leave
it in order to be reused.

@note This is most probably rare but expect the callback being invoked multiple times
in case the SCM didn't return information about all services at once.
*/
bool chunk_fetch_all_win32_services(SC_HANDLE scmHandle, DWORD stateMask, const function<bool(unique_ptr<ENUM_SERVICE_STATUS_PROCESS[]>&, DWORD /*nServices*/)>& cb, error_code* ec)
{
// (optionally) preallocate
// (the amount stems from Win XP's upper size of 64k;
//  on a typical Win7 system there are about 220 services (about 34k))
unique_ptr<BYTE[]> mem = make_unique<BYTE[]>(64 * 1024);
DWORD nAllocated = 64 * 1024, nRemaining;
DWORD resumePoint = 0;
do
{
DWORD nServices;
if (!EnumServicesStatusEx(scmHandle, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, stateMask, mem.get(), nAllocated, &nRemaining, &nServices, &resumePoint, nullptr))
{
const int errorCode = GetLastError();
if (errorCode != ERROR_MORE_DATA)
{
if (!ec)
throw system_error{ errorCode, system_category(), "Can't enumerate services" };
ec->assign(errorCode, system_category());
return false;
}
}

if (nServices)
{
// memory initialized, transfer ownership to typed pointer
unique_ptr<ENUM_SERVICE_STATUS_PROCESS[]> cache{ LPENUM_SERVICE_STATUS_PROCESS(mem.release()) };
if (!cb(cache, nServices))
// early bail-out requested
return true;

// document that the typed pointer can be 'downgraded' again without the need to destroy objects
static_assert(std::is_trivially_destructible_v<ENUM_SERVICE_STATUS_PROCESS>);
// possibly reuse existing buffer
mem.reset(PBYTE(cache.release()));
}

if (nRemaining)
{
// re-allocate if buffer too small or consumed by callback
if (!mem || nAllocated < nRemaining)
mem = make_unique<BYTE[]>(nRemaining),
nAllocated = nRemaining;
}

// loop as long as there are more services to be enumerated
} while (nRemaining);

return true;
}

/** @short Enumerate all win32 services.

This function enumerates all services of type `SERVICE_WIN32=(SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS)`.

@param scmHandle Handle to the SCM with access right SC_MANAGER_ENUMERATE_SERVICE
@param stateMask One of SERVICE_ACTIVE, SERVICE_INACTIVE, SERVICE_STATE_ALL
@param cb Callback receiving process information of a single service process information and the number of services.

@note This is most probably rare but expect the number of services passed to the callback to change
in case the SCM didn't return information about all services at once; if, then this of course happens
after the provided number of information items have been iterated.
*/
bool enum_all_win32_services(SC_HANDLE scmHandle, DWORD stateMask, const function<bool(ENUM_SERVICE_STATUS_PROCESS&, DWORD /*nServices*/)>& cb, error_code* ec)
{
return chunk_fetch_all_win32_services(scmHandle, stateMask, [&cb](unique_ptr<ENUM_SERVICE_STATUS_PROCESS[]>& cache, DWORD nServices)
{
for (size_t idx = 0; idx < nServices; ++idx)
{
if (!cb(cache[idx], nServices))
// early bail-out requested
return true;
}

return true;
}, ec);
}
1

По вопросам рекламы [email protected]