source

자체 수정 코드를 사용하여 x86에서 오래된 명령 페칭을 관찰

factcode 2023. 7. 23. 14:45
반응형

자체 수정 코드를 사용하여 x86에서 오래된 명령 페칭을 관찰

인텔의 설명서에서 메모리에 명령어를 쓸 수 있다는 말을 듣고 읽었지만, 명령어 프리페치 대기열은 이미 오래된 명령어를 가져왔고 이전 명령어를 실행할 것입니다.저는 이 행동을 관찰하는 데 실패했습니다.저의 방법론은 다음과 같습니다.

인텔 소프트웨어 개발 매뉴얼은 섹션 11.6부터 다음과 같이 기술하고 있습니다.

프로세서에 현재 캐시된 코드 세그먼트의 메모리 위치에 쓰기를 수행하면 연결된 캐시 라인이 무효화됩니다.이 검사는 지침의 실제 주소를 기반으로 합니다.또한 P6 제품군 및 Pentium 프로세서는 코드 세그먼트에 쓰기가 실행을 위해 미리 페치된 명령을 수정할 수 있는지 여부를 확인합니다. 쓰기가 프리페치된 명령어에 영향을 미치는 경우 프리페치 대기열은 무효화됩니다. 이 후자의 검사는 명령어의 선형 주소를 기반으로 합니다.

따라서 오래된 명령을 실행하려면 동일한 물리적 페이지를 참조하는 두 개의 서로 다른 선형 주소가 필요합니다.그래서 저는 파일을 두 개의 다른 주소로 매핑합니다.

int fd = open("code_area", O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
assert(fd>=0);
write(fd, zeros, 0x1000);
uint8_t *a1 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
        MAP_FILE | MAP_SHARED, fd, 0);
uint8_t *a2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
        MAP_FILE | MAP_SHARED, fd, 0);
assert(a1 != a2);

제게는 변경하려는 명령어에 대한 포인터인 단일 인수를 사용하는 어셈블리 함수가 있습니다.

fun:
    push %rbp
    mov %rsp, %rbp

    xorq %rax, %rax # Return value 0

# A far jump simulated with a far return
# Push the current code segment %cs, then the address we want to far jump to

    xorq %rsi, %rsi
    mov %cs, %rsi
    pushq %rsi
    leaq copy(%rip), %r15
    pushq %r15
    lretq

copy:
# Overwrite the two nops below with `inc %eax'. We will notice the change if the
# return value is 1, not zero. The passed in pointer at %rdi points to the same physical
# memory location of fun_ins, but the linear addresses will be different.
    movw $0xc0ff, (%rdi)

fun_ins:
    nop   # Two NOPs gives enough space for the inc %eax (opcode FF C0)
    nop
    pop %rbp
    ret
fun_end:
    nop

C에서 나는 코드를 메모리 맵 파일에 복사합니다. 주소 선형소주에서함호수출다니합를▁▁linear▁function다▁address▁the▁i니▁invoke합에서 함수를 호출합니다.a1하지만 나는 그에게 포인터를 건네줍니다.a2코드 수정의 대상으로서.

#define DIFF(a, b) ((long)(b) - (long)(a))
long sz = DIFF(fun, fun_end);
memcpy(a1, fun, sz);
void *tochange = DIFF(fun, fun_ins);
int val = ((int (*)(void*))a1)(tochange);

CPU가 수정된 코드인 val==1을 선택한 경우.그렇지 않으면 오래된 명령이 실행된 경우(두 번의 nop) val==0입니다.

전 이걸 1.7로 실행했습니다.GHz Intel Core i5(2011 macbook air) 및 Intel(R) Xeon(R) CPU X3460 @ 2.80GHz.그러나 CPU가 항상 새 명령을 인식한다는 것을 나타내는 val==1이 매번 표시됩니다.

