Skip to content

Commit 80bd9c5

Browse files
ilya-nozhkintkrasnukha
authored andcommitted
Add break-watch command and related resources
This command asks a debugger to set a watchpoint on the given expression. See https://ftp.gnu.org/old-gnu/Manuals/gdb/html_node/gdb_222.html
1 parent 513f414 commit 80bd9c5

File tree

5 files changed

+302
-0
lines changed

5 files changed

+302
-0
lines changed

src/MICmdCmdBreak.cpp

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
// CMICmdCmdBreakCondition implementation.
1515

1616
// Third Party Headers:
17+
#include "cassert"
1718
#include "lldb/API/SBBreakpointLocation.h"
19+
#include "lldb/API/SBThread.h"
1820

1921
// In-house headers:
2022
#include "MICmdArgValFile.h"
@@ -1128,3 +1130,246 @@ bool CMICmdCmdBreakCondition::UpdateStopPtInfo(
11281130
sStopPtInfo.m_strCondition = m_strBrkPtExpr;
11291131
return rSessionInfo.RecordStopPtInfo(sStopPtInfo);
11301132
}
1133+
1134+
// Details: CMICmdCmdBreakWatch constructor.
1135+
// Type: Method.
1136+
// Args: None.
1137+
// Return: None.
1138+
// Throws: None.
1139+
//--
1140+
CMICmdCmdBreakWatch::CMICmdCmdBreakWatch()
1141+
: m_constStrArgNamedAccessWatchPt("a"), m_constStrArgNamedReadWatchPt("r"),
1142+
m_constStrArgNamedExpr("expr"), m_stopPtInfo(), m_watchPt() {
1143+
// Command factory matches this name with that received from the stdin stream
1144+
m_strMiCmd = "break-watch";
1145+
1146+
// Required by the CMICmdFactory when registering *this command
1147+
m_pSelfCreatorFn = &CMICmdCmdBreakWatch::CreateSelf;
1148+
}
1149+
1150+
//++
1151+
// Details: The invoker requires this function. The parses the command line
1152+
// options arguments to extract values for each of those arguments.
1153+
// Type: Overridden.
1154+
// Args: None.
1155+
// Return: MIstatus::success - Functional succeeded.
1156+
// MIstatus::failure - Functional failed.
1157+
// Throws: None.
1158+
//--
1159+
bool CMICmdCmdBreakWatch::ParseArgs() {
1160+
m_setCmdArgs.Add(new CMICmdArgValOptionShort(m_constStrArgNamedAccessWatchPt,
1161+
false, true));
1162+
m_setCmdArgs.Add(
1163+
new CMICmdArgValOptionShort(m_constStrArgNamedReadWatchPt, false, true));
1164+
m_setCmdArgs.Add(new CMICmdArgValText(m_constStrArgNamedExpr, true, true));
1165+
return ParseValidateCmdOptions();
1166+
}
1167+
1168+
static bool FindLocalVariableAddress(lldb::SBTarget &rSbTarget,
1169+
lldb::SBFrame &rSbFrame,
1170+
const CMIUtilString &rExpression,
1171+
lldb::addr_t &rAddress, size_t &rSize) {
1172+
auto sbVariableValue = rSbFrame.GetValueForVariablePath(
1173+
rExpression.c_str(), lldb::eNoDynamicValues);
1174+
auto sbAddress = sbVariableValue.GetAddress();
1175+
1176+
bool isValid = sbVariableValue && sbAddress;
1177+
if (isValid) {
1178+
rAddress = sbAddress.GetLoadAddress(rSbTarget);
1179+
rSize = sbVariableValue.GetByteSize();
1180+
}
1181+
1182+
return isValid;
1183+
}
1184+
1185+
static bool FindGlobalVariableAddress(lldb::SBTarget &rSbTarget,
1186+
lldb::SBFrame &rSbFrame,
1187+
const CMIUtilString &rExpression,
1188+
lldb::addr_t &rAddress, size_t &rSize) {
1189+
auto sbGlobalVariableValue =
1190+
rSbTarget.FindFirstGlobalVariable(rExpression.c_str());
1191+
if (sbGlobalVariableValue.IsValid()) {
1192+
auto sbAddress = sbGlobalVariableValue.GetAddress();
1193+
if (sbAddress.IsValid()) {
1194+
rAddress = sbAddress.GetLoadAddress(rSbTarget);
1195+
rSize = sbGlobalVariableValue.GetByteSize();
1196+
return MIstatus::success;
1197+
}
1198+
}
1199+
1200+
// In case the previous part didn't succeed, the expression must be something
1201+
// like "a.b". For locally-visible variables, there is
1202+
// SBFrame::GetValueForVariablePath that can handle this kind of expressions
1203+
// but there is no any analogue of this function for global variables. So, we
1204+
// have to try an address expression at least.
1205+
auto addressExpression = "&(" + rExpression + ")";
1206+
auto sbExpressionValue =
1207+
rSbFrame.EvaluateExpression(addressExpression.c_str());
1208+
1209+
lldb::SBError sbError;
1210+
rAddress =
1211+
static_cast<lldb::addr_t>(sbExpressionValue.GetValueAsUnsigned(sbError));
1212+
if (sbError.Fail())
1213+
return false;
1214+
1215+
assert(sbExpressionValue.TypeIsPointerType());
1216+
rSize = sbExpressionValue.GetType().GetPointeeType().GetByteSize();
1217+
1218+
return true;
1219+
}
1220+
1221+
static bool FindAddressByExpressionEvaluation(lldb::SBTarget &rSbTarget,
1222+
lldb::SBFrame &rSbFrame,
1223+
const CMIUtilString &rExpression,
1224+
lldb::addr_t &rAddress,
1225+
size_t &rSize) {
1226+
auto sbExpressionValue = rSbFrame.EvaluateExpression(rExpression.c_str());
1227+
1228+
lldb::SBError sbError;
1229+
rAddress =
1230+
static_cast<lldb::addr_t>(sbExpressionValue.GetValueAsUnsigned(sbError));
1231+
if (sbError.Fail())
1232+
return false;
1233+
1234+
if (sbExpressionValue.TypeIsPointerType())
1235+
rSize = sbExpressionValue.GetType().GetPointeeType().GetByteSize();
1236+
else
1237+
rSize = rSbTarget.GetDataByteSize();
1238+
1239+
return true;
1240+
}
1241+
1242+
//++
1243+
// Details: The invoker requires this function. The command does work in this
1244+
// function. The command is likely to communicate with the LLDB
1245+
// SBDebugger in here.
1246+
// Type: Overridden.
1247+
// Args: None.
1248+
// Return: MIstatus::success - Functional succeeded.
1249+
// MIstatus::failure - Functional failed.
1250+
// Throws: None.
1251+
//--
1252+
bool CMICmdCmdBreakWatch::Execute() {
1253+
CMICMDBASE_GETOPTION(pArgAccess, OptionShort,
1254+
m_constStrArgNamedAccessWatchPt);
1255+
CMICMDBASE_GETOPTION(pArgRead, OptionShort, m_constStrArgNamedReadWatchPt);
1256+
CMICMDBASE_GETOPTION(pArgExpr, Text, m_constStrArgNamedExpr);
1257+
1258+
// Ask LLDB for the target to check if we have valid or dummy one.
1259+
CMICmnLLDBDebugSessionInfo &rSessionInfo(
1260+
CMICmnLLDBDebugSessionInfo::Instance());
1261+
auto sbTarget = rSessionInfo.GetTarget();
1262+
auto sbProcess = rSessionInfo.GetProcess();
1263+
auto sbThread = sbProcess.GetSelectedThread();
1264+
auto sbFrame = sbThread.GetSelectedFrame();
1265+
1266+
if (!sbFrame) {
1267+
SetError(CMIUtilString::Format(MIRSRC(IDS_CMD_ERR_INVALID_FRAME),
1268+
m_cmdData.strMiCmd.c_str()));
1269+
return MIstatus::failure;
1270+
}
1271+
1272+
lldb::addr_t address;
1273+
size_t size;
1274+
1275+
bool isVariable = true;
1276+
auto expression = pArgExpr->GetValue();
1277+
if (!FindLocalVariableAddress(sbTarget, sbFrame, expression, address, size) &&
1278+
!FindGlobalVariableAddress(sbTarget, sbFrame, expression, address,
1279+
size)) {
1280+
isVariable = false;
1281+
if (!FindAddressByExpressionEvaluation(sbTarget, sbFrame, expression,
1282+
address, size)) {
1283+
SetError(CMIUtilString::Format(MIRSRC(IDS_CMD_ERR_FIND_EXPR_ADDRESS),
1284+
m_cmdData.strMiCmd.c_str(),
1285+
expression.c_str()));
1286+
return MIstatus::failure;
1287+
}
1288+
}
1289+
1290+
bool read = pArgAccess->GetFound() || pArgRead->GetFound();
1291+
bool write = !pArgRead->GetFound();
1292+
1293+
lldb::SBError sbError;
1294+
m_watchPt = sbTarget.WatchAddress(address, size, read, write, sbError);
1295+
1296+
if (!m_watchPt) {
1297+
const char *type = "write";
1298+
if (pArgAccess->GetFound())
1299+
type = "access";
1300+
else if (pArgRead->GetFound())
1301+
type = "read";
1302+
1303+
SetError(CMIUtilString::Format(
1304+
MIRSRC(IDS_CMD_ERR_CREATE_WATCHPT), m_cmdData.strMiCmd.c_str(), type,
1305+
static_cast<uint64_t>(address), static_cast<uint64_t>(size)));
1306+
return MIstatus::failure;
1307+
}
1308+
1309+
if (!rSessionInfo.GetStopPtInfo(m_watchPt, m_stopPtInfo)) {
1310+
SetError(CMIUtilString::Format(
1311+
MIRSRC(IDS_CMD_ERR_WATCHPT_STOPPT_INFO_CREATE),
1312+
m_cmdData.strMiCmd.c_str(), static_cast<uint64_t>(m_watchPt.GetID())));
1313+
return MIstatus::failure;
1314+
}
1315+
1316+
m_stopPtInfo.m_bDisp = false;
1317+
m_stopPtInfo.m_bEnabled = m_watchPt.IsEnabled();
1318+
m_stopPtInfo.m_bHaveArgOptionThreadGrp = false;
1319+
m_stopPtInfo.m_nTimes = m_watchPt.GetHitCount();
1320+
m_stopPtInfo.m_watchPtVariable = isVariable;
1321+
m_stopPtInfo.m_watchPtExpr = expression;
1322+
m_stopPtInfo.m_watchPtRead = read;
1323+
m_stopPtInfo.m_watchPtWrite = write;
1324+
m_stopPtInfo.m_nIgnore = m_watchPt.GetIgnoreCount();
1325+
m_stopPtInfo.m_bPending = false;
1326+
m_stopPtInfo.m_bCondition = m_watchPt.GetCondition() != nullptr;
1327+
m_stopPtInfo.m_strCondition =
1328+
m_stopPtInfo.m_bCondition ? m_watchPt.GetCondition() : "";
1329+
m_stopPtInfo.m_bBrkPtThreadId = false;
1330+
1331+
if (!rSessionInfo.RecordStopPtInfo(m_stopPtInfo)) {
1332+
SetError(CMIUtilString::Format(
1333+
MIRSRC(IDS_CMD_ERR_STOPPT_INFO_SET), m_cmdData.strMiCmd.c_str(),
1334+
static_cast<uint64_t>(m_stopPtInfo.m_nMiId)));
1335+
return MIstatus::failure;
1336+
}
1337+
1338+
return MIstatus::success;
1339+
}
1340+
1341+
//++
1342+
// Details: The invoker requires this function. The command prepares a MI Record
1343+
// Result for the work carried out in the Execute().
1344+
// Type: Overridden.
1345+
// Args: None.
1346+
// Return: MIstatus::success - Functional succeeded.
1347+
// MIstatus::failure - Functional failed.
1348+
// Throws: None.
1349+
//--
1350+
bool CMICmdCmdBreakWatch::Acknowledge() {
1351+
assert(m_watchPt.IsValid());
1352+
1353+
CMICmnMIValueResult miValueResult;
1354+
CMICmnLLDBDebugSessionInfo::Instance().MIResponseFormWatchPtInfo(
1355+
m_stopPtInfo, miValueResult);
1356+
1357+
const CMICmnMIResultRecord miRecordResult(
1358+
m_cmdData.strMiCmdToken, CMICmnMIResultRecord::eResultClass_Done,
1359+
miValueResult);
1360+
m_miResultRecord = miRecordResult;
1361+
1362+
return MIstatus::success;
1363+
}
1364+
1365+
//++
1366+
// Details: Required by the CMICmdFactory when registering *this command. The
1367+
// factory calls this function to create an instance of *this command.
1368+
// Type: Static method.
1369+
// Args: None.
1370+
// Return: CMICmdBase * - Pointer to a new command.
1371+
// Throws: None.
1372+
//--
1373+
CMICmdBase *CMICmdCmdBreakWatch::CreateSelf() {
1374+
return new CMICmdCmdBreakWatch();
1375+
}

