preface

Recently, I read a colleague’s series of articles titled “iOS Memory Things”. In one of the articles, he told me that when he studied the memory management in WebKit, he found that he could measure the memory by phys_footprint, and the result was basically the same as the value displayed by Xcode Debug. Why not directly reverse Xcode and learn how Xcode debug app implements memory monitoring? Just recently in self-learning reverse knowledge, incidentally also come to practice hand ~

To prepare a small project to run, we can see the Memory report message in the Debug panel

LLDB and hopper can be debugged by attaching Xcode directly

➜  ~ lldb -n Xcode
(lldb) process attach --name "Xcode"
Process 969 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x00007fffe2bcb34a libsystem_kernel.dylib`mach_msg_trap + 10
libsystem_kernel.dylib`mach_msg_trap:
->  0x7fffe2bcb34a <+10>: retq
    0x7fffe2bcb34b <+11>: nop

libsystem_kernel.dylib`mach_msg_overwrite_trap:
    0x7fffe2bcb34c <+0>:  movq   %rcx, %r10
    0x7fffe2bcb34f <+3>:  movl   $0x1000020, %eax          ; imm = 0x1000020
Target 0: (Xcode) stopped.

Executable module set to "/Applications/Xcode.app/Contents/MacOS/Xcode".
Architecture setTo: x86_64H-apple-Macosx. (LLDB) c Process 969 pollution (LLDB) Copy code To the Xcode Debug panel, you can directly see the memory information when the APP is running. Let's see if the memory bar responds to a click. ~ (LLDB) b -[NSResponder mouseUp:] Breakpoint 1:where = AppKit`-[NSResponder mouseUp:], address = 0x00007fffcb070177
Process 969 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00007fffcb070177 AppKit`-[NSResponder mouseUp:]
AppKit`-[NSResponder mouseUp:]:
->  0x7fffcb070177 <+0>: pushq  %rbp
    0x7fffcb070178 <+1>: movq   %rsp, %rbp
    0x7fffcb07017b <+4>: popq   %rbp
    0x7fffcb07017c <+5>: jmp    0x7fffcaf94724            ; forwardMethod
Target 0: (Xcode) stopped.(lldb)
Copy the code

Copy the code because Xcode must be compiled with the x86_64 schema, by Po $rdi, you can see that the click method object is <NSTextField: 0x7FB7aed38280 >. First instinct tells me, is an NSTextField like a UITextField, with a text property that can be assigned? If you look at the Apple documentation, its parent NSControl class has a stringValue property that you can set, set a breakpoint, and if you see that the breakpoint is triggered when memory changes on the panel, bt it, you can see the following information (note that the breakpoint is triggered by the NSTextField that shows memory)

As a developer, it’s especially important to have a learning atmosphere and a networking community, and this is one of my iOS networking groups:651612063Enter group password 111, no matter you are small white or big ox welcome to enter, share BAT, Ali interview questions, interview experience, discuss technology, we exchange learning and growth together!

