Skip to content

Commit e3ccbae

Browse files
committed
[lldb-vscode] Send Statistics Dump in terminated event
This patch will gather debug info & breakpoint info from the statistics dump (from `(SBTarget.GetStatistics())` func) and send to DAP in terminated event. The statistics content can be huge (especially the `modules`) and dumping in full JSON can create delay in the IDE's debugging UI. (For more details, please read: 7bbd0fb ). Hence, we will filter out large contents before returning it in terminated event. It will keep all the metadata fields (those starts with "total"). For large contents, it uses the opt-out strategy. Currently it only removes the "modules" field. This way every time a new top-level field being added, we will be able to capture them from DAP log without changing lldb-vscode. The DAP terminated event should look like ``` { "event":"terminated", "seq":0, "statistics": { "memory": <JSON string> "targets": <JSON string>, // it's a JSON array, breakpoints info included in each target <metadata_key: value> // pairs }, "type":"event" } ``` All the info above will be append to statistics field in the terminated event Test Plan Debugged a simple hello world program from VSCode. Exit debug session in two ways: 1) run to program exit; 2) user initiated debug session end (quit debugging before program exit). Check DAP log and see both debug sessions have statistics returned in terminated event. Here's an example when debugging the test program: ``` {"event":"terminated","seq":0,"statistics":{"memory":"{\"strings\":{\"bytesTotal\":1843200,\"bytesUnused\":897741,\"bytesUsed\":945459}}","targets":"[{\"breakpoints\":[{\"details\":{\"Breakpoint\":{\"BKPTOptions\":{\"AutoContinue\":false,\"ConditionText\":\"\",\"EnabledState\":true,\"IgnoreCount\":0,\"OneShotState\":false},\"BKPTResolver\":{\"Options\":{\"NameMask\":[56],\"Offset\":0,\"SkipPrologue\":true,\"SymbolNames\":[\"foo\"]},\"Type\":\"SymbolName\"},\"Hardware\":false,\"Names\":[\"vscode\"],\"SearchFilter\":{\"Options\":{},\"Type\":\"Unconstrained\"}}},\"id\":1,\"internal\":false,\"numLocations\":1,\"numResolvedLocations\":1,\"resolveTime\":0.002232},{\"details\":{\"Breakpoint\":{\"BKPTOptions\":{\"AutoContinue\":false,\"ConditionText\":\"\",\"EnabledState\":true,\"IgnoreCount\":0,\"OneShotState\":false},\"BKPTResolver\":{\"Options\":{\"Column\":0,\"Exact\":false,\"FileName\":\"/data/users/wanyi/llvm-sand/external/llvm-project/lldb/test/API/tools/lldb-vscode/terminated-event/main.cpp\",\"Inlines\":true,\"LineNumber\":5,\"Offset\":0,\"SkipPrologue\":true},\"Type\":\"FileAndLine\"},\"Hardware\":false,\"Names\":[\"vscode\"],\"SearchFilter\":{\"Options\":{},\"Type\":\"Unconstrained\"}}},\"id\":2,\"internal\":false,\"numLocations\":0,\"numResolvedLocations\":0,\"resolveTime\":0.23203799999999999},{\"details\":{\"Breakpoint\":{\"BKPTOptions\":{\"AutoContinue\":false,\"ConditionText\":\"\",\"EnabledState\":true,\"IgnoreCount\":0,\"OneShotState\":false},\"BKPTResolver\":{\"Options\":{\"Language\":\"c\",\"NameMask\":[4,4,4,4,4,4],\"Offset\":0,\"SkipPrologue\":false,\"SymbolNames\":[\"_dl_debug_state\",\"rtld_db_dlactivity\",\"__dl_rtld_db_dlactivity\",\"r_debug_state\",\"_r_debug_state\",\"_rtld_debug_state\"]},\"Type\":\"SymbolName\"},\"Hardware\":false,\"SearchFilter\":{\"Options\":{\"ModuleList\":[\"/usr/lib64/ld-2.28.so\"]},\"Type\":\"Modules\"}}},\"id\":-1,\"internal\":true,\"kindDescription\":\"shared-library-event\",\"numLocations\":1,\"numResolvedLocations\":1,\"resolveTime\":0.00026699999999999998}],\"expressionEvaluation\":{\"failures\":0,\"successes\":0},\"firstStopTime\":0.087458974999999994,\"frameVariable\":{\"failures\":0,\"successes\":0},\"launchOrAttachTime\":0.052953161999999998,\"moduleIdentifiers\":[94554748126576,94554747837792,94554747149216,139800112130176,139800112161056,139800112206064,139800112340224,139800112509552,139800112236528],\"signals\":[{\"SIGSTOP\":1}],\"sourceMapDeduceCount\":0,\"stopCount\":8,\"targetCreateTime\":0.00057700000000000004,\"totalBreakpointResolveTime\":0.234537}]","totalDebugInfoByteSize":1668056,"totalDebugInfoEnabled":3,"totalDebugInfoIndexLoadedFromCache":0,"totalDebugInfoIndexSavedToCache":0,"totalDebugInfoIndexTime":0.027963000000000002,"totalDebugInfoParseTime":0.34354800000000002,"totalModuleCount":10,"totalModuleCountHasDebugInfo":3,"totalSymbolTableIndexTime":0.056050000000000003,"totalSymbolTableParseTime":0.23930000000000001,"totalSymbolTableStripped":0,"totalSymbolTablesLoadedFromCache":0,"totalSymbolTablesSavedToCache":0},"type":"event"} ``` Differential Revision: https://reviews.llvm.org/D137003
1 parent 2bbafe0 commit e3ccbae

