26/09/2019

iOS Address Sanitizer в UE4

В процессе разработки может случиться момент, когда приложение завершает свою работу совершенно в неочевидных местах в коде движка или вашего проекта. При этом при каждом таком завершении место может быть разным. Одной из причин такого поведения может являться некорректная работа с памятью, в частности — запись новых данных туда, где уже содержатся используемые движком/проектом данные. Найти такие места в коде обычным отладчиком может оказаться не просто. В таком случае на помощь может прийти Address Sanitizer. Он поможет найти некоторые места, где происходит некорректная работа с памятью.

В этой статье мы рассмотрим Address Sanitizer для отладки iOS приложения на UE4. Исходя из официальной документации Apple, нам понадобится XCode для отладки приложения для iOS. 

Будем рассматривать разработку приложения на Windows и сборку iOS билда с помощью удаленного мака. Для примеров будем использовать версию движка 4.22.3.

Компиляция

Для включения отладчика нужно добавить флаг -fsanitize=address в аргументы компиляции и в аргументы линковщика. В UE4 такой флаг уже добавлен, его нужно только включить. Его можно найти в iOSToolChain в функциях GetCompileArguments_Global и GetLinkArguments_Global. Для включения нужно добавить переменную среды ENABLE_ADDRESS_SANITIZER на маке. Для упрощения мы добавим эту переменную среды в *.Target.cs нашего проекта следующим образом: 

Environment.SetEnvironmentVariable("ENABLE_ADDRESS_SANITIZER", "YES");

Следующим условием для включения отладчика является отключение оптимизации кода. Так как в UE4 по умолчанию в сборке для iOS оптимизация кода включена в независимости от конфигурации DebugGame или Development. При отключении оптимизации FPS будет значительно меньше.

Для отключения оптимизации в флагах компиляции параметр, отвечающий за неё должен быть выставлен в “-O0”. Для этого добавим в файл *.Build.cs нашего проекта:

OptimizeCode = ModuleRules.CodeOptimization.Never;

Для удобства можно обернуть это в проверку на текущую конфигурацию DebugGame.

Пример для *.Build.cs:

If (Target.Configuration == UnrealTargetConfiguration.DebugGame)
{ 
… 
}

Данная настройка отключает оптимизацию для конкретного модуля движка (модуля проекта) в случае, если билд не монолитный. Так как iOS билд монолитный, оптимизация будет отключена для всего движка и проекта, что нам подходит для отладки.

Возможные ошибки компиляции:

final section layout:

3>      __TEXT/__text addr=0x10000606C, size=0x0AB95B3C, fileOffset=0x0000606C, type=1

3>      __TEXT/__.uedbg addr=0x10AB9BBA8, size=0x00663B38, fileOffset=0x0AB9BBA8, type=1

3>      __TEXT/__stubs addr=0x10B1FF6E0, size=0x00001E54, fileOffset=0x0B1FF6E0, type=28

3>      __TEXT/__stub_helper addr=0x10B201534, size=0x00001BE4, fileOffset=0x0B201534, type=32

3>      __TEXT/__gcc_except_tab addr=0x10B203118, size=0x0001005C, fileOffset=0x0B203118, type=0

3>      __TEXT/__objc_methname addr=0x10B213174, size=0x0000AD70, fileOffset=0x0B213174, type=14

3>      __TEXT/__cstring addr=0x10B21DEE4, size=0x00B90C0F, fileOffset=0x0B21DEE4, type=13

3>      __TEXT/__objc_classname addr=0x10BDAEAF3, size=0x000008D6, fileOffset=0x0BDAEAF3, type=14

3>      __TEXT/__objc_methtype addr=0x10BDAF3C9, size=0x00006FF4, fileOffset=0x0BDAF3C9, type=14

3>      __TEXT/__const addr=0x10BDB63C0, size=0x00671812, fileOffset=0x0BDB63C0, type=0

3>      __TEXT/__asan_cstring addr=0x10C427BE0, size=0x003CC9E0, fileOffset=0x0C427BE0, type=0

3>      __TEXT/__ustring addr=0x10C7F45C0, size=0x00000A80, fileOffset=0x0C7F45C0, type=16

3>      __TEXT/__unwind_info addr=0x10C7F5040, size=0x000A5B2C, fileOffset=0x0C7F5040, type=22

3>      __TEXT/__eh_frame addr=0x10C89AB70, size=0x00009484, fileOffset=0x0C89AB70, type=19

3>      __DATA/__got addr=0x10C8A4000, size=0x00002260, fileOffset=0x0C8A4000, type=29

3>      __DATA/__la_symbol_ptr addr=0x10C8A6260, size=0x00001438, fileOffset=0x0C8A6260, type=27

3>      __DATA/__mod_init_func addr=0x10C8A7698, size=0x00001D08, fileOffset=0x0C8A7698, type=33

3>      __DATA/__mod_term_func addr=0x10C8A93A0, size=0x00000E00, fileOffset=0x0C8A93A0, type=34

3>      __DATA/__const addr=0x10C8AA1A0, size=0x00B6E400, fileOffset=0x0C8AA1A0, type=0

3>      __DATA/__cfstring addr=0x10D4185A0, size=0x00002060, fileOffset=0x0D4185A0, type=17