제가 관찰하고 싶은 행동을 경험한 사람이 있습니까?제 추론이 맞습니까?P6와 Pentium 프로세서에 대한 설명서와 Core i5 프로세서에 대한 설명이 무엇이 부족한지에 대해 조금 혼란스럽습니다.CPU가 명령 프리페치 대기열을 플러시하는 다른 일이 일어나고 있는 것이 아닐까요?어떤 식으로든 도움이 될 것입니다!

제 생각에, 당신은 성능 카운터(일부)를 확인해야 합니다.MACHINE_CLEARS이벤트) CPU(에어 파워북에 사용되는 샌디 브리지 1에서 사용할 수 있으며, 제온(네할렘 2)에서도 사용할 수 있습니다. 검색 "smc".사용할 수 있습니다.oprofile,perf또는의텔.Vtune: 값기찾:

http://software.intel.com/sites/products/documentation/doclib/iss/2013/amplifier/lin/ug_docs/GUID-F0FD7660-58B5-4B5D-AA9A-E1AF21DDCA0E.htm

컴퓨터 지우기

메트릭 설명

특정 이벤트에서는 마지막으로 회수된 명령 직후부터 전체 파이프라인을 지우고 다시 시작해야 합니다.이 메트릭은 메모리 순서 위반, 자체 수정 코드 및 잘못된 주소 범위에 대한 특정 로드와 같은 세 가지 이벤트를 측정합니다.

발생 가능한 문제

실행 시간의 상당 부분이 컴퓨터 지우기를 처리하는 데 사용됩니다.MACHINE_CLEARS 이벤트를 검사하여 특정 원인을 확인합니다.

SMC: http://software.intel.com/sites/products/documentation/doclib/stdxe/2013/amplifierxe/win/win_reference/snb/events/machine_clears.html

MACHINE_CLEARS 이벤트 코드: 0xC3 SMC 마스크: 0x04

자체 수정 코드(SMC)가 감지되었습니다.

자체 수정 코드 컴퓨터 지우기 수가 탐지되었습니다.

Intel은 smc http://software.intel.com/en-us/forums/topic/345561 에 대해서도 언급합니다(Intel Performance Bottleneck Analyzer의 분류법에서 링크됨).

이 이벤트는 자체 수정 코드가 탐지될 때 실행됩니다.일반적으로 이진 편집을 수행하는 사용자가 특정 경로(예: 해커)를 사용하도록 강제할 수 있습니다.이 이벤트는 프로그램이 코드 섹션에 쓰는 횟수를 계산합니다.자체 수정 코드는 모든 Intel 64 및 IA-32 프로세서에서 심각한 패널티를 발생시킵니다.수정된 캐시 라인은 L2 및 LLC 캐시에 다시 기록됩니다.또한 지침을 다시 로드해야 하므로 성능 저하가 발생할 수 있습니다.

제 생각에, 여러분은 그런 행사들을 보게 될 것입니다.만약 그렇다면 CPU는 코드를 자체 수정하는 동작을 감지할 수 있었고 파이프라인의 완전한 재시작인 "기계 지우기"를 발생시켰습니다.첫 번째 단계는 Fetch이고 그들은 L2 캐시에 새로운 opcode를 요청할 것입니다.코드 실행당 SMC 이벤트의 정확한 수에 관심이 많습니다. 이를 통해 지연 시간에 대한 견적을 얻을 수 있습니다.(SMC는 1개의 장치가 1.5 CPU 사이클로 가정되는 일부 장치에서 계산됩니다 - Intel Optimization Manual의 B.6.2.6)

은 " 은퇴 되었습니다."라고 명령은 인텔마 "으지막로고명은직시"가 될 합니다.mov그리고 당신의 낮잠은 이미 파이프라인에 있습니다.하지만 SMC는 무브가 은퇴할 때 양육될 것이고 그것은 노우프를 포함한 파이프라인의 모든 것을 죽일 것입니다.

이 SMC 유도 파이프라인 재시작은 비용이 저렴하지 않습니다. Agner는 Optimizing_assembly.pdf - "17.10 자체 수정 코드(모든 프로세서)"에 몇 가지 측정값이 있습니다. (여기 있는 모든 Core2/CoreiX는 PM과 같습니다.)

코드를 수정한 후 바로 실행하면 P1의 경우 약 19클럭, PMMX의 경우 약 31클럭, PPro, P2, P3, PM의 경우 약 150-300이 발생합니다.P4는 자체 수정 코드 후 전체 추적 캐시를 삭제합니다.80486 및 이전 프로세서에서는 코드 캐시를 플러시하기 위해 수정 코드와 수정 코드 사이를 건너뛰어야 합니다.

자체 수정 코드는 좋은 프로그래밍 방법으로 간주되지 않습니다.속도 향상이 상당하고 수정된 코드가 너무 많이 실행되어 자체 수정 코드 사용에 대한 페널티를 초과하는 이점이 있는 경우에만 사용해야 합니다.

SMC 디텍터를 실패하기 위해 서로 다른 선형 주소를 사용하는 것이 권장되었습니다. https://stackoverflow.com/a/10994728/196561 - 실제 정보 설명서를 찾아보겠습니다.지금은 당신의 진짜 질문에 대답할 수 없습니다.

여기에는 몇 가지 힌트가 있을 수 있습니다. Optimization Manual, 248966-026, 2012년 4월 "3.6.9 Mixing Code and Data":

쓰기 가능한 데이터를 코드 세그먼트에 배치하는 것은 자체 수정 코드와 구별하지 못할 수 있습니다.코드 세그먼트의 쓰기 가능 데이터는 자체 수정 코드와 동일한 성능 저하를 겪을 수 있습니다.

및 다음 섹션

소프트웨어는 실행 중인 동일한 1KByte 하위 페이지의 코드 페이지에 쓰거나 작성 중인 동일한 2KByte 하위 페이지에서 코드를 가져오는 것을 피해야 합니다.또한 직접 또는 투기적으로 실행된 코드가 포함된 페이지를 다른 프로세서와 데이터 페이지로 공유하면 시스템의 전체 파이프라인과 추적 캐시가 지워지는 SMC 조건이 트리거될 수 있습니다.이는 자체 수정 코드 상태로 인해 발생합니다.

쓰기 가능한 하위 페이지와 실행 가능한 하위 페이지의 교차점을 제어하는 몇 가지 스키마가 있을 수 있습니다.

다른 스레드(교차 수정 코드)에서 수정을 시도할 수 있지만 매우 신중한 스레드 동기화 및 파이프라인 플러시가 필요합니다(라이터 스레드에 지연에 대한 일부 무차별 대입을 포함할 수도 있습니다. 동기화 직후 CPUID를 사용하는 것이 좋습니다).하지만 당신은 그들이 이미 ""을 사용하여 이것을 고쳤다는 것을 알아야 합니다 - US6857064 특허를 확인하세요.

P6와 펜티엄 프로세서를 언급하는 설명서에 대해 조금 혼란스럽습니다.

이것은 인텔의 사용 설명서의 오래된 버전을 가져와서 디코딩하고 실행한 경우에 가능합니다.파이프라인을 재설정하고 다음 버전을 확인할 수 있습니다.주문 번호: 325462-047US, 2013년 6월 "11.6 자체 수정 코드".이 버전에서는 여전히 최신 CPU에 대해 설명하지 않지만 다른 가상 주소를 사용하여 수정할 때 마이크로아키텍처 간에 동작이 호환되지 않을 수 있습니다(Nehalem/Sandy Bridge에서 작동할 수 있고 에서 작동하지 않을 수 있음).스카이몬트)