File tree

9 files changed

+176
-3
lines changed

9 files changed

+176
-3
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,13 @@ def wait_for_stopped(self, timeout=None):
369369
def wait_for_exited(self):
370370
event_dict = self.wait_for_event('exited')
371371
if event_dict is None:
372-
raise ValueError("didn't get stopped event")
372+
raise ValueError("didn't get exited event")
373+
return event_dict
374+
375+
def wait_for_terminated(self):
376+
event_dict = self.wait_for_event('terminated')
377+
if event_dict is None:
378+
raise ValueError("didn't get terminated event")
373379
return event_dict
374380

375381
def get_initialize_value(self, key):
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
DYLIB_NAME := foo
2+
DYLIB_CXX_SOURCES := foo.cpp
3+
CXX_SOURCES := main.cpp
4+
5+
LD_EXTRAS := -Wl,-rpath "-Wl,$(shell pwd)"
6+
USE_LIBDL :=1
7+
8+
include Makefile.rules
9+
10+
all: a.out.stripped
11+
12+
a.out.stripped:
13+
strip -o a.out.stripped a.out
14+
15+
ifneq "$(CODESIGN)" ""
16+
$(CODESIGN) -fs - a.out.stripped
17+
endif
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""
2+
Test lldb-vscode terminated event
3+
"""
4+
5+
import vscode
6+
from lldbsuite.test.decorators import *
7+
from lldbsuite.test.lldbtest import *
8+
from lldbsuite.test import lldbutil
9+
import lldbvscode_testcase
10+
import re
11+
import json
12+
13+
class TestVSCode_terminatedEvent(lldbvscode_testcase.VSCodeTestCaseBase):
14+
15+
@skipIfWindows
16+
@skipIfRemote
17+
def test_terminated_event(self):
18+
'''
19+
Terminated Event
20+
Now contains the statistics of a debug session:
21+
metatdata:
22+
totalDebugInfoByteSize > 0
23+
totalDebugInfoEnabled > 0
24+
totalModuleCountHasDebugInfo > 0
25+
...
26+
targetInfo:
27+
totalBreakpointResolveTime > 0
28+
breakpoints:
29+
recognize function breakpoint
30+
recognize source line breakpoint
31+
It should contains the breakpoints info: function bp & source line bp
32+
'''
33+
34+
program_basename = "a.out.stripped"
35+
program = self.getBuildArtifact(program_basename)
36+
self.build_and_launch(program)
37+
# Set breakpoints
38+
functions = ['foo']
39+
breakpoint_ids = self.set_function_breakpoints(functions)
40+
self.assertEquals(len(breakpoint_ids), len(functions), 'expect one breakpoint')
41+
main_bp_line = line_number('main.cpp', '// main breakpoint 1')
42+
breakpoint_ids.append(self.set_source_breakpoints('main.cpp', [main_bp_line]))
43+
44+
self.continue_to_breakpoints(breakpoint_ids)
45+
self.continue_to_exit()
46+
47+
statistics = self.vscode.wait_for_terminated()['statistics']
48+
self.assertTrue(statistics['totalDebugInfoByteSize'] > 0)
49+
self.assertTrue(statistics['totalDebugInfoEnabled'] > 0)
50+
self.assertTrue(statistics['totalModuleCountHasDebugInfo'] > 0)
51+
52+
self.assertIsNotNone(statistics['memory'])
53+
54+
# lldb-vscode debugs one target at a time
55+
target = json.loads(statistics['targets'])[0]
56+
self.assertTrue(target['totalBreakpointResolveTime'] > 0)
57+
58+
breakpoints = target['breakpoints']
59+
self.assertIn('foo',
60+
breakpoints[0]['details']['Breakpoint']['BKPTResolver']['Options']['SymbolNames'],
61+
'foo is a symbol breakpoint')
62+
self.assertTrue(breakpoints[1]['details']['Breakpoint']['BKPTResolver']['Options']['FileName'].endswith('main.cpp'),
63+
'target has source line breakpoint in main.cpp')
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
int foo() {
2+
return 12;
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int foo();
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include <iostream>
2+
#include "foo.h"
3+
4+
int main(int argc, char const *argv[]) {
5+
std::cout << "Hello World!" << std::endl; // main breakpoint 1
6+
foo();
7+
return 0;
8+
}

lldb/tools/lldb-vscode/JSONUtils.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include "lldb/API/SBBreakpoint.h"
2020
#include "lldb/API/SBBreakpointLocation.h"
2121
#include "lldb/API/SBDeclaration.h"
22+
#include "lldb/API/SBStringList.h"
23+
#include "lldb/API/SBStructuredData.h"
2224
#include "lldb/API/SBValue.h"
2325
#include "lldb/Host/PosixApi.h"
2426

@@ -1140,6 +1142,73 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
11401142
return reverse_request;
11411143
}
11421144

1145+
// Keep all the top level items from the statistics dump, except for the
1146+
// "modules" array. It can be huge and cause delay
1147+
// Array and dictionary value will return as <key, JSON string> pairs
1148+
void FilterAndGetValueForKey(const lldb::SBStructuredData data, const char *key,
1149+
llvm::json::Object &out) {
1150+
lldb::SBStructuredData value = data.GetValueForKey(key);
1151+
std::string key_utf8 = llvm::json::fixUTF8(key);
1152+
if (strcmp(key, "modules") == 0)
1153+
return;
1154+
switch (value.GetType()) {
1155+
case lldb::eStructuredDataTypeFloat:
1156+
out.try_emplace(key_utf8, value.GetFloatValue());
1157+
break;
1158+
case lldb::eStructuredDataTypeInteger:
1159+
out.try_emplace(key_utf8, value.GetIntegerValue());
1160+
break;
1161+
case lldb::eStructuredDataTypeArray: {
1162+
lldb::SBStream contents;
1163+
value.GetAsJSON(contents);
1164+
EmplaceSafeString(out, key, contents.GetData());
1165+
} break;
1166+
case lldb::eStructuredDataTypeBoolean:
1167+
out.try_emplace(key_utf8, value.GetBooleanValue());
1168+
break;
1169+
case lldb::eStructuredDataTypeString: {
1170+
// Get the string size before reading
1171+
const size_t str_length = value.GetStringValue(nullptr, 0);
1172+
std::string str(str_length + 1, 0);
1173+
value.GetStringValue(&str[0], str_length);
1174+
EmplaceSafeString(out, key, str);
1175+
} break;
1176+
case lldb::eStructuredDataTypeDictionary: {
1177+
lldb::SBStream contents;
1178+
value.GetAsJSON(contents);
1179+
EmplaceSafeString(out, key, contents.GetData());
1180+
} break;
1181+
case lldb::eStructuredDataTypeNull:
1182+
case lldb::eStructuredDataTypeGeneric:
1183+
case lldb::eStructuredDataTypeInvalid:
1184+
break;
1185+
}
1186+
}
1187+
1188+
void addStatistic(llvm::json::Object &event) {
1189+
lldb::SBStructuredData statistics = g_vsc.target.GetStatistics();
1190+
bool is_dictionary =
1191+
statistics.GetType() == lldb::eStructuredDataTypeDictionary;
1192+
if (!is_dictionary)
1193+
return;
1194+
llvm::json::Object stats_body;
1195+
1196+
lldb::SBStringList keys;
1197+
if (!statistics.GetKeys(keys))
1198+
return;
1199+
for (size_t i = 0; i < keys.GetSize(); i++) {
1200+
const char *key = keys.GetStringAtIndex(i);
1201+
FilterAndGetValueForKey(statistics, key, stats_body);
1202+
}
1203+
event.try_emplace("statistics", std::move(stats_body));
1204+
}
1205+
1206+
llvm::json::Object CreateTerminatedEventObject() {
1207+
llvm::json::Object event(CreateEventObject("terminated"));
1208+
addStatistic(event);
1209+
return event;
1210+
}
1211+
11431212
std::string JSONToString(const llvm::json::Value &json) {
11441213
std::string data;
11451214
llvm::raw_string_ostream os(data);

lldb/tools/lldb-vscode/JSONUtils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,12 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
485485
llvm::StringRef debug_adaptor_path,
486486
llvm::StringRef comm_file);
487487

488+
/// Create a "Terminated" JSON object that contains statistics
489+
///
490+
/// \return
491+
/// A body JSON object with debug info and breakpoint info
492+
llvm::json::Object CreateTerminatedEventObject();
493+
488494
/// Convert a given JSON object to a string.
489495
std::string JSONToString(const llvm::json::Value &json);
490496

lldb/tools/lldb-vscode/lldb-vscode.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ void SendTerminatedEvent() {
204204
g_vsc.sent_terminated_event = true;
205205
g_vsc.RunTerminateCommands();
206206
// Send a "terminated" event
207-
llvm::json::Object event(CreateEventObject("terminated"));
207+
llvm::json::Object event(CreateTerminatedEventObject());
208208
g_vsc.SendJSON(llvm::json::Value(std::move(event)));
209209
}
210210
}
@@ -2949,7 +2949,7 @@ void request_variables(const llvm::json::Object &request) {
29492949
const uint32_t addr_size = g_vsc.target.GetProcess().GetAddressByteSize();
29502950
lldb::SBValue reg_set = g_vsc.variables.registers.GetValueAtIndex(0);
29512951
const uint32_t num_regs = reg_set.GetNumChildren();
2952-
for (uint32_t reg_idx=0; reg_idx<num_regs; ++reg_idx) {
2952+
for (uint32_t reg_idx = 0; reg_idx < num_regs; ++reg_idx) {
29532953
lldb::SBValue reg = reg_set.GetChildAtIndex(reg_idx);
29542954
const lldb::Format format = reg.GetFormat();
29552955
if (format == lldb::eFormatDefault || format == lldb::eFormatHex) {

0 commit comments

Comments
 (0)