This is an archive of the discontinued LLVM Phabricator instance.

[lldb-vscode] Add data breakpoint support
Needs ReviewPublic

Authored by cimacmillan on Dec 23 2022, 7:48 AM.

Details

Summary

Added support for setting data breakpoints to the LLDB vscode adapter. This is part of a workstream to improve the debugging support of the WebAssembly-Micro-Runtime, which uses lldb-vscode as it's debug adaptor: https://github.com/bytecodealliance/wasm-micro-runtime/issues/1810

Added an intergration test that runs two simple scenarios.

Diff Detail

Event Timeline

cimacmillan created this revision.Dec 23 2022, 7:48 AM
Herald added a project: Restricted Project. · View Herald TranscriptDec 23 2022, 7:48 AM
cimacmillan requested review of this revision.Dec 23 2022, 7:48 AM
Herald added a project: Restricted Project. · View Herald TranscriptDec 23 2022, 7:48 AM
cimacmillan edited the summary of this revision. (Show Details)Dec 23 2022, 7:52 AM
This comment was removed by cimacmillan.
This comment was removed by cimacmillan.
clayborg requested changes to this revision.Jan 11 2023, 2:57 PM
clayborg added inline comments.
lldb/test/API/tools/lldb-vscode/breakpoint_data/TestVSCode_setDataBreakpoints.py
75–82

This might fail on some systems if they put the value for the local variables "num_a" or "num_b" in a register. So you might need to modify this test to make sure it will run on any systems. The other thing you can do is to take the address of any local variable to ensure it is kept in memory, so adding "int *num_a_ptr = &num_a;" and "int *num_b_ptr = &num_b;" might help ensure this test always works.

96

I would suggest adding a test for a constant global value that appears in a section (like .rodata) that has no write permissions and verify that we correctly figure out that we can only do "read" access on such a variable (see code changes I suggest where we check the memory region).

We also need to test error conditions out when a variable is not in memory. If you compile some C code with optimizations, you can try to create a variable that is in a register with "int num_a = 1;" and in the test grab the SBValue for this local variable and test if the value has an invalid load address

lldb/tools/lldb-vscode/Watchpoint.cpp
35–37

no braces on single line if statements per llvm coding guidelines.

39–45

We should be using the "SBValue::Watch(...)" instead of SBTarget.WatchAddress(...). If you create a watchpoint for a local variable that is on the stack, this will cause all sorts of problems if the variable goes out of scope. SBValue::Watch(...) has the smarts to be able to figure out if a variable is local, and I seem to remember that it will disable this watchpoint as soon as a local variable goes out of scope, though I might not be remember things correctly. It can also figure out if a variable is currently in memory or not and it won't set a watchpoint if possible.

I might recommend that this Watchpoint class gets created with a SBValue, and then we use that for setting the watchpoint, the we set the watchpoint using:

lldb::SBWatchpoint lldb::SBValue::Watch(bool resolve_location, bool read, bool write, SBError &error);
lldb/tools/lldb-vscode/lldb-vscode.cpp
2431

Some variables don't have load addresses. This can happen when a variable is in a register. If a variable is in a register, then this function will return LLDB_INVALID_ADDRESS, and if that happens you will need to return something appropriate, like maybe returning "null" for the "dataId" in the response and then filling in an appropriate message in the "description" as it is documented above as "UI string that describes on what data the breakpoint is set on or why a data breakpoint is not available".

2441–2444

if "v_address != LLDB_INVALID_ADDRESS", then you can ask for the memory region for this address using:

lldb::SBError lldb::SBProcess::GetMemoryRegionInfo(lldb::addr_t load_addr, lldb::SBMemoryRegionInfo &region_info);

If you get an error back, then this memory address is unreadable and you should return an error. If no error is returned, then you should check if the memory region has read or write permissions with:

llvm::json::Array access_types;
lldb::SBMemoryRegionInfo region_info;
lldb::SBError error = g_vsc.target.GetProcess().GetMemoryRegionInfo(v_address, region_info);
if (error.Success()) {
  if (region_info.IsReadable()) {
    access_types.emplace_back("read");
    if (region_info.IsWritable())
      access_types.emplace_back("readWrite");
  }
  if (region_info. IsWritable())
    access_types.emplace_back("write");
}
This revision now requires changes to proceed.Jan 11 2023, 2:57 PM

@clayborg Thanks for your feedback. I'm part the way through implementing your changes. Specifically about this point:

I seem to remember that it will disable this watchpoint as soon as a local variable goes out of scope, though I might not be remember things correctly

This is a behaviour I'd like to address, where for instance watchpoints are triggered in different functions because the stack frame addresses align. I have this example, I can add a test for:

int test_a() {
    int x = 10; <- watchpoint set here
    x = 20; <- watchpoint triggered here
}

int test_b() {
    int b = 10; <- watchpoint triggered here also
    b = 20;
}

