애플리케이션 바이너리 인터페이스
애플리케이션 바이너리 인터페이스 (Application Binary Interface)
D 사양의 이 장에서는 ABI(Application Binary Interface, 응용 프로그램 바이너리 인터페이스) 를 다뤄요. ABI는 컴파일된 바이너리들이 어떤 규칙으로 메모리와 바이트를 배치하고, 함수를 호출하고, 심볼을 부르는지 그 '하위 수준의 계약'을 정의하는 거죠. 덕분에 서로 다른 D 구현으로 만든 라이브러리라도 한데 묶어 쓸 수 있어요.
본문
D ABI에 부합하는 D 구현이라면, 다른 구현으로 빌드한 D 바이너리와 상호 운용(interoperate)할 수 있는 라이브러리나 DLL 등을 만들어 낼 수 있어요.
C ABI
이 사양에서 말하는 C ABI란 대상 시스템의 C 응용 프로그램 바이너리 인터페이스를 뜻해요. C 코드와 D 코드는 자유롭게 링크될 수 있어야 하고, 특히 D 코드는 C ABI 런타임 라이브러리 전체에 접근할 수 있어야 해요.
엔디언 (Endianness)
데이터를 배치할 때의 엔디언(바이트 순서) 은 대상 머신의 엔디언을 따르게 돼요. Intel x86 CPU는 리틀 엔디언이라, 0x0A0B0C0D 값은 메모리에 0D 0C 0B 0A로 저장돼요.
기본 타입 (Basic Types)
| 타입 | 설명 |
|---|---|
| bool | 8비트 바이트. 값은 false가 0, true가 1 |
| byte | 8비트 부호 있는 값 |
| ubyte | 8비트 부호 없는 값 |
| short | 16비트 부호 있는 값 |
| ushort | 16비트 부호 없는 값 |
| int | 32비트 부호 있는 값 |
| uint | 32비트 부호 없는 값 |
| long | 64비트 부호 있는 값 |
| ulong | 64비트 부호 없는 값 |
| cent | 128비트 부호 있는 값 |
| ucent | 128비트 부호 없는 값 |
| float | 32비트 IEEE 754 부동소수점 값 |
| double | 64비트 IEEE 754 부동소수점 값 |
| real | 구현 정의 부동소수점 값. x86에서는 80비트 IEEE 754 확장 실수(extended real) |
| char | 8비트 부호 없는 값 |
| wchar | 16비트 부호 없는 값 |
| dchar | 32비트 부호 없는 값 |
델리게이트 (Delegates)
델리게이트는 두 부분으로 이루어진 fat pointer예요.
| 델리게이트 레이아웃 | 오프셋 | 프로퍼티 | 내용 |
|---|---|---|---|
| 0 | .ptr |
컨텍스트 포인터 | ptrsize |
| .funcptr | 함수를 가리키는 포인터 | ptrsize |
컨텍스트 포인터는 클래스의 this 참조, 구조체의 this 포인터, 클로저(중첩 함수)를 가리키는 포인터, 또는 바깥 함수(중첩 함수)의 스택 프레임을 가리키는 포인터가 될 수 있어요.
구조체와 공용체 (Structs and Unions)
구조체와 공용체는 대상의 C ABI 구조체 레이아웃을 따르는데, 몇 가지 예외가 있어요.
- 필드가 없는
extern(D)구조체나 공용체는 크기가 0이 아니라 1이에요. (Struct Layout 참고) - 공용체는 구조체처럼 이름 맹글링돼요.
- 이 문서의 나머지 부분에서는 공용체를 구조체와 외형적으로 동일하게 취급해요.
클래스 (Classes)
객체는 다음으로 이루어져요.
| 클래스 객체 레이아웃 | 크기 | 프로퍼티 | 내용 |
|---|---|---|---|
| ptrsize | .__vptr |
vtable을 가리키는 포인터 | |
| ptrsize | .__monitor |
synchronized 블록용 뮤텍스 |
|
| ... | 가장 파생된 것부터 가장 덜 파생된 것까지, super의 비정적 필드와 super의 인터페이스 vptr들 | ||
| ptrsize | 이 클래스가 구현하는 인터페이스들의 vptr을 왼쪽에서 오른쪽 순서로 | ||
| ... | 명명된 필드(non-static fields) |
vtable은 다음으로 이루어져요.
| 가상 함수 포인터 테이블 레이아웃 | 크기 | 내용 |
|---|---|---|
| ptrsize | TypeInfo 인스턴스를 가리키는 포인터 |
|
| ptrsize | 가상 멤버 함수를 가리키는 포인터들 ... |
클래스 객체를 인터페이스로 캐스팅하는 것은, 객체의 베이스 주소에 해당 인터페이스 vptr의 오프셋을 더하는 일이에요. 인터페이스 포인터를 다시 원래 클래스 타입으로 캐스팅할 때는 vtbl[0]의 object.Interface 항목에서 빼야 할 올바른 오프셋을 구해요. 어저스터 썽크(adjustor thunk) 를 만들어 그 포인터를 vtbl[]의 메서드 항목에 저장해서, 구현 메서드에 대응하는 객체 인스턴스의 시작점으로 this 포인터를 맞춰줘요.
어저스터 썽크는 다음과 같은 모양이에요.
ADD EAX,offset
JMP method
인터페이스의 상속 그래프에서 가장 왼쪽에 있는 쪽은 vptr을 공유해요. 이게 단일 상속 모델이죠. 상속 그래프가 (다중 상속 때문에) 갈라질 때마다 새로운 vptr이 만들어져 클래스 인스턴스에 저장돼요. 가상 메서드가 오버라이드될 때마다 갱신된 메서드 포인터를 담은 새 vtbl[]이 만들어져야 해요.
클래스 정의:
class XXXX
{
....
};
는 다음을 생성해요.
ClassXXXX라는Class인스턴스.- 모든 정적 멤버를 정의하는
StaticClassXXXX라는 타입. - 정적 멤버들을 위한
StaticXXXX라는StaticClassXXXX인스턴스.
인터페이스 (Interfaces)
인터페이스는 vtbl[]을 가리키는 포인터를 가리키는 포인터예요. vtbl[0] 항목은 대응하는 object.Interface 클래스 인스턴스를 가리키는 포인터고, 나머지 vtbl[1..$] 항목은 그 인터페이스가 구현하는 가상 함수들을 선언된 순서대로 가리키는 포인터들이에요.
COM 인터페이스는 일반 인터페이스와 달라요. vtbl[0]에 object.Interface 항목이 없고, vtbl[0..$] 항목이 전부 선언된 순서대로의 가상 함수 포인터예요. 이건 Windows에서 쓰는 COM 객체 레이아웃과 일치해요.
C++ 인터페이스는 일반 인터페이스와 달라요. 대상 머신에서 단일 상속을 쓰는 C++ 클래스의 레이아웃과 일치해요.
배열 (Arrays)
동적 배열은 다음으로 이루어져요.
| 동적 배열 레이아웃 | 오프셋 | 프로퍼티 | 내용 |
|---|---|---|---|
| 0 | .length |
배열 차원 | size_t |
| .ptr | 배열 데이터를 가리키는 포인터 |
동적 배열은 이렇게 선언돼요.
type[] array;
반면 정적 배열은 이렇게 선언돼요.
type[dimension] array;
따라서 정적 배열은 항상 차원을 타입의 일부로 정적으로 갖고 있어서, C에서처럼 구현돼요. 정적 배열과 동적 배열은 서로 쉽게 변환할 수 있어요.
연관 배열 (Associative Arrays)
연관 배열은 불투명한(oepaque) 구현 정의 타입을 가리키는 포인터로 이루어져요.
현재 구현은 rt/aaA.d에 담겨 있고 정의돼 있어요.
참조 타입 (Reference Types)
D에는 참조 타입이 있지만, 그것은 암시적이에요. 예를 들어 클래스는 항상 참조로 다뤄져서, 클래스 인스턴스는 절대 스택에 놓이거나 함수 매개변수로 전달될 수 없어요.
이름 맹글링 (Name Mangling)
D는 식별자에 스코프와 타입 정보를 섞어 넣어 타입 안전한 링킹을 이룩해요.
MangledName:
_D QualifiedName Type
_D QualifiedName Z // Internal
위의 Type은 변수의 타입이거나 함수의 반환 타입이에요. 이것은 결코 TypeFunction이 될 수 없는데, TypeFunction은 함수 포인터나 델리게이트를 통해서만 값에 묶일 수 있기 때문이에요.
QualifiedName:
SymbolFunctionName
SymbolFunctionName QualifiedName
SymbolFunctionName:
SymbolName
SymbolName TypeFunctionNoReturn
SymbolName M TypeModifiersopt TypeFunctionNoReturn
M은 그 심볼이 this 포인터를 필요로 하는 함수라는 뜻이에요. 클래스나 구조체의 필드는 M 없이 맹글링돼요. M이 한정자 scope가 붙은 매개변수와 혼동되지 않도록, 뒤따르는 타입이 TypeFunction인지 확인해야 해요.
SymbolName:
LName
TemplateInstanceName
IdentifierBackRef
0 // anonymous symbols
템플릿 인스턴스 이름은 매개변수의 타입과 값을 그 안에 인코딩해요.
TemplateInstanceName:
TemplateID LName TemplateArgs Z
TemplateID:
__T
__U // for symbols declared inside template constraint
TemplateArgs:
TemplateArg
TemplateArg TemplateArgs
TemplateArg:
TemplateArgX
H TemplateArgX
템플릿 인자가 전문화된(specialized) 템플릿 매개변수와 일치하면 그 인자는 접두사 H를 붙여 맹글링돼요.
TemplateArgX:
T Type
V Type Value
S QualifiedName
X Number ExternallyMangledName
ExternallyMangledName은 현재 플랫폼이 허용하는 임의의 문자 시퀀스일 수 있어요. 예를 들어 C++ 링키지가 있는 함수나 pragma(mangle,...)으로 주석이 달린 함수가 생성하는 것들처럼요.
Values:
Value
Value Values
Value:
n
i Number
N Number
e HexFloat
c HexFloat c HexFloat
CharWidth Number _ HexDigits
A Number Values
S Number Values
f MangledName
HexFloat:
NAN
INF
NINF
N HexDigits P Exponent
HexDigits P Exponent
Exponent:
N Number
Number
HexDigits:
HexDigit
HexDigit HexDigits
HexDigit:
Digit
A
B
C
D
E
F
CharWidth:
a
w
d
- n — null 인자를 뜻해요.
- i Number — 양수 숫자 리터럴(문자 리터럴 포함)을 뜻해요.
- N Number — 음수 숫자 리터럴을 뜻해요.
- e HexFloat — real 및 imaginary 부동소수점 리터럴을 뜻해요.
- c HexFloat c HexFloat — complex 부동소수점 리터럴을 뜻해요.
- CharWidth Number _ HexDigits —
CharWidth는 문자가 1바이트(a), 2바이트(w), 4바이트(d) 중 어느 크기인지 나타내요.Number는 문자열의 문자 수,HexDigits는 문자열의 16진 데이터예요. - A Number Values — 배열 또는 연관 배열 리터럴이에요.
Number는 배열의 길이예요. 일반 배열에서는Value가Number번 반복되고, 연관 배열에서는2 * Number번 반복돼요. - S Number Values — 구조체 리터럴이에요.
Value가Number번 반복돼요.
Name:
Namestart
Namestart Namechars
Namestart:
_
Alpha
Namechar:
Namestart
Digit
Namechars:
Namechar
Namechar Namechars
Name은 표준 D 식별자예요.
LName:
Number Name
Number __S Number // function-local parent symbols
Number:
Digit
Digit Number
Digit:
0
1
2
3
4
5
6
7
8
9
LName은 Name의 문자 수를 알려주는 Number가 앞에 붙은 이름이에요.
역참조 (Back references)
이전에 맹글링된 심볼에 이미 방출된 LName이나 기본이 아닌 타입(non-basic Type, 즉 고정된 한두 글자 시퀀스로 인코딩되지 않는 타입)은 다시 방출되지 않아요. 대신 맹글링된 심볼 이름에서 원래 발생 위치의 상대 위치를 인코딩하는 특별한 시퀀스로 참조돼요.
역참조의 숫자는 26진수(base-26) 로 인코딩돼요. 높은 자릿수는 대문자 A–Z를, 마지막 자릿수는 소문자 a–z를 사용해요.
TypeBackRef:
Q NumberBackRef
IdentifierBackRef:
Q NumberBackRef
NumberBackRef:
lower-case-letter
upper-case-letter NumberBackRef
역참조의 종류를 구분하려면 역참조된 문자를 살펴봐야 해요. 식별자 역참조는 항상 숫자 0부터 9를 가리키는 반면, 타입 역참조는 항상 문자를 가리켜요.
타입 맹글링 (Type Mangling)
타입은 단순한 선형 방식으로 맹글링돼요.
Type:
TypeModifiersopt TypeX
TypeBackRef
TypeX:
TypeArray
TypeStaticArray
TypeAssocArray
TypePointer
TypeFunction
TypeIdent
TypeClass
TypeStruct
TypeEnum
TypeTypedef
TypeDelegate
TypeVoid
TypeByte
TypeUbyte
TypeShort
TypeUshort
TypeInt
TypeUint
TypeLong
TypeUlong
TypeCent
TypeUcent
TypeFloat
TypeDouble
TypeReal
TypeIfloat
TypeIdouble
TypeIreal
TypeCfloat
TypeCdouble
TypeCreal
TypeBool
TypeChar
TypeWchar
TypeDchar
TypeNoreturn
TypeNull
TypeTuple
TypeVector
TypeModifiers:
Const
Wild
Wild Const
Shared
Shared Const
Shared Wild
Shared Wild Const
Immutable
Shared:
O
Const:
x
Immutable:
y
Wild:
Ng
TypeArray:
A Type
TypeStaticArray:
G Number Type
TypeAssocArray:
H Type Type
TypePointer:
P Type
TypeVector:
Nh Type
TypeFunction:
TypeFunctionNoReturn Type
TypeFunctionNoReturn:
CallConvention FuncAttrsopt Parametersopt ParamClose
CallConvention:
F // D
U // C
W // Windows
R // C++
Y // Objective-C
FuncAttrs:
FuncAttr
FuncAttr FuncAttrs
FuncAttr:
FuncAttrPure
FuncAttrNothrow
FuncAttrRef
FuncAttrProperty
FuncAttrNogc
FuncAttrReturn
FuncAttrScope
FuncAttrTrusted
FuncAttrSafe
FuncAttrLive
함수 속성은 위에 나열된 순서대로 방출돼요. 단 return과 scope는 예외예요. return scope 매개변수일 때는 return이 scope 앞에 오고, scope return ref 매개변수일 때는 scope 뒤에 와요.
FuncAttrPure:
Na
FuncAttrNogc:
Ni
FuncAttrNothrow:
Nb
FuncAttrProperty:
Nd
FuncAttrRef:
Nc
FuncAttrReturn:
Nj
FuncAttrScope:
Nl
FuncAttrTrusted:
Ne
FuncAttrSafe:
Nf
FuncAttrLive:
Nm
Parameters:
Parameter
Parameter Parameters
Parameter:
Parameter2
M Parameter2 // scope
Nk Parameter2 // return
Parameter2:
Type
I Type // in
J Type // out
K Type // ref
L Type // lazy
ParamClose:
X // variadic T t...) style
Y // variadic T t,...) style
Z // not variadic
TypeIdent:
I QualifiedName
TypeClass:
C QualifiedName
TypeStruct:
S QualifiedName
TypeEnum:
E QualifiedName
TypeTypedef:
T QualifiedName
TypeDelegate:
D TypeModifiersopt TypeFunction
TypeVoid:
v
TypeByte:
g
TypeUbyte:
h
TypeShort:
s
TypeUshort:
t
TypeInt:
i
TypeUint:
k
TypeLong:
l
TypeUlong:
m
TypeCent:
zi
TypeUcent:
zk
TypeFloat:
f
TypeDouble:
d
TypeReal:
e
TypeIfloat:
o
TypeIdouble:
p
TypeIreal:
j
TypeCfloat:
q
TypeCdouble:
r
TypeCreal:
c
TypeBool:
b
TypeChar:
a
TypeWchar:
u
TypeDchar:
w
TypeNoreturn:
Nn
TypeNull:
n
TypeTuple:
B Parameters Z
함수 호출 규약 (Function Calling Conventions)
extern(C)와 extern(D) 호출 규약은 호스트 시스템에서 지원되는 C 컴파일러가 쓰는 C 호출 규약과 일치해요. 단, Windows x86용 extern(D) 호출 규약은 여기서 따로 설명해요.
레지스터 규약 (Register Conventions)
- EAX, ECX, EDX는 스크래치 레지스터라 함수가 파괴해도 돼요.
- EBX, ESI, EDI, EBP는 함수 호출 전반에 걸쳐 보존돼야 해요.
- EFLAGS는 함수 호출 사이에 파괴된다고 가정해요. 단 방향 플래그(direction flag)는 앞으로(forward)여야 해요.
- 함수를 호출할 때 FPU 스택은 비어 있어야 해요.
- FPU 컨트롤 워드는 함수 호출 사이에 보존돼야 해요.
- 부동소수점 반환 값은 FPU 스택에 반환돼요. 사용하지 않더라도 호출자가 정리해야 해요.
반환 값 (Return Value)
bool,byte,ubyte,short,ushort,int,uint, 포인터,Object, 인터페이스 타입은 EAX로 반환돼요.long과ulong은 EDX,EAX로 반환되는데, EDX가 상위 절반을 받아요.float,double,real,ifloat,idouble,ireal은 ST0으로 반환돼요.cfloat,cdouble,creal은 ST1,ST0으로 반환되는데, ST1이 실수부, ST0이 허수부예요.- 동적 배열은 포인터를 EDX에, 길이를 EAX에 담아 반환해요.
- 연관 배열은 EAX로 반환돼요.
- 참조는 포인터로 EAX에 반환돼요.
- 델리게이트는 함수 포인터를 EDX에, 컨텍스트 포인터를 EAX에 담아 반환해요.
- 1, 2, 4바이트 구조체와 정적 배열은 EAX로 반환돼요.
- 8바이트 구조체와 정적 배열은 EDX,EAX로 반환되는데, EDX가 상위 절반을 받아요.
- 그 외 크기의 구조체와 정적 배열은 함수에 인자로 전달되는 숨은 포인터를 통해 반환 값이 저장돼요.
- 생성자는
this포인터를 EAX로 반환해요.
매개변수 (Parameters)
가변 인자가 없는 함수의 매개변수:
foo(a1, a2, ..., an);
는 다음과 같이 전달돼요.
| a1 | | a2 | | ... | | an | | hidden | | this |
여기서 hidden은 구조체 값을 반환하기 위해 필요할 때, this는 멤버 함수의 this 포인터나 중첩 함수의 컨텍스트 포인터가 필요할 때 존재해요.
마지막 매개변수는 다음 조건이 모두 충족되면 스택에 밀어 넣는 대신 EAX로 전달돼요.
- EAX에 들어맞는 크기일 것.
- 3바이트 구조체가 아닐 것.
- 부동소수점 타입이 아닐 것.
매개변수는 항상 4바이트의 배수로, 위로 반올림해서 밀어 넣어요. 그래서 스택은 항상 4바이트 경계에 정렬돼 있어요. 가장 중요한 부분을 먼저 밀어 넣어요. out과 ref는 포인터로 전달되고, 정적 배열은 첫 번째 원소를 가리키는 포인터로 전달돼요. Windows에서 real은 10바이트 크기로, creal은 20바이트 크기로 밀어 넣어요. Linux에서 real은 12바이트 크기로, creal은 12바이트 두 개로 밀어 넣어요. 여분의 2바이트 패딩은 '가장 중요한' 위치를 차지해요.
호출 수신자(callee)가 스택을 정리해요.
가변 인자 함수의 매개변수:
void foo(int p1, int p2, int[] p3...)
foo(a1, a2, ..., an);
는 다음과 같이 전달돼요.
| p1 | | p2 | | a3 | | hidden | | this |
가변 인자 부분은 동적 배열로 변환되고, 나머지는 가변 인자가 없는 함수와 동일해요.
가변 인자 함수의 매개변수:
void foo(int p1, int p2, ...)
foo(a1, a2, a3, ..., an);
는 다음과 같이 전달돼요.
| an | | ... | | a3 | | a2 | | a1 | | _arguments | | hidden | | this |
호출자(caller)가 스택을 정리해야 해요. _argptr는 전달되지 않고, 호출 수신자가 계산해요.
예외 처리 (Exception Handling)
Windows 32비트
Microsoft Windows의 구조적 예외 처리(Structured Exception Handling) 규약을 따라요.
Linux, FreeBSD, OS X
DWARF(debugging with attributed record formats) 예외 처리 규약을 따라요.
Windows 64비트
정적 주소 범위/핸들러 테이블을 사용해요. MSVC x64 예외 처리 테이블과는 호환되지 않아요. EBP/RBP 스택 프레임 규약을 쓴다고 가정하고 스택을 걷어요. EH(예외 핸들러) 테이블이 있는 모든 함수는 EBP/RBP 규약을 사용해야 해요.
예외 핸들러가 있는 각 함수에 대해 EH 테이블 항목이 생성돼요.
| EH 테이블 항목 | 필드 | 설명 |
|---|---|---|
| void* | 함수 시작을 가리키는 포인터 | |
| DHandlerTable* | 대응하는 EH 데이터를 가리키는 포인터 | |
| uint | 함수의 바이트 크기 |
EH 테이블 항목은 다음 특별한 세그먼트에 배치되고, 링커가 이들을 이어 붙여요.
| EH 테이블 세그먼트 | 운영체제 | 세그먼트 이름 |
|---|---|---|
| Win32 | FI | |
| Win64 | ._deh$B |
|
| Linux | .deh_eh |
|
| FreeBSD | .deh_eh |
|
| OS X | __deh_eh, __DATA |
나머지 EH 데이터는 어디에 놓아도 되며, 불변(immutable)이에요.
| DHandlerTable | 필드 | 설명 |
|---|---|---|
| void* | 함수 시작을 가리키는 포인터 | |
| uint | EBP/RBP로부터의 ESP/RSP 오프셋 | |
| uint | 함수 시작부터 반환 코드까지의 오프셋 | |
| uint | DHandlerInfo[]의 항목 수 |
|
| DHandlerInfo[] | 핸들러 정보 배열 |
| DHandlerInfo | 필드 | 설명 |
|---|---|---|
| uint | 함수 주소부터 보호 구간(guarded section) 시작까지의 오프셋 | |
| uint | 보호 구간 끝의 오프셋 | |
| int | 이전 테이블 인덱스 | |
| uint | 0이 아니면 테이블 시작부터 DCatchInfo 데이터까지의 오프셋 |
|
| void* | null이 아니면 실행할 finally 코드를 가리키는 포인터 |
| DCatchInfo | 필드 | 설명 |
|---|---|---|
| uint | DCatchBlock[]의 항목 수 |
|
| DCatchBlock[] | catch 정보 배열 |
| DCatchBlock | 필드 | 설명 |
|---|---|---|
| ClassInfo | catch 타입 | |
| uint | EBP/RBP로부터 catch 변수까지의 오프셋 | |
| void* | catch 핸들러 코드 |
가비지 컬렉션 (Garbage Collection)
이에 대한 인터페이스는 Druntime의 core/gc/gcinterface.d에서 찾을 수 있어요.
ModuleInfo 인스턴스 (ModuleInfo Instance)
컴파일러는 모든 모듈에 대해 ModuleInfo 인스턴스를 생성해 객체 파일에 넣어요. ModuleInfo는 D 런타임 라이브러리에 유용한 모듈 정보를 담고 있어요.
- 모듈에 정적 생성자, 정적 소멸자, 공유 정적 생성자, 공유 정적 소멸자가 있는지 여부.
- 모듈의 단위 테스트를 실행하는 함수에 대한 참조와, 개별적으로 방출된
unittest블록들에 대한 메타데이터. - 다음 중 하나 이상을 가진 참조된 모듈들에 대한 배열: 정적 생성자 · 정적 소멸자 · 공유 정적 생성자 · 공유 정적 소멸자 · 단위 테스트 · 1..5 중 하나 이상을 가진 모듈의 전이적(transitive) 임포트 · 순서 독립 생성자(현재
-cov구현에 필요). 이 덕분에 런타임은 단위 테스트를 실행하고, 모듈 생성자를 깊이 우선(depth-first) 순서로, 모듈 소멸자를 역순으로 실행할 수 있어요. - 모듈에 정의된 각 클래스에 대한
ClassInfo참조 배열. 참고: 이 기능은 제거될 수 있어요.
ModuleInfo는 Druntime의 object.d에 정의돼 있는데, 플래그 값과 필드 레이아웃 모두에서 컴파일러의 출력과 일치해야 해요.
선택적인 개별 unittest 메타데이터는 플래그 0x2000을 사용해요. NUL로 끝나는 모듈 이름 뒤, 포인터 정렬로 패딩된 곳에 위치하며, 불변 UnitTestInfo 레코드들을 가리키는 슬라이스(길이, 포인터)로 이루어져요. 각 레코드는 자신의 바이트 크기, void function() 포인터, 컴파일러가 생성한 테스트 이름을 담은 문자열(길이, 포인터)로 시작해요. 이 필드들은 고정된 오프셋을 가져요. 앞으로 필드가 추가될 수 있고, 접근 전에 방출된 바이트 크기와 대조해 확인해야 해요. 기존 ModuleInfo 필드들은 자기 오프셋을 유지해요.
-betterC로 컴파일된 모듈은 D 런타임 라이브러리 없이 동작해야 하므로 ModuleInfo 인스턴스가 생성되지 않아요. 마찬가지로 ImportC 모듈은 ModuleInfo를 생성하지 않아요.
모듈 초기화와 종료 (Module Initialization and Termination)
모듈의 모든 정적 생성자는 하나의 함수로 모아지고, 그 함수를 가리키는 포인터가 해당 모듈 ModuleInfo 인스턴스의 ctor 멤버에 들어가요.
모듈의 모든 정적 소멸자는 하나의 함수로 모아지고, 그 함수를 가리키는 포인터가 해당 모듈 ModuleInfo 인스턴스의 dtor 멤버에 들어가요.
단위 테스트 (Unit Testing)
모듈의 모든 단위 테스트는 하나의 함수로 모아지고, 그 함수를 가리키는 포인터가 해당 모듈 ModuleInfo 인스턴스의 unitTest 멤버에 들어가요.
런타임 헬퍼 함수 (Runtime Helper Functions)
이들은 Druntime의 rt/에서 찾을 수 있어요.
심볼릭 디버깅 (Symbolic Debugging)
D에는 기존 C나 C++ 디버거에 표현되지 않는 타입들이 있어요. 바로 동적 배열, 연관 배열, 델리게이트예요. 이 타입들을 구조체로 표현하면 문제가 생기는데, 구조체의 함수 호출 규약이 이런 타입들과 다른 경우가 많아서 C/C++ 디버거가 잘못 해석하게 되거든요. 이런 디버거들을 위해서는, 그 타입의 호출 규약과 일치하는 C 타입으로 표현돼요.
| C 디버거용 타입 | D 타입 | C 표현 |
|---|---|---|
| dynamic array | unsigned long long | |
| associative array | void* | |
| delegate | long long | |
| dchar | unsigned long |
새 타입을 받아들이도록 수정할 수 있는 디버거라면 다음 확장들이 그 타입을 온전히 지원하도록 도와줘요.
Codeview 디버거 확장 (Codeview Debugger Extensions)
D의 dchar 타입은 특별한 프리미티브 타입 0x78로 표현돼요.
D는 Codeview OEM 제네릭 타입 레코드 LF_OEM(0x0015)을 사용해요. 형식은 다음과 같아요.
| Codeview OEM D 확장 | 필드 | 크기 2 | 2 | 2 | 2 | 2 | 2 |
|---|---|---|---|---|---|---|---|
| D 타입 | Leaf | Index | OEM 식별자 | recOEM | num | indices | type index |
| dynamic array | LF_OEM | OEM | 1 | 2 | @ index | @ element | |
| associative array | LF_OEM | OEM | 2 | 2 | @ key | @ element | |
| delegate | LF_OEM | OEM | 3 | 2 | @ this | @ function |
여기서:
| OEM | 0x42 |
|---|---|
| index | 배열 인덱스의 타입 인덱스 |
| key | 키의 타입 인덱스 |
| element | 배열 원소의 타입 인덱스 |
| this | 컨텍스트 포인터의 타입 인덱스 |
| function | 함수의 타입 인덱스 |
이 확장들은 obj2asm으로 pretty-print할 수 있고, Ddbg 디버거가 지원해요.
더 알아보기 (Learn more)
- D Language Reference — 전체 사양 본문: https://dlang.org/spec/spec.html
- ABI 챕터 원문: https://dlang.org/spec/abi.html
- 이름 맹글링의 실제 예시와 규칙은
dmd문서 및 Druntime 소스(rt/,core/gc/gcinterface.d,object.d)를 함께 보면 좋아요.