Приветствую. В мануалах IA по инструкции pop SS сказано что она задерживает прерывания до завершения исполнения следующей инструкции: Это позволяет обнаружить эмуляцию/трансляцию кода статистически и однозначно. Есть ли еще инструкции, которые каким либо образом связаны в их последовательности, подобно popss ?
MaKsIm В чем суть мува не пойму, два потока чтение и запись переменной? Такую связку можно перемещать в памяти произвольно, а значит изменять. Обнаружить изменение переменной не атомарное?
В данном примере никакой. Суть в префиксе lock, а вот команда к префиксу просто неудачная. Вы же спрашивали про действия связки команд. Вот вам два примера связывания команд. Любые условные переходы и lock. Еще можно вспомнить про rep+команда.
Отладчик/транслятор/эмулятор перемещают инструкции или увеличивают их количество(в случае эмуляции). Таким образом если подобрать последовательность инструкций в которой они связаны так, что их перемещение изменяет поведение кода, тогда получится обнаружить отладку. Ветвления используют только контекст и не привязаны к предыдущим инструкциям.
А прикол с pop ss/mov sp(esp),value работает только в реальном или однозадачном режимах. Во всех остальных стеков у каждой задачи 4-е и при возникновении отладочного исключения процессор не трогает стек текущего (отличного от 0-го) уровня привилегий и перепрыгивает по вектору прерывания (при необходимости может выполнить даже переключение аппаратной таски (TSS)) И как вы собрались искать отладчики с этой последовательностью? В DOS это еще реально. А в x86_64 у вас даже ss нету. ADD: Вот что об этом говорит Intel SDM
MaKsIm В защищенном режиме x32, юзермод так же работает. На счет стека в данном случае не важно, вторая инструкция другой может быть. Это сделано для атомарной загрузки ss:esp, если прерывание обрабатывается на том же уровне привилегий(тогда нет переключения стека). IA SDM v2: 8E /r MOV Sreg,r/m16** 64 Valid CM/LM Valid Move r/m16 to segment register. Статистикой по прерываниям. Интересно какие еще есть подобные связки инструкций.
И вот тут то как раз нестыковка. Обработка отладочных исключений происходит в разных задачах (процессах) т.е. каждое такое исключение принудительно транслируется в другой процесс. С другой стороны отладчик может просто заменять инструкцию на int3 и потом просто эмитировать выполнение первой инструкции. Вам проще запустить свой же процесс под отладкой другой вашей программы (или той же, но с другими параметрами) и контролировать состояние этой отладки.
MaKsIm Прерывание это не исключение, про последние речи небыло. Аналогично прерываниям можно подавить отладочное исключение, через cpu.flags.RF, так например происходит сервисный возврат. Проще да, вот только все процессы и отладчик до кучи запустятся под бинарным транслятором/эмулятором, детектить такое не получится через всякие int3 Тут нужен принципиально иной подход.
Основные инструкции со связанным поведением: 1. STI (Set Interrupt Flag) Код (ASM): sti nop ; следующая инструкция выполняется без прерываний Задерживает прерывания на одну инструкцию после выполнения Позволяет атомарно включить прерывания и выполнить критическую операцию 2. MOV SS (Move to Stack Segment) Код (ASM): mov ss, ax mov esp, new_stack ; выполняется без прерываний Аналогично Код (ASM): POP SS , блокирует прерывания до следующей инструкции Обеспечивает атомарную замену SS:ESP 3. REP префикс Код (ASM): rep movsb ; префикс связан с командой Создает неделимую связь между префиксом и инструкцией Нельзя разделить префикс и команду 4. LOCK префикс Код (ASM): lock add [mem], eax ; атомарная операция Обеспечивает атомарность доступа к памяти Префикс неразрывно связан с инструкцией 5. Условные переходы с предсказанием Код (ASM): cmp eax, 0 je target ; связаны через предсказание переходов Создают связь через механизм предсказания ветвлений Изменение порядка может повлиять на производительность 6. Последовательности с флагами Код (ASM): pushf ; сохранить флаги cli ; запретить прерывания ; критическая секция popf ; восстановить флаги и состояние IF 7. Инструкции с задержкой исключений Код (ASM): mov ss, ax int 3 ; может использоваться для CVE-2018-8897 Код (C++): #include <iostream> #include <chrono> #include <vector> #include <cstring> #include <functional> #include <string> #include <intrin.h> #include <cmath> class EmulationDetector { private: static constexpr size_t SMALL_BUFFER = 1024; static constexpr size_t MEDIUM_BUFFER = 32768; static constexpr size_t LARGE_BUFFER = 1048576; static constexpr int ITERATIONS = 1000; struct TimingProfile { double rep_movsb_time; double memcpy_time; double ratio; uint64_t rdtsc_overhead; }; uint64_t read_performance_counter(int counter_id) { return __rdtsc(); } uint64_t precise_timing(std::function<void()> func) { uint64_t start, end; int cpuInfo[4]; __cpuid(cpuInfo, 0); start = __rdtsc(); func(); __cpuid(cpuInfo, 0); end = __rdtsc(); return end - start; } void rep_movsb_copy(void* dst, const void* src, size_t size) { char* d = static_cast<char*>(dst); const char* s = static_cast<const char*>(src); size_t sz = size; __asm { push esi push edi push ecx mov edi, d mov esi, s mov ecx, sz rep movsb pop ecx pop edi pop esi } } void rep_movsb_copy_intrinsic(void* dst, const void* src, size_t size) { char* d = static_cast<char*>(dst); const char* s = static_cast<const char*>(src); for (size_t i = 0; i < size; ++i) { d[i] = s[i]; } } void enhanced_rep_movsb_copy(void* dst, const void* src, size_t size) { try { rep_movsb_copy(dst, src, size); } catch (...) { rep_movsb_copy_intrinsic(dst, src, size); } } TimingProfile profile_rep_movsb(size_t buffer_size) { std::vector<char> src(buffer_size, 0xAA); std::vector<char> dst(buffer_size, 0x00); TimingProfile profile = {}; uint64_t rep_movsb_total = 0; for (int i = 0; i < ITERATIONS; ++i) { rep_movsb_total += precise_timing([&]() { enhanced_rep_movsb_copy(dst.data(), src.data(), buffer_size); }); } profile.rep_movsb_time = static_cast<double>(rep_movsb_total) / ITERATIONS; uint64_t memcpy_total = 0; for (int i = 0; i < ITERATIONS; ++i) { memcpy_total += precise_timing([&]() { memcpy(dst.data(), src.data(), buffer_size); }); } profile.memcpy_time = static_cast<double>(memcpy_total) / ITERATIONS; profile.ratio = profile.rep_movsb_time / profile.memcpy_time; uint64_t rdtsc_overhead = 0; for (int i = 0; i < 100; ++i) { rdtsc_overhead += precise_timing([]() { // Empty function to measure timing overhead }); } profile.rdtsc_overhead = rdtsc_overhead / 100; return profile; } bool test_microarchitectural_behavior() { int cpuInfo[4]; __cpuidex(cpuInfo, 7, 0); bool ermsb_supported = (cpuInfo[1] & (1 << 9)) != 0; if (!ermsb_supported) { return true; // Assume hardware if ERMSB not supported } std::vector<char> buffer(4096 + 64); char* aligned_ptr = reinterpret_cast<char*>( (reinterpret_cast<uintptr_t>(buffer.data()) + 63) & ~63 ); char* unaligned_ptr = aligned_ptr + 1; uint64_t aligned_time = precise_timing([&]() { enhanced_rep_movsb_copy(aligned_ptr + 2048, aligned_ptr, 2048); }); uint64_t unaligned_time = precise_timing([&]() { enhanced_rep_movsb_copy(unaligned_ptr + 2048, unaligned_ptr, 2048); }); if (aligned_time == 0) return true; // Avoid division by zero double alignment_ratio = static_cast<double>(unaligned_time) / aligned_time; // Real hardware typically shows 1.2-2.0x difference // VMs often show less sensitivity (closer to 1.0) return alignment_ratio > 1.15 && alignment_ratio < 3.0; } bool detect_via_performance_counters() { uint64_t start_cycles = __rdtsc(); uint64_t start_instructions = read_performance_counter(0); volatile int dummy = 0; for (int i = 0; i < 10000; ++i) { dummy += i; } uint64_t end_cycles = __rdtsc(); uint64_t end_instructions = read_performance_counter(0); if (end_cycles <= start_cycles) return false; double ipc = static_cast<double>(end_instructions - start_instructions) / (end_cycles - start_cycles); return ipc > 0.1 && ipc < 4.0; } bool detect_via_timing_patterns() { std::vector<uint64_t> timings; for (int i = 0; i < 100; ++i) { uint64_t timing = precise_timing([&]() { volatile int temp = 0; for (int j = 0; j < 1000; ++j) { temp += j; } }); timings.push_back(timing); } double mean = 0.0; for (uint64_t t : timings) { mean += t; } mean /= timings.size(); if (mean == 0) return false; double variance = 0.0; for (uint64_t t : timings) { variance += (t - mean) * (t - mean); } variance /= timings.size(); double coefficient_of_variation = sqrt(variance) / mean; // Real hardware: CV usually 0.1-0.5 // VMs: CV often < 0.1 (too consistent) or > 0.6 (too variable) return coefficient_of_variation > 0.05 && coefficient_of_variation < 0.6; } public: struct DetectionResult { bool is_emulated; double confidence; std::string detection_method; std::vector<std::string> indicators; }; DetectionResult detect_emulation() { DetectionResult result = {}; result.is_emulated = false; result.confidence = 0.0; std::vector<std::string> indicators; // Get timing profiles auto small_profile = profile_rep_movsb(SMALL_BUFFER); auto medium_profile = profile_rep_movsb(MEDIUM_BUFFER); auto large_profile = profile_rep_movsb(LARGE_BUFFER); // **MAIN DETECTION VECTOR: RDTSC Overhead** // This is the strongest indicator based on your data double avg_rdtsc_overhead = (small_profile.rdtsc_overhead + medium_profile.rdtsc_overhead + large_profile.rdtsc_overhead) / 3.0; if (avg_rdtsc_overhead > 5000) { indicators.push_back("Very high RDTSC overhead (virtualization detected)"); result.confidence += 0.8; // Very strong indicator } else if (avg_rdtsc_overhead > 1000) { indicators.push_back("High RDTSC overhead (possible emulation)"); result.confidence += 0.4; } // **TIMING RATIO ANALYSIS** // Hardware typically shows ratios around 0.95-1.05 double avg_ratio = (small_profile.ratio + medium_profile.ratio + large_profile.ratio) / 3.0; if (avg_ratio > 1.5 || avg_ratio < 0.3) { indicators.push_back("Abnormal REP MOVSB/memcpy performance ratio"); result.confidence += 0.3; } // **CACHE SCALING ANALYSIS** if (small_profile.rep_movsb_time > 0) { double cache_scaling = large_profile.rep_movsb_time / small_profile.rep_movsb_time; // Real hardware: ~500-1000x scaling from L1 to RAM // VMs: often different scaling patterns if (cache_scaling < 100 || cache_scaling > 2000) { indicators.push_back("Abnormal cache hierarchy scaling"); result.confidence += 0.2; } } // **ABSOLUTE TIMING ANALYSIS** // VMs often show much higher absolute timings if (small_profile.rep_movsb_time > 10000) { indicators.push_back("Extremely high absolute timing values"); result.confidence += 0.3; } // **MICROARCHITECTURAL BEHAVIOR** if (!test_microarchitectural_behavior()) { indicators.push_back("Abnormal alignment sensitivity"); result.confidence += 0.15; } // **TIMING CONSISTENCY** if (!detect_via_timing_patterns()) { indicators.push_back("Abnormal timing variance patterns"); result.confidence += 0.1; } // **FINAL DETERMINATION** result.is_emulated = result.confidence > 0.6; result.indicators = indicators; if (result.is_emulated) { result.detection_method = "Multi-vector timing analysis (Primary: RDTSC overhead)"; } else { result.detection_method = "Hardware execution detected"; } return result; } void print_detailed_analysis() { std::cout << "=== Enhanced REP MOVSB Emulation Detection Analysis ===" << std::endl; auto result = detect_emulation(); std::cout << "Detection Result: " << (result.is_emulated ? "EMULATED/VIRTUALIZED" : "HARDWARE") << std::endl; std::cout << "Confidence: " << (result.confidence * 100) << "%" << std::endl; std::cout << "Method: " << result.detection_method << std::endl; if (!result.indicators.empty()) { std::cout << "\nDetection Indicators:" << std::endl; for (const auto& indicator : result.indicators) { std::cout << " - " << indicator << std::endl; } } // Print detailed timing profiles std::cout << "\n=== Detailed Timing Analysis ===" << std::endl; auto small_profile = profile_rep_movsb(SMALL_BUFFER); auto medium_profile = profile_rep_movsb(MEDIUM_BUFFER); auto large_profile = profile_rep_movsb(LARGE_BUFFER); std::cout << "Small Buffer (1KB):" << std::endl; std::cout << " REP MOVSB: " << small_profile.rep_movsb_time << " cycles" << std::endl; std::cout << " memcpy: " << small_profile.memcpy_time << " cycles" << std::endl; std::cout << " Ratio: " << small_profile.ratio << std::endl; std::cout << " RDTSC overhead: " << small_profile.rdtsc_overhead << " cycles" << std::endl; std::cout << "Medium Buffer (32KB):" << std::endl; std::cout << " REP MOVSB: " << medium_profile.rep_movsb_time << " cycles" << std::endl; std::cout << " memcpy: " << medium_profile.memcpy_time << " cycles" << std::endl; std::cout << " Ratio: " << medium_profile.ratio << std::endl; std::cout << " RDTSC overhead: " << medium_profile.rdtsc_overhead << " cycles" << std::endl; std::cout << "Large Buffer (1MB):" << std::endl; std::cout << " REP MOVSB: " << large_profile.rep_movsb_time << " cycles" << std::endl; std::cout << " memcpy: " << large_profile.memcpy_time << " cycles" << std::endl; std::cout << " Ratio: " << large_profile.ratio << std::endl; std::cout << " RDTSC overhead: " << large_profile.rdtsc_overhead << " cycles" << std::endl; // Additional analysis double avg_rdtsc_overhead = (small_profile.rdtsc_overhead + medium_profile.rdtsc_overhead + large_profile.rdtsc_overhead) / 3.0; std::cout << "\n=== Key Metrics ===" << std::endl; std::cout << "Average RDTSC overhead: " << avg_rdtsc_overhead << " cycles" << std::endl; if (small_profile.rep_movsb_time > 0) { double cache_scaling = large_profile.rep_movsb_time / small_profile.rep_movsb_time; std::cout << "Cache scaling factor: " << cache_scaling << "x" << std::endl; } std::cout << "\n=== Interpretation ===" << std::endl; std::cout << "RDTSC overhead < 1000: Likely hardware" << std::endl; std::cout << "RDTSC overhead > 5000: Likely VM/emulation" << std::endl; std::cout << "Cache scaling 500-1000x: Typical for hardware" << std::endl; } }; int main() { EmulationDetector detector; detector.print_detailed_analysis(); return 0; } вроде как на vmware работает детект
galenkane ia sdm 3c, там где про vmx: Наверно тайминг должен отличаться на вирте. Если попробовать посмотреть за прерываниями, таким образом: Код (ASM): mov edx, 3 ; rpl(3) -> IRET -> !rpl mov ax, ss mov es, dx wait_for_int: mov cx, es jecxz int_loop jmp wait_for_int int_loop: ; accum of stat's.. mov es, dx mov ss, ax mov cx, es jecxz inc_int_count Изначально планировалось использовать это, для наблюдения за прерываниями. Статистику по пркрываниям можно собрать системным путем используя профайлер(NtStartProfile). Это хороший способ получить покрытие кода. Но есть некоторые сложности из за выравнивания счетчиков(не решено). --- Сообщение объединено, 7 июл 2025 --- QUOTE]Основные инструкции со связанным поведением [/QUOTE] 8. SERIALIZING INSTRUCTIONS
[TEST] CPUID Latency Analysis... -> CPUID cycles: 14257150 -> Baseline cycles: 18206 -> Ratio: 783.10x ANOMALY: CPUID extremely slow - VM exits detected --- Сообщение объединено, 7 июл 2025 --- Для комьюнити стоило б собрать все наработки и нормально их задокументировать, а то валяется по архивчикам и долго искать сюда-туда.
Эти инструкции привилегированные — то есть их нельзя вызывать из пользовательского режима (Ring 3). Если ты попытаешься — получишь #GP (General Protection Fault).
Есть ошибки, из за которых семпл не может работать. С cli/sti допустим может PVI, но такое: Код (ASM): TestMovSSBlocking: mov ax, ss rdtscp mov rsi, rax mov rdi, rdx mov ss, ax Это на необработанном фаулте упадет. Похоже семпл не проверен после сборки
Код (ASM): ; fasm-код для Windows User Mode (PE-файл) format PE console entry start section '.text' code readable executable start: ; --- Инициализация --- ; 1. Сохраняем текущий селектор SS для использования в тесте MOV [STACK_SEL], SS ; 2. NULL-селектор: 3 (RPL=3) MOV [DS_NULL_SEL], 3 ; 3. Количество итераций для усреднения MOV [ITERATIONS], 10000 ; 4. Обнуление суммарных счетчиков XOR EAX, EAX MOV [REF_TOTAL_COUNT], EAX MOV [TEST_TOTAL_COUNT], EAX ; ================================================================= ; 1. ЭТАЛОННЫЙ ЦИКЛ (Без маскировки SS) ; ================================================================= MOV ECX, [ITERATIONS] ; Счетчик итераций REF_INIT: PUSH ECX MOVZX EBX, word [DS_NULL_SEL] ; EBX = 3 MOV DS, BX ; Активация триггера: DS = 3 XOR EAX, EAX ; EAX = Счет инструкций (сброс в 0) REF_WAIT_LOOP: INC EAX ; 1. Считаем инструкцию MOVZX EBX, DS ; 2. Чтение DS TEST EBX, EBX ; 3. Проверка: DS сброшен до 0? JNZ REF_WAIT_LOOP ; 4. Если нет, ждем IRET ; IRET сработал ADD [REF_TOTAL_COUNT], EAX ; Добавляем результат к сумме POP ECX LOOP REF_INIT ; ================================================================= ; 2. ТЕСТИРУЕМЫЙ ЦИКЛ (С маскировкой SS) ; ================================================================= MOV ECX, [ITERATIONS] ; Счетчик итераций TEST_INIT: PUSH ECX MOVZX EBX, word [DS_NULL_SEL] ; EBX = 3 MOV DS, BX ; Активация триггера: DS = 3 XOR EAX, EAX ; EAX = Счет инструкций (сброс в 0) MOVZX EDI, word [STACK_SEL] ; EDI = Текущий селектор SS TEST_WAIT_LOOP: INC EAX ; 1. Считаем инструкцию MOV SS, DI ; 2. Загрузка SS (Блокирует INT на 1 инструкцию) MOVZX EBX, DS ; 3. Чтение DS (Атомарно с MOV SS) TEST EBX, EBX ; 4. Проверка: DS сброшен до 0? JNZ TEST_WAIT_LOOP ; 5. Если нет, ждем IRET ; IRET сработал ADD [TEST_TOTAL_COUNT], EAX ; Добавляем результат к сумме POP ECX LOOP TEST_INIT ; ================================================================= ; Вывод результатов (Для простоты оставим их в памяти) ; ================================================================= ; После выполнения: ; REF_TOTAL_COUNT - Суммарное количество инструкций в эталоне ; TEST_TOTAL_COUNT - Суммарное количество инструкций с маскировкой SS ; Выход из программы PUSH 0 CALL [ExitProcess] section '.data' data readable writable DS_NULL_SEL dw 0 STACK_SEL dw 0 ITERATIONS dd 0 REF_TOTAL_COUNT dd 0 TEST_TOTAL_COUNT dd 0 section '.idata' import data readable writeable library kernel32, 'kernel32.dll' import kernel32, ExitProcess, 'ExitProcess' --- Сообщение объединено, 30 ноя 2025 --- Код (ASM): ; fasm-код для Windows User Mode (PE-файл) format PE console entry start section '.text' code readable executable start: ; --- Инициализация --- ; 1. Сохраняем текущий селектор SS MOV [STACK_SEL], SS ; 2. NULL-селектор: 3 (RPL=3) MOV [DS_NULL_SEL], 3 ; 3. Обнуление суммарных счетчиков TSC (64-битные) XOR EAX, EAX MOV [REF_TOTAL_TSC_LO], EAX MOV [REF_TOTAL_TSC_HI], EAX MOV [TEST_TOTAL_TSC_LO], EAX MOV [TEST_TOTAL_TSC_HI], EAX ; 4. Установка целевого времени (100 мс) CALL [GetTickCount] ADD EAX, 100 MOV [END_TIME_MS], EAX ; ================================================================= ; 1. ЭТАЛОННЫЙ ЦИКЛ (Без маскировки SS) - Выполняем до END_TIME_MS ; ================================================================= REF_MAIN_LOOP: ; Проверка времени CALL [GetTickCount] CMP EAX, [END_TIME_MS] JAE REF_END ; Если время вышло (A > B или A = B), выходим ; --- Начало одной итерации измерения --- MOVZX EBX, word [DS_NULL_SEL] MOV DS, BX ; Чтение TSC ДО IRET-события RDTSC MOV [TSC_START_LO], EAX MOV [TSC_START_HI], EDX REF_WAIT_LOOP: ; Ждем сброса DS MOVZX EBX, DS TEST EBX, EBX JNZ REF_WAIT_LOOP ; IRET сработал ; Чтение TSC ПОСЛЕ IRET-события RDTSC MOV [TSC_END_LO], EAX MOV [TSC_END_HI], EDX ; --- Расчет дельты TSC и суммирование --- CALL TSC_DELTA_ADD ; TSC_DELTA_ADD суммирует разницу TSC в REF_TOTAL_TSC_... JMP REF_MAIN_LOOP REF_END: ; Сброс счетчика времени для следующего теста CALL [GetTickCount] ADD EAX, 100 MOV [END_TIME_MS], EAX ; ================================================================= ; 2. ТЕСТИРУЕМЫЙ ЦИКЛ (С маскировкой SS) - Выполняем до END_TIME_MS ; ================================================================= TEST_MAIN_LOOP: ; Проверка времени CALL [GetTickCount] CMP EAX, [END_TIME_MS] JAE TEST_END ; Если время вышло, выходим ; --- Начало одной итерации измерения --- MOVZX EBX, word [DS_NULL_SEL] MOV DS, BX ; Чтение TSC ДО IRET-события RDTSC MOV [TSC_START_LO], EAX MOV [TSC_START_HI], EDX MOVZX EDI, word [STACK_SEL] ; EDI = Текущий селектор SS TEST_WAIT_LOOP: MOV SS, DI ; Загрузка SS (Маскирует INT на 1 инструкцию) ; Ждем сброса DS MOVZX EBX, DS TEST EBX, EBX JNZ TEST_WAIT_LOOP ; IRET сработал ; Чтение TSC ПОСЛЕ IRET-события RDTSC MOV [TSC_END_LO], EAX MOV [TSC_END_HI], EDX ; --- Расчет дельты TSC и суммирование --- CALL TSC_DELTA_ADD_TEST JMP TEST_MAIN_LOOP TEST_END: ; ================================================================= ; 3. РАСЧЕТ ОТНОШЕНИЯ (TEST / REF) ; ================================================================= ; Для простоты оставим результаты в 64-битных переменных: ; REF_TOTAL_TSC_HI:LO - Суммарный TSC эталона ; TEST_TOTAL_TSC_HI:LO - Суммарный TSC теста ; (Расчет 64-битного деления здесь опущен из-за сложности, ; но принцип остается: Ratio = TEST_TOTAL_TSC / REF_TOTAL_TSC) PUSH 0 CALL [ExitProcess] ; --- Подпрограммы --- section '.text' code readable executable ; Подпрограмма для расчета дельты TSC (TSC_END - TSC_START) и суммирования в REF_TOTAL_TSC ; Использует 64-битное вычитание и сложение. TSC_DELTA_ADD: ; Вычитание (TSC_END - TSC_START) PUSHAD MOV EAX, [TSC_END_LO] SUB EAX, [TSC_START_LO] MOV EDX, [TSC_END_HI] SBB EDX, [TSC_START_HI] ; SBB учитывает заем из EAX ; Суммирование в REF_TOTAL_TSC ADD [REF_TOTAL_TSC_LO], EAX ADC [REF_TOTAL_TSC_HI], EDX ; ADC учитывает перенос из LO POPAD RET ; Подпрограмма для расчета дельты TSC (TSC_END - TSC_START) и суммирования в TEST_TOTAL_TSC TSC_DELTA_ADD_TEST: ; Вычитание (TSC_END - TSC_START) PUSHAD MOV EAX, [TSC_END_LO] SUB EAX, [TSC_START_LO] MOV EDX, [TSC_END_HI] SBB EDX, [TSC_START_HI] ; Суммирование в TEST_TOTAL_TSC ADD [TEST_TOTAL_TSC_LO], EAX ADC [TEST_TOTAL_TSC_HI], EDX POPAD RET section '.data' data readable writable DS_NULL_SEL dw 0 STACK_SEL dw 0 END_TIME_MS dd 0 ; 64-битные счетчики для суммарного времени TSC REF_TOTAL_TSC_LO dd 0 REF_TOTAL_TSC_HI dd 0 TEST_TOTAL_TSC_LO dd 0 TEST_TOTAL_TSC_HI dd 0 ; Переменные для хранения TSC (64-бит) TSC_START_LO dd 0 TSC_START_HI dd 0 TSC_END_LO dd 0 TSC_END_HI dd 0 section '.idata' import data readable writeable library kernel32, 'kernel32.dll' import kernel32, ExitProcess, 'ExitProcess', GetTickCount, 'GetTickCount' --- Сообщение объединено, 30 ноя 2025 --- sdm, mov: Код (Text): IF DS, ES, FS, or GS is loaded with non-NULL selector THEN IF segment selector index is outside descriptor table limits OR segment is not a data or readable code segment OR ((segment is a data or nonconforming code segment) AND ((RPL > DPL) or (CPL > DPL))) THEN #GP(selector); FI; IF segment not marked present THEN #NP(selector); ELSE SegmentRegister := segment selector; SegmentRegister := segment descriptor; FI; FI; IF DS, ES, FS, or GS is loaded with NULL selector THEN SegmentRegister := segment selector; SegmentRegister := segment descriptor; FI; iret: Код (Text): FOR each SegReg in (ES, FS, GS, and DS) DO tempDesc := descriptor cache for SegReg (* hidden part of segment register *) IF (SegmentSelector == NULL) OR (tempDesc(DPL) < CPL AND tempDesc(Type) is (data or non-conforming code))) THEN (* Segment register invalid *) SegmentSelector := 0; (*Segment selector becomes null*) FI; OD; - если это обьяснить, начинает норм думать, иначе тупит. Код (Text): ; ... (Начальная инициализация, REF_TOTAL_TSC, TEST_TOTAL_TSC) ... ; --- Дополнительная инициализация --- MOV [DS_NULL_SEL], 3 MOV [FS_UNIQUE_SEL], DS ; Сохраняем текущий селектор DS как уникальный маркер FS MOV [ITERATIONS], 1000000 ; ... ; ================================================================= ; 2. ТЕСТИРУЕМЫЙ ЦИКЛ (С маскировкой SS и учетом свапа) ; ================================================================= MOV ECX, [ITERATIONS] TEST_INIT: PUSH ECX MOVZX EBX, word [DS_NULL_SEL] MOV DS, BX ; DS = 3 (Триггер IRET-сброса) MOVZX EBX, word [FS_UNIQUE_SEL] MOV FS, BX ; Чтение TSC ДО IRET-события RDTSC MOV [TSC_START_LO], EAX MOV [TSC_START_HI], EDX MOVZX EDI, word [STACK_SEL] MOVZX ESI, word [FS_UNIQUE_SEL] ; ESI = исходное значение FS TEST_WAIT_LOOP: MOV SS, DI ; 1. Проверка DS: Ждем IRET-сброса MOVZX EBX, DS TEST EBX, EBX JNZ TEST_WAIT_LOOP ; Если DS != 0, ждем ; --- DS сброшен до 0: IRET или СВАП произошел --- ; 2. Проверка FS: Детектирование свапа контекста MOVZX EAX, FS ; Читаем текущее значение FS CMP EAX, ESI ; Сравниваем с исходным маркером (FS_UNIQUE_SEL) JNE CONTEXT_SWAP_DETECTED ; Если FS изменился, возможно, был свап! ; --- СВАПА НЕТ, СЧИТАЕМ РЕЗУЛЬТАТ --- RDTSC MOV [TSC_END_LO], EAX MOV [TSC_END_HI], EDX CALL TSC_DELTA_ADD_TEST ; Суммируем TSC JMP CONTINUE_TEST CONTEXT_SWAP_DETECTED: ; Если произошел свап, мы пропускаем это измерение. ; Это событие, где шум планировщика полностью доминировал. NOP ; Пропускаем TSC_DELTA_ADD_TEST CONTINUE_TEST: POP ECX LOOP TEST_INIT ; ... (Дальнейший код)
Код (Text): ; ... (Инициализация и т.д.) ... ; ================================================================= ; 2. ТЕСТИРУЕМЫЙ ЦИКЛ (Измерение серией на одном кванте) ; ================================================================= MOV ECX, [ITERATIONS] ; Общее число требуемых IRET-событий TEST_START: PUSH ECX ; Сохраняем общий счетчик ; *** 1. Стимуляция начала кванта *** PUSH 0 CALL [Sleep] ; Добровольно отдаем процессор. После возврата - новый квант. ; 2. Инициализация маркера FS (FS_UNIQUE_SEL) MOVZX EBX, word [FS_UNIQUE_SEL] MOV FS, BX MOV ESI, EBX ; ESI = Исходный маркер FS SERIES_LOOP: ; 3. Проверка оставшихся итераций TEST ECX, ECX JZ TEST_END_ALL ; --- Начало замера одиночного IRET --- MOVZX EBX, word [DS_NULL_SEL] MOV DS, BX RDTSC ; TSC_START MOV [TSC_START_LO], EAX MOV [TSC_START_HI], EDX WAIT_IRET_LOOP: MOVZX EDI, word [STACK_SEL] MOV SS, DI ; Маскировка INT ; Проверка DS: Ждем IRET-сброса MOVZX EBX, DS TEST EBX, EBX JNZ WAIT_IRET_LOOP ; --- IRET сработал --- ; 4. Проверка FS: Детектирование свапа контекста MOVZX EAX, FS CMP EAX, ESI JNE CONTEXT_SWAP_TERMINATED ; Если FS изменился, квант кончился! ; --- Свапа нет, это чистое измерение --- RDTSC MOV [TSC_END_LO], EAX MOV [TSC_END_HI], EDX CALL TSC_DELTA_ADD_TEST ; Суммируем TSC DEC ECX ; Успешно посчитали одно событие IRET JMP SERIES_LOOP CONTEXT_SWAP_TERMINATED: ; FS изменился: квант истек или произошел немедленный свап. ; Отбрасываем текущее измерение и перезапускаем квант. POP ECX JMP TEST_START TEST_END_ALL: POP ECX ; ... (Дальнейший код) ... Результат Теперь мы измеряем серию чистых событий IRET (возможно, десятки или сотни), пока не сработает детектор FS. Это максимально эффективно использует каждый выданный квант времени, давая вам самую высокую плотность "чистых" измерений.