https://developer.apple.com/wwdc22/110362
우리는 코드를 작성하면서 라이브러리나 프레임워크의 형태로 다른 사람의 코드도 사용한다.
이런 라이브러리들을 사용하려면 링커(Linker)가 필요하다.
Linking의 종류
Static Linking
- 앱을 빌드할 때 발생한다.
- 빌드 시간과 앱의 최종 크기에 영향을 미칠 수 있다.
Dynamic Linking
- 앱이 실행(런타임)될 때 발생한다.
- 앱이 lauch 될 때까지의 고객의 대기 시간에 영향을 줄 수 있다.
목차
1. What is static linking?
2. Recent ld64 improvements
3. Static linking best practics
4. What is dynamic linking?
5. Recent dyld improvements
6. Dynamic linking best practices
7. New tools
What is static linking?
- 프로그래밍 초기에는 프로그램들이 간단했고 소스 파일도 하나뿐이었다.
- 빌드도 쉬웠다.
- 단 하나의 소스 파일에서 컴파일러를 실행하면 executable 프로그램을 생성해줬다.
- 하지만 모든 소스코드가 있는 1개의 파일은 확장성이 없다.
- 여러 소스 파일을 가질 때 모든 함수를 다시 컴파일하지 않고 어떻게 컴파일 할 수 있을까?
- 이 문제를 해결하기 위해 컴파일러를 두 부분으로 나누었다.
- 첫 번째 부분은 소스 코드를 intermediate "relocatable object" (.o) 파일로 컴파일한다.
- 두 번째 부분은 relocatable .o 파일을 읽어서 executable 프로그램을 생성한다.
- 우리는 이 두 번째 부분의 정적 링커를 ld라고 부른다.
- 소프트웨어가 발전하면서 사람들은 .o 파일들을 주고 받았다.
- 하지만 이는 번거러웠고 누군가는 .o 파일들을 하나의 라이브러리로 묶으면 좋지 않을까 생각했다.
- 당시에 파일들을 묶는 표준 방법은 아카이브 도구 'ar'을 사용하는 것이었다.
- 백업과 배포에 사용되었다.
- 따라서 워크플로우는 위의 그림과 같아졌다.
- 여러개의 .o 파일들을 하나의 아카이브로 'ar'화 하고 아카이브 파일에서 직접 .o 파일들을 읽도록 링커가 향상되었다.
- 이것은 공통 코드를 공유하는데 매우 큰 진전이었다.
- 당시에는 이것을 단지 라이브러리 또는 아카이브라고 불렀고 현재는 Static Library라고 부른다.
하지만 문제가 있었는데 바로, 최종 프로그램이 무거워진다는 점이었다.
라이브러리의 수천 개의 함수가 프로그램에 복사되었기 때문이다.
단지 몇 개의 함수만 사용된다고 하더라도 전체에 복사가 발생했기 때문에 문제가 생겼었다.
이를 해결하기 위해 최적화가 추가되었다.
링커가 정적 라이브러리의 모든 .o 파일을 사용하는 대신 undefined symbol 문제가 해결되는 경우의 .o 파일만 정적 라이브러리에서 가져오는 것이다.
이것은 누군가 모든 C 표준 라이브러리 함수를 포함하는 거대한 하나의 libc.a를 구축할 수 있음을 의미했다.
모든 프로그램이 하나의 libc.a에 연결할 수 있지만 각 프로그램은 실제로 필요로 하는 libc의 일부만 가져온다.
이 모델은 현재도 존재한다.
하지만, 이런 정적 라이브러리의 selective loading은 분명하지 않고 많은 프로그래머들을 곤경에 빠뜨린다.
예를 들어, 위와 같은 예시 코드가 있다.
- main.c 에는 foo 함수를 호출하는 main 함수가 있다.
- foo.c 에는 bar를 호출하는 foo 함수가 있다.
- bar.c 에는 bar의 구현부가 있고 unused라는 또 다른 함수도 구현되어 있다.
- baz.c 에는 undef 함수를 호출하는 baz 함수가 있다.
- 이제 이들을 .o 파일로 컴파일하면 위와 같이 컴파일된다.
- foo, bar, undef에는 회색 박스가 없다는 것을 확인할 수 있다. 정의되지 않았기 때문이다. (undefined)
- 심볼을 사용했지만 정의는 해당 c 파일에서 하지 않았기 때문! (That is use of symbol not definition)
- bar.o와 baz.o를 정적 라이브러리 하나로 합친다고 해보자
- 그 다음 두개의 .o파일과 이 정적 라이브러리를 링크한다.
단계 별로 살펴보면 다음과 같다!
- 링커는 명령어 라인 순서대로 파일을 읽어나간다.
- 가장 먼저 찾는 것은 main.o이다.
- main.o를 로드하고 main에 대한 정의를 찾는다. (심볼 테이블에 보이는 main을 찾는 것!)
- main 안에 정의되지 않은 foo가 있다는 것도 찾는다.
- 링커는 이제 명령어 라인의 다음 파일 foo.o를 파싱한다.
- foo에 대한 정의를 추가한다. (foo의 구현부를 찾았기 때문에 더 이상 undefined foo가 아니다!!)
- foo.o의 로드는 정의되지 않은 새 심볼 bar 또한 추가한다.
- Command line의 모든 .o 파일이 로드되었기 때문에 링커는 정의하지 않은 심볼이 남아 있는지 확인한다.
- 이 경우 아직 bar가 정의되지 않았다.
- 따라서 링커는 명령어 라인의 라이브러리들을 살펴보면서 정의되지 않은 bar 심볼을 충족하는 라이브러리를 찾는다.
- 위 예시에서는 링커가 정적 라이브러리 bar.o 에서 bar 심볼이 정의되는 것을 찾는다.
- 링커는 이제 bar.o를 로드한다.
- 이 시점에서는 더 이상 정의되지 않은 심볼이 없다.
- 따라서 라이브러리 탐색을 중단한다.
- 이제 링커는 다음 단계로 이동한다.
- 프로그램에 포함될 모든 함수와 데이터에 주소를 할당한다.
- 그런 다음 모든 함수와 데이터를 output 파일에 복사한다.
- 모든 과정이 끝났다!!
- baz.o는 정적 라이브러리에 있지만 프로그램에 로드되지 않았다.
- 이 방식은 분명하진 않지만 정적 라이브러리의 핵심적인 측면이다.
- 모든 파일이 로드되지 않아서 프로그래머에게 혼동을 줄 수 있기 때문에 분명하지 않다고 표현한 것 같다.
Recent ld64 improvements
ld64는 애플의 최신 정적 링커이다.
- Xcode 14에서 애플의 정적 링커는 더 빨라졌다.
- 개발 시스템에서 코어를 더 잘 활용한다.
- 링커 작업을 병렬로 수행하도록 여러개의 코어를 사용할 수 있는 여러 영역을 찾았다.
- Input 파일의 내용을 Output 파일로 복사하고 LINKEDIT의 부분을 병렬로 구축한다.
- UUID 계산을 변경하고 코드 서명 해시를 병렬로 수행하는 것이 포함된다.
- 이외에도 여러 알고리즘을 개선했다.
Static linking best practices
위와 같은 순서로 Link 시간 개선을 위해 할 수 있는 것들을 살펴보자!
When to use static libraries
- 정적 라이브러리에 빌드되는 소스 파일에 열심히 작업해서 빌드 시간이 느려지는 경우이다.
- 파일이 컴파일 되고 나면 table of contents를 포함한 정적 라이브러리 전체를 다시 빌드해야 하기 때문이다.
- 단지 추가적인 I/O가 많은 것이다.
- 안정적인 코드에는 정적 라이브러리가 적합하다 (코드가 활발히 변경되지 않는 경우)
- 빌드 시간을 줄이기 위해서는 코드를 정적 라이브러리 밖으로 이동하는 것을 고려해야 한다.
- 아카이브에서 선택적 로딩을 하는데 이것의 단점은 링커가 느려진다는 점이다.
- 그 이유는 빌드를 재현 가능하도록 하면서 전통적인 정적 라이브러리의 의미를 따르기 위해서는 링커가 라이브러리를 고정된 직렬 순서로 처리해야 하기 때문이다. (앞서 Command Line을 순차적으로 읽으며 심볼을 찾던 과정을 말하는 듯!)
- 즉, ld의 병렬화 이점을 정적 라이브러리와 함께 사용할 수 없다는 뜻이다.
그렇지만 이런 historical behavior(selective loading를 말하는 듯!)가 별로 필요하지 않은 경우라면 링커 옵션을 사용해 빌드 속도를 높일 수 있다.
해당 링커 옵션을 'all load'라고 한다.
-all_load
- 모든 정적 라이브러리의 모든 .o 파일을 로드하도록 링크에게 지시한다.
- 이는 앱이 selectively loading 하지만 결국 모든 정적 라이브러리로부터 대부분의 콘텐츠를 로드하는 경우라면 유용하다.
- -all_load 의 사용은 링커가 모든 정적 라이브러리와 해당 콘텐츠를 병렬로 파싱할 수 있도록 한다.
- _all_load의 단점
- 앱이 영리한 트릭을 사용해 동일한 심볼을 구현하는 여러 정적 라이브러리가 있어서 명령어 라인의 정적 라이브러리 순서에 의존해 어떤 구현을 사용할지 정하는 방식이라면 이 옵션은 적합하지 않다.
- 링커는 모든 구현을 로드하기 때문에 일반 정적 링킹 모드에서 찾을 수 있는 심볼의 의미를 가져오리란 법이 없기 때문이다.
- 프로그램이 무거워질 수 있다.
- 'unused' 코드가 계속 추가되기 때문이다.
- 이를 보완하기 위해 -dead_strip 링커 옵션을 사용할 수 있다!
- 이 옵션으로 unreachable 코드와 데이터를 링커가 제거한다.
- dead stripping 알고리즘은 빠르고 Output 파일의 사이즈를 줄임으로써 자체적으로 크기를 절약하지만 -all_load와 -dead_strip 모두를 사용하려 한다면 이들 옵션에 유무에 따른 링커 시간을 측정해서 각자의 상황에서 유리한 선택인지를 확인해야 한다.
- 앱이 영리한 트릭을 사용해 동일한 심볼을 구현하는 여러 정적 라이브러리가 있어서 명령어 라인의 정적 라이브러리 순서에 의존해 어떤 구현을 사용할지 정하는 방식이라면 이 옵션은 적합하지 않다.
-no_exported_symbols
- 배경 설명
- 링커가 생성하는 LINKEDIT 세그먼트의 한 부분은 하나의 exports-trie로 접두사 트리이며 내보낸(exported) 심볼 이름, 주소, 및 플래그를 인코딩한다.
- 모든 dylib에는 exported symbols가 필요하지만 main 앱 바이너리는 일반적으로 exported symbols가 필요하지 않다.
- 즉, 일반적으로 메인 executable 파일에서는 심볼을 조회하지 않는다.
- 그런 경우 -no_exported_symbols를 사용하여 앱 타겟에 대해 LINKEDIT에서 trie 데이터 구조 생성을 건너뛰도록 할 수 있다.
- 링크 시간이 향상된다.
- 그러나 앱이 메인 executable 파일로 다시 연결되는 플러그인을 로드하거나 xctest 번들 실행을 위해 호스트 환경으로 앱과 xctest를 사용하면 앱에 모든 exports가 있어야 한다.
- 즉, 해당 구성에서는 -no_exported_symbols를 사용할 수 없다는 것이다.
- 사이즈가 큰 경우엔 exports trie의 억제가 합리적이다.
- 위 사진에 있는 dyld_info 명령어를 실행하여 내보낸 심볼의 수를 계산할 수 있다.
- 실제로 어떤 대형 앱에서 심볼 수를 확인했을 때 약 밴만개가 내보내졌다.
- 그래서 이 링커 옵션을 넣었을 때 2~3초의 링크 시간을 단축시킬 수 있었다.
+ LINKEDIT이 무엇인지 ChatGPT에게 물어봤다.
-no_deduplicate
- 몇년전 애플은 instructions은 같지만 이름이 다른 함수를 병합하기 위해서 링커에 새로운 pass를 추가했다.
- C++의 tamplate expansions으로 그러한 함수들을 많이 얻게 되었다.
- 그러나 이 알고리즘은 expensive하다.
- 링커는 중복을 찾기 위해 모든 함수의 명령을 반복적으로 해시해야 한다.
- 이러한 비용 문제 때문에 링커가 weak-def 심볼만 살펴보도록 알고리즘을 제한시켰다.
- C++ 컴파일러가 내보내는 인라인 되지 않은 것들이 제한됐다.
- de-dup 중복 제거는 사이즈 최적화이며 디버그 빌드는 사이즈가 아닌 속도가 빠른 빌드에 관한 것이다.
- 기본적으로 Xcode는 Debug Configurations를 위해 링커에 -no_deduplicate를 전달해 de-dup 최적화를 비활성화한다.
- clang 또한 no-dedup 옵션을 링커에 전달한다.
- 요약
- C++을 사용하고 사용자 정의 빌드가 있는 경우, 링크 시간 개선을 위해 -no_deduplicate를 추가해야 한다.
- 링커 옵션을 변경하고 싶다면 Xcode 빌드 세팅에서 Other Linker Flags를 변경하면 된다.
Static library를 사용할 때 경험할 수 있는 놀라운 내용들
- 정적 라이브러리에 소스 코드가 빌드되고 앱이 이 라이브러리에 링크되지만 해당 코드는 최종 앱에 포함되지 않을 수 있다.
- 어떤 함수에 'attribute used'를 추가하거나 Objective-C 카테고리가 있는 경우 링커가 Selective loading을 수행하기 때문에 링크 과정 중에 필요한 일부 심볼 또한 정의하지 않는다면 해당 객체 파일은 링커가 로드하지 않는다.
- Dead Stripping 또한 흥미롭다.
- 데드 스트리핑은 많은 정적 라이브러리 문제를 숨길 수 있다.
- 심볼의 누락이나 중복되는 심볼은 링커의 오류를 유발하지만 데드 스트리핑은 링커가 메인에서 시작해서 모든 코드와 데이터에 걸쳐 unreachable code로 판명되면 링커가 심볼 누락 오류를 억제하도록 만든다.
- 정적 라이브러리에 중복되는 심볼이 있는 경우 링커는 첫번째 것을 선택하고 오류를 내지 않는다.
- 데드 스트리핑은 많은 정적 라이브러리 문제를 숨길 수 있다.
- 정적 라이브러리가 여러 프레임워크에 통합되는 경우
- 각각의 프레임워크는 개별적으로는 잘 실행된다.
- 하지만, 어느 시점에서 일부 앱은 두 가지 프레임워크를 모두 사용하게 되고 동일한 심볼에 대한 여러 개의 정의로 인해 런타임 문제를 발생시킨다.
- 가장 흔한 경우는 Objective-C 런타임 경고로 동일한 클래스 이름의 여러 인스턴스에 대한 경고이다.
What is Dynamic library?
정적 라이브러리가 더 많이 생기면 위처럼 최종 프로그램의 크기가 계속 커지게 된다.
따라서 해당 프로그램 빌드에 드는 정적 링크 시간도 증가한다.
'ar'를 'ld'로 바꾸고 Output 라이브러리를 Executable binary로 바꾸었다.
이것이 90년대 동적 라이브러리의 시작이다.
동적 라이브러리를 약칭으로 'dylibs'라고 부르고 다른 플랫폼에서는 'DSO' 또는 'DLL'이라고도 한다.
핵심은 정적 링커가 동적 라이브러리의 링킹을 다르게 취급한다는 점이다.
- 최종 프로그램으로 코드를 복사하는 것 대신 링커가 일종의 약속을 기록한다.
- 동적 라이브러리에서 사용되는 심볼 및 런타임시 라이브러리 경로를 기록하는 것이다.
Dynamic Library의 장점
- 프로그램 파일 사이즈가 우리의 통제 하에 있게 된다.
- 우리가 작성한 코드와 런타임에 필요한 동적 라이브러리 목록만 포함된다.
- 프로그램의 정적 링크 시간은 이제 코드 크기에 비례하고 링크하는 dylib 수와는 무관해진다.
- 가상 메모리 시스템이 빛날 수 있다.
- 여러 프로세스에서 동일한 동적 라이브러리가 사용되는 경우 가상 메모리 시스템은 그 dylib를 사용하는 모든 프로세스에서 해당 dylib에 대한 동일한 물리적 RAM 페이지를 재사용한다.
Dynamic Library의 단점
- 앱 런치 속도가 느려진다.
- 앱 시작시 프로그램 파일 1개만 로드하는 것이 아니라 모든 dylib들이 로드되고 함께 연결돼야 한다.
- 동적 라이브러리 기반 프로그램은 Dirty Page가 더 많다.
- 정적 라이브러리인 경우 모든 정적 라이브러리의 모든 global들을 링커가 메인 실행 파일의 동일한 DATA Page에 다 같이 배치한다.
- 그러나 dylib을 사용하면 DATA 페이지가 각 라이브러리에 있다.
- 동적 링커가 필요하다.
Dynamic Linker
- dyld 동적 링커는 런타임에 라이브러리를 로드한다.
- Executable Binary는 Segment로 분할된다. (위의 초록색 박스 안에 있는 연두색 박스)
- 일반적으로 최소한 TEXT, DATA, LINKEDIT으로 분할된다.
- 세그먼트는 항상 OS 페이지 사이즈의 배수이다.
- 각 세그먼트는 다른 권한을 가진다.
- TEXT 세그먼트: execute 권한 (CPU가 페이지의 바이트를 기계 명령어로 처리 가능함을 의미)
- 런타임에 dyld는 위 사진처럼 각 세그먼트의 권한을 받아 실행 파일들을 메모리에 mmap() 해야 한다.
- 세그먼트들은 page sized이고 page aligned이기 때문에 가상 메모리 시스템이 프로그램이나 dylib 파일을 VM 범위의 백업 저장소로 설정하는 것이 간단하다.
- 즉, 해당 페이지에 메모리 엑세스가 있을 때까지 RAM 에 아무것도 로드되지 않으며, 이로 인해 페이지 폴트가 발생하고 이는 VM 시스템이 파일의 적절한 하위 범위를 읽고 그 콘텐츠로 필요한 RAM 페이지를 채우도록 만든다.
하지만, 매핑만으로는 충분하지 않다. 어떻게든 프로그램은 연결(wired up)되거나 dylib에 바인딩되어야 한다.
이를 위해 수정(fix ups)라는 개념이 있다.
- 다어어그램에서 프로그램에 포인터들이 설정된 것을 볼 수 있다.
- 프로그램이 사용하는 dylib 부분들을 향하고 있다.
- 좌측 초록색 박스는 mach-o 파일이다.
- TEXT는 immutable이다. 또한 코드 서명을 기반으로 하는 시스템 안에 있어야 한다.
- malcloc()을 호출하는 함수가 하나 있다면 어떻게 동작할까?
- _malloc의 상대주소는 프로그램이 빌드될 때 알 수 없다. (런타임에 알 수 있다.)
- malloc이 dylib에 있는 것을 보고 정적 링커가 call site를 반환한다.
- call site는 stub에 대한 호출이 된다. (동일한 TEXT 세그먼트의 링커에 의해서)
- 따라서 빌드 시에 상대 주소가 열려지고 이는 BL 명령어가 올바르게 형성될 수 있음을 의미한다.
- stub이 DATA로부터 포인터를 로드하고 해당 위치로 바로 점프한다는 점에서 도움이 된다.
- 런타임에서는 TEXT에 대한 변경이 필요 없다.
- dylib에 의해 DATA만 변경된다.
- dyld를 이해하는데 중요한 것은 dyld에 의한 모든 수정이 단지 DATA에 대한 dyld의 포인터 설정 뿐이라는 것이다.
dyld가 수행하는 수정(fix ups) 사항에 대해 더 자세히 알아보자!
- LINKEDIT 어디낙에 dyld가 수정 작업에 써야하는 정보가 있다.
- 수정에는 2가지 유형이 있다.
1. Rebase: dylib 또는 app 자체 내에서 가리키는 포인터를 갖는 경우
- ASLR이라는 보안 기능이 있는데 dyld가 임의의 주소에서 dylib를 로드하도록 만든다.
- 따라서 내부 포인터는 빌드 시에 설정할 수 없다.
- 이러한 포인터들은 시작 시에 dyld가 조정하거나 'Rebase'해야 한다.
- 디스크에서 이런 포인터들은 대상 주소를 포함한다.
- dyib가 주소 0에서 로드되는 경우LINKEDIT이 기로갷야 하는건 각 리베이스의 위치가 전부이다.
- 그러면 dyld는 dylib의 실제 로드 주소를 각 리베이스 위치에 추가해 올바르게 수정할 수 있다.
2. binds: symbolic references
- 타겟이 숫자가 아닌 Symbol name이다.
- malloc 함수에 대한 포인터를 예로 들 수 있다.
- 문장려 '_malloc'은 실제로 LINKEDIT에 저장되며 dyld는 해당 문자열을 사용해 libSystem.dylib의 exports trie에서 malloc의 실제 주소를 조회한다.
- 그 다음 dyld는 해당 값을 바인드가 지정한 위치에 저장한다.
chained fixups
fix ups를 인코딩하는 새로운 방법이다.
- LINKEDIT이 더 작아진다.
- 모든 수정 위치를 저장하는 대신 새 형식은 각 DATA 페이지의 첫 번째 수정 위치와 가져온 심볼 목록만을 저장하기 때문이다.
- 나머지 정보는 DATA 세그먼트 자체에 인코딩된다.
- 이 위치에 최종적으로 수정이 고정된다.
- 이 새로운 형식이 'chained fixups'란 이름을 얻게 된 것은 수정 위치가 서로 연결되어 있다는 점 때문이다.
- LINKEDIT은 첫번째 수정이 있었떤 위치만 알려주고 DATA의 64비트 포인터 위치에 있는 일부 비트들은 다음 수정 위치에 대한 오프셋을 포함한다.
- 그 안에 수정이 바인드인지 리베이스인지 알려주는 비트도 들어있다.
How dyld works
- dyld는 실행 파일에서 시작한다. (앱이라고 해보자!)
- mach-o를 파싱해서 종속 dylibs들을 찾는다.
- 해당 dylib들을 찾아 mmap()을 수행한다. 그 다음 이들 각각에 대해 재귀적으로 mach-o 구조를 파싱한다.
- 추가적으로 dylib을 로드하고 모든 것이 로딩되면 dyld는 필요한 모든 바인드 심볼을 조회하고 수정을 수행할 때 해당 주소를 사용한다.
- 모든 수정이 완료되면 dyld는 이니셜라이저를 상향식(bottom-up)으로 실행한다.
- 앱이 실행될 때마다 위 사진에서 녹색 단계는 매번 동일하기 때문에 프로그램과 dylib들이 변경되지 않는한 모든 녹색 단계는 첫 실행에서 캐시된다.
Recent dyld improvements
새로운 dyld 속성 'page-in linking'이다.
- dyld가 모든 수정 사항을 실행 시에 모든 dylib에 적용하는 대신 커널이 수정 사항을 DATA 페이지에 lazy하게 적용한다.
- page-in에서 mmap 처리된 일부 페이지에서 일부 주소를 처음 사용하면 커널이 해당 파이지를 읽도록 유발하는 경우가 항상 있었다.
- 그러나 이제 DATA 페이지의 경우 커널은 해당 페이지에 필요한 수정도 적용한다.
- 이 매커니즘은 더티 메모리와 실행 시간을 줄인다.
- 이는 DATA_CONST 페이지가 clean 하다는 의미이고 TEXT 페이지처럼 축출과 재생성이 가능해서 메모리 pressure가 감소한다.
- 2017년부터 2022년까지 dyld는 위의 녹색 단계를 최적화 해왔다.
- 이제 위에서 파란 단계인 fix ups 단계를 최적화 할 수 있다.
- 수정을 직접 수행하지 않고 lazy하게 page-in에서 커널이 하도록 만들기 때문이다.
Dynamic linking best practices
- dyld는 이미 동적 링킹의 대부분의 단계를 가속화했다.
- 우리가 제어할 수 있는 한가지는 dylib의 수이다.
- dylib의 수가 적을수록 dyld가 수행해야 하는 작업 또한 줄어든다.
- 정적 이니셜라이저를 최적화하거나 제거하자
- 정적 이니셜라이저는 main 이전에 실행된다.
- 정적 이니셜라이저에는 I/O 또는 네트워킹을 수행하지 말자
- 정적 라이브러리와 동적 라이브러리 사이의 최적의 지점을 찾자
- 너무 많은 정적 라이브러리는 느린 빌드/디버그 사이클을 만든다.
- 너무 많은 동적 라이브러리는 느린 런치 시간을 만든다.
- 그러나 22년부터는 ld64의 속도를 높였기 때문에 최적의 지점이 변결될 수 있다.
- 더 많은 정적 라이브러리를 사용해도 빌드 시간이 이전보다 감소된다.
- iOS 타겟을 13.4 이상으로 설정하여 chained fix-ups을 생성하여 바이너를 더 작게 만들고 실행 시간을 단축할 수 있다.
New tools
링킹 과정을 들여다보는 데 도움이 되는 새로운 2가지 도구가 있다.
1. dyld_usage
- macOS에만 있지만 시뮬레이터에서 앱 실행을 추적하거나 앱이 Mac Catalyst 용으로 빌드된 경우에 사용 가능하다.
- 사용 예시로 맥에 있는 "텍스트 편집기"를 실행해보면 위처럼 실행하는데 총 15ms가 걸린 것을 확인할 수 있다.
- 대부분 정적 이니셜라이저에서 소요되었다.
2. dyld_info
- 디스크와 현재 dyld 캐시 모두에서 바이너리를 검사할 수 있다.
- 다양한 옵션 중에서 exports와 fixups의 사용 예시이다.
- -fixup 옵션은 dyld가 처리할 모든 수정 위치와 그 대상을 표시한다.
- -exports 옵션은 dylib에 있는 모든 내보내기된 심볼 및 dylib의 시작 부분의 각 심볼에 대한 오프셋을 표시한다.
- 위의 경우에는 dylib인 Foundation.framework에 대한 정보를 보여주고 있다.
'iOS > 개발' 카테고리의 다른 글
[iOS] SOPT - 푸시 알림 딥링크 라우팅 개발 여정 1 (0) | 2024.01.05 |
---|---|
Demystify parallelization in Xcode builds (1) | 2023.11.03 |
[iOS] 프로젝트 개발 환경 세팅 자동화 with fastlane, Makefile (0) | 2023.08.07 |
[Swift] iOS 네이버 지도 SDK - 지도 뷰 커스텀 (3) | 2023.07.12 |
Fastlane으로 Versioning 하기 (+ 트러블 슈팅) (0) | 2023.07.03 |
댓글