Submind YouTube summaries
Thumbnail for Deterministic debugging with Delve And the state of Delve

Deterministic debugging with Delve And the state of Delve

Watch on YouTube

Video summary

The video features Derek Parker, an original author of Delve at Red Hat, discussing the current state of the debugger and introducing the concept of deterministic debugging. He notes that while traditional debugging involves running a live process where variables like memory layout and thread scheduling can change between runs, this often makes it difficult to reproduce rare bugs. To address this, Parker explains that deterministic debugging aims to create an identical execution environment every time by recording non-deterministic events during the first run and replaying them exactly in subsequent sessions. This approach allows developers to isolate issues that might only appear once out of a hundred runs without needing to wait for random conditions to align again. Parker provides an update on Delve's development over the past year, highlighting significant expansions such as support for ARM64 architecture, FreeBSD operating systems, and Go plugins. He also details new scripting capabilities using StarLark language, which allows users to define custom commands directly within debugging sessions. Other improvements include better handling of optimized production binaries, enhanced function call support, and fixes related to position-independent executables. Despite these advancements, Parker emphasizes that the project remains focused on core stability while encouraging community contributions to further its growth as a six-year-old tool with over 175 commits in just one year. The core mechanism behind deterministic debugging relies heavily on Mozilla's rr backend rather than virtual machine recording or code instrumentation methods, which are often too heavy or require kernel extensions that reduce portability. The system works by trapping and recording specific non-deterministic sources such as random number generation from `/dev/random`, CPU timestamps, thread scheduling decisions, and shared memory operations. During replay mode, Delve runs the program in a single-threaded environment to ensure consistency, effectively neutralizing factors like signal delivery timing and variable addresses so that every execution follows the exact same path leading up to the bug. In his live demonstration, Parker shows how setting breakpoints on random number generation results in identical values being printed during replay, proving that even sources of randomness are faithfully reproduced from a recorded trace. He outlines three methods for integrating this into Delve workflows: manually building and tracing binaries with optimizations disabled, using a simplified `delve debug` command to automate the process, or employing a custom script called "flaky" that automatically reruns the program repeatedly until it crashes so the bug can be captured instantly. Parker concludes by emphasizing that while there are performance trade-offs for highly parallel programs due to single-threaded execution, this technique offers powerful capabilities like reverse debugging and local replay of traces recorded on remote servers, making rare bugs much easier to diagnose in daily development workflows.
Read the full video transcript
all right hello everybody um uh yes thank you very much uh my name is Derek Parker um I am once again going to be talking about delve this is true um uh so this time the the predominant theme of this talk will be deterministic debugging with delve uh this is a talk that I'm particularly excited about um as I think that this this style of debugging is um not only not very well known but as a result of that underutilized um I think a lot of people think of debugging in the traditional sense of you're debugging a live process um a lot of different things can happen a lot of things can happen between debugging sessions and between runs of the process and things like that so uh how do we how do we eliminate a lot of that and especially uh if you have a bug that say only uh only exposes itself maybe one out of a 100 runs how do you sanely debug that that's the kind of things that I'm going to be talking about during this talk um just to introduce myself a little bit more again my name is Derek Parker I am a software engineer at Red Hat um I work on go tooling Upstream um and I work on delve as well and some of the go packages that we ship through uh real um I uh am the original author of delve um and have been working on that for a long time um among among a few other things okay so I want to give kind of a run down of what we're going to go through in this talk so as mentioned um I've I've started to steal the uh the idea for the state of go and um I've spoken here uh for a few years so I like to kind of give an update on the state of delve what's happened uh what new features and what things have changed since last year um because in a year a lot of things can change a lot of new features uh can be can be completed um um and I think it it gives kind of an idea of the pace of the project and the types of things that we're working on and focused on also allows a little bit bit of feedback from from just users in general everybody here about maybe what they would like to see in the future uh after that I'll go into what deterministic debugging is and explain uh how it works first of all just explain the concept for for those who may not be familiar with it and then I'll dig into a little bit of how it works under the hood so actually um how how you can get rid of all of these deterministic or un non-deterministic things that happen in every process run um and have a a repeatable uh debugging session so how that actually works from a relatively low level following that I'll talk about how delve leverages this to uh be able to debug go program uh and then finally I'll do a live demo just kind of showing off uh showing off the concept A little bit showing it in action and giving a sense of how just how easy it is to do this yourself in your day-to-day uh debugging and and work and programming workflow so that hopefully this is something that you'll reach for or at least know to reach for if you come into a problem where uh this style of debugging might come in handy okay so first of all let's just jump right into the state of delve let's talk about what's what's happened in the last year um some of the new features some of the new changes and then we'll briefly talk about some things that we're that we're going to work on going forward um so the project is now 6 years old uh which is crazy to me um it's uh you know they grw up so fast right um uh I'm I'm very excited to to have been working on it and um that uh so many people have found it to be useful it's is very exciting um we've had 175 commits over the last year uh not a ton but for a more esoteric project as a debugger um that's that's really really good and and we've had a ton of new contributors um which is great we love first-time contributors and try to encourage that as much as possible um and folks building in some really huge features which I'll I'll talk about next so what's uh what's changed since last fom well there's been a couple go releases right so we've we've released uh uh some new versions of delve that support that so specifically 113 and 114 which which will be released uh another big big big change that's happened uh over the last year is arm 64 support so uh before this we only really supported x86 64 um we've just gotten a a a great patch in recently to add uh kind of initial arm 64 support there's still some more work to be done there but that's that's still a huge uh a huge addition to the project we obviously aim to support every architecture and operating system that go supports natively uh but you know it just takes some time so contribu are always welcome uh another big thing is free BSD support again we want to uh we want to include as many operating systems and and everything as we possibly can so that was another big big addition um we already support you know various linuxes um windows and OSX uh so this just adds one more operating system to the repertoire um we've added uh the ability to script delve via a language called star which was um originally uh created for the build system basil um it's Bas it's a pythonesque dialect uh and that adds a lot of powerful scripting features uh uh into delve so you can you can Define new commands within delve and other things uh just by writing this this starlark language um and those those commands will be available to you in your debug session uh We've also added support for debugging go plugins um So for anybody using that feature that's Now supported um Windows mini dump support which is basically Windows core dumps um and improved support for debugging pies uh position independent executables uh some other notable just changes some internal refactoring um just ongoing code maintenance cleanup I think that's an important thing um several performance improvements uh es especially on um breakpoint handling and a lot of other things um improved handling of optimized binaries I think this is an important one and this took a lot of coordination with the go team uh you if you have a if you have a binary that's actually running in production or something like that or you build it just just normally with go build it's going to be optimized so we want to be able to to debug those ideally as well as you can debug uh uh binaries that you've built with optimizations explicitly turned off um we've improved function call support and just in general uh uh We've we've included a bunch of other improvements and Bug fixes okay so now let's go on to part two the uh the real the real kind of me and subject of this talk so first off um I'm up here speaking about it but I would like to thank my co-maintainer who happens to be here uh for doing all of the hard work of actually Plumbing up delve to the uh the record replay back end that we used to to achieve this um he did a lot of the hard work I just reviewed it and now I'm up here bragging about it so I just wanted to give credit where credits to and say thank you okay now let's talk a little bit about uh what is determinate stick de plugging um and let's also talk about what's that weird little character down at the at the bottom of the screen uh so um my my son is four and a half years old watches a lot of cartoons this is a new Toy Story cartoon who starts off a lot of his sentences asking what is something so I thought it would be funny to include him in the slide saying what is deterministic debugging it's more for me than it is for you uh um anyway so just to kind of gauge the room a little bit how many people have heard of deterministic debugging very few that is perfect that is that is actually exactly what I wanted to see and it kind of proves my point that I think this is an an a relatively unknown and very underutilized uh way to debug your programs so I want to talk a little bit uh about what it is just to give some context and then as I as I mentioned I'll I'll go into uh like a pretty deep dive of how it works and and the the different kinds of things that that is done to uh to actually achieve this so um one more one more quick question how many people have seen the movie Groundhog Day a good amount of people okay so for those who haven't seen it um it's a movie where Bill Murray goes to This Town patani Pennsylvania where uh every year actually today on February 2nd it it turns out um a groundhog comes out of the out of the ground and if it sees its shadow or doesn't it has some sort of mystical bearing on on how long the the winner is going to last but the the connection here is that every day uh in this movie that he wakes up it's Groundhog Day over and over over and over again he's repeating the same day over and over and over again every single day every time he wakes up for a really long time um and the the no matter what he does no matter what he changes in in his environment when he starts the day over again the next day he goes through the same exact series of events the same exact people talk to him and say the same exact things over and over and over again and this happens forever well at least for a really long time um and and the connection here is that that's essentially what deterministic debugging is that's what it's trying to achieve so the point of it is that every single time you you start this session debugging what we're going to get to know as a recording everything is exactly the same um and this includes memory layout uh signal delivery uh you know a lot of a lot of the different variables that may change and may slightly change how a bug is is reproduced are always is exactly the same so as I mentioned before if this if your bug only shows up maybe once uh once in 100 runs if you capture it just that one time you can replay it indefinitely to try to figure out where the bug is so how is this possible uh as we know every time you run a program there's a lot of things that are different uh uh memory layout uh threads um uh where where variables are stored in memory what happens as a result of of certain CIS calls when signals are delivered Etc there's a lot of things that are different how can we eliminate all of this non-determinism and Faithfully reproduce the execution of a process and as a result Faithfully reproduce the events leading up to the bug um that you're trying to fix so the solution turns out to be record and repl uh and what this what this actually means is we we essentially record the execution of a process um record all uh the different types of the result of any kind of non-deterministic operation and are able to replay that back so anytime one of those operations is hit again instead of actually going through and doing the operation or making the actual assist call or anything like that um the the pre-recorded output is just replayed back to the program now this turns out to be pretty difficult but also very very powerful um now there's a lot of different types to do this some are are kind of better than others I'll I'll go through a little bit of of of uh existing implementations so there's VM recording which isn't actually very widely supported um VMware actually even dropped it from their uh their virtual machine offering um there's there's a couple things but they're really clunky really heavyweight and really typically Al you only want to record a process not the entire execution of the kernel and operating system and everything else so it tends to be bloated and not not very useful uh there's also some user space recording um undo DB uh replay engine and mazilla RR we'll talk about RR uh a lot more in the end of this talk there's pros and cons to each so some of them do code instrumentation which I personally am not a huge fan of um uh I think leaving leaving the code AS unchanged as possible possible is kind of in the spirit of debugging you're not you're not trying to change a person's code you're just trying to show them what happened so that they can find the issue so let's go through some of the pros and cons real quick of of some of the existing implementations as I mentioned with VM recording uh you have to debug within within a virtual machine that supports it that adds a lot of bloat um and not a lot of virtual machines actually support this uh so it's it's really not portable and doesn't doesn't t to work very well with your workflow um especially if you're not using a virtual machine or you're on bare metal or just kind of writing code on your laptop you're probably not running it within a virtual machine um it produces large traces a lot of bloat um it's not widely supported as I mentioned um and so let's let's move on to user space recording uh it doesn't some problems with that um it it doesn't record kernel execution uh we'll I'll explain later why this actually turns out to not be that big of a deal as I mentioned some require code instrumentation the the the backend that we use um does not uh so um we get rid of that and some require some require kernel extensions which again uh destroys portability so there's a lot of cons sounds terrible why am I even talking about it what's the solution uh so what we use is Milla RR and I I'll talk about how how it works a little bit um but it it solves a lot of those problems by not doing not doing any kind of code instrumentation it's it's pretty lightweight and and relatively performant there are some drawbacks that I'll discuss a little bit uh especially with um with certain go programs but it's still I think the best solution that we have available right now and it's a start right I I think as as kind of shown by the polling in the room not a lot of people know about or are using this technology so I think as awareness grows more people can work on it improve it and we'll get better Solutions in the future but right now uh uh RR is is um is the preferred solution so eliminating sources of non-determinism how can we do that and what are some of the sources of non-determinism uh so here's a couple that that we can control right uh certain CPU instructions so most CPU instructions are deterministic you feed them you know the the input or whatever they do an operation they put that the results of that operation in memory or register or whatever and uh it's going to be the same every single time you call it but there are some that that aren't so like Rd Rand a uh instruction to Generate random numbers hopefully that's not deterministic um so uh the the way to get around this the way that RR does it is it actually it turns out it's not used very much that actual instruction most people prefer Dev random so it can just be patched out of anything that uses it uh there's also like time stamp counter uh instructions um those can actually be trapped and recorded um and CPU ID that's mostly deterministic if you're running in the same the same Hardware uh but it does return which uh which core the process is running on that can be different each time so that's also trapped and recorded um on newer kernels and Intel CPUs uh thread scheduling so this is important especially for go uh so um what what RR does and and one of the the kind of performance hits that it takes is um it runs everything single threaded so it handles thread scheduling um it does do preemption and things like that for Via signals and everything but it runs your program single-threaded so for large go programs and programs that are are really really highly parallel this could incur some slowdown um but unfortunately that's that's a penalty in in a price that we have to pay uh for right now um so system calls uh the result of a system call could potentially be different uh we can we can trap uh the um whenever whenever a system call is executed and in and record inputs and outputs so we can just replay that back after uh when we're when we're replaying the the uh recording memory layout uh again uh we want to ensure that memories are that variables are at the same address in memory cuz potentially that leads to a certain bug or exhibits certain symptoms so we want to again we want to recreate the process as Faithfully as we can as it was executing exactly when it was first run um shared memory um this is a big a big one actually for non-determinism um uh but for for RR again it runs single-threaded so you don't have to worry about different threads or whatever within your process messing with shared memory um there there are some things where like the X server and pulse audio and things like that communicate back and forth using shared memory but we there's ways of disabling that signal handling um so RR is also able to handle this um we can we can uh replay signal execution uh and delivery um by using uh Hardware performance counters so basically we can count and know where we are in the program and what's happening when that's when we're at that specific spot so when a signal signal is delivered at a certain point in the execution of the process we can record when that happens and replay it um so some sources that we that we can't control just really quick uh we don't control anything that actually happens outside of user space but as I mentioned it turns out we don't really need to because we just we just record the result of that anyways um so Hardware failures I mean if that's happening you're not going to have a lot of luck anyways um so I'm kind of getting a little bit low on time so I'll kind of go through some of this pretty quickly to show a bit of a demo um so some pros and cons pros relatively low overhead um except for single threaded uh you can Faithfully reproduce bugs every single time um you can record a trace and replay it anywhere so you can record a trace on your server and replay it locally um for easier debugging uh can execute programs in Reverse so you can go to the where a bug exists itself and kind of work backwards instead of trying to work forwards towards the bug and replaying that every time which I think turns out to be pretty powerful um you can replay from the beginning execute the same sequence of events it's always recorded always the same um cons there's a performance hit for highly parallel programs uh it only works on Linux and uh only works with um uh performance counters uh enabled uh so let's talk about how to use it with delve first use Linux uh right now it's only supported on Linux um unfortunately OSX is not supported um install install RR mazil RR um that's not installed for you automatically when you install delve and there's three options for using the RR backend with delve which I'll talk about quickly now and then I'll I'll save a little bit of time still for a demo so option one is a little bit more manual um so the the first thing that you're going to want to do is build your program again ideally you're building it with optimizations disabled that's what those flags are uh you use RR and P pass it the path to your binary it's going to record it and save a trace somewhere and then you can use delve to replay that Trace um once it once it's completed uh option two uh is a little is a lot easier there's one command that just kind of does this whole process for you so you can use the delve debug uh subc command pass it the backend RR and then uh it'll compile the program run the trace record it everything and then open it up so that you can start debugging um and then last you can use the scripting feature that I mentioned earlier so um this this is actually included in the documentation on our GitHub so you don't have to memorize this command but essentially what it does is uh It intro introduces a command called flaky where it'll rerun the process over and over and over and over and over again until it uh until it hits a bug so it it exits um you know with uh with a bad error code um and then uh once it once it does that it'll stop running the process over and over again and you'll be debugging that Trace so if you don't want to do this manually you know uh make the computer do it right and uh once it's done and once it stops you'll be you'll be recording uh the uh the bug so let me do a quick little demo just to show um how how some of this stuff works um one minute okay I'll be really quick okay uh so um as mentioned you can just um debug with backend RR um it's recorded you can see the output of the run of the program um and uh um now we're actually we're actually debugging the trace so if we set a breakpoint at Maine um continue we can see some of the things that it's doing right so it's saying it's asking what date it is printing that out and it's also opening Dev random um which should be different every time right so if we if we next a little bit we can see um we read we read from Dev random uh and if we print the result of that so like the bites that we have have um you can see kind of roughly just the output that we get right so just remember the first two 180 183 uh so let's set a breakpoint here on our T.O 24 um so we'll restart we're restarting um all the way from the beginning again continue continue again to our second breakpoint and if we print the value of that variable again we see it's exactly the same um so even even complete sources of Randomness will be replayed Faithfully every single time um and just to show that it's not a trick uh you can see when I run it for real um every single time the output is actually different so we are recording and replaying faithfully and that's time thank you very much