Конференция "Прочее" » Отловить завершение чужих потоков
 
  • Crysis © (17.05.16 16:43) [0]
    Вопрос знатокам WinAPI (и желательно POSIX)

    В произвольное время разные потоки дёргают мою функцию. Причём потоки могут быть созданы в отдельном Dll кем угодно.
    В момент вызова функции я могу получить CurretThreadId.
    Необходимо, зная эти ThreadId, навесить на потоки какой-нибудь калбек, который будет вызываться после завершения потока. С эксепшном или в штатном режиме.
    Каким образом?

    Опережая вопрос "зачем тебе это".
    Есть несколько мультипоточных высоконагруженных проектов. Причём бывает потоки создаются в сторонней Dll.
    Было решено поменять принципы обработки данных, задействуя threadvar и кеширование данных/буферов.
    Но беда в том, что threadvar-области не финализируются при завершении потока.
    Тот же TParallel постоянно создаёт и удаляет потоки, а у нас течёт память, причём серьёзно.
  • Pavia © (17.05.16 19:18) [1]
    Вешаешь Хук на EndThread
  • Crysis © (17.05.16 20:21) [2]
    Как?
    Сработает ли это на потоки, которые созданы вне Delphi?
  • DayGaykin © (17.05.16 21:43) [3]
    Создаешь свой поток-следилку и там делаешь:
    OpenThread+WaitForSingleObject.

    Чтобы следить сразу за несколькими потоками: WaitForMultipleObjects и алгоритм посложнее. Тут можно добавить еще Event, чтобы можно было прерывать WaitForMultipleObjects и обновлять список слежения.

    Чтобы следить за более чем MAXIMUM_WAIT_OBJECTS потоками, необходимо создать несколько потоков-следилок.
  • Crysis © (17.05.16 22:39) [4]
    > DayGaykin ©   (17.05.16 21:43) [3]

    Такой подход наверное тоже некорректен.
    Калбек по идее должен вызываться в завершающемся потоке. Потому что нужен доступ к TLS.
    В предложенном тобой варианте - я его не увижу.
  • NoUser © (18.05.16 00:03) [5]
    DLL_THREAD_DETACH


    >  который будет вызываться после завершения потока
    в том же потоке.
  • Crysis © (18.05.16 00:06) [6]
    > NoUser ©   (18.05.16 00:03) [5]

    Но у меня же не Dll
    Точнее не обязательно Dll
  • NoUser © (18.05.16 00:19) [7]
    PS.
    > Было решено поменять принципы обработки

    А если у потока после работы с функцией еще много планов на дальнейшую жизнь, то 'ваша' память будет ему в подарок?
  • NoUser © (18.05.16 00:23) [8]
    > Но у меня же не Dll
    ну, добавьте ещё и dll
  • DayGaykin © (18.05.16 00:27) [9]

    > Crysis ©   (17.05.16 22:39) [4]
    > > DayGaykin ©   (17.05.16 21:43) [3]
    >
    > Такой подход наверное тоже некорректен.
    > Калбек по идее должен вызываться в завершающемся потоке.
    >  Потому что нужен доступ к TLS.
    > В предложенном тобой варианте - я его не увижу.

    Тогда нормально никак.

    Можно, конечно извратиться так:
    Перехватываешь createthread своего процесса. Вместо точки входа указываешь свою функцию, которая: навесит SEH фрейм и перейдет по исходной точке входа. А дальше должно быть понятно, если знать что такое SEH фрейм.
  • Crysis © (18.05.16 00:28) [10]
    > А если у потока после работы с функцией еще много планов
    > на дальнейшую жизнь, то 'ваша' память будет ему в подарок?


    В том то и дело
    "В подарок" только на время работы. А по завершению потока - память надо грамотно забрать.
  • Crysis © (18.05.16 00:30) [11]
    > Перехватываешь createthread своего процесса. Вместо точки
    > входа указываешь свою функцию, которая: навесит SEH фрейм
    > и перейдет по исходной точке входа. А дальше должно быть
    > понятно, если знать что такое SEH фрейм.


    Тогда уж не CreateThread, а ExitThread
    Но я боюсь, Касперский заблокирует наш софт у клиентов. Или иные антивирусы будут ругаться.
  • DayGaykin © (18.05.16 00:32) [12]

    > Crysis ©   (18.05.16 00:30) [11]

    В ExitThread может и не настать.


    > Но я боюсь, Касперский заблокирует наш софт у клиентов.
    > Или иные антивирусы будут ругаться.

    В своем процессе ты можешь делать что хочешь.
  • Rouse_ © (18.05.16 12:00) [13]
    Тогда лучше ZwCreateThread перехватить и там свою потоковую процедуру с SEH.
  • Leonid Troyanovsky © (18.05.16 13:27) [14]

    > Crysis ©   (17.05.16 16:43)

    >  Причём бывает потоки создаются в сторонней Dll.Было решено
    > поменять принципы обработки данных, задействуя threadvar
    > и кеширование данных/буферов.Но беда в том, что threadvar-
    > области не финализируются при завершении потока.

    threadvar - здесь костыль.

    Если какому-либо потоку нужны приватные данные, то он может
    распределить память и передать твоей функции указатель на нее.
    Перед завершением работы он должен ее освободить.

    В случае если код потоков менять нельзя, то память для
    потоков может выделять твоя функция, при условии, что
    один (или более поток) приложения будет следить путем
    WaitForMultipleObjects за рабочими и по их завершении
    освобождать связанную с ними память.

    Про синхронизацию доступа к общим ресурсам промолчу,
    бо, с этим, наверное, все в порядке.

    --
    Regards, LVT.
  • Leonid Troyanovsky © (18.05.16 14:02) [15]

    > Leonid Troyanovsky ©   (18.05.16 13:27) [14]

    > будет следить путем WaitForMultipleObjects за рабочими и
    > по их завершении освобождать связанную с ними память.

    Схему можно упростить до следующей:

    Функция ведет список (ThreadList) троек: ThreadId,
    OTHandle(хендл OpenThread),
    Data(указ. на распред. память).

    Когда список (или память) переполнится, проходим по списку,
    проверяем состояние OTHandle (f.e., GetThreadExitCode),
    утилизируем память, CloseHandle, удаляем лишнее.

    --
    Regards, LVT.
  • Crysis © (18.05.16 14:56) [16]
    > Rouse_ ©   (18.05.16 12:00) [13]
    > Тогда лучше ZwCreateThread перехватить и там свою потоковую
    > процедуру с SEH.


    А можно поподробнее?
    Подойдёт ли этот способ для потоков, созданных вне Delphi?
  • NoUser © (18.05.16 17:14) [17]
    > А можно поподробнее?
    Лучше расскажи/покажи как это "созданные кем угодно в отдельном Dll" потоки дёргают функцию в твоём exe ?
  • Crysis © (18.05.16 17:33) [18]
    > NoUser ©   (18.05.16 17:14) [17]

    А что удивительного? Передаётся калбек, он вызывает в отдельных потоках.
  • NoUser © (18.05.16 17:50) [19]
    > Передаётся калбек
    И что, внутри калбека каждому "новенькому" потоку даруется безвозмездно кусок памяти? - хорошенький "принцип обработки данных" !
  • Crysis © (18.05.16 18:42) [20]
    > NoUser ©   (18.05.16 17:50) [19]

    Ну какое значение имеет, даём мы кому-то кусок памяти или нет...
    Есть особенность использования. Если завершения всех потоков не получится отследить - я сделаю через SystemThreadEndProc для своих потоков и более сложный и медленный вариант для чужих. Но хочется универсально.
  • DayGaykin © (18.05.16 19:59) [21]
    Facepalm
  • NoUser © (18.05.16 20:05) [22]
    > Но хочется универсально.
    Сделай второй калбек (для зачистки), сделай свою длл-ку в которую "передай" его и там вызывай в момент DLL_THREAD_DETACH.
  • Rouse_ © (18.05.16 22:54) [23]

    > Crysis ©   (18.05.16 14:56) [16]
    > А можно поподробнее?
    > Подойдёт ли этот способ для потоков, созданных вне Delphi?

    Что значит "вне Delphi"?
    Так ты перехватишь создание нити в твоем приложении (за исключением случая CreateRemoteThread из другого процесса)
  • Crysis © (19.05.16 12:01) [24]
    > Rouse_ ©   (18.05.16 22:54) [23]

    Допустим я использую сторонний движок, будь то звуковая библиотека, физика, базы данных. Они написаны на другом языке программирования, исходников нет; зато известно, что они вызывают CreateThread и внутри потока дёргают мой калбек. "Перехват ZwCreateThread" поможет в данной ситуации или нет? Можно поподробнее, как перехватить?
  • Rouse_ © (19.05.16 16:04) [25]
    Поможет если будешь делать перехват сплайсингом делать.
    http://alexander-bagel.blogspot.ru/2013/01/intercept.html
    http://alexander-bagel.blogspot.ru/2013/05/intercept2.html
  • Crysis © (19.05.16 17:41) [26]
    Спасибо, буду пробовать!
  • han_malign © (19.05.16 17:48) [27]

    > решено поменять принципы обработки данных, задействуя threadvar
    > и кеширование данных/буферов

    - кеширование внешнего контекста в реентерабельной функции - нонсенс...
    А промежутоные буфера надо выделять на стеке(в пределах отведенных(по умолчанию) ~ 1Мб естественно)...
  • DayGaykin © (19.05.16 20:32) [28]

    > Rouse_ ©   (19.05.16 16:04) [25]

    А не проще перехватить импорт DLL?
  • Rouse_ © (19.05.16 20:37) [29]

    > DayGaykin ©   (19.05.16 20:32) [28]
    > А не проще перехватить импорт DLL?

    Он только для статической линковки, используется.
  • DayGaykin © (19.05.16 20:39) [30]

    > Rouse_ ©   (19.05.16 20:37) [29]

    Со сплайсингом в многопоточности точно не без проблем обойдется, да еще если на 64 битах запускать.
  • Rouse_ © (19.05.16 21:10) [31]
    Если правильно делать с использование хотпатча (атомарной замены 2 байт в области нопов  в прологе функции) - никаких проблем не будет. Этож штатный механизм.
  • Rouse_ © (19.05.16 21:17) [32]
    В частности в 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)
  • DayGaykin © (20.05.16 03:33) [33]

    > Rouse_ ©   (19.05.16 21:10) [31]
    > Если правильно делать с использование хотпатча (атомарной
    > замены 2 байт в области нопов  в прологе функции) - никаких
    > проблем не будет. Этож штатный механизм.

    Если я правильно помню, условия хот-патча не выполняются если программа запущена в 64хбитной ос.
  • Leonid Troyanovsky © (20.05.16 08:40) [34]

    > 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.
  • Rouse_ © (20.05.16 09:13) [35]

    > DayGaykin ©   (20.05.16 03:33) [33]
    > Если я правильно помню, условия хот-патча не выполняются
    > если программа запущена в 64хбитной ос.

    Все выполняется иначе как бы перехваты в моем софте работали?
  • Rouse_ © (20.05.16 09:37) [36]
    А, или ты про 32 битный софт в 64 битной ОС? Да, там некоторые функции идут без пролога, но там тоже можно пошаманить
  • DayGaykin © (20.05.16 13:32) [37]

    > Rouse_ ©   (20.05.16 09:37) [36]
    > А, или ты про 32 битный софт в 64 битной ОС? Да, там некоторые
    > функции идут без пролога, но там тоже можно пошаманить

    Да-да. Ты мне про это, кстати, и говорил.
  • Cobalt © (25.05.16 11:53) [38]
    Почему бы не поменять способ работы на что-то типа
    BeginUpdate;
    EndUpdate;


    ?
 
Конференция "Прочее" » Отловить завершение чужих потоков
Есть новые Нет новых   [134433   +22][b:0][p:0.003]