sl0n Самотрассировка - это п.6. Каким либо способом поток устанавливает TF, и следит за исключением. Кривые отладчики не пропускают его, тоесть если должно развернутбся исключение отладчик фильтрует его. Тут не подходит, отслеживаются инструкции которые устанавливают TF, следующая инструкция вызывет исключение которое будет развёрнуто в потоке.
Снова пост потёрли, ну лан. nester7 доделаем, клинопись не знаю.) В общем прикинул так. Обнаружение отладчика с помощью перекрывающегося кода. Если код изменятся в текущем потоке, это ненадёжно и легко обходится, к примеру поток вызывает сервис NtWriteVirtualMemory посредством Int2E, c адресом @+2, тоесть изменяет инструкцию следующую за Int, то это не проблема обойти - установка TF перед iret в KiServiceExit и инструкция следующая за Int после исполнения вызывет отладочное исключение. Другое дело если код выполняющийся в одном потоке изменяется кодом выполняющимся в другом потоке. Тут два варианта, а именно многопроцессорная или нет система. Если система однопроцессорная, то паралельное исполнение кода в двух потоках невозможно. Но вот на многопроцессорной системе с этим возникают проблемы. Если атомарно исполняется модификация кода - это синхронизация, тут ситуация аналогично как на однопроцессорной системе. Но сервисы пишущие в память выполняют запись не атомарно. Как вариант - изменение аффинитета процесса, допуская для всех потоков исполнение на одном процессоре. Этим антиотладка такого типа будет сломана. В таком случае следует следить за некоторыми сервисами, к примеру NtQueryInformationProcess(ProcessAffinityMask). Видимо это наилучший способ антиотладки, единственный минус - работает только на многопроцессорных системах. В совокупности с цепочкой нетрассируемого кода(префиксы/IntXX/pop ss/[код мод. другим потоком] весьма эффективен.
Как я мог забыть про мои любимые отладочные регистры. 14-й бит регистра DR6 взводится при возникновении исключения пошаговой отладки. Отладочные регистры недоступны из юзермода, но при возникновении исключения #DB в стеке фрейм(CONTEXT) содержит этот регистр с установленным 14-м битом.
[BUG] Вначале я подумал что это тупит оля, но оказалось это очередной баг в ISR. Предыдущий баг был описан(при исполнении двухбайтной инструкции Int 3 регистр Eip в контексте(CONTEXT) фрейма(EXCEPTION_RECORD) обработчика исключений (KiUserExceptionDispatcher) указывает на второй байт инструкции). Юзермодный калбак KiUserExceptionDispathcer - с этой точки в юзермоде поток начинает развёртку исключений. Управление на эту точку возвращает ядро вызывая KiDispatchException(). Нормально развёртка исключения в юзермоде невозможна с взведённым TF. Баг заключается в следующем. При исполнении инструкции, которая вызывает исключение общей защиты с установленым TF должно развернуться исключение #GP(KiTrap0D), а исключение #DB не должно быть вызвано. При возникновении исключения #GP, в стеке сохраняется фрейм, который содержит регистр флагов, а в нём и будет взведён TF. Затем по возврату в юзермод(iretd восстановит EFLAGS, макрос EXIT_ALL) для развёртывания исключения поток начнёт исполнять код KiUserExceptionDispatcher с взведённым TF, что вызывет исключение пошаговой отладки(обработчик исключений вызывет сам себя). Нормально обработка должна произойти только исключения #GP. На самом деле первый раз будет выполнена обработка исключения #DB с адресом KiUserExceptionDispatcher, а исключение #GP не будет обработано. Код (Text): push 100000000b ;Trap Flag popfd cli Заключённый в сех-фрейм этот код вызывет исключение, сех получит управление один раз при возникновении исключения пошаговой отладки после исполнения инструкции по адресу KiUserExceptionDispatcher. XPSP2, на остальных не проверял, но вроде тоже самое.
Код который фиксит этот баг(немного проблемно скомпилить): Код (Text): ExceptionHandler proc C mov edx,dword ptr [esp + 4] assume edx:PEXCEPTION_RECORD cmp [edx].ExceptionCode,STATUS_SINGLE_STEP jne return_ mov eax,dword ptr [KiUserExceptionDispatcher + 2] mov eax,dword ptr [eax] cmp [edx].ExceptionAddress,eax jb return_ add eax,15 ;Maximum instruction size cmp [edx].ExceptionAddress,eax ja return_ mov edx,dword ptr [esp + 3*4] assume edx:PCONTEXT and [edx].regEFlags,Not EFLAGS_TF ;On any event and [edx].regDr6,Not HB_EVENT_TRAP ;On any event push FALSE push edx Call ZwContinue return_: ; [...] Normal SEH handler
Тут пришёл к выводу что отношение времени исполнения Int2A к времени исполнения тестового кода(если он не трассируется) является константой для данного процессора и не зависит от его частоты и произ. системы. Если код трассируется, то эта константа уменьшается. Обойти это из юзермода - никак, из кернелмода - ставить задержку в ISR. Плохо представляю как это будет работать на других процессорах, но могу предположить что константа не сильно изменится, так как число тактов для инструкций не сильно изменили а код одинаков. Код (Text): SharedUserData equ 7FFE0000h ;Определяет число прерываний 0x2A вызванных в течении 20 миллисекунд. QueryInterruptPerformance proc uses ebx edi mov edi,SharedUserData xor ebx,ebx ;Счётчик числа прерываний. assume edi:PKUSER_SHARED_DATA mov ecx,[edi].TickCountLow ;Ожидание следующего тика чтобы начать счёт с начала тика. wait_next_tick_: cmp ecx,[edi].TickCountLow je wait_next_tick_ add ecx,DURATION interrupt_: Int 2Ah inc ebx cmp [edi].TickCountLow,ecx jb interrupt_ mov eax,ebx ret QueryInterruptPerformance endp QueryStrayPerformance proc uses ebx edi mov edi,SharedUserData xor ebx,ebx ;Счётчик числа прерываний. assume edi:PKUSER_SHARED_DATA mov ecx,[edi].TickCountLow wait_next_tick_: cmp ecx,[edi].TickCountLow je wait_next_tick_ add ecx,DURATION interrupt_: inc ebx cmp [edi].TickCountLow,ecx jb interrupt_ mov eax,ebx ret QueryStrayPerformance endp comment ' N = QueryInterruptPerformance() M = QueryStrayPerformance() dT(Int 2Ah) / dT(StrayTime) = Const dT(Int 2Ah) / dT(TestRoutine) = Const Ti = dT(Int 2Ah) Ts = dT(inc ebx + cmp [edi].TickCountLow,ecx + jb interrupt_) Ti / Ts = Const, Ti >> Ts, M >> N (Ti + Ts) * N = DURATION -> Ti = DURATION / N - Ts Ts * M = DURATION -> Ts = DURATION / M (Ti / Ts) = (DURATION / N - Ts) / (DURATION / M) = M / N - 1' ;Разделяемую память не следует юзать, её адрес в версиях меняется, к томуже 0x2A прерывание возвращает число тиков в Edx:Eax. Нужно поставить Int 2Ah вместо обращений к TickCountLow и пересчитать отношение (Ti / Ts). ;Сдесь используется чтобы не усложнять. QueryKernelUserPerformanceRelation proc uses ebx Call QueryInterruptPerformance mov ebx,eax Call QueryStrayPerformance inc ebx ;На случай защита от исключения при делении на ноль. xor edx,edx div ebx dec eax ;(Ti / Ts) = Const ret QueryKernelUserPerformanceRelation endp comment ' На P4(2 x 3014MHz) - результат колеблется в небольших пределах возле ~340, аффинитет не влияет на него, приоритет потока аномально смещает немного результат, причём занижая отношение независимо от того повышается или понижается приоритет, хз почему. Интересно на варе потестить.' хм.
Clerk Код (Text): ... mov ecx,[edi].TickCountLow ;; допустим в ECX попало что-то ;; больше чем 0xFFFF'FFFF минус DURATION, ;; но меньне чем 0xFFFF'FFFF ... add ecx,DURATION ;; тут перешагнули через ноль ... cmp [edi].TickCountLow,ecx ;; допустим [edi].TickCountLow еще ;; не перешагнул через ноль jb interrupt_ ;; куда попадем? ...
Clerk Ага, то говоришь, что замеры времени это ненадежно и бесполезно, то сам начинаешь изобретать "хлипкие" велосипеды
q_q Само собой разумеется что проверки перехода через ноль нужны, как и значения в регистре Edx возвр. Int 2A. Сдесь это не важно, переход через ноль произойдёт примерно один раз в 50 суток. И какова вероятность что код войдёт в этот цикл ожидания ? leo Трассировщик должен быть мощным, нужно знать с чем бороться. Хлипкие - это не тут, а в гугле. В реальном софте подобное не применяется, все юзают FindWindow(), тск и тому подобный гуан.
q_q offtop. Ты пишешь код. Скажи, хотябы раз ты выполнял проверку выравнивания стека на границу в 4-е байта ?, а вероятность ошибки огромная - запру я в стек 1 байт - и весь код падёт на первом - втором сервисе. Это искать баги в примерах описывающих технику ыы.
leo Апстену. Код (Text): DURATION equ 20 ;Milliseconds QueryInterruptPerformance proc uses ebx esi xor ebx,ebx mov esi,DURATION Int 2Ah mov ecx,eax wait_: Int 2Ah cmp eax,ecx je wait_ tick_: mov ecx,eax calc_: ; Int 2Ah Int 2Ah Int 2Ah inc ebx cmp eax,ecx je calc_ dec esi jnz tick_ mov eax,ebx ret QueryInterruptPerformance endp QueryStrayPerformance proc uses ebx esi xor ebx,ebx mov esi,DURATION Int 2Ah mov ecx,eax wait_: Int 2Ah cmp eax,ecx je wait_ tick_: mov ecx,eax calc_: Int 2Ah inc ebx cmp eax,ecx je calc_ dec esi jnz tick_ mov eax,ebx ret QueryStrayPerformance endp N = QueryInterruptPerformance() M = QueryStrayPerformance() Kcpu = M / N - 1 Раскомент Int2A -> Kcpu + 1. XPSP2/P4 - 2x3014: погрешность 5%. Кто способен привести ещё более эффективный метод для определения временных задержек ?
[offtop] Clerk искать баги в примерах описывающих технику ыы Прошу прощения, у меня сложилось впечатление, что ты запостил готовое решение. [/offtop]
Clerk Раз ты вычисляешь Kcpu*1000, то в QueryKernelUserPerformanceRelation нужно заменить dec eax на sub eax,1000
Clerk Не кипятись Проверил на P4 Northwood 3.2Ггц, P4 Prescott 3.0Ггц, PentiumD 3.0Ггц, Athlon 64 3200+ (2Ггц) На всех XP SP2. При однократных запусках, как и ожидалось, все путем - на всех процах без трассировки (за вычетом 1000) получается ~965-1000 (на P4 c HT иногда и за 1000 зашкаливает), а с трассировкой ~13-18 без HT и до 50 с вкл. HT Но ес-но можно смоделировать и сбойные ситуации - например, запускаем сразу несколько копий, затем аккуратно выстраиваем окошки в ряд и начинаем подряд нажимать OK - в итоге при трассировочном проходе могут получаться цифры > 9000. Поэтому ес-но нужен доп.контроль сбоев
Просто вывод кривой, надо было обнулить буфер перед вторым выводом строки. Отнимать от Kcpu 10^3 не следует, иногда отрицательные значения появляются. При аффинитете 11B второе значение приближается к первому иногда, но при аффинитете 1 - всегда на половину меньше(для многих одновр. зап. копий).