애플리케이션 바이너리 인터페이스

애플리케이션 바이너리 인터페이스 (Application Binary Interface)

D 사양의 이 장에서는 ABI(Application Binary Interface, 응용 프로그램 바이너리 인터페이스) 를 다뤄요. ABI는 컴파일된 바이너리들이 어떤 규칙으로 메모리와 바이트를 배치하고, 함수를 호출하고, 심볼을 부르는지 그 '하위 수준의 계약'을 정의하는 거죠. 덕분에 서로 다른 D 구현으로 만든 라이브러리라도 한데 묶어 쓸 수 있어요.

출처: https://dlang.org/spec/abi.html

본문

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 _ HexDigitsCharWidth는 문자가 1바이트(a), 2바이트(w), 4바이트(d) 중 어느 크기인지 나타내요. Number는 문자열의 문자 수, HexDigits는 문자열의 16진 데이터예요.
  • A Number Values — 배열 또는 연관 배열 리터럴이에요. Number는 배열의 길이예요. 일반 배열에서는 ValueNumber번 반복되고, 연관 배열에서는 2 * Number번 반복돼요.
  • S Number Values — 구조체 리터럴이에요. ValueNumber번 반복돼요.
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

LNameName의 문자 수를 알려주는 Number가 앞에 붙은 이름이에요.

역참조 (Back references)

이전에 맹글링된 심볼에 이미 방출된 LName이나 기본이 아닌 타입(non-basic Type, 즉 고정된 한두 글자 시퀀스로 인코딩되지 않는 타입)은 다시 방출되지 않아요. 대신 맹글링된 심볼 이름에서 원래 발생 위치의 상대 위치를 인코딩하는 특별한 시퀀스로 참조돼요.

역참조의 숫자는 26진수(base-26) 로 인코딩돼요. 높은 자릿수는 대문자 AZ를, 마지막 자릿수는 소문자 az를 사용해요.

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

함수 속성은 위에 나열된 순서대로 방출돼요. 단 returnscope는 예외예요. return scope 매개변수일 때는 returnscope 앞에 오고, 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로 반환돼요.
  • longulong은 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바이트 경계에 정렬돼 있어요. 가장 중요한 부분을 먼저 밀어 넣어요. outref는 포인터로 전달되고, 정적 배열은 첫 번째 원소를 가리키는 포인터로 전달돼요. 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)