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