11.6 자체 수정 코드 프로세서에 현재 캐시된 코드 세그먼트의 메모리 위치에 쓰기로 인해 관련 캐시 라인(또는 라인)이 무효화됩니다.이 검사는 지침의 실제 주소를 기반으로 합니다.또한 P6 제품군 및 Pentium 프로세서는 코드 세그먼트에 쓰기가 실행을 위해 미리 페치된 명령을 수정할 수 있는지 여부를 확인합니다.쓰기가 프리페치된 명령어에 영향을 미치는 경우 프리페치 대기열은 무효화됩니다.이 후자의 검사는 명령어의 선형 주소를 기반으로 합니다.Pentium 4 및 Intel Xeon 프로세서의 경우 대상 명령어가 이미 디코딩되어 추적 캐시에 상주하는 코드 세그먼트의 명령어 쓰기 또는 스눕은 전체 추적 캐시를 무효화합니다.후자의 동작은 코드를 자체 수정하는 프로그램이 Pentium 4 및 Intel Xeon 프로세서에서 실행될 때 성능이 심각하게 저하될 수 있음을 의미합니다.

실제로 선형 주소를 검사하면 IA-32 프로세서 간에 호환성 문제가 발생하지 않습니다.자체 수정 코드를 포함하는 응용 프로그램은 명령을 수정하고 가져오는 데 동일한 선형 주소를 사용합니다.