src/MICmdCmdBreak.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
// Third party headers:
3131
#include "lldb/API/SBBreakpoint.h"
32+
#include "lldb/API/SBWatchpoint.h"
3233

3334
// In-house headers:
3435
#include "MICmdBase.h"
@@ -296,3 +297,39 @@ class CMICmdCmdBreakCondition : public CMICmdBase {
296297
MIuint m_nMiStopPtId;
297298
CMIUtilString m_strBrkPtExpr;
298299
};
300+
301+
//++
302+
//============================================================================
303+
// Details: MI command class. MI commands derived from the command base class.
304+
// *this class implements MI command "break-watch".
305+
//--
306+
class CMICmdCmdBreakWatch : public CMICmdBase {
307+
// Statics:
308+
public:
309+
// Required by the CMICmdFactory when registering *this command
310+
static CMICmdBase *CreateSelf();
311+
312+
// Methods:
313+
public:
314+
/* ctor */ CMICmdCmdBreakWatch();
315+
316+
// Overridden:
317+
public:
318+
// From CMICmdInvoker::ICmd
319+
bool Execute() override;
320+
bool Acknowledge() override;
321+
bool ParseArgs() override;
322+
// From CMICmnBase
323+
/* dtor */ ~CMICmdCmdBreakWatch() override = default;
324+
325+
// Methods:
326+
private:
327+
// Attributes:
328+
private:
329+
const CMIUtilString m_constStrArgNamedAccessWatchPt;
330+
const CMIUtilString m_constStrArgNamedReadWatchPt;
331+
const CMIUtilString m_constStrArgNamedExpr;
332+
333+
CMICmnLLDBDebugSessionInfo::SStopPtInfo m_stopPtInfo;
334+
lldb::SBWatchpoint m_watchPt;
335+
};

