Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/debugger/breakpoints_line.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ void LineBreakpoints::ManagedLineBreakpoint::ToBreakpoint(Breakpoint &breakpoint
breakpoint.condition = this->condition;
breakpoint.source = Source(fullname);
breakpoint.line = this->linenum;
breakpoint.column = this->column;
breakpoint.endLine = this->endLine;
breakpoint.endColumn = this->endColumn;
breakpoint.hitCount = this->times;
}

Expand Down Expand Up @@ -169,7 +171,7 @@ static HRESULT ResolveLineBreakpoint(Modules *pModules, ICorDebugModule *pModule
else if (pModule) // Filter data from only one module during resolve, if need.
IfFailRet(pModule->GetBaseAddress(&modAddress));

IfFailRet(pModules->ResolveBreakpoint(modAddress, bp_fullname, bp_fullname_index, bp.linenum, resolvedPoints));
IfFailRet(pModules->ResolveBreakpoint(modAddress, bp_fullname, bp_fullname_index, bp.linenum, bp.column, resolvedPoints));
if (resolvedPoints.empty())
return E_FAIL;

Expand Down Expand Up @@ -224,6 +226,8 @@ static HRESULT ActivateLineBreakpoint(LineBreakpoints::ManagedLineBreakpoint &bp
// same for multiple breakpoint resolve for one module
bp.linenum = resolvedPoints[0].startLine;
bp.endLine = resolvedPoints[0].endLine;
bp.column = resolvedPoints[0].startColumn;
bp.endColumn = resolvedPoints[0].endColumn;
bp.modAddress = modAddress;

return S_OK;
Expand All @@ -245,6 +249,7 @@ HRESULT LineBreakpoints::ManagedCallbackLoadModule(ICorDebugModule *pModule, std
bp.module = initialBreakpoint.breakpoint.module;
bp.enabled = initialBreakpoint.enabled;
bp.linenum = initialBreakpoint.breakpoint.line;
bp.column = initialBreakpoint.breakpoint.column;
bp.endLine = initialBreakpoint.breakpoint.line;
bp.condition = initialBreakpoint.breakpoint.condition;
unsigned resolved_fullname_index = 0;
Expand Down Expand Up @@ -324,12 +329,13 @@ HRESULT LineBreakpoints::UpdateLineBreakpoint(bool haveProcess, int id, int line
bp.module = initialBreakpoint.breakpoint.module;
bp.enabled = initialBreakpoint.enabled;
bp.linenum = initialBreakpoint.breakpoint.line;
bp.column = initialBreakpoint.breakpoint.column;
bp.endLine = initialBreakpoint.breakpoint.line;
bp.condition = initialBreakpoint.breakpoint.condition;

unsigned resolved_fullname_index = 0;
std::vector<ModulesSources::resolved_bp_t> resolvedPoints;
if (FAILED(m_sharedModules->ResolveBreakpoint(modAddress, initialBreakpoints.first, resolved_fullname_index, bp.linenum, resolvedPoints)) ||
if (FAILED(m_sharedModules->ResolveBreakpoint(modAddress, initialBreakpoints.first, resolved_fullname_index, bp.linenum, bp.column, resolvedPoints)) ||
FAILED(ActivateLineBreakpoint(bp, initialBreakpoints.first, m_justMyCode, resolvedPoints)))
{
return S_OK;
Expand Down Expand Up @@ -434,6 +440,7 @@ HRESULT LineBreakpoints::SetLineBreakpoints(bool haveProcess, const std::string&
for (const auto &sb : lineBreakpoints)
{
int line = sb.line;
int column = sb.column;
Breakpoint breakpoint;

auto b = breakpointsInSourceMap.find(line);
Expand All @@ -448,6 +455,7 @@ HRESULT LineBreakpoints::SetLineBreakpoints(bool haveProcess, const std::string&
bp.id = initialBreakpoint.id;
bp.module = initialBreakpoint.breakpoint.module;
bp.linenum = line;
bp.column = column;
bp.endLine = line;
bp.condition = initialBreakpoint.breakpoint.condition;
unsigned resolved_fullname_index = 0;
Expand Down Expand Up @@ -511,6 +519,7 @@ HRESULT LineBreakpoints::SetLineBreakpoints(bool haveProcess, const std::string&
bp.id = initialBreakpoint.id;
bp.module = initialBreakpoint.breakpoint.module;
bp.linenum = line;
bp.column = column;
bp.endLine = line;
bp.condition = initialBreakpoint.breakpoint.condition;
bp.ToBreakpoint(breakpoint, filename);
Expand Down Expand Up @@ -573,6 +582,7 @@ HRESULT LineBreakpoints::UpdateBreakpointsOnHotReload(ICorDebugModule *pModule,
bp.module = initialBreakpoint.breakpoint.module;
bp.enabled = initialBreakpoint.enabled;
bp.linenum = initialBreakpoint.breakpoint.line;
bp.column = initialBreakpoint.breakpoint.column;
bp.endLine = initialBreakpoint.breakpoint.line;
bp.condition = initialBreakpoint.breakpoint.condition;
unsigned resolved_fullname_index = 0;
Expand Down
6 changes: 4 additions & 2 deletions src/debugger/breakpoints_line.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ class LineBreakpoints
std::string module;
CORDB_ADDRESS modAddress;
int linenum;
int column;
int endLine;
int endColumn;
bool enabled;
ULONG32 times;
std::string condition;
Expand All @@ -73,7 +75,7 @@ class LineBreakpoints
bool IsVerified() const { return !iCorFuncBreakpoints.empty(); }

ManagedLineBreakpoint() :
id(0), modAddress(0), linenum(0), endLine(0), enabled(true), times(0)
id(0), modAddress(0), linenum(0), column(0), endLine(0), endColumn(0), enabled(true), times(0)
{}

~ManagedLineBreakpoint()
Expand Down Expand Up @@ -107,7 +109,7 @@ class LineBreakpoints
unsigned resolved_fullname_index;
int resolved_linenum; // if int is 0 - no resolved breakpoint available in m_lineResolvedBreakpoints

ManagedLineBreakpointMapping() : breakpoint("", 0, ""), id(0), enabled(true), resolved_fullname_index(0), resolved_linenum(0) {}
ManagedLineBreakpointMapping() : breakpoint("", 0, 0, ""), id(0), enabled(true), resolved_fullname_index(0), resolved_linenum(0) {}
~ManagedLineBreakpointMapping() = default;
};

Expand Down
7 changes: 6 additions & 1 deletion src/interfaces/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,17 @@ struct Breakpoint
std::string message;
Source source;
int line;
int column;
int endLine;
int endColumn;

uint32_t hitCount; // exposed for MI protocol
std::string condition;
std::string module;
std::string funcname;
std::string params;

Breakpoint() : id(0), verified(false), line(0), endLine(0), hitCount(0) {}
Breakpoint() : id(0), verified(false), line(0), column(0), endLine(0), endColumn(0), hitCount(0) {}
};

enum SymbolStatus
Expand Down Expand Up @@ -434,13 +436,16 @@ struct LineBreakpoint
{
std::string module;
int line;
int column;
std::string condition;

LineBreakpoint(const std::string &module,
int linenum,
int col = 0,
const std::string &cond = std::string()) :
module(module),
line(linenum),
column(col),
condition(cond)
{}
};
Expand Down
47 changes: 37 additions & 10 deletions src/managed/SymbolReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -695,13 +695,17 @@ internal struct resolved_bp_t
{
public int startLine;
public int endLine;
public int startColumn;
public int endColumn;
public int ilOffset;
public int methodToken;

public resolved_bp_t(int startLine_, int endLine_, int ilOffset_, int methodToken_)
public resolved_bp_t(int startLine_, int endLine_, int startColumn_, int endColumn_, int ilOffset_, int methodToken_)
{
startLine = startLine_;
endLine = endLine_;
startColumn = startColumn_;
endColumn = endColumn_;
ilOffset = ilOffset_;
methodToken = methodToken_;
}
Expand All @@ -722,7 +726,7 @@ enum Position {
/// <param name="Count">entry's count in data</param>
/// <param name="data">pointer to memory with result</param>
/// <returns>"Ok" if information is available</returns>
internal static RetCode ResolveBreakPoints(IntPtr symbolReaderHandles, int tokenNum, IntPtr Tokens, int sourceLine, int nestedToken,
internal static RetCode ResolveBreakPoints(IntPtr symbolReaderHandles, int tokenNum, IntPtr Tokens, int sourceLine, int sourceColumn, int nestedToken,
out int Count, [MarshalAs(UnmanagedType.LPWStr)] string sourcePath, out IntPtr data)
{
Debug.Assert(symbolReaderHandles != IntPtr.Zero);
Expand All @@ -746,7 +750,7 @@ internal static RetCode ResolveBreakPoints(IntPtr symbolReaderHandles, int token
// We need check if nestedToken's method code closer to sourceLine than code from methodToken's method.
// If sourceLine closer to nestedToken's method code - setup breakpoint in nestedToken's method.

SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader reader, int methodToken)
SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader reader, int methodToken, bool filterByColumn = false)
{
// Note, SequencePoints ordered by IL offsets, not by line numbers.
// For example, infinite loop `while(true)` will have IL offset after cycle body's code.
Expand All @@ -757,6 +761,12 @@ SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader rea
if (p.StartLine == 0 || p.StartLine == SequencePoint.HiddenLine || p.EndLine < sourceLine)
continue;

// Skip sequence points that start strictly after our target column.
// Use > (not >=) so a cursor placed exactly at a statement's start column is kept.
// Only applied for the outer method — nested token calls use original line-only logic.
if (filterByColumn && sourceColumn > 0 && p.StartLine == sourceLine && p.StartColumn > sourceColumn)
continue;

// Note, in case of constructors, we must care about source too, since we may have situation when field/property have same line in another source.
var fileName = reader.GetString(reader.GetDocument(p.Document).Name);
if (fileName != sourcePath)
Expand All @@ -777,8 +787,16 @@ SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader rea
}
else
{
if ((reqPos == Position.First && p.EndColumn < nearestSP.EndColumn) ||
(reqPos == Position.Last && p.EndColumn > nearestSP.EndColumn))
// For column-aware outer-method selection: prefer the sequence point whose
// StartColumn is closest to (largest, not exceeding) the target column.
// For nested token calls and plain line breakpoints: keep original EndColumn logic.
if (filterByColumn && sourceColumn > 0 && p.StartLine == nearestSP.StartLine)
{
if (p.StartColumn > nearestSP.StartColumn)
nearestSP = p;
}
else if ((reqPos == Position.First && p.EndColumn < nearestSP.EndColumn) ||
(reqPos == Position.Last && p.EndColumn > nearestSP.EndColumn))
nearestSP = p;
}
}
Expand All @@ -794,7 +812,7 @@ SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader rea
MetadataReader reader = ((OpenedReader)gch.Target).Reader;

int methodToken = Marshal.ReadInt32(Tokens, i * elementSize);
SequencePoint current_p = SequencePointForSourceLine(Position.First, ref reader, methodToken);
SequencePoint current_p = SequencePointForSourceLine(Position.First, ref reader, methodToken, filterByColumn: true);
// Note, we don't check that current_p was found or not, since we know for sure, that sourceLine could be resolved in method.
// Same idea for nested_p below, if we have nestedToken - it will be resolved for sure.

Expand All @@ -812,24 +830,33 @@ SequencePoint SequencePointForSourceLine(Position reqPos, ref MetadataReader rea
if ((nested_start_p.StartLine > current_p.StartLine || (nested_start_p.StartLine == current_p.StartLine && nested_start_p.StartColumn > current_p.StartColumn)) &&
(nested_end_p.EndLine < current_p.EndLine || (nested_end_p.EndLine == current_p.EndLine && nested_end_p.EndColumn < current_p.EndColumn ))
) {
list.Add(new resolved_bp_t(current_p.StartLine, current_p.EndLine, current_p.Offset, methodToken));
break;
// Nested is fully within current_p's range.
// If column info places the cursor inside the nested body, fall through to
// condition 2 so the breakpoint lands in the lambda, not the outer call.
bool cursorInsideNested = sourceColumn > 0
&& sourceColumn >= nested_start_p.StartColumn
&& sourceColumn <= nested_end_p.EndColumn;
if (!cursorInsideNested)
{
list.Add(new resolved_bp_t(current_p.StartLine, current_p.EndLine, current_p.StartColumn, current_p.EndColumn, current_p.Offset, methodToken));
break;
}
}

// Note, sequence points can't partially overlap each other, since same lemmas can't belong to 2 different sequence points for sure.
// In this case we could check not "line" (start line - end line datas) but only "point" (end line data) for
// current method sequence point and first nested method sequence point.
if (current_p.EndLine > nested_start_p.EndLine || (current_p.EndLine == nested_start_p.EndLine && current_p.EndColumn > nested_start_p.EndColumn))
{
list.Add(new resolved_bp_t(nested_start_p.StartLine, nested_start_p.EndLine, nested_start_p.Offset, nestedToken));
list.Add(new resolved_bp_t(nested_start_p.StartLine, nested_start_p.EndLine, nested_start_p.StartColumn, nested_start_p.EndColumn, nested_start_p.Offset, nestedToken));
// (tokenNum > 1) can have only lines, that added to multiple constructors, in this case - we will have same for all Tokens,
// we need unique tokens only for breakpoints, prevent adding nestedToken multiple times.
break;
}
}
nestedToken = 0; // Don't check nested block next cycle (will have same results).

list.Add(new resolved_bp_t(current_p.StartLine, current_p.EndLine, current_p.Offset, methodToken));
list.Add(new resolved_bp_t(current_p.StartLine, current_p.EndLine, current_p.StartColumn, current_p.EndColumn, current_p.Offset, methodToken));
}

if (list.Count == 0)
Expand Down
6 changes: 3 additions & 3 deletions src/managed/interop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ typedef RetCode (*GetSequencePointsDelegate)(PVOID, mdMethodDef, PVOID*, int32_
typedef RetCode (*GetNextUserCodeILOffsetDelegate)(PVOID, mdMethodDef, uint32_t, uint32_t*, int32_t*);
typedef RetCode (*GetStepRangesFromIPDelegate)(PVOID, int32_t, mdMethodDef, uint32_t*, uint32_t*);
typedef RetCode (*GetModuleMethodsRangesDelegate)(PVOID, uint32_t, PVOID, uint32_t, PVOID, PVOID*);
typedef RetCode (*ResolveBreakPointsDelegate)(PVOID[], int32_t, PVOID, int32_t, int32_t, int32_t*, const WCHAR*, PVOID*);
typedef RetCode (*ResolveBreakPointsDelegate)(PVOID[], int32_t, PVOID, int32_t, int32_t, int32_t, int32_t*, const WCHAR*, PVOID*);
typedef RetCode (*GetAsyncMethodSteppingInfoDelegate)(PVOID, mdMethodDef, PVOID*, int32_t*, uint32_t*);
typedef RetCode (*GetSourceDelegate)(PVOID, const WCHAR*, int32_t*, PVOID*);
typedef PVOID (*LoadDeltaPdbDelegate)(const WCHAR*, PVOID*, int32_t*);
Expand Down Expand Up @@ -429,13 +429,13 @@ HRESULT GetModuleMethodsRanges(PVOID pSymbolReaderHandle, uint32_t constrTokensN
return retCode == RetCode::OK ? S_OK : E_FAIL;
}

HRESULT ResolveBreakPoints(PVOID pSymbolReaderHandles[], int32_t tokenNum, PVOID Tokens, int32_t sourceLine, int32_t nestedToken, int32_t &Count, const std::string &sourcePath, PVOID *data)
HRESULT ResolveBreakPoints(PVOID pSymbolReaderHandles[], int32_t tokenNum, PVOID Tokens, int32_t sourceLine, int32_t sourceColumn, int32_t nestedToken, int32_t &Count, const std::string &sourcePath, PVOID *data)
{
std::unique_lock<Utility::RWLock::Reader> read_lock(CLRrwlock.reader);
if (!resolveBreakPointsDelegate || !pSymbolReaderHandles || !Tokens || !data)
return E_FAIL;

RetCode retCode = resolveBreakPointsDelegate(pSymbolReaderHandles, tokenNum, Tokens, sourceLine, nestedToken, &Count, to_utf16(sourcePath).c_str(), data);
RetCode retCode = resolveBreakPointsDelegate(pSymbolReaderHandles, tokenNum, Tokens, sourceLine, sourceColumn, nestedToken, &Count, to_utf16(sourcePath).c_str(), data);
return retCode == RetCode::OK ? S_OK : E_FAIL;
}

Expand Down
2 changes: 1 addition & 1 deletion src/managed/interop.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ namespace Interop
HRESULT GetHoistedLocalScopes(PVOID pSymbolReaderHandle, mdMethodDef methodToken, PVOID *data, int32_t &hoistedLocalScopesCount);
HRESULT GetStepRangesFromIP(PVOID pSymbolReaderHandle, ULONG32 ip, mdMethodDef MethodToken, ULONG32 *ilStartOffset, ULONG32 *ilEndOffset);
HRESULT GetModuleMethodsRanges(PVOID pSymbolReaderHandle, uint32_t constrTokensNum, PVOID constrTokens, uint32_t normalTokensNum, PVOID normalTokens, PVOID *data);
HRESULT ResolveBreakPoints(PVOID pSymbolReaderHandles[], int32_t tokenNum, PVOID Tokens, int32_t sourceLine, int32_t nestedToken, int32_t &Count, const std::string &sourcePath, PVOID *data);
HRESULT ResolveBreakPoints(PVOID pSymbolReaderHandles[], int32_t tokenNum, PVOID Tokens, int32_t sourceLine, int32_t sourceColumn, int32_t nestedToken, int32_t &Count, const std::string &sourcePath, PVOID *data);
HRESULT GetAsyncMethodSteppingInfo(PVOID pSymbolReaderHandle, mdMethodDef methodToken, std::vector<AsyncAwaitInfoBlock> &AsyncAwaitInfo, ULONG32 *ilOffset);
HRESULT GetSource(PVOID symbolReaderHandle, const std::string fileName, PVOID *data, int32_t *length);
HRESULT LoadDeltaPdb(const std::string &pdbPath, VOID **ppSymbolReaderHandle, std::unordered_set<mdMethodDef> &methodTokens);
Expand Down
4 changes: 2 additions & 2 deletions src/metadata/modules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ HRESULT Modules::ForEachModule(std::function<HRESULT(ICorDebugModule *pModule)>
}

HRESULT Modules::ResolveBreakpoint(/*in*/ CORDB_ADDRESS modAddress, /*in*/ std::string filename, /*out*/ unsigned &fullname_index,
/*in*/ int sourceLine, /*out*/ std::vector<ModulesSources::resolved_bp_t> &resolvedPoints)
/*in*/ int sourceLine, /*in*/ int sourceColumn, /*out*/ std::vector<ModulesSources::resolved_bp_t> &resolvedPoints)
{
#ifdef WIN32
HRESULT Status;
Expand All @@ -749,7 +749,7 @@ HRESULT Modules::ResolveBreakpoint(/*in*/ CORDB_ADDRESS modAddress, /*in*/ std::

// Note, in all code we use m_modulesInfoMutex > m_sourcesInfoMutex lock sequence.
std::lock_guard<std::mutex> lockModulesInfo(m_modulesInfoMutex);
return m_modulesSources.ResolveBreakpoint(this, modAddress, filename, fullname_index, sourceLine, resolvedPoints);
return m_modulesSources.ResolveBreakpoint(this, modAddress, filename, fullname_index, sourceLine, sourceColumn, resolvedPoints);
}

HRESULT Modules::ApplyPdbDeltaAndLineUpdates(ICorDebugModule *pModule, bool needJMC, const std::string &deltaPDB,
Expand Down
Loading