명령을 가져오는 데 사용된 것과 다른 선형 주소를 사용하여 명령을 수정할 수 있는 디버거와 같은 시스템 소프트웨어는 수정된 명령이 실행되기 전에 CPUID 명령과 같은 직렬화 작업을 실행하여 명령 캐시와 프리페치 대기열을 자동으로 다시 동기화합니다.(자체 수정 코드 사용에 대한 자세한 내용은 섹션 8.1.3 "자체 및 교차 수정 코드 처리"를 참조하십시오.)

Intel486 프로세서의 경우 캐시에 있는 명령어에 쓰기를 수행하면 캐시와 메모리에서 모두 수정되지만, 쓰기 전에 명령어가 프리페치된 경우 이전 버전의 명령어가 실행될 수 있습니다.이전 명령어가 실행되지 않도록 하려면 명령어를 수정하는 쓰기 직후 점프 명령어를 코딩하여 명령어 프리페치 유닛을 플러시합니다.

"SMC 탐지"(인용부 포함)에 대해 검색된 REAL 업데이트와 최신 Core2/Core iX가 SMC를 탐지하는 방법 및 SMC 탐지기에 Xeons 및 Pentium이 걸려 있는 많은 오류 목록에 대한 자세한 내용은 다음과 같습니다.

  1. http://www.google.com/patents/US6237088 파이프라인에서의 기내 지시 추적 시스템 및 방법 @ 2001

  2. DOI 10.1535/itj.1203.03(Google, citeseerx.ist.psu.edu 에 무료 버전 있음) - "INCLUSION FILTER"가 Penryn에서 더 낮은 수의 잘못된 SMC 탐지에 추가되었습니다. "기존 포함 탐지 메커니즘"은 그림 9에 나와 있습니다.

  3. http://www.google.com/patents/US6405307 - SMC 탐지 로직에 대한 오래된 특허

특허 US6237088(도 5, 요약)에 따르면, "라인 주소 버퍼"(많은 선형 주소가 페치된 명령어당 하나의 주소로 구성됨)가 있습니다. 즉, 캐시 라인 정밀도로 페치된 IP로 가득 찬 버퍼입니다.모든 스토어 또는 모든 스토어의 보다 정확한 "스토어 주소" 단계는 병렬 비교기에 입력되어 확인되며, 현재 실행 중인 명령어에 대한 교차점을 저장합니다.

두 특허 모두 "SMC 로직에서 물리적 주소나 논리적 주소를 사용할 것인지..."라고 명확하게 명시되어 있지 않습니다.http://nick-black.com/dankwiki/index.php/Sandy_Bridge 따르면 Sandybridge의 L1i는 VIPT(가상 인덱스, 물리적 태그 지정, 인덱스의 가상 주소 및 태그의 물리적 주소)이므로 L1 캐시가 데이터를 반환할 때 물리적 주소를 사용합니다.인텔이 SMC 탐지 논리에 물리적 주소를 사용할 수 있다고 생각합니다.

게다가 http://www.google.com/patents/US6594734 @ 1999(2003년 업데이트, CPU 설계 주기는 약 3-5년이라는 것만 기억하세요)는 "요약" 섹션에서 SMC가 현재 TLB에 있으며 물리적 주소를 사용한다고 말합니다(즉, SMC 디텍터를 속이려고 하지 마십시오).

