itsource

'휘발성'의 정의는 이렇게 변덕스러운 건가요?아니면 GCC에 표준 컴플라이언스 문제가 있는 건가요?

mycopycode 2022. 8. 19. 21:16
반응형

'휘발성'의 정의는 이렇게 변덕스러운 건가요?아니면 GCC에 표준 컴플라이언스 문제가 있는 건가요?

(WinAPI의 Secure Zero Memory와 같이) 컴파일러가 그 이후에 메모리에 다시 액세스 할 수 없다고 생각하더라도 항상 메모리를 제로화하고 최적화하지 않는 기능이 필요합니다.변덕을 부릴 완벽한 후보인 것 같네요하지만 GCC와 함께 작동시키려면 문제가 좀 있습니다.다음은 기능의 예를 제시하겠습니다.

void volatileZeroMemory(volatile void* ptr, unsigned long long size)
{
    volatile unsigned char* bytePtr = (volatile unsigned char*)ptr;

    while (size--)
    {
        *bytePtr++ = 0;
    }
}

간단해.그러나 GCC가 실제로 생성하는 코드는 컴파일러 버전과 실제로 제로화하려는 바이트 수에 따라 크게 다릅니다.https://godbolt.org/g/cMaQm2

  • GCC 4.4.7 및 4.5.3은 휘발성을 무시하지 않습니다.
  • GCC 4.6.4 및 4.7.3은 어레이 크기 1, 2, 4에 대해 휘발성을 무시합니다.
  • GCC 4.8.1 ~4.9.2 에서는, 어레이 사이즈1 및 2 의 휘발성은 무시됩니다.
  • GCC 5.1 ~5.3 에서는, 어레이 사이즈가 1, 2, 4, 8 의 휘발성은 무시됩니다.
  • GCC 6.1은 어레이 크기에 관계없이 무시합니다(일관성을 유지하기 위한 주요 포인트).

테스트한 다른 컴파일러(clang, icc, vc)는 컴파일러 버전 및 어레이 크기를 불문하고 예상대로 스토어를 생성합니다.그래서 이 시점에서 나는 이것이 (매우 오래되고 심각한) 것인지 궁금하다.GCC 컴파일러의 버그 또는 표준에서 휘발성의 정의는 이것이 실제로 일치하는 동작임을 부정확하게 하여 휴대용 "Secure Zero Memory" 함수를 쓰는 것을 본질적으로 불가능하게 만드는 것입니까?

편집: 몇 가지 흥미로운 관찰이 있습니다.

#include <cstddef>
#include <cstdint>
#include <cstring>
#include <atomic>

void callMeMaybe(char* buf);

void volatileZeroMemory(volatile void* ptr, std::size_t size)
{
    for (auto bytePtr = static_cast<volatile std::uint8_t*>(ptr); size-- > 0; )
    {
        *bytePtr++ = 0;
    }

    //std::atomic_thread_fence(std::memory_order_release);
}

std::size_t foo()
{
    char arr[8];
    callMeMaybe(arr);
    volatileZeroMemory(arr, sizeof arr);
    return sizeof arr;
}

callMeMaybe()로부터의 기입에 의해 6.1 이외의 모든 GCC 버전이 예상되는 스토어를 생성합니다.메모리 펜스내의 코멘트에 의해서, GCC 6.1은 스토어를 생성합니다.단, call Me Maybe()로부터의 기입 가능성과 조합하는 것만으로 가능합니다.

또한 캐시를 플러시할 것을 제안했습니다.Microsoft 에서는, 「Secure Zero Memory」에서는 캐시를 플래시 하려고 하지 않습니다.캐시는 어쨌든 매우 빠르게 비활성화되기 때문에 큰 문제는 아닐 것입니다.또한 다른 프로그램이 데이터를 탐색하려고 하거나 페이지 파일에 쓰려고 할 경우 항상 제로 버전이 됩니다.

스탠드아론 함수에서 memset()을 사용하는 GCC 6.1에 대한 우려도 있습니다.GCC 6.1 컴파일러의 godbolt는 일부 사용자에게는 godbolt의 5.3과 같은 정상적인 루프를 생성하는 것처럼 보이기 때문에 빌드가 파손될 수 있습니다.(zwol의 답변 코멘트 읽기)

