Повреждение стека в одном из методов 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:]" — т.е. когда эскейпится переданный путь. Эта функция уже большая, и заказчик голову отровет за ее анализ, но хотя бы глянем на вход-выход

0x2ca50aec:   push.w {r8, r10, r11} 0x2ca50af0:   sub.w   sp, sp, #0x1020 0x2ca50af4:   sub     sp, #0x10 ... 0x2ca50e7a:   add.w   sp, sp, #0x1020 0x2ca50e7e:   add     sp, #0x10 0x2ca50e80:   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, так что стоит оставить себе зарубку на подкорке. Как видно в моем случае, креш может быть немного спрятан и не очевиден из-за неявного вызова проблемной функции через другую функцию. Ну и с повреждением стека может повезти по-разному, если зацепит только регистры, то это может далеко не сразу обнаружиться.

ссылка на оригинал статьи http://habrahabr.ru/post/254545/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *