1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
| #!/usr/bin/python
#----------------------------------------------------------------------
# This module is designed to live inside the "lldb" python package
# in the "lldb.macosx" package. To use this in the embedded python
# interpreter using "lldb" just import it:
#
# (lldb) script import lldb.macosx.heap
#----------------------------------------------------------------------
from __future__ import print_function
import lldb
import optparse
import os
import os.path
import re
import shlex
import string
import sys
import tempfile
import lldb.utils.symbolication
g_libheap_dylib_dir = None
g_libheap_dylib_dict = dict()
def get_iterate_memory_expr(
options,
process,
user_init_code,
user_return_code):
expr = '''
typedef unsigned natural_t;
typedef uintptr_t vm_size_t;
typedef uintptr_t vm_address_t;
typedef natural_t task_t;
typedef int kern_return_t;
#define KERN_SUCCESS 0
typedef void (*range_callback_t)(task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size);
'''
if options.search_vm_regions:
expr += '''
typedef int vm_prot_t;
typedef unsigned int vm_inherit_t;
typedef unsigned long long memory_object_offset_t;
typedef unsigned int boolean_t;
typedef int vm_behavior_t;
typedef uint32_t vm32_object_id_t;
typedef natural_t mach_msg_type_number_t;
typedef uint64_t mach_vm_address_t;
typedef uint64_t mach_vm_offset_t;
typedef uint64_t mach_vm_size_t;
typedef uint64_t vm_map_offset_t;
typedef uint64_t vm_map_address_t;
typedef uint64_t vm_map_size_t;
#define VM_PROT_NONE ((vm_prot_t) 0x00)
#define VM_PROT_READ ((vm_prot_t) 0x01)
#define VM_PROT_WRITE ((vm_prot_t) 0x02)
#define VM_PROT_EXECUTE ((vm_prot_t) 0x04)
typedef struct vm_region_submap_short_info_data_64_t {
vm_prot_t protection;
vm_prot_t max_protection;
vm_inherit_t inheritance;
memory_object_offset_t offset; // offset into object/map
unsigned int user_tag; // user tag on map entry
unsigned int ref_count; // obj/map mappers, etc
unsigned short shadow_depth; // only for obj
unsigned char external_pager; // only for obj
unsigned char share_mode; // see enumeration
boolean_t is_submap; // submap vs obj
vm_behavior_t behavior; // access behavior hint
vm32_object_id_t object_id; // obj/map name, not a handle
unsigned short user_wired_count;
} vm_region_submap_short_info_data_64_t;
#define VM_REGION_SUBMAP_SHORT_INFO_COUNT_64 ((mach_msg_type_number_t)(sizeof(vm_region_submap_short_info_data_64_t)/sizeof(int)))'''
if user_init_code:
expr += user_init_code
expr += '''
task_t task = (task_t)mach_task_self();
mach_vm_address_t vm_region_base_addr;
mach_vm_size_t vm_region_size;
natural_t vm_region_depth;
vm_region_submap_short_info_data_64_t vm_region_info;
kern_return_t err;
for (vm_region_base_addr = 0, vm_region_size = 1; vm_region_size != 0; vm_region_base_addr += vm_region_size)
{
mach_msg_type_number_t vm_region_info_size = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
err = (kern_return_t)mach_vm_region_recurse (task,
&vm_region_base_addr,
&vm_region_size,
&vm_region_depth,
&vm_region_info,
&vm_region_info_size);
if (err)
break;
// Check all read + write regions. This will cover the thread stacks
// and any regions of memory like __DATA segments, that might contain
// data we are looking for
if (vm_region_info.protection & VM_PROT_WRITE &&
vm_region_info.protection & VM_PROT_READ)
{
baton.callback (task,
&baton,
64,
vm_region_base_addr,
vm_region_size);
}
}'''
else:
if options.search_stack:
expr += get_thread_stack_ranges_struct(process)
if options.search_segments:
expr += get_sections_ranges_struct(process)
if user_init_code:
expr += user_init_code
if options.search_heap:
expr += '''
#define MALLOC_PTR_IN_USE_RANGE_TYPE 1
typedef struct vm_range_t {
vm_address_t address;
vm_size_t size;
} vm_range_t;
typedef kern_return_t (*memory_reader_t)(task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory);
typedef void (*vm_range_recorder_t)(task_t task, void *baton, unsigned type, vm_range_t *range, unsigned size);
typedef struct malloc_introspection_t {
kern_return_t (*enumerator)(task_t task, void *, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder); /* enumerates all the malloc pointers in use */
} malloc_introspection_t;
typedef struct malloc_zone_t {
void *reserved1[12];
struct malloc_introspection_t *introspect;
} malloc_zone_t;
memory_reader_t task_peek = [](task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory) -> kern_return_t {
*local_memory = (void*) remote_address;
return KERN_SUCCESS;
};
vm_address_t *zones = 0;
unsigned int num_zones = 0;task_t task = 0;
kern_return_t err = (kern_return_t)malloc_get_all_zones (task, task_peek, &zones, &num_zones);
if (KERN_SUCCESS == err)
{
for (unsigned int i=0; i<num_zones; ++i)
{
const malloc_zone_t *zone = (const malloc_zone_t *)zones[i];
if (zone && zone->introspect)
zone->introspect->enumerator (task,
&baton,
MALLOC_PTR_IN_USE_RANGE_TYPE,
(vm_address_t)zone,
task_peek,
[] (task_t task, void *baton, unsigned type, vm_range_t *ranges, unsigned size) -> void
{
range_callback_t callback = ((callback_baton_t *)baton)->callback;
for (unsigned i=0; i<size; ++i)
{
callback (task, baton, type, ranges[i].address, ranges[i].size);
}
});
}
}'''
if options.search_stack:
expr += '''
#ifdef NUM_STACKS
// Call the callback for the thread stack ranges
for (uint32_t i=0; i<NUM_STACKS; ++i) {
range_callback(task, &baton, 8, stacks[i].base, stacks[i].size);
if (STACK_RED_ZONE_SIZE > 0) {
range_callback(task, &baton, 16, stacks[i].base - STACK_RED_ZONE_SIZE, STACK_RED_ZONE_SIZE);
}
}
#endif'''
if options.search_segments:
expr += '''
#ifdef NUM_SEGMENTS
// Call the callback for all segments
for (uint32_t i=0; i<NUM_SEGMENTS; ++i)
range_callback(task, &baton, 32, segments[i].base, segments[i].size);
#endif'''
if user_return_code:
expr += "\n%s" % (user_return_code,)
return expr
def get_member_types_for_offset(value_type, offset, member_list):
member = value_type.GetFieldAtIndex(0)
search_bases = False
if member:
if member.GetOffsetInBytes() <= offset:
for field_idx in range(value_type.GetNumberOfFields()):
member = value_type.GetFieldAtIndex(field_idx)
member_byte_offset = member.GetOffsetInBytes()
member_end_byte_offset = member_byte_offset + member.type.size
if member_byte_offset <= offset and offset < member_end_byte_offset:
member_list.append(member)
get_member_types_for_offset(
member.type, offset - member_byte_offset, member_list)
return
else:
search_bases = True
else:
search_bases = True
if search_bases:
for field_idx in range(value_type.GetNumberOfDirectBaseClasses()):
member = value_type.GetDirectBaseClassAtIndex(field_idx)
member_byte_offset = member.GetOffsetInBytes()
member_end_byte_offset = member_byte_offset + member.type.size
if member_byte_offset <= offset and offset < member_end_byte_offset:
member_list.append(member)
get_member_types_for_offset(
member.type, offset - member_byte_offset, member_list)
return
for field_idx in range(value_type.GetNumberOfVirtualBaseClasses()):
member = value_type.GetVirtualBaseClassAtIndex(field_idx)
member_byte_offset = member.GetOffsetInBytes()
member_end_byte_offset = member_byte_offset + member.type.size
if member_byte_offset <= offset and offset < member_end_byte_offset:
member_list.append(member)
get_member_types_for_offset(
member.type, offset - member_byte_offset, member_list)
return
def append_regex_callback(option, opt, value, parser):
try:
ivar_regex = re.compile(value)
parser.values.ivar_regex_blacklist.append(ivar_regex)
except:
print('error: an exception was thrown when compiling the ivar regular expression for "%s"' % value)
def add_common_options(parser):
parser.add_option(
'-v',
'--verbose',
action='store_true',
dest='verbose',
help='display verbose debug info',
default=False)
parser.add_option(
'-t',
'--type',
action='store_true',
dest='print_type',
help='print the full value of the type for each matching malloc block',
default=False)
parser.add_option(
'-o',
'--po',
action='store_true',
dest='print_object_description',
help='print the object descriptions for any matches',
default=False)
parser.add_option(
'-z',
'--size',
action='store_true',
dest='show_size',
help='print the allocation size in bytes',
default=False)
parser.add_option(
'-r',
'--range',
action='store_true',
dest='show_range',
help='print the allocation address range instead of just the allocation base address',
default=False)
parser.add_option(
'-m',
'--memory',
action='store_true',
dest='memory',
help='dump the memory for each matching block',
default=False)
parser.add_option(
'-f',
'--format',
type='string',
dest='format',
help='the format to use when dumping memory if --memory is specified',
default=None)
parser.add_option(
'-I',
'--omit-ivar-regex',
type='string',
action='callback',
callback=append_regex_callback,
dest='ivar_regex_blacklist',
default=[],
help='specify one or more regular expressions used to backlist any matches that are in ivars')
parser.add_option(
'-s',
'--stack',
action='store_true',
dest='stack',
help='gets the stack that allocated each malloc block if MallocStackLogging is enabled',
default=False)
parser.add_option(
'-S',
'--stack-history',
action='store_true',
dest='stack_history',
help='gets the stack history for all allocations whose start address matches each malloc block if MallocStackLogging is enabled',
default=False)
parser.add_option(
'-F',
'--max-frames',
type='int',
dest='max_frames',
help='the maximum number of stack frames to print when using the --stack or --stack-history options (default=128)',
default=128)
parser.add_option(
'-H',
'--max-history',
type='int',
dest='max_history',
help='the maximum number of stack history backtraces to print for each allocation when using the --stack-history option (default=16)',
default=16)
parser.add_option(
'-M',
'--max-matches',
type='int',
dest='max_matches',
help='the maximum number of matches to print',
default=32)
parser.add_option(
'-O',
'--offset',
type='int',
dest='offset',
help='the matching data must be at this offset',
default=-1)
parser.add_option(
'--ignore-stack',
action='store_false',
dest='search_stack',
help="Don't search the stack when enumerating memory",
default=True)
parser.add_option(
'--ignore-heap',
action='store_false',
dest='search_heap',
help="Don't search the heap allocations when enumerating memory",
default=True)
parser.add_option(
'--ignore-segments',
action='store_false',
dest='search_segments',
help="Don't search readable executable segments enumerating memory",
default=True)
parser.add_option(
'-V',
'--vm-regions',
action='store_true',
dest='search_vm_regions',
help='Check all VM regions instead of searching the heap, stack and segments',
default=False)
def type_flags_to_string(type_flags):
if type_flags == 0:
type_str = 'free'
elif type_flags & 2:
type_str = 'malloc'
elif type_flags & 4:
type_str = 'free'
elif type_flags & 1:
type_str = 'generic'
elif type_flags & 8:
type_str = 'stack'
elif type_flags & 16:
type_str = 'stack (red zone)'
elif type_flags & 32:
type_str = 'segment'
elif type_flags & 64:
type_str = 'vm_region'
else:
type_str = hex(type_flags)
return type_str
def find_variable_containing_address(verbose, frame, match_addr):
variables = frame.GetVariables(True, True, True, True)
matching_var = None
for var in variables:
var_addr = var.GetLoadAddress()
if var_addr != lldb.LLDB_INVALID_ADDRESS:
byte_size = var.GetType().GetByteSize()
if verbose:
print('frame #%u: [%#x - %#x) %s' % (frame.GetFrameID(), var.load_addr, var.load_addr + byte_size, var.name))
if var_addr == match_addr:
if verbose:
print('match')
return var
else:
if byte_size > 0 and var_addr <= match_addr and match_addr < (
var_addr + byte_size):
if verbose:
print('match')
return var
return None
def find_frame_for_stack_address(process, addr):
closest_delta = sys.maxsize
closest_frame = None
# print 'find_frame_for_stack_address(%#x)' % (addr)
for thread in process:
prev_sp = lldb.LLDB_INVALID_ADDRESS
for frame in thread:
cfa = frame.GetCFA()
# print 'frame #%u: cfa = %#x' % (frame.GetFrameID(), cfa)
if addr < cfa:
delta = cfa - addr
# print '%#x < %#x, delta = %i' % (addr, cfa, delta)
if delta < closest_delta:
# print 'closest'
closest_delta = delta
closest_frame = frame
# else:
# print 'delta >= closest_delta'
return closest_frame
def type_flags_to_description(
process,
type_flags,
ptr_addr,
ptr_size,
offset,
match_addr):
show_offset = False
if type_flags == 0 or type_flags & 4:
type_str = 'free(%#x)' % (ptr_addr,)
elif type_flags & 2 or type_flags & 1:
type_str = 'malloc(%6u) -> %#x' % (ptr_size, ptr_addr)
show_offset = True
elif type_flags & 8:
type_str = 'stack'
frame = find_frame_for_stack_address(process, match_addr)
if frame:
type_str += ' in frame #%u of thread #%u: tid %#x' % (frame.GetFrameID(
), frame.GetThread().GetIndexID(), frame.GetThread().GetThreadID())
variables = frame.GetVariables(True, True, True, True)
matching_var = None
for var in variables:
var_addr = var.GetLoadAddress()
if var_addr != lldb.LLDB_INVALID_ADDRESS:
# print 'variable "%s" @ %#x (%#x)' % (var.name, var.load_addr,
# match_addr)
if var_addr == match_addr:
matching_var = var
break
else:
byte_size = var.GetType().GetByteSize()
if byte_size > 0 and var_addr <= match_addr and match_addr < (
var_addr + byte_size):
matching_var = var
break
if matching_var:
type_str += ' in variable at %#x:\n %s' % (
matching_var.GetLoadAddress(), matching_var)
elif type_flags & 16:
type_str = 'stack (red zone)'
elif type_flags & 32:
sb_addr = process.GetTarget().ResolveLoadAddress(ptr_addr + offset)
type_str = 'segment [%#x - %#x), %s + %u, %s' % (
ptr_addr, ptr_addr + ptr_size, sb_addr.section.name, sb_addr.offset, sb_addr)
elif type_flags & 64:
sb_addr = process.GetTarget().ResolveLoadAddress(ptr_addr + offset)
type_str = 'vm_region [%#x - %#x), %s + %u, %s' % (
ptr_addr, ptr_addr + ptr_size, sb_addr.section.name, sb_addr.offset, sb_addr)
else:
type_str = '%#x' % (ptr_addr,)
show_offset = True
if show_offset and offset != 0:
type_str += ' + %-6u' % (offset,)
return type_str
def dump_stack_history_entry(options, result, stack_history_entry, idx):
address = int(stack_history_entry.address)
if address:
type_flags = int(stack_history_entry.type_flags)
symbolicator = lldb.utils.symbolication.Symbolicator()
symbolicator.target = lldb.debugger.GetSelectedTarget()
type_str = type_flags_to_string(type_flags)
result.AppendMessage(
'stack[%u]: addr = 0x%x, type=%s, frames:' %
(idx, address, type_str))
frame_idx = 0
idx = 0
pc = int(stack_history_entry.frames[idx])
while pc != 0:
if pc >= 0x1000:
frames = symbolicator.symbolicate(pc)
if frames:
for frame in frames:
result.AppendMessage(
' [%u] %s' %
(frame_idx, frame))
frame_idx += 1
else:
result.AppendMessage(' [%u] 0x%x' % (frame_idx, pc))
frame_idx += 1
idx = idx + 1
pc = int(stack_history_entry.frames[idx])
else:
pc = 0
if idx >= options.max_frames:
result.AppendMessage(
'warning: the max number of stack frames (%u) was reached, use the "--max-frames=<COUNT>" option to see more frames' %
(options.max_frames))
result.AppendMessage('')
def dump_stack_history_entries(options, result, addr, history):
# malloc_stack_entry *get_stack_history_for_address (const void * addr)
expr_prefix = '''
typedef int kern_return_t;
typedef struct $malloc_stack_entry {
uint64_t address;
uint64_t argument;
uint32_t type_flags;
uint32_t num_frames;
uint64_t frames[512];
kern_return_t err;
} $malloc_stack_entry;
'''
single_expr = '''
#define MAX_FRAMES %u
typedef unsigned task_t;
$malloc_stack_entry stack;
stack.address = 0x%x;
stack.type_flags = 2;
stack.num_frames = 0;
stack.frames[0] = 0;
uint32_t max_stack_frames = MAX_FRAMES;
stack.err = (kern_return_t)__mach_stack_logging_get_frames (
(task_t)mach_task_self(),
stack.address,
&stack.frames[0],
max_stack_frames,
&stack.num_frames);
if (stack.num_frames < MAX_FRAMES)
stack.frames[stack.num_frames] = 0;
else
stack.frames[MAX_FRAMES-1] = 0;
stack''' % (options.max_frames, addr)
history_expr = '''
typedef int kern_return_t;
typedef unsigned task_t;
#define MAX_FRAMES %u
#define MAX_HISTORY %u
typedef struct mach_stack_logging_record_t {
uint32_t type_flags;
uint64_t stack_identifier;
uint64_t argument;
uint64_t address;
} mach_stack_logging_record_t;
typedef void (*enumerate_callback_t)(mach_stack_logging_record_t, void *);
typedef struct malloc_stack_entry {
uint64_t address;
uint64_t argument;
uint32_t type_flags;
uint32_t num_frames;
uint64_t frames[MAX_FRAMES];
kern_return_t frames_err;
} malloc_stack_entry;
typedef struct $malloc_stack_history {
task_t task;
unsigned idx;
malloc_stack_entry entries[MAX_HISTORY];
} $malloc_stack_history;
$malloc_stack_history lldb_info = { (task_t)mach_task_self(), 0 };
uint32_t max_stack_frames = MAX_FRAMES;
enumerate_callback_t callback = [] (mach_stack_logging_record_t stack_record, void *baton) -> void {
$malloc_stack_history *lldb_info = ($malloc_stack_history *)baton;
if (lldb_info->idx < MAX_HISTORY) {
malloc_stack_entry *stack_entry = &(lldb_info->entries[lldb_info->idx]);
stack_entry->address = stack_record.address;
stack_entry->type_flags = stack_record.type_flags;
stack_entry->argument = stack_record.argument;
stack_entry->num_frames = 0;
stack_entry->frames[0] = 0;
stack_entry->frames_err = (kern_return_t)__mach_stack_logging_frames_for_uniqued_stack (
lldb_info->task,
stack_record.stack_identifier,
stack_entry->frames,
(uint32_t)MAX_FRAMES,
&stack_entry->num_frames);
// Terminate the frames with zero if there is room
if (stack_entry->num_frames < MAX_FRAMES)
stack_entry->frames[stack_entry->num_frames] = 0;
}
++lldb_info->idx;
};
(kern_return_t)__mach_stack_logging_enumerate_records (lldb_info.task, (uint64_t)0x%x, callback, &lldb_info);
lldb_info''' % (options.max_frames, options.max_history, addr)
frame = lldb.debugger.GetSelectedTarget().GetProcess(
).GetSelectedThread().GetSelectedFrame()
if history:
expr = history_expr
else:
expr = single_expr
expr_options = lldb.SBExpressionOptions()
expr_options.SetIgnoreBreakpoints(True)
expr_options.SetTimeoutInMicroSeconds(5 * 1000 * 1000) # 5 second timeout
expr_options.SetTryAllThreads(True)
expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus)
expr_options.SetPrefix(expr_prefix)
expr_sbvalue = frame.EvaluateExpression(expr, expr_options)
if options.verbose:
print("expression:")
print(expr)
print("expression result:")
print(expr_sbvalue)
if expr_sbvalue.error.Success():
if history:
malloc_stack_history = lldb.value(expr_sbvalue)
num_stacks = int(malloc_stack_history.idx)
if num_stacks <= options.max_history:
i_max = num_stacks
else:
i_max = options.max_history
for i in range(i_max):
stack_history_entry = malloc_stack_history.entries[i]
dump_stack_history_entry(
options, result, stack_history_entry, i)
if num_stacks > options.max_history:
result.AppendMessage(
'warning: the max number of stacks (%u) was reached, use the "--max-history=%u" option to see all of the stacks' %
(options.max_history, num_stacks))
else:
stack_history_entry = lldb.value(expr_sbvalue)
dump_stack_history_entry(options, result, stack_history_entry, 0)
else:
result.AppendMessage(
'error: expression failed "%s" => %s' %
(expr, expr_sbvalue.error))
def display_match_results(
process,
result,
options,
arg_str_description,
expr,
print_no_matches,
expr_prefix=None):
frame = lldb.debugger.GetSelectedTarget().GetProcess(
).GetSelectedThread().GetSelectedFrame()
if not frame:
result.AppendMessage('error: invalid frame')
return 0
expr_options = lldb.SBExpressionOptions()
expr_options.SetIgnoreBreakpoints(True)
expr_options.SetFetchDynamicValue(lldb.eNoDynamicValues)
expr_options.SetTimeoutInMicroSeconds(
30 * 1000 * 1000) # 30 second timeout
expr_options.SetTryAllThreads(False)
expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus)
if expr_prefix:
expr_options.SetPrefix(expr_prefix)
expr_sbvalue = frame.EvaluateExpression(expr, expr_options)
if options.verbose:
print("expression:")
print(expr)
print("expression result:")
print(expr_sbvalue)
if expr_sbvalue.error.Success():
match_value = lldb.value(expr_sbvalue)
i = 0
match_idx = 0
while True:
print_entry = True
match_entry = match_value[i]
i += 1
if i > options.max_matches:
result.AppendMessage(
'warning: the max number of matches (%u) was reached, use the --max-matches option to get more results' %
(options.max_matches))
break
malloc_addr = match_entry.addr.sbvalue.unsigned
if malloc_addr == 0:
break
malloc_size = int(match_entry.size)
offset = int(match_entry.offset)
if options.offset >= 0 and options.offset != offset:
print_entry = False
else:
match_addr = malloc_addr + offset
type_flags = int(match_entry.type)
#result.AppendMessage (hex(malloc_addr + offset))
if type_flags == 64:
search_stack_old = options.search_stack
search_segments_old = options.search_segments
search_heap_old = options.search_heap
search_vm_regions = options.search_vm_regions
options.search_stack = True
options.search_segments = True
options.search_heap = True
options.search_vm_regions = False
if malloc_info_impl(lldb.debugger, result, options, [
hex(malloc_addr + offset)]):
print_entry = False
options.search_stack = search_stack_old
options.search_segments = search_segments_old
options.search_heap = search_heap_old
options.search_vm_regions = search_vm_regions
if print_entry:
description = '%#16.16x: %s' % (match_addr, type_flags_to_description(
process, type_flags, malloc_addr, malloc_size, offset, match_addr))
if options.show_size:
description += ' <%5u>' % (malloc_size)
if options.show_range:
description += ' [%#x - %#x)' % (
malloc_addr, malloc_addr + malloc_size)
derefed_dynamic_value = None
dynamic_value = match_entry.addr.sbvalue.GetDynamicValue(
lldb.eDynamicCanRunTarget)
if dynamic_value.type.name == 'void *':
if options.type == 'pointer' and malloc_size == 4096:
error = lldb.SBError()
process = expr_sbvalue.GetProcess()
target = expr_sbvalue.GetTarget()
data = bytearray(
process.ReadMemory(
malloc_addr, 16, error))
if data == '\xa1\xa1\xa1\xa1AUTORELEASE!':
ptr_size = target.addr_size
thread = process.ReadUnsignedFromMemory(
malloc_addr + 16 + ptr_size, ptr_size, error)
# 4 bytes 0xa1a1a1a1
# 12 bytes 'AUTORELEASE!'
# ptr bytes autorelease insertion point
# ptr bytes pthread_t
# ptr bytes next colder page
# ptr bytes next hotter page
# 4 bytes this page's depth in the list
# 4 bytes high-water mark
description += ' AUTORELEASE! for pthread_t %#x' % (
thread)
# else:
# description += 'malloc(%u)' % (malloc_size)
# else:
# description += 'malloc(%u)' % (malloc_size)
else:
derefed_dynamic_value = dynamic_value.deref
if derefed_dynamic_value:
derefed_dynamic_type = derefed_dynamic_value.type
derefed_dynamic_type_size = derefed_dynamic_type.size
derefed_dynamic_type_name = derefed_dynamic_type.name
description += ' '
description += derefed_dynamic_type_name
if offset < derefed_dynamic_type_size:
member_list = list()
get_member_types_for_offset(
derefed_dynamic_type, offset, member_list)
if member_list:
member_path = ''
for member in member_list:
member_name = member.name
if member_name:
if member_path:
member_path += '.'
member_path += member_name
if member_path:
if options.ivar_regex_blacklist:
for ivar_regex in options.ivar_regex_blacklist:
if ivar_regex.match(
member_path):
print_entry = False
description += '.%s' % (member_path)
else:
description += '%u bytes after %s' % (
offset - derefed_dynamic_type_size, derefed_dynamic_type_name)
else:
# strip the "*" from the end of the name since we
# were unable to dereference this
description += dynamic_value.type.name[0:-1]
if print_entry:
match_idx += 1
result_output = ''
if description:
result_output += description
if options.print_type and derefed_dynamic_value:
result_output += ' %s' % (derefed_dynamic_value)
if options.print_object_description and dynamic_value:
desc = dynamic_value.GetObjectDescription()
if desc:
result_output += '\n%s' % (desc)
if result_output:
result.AppendMessage(result_output)
if options.memory:
cmd_result = lldb.SBCommandReturnObject()
if options.format is None:
memory_command = "memory read --force 0x%x 0x%x" % (
malloc_addr, malloc_addr + malloc_size)
else:
memory_command = "memory read --force -f %s 0x%x 0x%x" % (
options.format, malloc_addr, malloc_addr + malloc_size)
if options.verbose:
result.AppendMessage(memory_command)
lldb.debugger.GetCommandInterpreter().HandleCommand(memory_command, cmd_result)
result.AppendMessage(cmd_result.GetOutput())
if options.stack_history:
dump_stack_history_entries(options, result, malloc_addr, 1)
elif options.stack:
dump_stack_history_entries(options, result, malloc_addr, 0)
return i
else:
result.AppendMessage(str(expr_sbvalue.error))
return 0
def get_ptr_refs_options():
usage = "usage: %prog [options] <EXPR> [EXPR ...]"
description = '''Searches all allocations on the heap for pointer values on
darwin user space programs. Any matches that were found will dump the malloc
blocks that contain the pointers and might be able to print what kind of
objects the pointers are contained in using dynamic type information in the
program.'''
parser = optparse.OptionParser(
description=description,
prog='ptr_refs',
usage=usage)
add_common_options(parser)
return parser
def find_variable(debugger, command, result, dict):
usage = "usage: %prog [options] <ADDR> [ADDR ...]"
description = '''Searches for a local variable in all frames that contains a hex ADDR.'''
command_args = shlex.split(command)
parser = optparse.OptionParser(
description=description,
prog='find_variable',
usage=usage)
parser.add_option(
'-v',
'--verbose',
action='store_true',
dest='verbose',
help='display verbose debug info',
default=False)
try:
(options, args) = parser.parse_args(command_args)
except:
return
process = debugger.GetSelectedTarget().GetProcess()
if not process:
result.AppendMessage('error: invalid process')
return
for arg in args:
var_addr = int(arg, 16)
print("Finding a variable with address %#x..." % (var_addr), file=result)
done = False
for thread in process:
for frame in thread:
var = find_variable_containing_address(
options.verbose, frame, var_addr)
if var:
print(var)
done = True
break
if done:
break
def ptr_refs(debugger, command, result, dict):
command_args = shlex.split(command)
parser = get_ptr_refs_options()
try:
(options, args) = parser.parse_args(command_args)
except:
return
process = debugger.GetSelectedTarget().GetProcess()
if not process:
result.AppendMessage('error: invalid process')
return
frame = process.GetSelectedThread().GetSelectedFrame()
if not frame:
result.AppendMessage('error: invalid frame')
return
options.type = 'pointer'
if options.format is None:
options.format = "A" # 'A' is "address" format
if args:
# When we initialize the expression, we must define any types that
# we will need when looking at every allocation. We must also define
# a type named callback_baton_t and make an instance named "baton"
# and initialize it how ever we want to. The address of "baton" will
# be passed into our range callback. callback_baton_t must contain
# a member named "callback" whose type is "range_callback_t". This
# will be used by our zone callbacks to call the range callback for
# each malloc range.
expr_prefix = '''
struct $malloc_match {
void *addr;
uintptr_t size;
uintptr_t offset;
uintptr_t type;
};
'''
user_init_code_format = '''
#define MAX_MATCHES %u
typedef struct callback_baton_t {
range_callback_t callback;
unsigned num_matches;
$malloc_match matches[MAX_MATCHES];
void *ptr;
} callback_baton_t;
range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
callback_baton_t *lldb_info = (callback_baton_t *)baton;
typedef void* T;
const unsigned size = sizeof(T);
T *array = (T*)ptr_addr;
for (unsigned idx = 0; ((idx + 1) * sizeof(T)) <= ptr_size; ++idx) {
if (array[idx] == lldb_info->ptr) {
if (lldb_info->num_matches < MAX_MATCHES) {
lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
lldb_info->matches[lldb_info->num_matches].size = ptr_size;
lldb_info->matches[lldb_info->num_matches].offset = idx*sizeof(T);
lldb_info->matches[lldb_info->num_matches].type = type;
++lldb_info->num_matches;
}
}
}
};
callback_baton_t baton = { range_callback, 0, {0}, (void *)%s };
'''
# We must also define a snippet of code to be run that returns
# the result of the expression we run.
# Here we return NULL if our pointer was not found in any malloc blocks,
# and we return the address of the matches array so we can then access
# the matching results
user_return_code = '''if (baton.num_matches < MAX_MATCHES)
baton.matches[baton.num_matches].addr = 0; // Terminate the matches array
baton.matches'''
# Iterate through all of our pointer expressions and display the
# results
for ptr_expr in args:
user_init_code = user_init_code_format % (
options.max_matches, ptr_expr)
expr = get_iterate_memory_expr(
options, process, user_init_code, user_return_code)
arg_str_description = 'malloc block containing pointer %s' % ptr_expr
display_match_results(
process,
result,
options,
arg_str_description,
expr,
True,
expr_prefix)
else:
result.AppendMessage('error: no pointer arguments were given')
def get_cstr_refs_options():
usage = "usage: %prog [options] <CSTR> [CSTR ...]"
description = '''Searches all allocations on the heap for C string values on
darwin user space programs. Any matches that were found will dump the malloc
blocks that contain the C strings and might be able to print what kind of
objects the pointers are contained in using dynamic type information in the
program.'''
parser = optparse.OptionParser(
description=description,
prog='cstr_refs',
usage=usage)
add_common_options(parser)
return parser
def cstr_refs(debugger, command, result, dict):
command_args = shlex.split(command)
parser = get_cstr_refs_options()
try:
(options, args) = parser.parse_args(command_args)
except:
return
process = debugger.GetSelectedTarget().GetProcess()
if not process:
result.AppendMessage('error: invalid process')
return
frame = process.GetSelectedThread().GetSelectedFrame()
if not frame:
result.AppendMessage('error: invalid frame')
return
options.type = 'cstr'
if options.format is None:
options.format = "Y" # 'Y' is "bytes with ASCII" format
if args:
# When we initialize the expression, we must define any types that
# we will need when looking at every allocation. We must also define
# a type named callback_baton_t and make an instance named "baton"
# and initialize it how ever we want to. The address of "baton" will
# be passed into our range callback. callback_baton_t must contain
# a member named "callback" whose type is "range_callback_t". This
# will be used by our zone callbacks to call the range callback for
# each malloc range.
expr_prefix = '''
struct $malloc_match {
void *addr;
uintptr_t size;
uintptr_t offset;
uintptr_t type;
};
'''
user_init_code_format = '''
#define MAX_MATCHES %u
typedef struct callback_baton_t {
range_callback_t callback;
unsigned num_matches;
$malloc_match matches[MAX_MATCHES];
const char *cstr;
unsigned cstr_len;
} callback_baton_t;
range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
callback_baton_t *lldb_info = (callback_baton_t *)baton;
if (lldb_info->cstr_len < ptr_size) {
const char *begin = (const char *)ptr_addr;
const char *end = begin + ptr_size - lldb_info->cstr_len;
for (const char *s = begin; s < end; ++s) {
if ((int)memcmp(s, lldb_info->cstr, lldb_info->cstr_len) == 0) {
if (lldb_info->num_matches < MAX_MATCHES) {
lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
lldb_info->matches[lldb_info->num_matches].size = ptr_size;
lldb_info->matches[lldb_info->num_matches].offset = s - begin;
lldb_info->matches[lldb_info->num_matches].type = type;
++lldb_info->num_matches;
}
}
}
}
};
const char *cstr = "%s";
callback_baton_t baton = { range_callback, 0, {0}, cstr, (unsigned)strlen(cstr) };'''
# We must also define a snippet of code to be run that returns
# the result of the expression we run.
# Here we return NULL if our pointer was not found in any malloc blocks,
# and we return the address of the matches array so we can then access
# the matching results
user_return_code = '''if (baton.num_matches < MAX_MATCHES)
baton.matches[baton.num_matches].addr = 0; // Terminate the matches array
baton.matches'''
# Iterate through all of our pointer expressions and display the
# results
for cstr in args:
user_init_code = user_init_code_format % (
options.max_matches, cstr)
expr = get_iterate_memory_expr(
options, process, user_init_code, user_return_code)
arg_str_description = 'malloc block containing "%s"' % cstr
display_match_results(
process,
result,
options,
arg_str_description,
expr,
True,
expr_prefix)
else:
result.AppendMessage(
'error: command takes one or more C string arguments')
def get_malloc_info_options():
usage = "usage: %prog [options] <EXPR> [EXPR ...]"
description = '''Searches the heap a malloc block that contains the addresses
specified as one or more address expressions. Any matches that were found will
dump the malloc blocks that match or contain the specified address. The matching
blocks might be able to show what kind of objects they are using dynamic type
information in the program.'''
parser = optparse.OptionParser(
description=description,
prog='malloc_info',
usage=usage)
add_common_options(parser)
return parser
def malloc_info(debugger, command, result, dict):
command_args = shlex.split(command)
parser = get_malloc_info_options()
try:
(options, args) = parser.parse_args(command_args)
except:
return
malloc_info_impl(debugger, result, options, args)
def malloc_info_impl(debugger, result, options, args):
# We are specifically looking for something on the heap only
options.type = 'malloc_info'
process = debugger.GetSelectedTarget().GetProcess()
if not process:
result.AppendMessage('error: invalid process')
return
frame = process.GetSelectedThread().GetSelectedFrame()
if not frame:
result.AppendMessage('error: invalid frame')
return
expr_prefix = '''
struct $malloc_match {
void *addr;
uintptr_t size;
uintptr_t offset;
uintptr_t type;
};
'''
user_init_code_format = '''
typedef struct callback_baton_t {
range_callback_t callback;
unsigned num_matches;
$malloc_match matches[2]; // Two items so they can be NULL terminated
void *ptr;
} callback_baton_t;
range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
callback_baton_t *lldb_info = (callback_baton_t *)baton;
if (lldb_info->num_matches == 0) {
uint8_t *p = (uint8_t *)lldb_info->ptr;
uint8_t *lo = (uint8_t *)ptr_addr;
uint8_t *hi = lo + ptr_size;
if (lo <= p && p < hi) {
lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
lldb_info->matches[lldb_info->num_matches].size = ptr_size;
lldb_info->matches[lldb_info->num_matches].offset = p - lo;
lldb_info->matches[lldb_info->num_matches].type = type;
lldb_info->num_matches = 1;
}
}
};
callback_baton_t baton = { range_callback, 0, {0}, (void *)%s };
baton.matches[0].addr = 0;
baton.matches[1].addr = 0;'''
if args:
total_matches = 0
for ptr_expr in args:
user_init_code = user_init_code_format % (ptr_expr)
expr = get_iterate_memory_expr(
options, process, user_init_code, 'baton.matches')
arg_str_description = 'malloc block that contains %s' % ptr_expr
total_matches += display_match_results(
process, result, options, arg_str_description, expr, True, expr_prefix)
return total_matches
else:
result.AppendMessage(
'error: command takes one or more pointer expressions')
return 0
def get_thread_stack_ranges_struct(process):
'''Create code that defines a structure that represents threads stack bounds
for all threads. It returns a static sized array initialized with all of
the tid, base, size structs for all the threads.'''
stack_dicts = list()
if process:
i = 0
for thread in process:
min_sp = thread.frame[0].sp
max_sp = min_sp
for frame in thread.frames:
sp = frame.sp
if sp < min_sp:
min_sp = sp
if sp > max_sp:
max_sp = sp
if min_sp < max_sp:
stack_dicts.append({'tid': thread.GetThreadID(
), 'base': min_sp, 'size': max_sp - min_sp, 'index': i})
i += 1
stack_dicts_len = len(stack_dicts)
if stack_dicts_len > 0:
result = '''
#define NUM_STACKS %u
#define STACK_RED_ZONE_SIZE %u
typedef struct thread_stack_t { uint64_t tid, base, size; } thread_stack_t;
thread_stack_t stacks[NUM_STACKS];''' % (stack_dicts_len, process.target.GetStackRedZoneSize())
for stack_dict in stack_dicts:
result += '''
stacks[%(index)u].tid = 0x%(tid)x;
stacks[%(index)u].base = 0x%(base)x;
stacks[%(index)u].size = 0x%(size)x;''' % stack_dict
return result
else:
return ''
def get_sections_ranges_struct(process):
'''Create code that defines a structure that represents all segments that
can contain data for all images in "target". It returns a static sized
array initialized with all of base, size structs for all the threads.'''
target = process.target
segment_dicts = list()
for (module_idx, module) in enumerate(target.modules):
for sect_idx in range(module.GetNumSections()):
section = module.GetSectionAtIndex(sect_idx)
if not section:
break
name = section.name
if name != '__TEXT' and name != '__LINKEDIT' and name != '__PAGEZERO':
base = section.GetLoadAddress(target)
size = section.GetByteSize()
if base != lldb.LLDB_INVALID_ADDRESS and size > 0:
segment_dicts.append({'base': base, 'size': size})
segment_dicts_len = len(segment_dicts)
if segment_dicts_len > 0:
result = '''
#define NUM_SEGMENTS %u
typedef struct segment_range_t { uint64_t base; uint32_t size; } segment_range_t;
segment_range_t segments[NUM_SEGMENTS];''' % (segment_dicts_len,)
for (idx, segment_dict) in enumerate(segment_dicts):
segment_dict['index'] = idx
result += '''
segments[%(index)u].base = 0x%(base)x;
segments[%(index)u].size = 0x%(size)x;''' % segment_dict
return result
else:
return ''
def section_ptr_refs(debugger, command, result, dict):
command_args = shlex.split(command)
usage = "usage: %prog [options] <EXPR> [EXPR ...]"
description = '''Searches section contents for pointer values in darwin user space programs.'''
parser = optparse.OptionParser(
description=description,
prog='section_ptr_refs',
usage=usage)
add_common_options(parser)
parser.add_option(
'--section',
action='append',
type='string',
dest='section_names',
help='section name to search',
default=list())
try:
(options, args) = parser.parse_args(command_args)
except:
return
options.type = 'pointer'
sections = list()
section_modules = list()
if not options.section_names:
result.AppendMessage(
'error: at least one section must be specified with the --section option')
return
target = debugger.GetSelectedTarget()
for module in target.modules:
for section_name in options.section_names:
section = module.section[section_name]
if section:
sections.append(section)
section_modules.append(module)
if sections:
dylid_load_err = load_dylib()
if dylid_load_err:
result.AppendMessage(dylid_load_err)
return
frame = target.GetProcess().GetSelectedThread().GetSelectedFrame()
for expr_str in args:
for (idx, section) in enumerate(sections):
expr = 'find_pointer_in_memory(0x%xllu, %ullu, (void *)%s)' % (
section.addr.load_addr, section.size, expr_str)
arg_str_description = 'section %s.%s containing "%s"' % (
section_modules[idx].file.fullpath, section.name, expr_str)
num_matches = display_match_results(
target.GetProcess(), result, options, arg_str_description, expr, False)
if num_matches:
if num_matches < options.max_matches:
options.max_matches = options.max_matches - num_matches
else:
options.max_matches = 0
if options.max_matches == 0:
return
else:
result.AppendMessage(
'error: no sections were found that match any of %s' %
(', '.join(
options.section_names)))
def get_objc_refs_options():
usage = "usage: %prog [options] <CLASS> [CLASS ...]"
description = '''Searches all allocations on the heap for instances of
objective C classes, or any classes that inherit from the specified classes
in darwin user space programs. Any matches that were found will dump the malloc
blocks that contain the C strings and might be able to print what kind of
objects the pointers are contained in using dynamic type information in the
program.'''
parser = optparse.OptionParser(
description=description,
prog='objc_refs',
usage=usage)
add_common_options(parser)
return parser
def objc_refs(debugger, command, result, dict):
command_args = shlex.split(command)
parser = get_objc_refs_options()
try:
(options, args) = parser.parse_args(command_args)
except:
return
process = debugger.GetSelectedTarget().GetProcess()
if not process:
result.AppendMessage('error: invalid process')
return
frame = process.GetSelectedThread().GetSelectedFrame()
if not frame:
result.AppendMessage('error: invalid frame')
return
options.type = 'isa'
if options.format is None:
options.format = "A" # 'A' is "address" format
expr_options = lldb.SBExpressionOptions()
expr_options.SetIgnoreBreakpoints(True)
expr_options.SetTimeoutInMicroSeconds(
3 * 1000 * 1000) # 3 second infinite timeout
expr_options.SetTryAllThreads(True)
expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus)
num_objc_classes_value = frame.EvaluateExpression(
"(int)objc_getClassList((void *)0, (int)0)", expr_options)
if not num_objc_classes_value.error.Success():
result.AppendMessage('error: %s' %
num_objc_classes_value.error.GetCString())
return
num_objc_classes = num_objc_classes_value.GetValueAsUnsigned()
if num_objc_classes == 0:
result.AppendMessage('error: no objective C classes in program')
return
if args:
# When we initialize the expression, we must define any types that
# we will need when looking at every allocation. We must also define
# a type named callback_baton_t and make an instance named "baton"
# and initialize it how ever we want to. The address of "baton" will
# be passed into our range callback. callback_baton_t must contain
# a member named "callback" whose type is "range_callback_t". This
# will be used by our zone callbacks to call the range callback for
# each malloc range.
expr_prefix = '''
struct $malloc_match {
void *addr;
uintptr_t size;
uintptr_t offset;
uintptr_t type;
};
'''
user_init_code_format = '''
#define MAX_MATCHES %u
typedef int (*compare_callback_t)(const void *a, const void *b);
typedef struct callback_baton_t {
range_callback_t callback;
compare_callback_t compare_callback;
unsigned num_matches;
$malloc_match matches[MAX_MATCHES];
void *isa;
Class classes[%u];
} callback_baton_t;
compare_callback_t compare_callback = [](const void *a, const void *b) -> int {
Class a_ptr = *(Class *)a;
Class b_ptr = *(Class *)b;
if (a_ptr < b_ptr) return -1;
if (a_ptr > b_ptr) return +1;
return 0;
};
typedef Class (*class_getSuperclass_type)(void *isa);
range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
class_getSuperclass_type class_getSuperclass_impl = (class_getSuperclass_type)class_getSuperclass;
callback_baton_t *lldb_info = (callback_baton_t *)baton;
if (sizeof(Class) <= ptr_size) {
Class *curr_class_ptr = (Class *)ptr_addr;
Class *matching_class_ptr = (Class *)bsearch (curr_class_ptr,
(const void *)lldb_info->classes,
sizeof(lldb_info->classes)/sizeof(Class),
sizeof(Class),
lldb_info->compare_callback);
if (matching_class_ptr) {
bool match = false;
if (lldb_info->isa) {
Class isa = *curr_class_ptr;
if (lldb_info->isa == isa)
match = true;
else { // if (lldb_info->objc.match_superclasses) {
Class super = class_getSuperclass_impl(isa);
while (super) {
if (super == lldb_info->isa) {
match = true;
break;
}
super = class_getSuperclass_impl(super);
}
}
}
else
match = true;
if (match) {
if (lldb_info->num_matches < MAX_MATCHES) {
lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
lldb_info->matches[lldb_info->num_matches].size = ptr_size;
lldb_info->matches[lldb_info->num_matches].offset = 0;
lldb_info->matches[lldb_info->num_matches].type = type;
++lldb_info->num_matches;
}
}
}
}
};
callback_baton_t baton = { range_callback, compare_callback, 0, {0}, (void *)0x%x, {0} };
int nc = (int)objc_getClassList(baton.classes, sizeof(baton.classes)/sizeof(Class));
(void)qsort (baton.classes, sizeof(baton.classes)/sizeof(Class), sizeof(Class), compare_callback);'''
# We must also define a snippet of code to be run that returns
# the result of the expression we run.
# Here we return NULL if our pointer was not found in any malloc blocks,
# and we return the address of the matches array so we can then access
# the matching results
user_return_code = '''if (baton.num_matches < MAX_MATCHES)
baton.matches[baton.num_matches].addr = 0; // Terminate the matches array
baton.matches'''
# Iterate through all of our ObjC class name arguments
for class_name in args:
addr_expr_str = "(void *)[%s class]" % class_name
expr_options = lldb.SBExpressionOptions()
expr_options.SetIgnoreBreakpoints(True)
expr_options.SetTimeoutInMicroSeconds(
1 * 1000 * 1000) # 1 second timeout
expr_options.SetTryAllThreads(True)
expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus)
expr_sbvalue = frame.EvaluateExpression(
addr_expr_str, expr_options)
if expr_sbvalue.error.Success():
isa = expr_sbvalue.unsigned
if isa:
options.type = 'isa'
result.AppendMessage(
'Searching for all instances of classes or subclasses of "%s" (isa=0x%x)' %
(class_name, isa))
user_init_code = user_init_code_format % (
options.max_matches, num_objc_classes, isa)
expr = get_iterate_memory_expr(
options, process, user_init_code, user_return_code)
arg_str_description = 'objective C classes with isa 0x%x' % isa
display_match_results(
process,
result,
options,
arg_str_description,
expr,
True,
expr_prefix)
else:
result.AppendMessage(
'error: Can\'t find isa for an ObjC class named "%s"' %
(class_name))
else:
result.AppendMessage(
'error: expression error for "%s": %s' %
(addr_expr_str, expr_sbvalue.error))
else:
result.AppendMessage(
'error: command takes one or more C string arguments')
if __name__ == '__main__':
lldb.debugger = lldb.SBDebugger.Create()
# Make the options so we can generate the help text for the new LLDB
# command line command prior to registering it with LLDB below. This way
# if clients in LLDB type "help malloc_info", they will see the exact same
# output as typing "malloc_info --help".
ptr_refs.__doc__ = get_ptr_refs_options().format_help()
cstr_refs.__doc__ = get_cstr_refs_options().format_help()
malloc_info.__doc__ = get_malloc_info_options().format_help()
objc_refs.__doc__ = get_objc_refs_options().format_help()
lldb.debugger.HandleCommand(
'command script add -f %s.ptr_refs ptr_refs' %
__name__)
lldb.debugger.HandleCommand(
'command script add -f %s.cstr_refs cstr_refs' %
__name__)
lldb.debugger.HandleCommand(
'command script add -f %s.malloc_info malloc_info' %
__name__)
lldb.debugger.HandleCommand(
'command script add -f %s.find_variable find_variable' %
__name__)
# lldb.debugger.HandleCommand('command script add -f %s.heap heap' % package_name)
# lldb.debugger.HandleCommand('command script add -f %s.section_ptr_refs section_ptr_refs' % package_name)
# lldb.debugger.HandleCommand('command script add -f %s.stack_ptr_refs stack_ptr_refs' % package_name)
lldb.debugger.HandleCommand(
'command script add -f %s.objc_refs objc_refs' %
__name__)
print('"malloc_info", "ptr_refs", "cstr_refs", "find_variable", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.')
|