어셈블리 (CLI)
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
어셈블리 (CLI)는 CLI (Common Language Infrastructure) 환경에서 실행되는 코드의 기본 단위이다. 어셈블리는 어셈블리 이름, 버전, 문화권, 공개 키 토큰으로 구성되며, 어셈블리 이름은 어셈블리를 고유하게 식별하는 데 사용된다. 어셈블리 버전 관리를 통해 응용 프로그램 간의 충돌을 줄일 수 있으며, 코드 접근 보안은 어셈블리와 증거를 기반으로 한다. 위성 어셈블리는 현지화를 위한 리소스를 포함하며, C# 컴파일러의 `/reference` 플래그를 사용하여 어셈블리를 참조할 수 있다. Fusion은 .NET 어셈블리를 찾고 로드하는 메커니즘으로, 전역 어셈블리 캐시, 응용 프로그램 설정 파일, 응용 프로그램 폴더 등을 검색한다.
더 읽어볼만한 페이지
- 공통 언어 기반 - 범용 어셈블리 캐시
범용 어셈블리 캐시는 .NET 어셈블리를 저장하는 저장소이며, 여러 버전의 어셈블리를 동시에 사용할 수 있게 해준다.
어셈블리 (CLI) |
---|
2. 어셈블리 종류
Windows의 .NET Framework 구현에서 어셈블리는 PE 형식의 파일이며, 크게 두 가지 종류로 나뉜다.
- '''프로세스 어셈블리''' (EXE): 일반적으로 실행 가능한 프로그램을 나타낸다.
- '''라이브러리 어셈블리''' (DLL): 다른 프로그램에서 재사용될 수 있는 코드나 리소스를 담고 있는 라이브러리이다.
CLR은 어셈블리가 프로세스인지 라이브러리인지를 구분할 때 파일의 확장자가 아닌, 어셈블리 내부에 설정된 특정 플래그 값을 확인한다. 이 때문에 라이브러리 어셈블리가 .exe 확장자를 가질 수도 있다.
과거 CLR 1.1 버전에서는 클래스를 외부에 공개하는 기능이 라이브러리 어셈블리에서만 가능했지만, CLR 2.0 버전부터는 이러한 제한이 완화되어 프로세스 어셈블리에서도 클래스를 외부에 공개할 수 있게 되었다.
3. 어셈블리 이름
어셈블리의 이름은 다음 네 부분으로 구성된다.
- 짧은 이름: Windows 환경에서는 확장자를 제외한 PE 파일의 이름이다.
- 문화권: 어셈블리의 로케일을 나타내는 RFC 1766 식별자이다. 일반적으로 라이브러리 및 프로세스 어셈블리는 문화권 중립적으로 설정하며, 이 정보는 주로 위성 어셈블리에 사용된다.
- 버전: 주요(Major), 부(Minor), 빌드(Build), 수정(Revision)의 네 부분으로 구성되며 점(`.`)으로 구분되는 버전 번호이다.
- 공개 키 토큰: 어셈블리를 디지털 서명[1]하는 데 사용된 개인 키에 해당하는 공개 키의 64비트 해시 값이다. 이렇게 서명된 어셈블리는 강력한 이름을 가진다고 한다.
공개 키 토큰은 어셈블리 이름을 고유하게 만든다. 따라서 두 어셈블리가 동일한 PE 파일 이름을 갖더라도 CLI는 공개 키 토큰을 통해 이를 구별하여 서로 다른 어셈블리로 인식할 수 있다. 그러나 윈도우의 파일 시스템(예: FAT32, NTFS)은 PE 파일 이름만 인식하므로, 동일한 파일 이름을 가진 두 개의 어셈블리(문화권, 버전, 공개 키 토큰이 다르더라도)가 같은 폴더 안에 존재할 수 없다. 이 문제를 해결하기 위해 CLI는 GAC(Global Assembly Cache)를 도입했다. GAC는 런타임 환경에서는 단일 폴더처럼 보이지만, 실제로는 여러 중첩된 파일 시스템 폴더를 사용하여 구현되며, 이를 통해 동일한 파일 이름을 가진 여러 버전의 어셈블리를 관리할 수 있다.
어셈블리 서명은 스푸핑 공격을 방지하기 위한 중요한 보안 메커니즘이다. 즉, 악의적인 크래커가 자신의 악성 코드를 정상적인 어셈블리인 것처럼 위장하는 것을 막는다. 어셈블리 개발자는 서명에 사용된 개인 키를 비밀로 유지하므로, 크래커는 이 키에 접근하거나 추측할 수 없다. 따라서 크래커는 어셈블리를 임의로 변경한 후 올바르게 다시 서명할 수 없어 위장이 불가능해진다.
어셈블리 서명 과정은 다음과 같다. 먼저 어셈블리의 중요한 부분에 대한 해시 값을 계산한다. 그런 다음, 이 해시 값을 개발자의 개인 키를 사용하여 암호화한다. 이렇게 암호화된 해시(서명)는 공개 키와 함께 어셈블리 내에 저장된다. CLR(Common Language Runtime)이 강력한 이름의 어셈블리를 로드할 때, 어셈블리에 저장된 공개 키를 사용하여 서명을 복호화한다. 그리고 복호화된 해시 값과 로드된 어셈블리로부터 새로 계산한 해시 값을 비교한다. 두 해시 값이 일치하면, 해당 어셈블리가 서명 시 사용된 개인 키와 연관된 올바른 공개 키를 가지고 있으며 변조되지 않았음을 의미한다. 이는 어셈블리의 진위성을 보장하고 스푸핑 공격을 효과적으로 방지한다.
4. 어셈블리 버전 관리
CLI 어셈블리는 버전 정보를 포함하여 공유 어셈블리 사용 시 발생할 수 있는 응용 프로그램 간의 충돌 문제를 상당 부분 해결할 수 있다.[5][2] 하지만 모든 잠재적인 버전 충돌을 완전히 제거하지는 못한다.[6][3]
어셈블리의 버전 정보는 네 부분으로 구성된 번호를 사용하며, 각 부분은 점(`.`)으로 구분된다. 형식은 다음과 같다.
`MajorVersion.MinorVersion.BuildNumber.Revision`
이 버전 정보는 어셈블리 이름의 일부로 관리된다. 어셈블리 이름은 다음과 같은 네 가지 요소로 구성된다.
요소 | 설명 |
---|---|
약식 이름 | Windows 운영체제에서는 일반적으로 실행 파일(.exe)이나 라이브러리 파일(.dll)의 이름에서 확장자를 제외한 부분에 해당한다. |
컬처 | RFC 1766 표준에 따른 IETF 언어 태그로, 특정 지역 또는 언어 설정을 나타낸다. 보통 주 프로그램(프로세스 어셈블리)이나 일반 라이브러리는 특정 문화권에 종속되지 않는 뉴트럴 컬처를 가지며, 다국어 지원 등을 위한 위성 어셈블리에서 특정 컬처 값을 사용한다. |
버전 | 위에서 설명한 `MajorVersion.MinorVersion.BuildNumber.Revision` 형식의 네 부분으로 이루어진 번호이다. 이를 통해 어셈블리의 변경 이력을 관리하고 호환성을 유지한다. |
공개 키 토큰 | 어셈블리를 고유하게 식별하고 위변조를 방지하기 위해 사용되는 값이다. 어셈블리를 만들 때 개발자는 비밀 키로 서명하고, 이 비밀 키에 대응하는 공개 키의 일부(64비트 해시)를 공개 키 토큰으로 사용한다. 공개 키 토큰이 포함된 어셈블리 이름을 엄격한 이름(strong name)이라고 부른다. |
공개 키 토큰은 서로 다른 개발자가 만든 어셈블리가 우연히 같은 약식 이름을 갖더라도 .NET 환경이 이를 구별할 수 있게 해준다. 하지만 Windows의 파일 시스템은 파일 이름만을 기준으로 파일을 관리하기 때문에, 약식 이름이 같은 여러 버전의 어셈블리를 같은 폴더에 둘 수 없는 문제가 있다.
이 문제를 해결하기 위해 .NET은 글로벌 어셈블리 캐시(GAC, Global Assembly Cache)라는 특별한 저장소를 제공한다. GAC는 시스템 전체에서 공유되는 어셈블리들을 위한 중앙 저장소 역할을 하며, 동일한 약식 이름을 가진 여러 버전의 어셈블리나 서로 다른 컬처를 가진 어셈블리들을 함께 관리할 수 있게 해준다. CLR(Common Language Runtime)은 GAC에 등록된 어셈블리들을 마치 하나의 폴더에 있는 것처럼 인식하고 로드할 수 있다.
또한, 어셈블리 위조 공격을 방지하기 위해 어셈블리는 개발자의 비밀 키로 서명된다. 서명 과정에서는 어셈블리의 중요 내용에 대한 해시값을 계산하고 이를 비밀 키로 암호화한다. 이 암호화된 해시값과 공개 키는 어셈블리 파일 내에 함께 저장된다. CLR이 엄격한 이름(strong name)을 가진 어셈블리를 로드할 때는, 저장된 공개 키를 사용하여 암호화된 해시를 복호화하고, 이를 실제 어셈블리 내용의 해시값과 비교한다. 두 값이 일치하면 해당 어셈블리가 서명된 이후 변경되지 않았으며, 명시된 공개 키에 해당하는 비밀 키로 서명되었음을 신뢰할 수 있다.
5. 어셈블리와 CLI 보안
CLI의 코드 접근 보안은 어셈블리와 증거를 기반으로 작동한다. 증거는 주로 어셈블리의 출처 정보를 나타내는데, 예를 들어 어셈블리가 인터넷이나 인트라넷에서 다운로드되었는지, 아니면 사용자 컴퓨터에 직접 설치되었는지 등을 알려준다. 다른 컴퓨터에서 다운로드된 어셈블리는 샌드박스 위치인 GAC 내에 저장되므로 로컬 설치로 간주되지 않는다.
파일 시스템 권한은 어셈블리 전체에 적용되며, 개발자는 어셈블리가 실행되기 위해 필요한 최소한의 권한을 CLI 메타데이터 내 사용자 지정 특성을 통해 명시할 수 있다. CLR은 어셈블리를 로드할 때 이 증거 정보를 바탕으로 해당 어셈블리에 부여할 권한 집합을 생성한다. 이후 CLR은 이 생성된 권한 집합이 어셈블리에서 요구하는 최소 필수 권한을 모두 포함하고 있는지 검사한다.
CLI 코드는 실행 중 특정 작업을 수행하기 전에 '코드 접근 보안 요구'를 할 수 있다. 이는 현재 실행 호출 스택에 있는 모든 메서드의 모든 어셈블리가 특정 권한을 가지고 있는지 확인하는 과정이다. 만약 스택 내의 어느 한 어셈블리라도 필요한 권한이 없다면, 보안 관련 예외가 발생하여 작업이 중단된다.
또는 CLI 코드는 '연결된 요구'를 수행하여 호출 스택에서 권한을 얻을 수도 있다. 이 경우 CLR은 호출 스택 전체를 검사하는 대신, 스택의 가장 위에 있는(가장 최근에 호출된) 단일 메서드만 확인하여 필요한 권한이 있는지 검사한다. 이는 해당 메서드가 필요한 권한을 가지고 있다면, 그 아래 스택의 다른 메서드들도 암묵적으로 권한을 가진 것으로 간주하는 방식이다.
스푸핑 공격과 같이 악의적인 사용자가 어셈블리를 위장하려는 시도를 막기 위해, 어셈블리는 디지털 서명[1] 기술을 사용한다. 개발자는 자신만이 아는 개인 키로 어셈블리에 서명한다. 이 개인 키는 비밀로 유지되므로 다른 사람이 어셈블리를 임의로 수정하고 다시 서명하는 것이 불가능하다. 서명 과정에는 어셈블리의 중요 데이터에 대한 해시 값을 계산하고, 이를 개인 키로 암호화하는 작업이 포함된다. 이 암호화된 해시 값과 공개 키는 어셈블리 내에 함께 저장된다. CLR이 강력한 이름을 가진 어셈블리를 로드할 때, 어셈블리 데이터로부터 다시 해시 값을 계산하고, 저장된 암호화된 해시를 공개 키로 복호화한 값과 비교한다. 두 값이 일치하면 해당 어셈블리가 변조되지 않았고, 명시된 게시자가 서명한 것임을 신뢰할 수 있게 되어 스푸핑 공격을 효과적으로 방지한다.
6. 위성 어셈블리
일반적으로 어셈블리는 특정 문화권에 구애받지 않는 중립적인 리소스를 포함해야 한다. 만약 어셈블리를 특정 지역이나 언어에 맞게 현지화하고 싶다면(예를 들어, 로케일에 따라 다른 문자열을 사용하고 싶을 때), 위성 어셈블리(satellite|새틀라이트영어)를 사용해야 한다. 위성 어셈블리는 이름에서 알 수 있듯이, 주 어셈블리(main assembly)라고 불리는 핵심 어셈블리에 연결되는, 리소스만을 담고 있는 특별한 종류의 어셈블리이다. 위성 어셈블리에는 실행 코드가 포함되어서는 안 되며, CLI의 어셈블리 로더(Fusion)에 의해 직접 로드되지 않는다.
각 위성 어셈블리는 주 어셈블리의 파일 이름 끝에 ".resources"를 덧붙인 형태의 이름을 갖는다. 예를 들어, 주 어셈블리가 `lib.dll`이라면, 그에 연결된 위성 어셈블리의 파일 이름은 `lib.resources.dll`이 된다. 위성 어셈블리는 특정 문화권(예: 영국 영어(en-GB)) 정보를 담고 있지만, 윈도우의 파일 시스템(FAT32, NTFS)은 파일 이름만으로는 동일한 이름의 파일을 구분할 수 없다. 따라서 서로 다른 문화권의 위성 어셈블리 파일 이름이 중복되는 문제를 피하기 위해, 각 위성 어셈블리는 해당 문화권의 이름을 딴 하위 폴더 안에 저장해야 한다. 예를 들어, 영국 영어(en-GB) 문화권의 리소스를 담은 위성 어셈블리는 CLI 상에서는 `"lib.resources Version=0.0.0.0 Culture=en-GB PublicKeyToken=null"`과 같은 고유한 이름을 가지며, 실제 파일인 `lib.resources.dll`은 애플리케이션 폴더 아래의 `en-GB`라는 하위 폴더에 저장된다.
위성 어셈블리는 `System.Resources.ResourceManager` 클래스에 의해 로드된다. 개발자가 리소스의 이름과 주 어셈블리(중립 리소스를 포함하는)에 대한 정보를 제공하면, `System.Resources.ResourceManager` 클래스는 현재 시스템의 로케일(지역 및 언어 설정)을 확인한다. 이 로케일 정보와 주 어셈블리 이름을 조합하여 필요한 위성 어셈블리의 이름과 그것이 저장된 하위 폴더의 경로를 알아낸다. 그 후, 해당 경로에서 위성 어셈블리를 로드하여 현지화된 리소스를 가져올 수 있게 된다.
7. 어셈블리 참조
8. 어셈블리 지연 서명
공유 어셈블리는 여러 애플리케이션에서 함께 사용될 수 있으므로, 각 어셈블리를 고유하게 구별할 방법이 필요하다. 이를 위해 강력한 이름(Strong Name)을 사용한다. 강력한 이름은 공개 키 토큰, 문화(Culture), 버전(Version), 그리고 PE 파일 이름으로 이루어진다.
개발 과정에서 공유 어셈블리를 사용해야 할 경우, 완전한 서명 절차 대신 지연 서명(Delay Signing) 방식을 활용할 수 있다. 지연 서명은 개발 단계에서는 공개 키만 생성하여 어셈블리에 포함시키는 방식이며, 이때 개인 키는 생성하지 않는다. 실제 개인 키를 이용한 최종 서명은 어셈블리를 사용자에게 배포하는 시점에 이루어진다. 이 방식을 통해 개발 중에는 서명 과정을 간소화하고, 배포 시점에는 강력한 이름으로 보안을 확보할 수 있다.
9. 어셈블리의 언어
어셈블리는 중간 언어인 CIL(Common Intermediate Language) 코드로 구성된다. .NET 프레임워크는 내부적으로 이 CIL(바이트코드)을 실행 시점에 각 플랫폼에 맞는 네이티브 어셈블리 코드로 변환한다.
Windows 환경의 .NET 프레임워크 구현에서 어셈블리는 PE 파일 형식을 따른다. 어셈블리에는 두 가지 종류가 있다:
- '''프로세스 어셈블리''': 실행 가능한 파일(EXE)
- '''라이브러리 어셈블리''': 동적 연결 라이브러리(DLL)
과거 CLR 1.1 버전에서는 클래스를 외부에 공개하는 것이 라이브러리 어셈블리에서만 가능했지만, CLR 2.0부터는 이러한 제한이 완화되었다. .NET은 파일의 확장자가 아니라 내부에 설정된 플래그를 통해 어셈블리가 프로세스인지 라이브러리인지 구분한다. 따라서 라이브러리 어셈블리가 .exe 확장자를 가질 수도 있다.
어셈블리에 포함된 코드는 컴파일되어 CIL 형태로 존재하며, 이는 VES(Virtual Execution System)에 의해 실행된다.
하나의 어셈블리는 자신을 설명하는 정보인 '''매니페스트'''(Manifest)와 실제 실행 코드, 리소스 등을 포함하는 하나 이상의 파일로 구성될 수 있다. 코드가 포함된 파일을 모듈(Module)이라고 부르며, 어셈블리는 하나 이상의 모듈을 가질 수 있다. 이론적으로는 다양한 프로그래밍 언어를 사용하여 각기 다른 모듈을 작성하고 이를 하나의 어셈블리로 묶는 것이 가능하다. 하지만 Visual Studio와 같은 일반적인 개발 도구는 보통 하나의 모듈만을 포함하는 어셈블리를 생성한다.
9. 1. CIL 코드 예제
어셈블리는 중간 언어인 CIL 코드로 빌드된다. 프레임워크는 내부적으로 CIL(바이트코드)을 네이티브 어셈블리 코드로 변환한다. "Hello World"를 출력하는 프로그램이 있다면, 해당 메서드에 대한 CIL 코드는 다음과 같다..method private hidebysig static void Main(string[] args) cil managed {
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 11 (0xb)
.maxstack 1
IL_0000: ldstr "Hello World"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method Class1::Main
위 CIL 코드는 문자열 "Hello World"를 스택으로 적재(
ldstr
)한 뒤, 콘솔에 문자열을 출력하는 System.Console::WriteLine
함수를 호출(call
)하고 메서드를 종료(ret
)하는 과정을 보여준다.10. Fusion
윈도우 파일 시스템은 어셈블리의 버전이나 문화 정보를 인식할 수 없어, 동일한 이름에 버전만 다른 어셈블리를 하나의 폴더에 저장할 수 없다.
퓨전(Fusion)은 이러한 파일 시스템의 한계를 극복하고 버전이나 문화 같은 정보를 파일 시스템상의 이름으로 사용할 수 있도록 하는 기술이다.
퓨전은 어셈블리를 찾을 때 정해진 순서에 따라 검색을 수행한다.
# 어셈블리가 강력한 이름을 가지고 있다면 먼저 전역 어셈블리 캐시를 검색한다.
# 다음으로 애플리케이션 설정 파일의 리다이렉트 정보를 확인한다. 어셈블리가 강력한 이름이라면 다른 버전을 로드하도록 지정하거나, 로컬 파일 시스템 또는 웹 서버상의 어셈블리를 절대 경로로 지정할 수 있다. 어셈블리가 강력한 이름이 아니라면 애플리케이션 폴더 아래의 하위 폴더를 검색 경로로 지정할 수 있다.
# 다음으로 애플리케이션 폴더 내에서 .exe나 .dll 확장자를 가진 어셈블리를 검색한다.
# 마지막으로 어셈블리의 짧은 이름과 같은 이름을 가진 하위 폴더 내에 있는 .exe 및 .dll 파일을 검색한다.
만약 이 과정에서도 어셈블리를 찾지 못하면, 퓨전은 예외를 발생시키고 어셈블리 이름이나 검색 경로 등의 정보를 저장한다. 이 정보는 퓨전 로그 뷰어(fuslogvw)를 통해 확인할 수 있다.
참조
[1]
웹사이트
Giving a .NET Assembly a Strong Name
https://web.archive.[...]
2007-03-29
[2]
웹사이트
.NET Assembly Versioning Lifecycle
http://philippetruch[...]
2008-08-12
[3]
웹사이트
DLR Namespace Change Fire Drill
https://web.archive.[...]
2008-09-17
[4]
웹사이트
コンポーネントのバージョン管理
https://msdn.microso[...]
[5]
웹인용
.NET Assembly Versioning Lifecycle
http://philippetruch[...]
2008-08-12
[6]
웹인용
DLR Namespace Change Fire Drill
http://devhawk.net/2[...]
2008-09-17
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com