HackStudio: Implement "Step Out" debugging action

The "Step Out" action continues execution until the current function
returns.

Also, LibDebug/StackFrameUtils was introduced to eliminate the
duplication of stack frame inspection logic between the "Step Out"
action and the BacktraceModel.
This commit is contained in:
Itamar 2020-08-21 12:27:15 +03:00 committed by Andreas Kling
parent cb432ffe8c
commit 99788e6b32
Notes: sideshowbarker 2024-07-19 03:20:08 +09:00
8 changed files with 171 additions and 16 deletions

View File

Before

Width:  |  Height:  |  Size: 204 B

After

Width:  |  Height:  |  Size: 204 B

View File

@ -26,6 +26,7 @@
#include "BacktraceModel.h"
#include "Debugger.h"
#include <LibDebug/StackFrameUtils.h>
namespace HackStudio {
@ -63,8 +64,10 @@ Vector<BacktraceModel::FrameInfo> BacktraceModel::create_backtrace(const DebugSe
}
frames.append({ name, current_instruction, current_ebp });
current_instruction = Debugger::the().session()->peek(reinterpret_cast<u32*>(current_ebp + 4)).value();
current_ebp = Debugger::the().session()->peek(reinterpret_cast<u32*>(current_ebp)).value();
auto frame_info = StackFrameUtils::get_info(*Debugger::the().session(), current_ebp);
ASSERT(frame_info.has_value());
current_instruction = frame_info.value().return_address;
current_ebp = frame_info.value().next_ebp;
} while (current_ebp && current_instruction);
return frames;
}

View File

@ -50,17 +50,25 @@ void DebugInfoWidget::init_toolbar()
pthread_mutex_unlock(Debugger::the().continue_mutex());
});
m_singlestep_action = GUI::Action::create("Single Step", Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-single-step.png"), [&](auto&) {
m_singlestep_action = GUI::Action::create("Step Over", Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-step-over.png"), [&](auto&) {
pthread_mutex_lock(Debugger::the().continue_mutex());
Debugger::the().set_continue_type(Debugger::ContinueType::SourceStepOver);
pthread_cond_signal(Debugger::the().continue_cond());
pthread_mutex_unlock(Debugger::the().continue_mutex());
});
m_step_in_action = GUI::Action::create("Step In", Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-step-in.png"), [&](auto&) {
pthread_mutex_lock(Debugger::the().continue_mutex());
Debugger::the().set_continue_type(Debugger::ContinueType::SourceSingleStep);
pthread_cond_signal(Debugger::the().continue_cond());
pthread_mutex_unlock(Debugger::the().continue_mutex());
});
m_step_in_action = GUI::Action::create("Step In", Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-step-in.png"), [&](auto&) {
});
m_step_out_action = GUI::Action::create("Step Out", Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-step-out.png"), [&](auto&) {
pthread_mutex_lock(Debugger::the().continue_mutex());
Debugger::the().set_continue_type(Debugger::ContinueType::SourceStepOut);
pthread_cond_signal(Debugger::the().continue_cond());
pthread_mutex_unlock(Debugger::the().continue_mutex());
});
m_toolbar->add_action(*m_continue_action);

View File

