- DYLD 버그가 있다면 “DYLD USAGE:” 태그로 알려달라
- 새로운 용어: Launch Closure
- 원칙은 동일하다. 일을 적게 해라
- dylib를 적게 임베딩해라
- 클래서 선언/메소드 선언을 줄이라
- (정적) 초기화를 줄이라
- swift를 써라
- (정적)초기화가 없다 -> 모든 전역 객체는 첫 참조시에 초기화된다.
- swift가 크기가 더 작다.
- 새 도구
- Static Initializer Calls: 정적인 초기화가 일어나는 시점을 체크해준다.
- dyld의 역사
- 1.0(1996~2004)
- NeXTStep 3.3에 처음 도입
- POSIX의 dlopen()에 밀림
- 그래서 Unix 소프트웨어를 맥에서 쓰기 위해서는 래퍼가 필요했다. 하지만 매칭이 되지 않는 경우가 있었기 때문에 완벽하지 않았고, 느렸다.
- System들이 큰 c++라이브러리를 쓰기 시작하면서 성능상의 한계가 생겼다.
- macOS 10.0부터 prebinding을 도입: 미리 라이브러리가 로드될 주소를 계산해놓고, 로드에 성공하면 바이너리가 가진 주소값들을 미리 계산한 것으로 바꾸는 것
- 하지만 매 실행마다 바이너리 주소를 변경하는 것은 보안뿐 아니라 여러가지 이유로 좋지 않다.
- 2.0(2004~2007)
- Tiger부터 도입
- 완전히 재작성
- C++ 초기화 시맨틱을 올바르게 수정
- 네이티브로 dlopen/dlsym 지원 -> 기존 API는 deprecated
- 속도 중점적으로 작성
- 하지만 무결성 체크는 미흡했다. -> 보안 이슈가 남았다.
- dyld자체가 빨라져서 프리바인딩을 줄일 수 있었다. -> 애플리케이션을 고치지 않고, 시스템 라이브러리만 소프트웨어 업데이트 때마다 고쳐준다.
- 2.x(2007~2017)
- 더 다양한 아키텍쳐 지원
- 디바이스 다양화
- 보안 강화(Codesigning, ASLR, bounds checking)
- 성능 업데이트: prebinding이 shared cache로 완전히 대체
- 대부분의 system dylib를 대체하는 단일 파일
- 로드 속도를 높이기 위해 바이너리를 재정렬
- 사전링크가 된 바이너리들
- dyld와 Objc에서 사용되는 자료구조들이 모두 빌드되어 있다.
- macOS에서 로컬 빌드되서 모든 플랫폼으로 전달된다.
- 3.0(2017)
- dynamic Linking에 대해서 다시 생각해서 만들었다.
- 이후에는 dyld 2.x를 완전히 대체할 것이다.
- 성능 향상, 보안 , 안전성(테스트 가능성)
- 어떻게?
- 복잡한 연산은 다 뺐다. -> dyld의 대부분은 이제 일반적인 daemon이다.
- 나머지 부분도 최대한 작게 유지한다. -> 공격이 들어올만한 부분이 줄어든다.
- 런치 속도 증가 -> 실행되지도 않을 코드를 빼자.
- 로딩 과정 복습 및 3.0의 로딩과정
- 2.0의 과정
- mach-O 헤더 파싱
- 의존성 찾기
- mach-O 파일 매핑
- 심볼 검색
- 바인드 및 재배치
- (정적) 생성자 실행
- 3.0에서의 변경점
- 헤더 파싱과 의존성 검색, 심볼 검색 과정을 별도의 데몬에서 실행한다. -> 변조된 헤더와 범위 공격이 있을 수 있는 부분
- 의존성과 심볼은 런치 단계에서 변하지 않기 때문에 결과를 캐싱해놓는다.
- 그래서 3.0에서는?
- 별도의 데몬에서 mach-o 헤더 파싱, 의존성 검색, 심볼 검색을 수행한 후 해당 결과(Launch Closure)를 디스크에 저장한다.(mach-O parser/compiler)
- 이는 일반적인 데몬이기 때문에 테스팅이 가능하다.
- 프로그램을 실행할때는 이 클로저를 가져와서 읽고, 검증한다음에 mach-O를 매핑하고 바인딩&재배치하고 초기화한다.(in-process engine) -> dyld3의 본체
- mach-O 헤더를 파싱하거나 심볼 테이블에 접근할 필요성이 없다.
- 그리고 이 런치 클로저를 캐싱하기 위한 서비스가 별도로 존재한다.
- 대부분의 프로그램 실행은 이 캐시를 사용하고, mach-O parser/compiler를 돌리지 않는다.
- 런치 클로저는 mach-O에 비해 간단한 구조로 되어 있어 별도의 파싱이 필요하지 않다.
- compiler는 언제 도는가?
- 시스템 앱은 이미 캐시가 빌트인 된 상태로 있다.
- 서드파티 앱은 설치 과정에서 빌드된다. 업데이트가 일어나면 재빌드된다.
- macOS에서는 in-process 엔진이 필요하다고 판단하면 컴파일러를 돌릴 수 있다.
- dyld3 준비
- 2.x와 완전히 호환된다.
- 하지만 일부 API는 dyld3의 최적화를 방해할 것이고, 2.x대에 맞춘 최적화는 더이상 효과가 없을 것이다.
- 엄격한 링크 시맨틱
- 예전 바이너리를 위한 우회책은 있다.
- 하지맨 새로운 바이너리에서는 얄짤없다.
- __DATA 영역의 정렬되지 않은 포인터 문제
- global structure가 함수나 다른 global structure를 가리키고 있는 경우, 이 포인터는 실행전에 수정되어야 한다.
- 그리고 포인터는 시스템에 맞게 자연스럽게 정렬되어야 한다. -> 그런데 정렬되지 않은 포인터를 정렬하는 것은 복잡한 일이다.
- 여러 페이지에 걸쳐져 있는 경우에 일어날 수 있는 페이지 폴트 문제
- 멀티프로세서 상황에서 일어날 수 있는 원자성 문제
- 정적 링커가 이미 워닝을 보내고 있으니, 이걸 수정해라. -> 얼라인 강제로 수정하지 마라
- swift에서는 이러한 작업을 할 필요도 없고, 할 수도 없다.
- 키패스에는 약간 버그가 있긴한데, 고칠 것이다.
struct ListHead {
};
#pragma pack(1) // change default alignment
// #pragma pack(pop) // restore default alignment
struct List {
uint32_t count; // 4bytes @ 0x0
struct ListElement *head; // 8 bytes @ 0x4: MISALIGNED!!
} __attribute__((__packed, aligned(1))); // changes alignment for this struct
static struct ListElement sHead;
struct List gList = {0, &sHead}; // pointer not aligned at address 0x100001004 (_gList + 4 from ...)
- eager symbol resolution
- dyld2는 lazy symbol resolution을 수행함
- 심볼 룩업은 사전에 모두 실행하기에는 너무 무거운 작업이다.
- 실제 메소드가 아닌, 해당하는 메소드를 반환하는 dyld의 함수를 가리키는 포인터를 가지고 있다가 실행시 해당 메소드를 통해서 실제 메소드를 찾아냄 -> 두번째부터는 바로 찾아갈 수 있다.
- 심볼이 없으면 이 메소드를 처음으로 호출 할 때 크래시가 난다.
- dyld3는 eager symbol resolution을 수행한다.
- 모든 심볼 룩업 결과가 캐시되어서 빠르게 수행할 수 있다.
- 따라서 모든 심볼의 존재 여부를 파악할 수 있게 된다.
- 현재 SDK에서는 심볼이 없으면 처음 누락된 심볼을 호출할 때 크래시가 난다.
- 하지만 이후 SDK에서는 런치가 실패하게 된다.
- linker플래그에서 -bind_at_load를 주면, 현재도 시뮬레이팅할 수 있다. 하지만 느려지니까 릴리즈에서는 하지마라.
- dlopen()/dlsym()/dladdr()
- 문제가 있는 호출이지만, 필요할 수도 있다.
- dlsym()으로 찾아지는 심볼은 런타임에 발견되어야만 하기에, 최적화가 불가능하다.
- dlclose()
- 사실은 레퍼런스 카운팅을 하는 함수 -> 호출한다고 무조건 닫히는 게 아니다.
- 이는 하드웨어에 붙어서 사용되는 dylib 같은 게 하드웨어의 종료를 막는 경우가 있기 때문이다.
- dylib이 unload되지 않도록 하는 기능들
- Objective-C classes
- swift classes
- C __thread & C++ thread_local_variables
- macOS에서는 잘해주고 있지만, 애플 바깥에 가서는 문제의 소지가 있어서, 다른 플랫폼에서는 no-op으로 해줄 것을 고려하고 있다.
- all_image_infos
- 프로세스 안의 dylib 정보를 가져올 수 있는 인터페이스
- 메모리 안의 구조체
- 메모리 낭비의 원인 중 하나
- 이후 릴리즈에는 없앨 것
- 대체할 API를 준비중