다음을 통해 공유


CLR 디버깅 아키텍처

CLR(공용 언어 런타임) 디버깅 API는 운영 체제 커널의 일부인 것처럼 사용하도록 설계되었습니다. 비관리 코드에서 프로그램이 예외를 생성하면 커널은 프로세스 실행을 일시 중단하고 Win32 디버깅 API를 사용하여 디버거에 예외 정보를 전달합니다. CLR 디버깅 API에서는 관리 코드에 대해서도 동일한 기능을 제공합니다. 관리 코드에서 예외가 생성되면 CLR 디버깅 API에서는 프로세스 실행을 일시 중단하고 디버거에 예외 정보를 전달합니다.

이 항목에서는 CLR 디버깅 API가 관여하는 방법과 시기 및 제공하는 서비스에 대해 설명합니다.

프로세스 아키텍처

CLR 디버깅 API는 다음의 두 가지 주요 구성 요소로 이루어져 있습니다.

  • 디버깅 DLL. 항상 디버깅되는 프로그램과 동일한 프로세스로 로드됩니다. 이 런타임 컨트롤러에서는 CLR과 통신하고 관리 코드를 실행하는 스레드의 실행 제어 및 검사를 수행합니다.

  • 디버거 인터페이스. 디버깅되는 프로그램과 다른 프로세스로 로드됩니다. 디버거 인터페이스에서는 디버거를 대신하여 런타임 컨트롤러와 통신합니다. 또한 디버깅되는 프로세스에서 보내는 Win32 디버깅 이벤트를 처리하는 작업도 담당합니다. 그리고 이러한 이벤트를 처리하거나 비관리 코드 디버거에 전달합니다. 디버거 인터페이스는 CLR 디버깅 API에서 노출된 API를 포함하는 유일한 구성 요소입니다.

CLR 디버깅 API는 컴퓨터 간 또는 프로세스 간 원격 사용을 지원하지 않습니다. 즉, 다음 API 아키텍처 다이어그램에서 볼 수 있는 것처럼 API를 사용하는 디버거는 자체 프로세스 내에서 원격 사용을 지원해야 합니다. 이 그림에서는 CLR 디버깅 API의 여러 구성 요소가 있는 위치 및 이 구성 요소가 CLR 및 디버거와 상호 작용하는 방법을 보여 줍니다.

CLR 디버깅 API 아키텍처

CLR 디버깅 아키텍처

관리 코드 디버거

관리 코드만 지원하는 디버거를 만들 수 있습니다. CLR 디버깅 API에서는 소프트 연결 메커니즘을 사용하여 필요 시 이러한 디버거를 프로세스에 연결할 수 있습니다. 프로세스에 소프트 연결된 디버거는 나중에 프로세스에서 분리할 수 있습니다.

스레드 동기화

CLR 디버깅 API에서는 프로세스 아키텍처와 관련하여 상충하는 요구 사항이 있습니다. 한편으로 디버깅 논리를 디버깅되는 프로그램과 동일한 프로세스에 유지해야 하는 여러 타당한 이유가 있습니다. 예를 들어 데이터 구조가 복잡하고 고정된 메모리 레이아웃 대신 함수를 사용하여 데이터 구조를 빈번하게 조작할 수 있습니다. 이런 경우 프로세스 외부에서 데이터 구조를 디코딩하지 않고 함수를 직접 호출하는 것이 더 간단합니다. 디버깅 논리를 동일한 프로세스에 유지하는 또 다른 이유로는 프로세스 간 통신 오버헤드를 제거하여 성능을 개선한다는 점을 들 수 있습니다. 마지막으로 CLR 디버깅의 중요한 기능은 디버기를 사용하여 프로세스에서 사용자 코드를 실행하는 기능인데, 여기에는 분명히 디버기 프로세스와의 상호 협력이 필요합니다.

그러나 다른 한편으로 외부 프로세서에서만 올바로 수행할 수 있는 비관리 코드 디버깅을 CLR 디버깅과 함께 사용해야 합니다. 또한 out-of-process 디버거에서는 디버거 작업과 디버기 프로세스 간의 간섭이 최소화되기 때문에 out-of-process 디버거가 in-process 디버거보다 안전합니다.

