Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lldb command to function-step / trace-step: continue until next function call or until current function is returned from

In LLDB, how can I implement a function-step / trace-step? That is, continue until either a function is called, or the current function is returned from. Assume no source code is available to perform until.

This would be equivalent to perform step-inst until the stack frame structure changes.

like image 994
Danra Avatar asked Feb 25 '14 09:02

Danra


Video Answer


3 Answers

Here's an lldb targeted python script which adds a "step-function" command. The command stops whenever the call stack structure changes.

step_func.py

import lldb

def step_func(debugger, command, result, internal_dict):
    thread = debugger.GetSelectedTarget().GetProcess().GetSelectedThread()

    start_num_frames = thread.GetNumFrames()
    if start_num_frames == 0:
        return

    while True:
        thread.StepInstruction(0)
        if thread.GetNumFrames() != start_num_frames:
            stream = lldb.SBStream()
            thread.GetStatus(stream)
            description = stream.GetData()

            print >>result, "Call stack depth changed %d -> %d" % (start_num_frames, thread.GetNumFrames())
            print >>result, description,

            break

def __lldb_init_module (debugger, dict):
    debugger.HandleCommand('command script add -f %s.step_func sf' % __name__)

Usage example:

$ lldb /bin/ls
Current executable set to '/bin/ls' (x86_64).
(lldb) command script import step_func                                                                                                                                                                             (lldb) process launch --stop-at-entry                                                                                                                                                                              Process 12944 launched: '/bin/ls' (x86_64)
Process 12944 stopped
* thread #1: tid = 0x438b0, 0x00007fff5fc01028 dyld`_dyld_start, stop reason = signal SIGSTOP
    frame #0: 0x00007fff5fc01028 dyld`_dyld_start
dyld`_dyld_start:
-> 0x7fff5fc01028:  popq   %rdi
   0x7fff5fc01029:  pushq  $0
   0x7fff5fc0102b:  movq   %rsp, %rbp
   0x7fff5fc0102e:  andq   $-16, %rsp
(lldb) sf
Call stack depth changed 1 -> 2
* thread #1: tid = 0x438b0, 0x00007fff5fc0109e dyld`dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*), stop reason = instruction step into
    frame #0: 0x00007fff5fc0109e dyld`dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*)
dyld`dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*):
-> 0x7fff5fc0109e:  pushq  %rbp
   0x7fff5fc0109f:  movq   %rsp, %rbp
   0x7fff5fc010a2:  pushq  %r15
   0x7fff5fc010a4:  pushq  %r14
(lldb) 
Call stack depth changed 2 -> 3
* thread #1: tid = 0x438b0, 0x00007fff5fc22f9b dyld`mach_init, stop reason = instruction step into
    frame #0: 0x00007fff5fc22f9b dyld`mach_init
dyld`mach_init:
-> 0x7fff5fc22f9b:  pushq  %rbp
   0x7fff5fc22f9c:  movq   %rsp, %rbp
   0x7fff5fc22f9f:  movb   326075(%rip), %al         ; mach_init.mach_init_inited
   0x7fff5fc22fa5:  testb  %al, %al
(lldb) 
Call stack depth changed 3 -> 4
* thread #1: tid = 0x438b0, 0x00007fff5fc22fb9 dyld`mach_init_doit, stop reason = instruction step into
    frame #0: 0x00007fff5fc22fb9 dyld`mach_init_doit
dyld`mach_init_doit:
-> 0x7fff5fc22fb9:  pushq  %rbp
   0x7fff5fc22fba:  movq   %rsp, %rbp
   0x7fff5fc22fbd:  callq  0x7fff5fc23210            ; task_self_trap
   0x7fff5fc22fc2:  movl   %eax, 69740(%rip)         ; mach_task_self_
(lldb) 
Call stack depth changed 4 -> 5
* thread #1: tid = 0x438b0, 0x00007fff5fc23210 dyld`task_self_trap, stop reason = instruction step into
    frame #0: 0x00007fff5fc23210 dyld`task_self_trap
