mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-21 02:08:12 +03:00
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:
parent
cb432ffe8c
commit
99788e6b32
Notes:
sideshowbarker
2024-07-19 03:20:08 +09:00
Author: https://github.com/itamar8910 Commit: https://github.com/SerenityOS/serenity/commit/99788e6b328 Pull-request: https://github.com/SerenityOS/serenity/pull/3252
Before Width: | Height: | Size: 204 B After Width: | Height: | Size: 204 B |
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -7,6 +7,7 @@ set(SOURCES
|
||||
Dwarf/DwarfInfo.cpp
|
||||
Dwarf/Expression.cpp
|
||||
Dwarf/LineProgram.cpp
|
||||
StackFrameUtils.cpp
|
||||
)
|
||||
|
||||
serenity_lib(LibDebug debug)
|
||||
|
40
Libraries/LibDebug/StackFrameUtils.cpp
Normal file
40
Libraries/LibDebug/StackFrameUtils.cpp
Normal 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;
|
||||
}
|
||||
}
|
42
Libraries/LibDebug/StackFrameUtils.h
Normal file
42
Libraries/LibDebug/StackFrameUtils.h
Normal 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);
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user