변환 보기 사이드 버퍼를 사용하여 자체 수정 코드가 탐지되었습니다.[이것은] 저장소의 물리적 메모리 주소를 메모리에 사용하여 스눕을 수행할 수 있는 물리적 페이지 주소를 저장합니다. ...주소 페이지보다 세분화된 값을 제공하기 위해 FINE HIT 비트는 캐시의 각 항목에 포함되어 캐시의 정보를 메모리 내 페이지의 일부와 연결합니다.

(특허 US6594734에서 사분면으로 언급되는 페이지 부분은 1K 하위 페이지처럼 들리지 않습니까?)

그러면 그들은 말합니다.

따라서 메모리에 저장된 명령어에 의해 트리거된 스눕은 명령어 캐시에 저장된 모든 명령어의 물리적 주소를 메모리의 관련 페이지에 저장된 모든 명령어의 주소와 비교하여 SMC 탐지를 수행할 수 있습니다.일치하는 주소가 있으면 메모리 위치가 수정되었음을 나타냅니다.SMC 조건을 나타내는 주소가 일치하는 경우, 명령 캐시와 명령 파이프라인은 폐기 장치에 의해 플러시되고 새 명령은 저장을 위해 메모리에서 명령 캐시로 가져옵니다.

SMC 감지를 위한 스누핑은 물리적이며 ITLB는 일반적으로 물리적 주소로 변환하기 위해 선형 주소를 입력으로 수락하므로, ITLB는 물리적 주소에 컨텐츠 주소 지정 가능 메모리로 추가적으로 형성되며 추가 입력 비교 포트(스누핑 포트 또는 역변환 포트라고 함)를 포함합니다.

따라서 SMC를 탐지하기 위해 스눕을 통해 저장소가 물리적 주소를 명령 버퍼로 다시 전달하도록 강제합니다(다른 코어/cpus 또는 캐시에 대한 DMA 쓰기에서 유사한 스눕이 전달됩니다...). 스눕의 물리적 주소가 명령 버퍼에 저장된 캐시 라인과 충돌하면우리는 iTLB에서 은퇴 유닛으로 전달되는 SMC 신호를 통해 파이프라인을 다시 시작할 것입니다.iTLB를 통해 dTLB에서 폐기 장치로 이어지는 이러한 스눕 루프에서 얼마나 많은 CPU 클럭이 낭비될지 상상할 수 있습니다(이동보다 일찍 실행되었고 부작용이 없음에도 불구하고 다음 "nop" 명령을 폐기할 수 없습니다).그런데 WAT?ITLB에는 물리적 주소 입력과 두 번째 CAM(크고 핫)이 있습니다. 이는 미친 듯이 부정행위를 하는 자체 수정 코드를 지원하고 방어하기 위한 것입니다.

PS: 만약 우리가 거대한 페이지(4M 또는 1G일 수도 있음)로 작업한다면 어떨까요?L1TLB에는 페이지 항목이 너무 많아서 4MB 페이지의 1/4에 대해 잘못된 SMC 탐지가 많이 있을 수 있습니다...

PPS: 선형 주소가 다른 SMC의 잘못된 처리가 초기 P6/Ppro/P2에서만 발생했다는 변형이 있습니다.

인텔의 설명서에서 지침을 메모리에 쓸 수 있다는 말을 듣고 읽었지만, 지침 프리페치 대기열은 이미 오래된 지침을 가져왔으며 이전 지침을 실행할 수도 있습니다.저는 이 행동을 관찰하는 데 실패했습니다.

네, 그러실 겁니다.

모든 또는 거의 모든 최신 Intel 프로세서는 수동보다 엄격합니다.

그들은 선형이 아닌 물리적 주소를 기반으로 파이프라인을 스누핑합니다.

프로세서 구현은 설명서보다 엄격할 수 있습니다.

매뉴얼의 규칙을 준수하지 않는 코드가 발생하여 이를 위반하지 않도록 선택할 수 있습니다.

