Skip to content

Commit

Permalink
[lldb-vscode] Send Statistics Dump in terminated event
Browse files Browse the repository at this point in the history
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
  • Loading branch information
kusmour committed Nov 4, 2022
1 parent 2bbafe0 commit e3ccbae
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 3 deletions.
Expand Up @@ -369,7 +369,13 @@ def wait_for_stopped(self, timeout=None):
def wait_for_exited(self):
event_dict = self.wait_for_event('exited')
if event_dict is None:
raise ValueError("didn't get stopped event")
raise ValueError("didn't get exited event")
return event_dict

def wait_for_terminated(self):
event_dict = self.wait_for_event('terminated')
if event_dict is None:
raise ValueError("didn't get terminated event")
return event_dict

def get_initialize_value(self, key):
Expand Down
17 changes: 17 additions & 0 deletions lldb/test/API/tools/lldb-vscode/terminated-event/Makefile
@@ -0,0 +1,17 @@
DYLIB_NAME := foo
DYLIB_CXX_SOURCES := foo.cpp
CXX_SOURCES := main.cpp

LD_EXTRAS := -Wl,-rpath "-Wl,$(shell pwd)"
USE_LIBDL :=1

include Makefile.rules

all: a.out.stripped

a.out.stripped:
strip -o a.out.stripped a.out

ifneq "$(CODESIGN)" ""
$(CODESIGN) -fs - a.out.stripped
endif
@@ -0,0 +1,63 @@
"""
Test lldb-vscode terminated event
"""

import vscode
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
import lldbvscode_testcase
import re
import json

class TestVSCode_terminatedEvent(lldbvscode_testcase.VSCodeTestCaseBase):

@skipIfWindows
@skipIfRemote
def test_terminated_event(self):
'''
Terminated Event
Now contains the statistics of a debug session:
metatdata:
totalDebugInfoByteSize > 0
totalDebugInfoEnabled > 0
totalModuleCountHasDebugInfo > 0
...
targetInfo:
totalBreakpointResolveTime > 0
breakpoints:
recognize function breakpoint
recognize source line breakpoint
It should contains the breakpoints info: function bp & source line bp
'''

program_basename = "a.out.stripped"
program = self.getBuildArtifact(program_basename)
self.build_and_launch(program)
# Set breakpoints
functions = ['foo']
breakpoint_ids = self.set_function_breakpoints(functions)
self.assertEquals(len(breakpoint_ids), len(functions), 'expect one breakpoint')
main_bp_line = line_number('main.cpp', '// main breakpoint 1')
breakpoint_ids.append(self.set_source_breakpoints('main.cpp', [main_bp_line]))

self.continue_to_breakpoints(breakpoint_ids)
self.continue_to_exit()

statistics = self.vscode.wait_for_terminated()['statistics']
self.assertTrue(statistics['totalDebugInfoByteSize'] > 0)
self.assertTrue(statistics['totalDebugInfoEnabled'] > 0)
self.assertTrue(statistics['totalModuleCountHasDebugInfo'] > 0)

self.assertIsNotNone(statistics['memory'])

# lldb-vscode debugs one target at a time
target = json.loads(statistics['targets'])[0]
self.assertTrue(target['totalBreakpointResolveTime'] > 0)

breakpoints = target['breakpoints']
self.assertIn('foo',
breakpoints[0]['details']['Breakpoint']['BKPTResolver']['Options']['SymbolNames'],
'foo is a symbol breakpoint')
self.assertTrue(breakpoints[1]['details']['Breakpoint']['BKPTResolver']['Options']['FileName'].endswith('main.cpp'),
'target has source line breakpoint in main.cpp')
3 changes: 3 additions & 0 deletions lldb/test/API/tools/lldb-vscode/terminated-event/foo.cpp
@@ -0,0 +1,3 @@
int foo() {
return 12;
}
1 change: 1 addition & 0 deletions lldb/test/API/tools/lldb-vscode/terminated-event/foo.h
@@ -0,0 +1 @@
int foo();
8 changes: 8 additions & 0 deletions lldb/test/API/tools/lldb-vscode/terminated-event/main.cpp
@@ -0,0 +1,8 @@
#include <iostream>
#include "foo.h"

int main(int argc, char const *argv[]) {
std::cout << "Hello World!" << std::endl; // main breakpoint 1
foo();
return 0;
}
69 changes: 69 additions & 0 deletions lldb/tools/lldb-vscode/JSONUtils.cpp
Expand Up @@ -19,6 +19,8 @@
#include "lldb/API/SBBreakpoint.h"
#include "lldb/API/SBBreakpointLocation.h"
#include "lldb/API/SBDeclaration.h"
#include "lldb/API/SBStringList.h"
#include "lldb/API/SBStructuredData.h"
#include "lldb/API/SBValue.h"
#include "lldb/Host/PosixApi.h"