src/MICmdCommands.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ bool MICmnCommands::RegisterAll() {
7777
bOk &= Register<CMICmdCmdBreakDisable>();
7878
bOk &= Register<CMICmdCmdBreakEnable>();
7979
bOk &= Register<CMICmdCmdBreakInsert>();
80+
bOk &= Register<CMICmdCmdBreakWatch>();
8081
bOk &= Register<CMICmdCmdDataDisassemble>();
8182
bOk &= Register<CMICmdCmdDataEvaluateExpression>();
8283
bOk &= Register<CMICmdCmdDataInfoLine>();

src/MICmnResources.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,20 @@ const CMICmnResources::SRsrcTextData
380380
{IDS_CMD_ERR_STOPPT_CNT_EXCEEDED,
381381
"Command '%s'. Number of valid stoppoint exceeded %" PRIu64
382382
". Cannot create new stoppoint %" PRIu64},
383+
{IDS_CMD_ERR_INVALID_FRAME,
384+
"Command '%s'. The selected frame is invalid."},
385+
{IDS_CMD_ERR_WATCHPT_STOPPT_INFO_CREATE,
386+
"Command '%s'. Failed to create stoppoint "
387+
"information for watchpoint LLDB ID %" PRIu64},
388+
{IDS_CMD_ERR_STOPPT_INFO_SET,
389+
"Command '%s'. Failed to set stoppoint "
390+
"information for unified stoppoint ID %" PRIu64},
391+
{IDS_CMD_ERR_FIND_EXPR_ADDRESS,
392+
"Command '%s'. Expression '%s' does not specify a locally visible "
393+
"or global variable and cannot be interpreted as an address"},
394+
{IDS_CMD_ERR_CREATE_WATCHPT,
395+
"Command '%s'. Couldn't create %s watchpoint at address 0x%" PRIx64
396+
"with size %" PRIu64},
383397
{IDS_CMD_ERR_SOME_ERROR, "Command '%s'. Error: %s"},
384398
{IDS_CMD_ERR_THREAD_INVALID, "Command '%s'. Thread ID invalid"},
385399
{IDS_CMD_ERR_THREAD_FRAME_RANGE_INVALID,

src/MICmnResources.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,11 @@ enum {
236236
IDS_CMD_ERR_BRKPT_LOCATION_NOT_FOUND,
237237
IDS_CMD_ERR_STOPPT_INVALID,
238238
IDS_CMD_ERR_STOPPT_CNT_EXCEEDED,
239+
IDS_CMD_ERR_INVALID_FRAME,
240+
IDS_CMD_ERR_WATCHPT_STOPPT_INFO_CREATE,
241+
IDS_CMD_ERR_STOPPT_INFO_SET,
242+
IDS_CMD_ERR_FIND_EXPR_ADDRESS,
243+
IDS_CMD_ERR_CREATE_WATCHPT,
239244
IDS_CMD_ERR_SOME_ERROR,
240245
IDS_CMD_ERR_THREAD_INVALID,
241246
IDS_CMD_ERR_THREAD_FRAME_RANGE_INVALID,

0 commit comments

Comments
 (0)