(lldb) bt
* thread = 'MainQueue: # 1, the queue - [DBGLLDBSession processProfileDataString:] _block_invoke', the stop reason = 1.1 breakpoint
  * frame #0: 0x00007fffcaef1897 AppKit`-[NSControl setStringValue:]
    frame #1: 0x0000000125f5b305 DebuggerUI`__54-[DBGGaugeMemoryEditor _setupTopSectionComponentViews]_block_invoke + 955
    frame #2: 0x0000000106e49e36 DVTFoundation`-[DVTObservingBlockToken observeValueForKeyPath:ofObject:change:context:] + 610
    frame #3: 0x00007fffced5035d Foundation`NSKeyValueNotifyObserver + 350
    frame #4: 0x00007fffced4fbf4 Foundation`NSKeyValueDidChange + 486
    frame # 5: 0x00007fffcee8e867 Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:usingBlock:] + 944
    frame #6: 0x00007fffced1395d Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 60
    frame #7: 0x00007fffced7c23b Foundation`_NSSetObjectValueAndNotify + 261
    frame #8: 0x0000000106e8a742 DVTFoundation`__DVTDispatchAsync_block_invoke + 97
    frame #9: 0x00007fffe2a77524 libdispatch.dylib`_dispatch_call_block_and_release + 12
    frame #10: 0x00007fffe2a6e8fc libdispatch.dylib`_dispatch_client_callout + 8
    frame #11: 0x00007fffe2a849a0 libdispatch.dylib`_dispatch_queue_serial_drain + 896
    frame #12: 0x00007fffe2a77306 libdispatch.dylib`_dispatch_queue_invoke + 1046
    frame #13: 0x00007fffe2a7b908 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 505
    frame #14: 0x00007fffcd35bbc9 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    frame #15: 0x00007fffcd31cc0d CoreFoundation`__CFRunLoopRun + 2205
    frame #16: 0x00007fffcd31c114 CoreFoundation`CFRunLoopRunSpecific + 420
    frame #17: 0x00007fffcc87cebc HIToolbox`RunCurrentEventLoopInMode + 240
    frame #18: 0x00007fffcc87ccf1 HIToolbox`ReceiveNextEventCommon + 432
    frame #19: 0x00007fffcc87cb26 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 71
    frame #20: 0x00007fffcae15a54 AppKit`_DPSNextEvent + 1120
    frame #21: 0x00007fffcb5917ee AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 2796
    frame #22: 0x000000010743d98e DVTKit`-[DVTApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 390
    frame #23: 0x00007fffcae0a3db AppKit`-[NSApplication run] + 926
    frame #24: 0x00007fffcadd4e0e AppKit`NSApplicationMain + 1237
    frame #25: 0x00007fffe2aa4235 libdyld.dylib`start + 1
    frame #26: 0x00007fffe2aa4235 libdyld.dylib`start + 1
Copy the code