Expand Down Expand Up @@ -1140,6 +1142,73 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
return reverse_request;
}

// Keep all the top level items from the statistics dump, except for the
// "modules" array. It can be huge and cause delay
// Array and dictionary value will return as <key, JSON string> pairs
void FilterAndGetValueForKey(const lldb::SBStructuredData data, const char *key,
llvm::json::Object &out) {
lldb::SBStructuredData value = data.GetValueForKey(key);
std::string key_utf8 = llvm::json::fixUTF8(key);
if (strcmp(key, "modules") == 0)
return;
switch (value.GetType()) {
case lldb::eStructuredDataTypeFloat:
out.try_emplace(key_utf8, value.GetFloatValue());
break;
case lldb::eStructuredDataTypeInteger:
out.try_emplace(key_utf8, value.GetIntegerValue());
break;
case lldb::eStructuredDataTypeArray: {
lldb::SBStream contents;
value.GetAsJSON(contents);
EmplaceSafeString(out, key, contents.GetData());
} break;
case lldb::eStructuredDataTypeBoolean:
out.try_emplace(key_utf8, value.GetBooleanValue());
break;
case lldb::eStructuredDataTypeString: {
// Get the string size before reading
const size_t str_length = value.GetStringValue(nullptr, 0);
std::string str(str_length + 1, 0);
value.GetStringValue(&str[0], str_length);
EmplaceSafeString(out, key, str);
} break;
case lldb::eStructuredDataTypeDictionary: {
lldb::SBStream contents;
value.GetAsJSON(contents);
EmplaceSafeString(out, key, contents.GetData());
} break;
case lldb::eStructuredDataTypeNull:
case lldb::eStructuredDataTypeGeneric:
case lldb::eStructuredDataTypeInvalid:
break;
}
}

void addStatistic(llvm::json::Object &event) {
lldb::SBStructuredData statistics = g_vsc.target.GetStatistics();
bool is_dictionary =
statistics.GetType() == lldb::eStructuredDataTypeDictionary;
if (!is_dictionary)
return;
llvm::json::Object stats_body;

lldb::SBStringList keys;
if (!statistics.GetKeys(keys))
return;
for (size_t i = 0; i < keys.GetSize(); i++) {
const char *key = keys.GetStringAtIndex(i);
FilterAndGetValueForKey(statistics, key, stats_body);
}
event.try_emplace("statistics", std::move(stats_body));
}

llvm::json::Object CreateTerminatedEventObject() {
llvm::json::Object event(CreateEventObject("terminated"));
addStatistic(event);
return event;
}

std::string JSONToString(const llvm::json::Value &json) {
std::string data;
llvm::raw_string_ostream os(data);
Expand Down
6 changes: 6 additions & 0 deletions lldb/tools/lldb-vscode/JSONUtils.h
Expand Up @@ -485,6 +485,12 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
llvm::StringRef debug_adaptor_path,
llvm::StringRef comm_file);

/// Create a "Terminated" JSON object that contains statistics
///
/// \return
/// A body JSON object with debug info and breakpoint info
llvm::json::Object CreateTerminatedEventObject();

/// Convert a given JSON object to a string.
std::string JSONToString(const llvm::json::Value &json);

Expand Down
4 changes: 2 additions & 2 deletions lldb/tools/lldb-vscode/lldb-vscode.cpp
Expand Up @@ -204,7 +204,7 @@ void SendTerminatedEvent() {
g_vsc.sent_terminated_event = true;
g_vsc.RunTerminateCommands();
// Send a "terminated" event
llvm::json::Object event(CreateEventObject("terminated"));
llvm::json::Object event(CreateTerminatedEventObject());
g_vsc.SendJSON(llvm::json::Value(std::move(event)));
}
}
Expand Down Expand Up @@ -2949,7 +2949,7 @@ void request_variables(const llvm::json::Object &request) {
const uint32_t addr_size = g_vsc.target.GetProcess().GetAddressByteSize();
lldb::SBValue reg_set = g_vsc.variables.registers.GetValueAtIndex(0);
const uint32_t num_regs = reg_set.GetNumChildren();
for (uint32_t reg_idx=0; reg_idx<num_regs; ++reg_idx) {
for (uint32_t reg_idx = 0; reg_idx < num_regs; ++reg_idx) {
lldb::SBValue reg = reg_set.GetChildAtIndex(reg_idx);
const lldb::Format format = reg.GetFormat();
if (format == lldb::eFormatDefault || format == lldb::eFormatHex) {
Expand Down

0 comments on commit e3ccbae

Please sign in to comment.