@ -25,6 +25,7 @@
*/
#include "Debugger.h"
#include <LibDebug/StackFrameUtils.h>
namespace HackStudio {
@ -127,7 +128,7 @@ void Debugger::start()
int Debugger::debugger_loop()
{
DebuggingState state;
ASSERT(m_debug_session);
m_debug_session->run([&](DebugSession::DebugBreakReason reason, Optional<PtraceRegisters> optional_regs) {
if (reason == DebugSession::DebugBreakReason::Exited) {
@ -135,14 +136,15 @@ int Debugger::debugger_loop()
m_on_exit_callback();
return DebugSession::DebugDecision::Detach;
}
remove_temporary_breakpoints();
ASSERT(optional_regs.has_value());
const PtraceRegisters& regs = optional_regs.value();
auto source_position = m_debug_session->debug_info().get_source_position(regs.eip);
if (state.get() == Debugger::DebuggingState::SingleStepping) {
if (m_state.get() == Debugger::DebuggingState::SingleStepping) {
ASSERT(source_position.has_value());
if (state.should_stop_single_stepping(source_position.value())) {
state.set_normal();
if (m_state.should_stop_single_stepping(source_position.value())) {
m_state.set_normal();
} else {
return DebugSession::DebugDecision::SingleStep;
}
@ -160,13 +162,21 @@ int Debugger::debugger_loop()
m_continue_type = ContinueType::Continue;
}
if (m_continue_type == ContinueType::Continue) {
switch (m_continue_type) {
case ContinueType::Continue:
m_state.set_normal();
return DebugSession::DebugDecision::Continue;
}
if (m_continue_type == ContinueType::SourceSingleStep) {
state.set_single_stepping(source_position.value());
case ContinueType::SourceSingleStep:
m_state.set_single_stepping(source_position.value());
return DebugSession::DebugDecision::SingleStep;
case ContinueType::SourceStepOut:
m_state.set_stepping_out();
do_step_out(regs);
return DebugSession::DebugDecision::Continue;
case ContinueType::SourceStepOver:
m_state.set_stepping_over();
do_step_over(regs);
return DebugSession::DebugDecision::Continue;
}
ASSERT_NOT_REACHED();
});
@ -192,4 +202,37 @@ bool Debugger::DebuggingState::should_stop_single_stepping(const DebugInfo::Sour
return m_original_source_position.value() != current_source_position;
}
void Debugger::remove_temporary_breakpoints()
{
for (auto breakpoint_address : m_state.temporary_breakpoints()) {
bool rc = m_debug_session->remove_breakpoint((void*)breakpoint_address);
ASSERT(rc);
}
m_state.clear_temporary_breakpoints();
}
void Debugger::DebuggingState::clear_temporary_breakpoints()
{
m_addresses_of_temporary_breakpoints.clear();
}
void Debugger::DebuggingState::add_temporary_breakpoint(u32 address)
{
m_addresses_of_temporary_breakpoints.append(address);
}
void Debugger::do_step_out(const PtraceRegisters& regs)
{
auto frame_info = StackFrameUtils::get_info(*m_debug_session, regs.ebp);
ASSERT(frame_info.has_value());
u32 return_address = frame_info.value().return_address;
bool success = m_debug_session->insert_breakpoint(reinterpret_cast<void*>(return_address));
ASSERT(success);
m_state.add_temporary_breakpoint(return_address);
}
void Debugger::do_step_over(const PtraceRegisters&)
{
// TODO: Implement
}
}

View File

@ -66,6 +66,8 @@ public:
enum class ContinueType {
Continue,
SourceSingleStep,
SourceStepOut,
SourceStepOver,
};
void set_continue_type(ContinueType type) { m_continue_type = type; }
@ -75,17 +77,27 @@ private:
class DebuggingState {
public:
enum State {
Normal,
Normal, // Continue normally until we hit a breakpoint / program terminates
SingleStepping,
SteppingOut,
SteppingOver,
};
State get() const { return m_state; }
void set_normal();
void set_single_stepping(DebugInfo::SourcePosition original_source_position);
void set_stepping_out() { m_state = State::SteppingOut; }
void set_stepping_over() { m_state = State::SteppingOver; }
bool should_stop_single_stepping(const DebugInfo::SourcePosition& current_source_position) const;
void clear_temporary_breakpoints();
void add_temporary_breakpoint(u32 address);
const Vector<u32>& temporary_breakpoints() const { return m_addresses_of_temporary_breakpoints; }
private:
State m_state { Normal };
Optional<DebugInfo::SourcePosition> m_original_source_position; // The source position at which we started the current single step
Vector<u32> m_addresses_of_temporary_breakpoints;
};
explicit Debugger(
@ -98,12 +110,18 @@ private:
void start();
int debugger_loop();
void remove_temporary_breakpoints();
void do_step_out(const PtraceRegisters&);
void do_step_over(const PtraceRegisters&);
OwnPtr<DebugSession> m_debug_session;
DebuggingState m_state;
pthread_mutex_t m_continue_mutex {};
pthread_cond_t m_continue_cond {};
Vector<DebugInfo::SourcePosition> m_breakpoints;
String m_executable_path;
Function<HasControlPassedToUser(const PtraceRegisters&)> m_on_stopped_callback;

View File

@ -7,6 +7,7 @@ set(SOURCES
Dwarf/DwarfInfo.cpp
Dwarf/Expression.cpp
Dwarf/LineProgram.cpp
StackFrameUtils.cpp
)
serenity_lib(LibDebug debug)

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "StackFrameUtils.h"
namespace StackFrameUtils {
Optional<StackFrameInfo> get_info(const DebugSession& session, FlatPtr current_ebp)
{
auto return_address = session.peek(reinterpret_cast<u32*>(current_ebp + sizeof(FlatPtr)));
auto next_ebp = session.peek(reinterpret_cast<u32*>(current_ebp));
if (!return_address.has_value() || !next_ebp.has_value())
return {};
StackFrameInfo info = { return_address.value(), next_ebp.value() };
return info;
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/Optional.h>
#include <AK/Types.h>
#include "LibDebug/DebugSession.h"
namespace StackFrameUtils {
struct StackFrameInfo {
FlatPtr return_address;
FlatPtr next_ebp;
};
Optional<StackFrameInfo> get_info(const DebugSession&, FlatPtr current_ebp);
}