-
Вопрос знатокам WinAPI (и желательно POSIX)
В произвольное время разные потоки дёргают мою функцию. Причём потоки могут быть созданы в отдельном Dll кем угодно.
В момент вызова функции я могу получить CurretThreadId.
Необходимо, зная эти ThreadId, навесить на потоки какой-нибудь калбек, который будет вызываться после завершения потока. С эксепшном или в штатном режиме.
Каким образом?
Опережая вопрос "зачем тебе это".
Есть несколько мультипоточных высоконагруженных проектов. Причём бывает потоки создаются в сторонней Dll.
Было решено поменять принципы обработки данных, задействуя threadvar и кеширование данных/буферов.
Но беда в том, что threadvar-области не финализируются при завершении потока.
Тот же TParallel постоянно создаёт и удаляет потоки, а у нас течёт память, причём серьёзно.
-
Вешаешь Хук на EndThread
-
Как?
Сработает ли это на потоки, которые созданы вне Delphi?
-
Создаешь свой поток-следилку и там делаешь:
OpenThread+WaitForSingleObject.
Чтобы следить сразу за несколькими потоками: WaitForMultipleObjects и алгоритм посложнее. Тут можно добавить еще Event, чтобы можно было прерывать WaitForMultipleObjects и обновлять список слежения.
Чтобы следить за более чем MAXIMUM_WAIT_OBJECTS потоками, необходимо создать несколько потоков-следилок.
-
> DayGaykin © (17.05.16 21:43) [3]
Такой подход наверное тоже некорректен.
Калбек по идее должен вызываться в завершающемся потоке. Потому что нужен доступ к TLS.
В предложенном тобой варианте - я его не увижу.
-
DLL_THREAD_DETACH
> который будет вызываться после завершения потокав том же потоке.
-
> NoUser © (18.05.16 00:03) [5]
Но у меня же не Dll
Точнее не обязательно Dll
-
PS.
> Было решено поменять принципы обработки
А если у потока после работы с функцией еще много планов на дальнейшую жизнь, то 'ваша' память будет ему в подарок?
-
> Но у меня же не Dll
ну, добавьте ещё и dll
-
> Crysis © (17.05.16 22:39) [4]
> > DayGaykin © (17.05.16 21:43) [3]
>
> Такой подход наверное тоже некорректен.
> Калбек по идее должен вызываться в завершающемся потоке.
> Потому что нужен доступ к TLS.
> В предложенном тобой варианте - я его не увижу.
Тогда нормально никак.
Можно, конечно извратиться так:
Перехватываешь createthread своего процесса. Вместо точки входа указываешь свою функцию, которая: навесит SEH фрейм и перейдет по исходной точке входа. А дальше должно быть понятно, если знать что такое SEH фрейм.
-
> А если у потока после работы с функцией еще много планов
> на дальнейшую жизнь, то 'ваша' память будет ему в подарок?
В том то и дело
"В подарок" только на время работы. А по завершению потока - память надо грамотно забрать.
-
> Перехватываешь createthread своего процесса. Вместо точки
> входа указываешь свою функцию, которая: навесит SEH фрейм
> и перейдет по исходной точке входа. А дальше должно быть
> понятно, если знать что такое SEH фрейм.
Тогда уж не CreateThread, а ExitThread
Но я боюсь, Касперский заблокирует наш софт у клиентов. Или иные антивирусы будут ругаться.
-
> Crysis © (18.05.16 00:30) [11]
В ExitThread может и не настать.
> Но я боюсь, Касперский заблокирует наш софт у клиентов.
> Или иные антивирусы будут ругаться.
В своем процессе ты можешь делать что хочешь.
-
Тогда лучше ZwCreateThread перехватить и там свою потоковую процедуру с SEH.
-
> Crysis © (17.05.16 16:43)
> Причём бывает потоки создаются в сторонней Dll.Было решено
> поменять принципы обработки данных, задействуя threadvar
> и кеширование данных/буферов.Но беда в том, что threadvar-
> области не финализируются при завершении потока.
threadvar - здесь костыль.
Если какому-либо потоку нужны приватные данные, то он может
распределить память и передать твоей функции указатель на нее.
Перед завершением работы он должен ее освободить.
В случае если код потоков менять нельзя, то память для
потоков может выделять твоя функция, при условии, что
один (или более поток) приложения будет следить путем
WaitForMultipleObjects за рабочими и по их завершении
освобождать связанную с ними память.
Про синхронизацию доступа к общим ресурсам промолчу,
бо, с этим, наверное, все в порядке.
--
Regards, LVT.
-
> Leonid Troyanovsky © (18.05.16 13:27) [14]
> будет следить путем WaitForMultipleObjects за рабочими и
> по их завершении освобождать связанную с ними память.
Схему можно упростить до следующей:
Функция ведет список (ThreadList) троек: ThreadId,
OTHandle(хендл OpenThread),
Data(указ. на распред. память).
Когда список (или память) переполнится, проходим по списку,
проверяем состояние OTHandle (f.e., GetThreadExitCode),
утилизируем память, CloseHandle, удаляем лишнее.
--
Regards, LVT.
-
> Rouse_ © (18.05.16 12:00) [13]
> Тогда лучше ZwCreateThread перехватить и там свою потоковую
> процедуру с SEH.
А можно поподробнее?
Подойдёт ли этот способ для потоков, созданных вне Delphi?
-
> А можно поподробнее?
Лучше расскажи/покажи как это "созданные кем угодно в отдельном Dll" потоки дёргают функцию в твоём exe ?
-
> NoUser © (18.05.16 17:14) [17]
А что удивительного? Передаётся калбек, он вызывает в отдельных потоках.
-
> Передаётся калбек
И что, внутри калбека каждому "новенькому" потоку даруется безвозмездно кусок памяти? - хорошенький "принцип обработки данных" !
-
> NoUser © (18.05.16 17:50) [19]
Ну какое значение имеет, даём мы кому-то кусок памяти или нет...
Есть особенность использования. Если завершения всех потоков не получится отследить - я сделаю через SystemThreadEndProc для своих потоков и более сложный и медленный вариант для чужих. Но хочется универсально.
-
Facepalm
-
> Но хочется универсально.
Сделай второй калбек (для зачистки), сделай свою длл-ку в которую "передай" его и там вызывай в момент DLL_THREAD_DETACH.
-
> Crysis © (18.05.16 14:56) [16]
> А можно поподробнее?
> Подойдёт ли этот способ для потоков, созданных вне Delphi?
Что значит "вне Delphi"?
Так ты перехватишь создание нити в твоем приложении (за исключением случая CreateRemoteThread из другого процесса)
-
> Rouse_ © (18.05.16 22:54) [23]
Допустим я использую сторонний движок, будь то звуковая библиотека, физика, базы данных. Они написаны на другом языке программирования, исходников нет; зато известно, что они вызывают CreateThread и внутри потока дёргают мой калбек. "Перехват ZwCreateThread" поможет в данной ситуации или нет? Можно поподробнее, как перехватить?
-
-
Спасибо, буду пробовать!
-
> решено поменять принципы обработки данных, задействуя threadvar
> и кеширование данных/буферов
- кеширование внешнего контекста в реентерабельной функции - нонсенс...
А промежутоные буфера надо выделять на стеке(в пределах отведенных(по умолчанию) ~ 1Мб естественно)...
-
> Rouse_ © (19.05.16 16:04) [25]
А не проще перехватить импорт DLL?
-
> DayGaykin © (19.05.16 20:32) [28]
> А не проще перехватить импорт DLL?
Он только для статической линковки, используется.
-
> Rouse_ © (19.05.16 20:37) [29]
Со сплайсингом в многопоточности точно не без проблем обойдется, да еще если на 64 битах запускать.
-
Если правильно делать с использование хотпатча (атомарной замены 2 байт в области нопов в прологе функции) - никаких проблем не будет. Этож штатный механизм.
-
В частности в 64 битах делается воот так:
Вот тело ZwCreateThread (адрес точки входа 77AFD890), перед ней идет пятибайтный длинный NOP
77AFD88B: 0F 1F 44 00 00 nop dword ptr [rax+rax+00h]
77AFD890: B8 DB 3C 03 00 mov eax, 00033CDBh
77AFD895: 48 63 C0 movsxd rax, rax
77AFD898: FF E0 jmp rax
77AFD89A: C3 ret
первым шагом делаем установку джампа в область длинного нопа,
вторым шагом - атомарно (через LOCK XCHG) джамп на эту область вместо инструкции JMP RAX (для других функций вместо него будет SYSCALL)
-
> Rouse_ © (19.05.16 21:10) [31]
> Если правильно делать с использование хотпатча (атомарной
> замены 2 байт в области нопов в прологе функции) - никаких
> проблем не будет. Этож штатный механизм.
Если я правильно помню, условия хот-патча не выполняются если программа запущена в 64хбитной ос.
-
> Leonid Troyanovsky © (18.05.16 14:02) [15]
Вот набросал пример, без всякой оптимизации и контроля ошибок.
type
TMItem = packed record
ThreadId: Cardinal;
OTHandle: THandle;
Data: DWord;
end;
PMItem = ^TMItem;
var
ftl: TThreadList;
function GetDataPtr: PDWord;
var
i: Longint;
pmi: PMItem;
exCode: Cardinal;
begin
Result := nil;
with ftl.LockList do
try
for i:= Count-1 downto 0 do
begin
pmi := PMItem(List[i]);
if pmi.ThreadId = GetCurrentThreadId then
begin
Result := @pmi.Data;
Continue;
end;
GetExitCodeThread(pmi.OTHandle, exCode);
if ExCode <> STILL_ACTIVE then
begin
OutputDebugString(PChar(IntToStr(ExCode)));
CloseHandle(pmi.OTHandle);
Dispose(pmi);
Delete(i);
end;
end;
if Result = nil then
begin
New(pmi);
pmi.ThreadId := GetCurrentThreadId;
DuplicateHandle( GetCurrentProcess,
GetCurrentThread,
GetCurrentProcess,
@pmi.OTHandle,
0,
False,
DUPLICATE_SAME_ACCESS);
pmi.Data := 0;
Add(pmi);
Result := @pmi.Data;
end;
finally
ftl.UnlockList;
end;
end;
type
TMyThread = class(TThread)
procedure Execute; override;
end;
procedure TMyThread.Execute;
var
pData: PDWord;
begin
pData := GetDataPtr;
pData^ := Random(10000);
Sleep (pData^);
ReturnValue := pData^;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ftl := TThreadList.Create;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TMyThread.Create(False).FreeOnTerminate := True;
end;
Смотрим в EventLog.
--
Regards, LVT.
-
> DayGaykin © (20.05.16 03:33) [33]
> Если я правильно помню, условия хот-патча не выполняются
> если программа запущена в 64хбитной ос.
Все выполняется иначе как бы перехваты в моем софте работали?
-
А, или ты про 32 битный софт в 64 битной ОС? Да, там некоторые функции идут без пролога, но там тоже можно пошаманить
-
> Rouse_ © (20.05.16 09:37) [36]
> А, или ты про 32 битный софт в 64 битной ОС? Да, там некоторые
> функции идут без пролога, но там тоже можно пошаманить
Да-да. Ты мне про это, кстати, и говорил.
-
Почему бы не поменять способ работы на что-то типа
BeginUpdate;
EndUpdate;
?