GCC의 동작은 적합할 수 있습니다.그렇지 않다고 해도,volatile 위원회는 C를 설계했다.volatile 및 흐름 중에 및 "Signal Handler" 의 경우setjmp이것들만이 신뢰할 있는 것입니다.일반적인 "최적화 안 함" 주석으로 사용하는 것은 안전하지 않습니다.

특히 요점에서 기준이 불분명하다.(당신의 코드를 C로 변환했습니다.여기서 C와 C++ 사이에는 차이가 없습니다.또, 컴파일러가 그 시점에서 「인식」하고 있는 것을 나타내기 위해서, 문제가 있는 최적화를 실시하기 전에 발생하는 인라이닝도 수동으로 실시합니다.

extern void use_arr(void *, size_t);
void foo(void)
{
    char arr[8];
    use_arr(arr, sizeof arr);

    for (volatile char *p = (volatile char *)arr;
         p < (volatile char *)(arr + 8);
         p++)
      *p = 0;
}

는 " "에 액세스합니다.arr 단, volatile-qualified lvalue를 합니다.arr 자체는 선언되어 있지 않다.volatile따라서 적어도 C 컴파일러는 루프가 만든 저장소가 "dead"라고 추론하고 루프를 완전히 삭제할 수 있습니다.C 논거에는 위원회가 그 상점들을 보존할 것을 요구하는 것을 암시하는 텍스트가 있습니다. 하지만 제가 읽은 바로는 표준 자체가 실제로 그러한 요구사항을 만들지는 않습니다.

표준이 요구하는 것과 필요로 하지 않는 것에 대한 자세한 내용은 "휘발성 로컬 변수는 volatile 인수와 다르게 최적화되는가" 및 "옵티마이저는 volatile reference/pointer를 통해 선언된 비휘발성 객체에 액세스하는 것은 해당 접근 volatile 규칙을 부여하는가?" 및 "G"를 참조하십시오.CC 버그 71793

위원회의 의견에 대한 자세한 내용은 volatileC99 Reason에서 "volatile"이라는 단어를 검색합니다.John Regehr의 논문 "Volatiles is Miscompileed"는 프로그래머가 어떻게 에 대해 기대하는지를 자세히 보여줍니다.volatile운영 컴파일러에 의해 충족되지 않을 수 있습니다.LLVM 팀의 일련의 에세이 "What Every C Programmer Shot Know About Undefined Behavior"는 특별히 다루지 않습니다.volatile최신 C 컴파일러가 '휴대용 어셈블러'가 아닌 이유와 방법을 이해하는 데 도움이 됩니다.


원하는 기능을 구현하는 방법에 대한 실제 질문으로 이동합니다.volatileZeroMemory 방법:하는 것,이든 간에, 불가능한 입니다.volatile이걸 위해서.그렇지 않으면 다른 물건들이 너무 많이 부서질 수 있기 때문에 신뢰할 수 있는 대안이 있습니다.

extern void memory_optimization_fence(void *ptr, size_t size);
inline void
explicit_bzero(void *ptr, size_t size)
{
   memset(ptr, 0, size);
   memory_optimization_fence(ptr, size);
}

/* in a separate source file */
void memory_optimization_fence(void *unused1, size_t unused2) {}

이 부분은 꼭 해야 합니다.memory_optimization_fence어떤 상황에서도 삽입되지 않습니다.이 파일은 자체 소스 파일에 있어야 하며 링크 시간 최적화의 대상이 되지 않아야 합니다.

컴파일러의 확장에 의존하는 다른 옵션들은 상황에 따라 사용할 수 있고 더 엄격한 코드를 생성할 수 있습니다(이 답변의 이전 버전에 나타난 옵션들 중 하나). 그러나 어떤 옵션도 범용적이지 않습니다.

합니다.explicit_bzero여러 C 라이브러리에서 이 이름으로 사용할 수 있기 때문입니다.이 이름에는 적어도4개의 다른 후보자가 있지만 각각1개의 C 라이브러리에서만 채택되고 있습니다).

또, 이 기능을 이용할 수 있다고 해도, 그것만으로는 불충분할 수 있습니다.특히,

struct aes_expanded_key { __uint128_t rndk[16]; };

