ABI란 두개의 바이너리 프로그램 모듈 사이의 인터페이스를 말한다.
기계 수준, 이진값 수준에서 인터페이스를 뜻한다. 이진은 Binary로 0과 1을 뜻하는데 컴퓨터에서 가장 low-level을 나타낸다. ABI는 이렇게 바이너리 수준에서의 인터페이스르 뜻한다.
위 정의에서 말하는 모듈은 라이브러리 혹은 Operating System과 사용자에 의해서 실행되는 프로그램을 의미한다.
윈도우 환경에서 내 프로그램을 돌리는 경우를 생각해 볼 수 있다.
그런데 ABI가 호환되어야 프로그램을 돌릴 수 있다. 맥 환경에서 Window 응용 프로그램을 돌릴 수 없는 것이 ABI가 호환되지 않기 때문이다.
호환성이란 서로 교환한다는 뜻으로, 공산품에서 서로 다른 곳에서 만들었는데도 불구하고 부품을 서로 바꿔쓸 수 있는 경우를 가리킨다.
소스 호환성은 새로운 컴파일러가 이전 Swift 버전으로 작성된 코드를 컴파일 할 수 있는 것을 의미한다.
만약 소스 호환성이 없다면 프로젝트의 모든 소스코드와 패키지가 동일한 버전의 swift로 작성되어야 한다. 이는 version lock을 유발한다. 소스 호환성은 여러 Swift 버전에서 single code 기반을 유지하면서, 사용자가 새로운 버전의 Swift를 사용할 수 있게 해준다.
** Swift로 예시를 들었을 뿐, 특정 언어에 특화된 것은 아니다. 예를 들어, C 응용프로그램과 Pascal 응용프로그램은 컴파일 된 후 동일한 ABI를 사용할 수 있다.
Binary Framework = Swift 모듈 파일(framework API의 source-level의 정보를 제공) + 공유 라이브러리(런타임 시에 로드되는 컴파일되니 구현 정보를 제공)
바이너리 프레임워크 및 런타임 호환성은 여러 Swift 버전에서 작동하는 binary 형태의 framework를 배포할 수 있게 해준다.
binary entity들은 반드시 많은 low level의 세부사항들과 일치해야 한다.
Operating System과 application 사이에는 인터페이스가 존재한다. OS에서는 특정한 방식의 format을 갖추기를 기대할 것이다. 예를 들자면, binary의 first section에 특정한 메모리 offset 정보를 가지고 있는 ELF header를 이용하는 경우이다. 이렇게 정해진 format이 있기 때문에 Window App이 Linux machine에서 실행할 수 없는 것이다.
ABI는 API와 매우 유사하다. 소스 코드를 쓸 때, API를 통해서 라이브러리에 접근한다.
int main() {
printf("Hello World");
return 0;
}
printf
라는 함수 API를 통해서 표준입출력 라이브러리에 접근한 예시이다.
위 코드는 Window 환경과 Linux 환경에서 컴파일 한 후 실행하면 같은 결과가 나온다.
왜냐하면, Window 환경과 Linux 환경에서의 표준입출력 라이브러리에 구현된 printf
함수의 스펙이 같기 때문이다. 받는 인자가 같으며, 결과 또한 같다.
그렇다면 Window 환경에서 C 언어로 작성된 실행파일을 Linux 환경에서 실행할 수 있을까?
실행할 수 없다. 왜냐하면, 실행파일의 포멧이 다르기 때문이다.
어셈블리 언어의 경우 바이너리 값과 1대 1매칭이 된다. 같은 바이너리 값을 실행했을 때 같은 결과가 나타난다. 이는 어셈블리 수준에서 호환이 된다는 것을 의미한고, 곧 바이너리 수준에서 호환이 된다는 것을 의미한다.
API는 함수의 argument를 전달하는 순서를 정의한다면,
ABI는 이 arguement를 어떻게 전달할지에 대한 방법을 정의한다. (ex. register, stack 등)
API가 라이브러리의 일부 함수를 정의한다면,
ABI는 코드가 라이브러리 파일 안에 어떻게 저장될 것인지를 정의한다. 이를 통해서 라이브러리를 사용한 어떤 프로그램도 원하는 함수를 실행할 수 있게 된다.
API는 소스코드 레벨에서 호환이 된다면, ABI는 바이너리 수준(이진 수준, 기계 수준)에서 호환이 된다.
라이브러리 파일에서 함수를 찾을 때 일반적으로 함수는 이름으로 조회된다. C++에서는 함수 이름을 오버로드할 수 있으므로 이름만으로는 함수를 식별하기에 충분하지 않다. C++ 컴파일러에는 내부적으로 이를 처리하는 고유한 방법이 있는데, 이를 name mangling이라고 한다. ABI는 다른 언어나 컴파일러로 빌드된 프로그램이 필요한 것을 찾을 수 있도록 함수 이름을 인코딩하는 표준 방법을 정의할 수 있다. C++ 프로그램에서 extern "c"
를 사용하면 다른 소프트웨어에서 이해할 수 있는 이름을 기록하는 표준화된 방법을 사용하도록 컴파일러에 지시한다.
종종 ABI의 변경은 불가피하다. ABI 변경이 발생했을 때, 새로운 버전의 라이브러리로 re-compile하지 않는다면 프로그램은 라이브러리를 사용할 수 없게된다.
이러한 이유로 개발자는 ABI의 안전성을 계속해서 염두해 두어야 한다. ABI의 안전성을 유지한다는 것은 함수의 인터페이스(return type, number, types, order of arguements), 데이터 타입 정의, 데이터 구조 등을 변경하는 것을 의미하는 것이 아니다.
새로운 함수와 데이터 타입은 추가될 수 있지만, 존재하는 것은 반드시 동일하게 유지되어야 한다. 예를 들어서 만약 라이브러리가 32-bit Integer를 사용한다고 해보자. 이를 64-bit Integer로 변경하고자 한다면, 이전에 컴파일된 코드들은 라이브러리를 올바르게 사용할 수 없어진다. 데이터에 접근하는 동작이 컴파일 중에 메모리 주소나 offset을 변경할 수 있다. 또한, 만약 데이터 구조가 변경된다면 이러한 offset 변경은 예상치 않은 point를 가리키게 될 것이고, 예측할 수 없는 결과를 낳는다.
Reference
ABI를 이해하는데 도움이 되었습니다. 감사합니다.