3>      __DATA/__objc_classlist addr=0x10D41A600, size=0x00000208, fileOffset=0x0D41A600, type=0

3>      __DATA/__objc_catlist addr=0x10D41A808, size=0x00000010, fileOffset=0x0D41A808, type=24

3>      __DATA/__objc_protolist addr=0x10D41A818, size=0x000000E0, fileOffset=0x0D41A818, type=0

3>      __DATA/__objc_imageinfo addr=0x10D41A8F8, size=0x00000008, fileOffset=0x0D41A8F8, type=0

3>      __DATA/__objc_const addr=0x10D41A900, size=0x0000CAC8, fileOffset=0x0D41A900, type=0

3>      __DATA/__objc_selrefs addr=0x10D4273C8, size=0x00002E38, fileOffset=0x0D4273C8, type=15

3>      __DATA/__objc_protorefs addr=0x10D42A200, size=0x00000008, fileOffset=0x0D42A200, type=0

3>      __DATA/__objc_classrefs addr=0x10D42A208, size=0x00000698, fileOffset=0x0D42A208, type=23

3>      __DATA/__objc_superrefs addr=0x10D42A8A0, size=0x000001C0, fileOffset=0x0D42A8A0, type=0

3>      __DATA/__objc_ivar addr=0x10D42AA60, size=0x00000404, fileOffset=0x0D42AA60, type=0

3>      __DATA/__objc_data addr=0x10D42AE68, size=0x00001450, fileOffset=0x0D42AE68, type=0

3>      __DATA/__data addr=0x10D42C2C0, size=0x0006C53A, fileOffset=0x0D42C2C0, type=0

3>      __DATA/__asan_globals addr=0x10D498800, size=0x00D00680, fileOffset=0x0D498800, type=0

3>      __DATA/__asan_liveness addr=0x10E198E80, size=0x003401A0, fileOffset=0x0E198E80, type=0

3>      __DATA/__thread_vars addr=0x10E4D9020, size=0x00000048, fileOffset=0x0E4D9020, type=39

3>      __DATA/__thread_bss addr=0x10E4D9068, size=0x00000003, fileOffset=0x00000000, type=40

3>      __DATA/__bss addr=0x10E4D9080, size=0x00399618, fileOffset=0x00000000, type=25

3>      __DATA/__common addr=0x10E8726C0, size=0x002C2D48, fileOffset=0x00000000, type=25

3>  ld: b(l) ARM64 branch out of range

В нашем примере не принципиально — каким будет фикс, поэтому один из самых простых и быстрых способов решения: 

В IOSPlatform.h заменить

#define PLATFORM_CODE_SECTION(Name) __attribute__((section("__TEXT,__" Name ",regular,pure_instructions"))) \
__attribute__((aligned(4)))

На 

#if __has_feature(address_sanitizer)
#define PLATFORM_CODE_SECTION(Name)
#else
#define PLATFORM_CODE_SECTION(Name) __attribute__((section("__TEXT,__" Name ",regular,pure_instructions"))) \
__attribute__((aligned(4)))
#endif

Проверка

После добавления всех параметров нужно сделать Full Rebuild проекта в конфигурации DebugGame. После удачной сборки билда можно проверить присутствует ли Address Sanitizer в билде. Для этого откроем с помощью архива полученный *.ipa файл. Проверим наличие файла: *.ipa/Payload/[AppName].app/Frameworks/libclang_rt.asan_ios_dynamic.dylib. Если такой файл есть, санитайзер присутствует в билде. Можно приступать к отладке.

Подготовка билда

Для запуска отладки билда через XCode нам понадобится контент полученного *.ipa. Так как во время сборки iOS приложения он не перебрасывается на мак по причине ускорения сборки, его нужно перенести самостоятельно. Для этого копируем полученный *.ipa на мак в /users/mac/UE4/Builds/[MachineName]/[DiskName]/.../[ProjectName]/Binaries/IOS. Распаковываем с помощью архива, заменяя уже существующую в этой директории папку Payload с её содержимым.

Отладка

Открываем XCode проект на маке (/users/mac/UE4/Builds/[MachineName]/[DiskName]/.../[ProjectName]/Intermediate/ProjectFilesIOS/[ProjectName].xcodeproj)

Открываем настройки схемы
Во вкладке Run выставляем конфигурацию DebugGame (ту, с которой собирался билд)
Включаем Address Sanitizer во вкладке Diagnostics

Примеры

Out-of-Scope Stack Memory:

Пример неправильного использования TCHAR_TO_UTF8:

Прочие ошибки:

 

Recent Posts

Продвижение мобильного приложения. Шаг 2: рекламный трафик

14/10/2019
Вы рекламируете свою игру, чтобы показывать пользователям рекламу в своем приложении и получать с этого доход. 

Shores Unknown: история одного геймдизайнера из Японии

04/10/2019
Геймдизайнер Илья Руднев делится деталями разработки своей игры и полезными советами для инди-разработчиков.

Теория качественного звукового сопровождения в видеоиграх

02/10/2019
Разберем основы удачного саундтрека, как работает музыка, какие баги со звуком встречаются в играх и их причины.