이러한 상충되는 요구 사항을 고려하여 CLR 디버깅 API에서는 각 방법의 일부를 결합합니다. 즉 주 디버깅 인터페이스는 out-of-process이고 네이티브 Win32 디버깅 서비스와 함께 사용됩니다. 그러나 CLR 디버깅 API에서는 사용자 프로세스에서 코드를 안전하게 실행할 수 있도록 디버기 프로세스와 동기화하는 기능을 추가합니다. 이러한 동기화를 수행하기 위해 API는 운영 체제 및 CLR과 협력하여 작업을 중단하지 않고 런타임을 일관되지 않는 상태로 그대로 둘 위치의 프로세스에서 모든 스레드를 일시 중지합니다. 그러면 디버거에서 필요한 경우 런타임의 상태를 검사하고 사용자 코드를 호출할 수 있는 특수한 스레드에서 코드를 실행할 수 있습니다.

관리 코드에서 중단점 명령을 실행하거나 예외를 생성하면 런타임 컨트롤러에 알립니다. 이 구성 요소에서는 관리 코드를 실행하고 있는 스레드와 비관리 코드를 실행하고 있는 스레드를 확인합니다. 일반적으로 관리 코드를 실행하는 스레드는 안전하게 일시 중단할 수 있는 상태에 도달할 때까지 계속 실행할 수 있습니다. 예를 들어 이 스레드에서는 진행 중인 가비지 수집을 완료해야 할 수 있습니다. 관리 코드 스레드가 안전한 상태에 도달하면 모든 스레드가 일시 중단됩니다. 그런 다음 디버거 인터페이스에서 중단점 또는 예외를 수신했음을 디버거에 알립니다.

비관리 코드에서 중단점 명령을 실행하거나 예외를 생성하는 경우 디버거 인터페이스 구성 요소에서는 Win32 디버깅 API를 통해 알림을 받습니다. 이 알림은 관리되지 않는 디버거에 전달됩니다. 디버거에서 관리 코드 스택 프레임을 검사할 수 있도록 하는 등의 이유로 동기화를 수행하기로 결정하는 경우 디버거 인터페이스에서는 먼저 중지된 디버기 프로세스를 다시 시작한 다음 런타임 컨트롤러에 동기화를 수행하도록 알려야 합니다. 그런 다음 동기화가 완료되면 디버거 인터페이스에 알립니다. 이 동기화는 관리되지 않는 디버거에 투명하게 이루어집니다.

중단점 명령이나 예외를 생성한 스레드는 동기화 프로세스 동안 실행될 수 없어야 합니다. 이를 위해 디버거 인터페이스에서는 해당 스레드의 필터 체인에 특수한 예외 필터를 배치하여 스레드를 제어합니다. 스레드가 다시 시작되면 예외 필터가 시작되어 스레드가 런타임 컨트롤러의 제어를 받게 됩니다. 예외 처리를 계속하거나 예외를 취소할 시간이 되면 필터에서 제어 권한을 스레드의 일반 예외 필터 체인에게 반환하거나 올바른 결과를 반환하여 실행을 다시 시작합니다.

네이티브 예외를 생성하는 스레드에서 런타임 동기화가 완료될 수 있으려면 해제해야 하는 중요한 잠금을 유지하는 경우가 있습니다. 일반적으로 이러한 잠금은 malloc 힙에 대한 잠금과 같은 하위 수준의 라이브러리 잠금입니다. 이러한 경우에는 동기화 작업이 시간 초과되고 동기화에 실패합니다. 따라서 동기화가 필요한 특정 작업도 실패합니다.

In-Process 도우미 스레드

모든 CLR 프로세스에서는 CLR 디버깅 API가 올바로 작동할 수 있도록 하나의 디버거 도우미 스레드가 사용됩니다. 이 도우미 스레드는 경우에 따라 스레드 동기화를 지원할 뿐만 아니라 디버깅 API에서 제공하는 여러 검사 서비스를 처리합니다. ICorDebugProcess::GetHelperThreadID 메서드를 사용하여 도우미 스레드를 식별할 수 있습니다.

JIT 컴파일러와의 상호 작용

