1import lldb
2from intelpt_testcase import *
3from lldbsuite.test.lldbtest import *
4from lldbsuite.test import lldbutil
5from lldbsuite.test.decorators import *
6
7class TestTraceDumpInstructions(TraceIntelPTTestCaseBase):
8
9    def testErrorMessages(self):
10        # We first check the output when there are no targets
11        self.expect("thread trace dump instructions",
12            substrs=["error: invalid target, create a target using the 'target create' command"],
13            error=True)
14
15        # We now check the output when there's a non-running target
16        self.expect("target create " +
17            os.path.join(self.getSourceDir(), "intelpt-trace", "a.out"))
18
19        self.expect("thread trace dump instructions",
20            substrs=["error: Command requires a current process."],
21            error=True)
22
23        # Now we check the output when there's a running target without a trace
24        self.expect("b main")
25        self.expect("run")
26
27        self.expect("thread trace dump instructions",
28            substrs=["error: Process is not being traced"],
29            error=True)
30
31    def testRawDumpInstructionsInJSON(self):
32        self.expect("trace load -v " +
33            os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
34            substrs=["intel-pt"])
35
36        self.expect("thread trace dump instructions --raw --count 5 --forwards --json",
37            substrs=['''[{"id":0,"loadAddress":"0x400511"},{"id":1,"loadAddress":"0x400518"},{"id":2,"loadAddress":"0x40051f"},{"id":3,"loadAddress":"0x400529"},{"id":4,"loadAddress":"0x40052d"}]'''])
38
39        self.expect("thread trace dump instructions --raw --count 5 --forwards --pretty-json",
40            substrs=['''[
41  {
42    "id": 0,
43    "loadAddress": "0x400511"
44  },
45  {
46    "id": 1,
47    "loadAddress": "0x400518"
48  },
49  {
50    "id": 2,
51    "loadAddress": "0x40051f"
52  },
53  {
54    "id": 3,
55    "loadAddress": "0x400529"
56  },
57  {
58    "id": 4,
59    "loadAddress": "0x40052d"
60  }
61]'''])
62
63    def testRawDumpInstructionsInJSONToFile(self):
64        self.expect("trace load -v " +
65            os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
66            substrs=["intel-pt"])
67
68        outfile = os.path.join(self.getBuildDir(), "output.json")
69
70        self.expect("thread trace dump instructions --raw --count 5 --forwards --pretty-json --file " + outfile)
71
72        with open(outfile, "r") as out:
73            self.assertEqual(out.read(), '''[
74  {
75    "id": 0,
76    "loadAddress": "0x400511"
77  },
78  {
79    "id": 1,
80    "loadAddress": "0x400518"
81  },
82  {
83    "id": 2,
84    "loadAddress": "0x40051f"
85  },
86  {
87    "id": 3,
88    "loadAddress": "0x400529"
89  },
90  {
91    "id": 4,
92    "loadAddress": "0x40052d"
93  }
94]''')
95
96    def testRawDumpInstructions(self):
97        self.expect("trace load -v " +
98            os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
99            substrs=["intel-pt"])
100
101        self.expect("thread trace dump instructions --raw --count 21 --forwards",
102            substrs=['''thread #1: tid = 3842849
103    0: 0x0000000000400511
104    1: 0x0000000000400518
105    2: 0x000000000040051f
106    3: 0x0000000000400529
107    4: 0x000000000040052d
108    5: 0x0000000000400521
109    6: 0x0000000000400525
110    7: 0x0000000000400529
111    8: 0x000000000040052d
112    9: 0x0000000000400521
113    10: 0x0000000000400525
114    11: 0x0000000000400529
115    12: 0x000000000040052d
116    13: 0x0000000000400521
117    14: 0x0000000000400525
118    15: 0x0000000000400529
119    16: 0x000000000040052d
120    17: 0x0000000000400521
121    18: 0x0000000000400525
122    19: 0x0000000000400529
123    20: 0x000000000040052d'''])
124
125        # We check if we can pass count and skip
126        self.expect("thread trace dump instructions --count 5 --skip 6 --raw --forwards",
127            substrs=['''thread #1: tid = 3842849
128    6: 0x0000000000400525
129    7: 0x0000000000400529
130    8: 0x000000000040052d
131    9: 0x0000000000400521
132    10: 0x0000000000400525'''])
133
134        self.expect("thread trace dump instructions --count 5 --skip 6 --raw",
135            substrs=['''thread #1: tid = 3842849
136    14: 0x0000000000400525
137    13: 0x0000000000400521
138    12: 0x000000000040052d
139    11: 0x0000000000400529
140    10: 0x0000000000400525'''])
141
142        # We check if we can pass count and skip and instruction id in hex
143        self.expect("thread trace dump instructions --count 5 --skip 6 --raw --id 0xA",
144            substrs=['''thread #1: tid = 3842849
145    4: 0x000000000040052d
146    3: 0x0000000000400529
147    2: 0x000000000040051f
148    1: 0x0000000000400518
149    0: 0x0000000000400511'''])
150
151        # We check if we can pass count and skip and instruction id in decimal
152        self.expect("thread trace dump instructions --count 5 --skip 6 --raw --id 10",
153            substrs=['''thread #1: tid = 3842849
154    4: 0x000000000040052d
155    3: 0x0000000000400529
156    2: 0x000000000040051f
157    1: 0x0000000000400518
158    0: 0x0000000000400511'''])
159
160        # We check if we can access the thread by index id
161        self.expect("thread trace dump instructions 1 --raw",
162            substrs=['''thread #1: tid = 3842849
163    20: 0x000000000040052d'''])
164
165        # We check that we get an error when using an invalid thread index id
166        self.expect("thread trace dump instructions 10", error=True,
167            substrs=['error: no thread with index: "10"'])
168
169    def testDumpFullInstructionsWithMultipleThreads(self):
170        # We load a trace with two threads
171        self.expect("trace load -v " +
172            os.path.join(self.getSourceDir(), "intelpt-trace", "trace_2threads.json"))
173
174        # We print the instructions of a specific thread
175        self.expect("thread trace dump instructions 2 --count 2",
176            substrs=['''thread #2: tid = 3842850
177  a.out`main + 32 at main.cpp:4
178    20: 0x000000000040052d    jle    0x400521                  ; <+20> at main.cpp:5
179    19: 0x0000000000400529    cmpl   $0x3, -0x8(%rbp)'''])
180
181        # We use custom --count and --skip, saving the command to history for later
182        self.expect("thread trace dump instructions 2 --count 2 --skip 2", inHistory=True,
183            substrs=['''thread #2: tid = 3842850
184  a.out`main + 24 at main.cpp:4
185    18: 0x0000000000400525    addl   $0x1, -0x8(%rbp)
186  a.out`main + 20 at main.cpp:5
187    17: 0x0000000000400521    xorl   $0x1, -0x4(%rbp)'''])
188
189        # We use a repeat command twice and ensure the previous count is used and the
190        # start position moves with each command.
191        self.expect("", inHistory=True,
192            substrs=['''thread #2: tid = 3842850
193  a.out`main + 32 at main.cpp:4
194    16: 0x000000000040052d    jle    0x400521                  ; <+20> at main.cpp:5
195    15: 0x0000000000400529    cmpl   $0x3, -0x8(%rbp)'''])
196
197        self.expect("", inHistory=True,
198            substrs=['''thread #2: tid = 3842850
199  a.out`main + 24 at main.cpp:4
200    14: 0x0000000000400525    addl   $0x1, -0x8(%rbp)
201  a.out`main + 20 at main.cpp:5
202    13: 0x0000000000400521    xorl   $0x1, -0x4(%rbp)'''])
203
204    def testInvalidBounds(self):
205        self.expect("trace load -v " +
206            os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"))
207
208        # The output should be work when too many instructions are asked
209        self.expect("thread trace dump instructions --count 20 --forwards",
210            substrs=['''thread #1: tid = 3842849
211  a.out`main + 4 at main.cpp:2
212    0: 0x0000000000400511    movl   $0x0, -0x4(%rbp)
213  a.out`main + 11 at main.cpp:4
214    1: 0x0000000000400518    movl   $0x0, -0x8(%rbp)
215    2: 0x000000000040051f    jmp    0x400529                  ; <+28> at main.cpp:4'''])
216
217        # Should print no instructions if the position is out of bounds
218        self.expect("thread trace dump instructions --skip 23",
219            endstr='no more data\n')
220
221        # Should fail with negative bounds
222        self.expect("thread trace dump instructions --skip -1", error=True)
223        self.expect("thread trace dump instructions --count -1", error=True)
224
225    def testWrongImage(self):
226        self.expect("trace load " +
227            os.path.join(self.getSourceDir(), "intelpt-trace", "trace_bad_image.json"))
228        self.expect("thread trace dump instructions --forwards",
229            substrs=['''thread #1: tid = 3842849
230    ...missing instructions
231    0: 0x0000000000400511    error: no memory mapped at this address
232    1: 0x0000000000400518    error: no memory mapped at this address'''])
233
234    def testWrongCPU(self):
235        self.expect("trace load " +
236            os.path.join(self.getSourceDir(), "intelpt-trace", "trace_wrong_cpu.json"))
237        self.expect("thread trace dump instructions --forwards",
238            substrs=['''thread #1: tid = 3842849
239    ...missing instructions
240    0: error: unknown cpu'''])
241
242    def testMultiFileTraceWithMissingModuleInJSON(self):
243        self.expect("trace load " +
244            os.path.join(self.getSourceDir(), "intelpt-trace-multi-file", "multi-file-no-ld.json"))
245
246        self.expect("thread trace dump instructions --count 3 --id 4 --forwards --pretty-json",
247            substrs=['''[
248  {
249    "id": 4,
250    "loadAddress": "0x400510",
251    "module": "a.out",
252    "symbol": null,
253    "mnemonic": "pushq"
254  },
255  {
256    "id": 5,
257    "loadAddress": "0x400516",
258    "module": "a.out",
259    "symbol": null,
260    "mnemonic": "jmpq"
261  },
262  {
263    "id": 6,
264    "error": "0x00007ffff7df1950    error: no memory mapped at this address"
265  }
266]'''])
267
268        self.expect("thread trace dump instructions --count 4 --id 20 --forwards --pretty-json",
269                substrs=['''[
270  {
271    "id": 20,
272    "loadAddress": "0x400540",
273    "module": "a.out",
274    "symbol": "foo()",
275    "mnemonic": "jmpq"
276  },
277  {
278    "id": 21,
279    "loadAddress": "0x7ffff7bd96e0",
280    "module": "libfoo.so",
281    "symbol": "foo()",
282    "mnemonic": "pushq",
283    "source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.cpp",
284    "line": 3,
285    "column": 0
286  },
287  {
288    "id": 22,
289    "loadAddress": "0x7ffff7bd96e1",
290    "module": "libfoo.so",
291    "symbol": "foo()",
292    "mnemonic": "movq",
293    "source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.cpp",
294    "line": 3,
295    "column": 0
296  },
297  {
298    "id": 23,
299    "loadAddress": "0x7ffff7bd96e4",
300    "module": "libfoo.so",
301    "symbol": "foo()",
302    "mnemonic": "subq",
303    "source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.cpp",
304    "line": 4,
305    "column": 0
306  }
307]'''])
308
309    def testMultiFileTraceWithMissingModule(self):
310        self.expect("trace load " +
311            os.path.join(self.getSourceDir(), "intelpt-trace-multi-file", "multi-file-no-ld.json"))
312
313        # This instructions in this test covers the following flow:
314        #
315        # - The trace starts with a call to libfoo, which triggers the dynamic
316        #   linker, but the dynamic linker is not included in the JSON file,
317        #   thus the trace reports a set of missing instructions after
318        #   instruction [6].
319        # - Then, the dump continues in the next synchronization point showing
320        #   a call to an inlined function, which is displayed as [inlined].
321        # - Finally, a call to libfoo is performed, which invokes libbar inside.
322        #
323        # Whenever there's a line or symbol change, including the inline case, a
324        # line is printed showing the symbol context change.
325        #
326        # Finally, the instruction disassembly is included in the dump.
327        self.expect("thread trace dump instructions --count 50 --forwards",
328            substrs=['''thread #1: tid = 815455
329  a.out`main + 15 at main.cpp:10
330    0: 0x000000000040066f    callq  0x400540                  ; symbol stub for: foo()
331  a.out`symbol stub for: foo()
332    1: 0x0000000000400540    jmpq   *0x200ae2(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 40
333    2: 0x0000000000400546    pushq  $0x2
334    3: 0x000000000040054b    jmp    0x400510
335  a.out`(none)
336    4: 0x0000000000400510    pushq  0x200af2(%rip)            ; _GLOBAL_OFFSET_TABLE_ + 8
337    5: 0x0000000000400516    jmpq   *0x200af4(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 16
338    ...missing instructions
339    6: 0x00007ffff7df1950    error: no memory mapped at this address
340  a.out`main + 20 at main.cpp:10
341    7: 0x0000000000400674    movl   %eax, -0xc(%rbp)
342  a.out`main + 23 at main.cpp:12
343    8: 0x0000000000400677    movl   -0xc(%rbp), %eax
344    9: 0x000000000040067a    addl   $0x1, %eax
345    10: 0x000000000040067f    movl   %eax, -0xc(%rbp)
346  a.out`main + 34 [inlined] inline_function() at main.cpp:4
347    11: 0x0000000000400682    movl   $0x0, -0x4(%rbp)
348  a.out`main + 41 [inlined] inline_function() + 7 at main.cpp:5
349    12: 0x0000000000400689    movl   -0x4(%rbp), %eax
350    13: 0x000000000040068c    addl   $0x1, %eax
351    14: 0x0000000000400691    movl   %eax, -0x4(%rbp)
352  a.out`main + 52 [inlined] inline_function() + 18 at main.cpp:6
353    15: 0x0000000000400694    movl   -0x4(%rbp), %eax
354  a.out`main + 55 at main.cpp:14
355    16: 0x0000000000400697    movl   -0xc(%rbp), %ecx
356    17: 0x000000000040069a    addl   %eax, %ecx
357    18: 0x000000000040069c    movl   %ecx, -0xc(%rbp)
358  a.out`main + 63 at main.cpp:16
359    19: 0x000000000040069f    callq  0x400540                  ; symbol stub for: foo()
360  a.out`symbol stub for: foo()
361    20: 0x0000000000400540    jmpq   *0x200ae2(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 40
362  libfoo.so`foo() at foo.cpp:3
363    21: 0x00007ffff7bd96e0    pushq  %rbp
364    22: 0x00007ffff7bd96e1    movq   %rsp, %rbp
365  libfoo.so`foo() + 4 at foo.cpp:4
366    23: 0x00007ffff7bd96e4    subq   $0x10, %rsp
367    24: 0x00007ffff7bd96e8    callq  0x7ffff7bd95d0            ; symbol stub for: bar()
368  libfoo.so`symbol stub for: bar()
369    25: 0x00007ffff7bd95d0    jmpq   *0x200a4a(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 32
370  libbar.so`bar() at bar.cpp:1
371    26: 0x00007ffff79d7690    pushq  %rbp
372    27: 0x00007ffff79d7691    movq   %rsp, %rbp
373  libbar.so`bar() + 4 at bar.cpp:2
374    28: 0x00007ffff79d7694    movl   $0x1, -0x4(%rbp)
375  libbar.so`bar() + 11 at bar.cpp:3
376    29: 0x00007ffff79d769b    movl   -0x4(%rbp), %eax
377    30: 0x00007ffff79d769e    addl   $0x1, %eax
378    31: 0x00007ffff79d76a3    movl   %eax, -0x4(%rbp)
379  libbar.so`bar() + 22 at bar.cpp:4
380    32: 0x00007ffff79d76a6    movl   -0x4(%rbp), %eax
381    33: 0x00007ffff79d76a9    popq   %rbp
382    34: 0x00007ffff79d76aa    retq''',
383  '''libfoo.so`foo() + 13 at foo.cpp:4
384    35: 0x00007ffff7bd96ed    movl   %eax, -0x4(%rbp)
385  libfoo.so`foo() + 16 at foo.cpp:5
386    36: 0x00007ffff7bd96f0    movl   -0x4(%rbp), %eax
387    37: 0x00007ffff7bd96f3    addl   $0x1, %eax
388    38: 0x00007ffff7bd96f8    movl   %eax, -0x4(%rbp)
389  libfoo.so`foo() + 27 at foo.cpp:6
390    39: 0x00007ffff7bd96fb    movl   -0x4(%rbp), %eax
391    40: 0x00007ffff7bd96fe    addq   $0x10, %rsp
392    41: 0x00007ffff7bd9702    popq   %rbp
393    42: 0x00007ffff7bd9703    retq''',
394  '''a.out`main + 68 at main.cpp:16
395    43: 0x00000000004006a4    movl   -0xc(%rbp), %ecx
396    44: 0x00000000004006a7    addl   %eax, %ecx
397    45: 0x00000000004006a9    movl   %ecx, -0xc(%rbp)'''])
398
399
400        self.expect("thread trace dump instructions --count 50",
401            substrs=['''thread #1: tid = 815455
402  a.out`main + 73 at main.cpp:16
403    45: 0x00000000004006a9    movl   %ecx, -0xc(%rbp)
404    44: 0x00000000004006a7    addl   %eax, %ecx
405    43: 0x00000000004006a4    movl   -0xc(%rbp), %ecx
406  libfoo.so`foo() + 35 at foo.cpp:6
407    42: 0x00007ffff7bd9703    retq''',
408    '''41: 0x00007ffff7bd9702    popq   %rbp
409    40: 0x00007ffff7bd96fe    addq   $0x10, %rsp
410    39: 0x00007ffff7bd96fb    movl   -0x4(%rbp), %eax
411  libfoo.so`foo() + 24 at foo.cpp:5
412    38: 0x00007ffff7bd96f8    movl   %eax, -0x4(%rbp)
413    37: 0x00007ffff7bd96f3    addl   $0x1, %eax
414    36: 0x00007ffff7bd96f0    movl   -0x4(%rbp), %eax
415  libfoo.so`foo() + 13 at foo.cpp:4
416    35: 0x00007ffff7bd96ed    movl   %eax, -0x4(%rbp)
417  libbar.so`bar() + 26 at bar.cpp:4
418    34: 0x00007ffff79d76aa    retq''',
419    '''33: 0x00007ffff79d76a9    popq   %rbp
420    32: 0x00007ffff79d76a6    movl   -0x4(%rbp), %eax
421  libbar.so`bar() + 19 at bar.cpp:3
422    31: 0x00007ffff79d76a3    movl   %eax, -0x4(%rbp)
423    30: 0x00007ffff79d769e    addl   $0x1, %eax
424    29: 0x00007ffff79d769b    movl   -0x4(%rbp), %eax
425  libbar.so`bar() + 4 at bar.cpp:2
426    28: 0x00007ffff79d7694    movl   $0x1, -0x4(%rbp)
427  libbar.so`bar() + 1 at bar.cpp:1
428    27: 0x00007ffff79d7691    movq   %rsp, %rbp
429    26: 0x00007ffff79d7690    pushq  %rbp
430  libfoo.so`symbol stub for: bar()
431    25: 0x00007ffff7bd95d0    jmpq   *0x200a4a(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 32
432  libfoo.so`foo() + 8 at foo.cpp:4
433    24: 0x00007ffff7bd96e8    callq  0x7ffff7bd95d0            ; symbol stub for: bar()
434    23: 0x00007ffff7bd96e4    subq   $0x10, %rsp
435  libfoo.so`foo() + 1 at foo.cpp:3
436    22: 0x00007ffff7bd96e1    movq   %rsp, %rbp
437    21: 0x00007ffff7bd96e0    pushq  %rbp
438  a.out`symbol stub for: foo()
439    20: 0x0000000000400540    jmpq   *0x200ae2(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 40
440  a.out`main + 63 at main.cpp:16
441    19: 0x000000000040069f    callq  0x400540                  ; symbol stub for: foo()
442  a.out`main + 60 at main.cpp:14
443    18: 0x000000000040069c    movl   %ecx, -0xc(%rbp)
444    17: 0x000000000040069a    addl   %eax, %ecx
445    16: 0x0000000000400697    movl   -0xc(%rbp), %ecx
446  a.out`main + 52 [inlined] inline_function() + 18 at main.cpp:6
447    15: 0x0000000000400694    movl   -0x4(%rbp), %eax
448  a.out`main + 49 [inlined] inline_function() + 15 at main.cpp:5
449    14: 0x0000000000400691    movl   %eax, -0x4(%rbp)
450    13: 0x000000000040068c    addl   $0x1, %eax
451    12: 0x0000000000400689    movl   -0x4(%rbp), %eax
452  a.out`main + 34 [inlined] inline_function() at main.cpp:4
453    11: 0x0000000000400682    movl   $0x0, -0x4(%rbp)
454  a.out`main + 31 at main.cpp:12
455    10: 0x000000000040067f    movl   %eax, -0xc(%rbp)
456    9: 0x000000000040067a    addl   $0x1, %eax
457    8: 0x0000000000400677    movl   -0xc(%rbp), %eax
458  a.out`main + 20 at main.cpp:10
459    7: 0x0000000000400674    movl   %eax, -0xc(%rbp)
460    ...missing instructions
461    6: 0x00007ffff7df1950    error: no memory mapped at this address
462  a.out`(none)
463    5: 0x0000000000400516    jmpq   *0x200af4(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 16
464    4: 0x0000000000400510    pushq  0x200af2(%rip)            ; _GLOBAL_OFFSET_TABLE_ + 8
465  a.out`symbol stub for: foo() + 11
466    3: 0x000000000040054b    jmp    0x400510
467    2: 0x0000000000400546    pushq  $0x2
468    1: 0x0000000000400540    jmpq   *0x200ae2(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 40
469  a.out`main + 15 at main.cpp:10
470    0: 0x000000000040066f    callq  0x400540                  ; symbol stub for: foo()'''])
471
472        self.expect("thread trace dump instructions --skip 100 --forwards", inHistory=True,
473            substrs=['''thread #1: tid = 815455
474    no more data'''])
475
476        self.expect("", substrs=['''thread #1: tid = 815455
477    no more data'''])
478
479
480        self.expect("thread trace dump instructions --raw --all --forwards",
481            substrs=['''thread #1: tid = 815455
482    0: 0x000000000040066f
483    1: 0x0000000000400540''', '''5: 0x0000000000400516
484    ...missing instructions
485    6: 0x00007ffff7df1950    error: no memory mapped at this address
486    7: 0x0000000000400674''', '''43: 0x00000000004006a4
487    44: 0x00000000004006a7
488    45: 0x00000000004006a9
489    no more data'''])
490