다음을 통해 공유


디버깅 API를 사용하여 동적으로 코드 삽입

이 단원에서는 CLR(공용 언어 런타임) 디버깅 API를 사용한 동적 코드 삽입에 대해 설명합니다. 동적 코드 삽입에서는 원래의 이식 가능(PE) 파일에 없는 함수를 실행합니다. 예를 들어 Microsoft Visual Studio IDE(통합 개발 환경)에서 디버깅할 때 동적 코드 삽입을 사용하여 직접 실행 창에서 식을 실행할 수 있습니다. CLR에서는 활성 스레드를 도용하여 코드를 실행합니다. 이때 디버거에서 나머지 스레드를 실행하거나 중지하도록 CLR에 요청할 수 있습니다. 동적 코드 삽입은 함수 실행을 기반으로 하므로 동적으로 삽입된 함수를 일반 코드처럼 디버깅할 수 있습니다. 따라서 동적으로 삽입된 코드에 대해 중단점 설정, 단계별 실행 등과 같은 모든 표준 디버깅 서비스를 호출할 수 있습니다.

코드를 동적으로 삽입하려면 디버거에서 다음을 수행해야 합니다.

  • 동적 코드를 실행할 함수를 구성합니다.

  • 편집하며 계속하기를 사용하여 코드를 디버기에 삽입합니다.

  • 구성된 함수를 호출하여 원하는 만큼 반복해서 코드를 실행합니다.

다음 소단원에서는 이러한 단계에 대해 자세히 설명합니다.

함수 구성

  1. 동적 코드를 실행할 함수의 시그니처를 계산합니다. 이렇게 하려면 리프 프레임에서 실행되는 메서드의 시그니처에 지역 변수의 시그니처를 추가합니다. 이 방식으로 생성된 시그니처에는 사용되는 변수의 최소 부분 집합에 대한 계산이 필요하지 않습니다. 런타임에서는 사용되지 않는 변수를 무시합니다. 자세한 내용은 이 항목의 뒷부분에 나오는 "메타데이터에서 시그니처 가져오기"를 참조하십시오.

    함수의 모든 인수는 ByRef로 선언해야 합니다. 이렇게 하면 함수 실행에서 삽입된 함수의 변수에 대한 모든 변경 사항을 디버기의 리프 프레임으로 다시 전파할 수 있습니다.

    일부 변수는 동적 코드가 실행될 때 범위에 들지 않을 수 있습니다. 이런 경우에는 null 참조를 전달해야 합니다. 범위를 벗어난 변수를 참조하는 경우 NullReferenceException이 throw됩니다. 이런 상황이 발생하면 디버거에서는 ICorDebugManagedCallback::EvalException 콜백을 호출하여 함수 실행을 완료할 수 있습니다.

  2. 이 함수에 대해 고유한 이름을 선택합니다. 디버거에서 사용자가 함수를 검색하지 못하도록 함수 이름 앞에 문자열 "_Hidden:"을 붙여야 합니다. 이 함수가 추가되면 함수 이름이 특수함을 나타내는 플래그가 설정됩니다.

코드 삽입

  1. 디버거에서 동적으로 삽입될 코드가 본문에 포함된 함수를 만들도록 컴파일러에 요청해야 합니다.

  2. 디버거에서 델타 이식 가능(PE) 파일을 계산합니다. 이렇게 하려면 디버거에서 ICorDebugModule::GetEditAndContinueSnapshot 메서드를 호출하여 ICorDebugEditAndContinueSnapshot 개체를 가져옵니다. 디버거에서는 ICorDebugEditAndContinueSnapshot 메서드를 호출하여 델타 PE 이미지를 만들고 ICorDebugController::CommitChanges를 호출하여 실행 중인 이미지에 델타 PE를 설치합니다.

    동적으로 삽입되는 함수는 실행될 리프 프레임과 동일한 표시 수준에 배치해야 합니다. 리프 프레임이 인스턴스 메서드인 경우 동적으로 삽입되는 함수도 동일한 클래스의 인스턴스 메서드여야 합니다. 리프 프레임이 정적 메서드인 경우 동적으로 삽입되는 함수도 정적 메서드여야 합니다. 전역 메서드는 특정 클래스에 속한 정적 메서드입니다.

    참고참고

    동적 코드 삽입 프로세스가 완료된 후에도 함수가 디버기에 남게 됩니다.따라서 함수를 다시 구성하고 코드를 다시 삽입하지 않고도 이전에 삽입한 코드를 반복해서 다시 실행할 수 있습니다.따라서 이전에 삽입한 코드의 경우에는 이 단원과 앞 단원에서 설명한 단계를 건너뛸 수 있습니다.