int main() {
    test_a();
    test_b();

I've refactored the Watchpoint class to use "SBValue::Watch", and I can still reproduce this behaviour. I also can't find where this watchpoint disable on scope change might be implemented. Do you have any suggestions for this? Thanks

@clayborg Thanks for your feedback. I'm part the way through implementing your changes. Specifically about this point:

I seem to remember that it will disable this watchpoint as soon as a local variable goes out of scope, though I might not be remember things correctly

This is a behaviour I'd like to address, where for instance watchpoints are triggered in different functions because the stack frame addresses align. I have this example, I can add a test for:

int test_a() {
    int x = 10; <- watchpoint set here
    x = 20; <- watchpoint triggered here
}

int test_b() {
    int b = 10; <- watchpoint triggered here also
    b = 20;
}

int main() {
    test_a();
    test_b();

I've refactored the Watchpoint class to use "SBValue::Watch", and I can still reproduce this behaviour. I also can't find where this watchpoint disable on scope change might be implemented. Do you have any suggestions for this? Thanks

Upon further examination it doesn't seem like this is happening, though it should be possible to add some things to the watchpoint classes to make this happen. This is beyond the scope of this fix though, so no need to do it unless you have time. What we would need to do is store a StackID (see "lldb/Target/StackID.h") and thread ID with the watchpoint. When the watchpoint triggers, we would need to check if the StackID + thread ID is valid (we should store this info with the watchpoint when setting it from a SBValue::Watch(...), or not if we just watch the address) still exists in the thread's stack somewhere. If it does still exist then stop and report, else clear the watchpoint and don't report anything. If the stack ID isn't valid then we always stop.

cimacmillan edited the summary of this revision. (Show Details)

Add handling for const and register cases. Setting watchpoint on SBValue rather than the
address.

clayborg requested changes to this revision.Jan 19 2023, 3:30 PM
clayborg added inline comments.
lldb/test/API/tools/lldb-vscode/breakpoint_data/TestVSCode_setDataBreakpoints.py
74

might be better as suggested?

lldb/tools/lldb-vscode/Watchpoint.cpp
16

We should either rename the argument variables so they don't match the member variable names or if we add the "m_" prefix this all just will work.

17

remove "this->"

21–22
28

Remove all "this->" from this and all changes in this patch.

37–42

If we use two bools instead of using WatchPointType enum, this is much easier to code.

47–49

We should probably just leave the error inside of this object and then test if the watchpoint was set correctly and report it back in the response to "setDataBreakpoints" for each breakpoint instead of printing to console?

lldb/tools/lldb-vscode/Watchpoint.h
21

We could get rid of this enum and just store two bools and avoid the ReadWrite case. See comment below where WatchpointType is used for member variable

30–35

For classes we prefer things to have a "m_" prefix for all member variables. I know other parts of this code for breakpoints use "struct" instead. It helps keep code readable and avoids having variables and member variables with the same name. See next inline comment.

32

Maybe just use two bools and get rid of WatchPointType enum since we really want a bitfield for read and write?

lldb/tools/lldb-vscode/lldb-vscode.cpp
2125–2138

We shouldn't be logging to the debug console if the setDataBreakpoint packet is not valid, but we should return an error for that packet. Might be better to have this return a bool to indicate success or failure and have the prototype be:

static bool get_watchpoint_type(const std::string &type, bool &read, bool &write);

If this caller gets false returned from this, then we return an error to the packet with an appropriate error message.

2188

This function should probably be able to return an error for each watchpoint if anything fails so this can be reported back as a description or error response in the "setDataBreakpoints" response? We shouldn't be printing to the console for errors in the "breakpoint" object arguments or if the watchpoint actually fails to resolve (was in a register). An error message should be returned somehow if this is possible?

2192–2193

We should return an error for this breakpoint if the watchpoint type is not supported. Is there a way to return an error for a specific data breakpoint instead of us printing to the console?

2317–2318

You have to return a valid "Breakpoint" object for each item in "breakpoints". If we fail, it looks like you can put any error into the "Breakpoint.message" field and set "Breakpoint.verified = false".

This revision now requires changes to proceed.Jan 19 2023, 3:30 PM
cimacmillan marked 6 inline comments as done.Feb 1 2023, 3:46 AM
cimacmillan added inline comments.
lldb/tools/lldb-vscode/lldb-vscode.cpp
2188

Thanks for your feedback, working through comments now. About this and

Is there a way to return an error for a specific data breakpoint instead of us printing to the console?

We can return whether the breakpoint is verified, which displays a greyed dot similar to when a line breakpoint can't be set. There doesn't seem to be a way to display the error in VS code with the message. I think the logging would be useful in that case, but I can also include it in the message response in case this is added to VS code.

Refactored Watchpoint class and addressed comments

cimacmillan marked 12 inline comments as done.Feb 1 2023, 7:13 AM

@clayborg Thanks for your feedback. I've refactored the Watchpoint class slightly so that it encapsulates the parsing of the setDataBreakpoints request and be used to get the Breakpoint info to attach to the response. This is more similar to how the other breakpoint classes are written, and encapsulates things a bit better.

lldb/test/API/tools/lldb-vscode/breakpoint_data/TestVSCode_setDataBreakpoints.py
74

There isn't an IsValid function in this case as it's not the SBValue type. I suppose is the variable was invalid, the rest of the test would fail.

Sorry for delay, I have been on vacation for the last two weeks. I will be back next Monday and get to this soon. Feel free to ping again next week if I don't get to it!

Ping.

Greg's been out since late March, and isn't expected back for a while still. I am entirely unfamiliar with the lldb-vscode part of lldb, so I don't feel comfortable reviewing this.

jingham resigned from this revision.May 9 2023, 4:55 PM
wallace accepted this revision.May 26 2023, 8:15 AM

This seems reasonable to me and I'm accepting it to unblock this feature. When Greg gets back he should be able to provide some additional comments.

Ping

@clayborg would you mind having another look? Thanks

Greg hasn't been working for a while. You can go ahead and land this, and when he gets back he can share some feedback.

Sorry this fell off my radar. I was on medical leave for a surgery for 3 months. Back now

lldb/tools/lldb-vscode/Watchpoint.cpp
72
90
lldb/tools/lldb-vscode/Watchpoint.h
1

Fix width of this comment to match line 7

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

Any line over 80 chars should be formatted to not exceed. Best to try and. use clang-format

jgorbe added a subscriber: jgorbe.Sep 12 2023, 1:27 PM
cmtice added a subscriber: cmtice.Sep 13 2023, 9:45 AM