TerminateThread - как не завершить себя?

Тема в разделе "WASM.WIN32", создана пользователем S_T_A_S_, 13 янв 2005.

  1. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    CreateThread создаёт несколько трэдов, хэндлы сохраняются в сухом и прохладном месте.

    Далее, в какой-то момент, любой из трэдов вызавает код (реально это UnhandledExceptionFilter), который читает хэндлы из сухого и прохладного места, и для каждого вызывает TerminateThread.

    Тут-то и случается страшное :)



    Манипуляции с DuplicateHandle как у Рихтера не подходят - возвращается другой хэндл.

    Как я успел понять (официальных) путей получения именно того хэндла, который возвращает CreateThread нет ?



    В Windows Server 2003 появилась GetThreadId, можно сравнивать возвращаемое значение с GetCurrentThreadId. Но хочется, что бы код работал так же и в 95-м мастдае :-(



    Манипулировать с tls или pvArbitrary ( dword FS:[14h]) ?



    Пока что придумал - GetThreadContext и сравнивать CONTEXT.Esp на предмет попадания в диапазон FS:[4h] и FS:[8h]


    Код (Text):
    1.  
    2. __declspec(naked) int __fastcall
    3. safe_terminate_thread(void * thread)
    4. {
    5.     static  CONTEXT     context;
    6.     __asm
    7.     {   // ecx = handle to thread
    8.         mov     context.ContextFlags, CONTEXT_CONTROL
    9.         push    0                   // для TerminateThread
    10.         push    ecx                 // для TerminateThread
    11.         push    offset context
    12.         push    ecx
    13.         call    dword ptr GetThreadContext
    14.         xor     edx, edx
    15.         mov     eax, context.Esp
    16.         cmp     dword ptr fs:[edx+4], eax    // Top of user stack
    17.         setb    cl
    18.         cmp     dword ptr fs:[edx+8], eax    // Base of user stack
    19.         setae   ch
    20.         test    cx, cx
    21.         jz      current_thread
    22.         call    dword ptr TerminateThread
    23.         ret
    24. current_thread:
    25.         pop     ecx                 // убираем параметры TerminateThread
    26.         pop     ecx
    27.         ret
    28.     }
    29. }


    Есть ли какие-нибудь другие варианты?
     
  2. vinnie_pooh

    vinnie_pooh New Member

    Публикаций:
    0
    Регистрация:
    30 июн 2004
    Сообщения:
    98
    Можно запоминать ThreadID при создании потока:
    Код (Text):
    1. #include <windows.h>
    2.  
    3. struct ThreadInfo
    4. {
    5.     DWORD   ThreadID;
    6.     HANDLE  ThreadHandle;
    7. };
    8.  
    9. #define NUM_OF_THREADS  8
    10.  
    11. ThreadInfo      ti[NUM_OF_THREADS];
    12. ULONG __stdcall ThreadProc(LPVOID);
    13. void            TerminateAll();
    14. HANDLE          hEvent;
    15.  
    16. int __stdcall WinMain(HINSTANCE, HINSTANCE, char*, int)
    17. {
    18.     hEvent = CreateEvent(0, TRUE, FALSE, 0);
    19.     for(int i = 0; i < NUM_OF_THREADS; i++)
    20.         ti[i].ThreadHandle = CreateThread(0, 0, ThreadProc, 0, 0, &ti[i].ThreadID);
    21.     WaitForSingleObject(hEvent, -1);
    22.     return 0;
    23. }
    24.  
    25. ULONG __stdcall ThreadProc(LPVOID)
    26. {
    27.     Sleep((GetTickCount() & 7) * 1000);
    28.     TerminateAll();
    29.     SetEvent(hEvent);
    30.     return 0;
    31. }
    32.  
    33. void TerminateAll()
    34. {
    35.     DWORD currentID = GetCurrentThreadId();
    36.     for(int i = 0; i < NUM_OF_THREADS; i++)
    37.         if(currentID != ti[i].ThreadID)
    38.             TerminateThread(ti[i].ThreadHandle, 0);
    39. }
    40.  
     
  3. linker

    linker New Member

    Публикаций:
    0
    Регистрация:
    10 янв 2005
    Сообщения:
    2
    Адрес:
    none
    Не понял. Ты хочеш чтоб один поток не мог убить другой чтоли?.

    Вообще то DuplicateHandle используется для совместного использования объектов ядра несколькими процессами.

    Как это возвращается другой описатель :)
     
  4. Black_mirror

    Black_mirror Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2002
    Сообщения:
    1.035
    S_T_A_S_

    Можно при создании потока передать ему указатель на ячейку где будет сохранён его хэндл, или сразу же после создания отправить созданному потоку сообщение содержащее этот хендл, а он его уже извлечёт их своей очереди.
     
  5. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    AFAIK, handle полученный в одном потоке, валиден в любом другом того же процесса.

    за исключением pseudo handle конечно.
     
  6. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    vinnie_pooh >




    Это слишком простое решение, воины дзена не ищут лёгких путей :)



    Я немного упростил ситуацию, на самом деле трэды завершаются не в одном месте (цикле):

    При нормальном ходе работы первичный трэд вызывает всякие "деструкторы", а в тех код освобождения ресурсов и т.п. + TerminateTread.

    Появление фильтра исключений "немного" портит эту картину.

    Поэтому при твоём подходе нужно будет переделывать все эти места в проге, код создания трэдов + добавлять переменные.

    А я хочу "глобальное решение" вроде такой строки в начале программы:



    #define TerminateTread(X,Y) safe_terminate_thread(X)



    В общем-то я его нашел, просто оно немного через э-э-э.. ну, короче, контекст =)





    linker >




    См. сабж. Мне нужно что бы произвольный трэд закрывая все остальные "случайно" не завершил свою работу - как узнать, что некоторый хэндл принадлежит конкретному потоку?



    >




    Классический пример - у Рихтера - "дублируется" псевдохендл возвращаемый GetCurrentThread - получается хэндл, которым может оперировать другой поток. Но этот хэндл (численно) не равен тому, что возвращает

    CreateThread.





    green



    Но как я узнаю, что какойто хэндл - именно текущего потока?

    В филтр может попасть произвольный поток!
     
  7. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    Похоже, я плохо объяснил:



    трэд А создаёт трэды B и C.

    программа работает

    трэд А завершает трэды B и C.

    -------------------------------------

    Но может возникнуть exeption!

    тогда либо трэд A, либо B, либо C

    попадыют в UnhandledExceptionFilter

    и либо трэд A, либо B, либо C пытаются завершить трэды B и C

    :)

    Какова вероятность, трэды B и C дойдут до этого места?!
     
  8. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    ок, я просто не понял проблемы...



    ну может тогда "в сухом и прохладном месте" сохранять не хэндлы а id потоков ?

    правда в фильтре придется делать OpenThread но такой фильтр ведь должен срабатывать редко ?
     
  9. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    можно конечно, vinnie_pooh так и предлагал - но IMHO это в решение более сложное, чем то, которое уже есть. Проще же залезть в TIB, чем переделывать прогу :)
     
  10. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    да, наверно...



    только напрямую лезть в TIB, мне кажется, не обязательно тем более что он недокументирован.



    можно определить что хэндл является хэндлом текущего треда, сравнивая fs из его контекста с текущим значением fs.
     
  11. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    FS не будет работать. вот что пишет Питрек:





    Фактически, я и делаю подобное сравнение.. каждый трэд имеет свой стэк, следовательно esp можно рассматривать как его "уникальный" идентификатор.. теоретически, эта схама не полностью надёжна, но работает на практике.



    Просто подумал, может есть ещё какие-то "зацепки"
     
  12. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    опс, не знал...



    хотя оно и логично: кол. потоков может перевалить за макс. кол. дескрипторов сегментов.



    а линк на эту статью Питрека можешь дать ?
     
  13. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
  14. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    спасибо!
     
  15. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    Что-то я в замешательстве:

    если esp выходит за пределы FS:[8] ... FS:[4] то safe_terminate_thread благополучно завершит трэд..

    С другой стороны, в таком случае фильтр исключений всё равно не будет вызван.

    Т.е. если делать:



    xor esp, esp

    push esp



    система молча прибивает процесс.



    Получается, что ошибка в методе есть, но сама система не даёт ей проявляться! :)



    Хотя правильнее видимо делать GetThreadSelectorEntry для хэндла трэда и для GetCurrentThread, и потом сравнивать LDT_ENTRY структуры..
     
  16. je_

    je_ New Member

    Публикаций:
    0
    Регистрация:
    27 янв 2004
    Сообщения:
    143
    в FS:[18h] сидит FS-linear и в w9x и в NT..

    сравнивай на щастье!
     
  17. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    т.е переводить в линейный адрес через GetThreadSelectorEntry..

    ну это можно, но телодвижений больше, чем уже есть.



    На всякий случай, значения fs из контекстов для разных систем:
    Код (Text):
    1.  
    2. #include <windows.h>
    3. #include <stdio.h>
    4.  
    5. unsigned long __stdcall thread(void * foo)
    6. {
    7.     Sleep(-1);
    8.     return 0;
    9. }
    10.  
    11.  
    12. void main()
    13. {
    14.     CONTEXT     context;
    15.  
    16.     for( int i = 5; i; --i )
    17.     {
    18.         unsigned long id;
    19.         void   * handle = CreateThread(0, 0, thread, 0, 0, &id);
    20.         Sleep(2);   // для мастдая
    21.         context.ContextFlags = CONTEXT_SEGMENTS | CONTEXT_CONTROL;
    22.         GetThreadContext(handle, &context);
    23.  
    24.         printf("Thread #%08X : CONTEXT.SegFs = %08X\tCONTEXT.Esp = %08X\n",
    25.                 handle, context.SegFs, context.Esp);
    26.     }
    27. }
    28.  




    А вот результаты работы:
    Код (Text):
    1.  
    2. 98se:
    3.  
    4. Thread #00000014 : CONTEXT.SegFs = 0000252F CONTEXT.Esp = 0096FF90
    5. Thread #00000018 : CONTEXT.SegFs = 0000377F CONTEXT.Esp = 00A8FF90
    6. Thread #0000001C : CONTEXT.SegFs = 000037CF CONTEXT.Esp = 00BAFF90
    7. Thread #00000020 : CONTEXT.SegFs = 00003767 CONTEXT.Esp = 00CCFF90
    8. Thread #00000024 : CONTEXT.SegFs = 000037D7 CONTEXT.Esp = 00DEFF90
    9.  
    10. XP:
    11.  
    12. Thread #000007E8 : CONTEXT.SegFs = 0000003B CONTEXT.Esp = 0050FF40
    13. Thread #000007F4 : CONTEXT.SegFs = 0000003B CONTEXT.Esp = 0060FF40
    14. Thread #000007DC : CONTEXT.SegFs = 0000003B CONTEXT.Esp = 0070FF40
    15. Thread #000007D8 : CONTEXT.SegFs = 0000003B CONTEXT.Esp = 0080FF40
    16. Thread #000007D4 : CONTEXT.SegFs = 0000003B CONTEXT.Esp = 0090FF40
    17.  


    esp можно сразу использовать