디버거에서 JIT(Just-In-Time) 컴파일된 코드를 디버깅할 수 있으려면 CLR 디버깅 API에서 함수의 MSIL(Microsoft Intermediate Language) 버전 정보를 함수의 네이티브 버전에 매핑할 수 있어야 합니다. 이러한 정보에는 코드의 시퀀스 위치 및 지역 변수 위치 정보가 포함됩니다. 이러한 정보가 .NET Framework 버전 1.0 및 1.1에서는 런타임이 디버깅 모드에 있는 경우에만 생성되었지만 .NET Framework 2.0에서는 항상 생성됩니다.

또한 JIT 컴파일된 코드도 고도로 최적화될 수 있습니다. 공통 부분식 제거, 함수의 인라인 확장, 루프 해제, 코드 호이스팅 등과 같은 최적화로 인해 코드를 실행하기 위해 호출되는 함수의 MSIL 코드와 네이티브 코드 간의 상관 관계가 손실될 수 있습니다. 따라서 이러한 적극적인 코드 최적화 기술은 올바른 매핑 정보를 제공하는 JIT 컴파일러의 기능에 심각한 영향을 미칩니다. 그러므로 런타임이 디버깅 모드에서 실행되고 있는 경우에는 JIT 컴파일러에서 특정 최적화를 수행하지 않습니다. 이러한 제한을 통해 디버거에서 소스 줄 매핑과 모든 지역 변수 및 인수의 위치를 정확하게 결정할 수 있습니다.

디버깅 모드

CLR 디버깅 API에서는 다음 두 경우에 특수한 디버깅 모드를 제공합니다.

  • 편집하며 계속하기 모드. 이 경우에는 나중에 코드를 변경할 수 있도록 런타임이 다르게 작동됩니다. 이는 편집하며 계속하기를 지원하기 위해서는 특정 런타임 데이터 구조의 레이아웃이 달라야 하기 때문입니다. 이 모드는 성능에 좋지 않은 영향을 주므로 편집하며 계속하기 기능이 필요한 경우 외에는 사용하지 마십시오.

  • 디버깅 모드. 이 모드를 사용하면 JIT 컴파일러에서 최적화를 생략할 수 있습니다. 따라서 네이티브 코드 실행이 고급 언어 소스와 보다 가깝게 일치하게 됩니다. 이 모드도 성능에 좋지 않은 영향을 주므로 필요한 경우 외에는 사용하지 마십시오.

편집하며 계속하기 모드 밖에서 프로그램을 디버깅하는 경우 편집하며 계속하기 기능이 지원되지 않습니다. 디버깅 모드 밖에서 프로그램을 디버그하는 경우에는 대부분의 디버깅 기능이 계속 지원되지만 최적화가 예기치 않게 동작할 수 있습니다. 예를 들어 한 단계씩 이동이 메서드의 줄 사이를 임의로 이동하는 것처럼 보이거나 인라인된 메서드가 스택 추적에 표시되지 않을 수 있습니다.

런타임에서 프로세스를 초기화하기 전에 디버거에서 프로세스에 대한 제어권을 얻는 경우 디버거에서는 CLR 디버깅 API를 통해 프로그래밍 방식으로 편집하며 계속하기 및 디버깅 모드를 모두 사용할 수 있습니다. 대부분의 경우 이것만으로도 충분하지만 이미 잠시 동안(예: JIT 디버깅 동안) 실행되고 있던 프로세스에 연결된 디버거에서는 이러한 모드를 시작할 수 없습니다.

이러한 문제를 해결하려면 프로그램을 디버거와 별도로 JIT 모드나 디버거 모드로 실행합니다. 디버깅을 사용하는 방법에 대한 자세한 내용은 디버깅, 추적 및 프로파일링을 참조하십시오.

JIT 최적화는 응용 프로그램의 디버깅 가능성을 줄일 수 있습니다. CLR 디버깅 API를 사용하면 최적화된 JIT 컴파일된 코드에서 스택 프레임 및 지역 변수를 검사할 수 있습니다. 단계별 실행은 지원되기는 하지만 부정확할 수 있습니다. JIT 컴파일러에게 모든 JIT 최적화를 해제하여 디버깅 가능한 코드를 만들도록 지시하는 프로그램을 실행할 수 있습니다. 자세한 내용은 쉽게 디버깅할 수 있도록 이미지 만들기를 참조하십시오.

참고 항목

개념

CLR 디버깅 개요

기타 리소스

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