void encrypt(const char *key, const char *iv,
             const char *in, char *out, size_t size)
{
    aes_expanded_key ek;
    expand_key(key, ek);
    encrypt_with_ek(ek, iv, in, out, size);
    explicit_bzero(&ek, sizeof ek);
}

하면 AES 액셀러레이션명령어일 경우expand_key ★★★★★★★★★★★★★★★★★」encrypt_with_ek inline,는 inline을 유지할 수 .ek파일 가 -- 「」에의 이 송신될 까지.explicit_bzero이것은 기밀 데이터를 삭제하기 위해 스택에 복사하도록 강요하고, 더 나쁜 것은 벡터 레지스터에 남아 있는 키에 대해 전혀 조치를 취하지 않는다는 것입니다.

(WinAPI의 Secure Zero Memory와 같이) 항상 메모리를 제로화하고 최적화하지 않는 기능이 필요합니다.

입니다.memset_s를 위한 것입니다.


휘발성 있는 이 동작이 적합한지 아닌지에 대해서는 말하기 어렵지만 휘발성은 오랫동안 버그에 시달려 왔다고 알려져 있습니다.

한 가지 문제는 "휘발성 객체에 대한 액세스는 추상 머신의 규칙에 따라 엄격하게 평가된다"는 사양이 있다는 것입니다.그러나 이는 '휘발성 객체'만을 의미하며 휘발성이 추가된 포인터를 통해 비휘발성 객체에 액세스하지 않습니다.따라서 컴파일러가 실제로 휘발성 오브젝트에 액세스하고 있지 않다는 것을 알 수 있다면 결국 그 오브젝트를 휘발성 오브젝트로 취급할 필요는 없습니다.

이 버전을 휴대용 C++로 제공합니다(단, 의미는 미묘하게 다릅니다).

void volatileZeroMemory(volatile void* const ptr, unsigned long long size)
{
    volatile unsigned char* bytePtr = new (ptr) volatile unsigned char[size];

    while (size--)
    {
        *bytePtr++ = 0;
    }
}

이제 휘발성 객체에 대한 쓰기 액세스 권한을 갖게 되었습니다.또한 객체의 휘발성 뷰를 통해 작성된 비휘발성 객체에 대한 액세스 권한뿐만이 아닙니다.

의미상의 차이는 메모리가 재사용되었기 때문에 메모리 영역을 점유한 객체의 수명이 공식적으로 종료된다는 것입니다.따라서 오브젝트의 내용을 제로로 한 후 오브젝트에 대한 액세스는 이제 확실히 정의되지 않은 동작입니다(이전에는 대부분의 경우 정의되지 않은 동작이었지만 일부 예외는 분명히 존재합니다).

이 는, 는 placement 「 「 」 「 」 「 」 「 」 「 」 「 」 「 」 「 」 「 」 「 」를가 있습니다.new원래 유형의 새 인스턴스를 다시 넣습니다.

값 초기화를 사용하면 코드를 짧게(명료하게는 할 수 없지만) 수 있습니다.

void volatileZeroMemory(volatile void* const ptr, unsigned long long size)
{
    new (ptr) volatile unsigned char[size] ();
}

현시점에서는 원라이너로 도우미 기능은 거의 보증되지 않습니다.

오른쪽에 있는 휘발성 객체를 사용하여 컴파일러가 스토어를 어레이에 보존하도록 강제함으로써 함수의 포터블 버전을 작성할 수 있습니다.

void volatileZeroMemory(void* ptr, unsigned long long size)
{
    volatile unsigned char zero = 0;
    unsigned char* bytePtr = static_cast<unsigned char*>(ptr);

    while (size--)
    {
        *bytePtr++ = zero;
    }

    zero = static_cast<unsigned char*>(ptr)[zero];
}

zerovolatile컴파일러가 항상 0으로 평가되더라도 그 값에 대해 어떠한 가정도 할 수 없도록 합니다.

마지막 할당식은 배열의 휘발성 인덱스를 읽고 값을 휘발성 개체에 저장합니다.이 읽기는 최적화할 수 없기 때문에 컴파일러가 루프에 지정된 저장소를 생성해야 합니다.

언급URL : https://stackoverflow.com/questions/38230856/is-the-definition-of-volatile-this-volatile-or-is-gcc-having-some-standard-co

반응형