This chapter will be more of a catch-all principle for dtrace, destroying the action (yes!), and how to use the DTrace with Swift. Before I go into the theory, I'll start by telling you something exciting. I will first explain how to use Swift with DTrace and go into the concept of letting you cry and want to fall asleep. Believe me, it's going to be fun!
In this section, you will learn some other ways dtrace can dissect your code, and how to enhance existing code without moving the executable with a single finger.
It's amazing!
Begin
We didn't extract it on Ray Wenderlich. There is another project in this section that is inspired by the theme of the movie with Ray's name.
Open the finding Ray program in the starter directory. No special settings need to be made. Just build and run the iphone 7Plus Simulator.
This project was written mainly in Swift, although many of Swift's subclasses inherit from NSObject. Because it uses a lot of uikit components, and Uikit still has a lot of objective-c code.
DTrace does not know which class the swift code inherits from, because it appears to dtrace that they are all the same. You can still parse the code of the Objective-c code through the SWIFT code, as they all inherit from NSObject through Objc$target. At the bottom, it seems that Swift's class has no way of implementing a new method or overriding the parent class, and you can't see them in any objective-c probes.
The theory of DTrace and Swift
Let's discuss how to use DTrace to dissect Swfit code. Some good ideas on the internet are worth considering.
First, the good news is that Swift is well-compatible with DTrace modules! That is, you can easily filter out Swift code in a particular module. This module may be the name of the target in your Xcode that contains the SWIFT code (unless you change the target name in Xcode's build settings).
This means that you can filter out the implementation of the following swift code in the Sometarget module.
Pid$target:sometarget::entry
This creates a probe in the Sometarget module where each function implementation signature begins. Because Pid$target is behind all non-objective-c code, these probes also capture C or C + + code, But in the second part you will see that we can easily filter out the well-designed query statements.
Now for the bad news. Because the information about the module has been removed, the Swift method's class and function names are in the DTrace section (also known as probefunc). This means that you need to be more creative in the DTrace query code.
In addition, the name of the SWIFT function has been disrupted. This means that you need to transfer dtrace output to a command that can rearrange the scrambled swift function names to make it easier to understand.
Fortunately, there is a very good terminal command in Swift called Swift-demangle, and we can find it here:
/applications/xcode.app/contents/developer/toolchains/xcodedefault.xctoolchain/usr/bin/swift-demangle
Set the following symbolic link through the terminal so that you do not need to re-enter this path every time you use this command to reorganize the SWIFT function name.
Ln-s/applications/xcode.app/contents/developer/toolchains/xcodedefault.xctoolchain/usr/bin/swift-demangle/usr/ local/bin/
To add the path to the/usr/local/bin/, you can simply enter the Swift-demangle to use this command:
Note: You can also run this command from Xcrun Swift-demangle, but the performance overhead requires special attention when profiling DTrace code with multiple Swift probes. This symbolic link is the most important solution to this problem when using DTrace with Swift.
Let's look at an example of using the DTrace probe through Swift.
Imagine that you have a Uiviewcontroller subclass Viewcontroller, which only overrides the Viewdidload method. Just like this:
Class Viewcontroller:uiviewcontroller {
Override Func Viewdidload () {
Super.viewdidload ()
}
}
If you want to create a breakpoint on this method, the full name of the breakpoint should look like this:
SomeTarget.ViewController.viewDidLoad () ()
Don't be surprised. You have defeated the concept of the first part. But what does the function name look like after it's been disrupted? This is the SWIFT function returned by the efficient C function. He looks like this:
_TTOFC10SOMETARGET14VIEWCONTROLLER11VIEWDIDLOADFTT
If you don't believe me, you can try the following code in the terminal:
$ echo "_ttofc10sometarget14viewcontroller11viewdidloadftT" | Xcrunswift-demangle
You will get the following output:
@objc SomeTarget.ViewController.viewDidLoad () ()
Accident! Again got the name of the function before the upset.
The name of the upset is that DTrace sees the name in the swift probe. If you want to search sometargettarget (easy-to-remember name, right?) In each of the Viewdidload () implemented with Swift, you can create a probe like this:
Pid$target:sometarget:sometargetviewdidload*:entry
This sentence is very efficient, it means that "as long as Sometarget and viewdidload in the function area, give me this probe."
It's time to put the theory into practice in the finding Ray program.
DTrace and Swift Exercises
If the finding Ray app is not running yet, use the iphone 7 plus simulator to get it up and running.
Open a new terminal window and enter the following command:
sudo dtrace-n ' pid$target:finding? Ray::entry '-Ppgrep "Finding Ray"
I intentionally selected an item with a space in the name. To remind you that you need to solve the whitespace problem in Xcode target when using DTrace scripts. The Probemod section uses a wildcard character as a space. In addition, you need to wrap the statement in the PGREP process name, otherwise the sentence will not take effect.
After you enter your password, in the finding Ray module you will receive a total of 240 non-objective-c function probes.
Click and drag on the simulator and watch the method captured in the terminal.
Picture. png
The name is not upset. Fortunately, you can easily solve this problem with the Swift-demangle command you saw earlier.
Use CTRL + C to kill the DTrace script, so that after the swift-demangle behind the next pipe, it should look like this:
sudo dtrace-n ' pid$target:finding? Ray::entry '-P pgrep "Finding Ray"
|swift-demangle-simplified
Make sure that the new Drace script is ready to run, then drag Ray Wenderlich in the emulator and look at the output in the terminal.
Kill the DTrace script and replace it with the following command:
sudo dtrace-qn ' pid$target:finding? ray::entry {printf ("%s\n", Probefunc);} '-P pgrep "Finding Ray"
| swift-demangle-simplified
It's tricky, but you've added the-Q (or--quiet) option. This will tell DTrace not to show the serial number of the probe you found, and do not display the default output when a probe is penalized. Fortunately, you also added a printf statement to manually output the Probefunc.
Wait for DTrace to start to complete, and then drag again.
It's interesting. Unfortunately, you'll still get some of the methods that I didn't write out of the Swift compiler generation. You don't want to see any methods generated by the Swift compiler. You just want to see the code I wrote in the Swfit class.
Kill the previous DTrace script and then add the description of the probe to the code that contains only your implementation, not the Swfit compiler:
sudo dtrace-qn ' pid$target:finding? Ray:finding? Ray: Entry
{printf ("%s\n", Probefunc);} '-P pgrep "Finding Ray"
| swift-
Demangle-simplified
Compile run and then drag Ray. Notice the difference?
Here you have added a finding to the function ? Ray. As you know in breakpoints, this module contains all the SWIFT functions. This filters out any of the compiler-generated swift methods, which are very clean.
According to our previous assumptions, you have already exported the pointers to the previously conceived objects, as tobjectivec.py in the previous chapters.
Kill the script in the terminal and enter the following:
sudo dtrace-qn ' pid$target:finding? Ray:finding? Ray: Entry
{printf ("0x%p%s\n", arg0, Probefunc);} '-P pgrep "Finding Ray"
|
Swift-demangle-simplified
Run it and drag ray. You will get some output similar to the following:
0x7f8b17d02e40 MotionView.transformAmount.getter
0x7f8b17d02e40 MotionView.motionAmount.getter
0x7f8b17d02280 type metadata accessor for Motionview
If you pause the execution of the finding ray executable file via Lldb and copy, paste, and then po one of the addresses, you will Fan County this address reference to a valid object.
Finally, add a little. Remove the code that prints the pointer in the script, replace it with the trace Swift function entry and exit, and then use DTrace's flowindent to show where a function is performing relative to the other functions:
sudo dtrace-qfn ' pid$target:finding? Ray:finding? Ray:R
{printf ("%s\n", Probefunc);} '-P pgrep "Finding Ray"
| swift-
Demangle-simplified
Here are a few things to keep in mind. You have added the-F option for flowindent. Check the name part of the probe description, R. What is this?
From the dtrace point of view, most functions in a process have portals, return points, and function offsets for each assembly instruction. These offsets are given in the form of 16. This means "give me all names that contain R letters." This returns the entry and return points in the name described by the probe, but optimizes any function offsets because the assembly starts with F only. It's smart, isn't it?
With each of the available Swift function entries and return points, you can clear the see what functions are executed and where they are executed. Wait for DTrace to start, and then drag and drop for Ray Wenderlich's face. You'll get the perfect output like this:
Picture. png
The amount ..... You're supposed to be kicking out one of them!
DTrace variables and Control flow
Now you will learn some theories that need to be used in the remainder of this chapter.
There are several ways dtrace can create and reference variables in your scripts. They have pros and cons in terms of speed and convenience when using DTrace.
Scalar variable (standard quantitative measure)
The first way to create a variable is to use the scalar variable. These simple variables can only carry some fixed-size data. You do not need to declare the type of scalar variables, or the type of other variables in your dtrace script. I prefer to use scalar variable as a Boolean value in the DTrace script. This is due to the limitations of the DTrace conditional statement logic-you can only really separate the logic with the predicate and ternary operators.
For example, here is a typical example of using scalar variable:
#!/usr/sbin/dtrace-s
#pragma D option quiet
Dtrace:::begin
{
IsSet = 0;
Object = 0; }
Objc$target:nsobject:-init:return/isset = = 0/
{object = Arg1;
IsSet = 1; }
Objc$target:::entry/isset && Object = = arg0/
{
printf ("0x%p%c[%s%s]\n", arg0, Probefunc[0], Probemod,
(string) &probefunc[1]);
}
This script declares two scalar variables:isset this scalar variables checks and determines whether the object scalar variables is assigned. If not, the script assigns the next object to the object variable. This script will track all objective-c methods that use the object variable.
Local clause variable (clause-local variables)
The next step is the local clause variable. They use this-> to represent the variable name on the right and can always think of any type, including the char* type. Local clause variables can survive the same probe. If you try to refer to them by a different probe, that doesn't work. For example, take a look at the following code:
Pid$target::objc_msgsend:entry
{
This->object = arg0;
}
Pid$target::objc_msgsend:entry/this->object! = 0/{
/do some logic here/
}
Obc$target:::entry {
This-f = this->object; / Won ' t work since different probe /
}
I tend to do my best to paste clause local variables, because it's fast and I don't need to release them manually, as I did for the next type of variable ...
Thread-local variable (thread-local variables)
Thread local variables provide a lot of flexibility in running speed. In addition, you need to release them manually, or you will incur a memory leak. Thread Local variables can be accessed and used by adding self-> to the variable name.
One advantage of thread-local variables is that they can be used by different probes, like this:
Objc$target:nsobject:init:entry {
Self->a = arg0;
}
Objc$target::-d ealloc:entry/arg0 = = Self->a/{
Self->a = 0;
}
This assigns the initialized object to Self->a. When this object is released, you also need to manually release the object that was assigned to a by setting a to 0.
Discussing the variables in DTrace has deviated from our topic, let's say how to use variables to execute conditional statements.
DTrace condition
The conditional sentences within DTrace are extremely limited. There are no statements like If-else in DTrace. This is a wise choice because DTrace scripting is designed to be quick and easy.
However, this does not mean that you will encounter problems when you want to execute conditional statements on the information contained in a particular probe or probe. Around this problem, there are two main ways to get you to execute a conditional statement.
The first method is to use the ternary operator.
Take a look at the logic in Objective-c:
int B = 10;
int a = 0;
if (b = = 10) {
A = 5;
} else {
A = 6; }
This logic can be overridden in DTrace to:
b = 10;
A = 0;
A = b = = 10? 5:6
Here's an example with no else statement:
int B = 10;
int a = 0;
if (b = = 10) {
a++; }
In the form of DTrace, it looks like this:
b = 10;
A = 0;
A = b = = 10? A + 1:a
Another solution is to use multiple DTrace statements to determine a situation. The first statement sets the necessary information for the second statement, which depends on whether the second statement performs an action in the judgment sentence.
I know you've forgotten all the terms in these dtrace components so let's take a look at an example.
For example, if you want to track every call from start to finish in a function. Normally, I would recommend that you set up a DTrace script to capture each call and then use LLDB to execute the command. But what are the things you can only do in DTrace?
In this particular example, you want to use the following DTrace script to track all-[uiviewcontroller Initwithnibname:bundle:] The method that is called when executed:
#!/usr/sbin/dtrace-s
#pragma D option quiet
Dtrace:::begin
{
trace = 0;
}
Objc$target:target:uiviewcontroller:-initwithnibname?bundle?:entry {
trace = 1}
Objc$target:target:::entry/trace/{
printf ("%s\n", Probefunc);
}
Objc$target:target:uiviewcontroller:-initwithnibname?bundle?:return {
trace = 0}
Initwithnibname:bundle: Once execution is complete, the tracking variable is set. From this point on, every single Objective-c method will be displayed after Initwithnibname:bundle: return.
You can't use annoying loop statements and conditional statements first when writing DTrace scripts, but think about how you've become a person accustomed to brain teasers that don't rely on common grammatical habits.
It's time for the head to discuss another big problem: Check the memory of the process in your DTrace script.
Check process Memory
It may have come as a surprise, but the DTrace script you wrote earlier was executed in its own kernel. That's why they're so fast and why you don't need to change any code when you're doing dynamic tracking in a compiled program. are directly accessible to the kernel!
DTrace has probes all over your computer. There are probes in the kernel, probes for the user area, and even probes that use FBT to describe the connection between the kernel and the user area.
Here's a picture of a very, very small part of your computer's probe.
Picture. png
Focus your manager on the two of the thousands of probes is the system call open, and the other is the open_nocancel of the system call. Both functions are now in the kernel and are used to open any type of file, whether it is read-only or read-only or can be written.
The Declaration of the Open function in the system looks like this:
int open (const char *path, int oflag, ...);
In essence, the open function sometimes calls the Open_nocancel function, and the declaration of the Open_nocancel function is this:
int open_nocancel (const char path, int flags, mode_t mode);
The first parameter of both functions is a char type. In front of the DTrace probe you have grabbed the parameter from the function using arg0 and arg1. What you have not done is to dereference these pointers to see their data. Like the previous Sbvalue chapter, you can use DTrace to view memory and even get the string of the first argument in the open function of the system call.
Although you already understand. The DTrace script is executed in the kernel. The ARGX parameter has been given to you, but these are pointers to the values in the program's memory space. However, DTrace is executed in the kernel. So you need to manually copy any data that you read in the kernel's memory space.
This is achieved through the copyin and COPYINSTR functions. Copyin has an address with the number of data you want to read, and copyinstr copies a char*.
In terms of the functions of the Open family called by the system, you can read the first parameter as a string using the following DTrace statement:
sudo dtrace-n ' syscall::open:entry {printf ("%s", Copyinstr (arg0));} '
For example, if a process with a PID of 12345 tries to open/applications/someapp.app/, DTrace can use COPYINSTR (arg0) to read the first parameter.
Picture. png
In this example, DTrace reads arg0, which in this case is equivalent to 0x7fff58034300. In the COPYINSTR function, the memory address 0x7fff58034300 will be dereferenced and crawled to the char* representing pathname, "/applications/someapp.app/".
Application of Open Syscalls
With your knowledge of checking the process memory, create a DTrace script that detects the system calling the Open family function. Enter the following in the terminal:
sudo dtrace-qn ' Syscall::open: entry {printf ("%s opened%s\n",
Execname, Copyinstr (arg0)); Ustack (); }‘
This will print out the open (or open_nocancel) contents of the system that the program calls in the stack record in the user area.
DTrace is powerful, isn't it?
The functions of the Open family called by the system are centered on the finding Ray process.
sudo dtrace-qn ' syscall::open: Entry/execname = = "Finding Ray"/
{printf ("%s opened%s\n", Execname, Copyinstr (arg0)); Ustack ();} '
Note: The actions you perform with dtrace in the terminal sometimes produce some errors stderr
. Depending on these errors, you can use the DTrace judgment clause you create to check whether the input is appropriate to bypass these errors, or you may be able to filter your probe description with a small number of query probes. Hint, Ignores all the 2>/dev/null
errors that dtrace produces by adding a single command-line program. This can tell you efficiently that any content that the DTrace single command-line output stderr
will be ignored. I often use this method to solve the error-prone probes, but ignore any errors I make when I track.
Re-build and run the program.
The stack record will now show only any open functions that are called by the system in the finding Ray app . Run the program in the simulator and see if you can get him to output a content!
Filter open Syscalls via paths
In the finding Ray project, I remember doing some things with ray.png pictures, but I don't remember being in that position. The good news is that I used DTrace to find the location where Ray.png was opened along grep.
Kill your current DTrace script and add a grep query like this:
sudo dtrace-qn ' syscall::open*:entry/execname = = "Finding Ray"/
{printf ("%s opened%s\n", Execname, Copyinstr (arg0)); Ustack ();} ' |
grep ray.png-a40
This will graft all the output to grep and search for references to any ray.png images. If you search, print out the next 40 lines of code.
Note: /usr/bin/
There is a pretty scary named DTrace script in your computer, and opensnoop
it has a lot of options to detect the function of the system call's open
family and it is simpler to use than the script written. But if you only use what's ready, you won't learn anything. Right? You can take a look at this script when you are free. What it can do will not let you down.
Here's a more elegant solution that doesn't need plumbing (well, at least in my opinion) to implement this feature. You can use the predicate sentence in the DTrace statement to search for the ray.png string of the char* type entered by the user area.
You will use the STRSTR function of DTrace to do this check. This function takes two parameters and returns a second string that first appears in the first string in a pointer. If a second string is not found in the first string, it will return null. This means that you can search for a path containing ray.png by checking if the return value in the sentence is null.
The improved DTrace script becomes more and more complex and ugly, just like this:
sudo dtrace-qn ' syscall::open*:entry/execname = = "Finding Ray" &&
Strstr (Copyinstr (arg0), "ray.png")! = NULL/{printf ("%s opened%s\n",
Execname, Copyinstr (arg0)); Ustack (); }‘
Build and rerun the program.
You lose the grep pipeline and use a conditional sentence to determine if the file path of XXX in the Findingray process contains ray.png.
In addition, you can easily pinpoint the stack record that is responsible for opening ray.png images.
DTrace and destructive operations
Note: What I'm going to show you is very dangerous. I repeat: The next operation is very dangerous.
If you mess up a command you may lose some of your favorite images. Only on your own hard drive!
In fact, it is safe to close all applications that are using images (for example, Photos, PhotoShop, and so on). I and the Razeware team are not responsible for any of the things that happen on your computer.
We've already warned you!
Uh ... I bet the legal statement above makes you nervous!
You will use DTrace to perform a destructive operation. That is, normally dtrace will only detect your computer, but now you will actually modify the logic of your program. You will detect functions of the open family of system calls executed by finding Rayapp. If there is a system call to the open function, one argument contains the word. png (that is, the path parameter of the char* type it opens), you will replace that parameter with a different PNG image.
These can be implemented by the two DTrace commands, Copyout and copyoutstr. In this example you will explicitly use COPYOUTSTR. You will notice the names like Copyin and Copyinstr. In and out of this context indicate the directory of the data you copied, both the directory in which DTrace can read the data, and the directory where the external process can read it.
in this projects directory, there is a picture called troll.png. Use the. + N to create a new Finder window, then press the? + Shift + H to enter your home directory. Drag troll.png to this directory ( You can delete it at the end of this chapter). Here's a crazy way to do this, only I can bear! Why do you need to do it? You will overwrite the memory in an exciting program. There is only a limited amount of space allocated for this string in the memory of this program. This could be a very long string because you are in the iphone simulator and your process (most likely) is reading the picture in his own sandbox.
Do you remember searching for ray.png? Here is the full path on my computer. Your path is different from mine:
/users/derekselander/library/developer/coresimulator/devices/
97f8be2c-4547-470c-955f-3654a8347c41/data/containers/bundle/application/
102bde66-79cb-453c-ba71-4062b2bc5297/finding Ray.app/ray.png
Our plan is to use DTrace to read a picture with a shorter path, which should look like this in the program's Memory:
/users/derekselander/troll.png\0veloper/coresimulator/devices/
97f8be2c-4547-470c-955f-3654a8347c41/data/containers/bundle/application/
102bde66-79cb-453c-ba71-4062b2bc5297/finding Ray.app/ray.png
Did you see the char* here? This is the Terminator. So essentially this string is simply:
/users/derekselander/troll.png
Because that's how a string is terminated with null!
Get the length of your path
After you've finished writing the data, you need to figure out how many chars the Troll.png picture's completion path is. I know the length of my path, unfortunately, I don't know the length of your path.
In the terminal, enter:
Echo ~/troll.png
You will get the full path to the Troll.png picture. You will paste this path into your script later. You also need to find the length of the string in the terminal.
echo ~/troll.png | Wc-m
In my case,/users/derekselander/troll.png is 31 characters. But obviously: you need to count the Terminator null in. This means that the length of the new string I need to insert is 32 or more.
The arg0 parameter in the open* function points to some data in memory. If you write a string longer than this string to this location, then this time destroys the memory and exits the program. Obviously, you don't want this to happen, so you need to copy the troll.png into a directory with a shorter path character.
You also need to check with DTrace's judgment to make sure you have enough controls to store the string. Come on, you're a rigorous and diligent programmer, aren't you?
Enter the following in the terminal, replacing/users/derekselander and 32 with your values:
sudo dtrace-wn ' syscall::open*:entry/execname = = "Finding Ray" && arg0
0xFFFFFFFE && strstr (Copyinstr (arg0), ". png")! = NULL &&
Strlen (Copyinstr (arg0)) >=/{this->a = "/users/derekselander/
Troll.png "; Copyoutstr (This->a, arg0, 32); }‘
Re-build and run the finding Ray application after the new DTrace script is activated.
If your execution is smooth, when the finding Ray process tries to open a file containing. png characters, you will return a troll.png instead.
Picture. png
Other destructive operations
In addition to Copyoutstr and Copyout, DTrace has some other disruptive operations to note:
? Stop (void): This freezes the currently running user process (given by the internal PID parameter). This is a perfect way if you want to stop executing a user process and attach the LLDB attach to the process and browse further.
? raise (int signal): This is responsible for adding a signal to the probe to the process.
? System (String program, ...): This allows you to execute a command just like in the terminal. It has an additional benefit that you can access all of the variables within DTrace, such as Execname and Probemod, for printf style formatting.
I encourage you to look at this destructive action (especially the Stop () operation) when you are free. However, use these system functions with care. If used incorrectly you can really easily cause the girder to break.
Why are we learning this?
There are many powerful dtrace scripts in your MacOS machine. You can capture them by Man-k DTrace, and then slowly figure out what these scripts do. In addition, you can learn a lot of knowledge in their code. Remember, they are scripts, not compiled executables, so the source code is a fair game.
Similarly, you must be careful when performing destructive operations. In other words, you can put Aywenderlich anywhere on your computer.
Picture. png
Is this not officially the effect you want?
Seriously, you can do some crazy things on your computer and you can use DTrace to gain insight into a lot of information.
Advanced+apple+debugging (16)