또는... 아키텍처 사양을 준수하는 가장 쉬운 방법(SMC의 경우 공식적으로 "다음 번 직렬화 지침까지"였지만, 실제로 레거시 코드의 경우 "다음 번 분기까지?" 이상이 될 때까지"였기 때문입니다."bytes away")는 더 엄격할 수 있습니다.

샌디브리지 가족(적어도 스카이레이크)은 여전히 동일한 행동을 하며, 분명히 물리적 주소를 기웃거립니다.

하지만 당신의 시험은 다소 복잡합니다.멀리뛰기의 요점을 알 수 없으며, SMC 기능을 플랫 바이너리로 조립(및 필요한 경우 링크)하면 + mm 맵을 두 번만 열 수 있습니다.만들다a1그리고.a2함수 포인터, 주 캔return a1(a2)매핑 후

다음은 간단한 테스트 하네스입니다. 누구나 자신의 기계를 사용해 볼 수 있도록 하기 위한 것입니다. (열린/주장/mmap 블록은 질문에서 복사한 것입니다. 시작점에 감사드립니다.

SMC 플랫 바이너리를 매핑하면 실제로 수정되므로 매번 다시 빌드해야 합니다.IDK 기본 파일을 수정하지 않는 동일한 실제 페이지의 두 매핑을 가져오는 방법. MAP_PRIVATE에 쓰는 경우 다른 실제 페이지로 COW할 수 있습니다.그래서 기계 코드를 파일에 쓰고 그들이 그것을 매핑하는 것은 이제 제가 이것을 깨달았으니 말이 됩니다.하지만 제 계산은 여전히 훨씬 더 간단합니다.)

// smc-stale.c
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>

typedef int (*intfunc_t)(void *);   // __attribute__((sysv_abi))  // in case you're on Windows.

int main() {
    int fd = open("smc-func", O_RDWR);

    assert(fd>=0);
    intfunc_t a1 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
                MAP_FILE | MAP_SHARED, fd, 0);
    intfunc_t a2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
                MAP_FILE | MAP_SHARED, fd, 0);
    assert(a1 != a2);
    return a1(a2);
}

테스트 기능용 NASM 소스:

(GNU GAS 어셈블리어를 사용하여 nasm-fbin과 같은 일반 바이너리를 생성하는 방법을 참조하십시오.당분간as+ld에대한대의 .nasm -f)

;;build with nasm smc-func.asm     -fbin is the default.
bits 64
entry:   ; rdi = another mapping of the same page that's executing
    mov  byte [rdi+dummy-entry], 0xcc       ; trigger any copy-on-write page fault now

    mov  r8, rbx    ; CPUID steps on call-preserved RBX
    cpuid               ; serialize for good measure
    mov  rbx, r8
;    mfence
;    lfence

    mov   dword [rdi + retmov+1 - entry],  0       ; return 0 for snooping
retmov:
    mov   eax, 1      ; opcode + imm32             ; return 1 for stale
    ret

dummy:  dd 0xcccccccc

Linux 4.20.3-arch1-1-ARCH를 실행하는 i7-6700k에서는 오래된 코드 가져오기가 관찰되지 않습니다.mov그 즉시를 덮어쓴.10실행하기 전에 명령을 수정했습니다.

peter@volta:~/src/experiments$ gcc -Og -g smc-stale.c
peter@volta:~/src/experiments$ nasm smc-func.asm && ./a.out; echo $?
0
# remember to rebuild smc-func every time, because MAP_SHARED modifies it

훨씬 오래된 CPU(Intel 8088)를 대상으로 하지만 8088 MPH 데모의 마지막에 있는 4채널 음악 플레이어는 오래된 명령을 실행할 뿐만 아니라 오래된 명령에 의존합니다!https://www.reenigne.org/blog/8088-pc-speaker-mod-player-how-its-done/

언급URL : https://stackoverflow.com/questions/17395557/observing-stale-instruction-fetching-on-x86-with-self-modifying-code

반응형