[Из песочницы] Повреждение стека в одном из методов NSString

Хочу написать про один странный креш, с которым разбирался на работе.Креш происходил стабильно при заходе в папку с корейскими символами. Проблема оказалась во вроде бы безобидном коде следующего вида:

NSURLComponents* urlComp = [[NSURLComponents new] autorelease]; … urlComp.path = path; urlComp.user = username; … Падает при выставлении user — EXC_BAD_ACCESS внутри сеттера при посылке objc_msgSend кому-то. Все переменные в порядке, ничто не могло сломаться. При этом креш воспроизводится в релизной конфигурации, но не в дебажной. Ругаясь на плохую работу отладчика в релизной конфе, идем смотреть дальше.Хоть отладчик зачастую и не может в релизе распечатать переменные, но по дизассемблерному листингу легко видеть в каком регистре, какие переменные должны быть, и отладчик способен нормально выводить объекты (например, po $r0). Быстро становится понятно, что сдох username (в моем случае регистр r10) — po $r10 выводит число, а не объект. Несколько менее быстро становится понятно, что значение в регистре r10 поменялось после выставления path.

Окей, лезем смотреть, что происходит в методе »-[__NSConcreteURLComponents setPath:]». Благо дело он небольшой и видно, что регистр r10 слетает при вызове »-[NSString (NSURLUtilities) stringByAddingPercentEncodingWithAllowedCharacters:]» — т.е. когда эскейпится переданный путь. Эта функция уже большая, и заказчик голову отровет за ее анализ, но хотя бы глянем на вход-выход

0×2ca50aec: push.w {r8, r10, r11} 0×2ca50af0: sub.w sp, sp, #0×1020 0×2ca50af4: sub sp, #0×10 … 0×2ca50e7a: add.w sp, sp, #0×1020 0×2ca50e7e: add sp, #0×10 0×2ca50e80: pop.w {r8, r10, r11} При входе наш r10 сохраняется в стек, а при выходе восстанавливается. Указатель стека (sp) в порядке, что было, то и вернулось, а вот само содержимое стека уже не то — значение r10 восстановилось неверно. Таким образом, в системной функции для percent encoding’a есть повреждение стека.Для наглядности я вынес код-пример в чистый тестовый проект:

NSObject* obj1 = [[NSObject new] autorelease]; NSObject* obj2 = [[NSObject new] autorelease]; NSObject* obj3 = [[NSObject new] autorelease]; NSObject* obj4 = [[NSObject new] autorelease]; NSString* str = @»/Users/zaryanov/Movies/rootfolder/시티 오브 히어로 (City of Heroes)/로니 리 가드너 (1961년부터 2010년까지)는 1985 년에 살인죄로 사형을받은 유타 주에서 총살형 된 미국의 악당이었다. 1984 년에 그는 솔트 레이크 시티에서 강도 동안 바텐더를 살해.m4v»; NSLog (@»%s str %@», __func__, str); NSCharacterSet* charSet = [NSCharacterSet URLPathAllowedCharacterSet]; str = [str stringByAddingPercentEncodingWithAllowedCharacters: charSet]; NSLog (@»%s str %@», __func__, str); NSLog (@»%s obj1%@ obj2%@ obj3%@ obj4%@», __func__, obj1, obj2, obj3, obj4); При этом креш произошел не при выводе в лог, как я ожидал, а в самой проблемной функции (эскейпинга). Сработал abort в функции __stack_chk_fail — дело в том, что в чистом тестовом проекте была разрешена архитектура arm64, и там, по всей видимости, есть проверка стека. Если оставить только armv7, то креш происходит при выводе объектов в лог, как я и ожидал. В любом случае, стек поврежден.Далее гугление по «stringByAddingPercentEncodingWithAllowedCharacters crash» дает некоторые подтверждающие результаты:

https://github.com/Alamofire/Alamofire/issues/206 — здесь, правда, жалуются на большое потребление памяти, но функция та же; https://gist.github.com/clowwindy/0d800f07a5e95e5c4dd0 — здесь пример, который сносит стек совсем, а не пару регистров.

Собственно, из первой приведенной ссылки я и взял логичное решение — юзать CFURLCreateStringByAddingPercentEscapes, менее удобно, зато работает. Тому же NSURLComponents можно выставить самим уже заэскейпленный путь.

Проблема воспроизводится на iOS 8.2, так что стоит оставить себе зарубку на подкорке. Как видно в моем случае, креш может быть немного спрятан и не очевиден из-за неявного вызова проблемной функции через другую функцию. Ну и с повреждением стека может повезти по-разному, если зацепит только регистры, то это может далеко не сразу обнаружиться.

© Habrahabr.ru