From the function call stack, we can see that the NSTextField value is changed by a value of KVO. Image lookup -rn ‘[dbggaugemoryeditor \, You can find it in the/Applications/Xcode. The app/Contents/PlugIns/DebuggerUI ideplugin/Contents/MacOS/DebuggerUI, Pull the binary file to see – in hooper [DBGGaugeMemoryEditor _setupTopSectionComponentViews] _block_invoke implementation.

By looking at the corresponding implementation, you can know that it is through the debugSession instance to obtain relevant information, combined with the call stack information, the debugSession must be DBGLLDBSession. Again, with LLDB, We can find DBGLLDBSession is located in the/Applications/Xcode. The app/Contents/PlugIns/DebuggerLLDB ideplugin/Contents/MacOS/DebuggerLLDB, Hooper tried to see how it was implemented, but didn’t see anything useful. You can only continue to try LLDB breakpoints. Po $RDX prints its arguments and seems to produce a strange string? !

(lldb) b -[DBGLLDBSession processProfileDataString:]
Breakpoint 3: where = DebuggerLLDB`-[DBGLLDBSession processProfileDataString:], address = 0x0000000115f99c52
(lldb) c
Process 4489 resuming
Process 4489 stopped
* thread #30, name = '
      
       ', stop Reason = breakPoint 3.1
      
    frame #0: 0x0000000115f99c52 DebuggerLLDB`-[DBGLLDBSession processProfileDataString:]
DebuggerLLDB`-[DBGLLDBSession processProfileDataString:]:
->  0x115f99c52 <+0>: push   rbp
    0x115f99c53 <+1>: mov    rbp, rsp
    0x115f99c56 <+4>: push   r15
    0x115f99c58 <+6>: push   r14
Target 0: (Xcode) stopped.
(lldb) po $rdi
<DBGLLDBSession: 0x7f9c998bec90>

(lldb) po $rdxnum_cpu:8; host_user_ticks:3332988; host_sys_ticks:2148237; host_idle_ticks:23546214; elapsed_usec:1513647973058229; task_used_usec:43128; thread_used_id:1; thread_used_usec:841463; thread_used_name:; thread_used_id:4; thread_used_usec:595; thread_used_name:; thread_used_id:5; thread_used_usec:2130; thread_used_name:576562546872656164; thread_used_id:6; thread_used_usec:3012; thread_used_name:636f6d2e6170706c652e75696b69742e6576656e7466657463682d746872656164; thread_used_id:11; thread_used_usec:255; thread_used_name:; total:17179869184; used:14274596864; rprvt:0; purgeable:0; anonymous:57823232; energy:98435210380;Copy the code

Host_sys_ticks = LLDB; host_sys_ticks = LLDB; host_sys_ticks = LLDB; I first checked the LLDB version of this machine (llDB-900.0.45), but I could not find this version of LLDB on the official website of Apple Open Source. I have no choice but to clone a copy of the latest code on the LLDB website. Although I don’t know which LLDB version Apple is based on, I can’t go wrong by looking at the latest implementation

The string is generated in STD :: String MachTask::GetProfileData(DNBProfileDataScanType scanType). Such as CPU, memory, etc.

Just to venture a guess, Xcode’s memory monitoring is regularly displayed by getting information from this method of the debugserver!!

Also, for the debugServer, see the introduction here. Simply put, it is a “remote debug” server running on ios that accepts LLDB front-end commands. On jailbreak devices, you can do a lot of tricks with it, but I won’t list them here.

Verify the initial stripping of memory profile code implementation as follows:

static void GetPurgeableAndAnonymous(task_t task, uint64_t &purgeable,
                                     uint64_t &anonymous) {
#if defined(TASK_VM_INFO) && TASK_VM_INFO >= 22

  kern_return_t kr;
  mach_msg_type_number_t info_count;
  task_vm_info_data_t vm_info;

  info_count = TASK_VM_INFO_COUNT;
  kr = task_info(task, TASK_VM_INFO_PURGEABLE, (task_info_t)&vm_info,
                 &info_count);
  if (kr == KERN_SUCCESS) {
    purgeable = vm_info.purgeable_volatile_resident;
    anonymous =
        vm_info.internal + vm_info.compressed - vm_info.purgeable_volatile_pmap;
  }

#endif
}
Copy the code

Looking at the byte value of anonymous, 57823232 corresponds to 55.1445007MB, which is the value displayed in the DEBUG memory panel!

Attached here is the WebKit memory calculation formula found by my colleague, which can be compared and understood. For details, see here and search phys_footprint.

phys_footprint = (internal - alternate_accounting) + (internal_compressed - alternate_accounting_compressed) + iokit_mapped + purgeable_nonvolatile + purgeable_nonvolatile_compressed + page_table
Copy the code

specific

Originally, it should have been directly copied out the above code for further confirmation. But accidentally found a lazy way. Since Xcode gets the information from the DebugServer, is there any way to directly interact with the DebugServer to get the information?

Looking at the LLDB code, the LLDB front end does have corresponding commands to trigger the execution of the debugServer.

STD :: String MachTask::GetProfileData(DNBProfileDataScanType scanType) is executed when RNBRemote receives qGetProfileData, The LLDB can directly send packet commands to the debugserver through the process Plugin packet send command.

In other words, command validation is performed directly on the Xcode terminal

With the use of LLDB scripts, visual verification is not difficult.

Of course, eventually you might want to just copy out the code and verify it, but try that later.

conclusion

Although the full text is smooth at first glance, but as a reverse novice, there are still many problems in the middle, but the harvest is also a lot of drops

LLDB and Hopper are really powerful, so it’s worth taking a closer look at the LLDB source code, and there are some interesting places to explore.

By exploring the principles of Xcode Debug, we can learn its profile implementation and do a set of performance monitoring ourselves.

recommended

Recommended article there is 2020 factory interview address

Personal article

Click to enter group communication password :111