Роман Ермолов
Отладка приложений
под iOS
План
1 Интересные возможности LLDB
2 Отладка иерархии UIView
3 Отладка без исходников
Интересные возможности
LLDB
1 breakpoint
1.1 condition
1.2 command
Интересные возможности
LLDB
1 breakpoint
1.1 condition
1.2 command
breakpoint
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
self.contentManagerVC.view.frame =
self.layout.contentFrame;
// ...
self.voiceSearchController.view.frame =
self.layout.voiceSearchFrame;
}
6
breakpoint
7
breakpoint
8
Интересные возможности
LLDB
1 breakpoint
1.1 condition
1.2 command
condition
10
@implementation YBSplitViewController
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
// ...
}
- (void)viewDidLoad {
[super viewDidLoad];
// ...
}
@end
condition
11
@implementation YBSplitViewController
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
// ...
}
- (void)viewDidLoad {
[super viewDidLoad];
// ...
}
- (void)didMoveToParentViewController:(UIViewController*)parent {
[super didMoveToParentViewController:parent];
}
@end
condition
// Устанавливаем символьный брейкпоинт
-[YBSplitViewController didMoveToParentViewController:]
12
// Устанавливаем символьный брейкпоинт
-[UIViewController didMoveToParentViewController:]
// Добавляем условие
(Class)[$arg1 class] == [YBSplitViewController class]
(lldb) breakpoint set -F
"-[YBSplitViewController didMoveToParentViewController:]"
Breakpoint 1: no locations (pending).
WARNING: Unable to resolve breakpoint to any actual locations.
condition
(lldb) po $arg1
<YBSplitViewController: 0xSome_Address>
13
Интересные возможности
LLDB
1 breakpoint
1.1 condition
1.2 command
print/po
print/po
- (void)configureForScreenType:(YBScreenType)screenType {
self.collapsedHeight =
[[self class] collapsedHeightForScreenType:screenType];
//...
}
16
- (void)configureForScreenType:(YBScreenType)screenType {
self.collapsedHeight =
[[self class] collapsedHeightForScreenType:screenType];
NSLog(@"%f", self.collapsedHeight);
//...
}
17
print/po
- (void)configureForScreenType:(YBScreenType)screenType {
self.collapsedHeight =
[[self class] collapsedHeightForScreenType:screenType];
//...
}
18
// Добавляем команду
print self.collapsedHeight
(CGFloat) $0 = 15
print/po
print/po
19
// Добавляем команду
po self.childViewController
// Добавляем команду
expression (void)NSLog(@"Content insets %@",
(NSString *)NSStringFromUIEdgeInsets(self.contentInsetsInCarousel))
<ViewController 0xSome_Address lines: 5, text: some_title>
2015-08-16 12:30:25.604 Debugging-Objc[7319:398126] Content insets
{10, 20, 30, 40}
thread return
thread return
- (void)showAlertViewIfNeeded {
if (![self shouldShowAlertView]) {
return;
}
[self showAlertView];
}
- (BOOL)shouldShowAlertView {
return ([self application].applicationState == UIApplicationStateActive
&& [self isSyncInitializing]);
}
21
thread return
- (void)showAlertViewIfNeeded {
// if (![self shouldShowAlertView]) {
// return;
// }
[self showAlertView];
}
- (BOOL)shouldShowAlertView {
return YES;
// return ([self application].applicationState == UIApplicationStateActive
// && [self isSyncInitializing]);
}
22
thread return
- (BOOL)shouldShowAlertView {
return ([self application].applicationState == UIApplicationStateActive
&& [self isSyncInitializing]);
}
// Добавляем команду
thread return YES
23
thread return 123.45
thread return [NSArray array]
thread return @"string"
Интересные возможности
LLDB
1 breakpoint
1.1 condition
1.2 command
watchpoint
@implementation YBFieldValidator
- (void)validateField:(NSString *)field {
if ([self fieldIsEmpty:field]) {
_state = YBJSONRequestStateFailed;
} else {
_state = YBJSONRequestStateSuccess;
}
}
- (void)setInvalidStateWithErrors:(NSArray *)errors {
_state = YBJSONRequestStateFailed;
}
@end
25
watchpoint
26
@implementation YBFieldValidator
- (void)validateField:(NSString *)field {
if ([self fieldIsEmpty:field]) {
_state = YBJSONRequestStateFailed;
} else {
_state = YBJSONRequestStateSuccess;
}
}
- (void)setInvalidStateWithErrors:(NSArray *)errors {
_state = YBJSONRequestStateFailed;
}
@end
watchpoint
(lldb) watchpoint set variable —-watch read_write _state
Watchpoint created:
Watchpoint 1: addr = 0xSome_Address size = 8 state = enabled type = rw
watchpoint spec = '_state'
new value: 0
27
Watchpoint 1 hit:
old value: 0
new value: 3
Отладка иерархии UIView
Отладка иерархии UIView
29
iOS 7-
(lldb) po [[[[UIApplication sharedApplication] delegate] window]
recursiveDescription]
<UIWindow: 0xSome_Address; frame = (0 0; 375 667); ...>
| <UIView: 0xSome_Address; frame = (0 0; 375 667); ...>
| | <ViewWithCustomNextResponder: 0xSome_Address; ...>
iOS 8+
Отладка иерархии UIView
30
Отладка иерархии UIView
31
Отладка иерархии UIView
Reveal Spark Inspector
32
Отладка иерархии UIView
(lldb) expression (void)[0x7fd7aaec45b0 setTintColor:[UIColor redColor]]
33
(lldb) expression (void)[CATransaction flush]
Отладка без исходников
Отладка без исходников
35
Отладка без исходников
36
Sample app Safari
Процесс отладки
1 Определение точки входа
2 Остановка программы
3 Оценка текущего состояния
4 Анализ поведения
37
Работа с методами
Работа с методами
// objc.h
typedef id (*IMP)(id self, SEL _cmd, ...);
39
@interface CustomClass : NSObject
- (void)methodFoo:(id)foo bar:(id)bar baz:(id)baz;
- (void)setQux:(CGFloat)qux;
- (void)setCorge:(CGRect)corge;
@end
// NSObject.h
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
Работа с методами
40
// 1. Получаем адрес функции
(lldb) expression IMP $address = (IMP)[[CustomClass class]
instanceMethodForSelector:@selector(methodFoo:bar:baz:)]
// 2. Ставим breakpoint на адрес
(lldb) breakpoint set —-address $address
Breakpoint 1:
where = SomeApplication`___lldb_unnamed_function653$$,
address = 0xSome_Address
Работа с методами. Аргументы
Способы передачи аргументов в функцию:
через регистры процессора
через стек
смешанный (часть передается через регистры, часть
через стек или другую память)
41
Архитектуры:
armv7* / arm64 / x86_64
Работа с методами. Аргументы
42
armv7 arm64 x86_64 алиас
self r0 x0 rdi $arg1
_cmd r1 x1 rsi $arg2
arg1 r2 x2 rdx $arg3
arg2 r3 x3 rcx $arg4
arg3 $(sp) x4 r8 $arg5
Работа с методами. Аргументы
43
// Устанавливаем breakpoint
- (void)methodFoo:(id)foo bar:(id)bar baz:(id)baz;
(lldb) po $arg1
<CustomClass: 0xSome_Address>
(lldb) print (SEL)$arg2
(SEL) $0 = "methodFoo:bar:baz:"
(lldb) po $arg3
<FooClass: 0xSome_Address>
(lldb) po $arg4
<BarClass: 0xSome_Address>
(lldb) po $arg5
<BazClass: 0xSome_Address>
Работа с методами. Аргументы (x86_64)
44
// Устанавливаем breakpoint
- (void)setQux:(CGFloat)qux;
(lldb) po $arg1
<CustomClass: 0xSome_Address>
(lldb) print (SEL)$arg2
(SEL) $0 = "setQux:"
(lldb) print $arg3
(unsigned long) $1 = 4677571844
(lldb) expression (void)NSLog(@"%f", $xmm0)
2015-08-16 12:33:50.316 Debugging-Objc[4470:281979] 0.900000
[customClassObject setQux:0.9];
Работа с методами. Аргументы (x86_64)
45
// Устанавливаем breakpoint
- (void)setCorge:(CGRect)corge;
(lldb) po $arg1
<CustomClass: 0xSome_Address>
(lldb) print (SEL)$arg2
(SEL) $0 = "setCorge:"
(lldb) print $arg3
(unsigned long) $1 = 4677571844
[customClassObject setCorge:CGRectMake(10.5, 15.5, 30, 40)];
Работа с методами. Аргументы (x86_64)
46
struct CGRect {
CGPoint origin;
CGSize size;
};
(lldb) memory read $rsp+8 --format float64 --count 4 --size 8
struct CGPoint {
CGFloat x;
CGFloat y;
};
struct CGSize {
CGFloat width;
CGFloat height;
};
0xSome_Address : {10.5} // origin.x
0xSome_Address+8 : {15.5} // origin.y
0xSome_Address+16: {30} // size.width
0xSome_Address+24: {40} // size.height
Работа с объектами
Работа с объектами
/// A pointer to an instance of a class.
typedef struct objc_object *id;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
48
Работа с объектами
49
@interface CustomClass : NSObject {
@private
NSInteger _counter;
}
@end
struct CustomClass_object {
Class isa;
NSInteger _counter;
};
Работа с объектами. Значение ivar
(lldb) po [0xSome_Address valueForKey:@"_counter"]
123
50
// 1. Получаем описание переменной
(lldb) expression struct objc_ivar* $variable = (struct objc_ivar*)
class_getInstanceVariable([CustomClass class], "_counter")
// 2. Получаем смещение
(lldb) expression ptrdiff_t $offset =
(ptrdiff_t)ivar_getOffset($variable)
// 3. Получаем значение переменной
(lldb) memory read 0xSome_Address+$offset --format int64
--count 1 --size 8
0xSome_Address+$offset: {123}
Работа с объектами. Watchpoint
51
// 1. Получаем описание переменной
(lldb) expression struct objc_ivar* $variable = (struct objc_ivar*)
class_getInstanceVariable([CustomClass class], "_counter")
// 2. Получаем смещение
(lldb) expression ptrdiff_t $offset =
(ptrdiff_t)ivar_getOffset($variable)
// 3. Ставим watchpoint на адрес
(lldb) watchpoint set expression —-watch read_write —-size 8
-- $arg1+$offset
Watchpoint created:
Watchpoint 0: addr = 0xSome_Address size = 8 type = rw
new value: 0
Chisel
Установка брейкпоинтов на метод класса/объекта
Установка watchpoint’ов
Иерархия UIViewController
Открытие UIImage, UIView, UIColor в Preview
многое другое!
52
Hopper
Hopper
Дизассемблер
Декомпилятор
Отладчик
54
Hopper
55
Hopper
56
Hopper
57
Hopper
58
Процесс отладки
59
1 Определение точки входа
2 Остановка программы
3 Оценка текущего состояния
4 Анализ поведения
Итоги
1 Интересные возможности LLDB
2 Отладка иерархии UIView
3 Отладка без исходников
Материалы
LLDB Command Map
Mac OS X Debugging Magic
iOS Debugging Magic
iOS ABI Function Call Guide
Hopper
Chisel
61
Спасибо за внимание!
Вопросы? (:
64
Роман Ермолов
iOS-разработчик
Контакты
@roman_ermolov
iforve@yandex.ru

Роман Ермолов - Отладка приложений под iOS