삽입된 코드 실행

  1. 디버깅 검사 루틴을 사용하여 시그니처의 각 변수 값(ICorDebugValue 개체)을 가져옵니다. ICorDebugThread::GetActiveFrame 또는 ICorDebugChain::GetActiveFrame 메서드를 사용하여 리프 프레임 및 ICorDebugILFrame에 대한 QueryInterface를 가져올 수 있습니다. ICorDebugILFrame::EnumerateLocalVariables, ICorDebugILFrame::EnumerateArguments, ICorDebugILFrame::GetLocalVariable 또는 ICorDebugILFrame::GetArgument 메서드를 호출하여 실제 변수를 가져옵니다.

    참고참고

    디버거가 CORDBG_ENABLE이 설정되지 않은 디버기 즉, 디버깅 정보를 수집하고 있지 않는 디버기에 연결되어 있는 경우에는 디버거에서 ICorDebugILFrame 개체를 가져올 수 없으므로 함수 실행에 필요한 값을 수집할 수 없습니다.

    동적으로 삽입된 함수의 인수인 개체에 대한 역참조가 약한지 강한지는 중요하지 않습니다. 함수를 실행할 때 런타임에서는 삽입이 발생한 스레드를 도용합니다. 따라서 원래의 리프 프레임과 원래의 모든 강한 참조는 스택에 그대로 남습니다. 그러나 디버기의 모든 참조가 약한 경우에는 동적 코드 삽입을 실행하면 가비지 수집이 트리거되어 개체가 가비지 수집될 수 있습니다.

  2. ICorDebugThread::CreateEval 메서드를 사용하여 ICorDebugEval 개체를 만듭니다. ICorDebugEval에서는 함수를 실행하는 메서드를 제공합니다. 이러한 메서드 중 하나를 호출합니다. ICorDebugEval::CallFunction과 같은 메서드는 함수 실행만 설정합니다. 이 경우 디버거에서 ICorDebugController::Continue를 호출하여 디버기를 실행하고 함수를 실행해야 합니다. 실행이 완료되면 디버깅 서비스에서 ICorDebugManagedCallback::EvalComplete 또는 ICorDebugManagedCallback::EvalException 메서드를 호출하여 함수 실행에 대해 디버거에 알립니다.

    함수 실행에서 개체가 반환되는 경우 이 개체는 강하게 참조됩니다.

    동적으로 삽입된 코드에서 코드를 래핑하는 함수에 전달되는 null 참조를 역참조하려는 경우 CLR 디버깅 서비스에서 ICorDebugManagedCallback::EvalException을 호출합니다. 응답으로 디버거는 삽입된 코드를 실행할 수 없다고 사용자에게 알릴 수 있습니다.

    ICorDebugEval::CallFunction 메서드에서는 가상 디스패치를 수행하지 않습니다. 가상 디스패치가 필요한 경우 대신 ICorDebugObjectValue::GetVirtualMethod를 사용하십시오.

    디버기가 다중 스레드이고 디버거에서 다른 스레드가 실행되지 않게 하려면 디버거에서 ICorDebugController::SetAllThreadsDebugState를 호출하고 함수 실행에 사용되는 스레드를 제외한 모든 스레드의 상태를 THREAD_SUSPEND로 설정해야 합니다. 이렇게 설정하면 동적으로 삽입된 코드에서 수행하는 작업에 따라 교착 상태가 발생할 수 있습니다.

기타 문제

  • .NET Framework 보안은 컨텍스트 정책에 따라 결정되므로 보안 설정을 명시적으로 변경하지 않는 한 동적 코드 삽입은 리프 프레임과 동일한 보안 권한 및 기능으로 작동합니다.

  • 편집하며 계속하기 기능을 사용하여 함수를 추가할 수 있는 경우 언제나 동적으로 삽입된 함수를 추가할 수 있습니다. 동적으로 삽입된 함수는 리프 프레임에 삽입하는 것이 좋습니다.

  • 편집하며 계속하기 작업을 통해 클래스에 추가할 수 있는 필드, 인스턴스 메서드 또는 정적 메서드의 수에는 제한이 없습니다. 허용되는 최대 정적 데이터 양은 미리 정의되며 현재 모듈당 1MB로 제한되어 있습니다.

  • 동적으로 삽입된 코드에서는 비지역 goto 문을 사용할 수 없습니다.

메타데이터에서 시그니처 가져오기

메타데이터 디스펜서 가져오기

IMetaDataImport를 사용하여 메서드 찾기

  • 디버거에서는 IMetaDataImport::GetMethodProps를 호출하고 해당 토큰을 사용하여 메서드를 조회합니다. 메서드 토큰은 ICorDebugFunction::GetToken 메서드를 사용하여 가져올 수 있습니다. IMetaDataImport::GetMethodProps에서는 메서드의 시그니처를 반환합니다.

  • 디버거에서는 IMetaDataImport::GetSigFromToken 메서드를 호출하여 지역 시그니처 즉, 지역 변수의 시그니처를 가져옵니다. 디버거에서 지역 변수의 시그니처에 대한 토큰을 제공해야 합니다. 디버거에서 ICorDebugFunction::GetLocalVarSigToken 메서드를 호출하여 이 토큰을 얻을 수 있습니다.

함수 시그니처 생성

시그니처의 형식은 "메타데이터의 형식 및 시그니처 인코딩" 사양에 설명되어 있으며 이 개요의 정보보다 우선합니다.

사양의 "메서드 선언" 섹션에서 메서드 시그니처의 형식에 대해 설명합니다. 싱글바이트의 호출 규칙, 싱글바이트의 인수 수 및 형식 목록이 차례로 오는 형식입니다. 각 형식의 크기가 다를 수 있습니다. 가변 인수 호출 규칙을 지정하는 경우 인수의 수는 고정 인수 및 가변 인수를 포함한 총 인수 수입니다. ELEMENT_TYPE_SENTINEL 바이트는 고정 인수가 끝나고 가변 인수가 시작되는 위치를 표시합니다.

사양의 "독립 실행형 시그니처" 섹션에서는 지역 시그니처의 형식에 대해 설명합니다. 독립 실행형 시그니처에서는 가변 인수 호출 규칙을 사용하지 않습니다.

메서드 시그니처 및 지역 시그니처를 가져온 후 디버거에서 새 시그니처를 위한 공간을 할당해야 합니다. 그런 다음 메서드 시그니처를 반복합니다. 각 형식에 대해 ELEMENT_TYPE_BYREF 바이트 다음에 새 시그니처의 형식을 배치합니다. 메서드 시그니처의 끝이나 ELEMENT_TYPE_SENTINEL로 표시된 형식에 도달할 때까지 프로세스를 반복해야 합니다. 그런 다음 디버거에서 지역 시그니처 형식을 복사하고 각 형식을 ELEMENT_TYPE_BYREF로 표시해야 합니다. 메서드 시그니처에 가변 인수 호출 규칙이 있는 경우 디버거에서는 이러한 형식을 복사하고 ELEMENT_TYPE_BYREF로 표시해야 합니다. 마지막으로 디버거에서 인수의 수를 업데이트해야 합니다.

참고 항목

개념

CLR 디버깅 개요

기타 리소스

디버깅(관리되지 않는 API 참조)