dyld`task_self_trap:
-> 0x7fff5fc23210:  movq   %rcx, %r10
   0x7fff5fc23213:  movl   $16777244, %eax
   0x7fff5fc23218:  syscall 
   0x7fff5fc2321a:  ret    
(lldb) 
Call stack depth changed 5 -> 4
* thread #1: tid = 0x438b0, 0x00007fff5fc22fc2 dyld`mach_init_doit + 9, stop reason = instruction step into
    frame #0: 0x00007fff5fc22fc2 dyld`mach_init_doit + 9
dyld`mach_init_doit + 9:
-> 0x7fff5fc22fc2:  movl   %eax, 69740(%rip)         ; mach_task_self_
   0x7fff5fc22fc8:  callq  0x7fff5fc231f8            ; mach_reply_port
   0x7fff5fc22fcd:  leaq   69724(%rip), %rcx         ; _task_reply_port
   0x7fff5fc22fd4:  movl   %eax, (%rcx)
(lldb) 
like image 55
Danra Avatar answered Oct 04 '22 13:10

Danra


In LLDB, how can I step until the current assembly-level function is left? (No source code is available to perform until). I'm looking for an automated way to perform step-inst until the stack frame structure changes, that is a function is called or the current one is returned from.

As I checked, current current version of LLVM have no such stepping mode, which will stop at function return or at any function call.

There is "finish" ("thread step-out") to stop at exit of function; there is also "nexti" ("thread step-inst-over") to single stepping without visiting called functions.

There are sources of LLDB with list of all supported modes: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Commands/CommandObjectThread.cpp?revision=194531&view=markup - check the very end of file for command list - in CommandObjectMultiwordThread::CommandObjectMultiwordThread

I think it can be easy to implement the needed stepping mode in the LLDB, because there are components to implement both stepping until return (CommandObjectThreadStepWithTypeAndScope (... eStepTypeOut, eStepScopeSource) => QueueThreadPlanForStepOut) and function call detector (for CommandObjectThreadStepWithTypeAndScope (...eStepTypeTraceOver,eStepScopeInstruction) => QueueThreadPlanForStepSingleInstruction). The code in Target/ThreadPlanStepInstruction.cpp should help.

like image 31
osgx Avatar answered Oct 04 '22 11:10

osgx


Not clear whether you want to step or just continue until the function exits. For the latter case, if you can figure out the return address location on the stack, you can put a read watch on it. The RET instruction that will eventually leave the current function will need to read that location to find the return address.

Finding the return address location can be automated if you have a valid frame pointer. Here is an example using gdb:

Breakpoint 1, 0x080483e6 in foo ()
(gdb) disas foo
Dump of assembler code for function foo:
   0x080483e3 <+0>:     push   %ebp
   0x080483e4 <+1>:     mov    %esp,%ebp
=> 0x080483e6 <+3>:     nop
   0x080483e7 <+4>:     xor    %eax,%eax
   0x080483e9 <+6>:     mov    %ebp,%esp
   0x080483eb <+8>:     pop    %ebp
   0x080483ec <+9>:     ret
   0x080483ed <+10>:    nop
   0x080483ee <+11>:    nop
   0x080483ef <+12>:    nop
End of assembler dump.
(gdb) p/a $ebp+4
$1 = 0xffffd9f8
(gdb) rwatch *(int*)0xffffd9f8
Hardware read watchpoint 2: *(int*)0xffffd9f8
(gdb) c
Continuing.
Hardware read watchpoint 2: *(int*)0xffffd9f8

Value = 134513633
0x080483e1 in main ()
(gdb) disas main
Dump of assembler code for function main:
   0x080483dc <+0>:     call   0x80483e3 <foo>
=> 0x080483e1 <+5>:     nop
   0x080483e2 <+6>:     ret
End of assembler dump.

Once you have the return address, you can also use everyday temporary breakpoint if your function is not reentrant:

(gdb) x/a $ebp+4
0xffffd9f8:     0x80483e1 <main+5>
(gdb) tbreak *0x80483e1
Temporary breakpoint 3 at 0x80483e1
(gdb) c
Continuing.

Temporary breakpoint 3, 0x080483e1 in main ()

Without a frame pointer, it's only easy to find the return address at the beginning of the function. Otherwise you will need to do some reverse engineering, to see how the stack pointer has been changed since the function entry.

like image 30
Jester Avatar answered Oct 04 '22 12:10

Jester