This is an archive of the discontinued LLVM Phabricator instance.

[trace] Add a flag to the decoder to output the instruction type
ClosedPublic

Authored by persona0220 on Jun 23 2022, 4:16 PM.

Details

Summary

To build complex binding upon instruction trace, additional metadata 'instruction type' is needed.

This diff has followings:

  • Add a flag -k / --kind for instruction dump
  • Remove SetGranularity and SetIgnoreErros from Trace cursor

Sample output:

(lldb) thread trace dump instruction -k
thread #1: tid = 3198805
  libc.so.6`_IO_puts + 356
    2107: 0x00007ffff7163594 (    return)     retq
    2106: 0x00007ffff7163592 (     other)     popq   %r13
    2105: 0x00007ffff7163590 (     other)     popq   %r12
    2104: 0x00007ffff716358f (     other)     popq   %rbp
    2103: 0x00007ffff716358e (     other)     popq   %rbx
    2102: 0x00007ffff716358c (     other)     movl   %ebx, %eax
    2101: 0x00007ffff7163588 (     other)     addq   $0x8, %rsp
    2100: 0x00007ffff7163570 ( cond jump)     je     0x89588                   ; <+344>
    2099: 0x00007ffff716356e (     other)     decl   (%rdx)
    2098: 0x00007ffff7163565 ( cond jump)     je     0x8956e                   ; <+318>
    2097: 0x00007ffff716355e (     other)     cmpl   $0x0, 0x33c02b(%rip)      ; __libc_multiple_threads
    2096: 0x00007ffff7163556 (     other)     movq   $0x0, 0x8(%rdx)
    2095: 0x00007ffff7163554 ( cond jump)     jne    0x89588                   ; <+344>
    2094: 0x00007ffff7163550 (     other)     subl   $0x1, 0x4(%rdx)
    2093: 0x00007ffff7163549 (     other)     movq   0x88(%rbp), %rdx
    2092: 0x00007ffff7163547 ( cond jump)     jne    0x89588                   ; <+344>
    2091: 0x00007ffff7163540 (     other)     testl  $0x8000, (%rbp)           ; imm = 0x8000
    2090: 0x00007ffff716353c (     other)     cmovaq %rax, %rbx
    2089: 0x00007ffff7163535 (     other)     cmpq   $0x7fffffff, %rbx         ; imm = 0x7FFFFFFF
    2088: 0x00007ffff7163530 (     other)     movl   $0x7fffffff, %eax         ; imm = 0x7FFFFFFF

Diff Detail

Event Timeline

persona0220 created this revision.Jun 23 2022, 4:16 PM
Herald added a project: Restricted Project. · View Herald TranscriptJun 23 2022, 4:16 PM
persona0220 requested review of this revision.Jun 23 2022, 4:16 PM
Herald added a project: Restricted Project. · View Herald TranscriptJun 23 2022, 4:16 PM

This categorization seems like it is relevant for any instruction, not just instructions found in a trace buffer. Is there any reason why this isn't just an annotation added by the standard instruction printer, that you then turn on with the -k flag to thread trace dump (but which might also be useful for the regular disassemble command)?

wallace requested changes to this revision.Jun 23 2022, 6:15 PM

@jingham, that's a pretty good idea. We'll see how to implement it

This revision now requires changes to proceed.Jun 23 2022, 6:15 PM
jj10306 requested changes to this revision.Jun 24 2022, 7:03 AM

Thanks for working on this 🙂
Looks good overall, just left some minor comments.

lldb/include/lldb/Target/TraceCursor.h
64 ↗(On Diff #439560)

For some reason I can't make inline suggestions, so I'll just put my suggested edit below:

s/which direction/which uses the current direction of the trace

lldb/include/lldb/Target/TraceInstructionDumper.h
105–107 ↗(On Diff #439560)

Don't forget to rebase onto mainline, @wallace's recent diff (https://reviews.llvm.org/D128316) refactored some of this code, so that will effect where some of these new changes should live.

lldb/source/Commands/Options.td
1132

nit: add a period at the end of the description (:

lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp
46–50 ↗(On Diff #439560)

Apologies again bc I can't "Suggest Edits" inline, so leaving my suggestion in a code block below:

IIUC, we previously had a while loop here because the possibility of "skipping" instructions due to ignoring errors or not matching the granularity. Since this diff removes those two things, this logic can now be simplified since the trace cursor can no longer skip the instructions - below is roughly what I'm thinking:

bool TraceCursorIntelPT::Next() {
  auto canMoveOne = [&]() {
    if (IsForwards())
      return m_pos + 1 < GetInternalInstructionSize();
    return m_pos > 0;
  };

  if (!canMoveOne())
    return false;

  m_pos += IsForwards() ? 1 : -1;
  if (m_tsc_range && !m_tsc_range->InRange(m_pos))
    m_tsc_range = IsForwards() ? m_tsc_range->Next() : m_tsc_range->Prev();

  return true;
}

@wallace wdyt?

This new diff attach additional metadata 'control flow kind' for instruction.
Add '-k / --kind' flag for both disassemble and thread trace dump command.

Thanks to @jingham for the suggestion!

Sample output:

(lldb) disas -k
a.out`main:
    0x4005b6 <+0>:  other     pushq  %rbp
    0x4005b7 <+1>:  other     movq   %rsp, %rbp
    0x4005ba <+4>:  other     movl   $0x400668, %edi           ; imm = 0x400668
    0x4005bf <+9>:  call      callq  0x4004c0                  ; symbol stub for: puts
