llvm/lldb/test/API/commands/trace/TestTraceDumpInstructions.py

from intelpt_testcase import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *


class TestTraceDumpInstructions(TraceIntelPTTestCaseBase):
    def testErrorMessages(self):
        # We first check the output when there are no targets
        self.expect(
            "thread trace dump instructions",
            substrs=[
                "error: invalid target, create a target using the 'target create' command"
            ],
            error=True,
        )

        # We now check the output when there's a non-running target
        self.expect(
            "target create "
            + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")
        )

        self.expect(
            "thread trace dump instructions",
            substrs=["error: Command requires a current process."],
            error=True,
        )

        # Now we check the output when there's a running target without a trace
        self.expect("b main")
        self.expect("run")

        self.expect(
            "thread trace dump instructions",
            substrs=["error: Process is not being traced"],
            error=True,
        )

    def testRawDumpInstructionsInJSON(self):
        self.expect(
            "trace load -v "
            + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
            substrs=["intel-pt"],
        )

        self.expect(
            "thread trace dump instructions --raw --count 5 --forwards --json",
            substrs=[
                """[{"id":3,"loadAddress":"0x400511"}""",
                """{"id":7,"loadAddress":"0x400518"}""",
                """{"id":8,"loadAddress":"0x40051f"}""",
                """{"id":9,"loadAddress":"0x400529"}""",
                """{"id":10,"loadAddress":"0x40052d"}""",
            ],
        )

        self.expect(
            "thread trace dump instructions --raw --count 5 --forwards --pretty-json",
            substrs=[
                """[
  {
    "id": 3,
    "loadAddress": "0x400511"
  },
  {
    "id": 7,
    "loadAddress": "0x400518"
  },
  {
    "id": 8,
    "loadAddress": "0x40051f"
  },
  {
    "id": 9,
    "loadAddress": "0x400529"
  },
  {
    "id": 10,
    "loadAddress": "0x40052d"
  }
]"""
            ],
        )

    def testRawDumpInstructionsInJSONToFile(self):
        self.expect(
            "trace load -v "
            + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
            substrs=["intel-pt"],
        )

        outfile = os.path.join(self.getBuildDir(), "output.json")

        self.expect(
            "thread trace dump instructions --raw --count 5 --forwards --pretty-json --file "
            + outfile
        )

        with open(outfile, "r") as out:
            self.assertEqual(
                out.read(),
                """[
  {
    "id": 3,
    "loadAddress": "0x400511"
  },
  {
    "id": 7,
    "loadAddress": "0x400518"
  },
  {
    "id": 8,
    "loadAddress": "0x40051f"
  },
  {
    "id": 9,
    "loadAddress": "0x400529"
  },
  {
    "id": 10,
    "loadAddress": "0x40052d"
  }
]""",
            )

    def testRawDumpInstructions(self):
        self.expect(
            "trace load -v "
            + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
            substrs=["intel-pt"],
        )

        self.expect(
            "thread trace dump instructions --raw --count 21 --forwards",
            substrs=[
                """thread #1: tid = 3842849
    3: 0x0000000000400511
    7: 0x0000000000400518
    8: 0x000000000040051f
    9: 0x0000000000400529
    10: 0x000000000040052d
    11: 0x0000000000400521
    12: 0x0000000000400525
    13: 0x0000000000400529
    14: 0x000000000040052d
    15: 0x0000000000400521
    16: 0x0000000000400525
    17: 0x0000000000400529
    18: 0x000000000040052d
    19: 0x0000000000400521
    20: 0x0000000000400525
    21: 0x0000000000400529
    22: 0x000000000040052d
    23: 0x0000000000400521
    24: 0x0000000000400525
    25: 0x0000000000400529
    26: 0x000000000040052"""
            ],
        )

        # We check if we can pass count and skip
        self.expect(
            "thread trace dump instructions --count 5 --skip 6 --raw --forwards",
            substrs=[
                """thread #1: tid = 3842849
    7: 0x0000000000400518
    8: 0x000000000040051f
    9: 0x0000000000400529
    10: 0x000000000040052d
    11: 0x0000000000400521"""
            ],
        )

        self.expect(
            "thread trace dump instructions --count 5 --skip 6 --raw",
            substrs=[
                """thread #1: tid = 3842849
    21: 0x0000000000400529
    20: 0x0000000000400525
    19: 0x0000000000400521
    18: 0x000000000040052d
    17: 0x0000000000400529"""
            ],
        )

        # We check if we can pass count and skip and instruction id in hex
        self.expect(
            "thread trace dump instructions --count 5 --skip 6 --raw --id 0xE",
            substrs=[
                """thread #1: tid = 3842849
    8: 0x000000000040051f
    7: 0x0000000000400518
    3: 0x0000000000400511
    no more data"""
            ],
        )

        # We check if we can pass count and skip and instruction id in decimal
        self.expect(
            "thread trace dump instructions --count 5 --skip 6 --raw --id 14",
            substrs=[
                """thread #1: tid = 3842849
    8: 0x000000000040051f
    7: 0x0000000000400518
    3: 0x0000000000400511
    no more data"""
            ],
        )

        # We check if we can access the thread by index id
        self.expect(
            "thread trace dump instructions 1 --raw",
            substrs=[
                """thread #1: tid = 3842849
    26: 0x000000000040052d"""
            ],
        )

        # We check that we get an error when using an invalid thread index id
        self.expect(
            "thread trace dump instructions 10",
            error=True,
            substrs=['error: no thread with index: "10"'],
        )

    def testDumpFullInstructionsWithMultipleThreads(self):
        # We load a trace with two threads
        self.expect(
            "trace load -v "
            + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_2threads.json")
        )

        # We print the instructions of a specific thread
        self.expect(
            "thread trace dump instructions 2 --count 2",
            substrs=[
                """thread #2: tid = 3842850
  a.out`main + 32 at main.cpp:4
    26: 0x000000000040052d    jle    0x400521                  ; <+20> at main.cpp:5
    25: 0x0000000000400529    cmpl   $0x3, -0x8(%rbp)"""
            ],
        )

        # We use custom --count and --skip, saving the command to history for later
        self.expect(
            "thread trace dump instructions 2 --count 2 --skip 2",
            inHistory=True,
            substrs=[
                """thread #2: tid = 3842850
  a.out`main + 28 at main.cpp:4
    25: 0x0000000000400529    cmpl   $0x3, -0x8(%rbp)
    24: 0x0000000000400525    addl   $0x1, -0x8(%rbp)"""
            ],
        )

        # We use a repeat command twice and ensure the previous count is used and the
        # start position moves with each command.
        self.expect(
            "",
            inHistory=True,
            substrs=[
                """thread #2: tid = 3842850
  a.out`main + 20 at main.cpp:5
    23: 0x0000000000400521    xorl   $0x1, -0x4(%rbp)
  a.out`main + 32 at main.cpp:4
    22: 0x000000000040052d    jle    0x400521                  ; <+20> at main.cpp:5"""
            ],
        )

        self.expect(
            "",
            inHistory=True,
            substrs=[
                """thread #2: tid = 3842850
  a.out`main + 28 at main.cpp:4
    21: 0x0000000000400529    cmpl   $0x3, -0x8(%rbp)
    20: 0x0000000000400525    addl   $0x1, -0x8(%rbp"""
            ],
        )

    def testInvalidBounds(self):
        self.expect(
            "trace load -v "
            + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json")
        )

        # The output should be work when too many instructions are asked
        self.expect(
            "thread trace dump instructions --count 20 --forwards",
            substrs=[
                """thread #1: tid = 3842849
  a.out`main + 4 at main.cpp:2
    3: 0x0000000000400511    movl   $0x0, -0x4(%rbp)
  a.out`main + 11 at main.cpp:4
    7: 0x0000000000400518    movl   $0x0, -0x8(%rbp)
    8: 0x000000000040051f    jmp    0x400529                  ; <+28> at main.cpp:4"""
            ],
        )

        # Should print no instructions if the position is out of bounds
        self.expect("thread trace dump instructions --skip 23", endstr="no more data\n")

        # Should fail with negative bounds
        self.expect("thread trace dump instructions --skip -1", error=True)
        self.expect("thread trace dump instructions --count -1", error=True)

    def testWrongImage(self):
        self.expect(
            "trace load "
            + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_bad_image.json")
        )
        self.expect(
            "thread trace dump instructions --forwards",
            substrs=[
                """thread #1: tid = 3842849
    ...missing instructions
    3: (error) no memory mapped at this address: 0x0000000000400511"""
            ],
        )

    def testWrongCPU(self):
        self.expect(
            "trace load "
            + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_wrong_cpu.json")
        )
        self.expect(
            "thread trace dump instructions --forwards",
            substrs=["error: unknown cpu"],
            error=True,
        )

    def testMultiFileTraceWithMissingModuleInJSON(self):
        self.expect(
            "trace load "
            + os.path.join(
                self.getSourceDir(), "intelpt-trace-multi-file", "multi-file-no-ld.json"
            )
        )

        self.expect(
            "thread trace dump instructions --count 4 --id 9 --forwards --pretty-json",
            substrs=[
                """[
  {
    "id": 9,
    "loadAddress": "0x40054b",
    "module": "a.out",
    "symbol": "foo()",
    "mnemonic": "jmp"
  },
  {
    "id": 10,
    "loadAddress": "0x400510",
    "module": "a.out",
    "symbol": null,
    "mnemonic": "pushq"
  },
  {
    "id": 11,
    "loadAddress": "0x400516",
    "module": "a.out",
    "symbol": null,
    "mnemonic": "jmpq"
  },
  {
    "id": 12,
    "error": "no memory mapped at this address: 0x00007ffff7df1950"
  },
  {
    "id": 16,
    "loadAddress": "0x400674",
    "module": "a.out",
    "symbol": "main",
    "mnemonic": "movl",
    "source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/main.cpp",
    "line": 10,
    "column": 0
  }
]"""
            ],
        )

        self.expect(
            "thread trace dump instructions --count 4 --id 20 --forwards --pretty-json",
            substrs=[
                """[
  {
    "id": 20,
    "loadAddress": "0x400677",
    "module": "a.out",
    "symbol": "main",
    "mnemonic": "movl",
    "source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/main.cpp",
    "line": 12,
    "column": 0
  },
  {
    "id": 21,
    "loadAddress": "0x40067a",
    "module": "a.out",
    "symbol": "main",
    "mnemonic": "addl",
    "source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/main.cpp",
    "line": 12,
    "column": 0
  },
  {
    "id": 22,
    "loadAddress": "0x40067f",
    "module": "a.out",
    "symbol": "main",
    "mnemonic": "movl",
    "source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/main.cpp",
    "line": 12,
    "column": 0
  },
  {
    "id": 26,
    "loadAddress": "0x400682",
    "module": "a.out",
    "symbol": "inline_function()",
    "mnemonic": "movl",
    "source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/main.cpp",
    "line": 4,
    "column": 0
  }
]"""
            ],
        )

    def testMultiFileTraceWithMissingModule(self):
        self.expect(
            "trace load "
            + os.path.join(
                self.getSourceDir(), "intelpt-trace-multi-file", "multi-file-no-ld.json"
            )
        )

        # This instructions in this test covers the following flow:
        #
        # - The trace starts with a call to libfoo, which triggers the dynamic
        #   linker, but the dynamic linker is not included in the JSON file,
        #   thus the trace reports a set of missing instructions after
        #   instruction [6].
        # - Then, the dump continues in the next synchronization point showing
        #   a call to an inlined function, which is displayed as [inlined].
        # - Finally, a call to libfoo is performed, which invokes libbar inside.
        #
        # Whenever there's a line or symbol change, including the inline case, a
        # line is printed showing the symbol context change.
        #
        # Finally, the instruction disassembly is included in the dump.
        self.expect(
            "thread trace dump instructions --count 50 --forwards",
            substrs=[
                """thread #1: tid = 815455
  a.out`main + 15 at main.cpp:10
    3: 0x000000000040066f    callq  0x400540                  ; symbol stub for: foo()
  a.out`symbol stub for: foo()
    7: 0x0000000000400540    jmpq   *0x200ae2(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 40
    8: 0x0000000000400546    pushq  $0x2
    9: 0x000000000040054b    jmp    0x400510
  a.out`(none)
    10: 0x0000000000400510    pushq  0x200af2(%rip)            ; _GLOBAL_OFFSET_TABLE_ + 8
    11: 0x0000000000400516    jmpq   *0x200af4(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 16
    ...missing instructions
    12: (error) no memory mapped at this address: 0x00007ffff7df1950
  a.out`main + 20 at main.cpp:10
    16: 0x0000000000400674    movl   %eax, -0xc(%rbp)
  a.out`main + 23 at main.cpp:12
    20: 0x0000000000400677    movl   -0xc(%rbp), %eax
    21: 0x000000000040067a    addl   $0x1, %eax
    22: 0x000000000040067f    movl   %eax, -0xc(%rbp)
  a.out`main + 34 [inlined] inline_function() at main.cpp:4
    26: 0x0000000000400682    movl   $0x0, -0x4(%rbp)
  a.out`main + 41 [inlined] inline_function() + 7 at main.cpp:5
    27: 0x0000000000400689    movl   -0x4(%rbp), %eax
    28: 0x000000000040068c    addl   $0x1, %eax
    29: 0x0000000000400691    movl   %eax, -0x4(%rbp)
  a.out`main + 52 [inlined] inline_function() + 18 at main.cpp:6
    30: 0x0000000000400694    movl   -0x4(%rbp), %eax
  a.out`main + 55 at main.cpp:14
    31: 0x0000000000400697    movl   -0xc(%rbp), %ecx
    32: 0x000000000040069a    addl   %eax, %ecx
    33: 0x000000000040069c    movl   %ecx, -0xc(%rbp)
  a.out`main + 63 at main.cpp:16
    37: 0x000000000040069f    callq  0x400540                  ; symbol stub for: foo()
  a.out`symbol stub for: foo()
    38: 0x0000000000400540    jmpq   *0x200ae2(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 40
  libfoo.so`foo() at foo.cpp:3
    39: 0x00007ffff7bd96e0    pushq  %rbp
    40: 0x00007ffff7bd96e1    movq   %rsp, %rbp
  libfoo.so`foo() + 4 at foo.cpp:4
    41: 0x00007ffff7bd96e4    subq   $0x10, %rsp
    42: 0x00007ffff7bd96e8    callq  0x7ffff7bd95d0            ; symbol stub for: bar()
  libfoo.so`symbol stub for: bar()
    43: 0x00007ffff7bd95d0    jmpq   *0x200a4a(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 32
  libbar.so`bar() at bar.cpp:1
    44: 0x00007ffff79d7690    pushq  %rbp
    45: 0x00007ffff79d7691    movq   %rsp, %rbp
  libbar.so`bar() + 4 at bar.cpp:2
    46: 0x00007ffff79d7694    movl   $0x1, -0x4(%rbp)
  libbar.so`bar() + 11 at bar.cpp:3
    47: 0x00007ffff79d769b    movl   -0x4(%rbp), %eax
    48: 0x00007ffff79d769e    addl   $0x1, %eax
    49: 0x00007ffff79d76a3    movl   %eax, -0x4(%rbp)
  libbar.so`bar() + 22 at bar.cpp:4
    50: 0x00007ffff79d76a6    movl   -0x4(%rbp), %eax
    51: 0x00007ffff79d76a9    popq   %rbp
    52: 0x00007ffff79d76aa    retq""",
                """libfoo.so`foo() + 13 at foo.cpp:4
    53: 0x00007ffff7bd96ed    movl   %eax, -0x4(%rbp)
  libfoo.so`foo() + 16 at foo.cpp:5
    54: 0x00007ffff7bd96f0    movl   -0x4(%rbp), %eax
    55: 0x00007ffff7bd96f3    addl   $0x1, %eax
    56: 0x00007ffff7bd96f8    movl   %eax, -0x4(%rbp)
  libfoo.so`foo() + 27 at foo.cpp:6
    57: 0x00007ffff7bd96fb    movl   -0x4(%rbp), %eax
    58: 0x00007ffff7bd96fe    addq   $0x10, %rsp
    59: 0x00007ffff7bd9702    popq   %rbp
    60: 0x00007ffff7bd9703    retq""",
                """a.out`main + 68 at main.cpp:16
    61: 0x00000000004006a4    movl   -0xc(%rbp), %ecx
    62: 0x00000000004006a7    addl   %eax, %ecx
    63: 0x00000000004006a9    movl   %ecx, -0xc(%rbp)
    no more data""",
            ],
        )

        self.expect(
            "thread trace dump instructions --count 50",
            substrs=[
                """thread #1: tid = 815455
  a.out`main + 73 at main.cpp:16
    63: 0x00000000004006a9    movl   %ecx, -0xc(%rbp)
    62: 0x00000000004006a7    addl   %eax, %ecx
    61: 0x00000000004006a4    movl   -0xc(%rbp), %ecx
  libfoo.so`foo() + 35 at foo.cpp:6
    60: 0x00007ffff7bd9703    retq""",
                """59: 0x00007ffff7bd9702    popq   %rbp
    58: 0x00007ffff7bd96fe    addq   $0x10, %rsp
    57: 0x00007ffff7bd96fb    movl   -0x4(%rbp), %eax
  libfoo.so`foo() + 24 at foo.cpp:5
    56: 0x00007ffff7bd96f8    movl   %eax, -0x4(%rbp)
    55: 0x00007ffff7bd96f3    addl   $0x1, %eax
    54: 0x00007ffff7bd96f0    movl   -0x4(%rbp), %eax
  libfoo.so`foo() + 13 at foo.cpp:4
    53: 0x00007ffff7bd96ed    movl   %eax, -0x4(%rbp)
  libbar.so`bar() + 26 at bar.cpp:4
    52: 0x00007ffff79d76aa    retq""",
                """51: 0x00007ffff79d76a9    popq   %rbp
    50: 0x00007ffff79d76a6    movl   -0x4(%rbp), %eax
  libbar.so`bar() + 19 at bar.cpp:3
    49: 0x00007ffff79d76a3    movl   %eax, -0x4(%rbp)
    48: 0x00007ffff79d769e    addl   $0x1, %eax
    47: 0x00007ffff79d769b    movl   -0x4(%rbp), %eax
  libbar.so`bar() + 4 at bar.cpp:2
    46: 0x00007ffff79d7694    movl   $0x1, -0x4(%rbp)
  libbar.so`bar() + 1 at bar.cpp:1
    45: 0x00007ffff79d7691    movq   %rsp, %rbp
    44: 0x00007ffff79d7690    pushq  %rbp
  libfoo.so`symbol stub for: bar()
    43: 0x00007ffff7bd95d0    jmpq   *0x200a4a(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 32
  libfoo.so`foo() + 8 at foo.cpp:4
    42: 0x00007ffff7bd96e8    callq  0x7ffff7bd95d0            ; symbol stub for: bar()
    41: 0x00007ffff7bd96e4    subq   $0x10, %rsp
  libfoo.so`foo() + 1 at foo.cpp:3
    40: 0x00007ffff7bd96e1    movq   %rsp, %rbp
    39: 0x00007ffff7bd96e0    pushq  %rbp
  a.out`symbol stub for: foo()
    38: 0x0000000000400540    jmpq   *0x200ae2(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 40
  a.out`main + 63 at main.cpp:16
    37: 0x000000000040069f    callq  0x400540                  ; symbol stub for: foo()
  a.out`main + 60 at main.cpp:14
    33: 0x000000000040069c    movl   %ecx, -0xc(%rbp)
    32: 0x000000000040069a    addl   %eax, %ecx
    31: 0x0000000000400697    movl   -0xc(%rbp), %ecx
  a.out`main + 52 [inlined] inline_function() + 18 at main.cpp:6
    30: 0x0000000000400694    movl   -0x4(%rbp), %eax
  a.out`main + 49 [inlined] inline_function() + 15 at main.cpp:5
    29: 0x0000000000400691    movl   %eax, -0x4(%rbp)
    28: 0x000000000040068c    addl   $0x1, %eax
    27: 0x0000000000400689    movl   -0x4(%rbp), %eax
  a.out`main + 34 [inlined] inline_function() at main.cpp:4
    26: 0x0000000000400682    movl   $0x0, -0x4(%rbp)
  a.out`main + 31 at main.cpp:12
    22: 0x000000000040067f    movl   %eax, -0xc(%rbp)
    21: 0x000000000040067a    addl   $0x1, %eax
    20: 0x0000000000400677    movl   -0xc(%rbp), %eax
  a.out`main + 20 at main.cpp:10
    16: 0x0000000000400674    movl   %eax, -0xc(%rbp)
    ...missing instructions
    12: (error) no memory mapped at this address: 0x00007ffff7df1950
  a.out`(none)
    11: 0x0000000000400516    jmpq   *0x200af4(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 16
    10: 0x0000000000400510    pushq  0x200af2(%rip)            ; _GLOBAL_OFFSET_TABLE_ + 8
  a.out`symbol stub for: foo() + 11
    9: 0x000000000040054b    jmp    0x400510
    8: 0x0000000000400546    pushq  $0x2
    7: 0x0000000000400540    jmpq   *0x200ae2(%rip)           ; _GLOBAL_OFFSET_TABLE_ + 40
  a.out`main + 15 at main.cpp:10
    3: 0x000000000040066f    callq  0x400540                  ; symbol stub for: foo()
    no more data""",
            ],
        )

        self.expect(
            "thread trace dump instructions --skip 100 --forwards",
            inHistory=True,
            substrs=[
                """thread #1: tid = 815455
    no more data"""
            ],
        )

        self.expect(
            "",
            substrs=[
                """thread #1: tid = 815455
    no more data"""
            ],
        )

        self.expect(
            "thread trace dump instructions --raw --all --forwards",
            substrs=[
                """thread #1: tid = 815455
    3: 0x000000000040066f
    7: 0x0000000000400540""",
                """11: 0x0000000000400516
    ...missing instructions
    12: (error) no memory mapped at this address: 0x00007ffff7df1950
    16: 0x0000000000400674""",
                """61: 0x00000000004006a4
    62: 0x00000000004006a7
    63: 0x00000000004006a9
    no more data""",
            ],
        )