->  0x4005c4 <+14>: other     movl   $0x0, %eax
    0x4005c9 <+19>: other     popq   %rbp
    0x4005ca <+20>: return    retq

(lldb) thread trace dump instruction --kind
    thread #1: tid = 1893259
  libc.so.6`write + 30
    2027: 0x00007ffff71fa86e    cond jump ja     0x1208c8                  ; <+120>
    2026: 0x00007ffff71fa868    other     cmpq   $-0x1000, %rax            ; imm = 0xF000
    2025: 0x00007ffff71fa866    far call  syscall
    2024: 0x00007ffff71fa861    other     movl   $0x1, %eax
    2023: 0x00007ffff71fa85f    cond jump jne    0x120878                  ; <+40>
    2022: 0x00007ffff71fa85d    other     testl  %eax, %eax
    2021: 0x00007ffff71fa85b    other     movl   (%rax), %eax
    2020: 0x00007ffff71fa854    other     leaq   0x2a4d35(%rip), %rax      ; __libc_multiple_threads
    2019: 0x00007ffff71fa850    other     endbr64
wallace requested changes to this revision.Jun 28 2022, 4:40 PM

remove unwanted files

This revision now requires changes to proceed.Jun 28 2022, 4:40 PM
wallace added inline comments.Jun 28 2022, 4:53 PM
lldb/include/lldb/Core/Disassembler.h
82–86

as this code is new, please write documentation and function names should be in PascalCase. Try to have a more descriptive name as well, because instruction_decode is very vague.

152–153

let's try to be more explicit with the naming

226–233

add documentation. Also, if this mapping is intel-specific, I recommend moving this to the cpp file so that it's not exposed publicly. The Disassembler is architecture agnostic, so its API should try to remain that way

332

name

389
lldb/include/lldb/Target/TraceCursor.h
35 ↗(On Diff #440806)

this file has changed, could you rebase from upstream/main.

230–234 ↗(On Diff #440806)

when you rebase, could you delete any references to this method and its implementation as well? We won't use it anymore

lldb/include/lldb/Target/TraceInstructionDumper.h
38 ↗(On Diff #440806)

name

lldb/include/lldb/lldb-enumerations.h
973–996

this doesn't need to be a FLAGS_ENUM anymore (i.e. bitmask), you can use a simple enum instead, like the ExpressionEvaluationPhase above

lldb/source/API/SBInstruction.cpp
244–245

include this kind of comment wherever you have added this parameter

lldb/source/Commands/CommandObjectDisassemble.h
49

ame

nice job!!

lldb/source/Commands/Options.td
304

mention here the enum to inspect for documentation. Besides that, you can quickly mention here that far jumps and far returns often indicate a kernel calls and returns. That might be enough because the other categories are pretty intuitive

1159

same here

lldb/source/Core/Disassembler.cpp
682

you have to check here that the triple of the current architecture of the current target (you can get that from exe_ctx) is x86, otherwise directly return eInstructionControlFlowTypeUnknown. You can just rely on the check you are doing in line 685 because that check might fail in the future

683

from this point on, the code is specifically meant for x86, so I recommend creating a x86 namespace in this file and include a GetControlFlowInstructionKind method along with the instruction_decode method there. That way we organize the code in a more cleaner manner

683

add a comment explaining what these variables are

694

add a comment explaining what this prefix handling is actually doing

717

you can move this to the beginning of the function and assume that this mapping is x86 only, which is how it should be

775

you can also mention here how this mapping is actually generated and were you got inspiration from, like libipt

949

should you remove this?

952

unknown

lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp
63 ↗(On Diff #440808)

remove this, because we won't use it anymore

persona0220 marked 26 inline comments as done.
  • Modify function name instruction_decode into DecodeInstructionOpcode
  • explicit naming for flag show_kind -> show_control_flow_kind
  • Convert InstructionControlFlowType FLAG_ENUM into simple enum
  • Create x86 namespace in Disassembler.cpp to decode instruction.
wallace requested changes to this revision.Jul 5 2022, 12:15 AM

much better! Thanks for doing this.

There are two main things to do.

  1. The algorithm you are using to classify the instructions uses too many acronyms and it's very well documented. Try to make it understandable enough so that anyone with little familiarity can have an idea of what is happening
  2. write a unit test. For this, you can follow any of the tests in lldb/unittests/Disassembler/. In order to run it, you'll need to do ninja DisassemblerTests and then run the output path printed by that command. It uses gtest. You can see the tests in that folder for inspiration.
lldb/include/lldb/Core/Disassembler.h
82

better this

84

Rename this to

GetControlFlowKind

because we are already inside the Instruction

lldb/include/lldb/lldb-enumerations.h
976
lldb/source/API/SBInstruction.cpp
244–245

the llvm style requires a =

279–280

same here

lldb/source/API/SBInstructionList.cpp
169

ditto

lldb/source/Commands/Options.td
306
1161

copy the changes from above

lldb/source/Core/Disassembler.cpp
575

add documentation mentioning what a PTI_MAP is. Try to make the documentation clear enough for anyone without knowledge of this matter. Also, mention what modrm is why it's important here.
Also, it's better if don't use the PTI acronym but instead use the full name. It'd make it easier to read.
Also, this is an enum and not a map, so better use an identifier different than a map

584–586

let's be more specific

695–696

another reference to opcode, modrm, and map bytes. I recommend writing extensive documentation in PTI_MAP and then mention here that the full documentation can be found in the PTI_MAP definition

703–704

this comment is not useful because this method is in the x86 namespace

710–713
789–790

you use exe_ctx->GetTargetRef().GetArchitecture().GetMachine() != llvm::Triple::ArchType::x86_64 a lot. What about creating a variable that holds this comparison?

847–848

use { } in this else, because the if already used it

This revision now requires changes to proceed.Jul 5 2022, 12:15 AM
persona0220 updated this revision to Diff 442417.EditedJul 5 2022, 5:32 PM
persona0220 marked 13 inline comments as done.
  • Terms ‘control flow kind’ and ‘control flow type’ was mixed across source codes -> Unify into ‘control flow kind’
  • Divide the ‘GetControlFlowKind’ function into two functions:
    • InstructionLengthDecode: Decode raw instruction bytes into opcode, modrm and map.
    • MapOpcodeIntoControlFlowKind: Get control flow find from opcode, modrm and map.
  • Add detailed comments for each new functions.
persona0220 marked an inline comment as done.

Add some more documentation

wallace requested changes to this revision.Jul 5 2022, 9:03 PM

let's try to have tests soon. It seems that the code can be simplified and tests will be very handy

lldb/include/lldb/lldb-enumerations.h
976
lldb/source/Core/Disassembler.cpp
730–740

I'm noticing that MapOpcodeIntoControlFlowKind only uses PTI_MAP_0 and PTI_MAP_1, which are opcodes of 1 and 2 bytes. Any opcode of 3 bytes and even amd3dnow is not used at all. That means that you don't need that enum, so please delete it. Instead, use the length of the opcode throughout the code.

882

does this mean that all x86 and x86_64 instructions are categorized as Opcode::Type::eTypeBytes?
I'm asking because after reading GetOpcodeBytes, it only returns data if the type is eTypeBytes. Did you check that?

885

you don't need to copy GetOpcodeBytes, you can just pass it directly to InstructionLengthDecode

This revision now requires changes to proceed.Jul 5 2022, 9:03 PM
persona0220 marked 4 inline comments as done.
  • Delete PTI_MAP enum and use opcode_len instead.
  • Add a unit test
    • Divide existing Disassembler test into x86 / ARM subdirectory
    • Unittests/Disassembler/x86/TestGetControlFlowKindx86.cpp tests categorizing x86_64 instructions
  • Modify GetControlFlowKind function to get ArchSpec instead of exe_ctx
persona0220 marked an inline comment as done.Jul 8 2022, 12:01 PM
wallace requested changes to this revision.Jul 8 2022, 12:56 PM

almost there! I like the tests btw :)

lldb/source/Commands/Options.td
304
1159

same

lldb/source/Core/Disassembler.cpp
578
588
592–594
705
734–736

We try not to use out parameters this way. Instead, create a new struct InstructionOpcodeAndModrm with the three values that are important to you. Then, change this function to return Optional<InstructionOpcodeAndModrm>, where the None case means that decoding failed

lldb/unittests/Disassembler/x86/TestGetControlFlowKindx86.cpp
29–30

delete if not used

This revision now requires changes to proceed.Jul 8 2022, 12:56 PM
persona0220 marked 8 inline comments as done.

Added a struct InstructionOpcodeAndModrm, which contains primary_opcode, opcode_len and modrm bytes decoded from InstructionLengthDecode function.

wallace accepted this revision.Jul 12 2022, 12:30 PM

I'll commit this for you

This revision was not accepted when it landed; it landed in state Needs Review.Jul 12 2022, 4:23 PM
This revision was automatically updated to reflect the committed changes.