<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Undo Bytes on Medium]]></title>
        <description><![CDATA[Stories by Undo Bytes on Medium]]></description>
        <link>https://medium.com/@undo-bytes?source=rss-bda2af557098------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*PHjP73HQiORXaKjXz4uSTg.png</url>
            <title>Stories by Undo Bytes on Medium</title>
            <link>https://medium.com/@undo-bytes?source=rss-bda2af557098------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 17 Jun 2026 10:43:57 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@undo-bytes/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[AI Agents Don’t Have an Intelligence Problem. They Have a Context Problem.]]></title>
            <link>https://undo-bytes.medium.com/ai-agents-dont-have-an-intelligence-problem-they-have-a-context-problem-015b08124b9b?source=rss-bda2af557098------2</link>
            <guid isPermaLink="false">https://medium.com/p/015b08124b9b</guid>
            <category><![CDATA[coding-agents]]></category>
            <category><![CDATA[cplusplus]]></category>
            <dc:creator><![CDATA[Undo Bytes]]></dc:creator>
            <pubDate>Thu, 28 May 2026 10:21:29 GMT</pubDate>
            <atom:updated>2026-05-28T10:21:29.354Z</atom:updated>
            <content:encoded><![CDATA[<p>By Greg Law, CEO at <a href="https://undo.io">Undo</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yvc8iGfi8MUFDNuR7pYt3g.png" /></figure><p>Over the past year, we’ve seen an explosion in AI-generated code.</p><p>On the surface, this looks like a productivity breakthrough. More code, written faster, with less effort. But if you spend time inside large, real systems, it doesn’t feel that simple.</p><p>In fact, something concerning is happening: we’re producing systems that fewer and fewer people (or machines!) actually understand.</p><h3>More code. Less understanding.</h3><p>AI has dramatically increased the volume of code being written.</p><p>But a growing proportion of that code is:</p><ul><li>Not especially well structured</li><li>Only partially understood</li><li>Generated faster than it can realistically be validated</li></ul><p>And there’s a tendency to trust it a bit more than we should.</p><p>The net effect isn’t just more software, but more unknowns and uncertainty.</p><p>Systems become harder to reason about and failures become harder to diagnose. Indeed, when something goes wrong, it’s harder to answer fairly basic questions like ‘what happened?’ or ‘why did that happen?’.</p><h3>Where AI agents struggle</h3><p>There’s a lot of interest in using AI agents to debug systems. And in simple cases, they can be very helpful indeed. But once you get into real-world systems (multi-process, multithreaded, legacy monolithic codebases), they tend to struggle.</p><p>It’s not really an intelligence issue. It’s just that they can’t see what happened across processes or threads or other kinds of complex interactions, especially where cause and effect are far apart. They can read static code or scan logs and follow documentation (where it exists!).</p><p>But they cannot see:</p><ul><li>The actual execution path taken through the code.</li><li>The values that flowed through it at runtime.</li><li>The state as it changed across modules and services.</li><li>The exact sequence of events that led to an unexpected behavior or failure.</li><li>What <em>actually happened</em> in production.</li></ul><p>So they fill the gaps with something that looks plausible… in other words, <strong>they guess.</strong> And as engineers, we don’t like guessing…</p><h3>AI effectiveness = model + context</h3><p>No matter how good a model is, it is only as good as the context it’s given. If you don’t provide enough of the right information (high-quality relevant context), even a very good model will struggle.</p><blockquote><em>And the hardest problems in software don’t live in static code. They live in runtime behavior.</em></blockquote><h3>Why runtime is where the truth lives</h3><p>In complex systems, cause and effect are often quite far apart.</p><p>A failure might be triggered by:</p><ul><li>A specific ordering of events</li><li>A subtle state change that only happens under load</li><li>A wrong value that propagates across multiple services</li><li>A customer-specific environment that only exists in production</li></ul><p>These things don’t show up clearly in logs (that’s if you have the right logs in the first place). They aren’t obvious from reading code either. So AI agents try to reconstruct what happened from incomplete information.</p><p>And they are often non-deterministic — meaning you can’t just “re-run” the system and expect the same result. This is why both engineers and AI agents struggle.</p><h3>What’s missing: complete runtime context</h3><p><strong>If we want AI agents to be genuinely useful for understanding complex issues in complex codebases, we need to give them access to what actually happened when the program was running.</strong></p><p>That means:</p><ul><li>Full control flow across the system</li><li>Complete data flow (the values, as they changed)</li><li>A faithful recording of the exact software execution</li></ul><p>When you provide this level of <strong>deep runtime context</strong>, AI agents stop guessing entirely and start reasoning based on some ground truth.</p><h3>What this unlocks</h3><p>With deep runtime context that is specific to your application, AI can move beyond surface-level assistance into something far more powerful:</p><p><strong>Automatic root cause analysis </strong>— Not “possible causes”, but precise explanations grounded in actual execution. <em>It can even mean no human engineer needs to see potentially sensitive customer information</em>.</p><p><strong>Faster, cheaper debugging</strong> — Less back-and-forth, fewer tokens, and the ability to use smaller models effectively.</p><p><strong>Elimination of blind trust </strong>— Engineers can verify exactly what happened, rather than relying on AI-generated narratives.</p><p><strong>Reduced operational drag</strong> — Less firefighting. Faster resolution. Fewer escalations.</p><p><strong>Maintainable AI-generated code</strong> — Because if you can’t debug it, you can’t safely scale it.</p><h3>The bigger picture</h3><p>AI is increasing the scale and complexity of the systems we build. That’s fine… as long as our ability to understand those systems keeps up.</p><blockquote><em>If we don’t solve the context problem, we risk trading short-term productivity gains for long-term instability. If we do solve this problem, AI becomes much more useful. Not just for writing code, but for understanding it.</em></blockquote><p>In the end, this isn’t really about smarter models, but about giving them access to a ground truth (a record of events).</p><p><strong>See how this works in real life</strong></p><p>This keynote presentation actually does a good job of illustrating what you can achieve with deep runtime context <em>specific </em>to the application you’re working on:</p><p><a href="https://www.youtube.com/watch?v=v6OyVjQpjjc&amp;t=3592s">https://www.youtube.com/watch?v=v6OyVjQpjjc&amp;t=3592s</a></p><p><strong>👉🏻 Feel free to DM </strong><a href="https://uk.linkedin.com/in/gregthelaw"><strong>Greg</strong></a><strong> if you want to see how this might work on your own system.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=015b08124b9b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Boost Your C/C++ Debugging in VS Code with Copilot and Time Travel Debugging]]></title>
            <link>https://undo-bytes.medium.com/boost-your-c-c-debugging-in-vs-code-with-copilot-and-time-travel-debugging-fc974947c1e1?source=rss-bda2af557098------2</link>
            <guid isPermaLink="false">https://medium.com/p/fc974947c1e1</guid>
            <category><![CDATA[co-pilot]]></category>
            <category><![CDATA[debugging]]></category>
            <category><![CDATA[vscode]]></category>
            <dc:creator><![CDATA[Undo Bytes]]></dc:creator>
            <pubDate>Thu, 02 Oct 2025 16:24:39 GMT</pubDate>
            <atom:updated>2025-10-02T16:24:39.278Z</atom:updated>
            <content:encoded><![CDATA[<h3>Copilot is driving a VS Code standard</h3><p>For years, developers have had freedom of choice when it comes to editors and IDEs. Vim, Emacs, Visual Studio, Eclipse, CLion. Every team had its mix. But in recent times, a shift has accelerated: <a href="https://code.visualstudio.com/docs/copilot/overview">GitHub Copilot</a><strong> has pulled developers into Visual Studio Code.</strong></p><p>It’s not always because they <em>want</em> to switch. It’s because they <em>need</em> to. Copilot is becoming a must-have productivity tool, and the best Copilot experience is in VS Code. As a result, many teams are standardizing on VS Code, even when they’ve historically been fragmented across different tools.</p><p>This standardization is not a bad thing. It reduces toolchain friction, simplifies onboarding, and aligns workflows. But it creates a new dynamic: developers are writing code faster than ever before, thanks to Copilot, yet when it comes to debugging, they’re still stuck with the same old breakpoints and print statements.</p><h3>The productivity gap Copilot leaves behind</h3><p>Copilot has proven its value:</p><ul><li>It suggests boilerplate and idiomatic code</li><li>It accelerates feature development</li><li>It reduces friction for newer developers learning a codebase</li></ul><p>But Copilot stops helping once code compiles and runs. When something goes wrong (a crash, a regression, or that intermittent race condition), Copilot doesn’t have answers. Sure, it can help when you have a simple 100% reproducible bug, or when you have good logs, but how many difficult bugs are like this? Debugging remains <strong>manual, slow, and frustrating</strong>.</p><p>For C and C++ teams, the problem is even sharper. These languages come with unique debugging pain points:</p><ul><li>Memory corruption that only manifests under certain inputs</li><li>Data races and concurrency issues that vanish when you add logs</li><li>Legacy codebases where nobody truly understands the logic flow</li></ul><p>The result? Developers spend <strong>disproportionate time debugging compared to writing new code.</strong> In fact, industry studies suggest engineers can spend <em>more than 50% of their time</em> in debugging mode. If Copilot makes you write code 20% faster but you’re still spending weeks chasing after an elusive bug, the productivity gains vanish.</p><h3>Enter Undo: Time travel debugging in VS Code</h3><p>Traditional debugging in C/C++ relies on a forward-only model: you set a breakpoint, run until it’s hit, then step line by line. If you overshoot the bug, you restart the program and try again. This <em>trial-and-error cycle</em> wastes time and makes it nearly impossible to capture nondeterministic issues like race conditions.</p><p><a href="https://undo.io/">Undo’s time travel debugging</a> eliminates this guesswork. The Undo extension for VS Code records the complete execution of your program, including all memory changes, system calls, and thread interleavings. Once recorded, the execution becomes deterministic and fully replayable. This enables:</p><ul><li><strong>Reverse step &amp; reverse continue</strong> — move backwards through your program execution just like stepping forwards, so you can trace the chain of events leading up to a failure</li><li><strong>Memory watchpoints in reverse</strong> — instantly jump to the point in time when a variable or memory location was last modified. Instead of hunting through logs, you can pinpoint where corruption first occurred</li><li><strong>Cross-thread causality</strong> — for multithreaded code, Undo shows you exactly which thread wrote to shared state and when, making data race reproduction straightforward</li><li><strong>Deterministic replay</strong> — the same bug can be replayed as many times as needed, with identical behavior. This is critical for those “heisenbugs” that disappear when you add logging</li></ul><p>This means debugging becomes less about guesswork and more like forensic analysis. You don’t ask “what might have happened?” You <em>see what actually did happen</em>.</p><h3>Debugging your way: GUI or terminal</h3><p>One of the challenges with introducing new tools is developer preference. Some engineers live happily in a graphical debugger inside VS Code. Others are die-hard terminal users who find GUIs distracting. Undo respects both.</p><h4>Option 1: Graphical debugging in VS Code</h4><p>Inside VS Code’s Debugger pane, you’ll see familiar controls (breakpoints, call stack, variables, watch expressions) but now with time travel capabilities. For example:</p><ul><li>When you hit a breakpoint and notice an incorrect value, you can reverse-step until you see the exact instruction that changed it</li><li>You can <strong>navigate backwards in the call stack</strong> to watch function parameters evolve over time</li><li>You can even <strong>scrub back through the timeline</strong> of execution to quickly jump between failure states and earlier execution points</li></ul><p>This makes time travel debugging accessible to developers who already use VS Code’s native debugger. No new interface to learn, just more powerful capabilities layered on top.</p><figure><img alt="Time travel debugging in VS Code" src="https://cdn-images-1.medium.com/max/1024/1*uO7cjaw9-2EbSkt_MgITng.png" /></figure><h4>Option 2: Terminal debugging inside VS Code</h4><p>For developers who prefer the raw power and speed of a CLI debugger, the extension also exposes <a href="https://undo.io/products/udb/"><strong>UDB</strong></a>, Undo’s GDB-compatible interface, directly inside VS Code’s integrated terminal.</p><ul><li>Commands like reverse-stepi, reverse-continue, and watch give you low-level control to move backwards through program flow.</li><li>Since UDB is GDB-compatible, most existing workflows and scripts translate directly, just with the added dimension of time travel.</li><li>This means you can stay in VS Code for editing, but still have the feel of a classic terminal debugger session without context-switching into a different tool.</li></ul><figure><img alt="UDB exposed inside VS Code’s integrated terminal" src="https://cdn-images-1.medium.com/max/1024/1*2ITCOOleDIMWM9HB_3-X_Q.png" /></figure><p>By offering <strong>both GUI and CLI workflows</strong>, Undo ensures that teams don’t have to compromise on debugging style. Some developers can use the point-and-click interface inside VS Code; others can live inside UDB in the terminal. And you don’t have to pick just one: many developers mix and match, using both at the same time. For example, you might keep the GUI open to visualize program state while simultaneously issuing precise reverse-step commands in UDB within the terminal. Both are powered by the same recording and time travel engine under the hood, so the context stays perfectly in sync.</p><h3>A modern workflow: Copilot + Undo together</h3><p>When you combine Copilot with Undo, you get a <strong>full-cycle development workflow</strong> inside VS Code:</p><ol><li><strong>Write code with Copilot </strong>— Copilot accelerates boilerplate, suggests APIs, and makes implementing new logic smoother</li><li><strong>Run and test </strong>— As always, you compile and execute your program. If everything works, great. If not, this is where Undo comes in</li><li><strong>Debug with Undo’s time travel </strong>— Instead of sprinkling logs or setting endless breakpoints, you record execution and <em>rewind</em> to the exact failure point</li><li><strong>Fix confidently </strong>— Once the root cause is identified, you return to writing, with Copilot ready to help propose fixes or new implementations</li></ol><p>This loop minimizes context switching. You’re not juggling multiple tools or wasting time reconstructing failure scenarios. Everything happens in VS Code, and both Copilot and Undo are playing to their strengths.</p><h3>Why it matters for C/C++ teams</h3><p>Higher-level languages often come with safer abstractions, runtime protections, or frameworks that simplify debugging. C and C++ do not. That’s part of their power: direct control over memory, performance, and system resources, but it’s also the source of pain.</p><p>When debugging becomes the bottleneck, the productivity gains of Copilot are capped. By pairing Copilot’s <em>faster coding</em> with Undo’s <em>faster debugging</em>, C/C++ teams can achieve a step-change in velocity. The two tools together:</p><ul><li><a href="https://undo.io/solutions/developer-productivity/reduce-time-spent-debugging/">Reduce time wasted on hard-to-find bugs</a></li><li><a href="https://undo.io/solutions/developer-productivity/reduce-debug-cycles-down-just-1-build/">Shorten feedback loops between writing and fixing</a></li><li><a href="https://undo.io/solutions/developer-productivity/fix-flaky-tests-problem/">Help teams ship more reliable software, faster</a></li></ul><blockquote><em>In other words, </em><strong><em>the modern C/C++ workflow is no longer just Copilot + VS Code. It’s Copilot + Undo inside VS Code.</em></strong></blockquote><h3>Closing thoughts</h3><p>The future of development isn’t just about writing more code. It’s about <strong>trusting, testing, and debugging code faster</strong>. Copilot is already the default for writing. Undo is the natural counterpart for debugging. Together, they create a complete loop of productivity inside VS Code.</p><p>If your team is standardizing on VS Code and Copilot, don’t leave debugging stuck in the past. Pair Copilot with Undo and make debugging as modern as coding.</p><p>👉<a href="https://marketplace.visualstudio.com/items?itemName=Undo.udb"> <strong>Try the Undo Time Travel Debug for C/C++ VS Code extension today</strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fc974947c1e1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Is Your AI Budget Too High?]]></title>
            <link>https://undo-bytes.medium.com/is-your-ai-budget-too-high-139fcee46258?source=rss-bda2af557098------2</link>
            <guid isPermaLink="false">https://medium.com/p/139fcee46258</guid>
            <category><![CDATA[ai-budgeting-tools]]></category>
            <category><![CDATA[debugging]]></category>
            <category><![CDATA[ai-debugging]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Undo Bytes]]></dc:creator>
            <pubDate>Mon, 21 Jul 2025 14:03:04 GMT</pubDate>
            <atom:updated>2025-07-21T22:10:45.898Z</atom:updated>
            <content:encoded><![CDATA[<h4>Let Us Help You Spend It 😉</h4><p>AI is <strong>everywhere</strong> these days.</p><p>If you work in software, you’ve probably been asked what your AI strategy is.</p><p>Or worse — told what it should be… by someone who doesn’t know what a compiler does.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ckoyyOIsNK91SCdCUugOSA.png" /><figcaption>Nothing says technical innovation like advice from someone who’s never compiled a line of code.</figcaption></figure><p><strong>The result?</strong></p><p>AI budgets are blooming like Japanese knotweed.</p><p>Meanwhile, actual improvements to engineering productivity remain… elusive.</p><h3>AI meets real engineering</h3><p>Much of today’s AI tooling in software development is impressive — until you ask it to do something genuinely hard.</p><p>AI is brilliant at:</p><ul><li>Suggesting things you already knew</li><li>Explaining the obvious in a more verbose way</li><li>Making confident guesses that are ever so slightly wrong</li></ul><p>What it <em>can’t</em> do is debug a thorny, intermittent fault buried in a million-line C++ codebase maintained by four different teams over 12 years. At least, not without help.</p><p>That’s where we come in.</p><p>Undo makes recordings of your program’s actual execution — every line, every thread, every change in state — and lets you replay it like a black box flight recorder.</p><p>Now, we’re feeding that data to AI.</p><h3>Time travel for debuggers and other intelligent lifeforms</h3><p>With Undo, your engineers already get to “rewind” and “fast-forward” through the exact behavior of their programs to find the root cause of bugs.</p><p>Now, with our AI integration, the machine can help too.</p><p>How?</p><ul><li>It reads real execution history — not just logs or guesses.</li><li>It explains <em>what happened</em> and <em>why</em> in natural language.</li><li>It highlights suspicious states, summarizes complex call paths, and yes, even suggests fixes.</li></ul><p>Think of it as giving your AI an actual memory, not just a hunch.</p><h3>Justifying Undo on the AI budget (without blushing)</h3><p>Here’s the good news: we tick all the right boxes.</p><ul><li>Developers love us.</li><li>Managers understand us.</li><li>Procurement hears “AI-enabled” and relaxes.</li><li>And, crucially, it actually works.</li></ul><p>If you need to show you’re spending your AI budget wisely, few things say “innovation” like letting your software <em>replay itself</em>, and then <em>explain itself</em>.</p><p>Especially when one of the world’s leading AI labs literally gasped when we showed them <a href="https://undo.io/">Undo</a>.</p><h3>Final thoughts (before your CFO asks for a slide deck)</h3><p>If you’ve got money set aside for AI, don’t waste it on novelty or noise.</p><p>Spend it on something your team will thank you for.</p><p><strong>Undo: Time travel debugging, now with AI support.</strong></p><p>For when your software breaks and you’d rather not guess why.</p><blockquote><a href="https://undo.io/products/undo-ai/">Can Undo AI help my business?</a></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=139fcee46258" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AddressSanitizer and Undo]]></title>
            <link>https://undo-bytes.medium.com/addresssanitizer-and-undo-c1c14dbb6019?source=rss-bda2af557098------2</link>
            <guid isPermaLink="false">https://medium.com/p/c1c14dbb6019</guid>
            <category><![CDATA[undo]]></category>
            <category><![CDATA[addresssanitizer]]></category>
            <category><![CDATA[asan]]></category>
            <dc:creator><![CDATA[Undo Bytes]]></dc:creator>
            <pubDate>Mon, 14 Jul 2025 14:17:10 GMT</pubDate>
            <atom:updated>2025-07-14T14:17:10.183Z</atom:updated>
            <content:encoded><![CDATA[<p><a href="https://github.com/google/sanitizers/wiki/addresssanitizer">AddressSanitizer</a> and Undo are two tools that can be used to find bugs in your code. This article compares the two tools, and shows how they can be used together to debug issues more effectively than either tool alone.</p><h3>What is AddressSanitizer?</h3><p>AddressSanitizer (ASan) is a compiler extension and runtime library, originally developed by Google. It has since been integrated into many compilers, including <a href="https://clang.llvm.org/docs/AddressSanitizer.html">Clang/LLVM</a> and <a href="https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html#index-fsanitize_003daddress">GCC</a>. It can be enabled at compile time by compiling the application with the -fsanitize=address option.</p><p>AddressSanitizer works by conceptually dividing the applications address space into two regions: one that the application uses and a “shadow” region. The runtime library replaces the malloc and free operation such that for every memory map created in the application region, a corresponding map is created in the shadow region. When the application map is freed, the shadow map is filled with “poison” values. AddressSanitizer modifies how the compiler generates machine code so that whenever it reads/writes memory, it also checks that the shadow map doesn’t contain poison values. See the <a href="https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm">AddressSanitizer algorithm</a> for more details.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/610/1*ll_cFaKLLRJhqj5UHIO4OA.png" /></figure><p>This allows AddressSanitizer to detect a wide variety of memory access bugs, including use-after-free bugs, buffer overflow bugs, and memory leaks. When AddressSanitizer detects an issue, its default behavior is to print some diagnostics and make the program exit immediately.</p><h3>Key takeaways</h3><ul><li>AddressSanitizer can detect use-after-free, buffer overflows, memory leaks and other types of bugs.</li><li>AddressSanitizer can be enabled using the -fsanitize=address flag in GCC/Clang.</li><li>AddressSanitizer instruments the code to insert runtime checks before each memory access operation.</li></ul><h3>What is Undo?</h3><p>Undo is a time travel debugging solution for large-scale enterprise applications. When there’s a bug in your software, you can use Undo to capture the bug in a recording file (capturing the full program execution in a single binary file) and to step back in the recording to examine the full state of the program at any point in time.</p><p>Undo comes with 2 components:</p><ol><li><a href="https://undo.io/products/undo-c-plusplus/">LiveRecorder</a>: for recording the runtime behavior of an application and saving it as a portable recording.</li><li><a href="https://undo.io/products/udb/">UDB</a>: for replaying recording files (or live debug sessions) back and forth in time to see what happened.</li></ol><h3>AddressSanitizer vs Undo: How do they compare?</h3><p>ASan has to be enabled at compile time, and since the clang <a href="https://clang.llvm.org/docs/AddressSanitizer.html#security-considerations">documentation</a> recommends against using ASan in production, this usually means the application needs to be recompiled as an internal debug build to use ASan.</p><p>Undo on the other hand works well on production applications (as long as symbols are available when replaying the recording), so a special build isn’t required.</p><p>ASan and Undo both require additional memory when running the application. The memory overhead of ASan largely depends on the memory allocations the application performs; fewer, larger maps have a lower overhead than many small maps. ASan also imposes a larger (up to 3x) memory overhead for stack memory. Additionally, ASan uses a very large (but mostly unused) memory map for the shadow region. This usually doesn’t matter, unless <a href="https://www.kernel.org/doc/Documentation/vm/overcommit-accounting">memory overcommit mode</a> is disabled on the system. In that case, the system will run out of memory and kill the application when ASan tries to create the shadow map.</p><p>The memory overhead introduced by Undo is usually less than 2x. While Undo doesn’t require overcommit mode like ASan, we still <a href="https://docs.undo.io/Limitations.html#system-limitations">recommend</a> using it.</p><p>In terms of execution speed, Google’s documentation of ASan says <em>“The average slowdown of the instrumented program is ~2x”</em>.</p><p>The slowdown of Undo is highly workload dependent. Many real-world programs can be recorded running at better than half-speed. For those with more threads, expect 1.5–5x slowdown per thread. Undo’s dynamic just-in-time instrumentation captures only the minimum data required to replay the process — 99% of the program state can be reconstructed on demand, so only the non-deterministic inputs need to be recorded. (see <a href="https://undo.io/resources/undo-performance-benchmarks/">performance benchmarks</a>)</p><h3>Detecting vs understanding errors</h3><p>Perhaps the biggest difference between ASan and Undo is that, while they’re both tools for improving software quality and stability, they’re useful at different points in the process. ASan (and other sanitizers, like <a href="https://undo.io/resources/threadsanitizer-or-thread-fuzzing/">ThreadSanitizer</a>) excel at detecting potential issues, but don’t offer much support for working out why the application tried to perform an invalid access memory.</p><p>In contrast, Undo does not detect that the program it’s recording is behaving incorrectly, but once you’ve recorded an occurrence of a bug, you can find out exactly the sequence of events that caused it to happen.</p><p>Therefore, the natural question to ask is:</p><p><strong>Can Undo find the cause of issues that ASan has detected?</strong></p><h3>Using Undo and AddressSanitizer together</h3><p>Let’s try an example of combining Undo and ASan to detect and root-cause a bug.</p><p>For this demonstration, we have created a simple program called malloc-var. The program is intended to allocate an integer on the heap, then increment it. However the program has a bug; let’s run the program to see it:</p><pre>$&gt; cd examples/<br>$&gt; make malloc-var<br>$&gt; ./malloc-var<br>Set the value to 42<br>Incremented value from 42 to 0</pre><p>Well that’s not right, so we’ll try using ASan and Undo’s LiveRecorder tool to locate and root-cause the bug. (We’re also going to pretend we aren’t allowed to read the source code of malloc-var unless one of our tools tells us that a line is of interest. This is to simulate how we might debug a real application that we aren’t familiar with, rather than a 22 line example program).</p><p>First, we’ll recompile malloc-var with -fsanitize=address, and rerun it under LiveRecorder:</p><pre>$&gt; make CFLAGS=&#39;-fsanitize=address -g -O0&#39; malloc-var<br>$&gt; live-record -o malloc-var.undo ./malloc-var<br>live-record: Maximum event log size is 1G.<br>Set the value to 42<br>=================================================================<br>==571059==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000014 at pc 0x5602a42402d5 bp 0x7ffc3bac05a0 sp 0x7ffc3bac0590<br>READ of size 4 at 0x602000000014 thread T0<br>    #0 0x5602a42402d4 in increment_pointed_value /home/dstevenson/undo/release/examples/malloc-var.c:12<br>    #1 0x5602a4240373 in main /home/dstevenson/undo/release/examples/malloc-var.c:23<br>    #2 0x7f999a7d3082 in __libc_start_main ../csu/libc-start.c:308<br>    #3 0x5602a424018d in _start (/home/dstevenson/undo/release/examples/malloc-var+0x118d)<br><br>0x602000000014 is located 0 bytes to the right of 4-byte region [0x602000000010,0x602000000014)<br>allocated by thread T0 here:<br>    #0 0x7f999aaae808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144<br>    #1 0x5602a4240309 in main /home/dstevenson/undo/release/examples/malloc-var.c:18<br>    #2 0x7f999a7d3082 in __libc_start_main ../csu/libc-start.c:308<br><br>SUMMARY: AddressSanitizer: heap-buffer-overflow /home/dstevenson/undo/release/examples/malloc-var.c:12 in increment_pointed_value<br>Shadow bytes around the buggy address:<br>  0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br>  0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br>  0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br>  0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br>  0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br>=&gt;0x0c047fff8000: fa fa[04]fa fa fa fa fa fa fa fa fa fa fa fa fa<br>  0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa<br>  0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa<br>  0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa<br>  0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa<br>  0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa<br>Shadow byte legend (one shadow byte represents 8 application bytes):<br>  Addressable:           00<br>  Partially addressable: 01 02 03 04 05 06 07 <br>  Heap left redzone:       fa<br>  Freed heap region:       fd<br>  Stack left redzone:      f1<br>  Stack mid redzone:       f2<br>  Stack right redzone:     f3<br>  Stack after return:      f5<br>  Stack use after scope:   f8<br>  Global redzone:          f9<br>  Global init order:       f6<br>  Poisoned by user:        f7<br>  Container overflow:      fc<br>  Array cookie:            ac<br>  Intra object redzone:    bb<br>  ASan internal:           fe<br>  Left alloca redzone:     ca<br>  Right alloca redzone:    cb<br>  Shadow gap:              cc<br>==571059==ABORTING<br>live-record: Saving to /home/dstevenson/undo/release/examples/malloc-var.undo ...<br>live-record: Saving     99%<br><br>live-record: Termination recording written to /home/dstevenson/undo/release/examples/malloc-var.undo<br>live-record: Detaching...</pre><p>This error message has a lot of information, but the main takeaway is that ASan detected a heap buffer overflow in malloc-var.c on line 12. If we peek at that line, we can infer that the memory referenced by ptr_to_value_to_increment must contain the value 0, but it’s not immediately obvious why:</p><pre>printf(&quot;Incremented value from %d to %d\n&quot;, old, *ptr_to_value_to_increment);</pre><p>However, we also saved a recording of the program, so let’s load that in Undo’s UDB debugger, jump to the end of the recording and look at the backtrace:</p><pre>$&gt; udb malloc-var.undo<br>[...]<br>start 1&gt; ugo end<br>[...]<br>end 66,947,821&gt; backtrace<br>#0  __sanitizer::internal__exit (exitcode=1) at ../../../../src/libsanitizer/sanitizer_common/sanitizer_linux.cc:429<br>#1  0x00007f999aad6e97 in __sanitizer::Die () at ../../../../src/libsanitizer/sanitizer_common/sanitizer_flags.h:37<br>#2  0x00007f999aab852c in __asan::ScopedInErrorReport::~ScopedInErrorReport (this=0x7ffc3babf926, __in_chrg=&lt;optimised out&gt;) at ../../../../src/libsanitizer/asan/asan_report.cc:185<br>#3  0x00007f999aab7fa3 in __asan::ReportGenericError (pc=94569343746773, bp=bp@entry=140721309615520, sp=sp@entry=140721309615504, addr=105690555219988, is_write=is_write@entry=false,<br>	access_size=access_size@entry=4, exp=0, fatal=true) at ../../../../src/libsanitizer/asan/asan_report.cc:458<br>#4  0x00007f999aab8ccb in __asan::__asan_report_load4 (addr=&lt;optimised out&gt;) at ../../../../src/libsanitizer/asan/asan_rtl.cc:118<br>#5  0x00005602a42402d5 in increment_pointed_value (ptr_to_value_to_increment=0x602000000014) at malloc-var.c:12<br>#6  0x00005602a4240374 in main () at malloc-var.c:23<br>end 66,947,821&gt;</pre><p>Most of the backtrace is the code ASan calls to report the error, but in stack frame #5 we’re on line 12 in our code. Let’s place a watchpoint on ptr_to_value_to_increment, and reverse to see where that pointer came from:</p><pre>end 66,947,821&gt; frame 5<br>end 66,947,821&gt; watch ptr_to_value_to_increment<br>end 66,947,821&gt; reverse-continue<br>Continuing.<br><br>Hardware watchpoint 1: ptr_to_value_to_increment<br><br>Was = (int *) 0x602000000014<br>Now = (int *) 0x602000000010<br>increment_pointed_value (ptr_to_value_to_increment=0x602000000010) at malloc-var.c:11<br>11  ptr_to_value_to_increment += 1;<br>0% 3,007&gt;</pre><p>Reversing has taken us back to line 11, where we increment the pointer. But from the name of the function (increment_pointed_value) and the intended behavior of the program we can infer that we actually wanted to increment the value on the heap, not the value of the pointer.</p><p>And with that, we’ve root-caused this (very simple) bug; the corrected source code of malloc-var is:</p><pre>1	/* This is free and unencumbered software released into the public domain.<br>2	 * Refer to LICENSE.txt in this directory. */<br>3    <br>4	#include &lt;stdio.h&gt;<br>5	#include &lt;stdlib.h&gt;<br>6    <br>7	static void<br>8	increment_pointed_value(int *ptr_to_value_to_increment)<br>9	{<br>10		int old = *ptr_to_value_to_increment;<br>11 -	ptr_to_value_to_increment += 1;<br>   +	*ptr_to_value_to_increment += 1;<br>12		printf(&quot;Incremented value from %d to %d\n&quot;, old, *ptr_to_value_to_increment);<br>13	}<br>14    <br>15	int<br>16	main(void)<br>17	{<br>18		int *ptr = malloc(sizeof(int));<br>19    <br>20		*ptr = 42;<br>21		printf(&quot;Set the value to %d\n&quot;, *ptr);<br>22    <br>23		increment_pointed_value(ptr);<br>24    <br>25		free(ptr);<br>26    <br>27		return EXIT_SUCCESS;<br>28	}</pre><p>We can also rerun malloc-var to prove that this fixes the bug:</p><pre>$&gt; ./malloc-var<br>Set the value to 42<br>Incremented value from 42 to 43</pre><h3>Caveats</h3><p>Undo is compatible with ASan, but for some features a little configuration is required. Undo’s tools have the ability to attach to running processes, but in order to attach to applications that use Asynchronous I/O, the application must have been started with a special preload library. However, ASan’s runtime library must be loaded before all other shared libraries, and this has to be manually configured by setting the LD_PRELOAD environment variable.</p><pre># Compile examples/aio.c with AddressSanitizer<br>$&gt; make CFLAGS=-fsanitize=address aio<br><br># Start my-program, with the Undo Async I/O preload library<br>$&gt; LD_PRELOAD=libasan.so:tools/libundo_aio_preload_x64.so ./my-program &amp;<br><br># Attach LiveRecorder to the process.<br>$&gt; live-record -p $! -o recording.undo</pre><h3>Conclusion</h3><p>In conclusion, AddressSanitizer and Undo are complementary tools that work well together to enhance developer productivity and improve the quality and correctness of applications developed with their help.</p><p><strong>Interested in seeing Undo in action? </strong>Book a slot with one of our Solutions Engineers for a quick demo and determine whether this will work in your environment.</p><blockquote><a href="https://calendly.com/undo-time-travel-debugging/30min">https://calendly.com/undo-time-travel-debugging/30min</a></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c1c14dbb6019" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using AI to Debug your Programs with Undo]]></title>
            <link>https://undo-bytes.medium.com/using-ai-to-debug-your-programs-with-undo-3835642ecc61?source=rss-bda2af557098------2</link>
            <guid isPermaLink="false">https://medium.com/p/3835642ecc61</guid>
            <category><![CDATA[debug]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[debugging-tools]]></category>
            <category><![CDATA[debugging-tips]]></category>
            <dc:creator><![CDATA[Undo Bytes]]></dc:creator>
            <pubDate>Thu, 10 Jul 2025 16:46:25 GMT</pubDate>
            <atom:updated>2025-07-10T16:46:25.778Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Author: Marco Barisione, Principal Software Engineer at Undo</em></p><p>AI is transforming how we write software, but debugging remains in the dark ages. At Undo, we’re changing that by giving AI access to complete execution history. Our time travel debugging engine records every instruction, variable, and function call, allowing the AI not only to watch what happened but also to understand why.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/306/1*Jv3XgQom3xwlxCrQXgzmLA.png" /></figure><h3>What is Undo?</h3><p>If you are unfamiliar with <a href="https://undo.io/">Undo</a>, at the core of our technology is the Undo Engine, which implements record, replay, and time travel debugging. It can produce recordings of your program’s execution.</p><p>Recordings capture the whole execution history — every line of code in every thread, every variable, every I/O. All you have to do is rewind and fast-forward in time in the recording to observe how the state changes and why the code behaves the way it does.</p><p>This means you can:</p><ul><li>Get a picture of the code flow by exploring how the code is executed dynamically</li><li>See exactly what happened and how it happened</li><li>Understand subtle interactions amongst complex components</li></ul><p>Recordings are portable, allowing them to be replayed outside of the original environment and shared with your team, or even generated on a customer or production system and then shared with developers.</p><blockquote><strong><em>But can we use execution history, along with AI, either to fix existing bugs or to be part of the AI feedback loop when generating new code?</em></strong></blockquote><h3>Debugging with AI</h3><p>You’ve just been assigned a bug. It only happened once on a test machine, or it happens in a customer environment you don’t have access to. The code is unfamiliar, large, and complicated. Maybe you’ve got a few logs and a vague description, and now it’s your problem to solve. This is probably a situation all developers have had to deal with — what are your options?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*JaVVcwgTHHhAk_maA9i8_A.jpeg" /></figure><p>Nowadays, many developers will probably try using an <strong>AI with some logs and relevant code</strong>. It might make a plausible guess, but most of the time, with complex bugs, it will fixate on something unrelated, like a line that wasn’t even executed, or give a general suggestion that doesn’t apply. Without knowing what <em>actually</em> happened during the run, the AI is operating in the dark.</p><p>Now, imagine you have an <strong>Undo recording and you can use time travel debugging </strong>on the failing program run. You can see exactly what happened: which paths the program took, what values changed, and when. You can step back from the failure, inspect state, and understand the code as it actually behaved. This is powerful — and it’s what our customers already rely on — but it still involves a lot of manual work, especially when the code is unfamiliar.</p><p>A possible next step is to <strong>integrate Undo with an AI</strong>: you give the AI access to the recording and let it help drive the investigation. It can walk through function calls, summarize what the program did, and highlight areas worth looking into. This speeds up exploration, but current models still struggle with complex debugging, probably because, while there is a lot of training data on how to write code, there isn’t much high-quality data on how to debug.</p><p>The core problem is that the number of possible program states is astronomically huge, but most bugs only show up in one very specific scenario. <strong>What the AI needs is guidance.</strong> Instead of the AI guessing or manually probing the recording, we feed it structured, targeted insights from the program’s execution history. The model isn’t just driving a debugger — it’s informed by what actually happened and why. There are quite a few open questions about how to achieve this, but we’ve discovered a handful of new and interesting ways that are really promising to greatly improve debugging capabilities.</p><blockquote><strong><em>The goal is for the LLM not only to identify when something went wrong in the recording, but also to explain why it happened — and even suggest a fix.</em></strong></blockquote><p>Watch this space!</p><h3>A less annoying Clippy?</h3><p>In the meantime, while we work on deeper integrations, <strong>is there anything we can do now to gain some practical value from AI</strong>?</p><p>If you remember Clippy, the animated paperclip from Office 97, you’ll understand why we were cautious about bolting AI onto a debugger. Until recently, the idea felt a bit gimmicky. But newer models like ChatGPT, Claude Opus and Google’s Gemini have changed that. They’re more capable, more context-aware, and genuinely helpful in the right situations. While this is not yet the “help the AI” future we envision in the long term, we believe that some integration can already be very beneficial to our users.</p><p>We’ve been experimenting with a new explain command in UDB, which allows an AI (we currently use <a href="https://www.anthropic.com/claude-code">Claude Code</a>) to drive the debugger to answer your questions.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FEIieX40vwHY&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DEIieX40vwHY&amp;image=http%3A%2F%2Fi.ytimg.com%2Fvi%2FEIieX40vwHY%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/a14e0a69258c67aed05ac7ca9290ca76/href">https://medium.com/media/a14e0a69258c67aed05ac7ca9290ca76/href</a></iframe><p>In the video, you can see the AI solve a stack smash bug. This is, of course, a very simple example, but the AI integration has already proven useful in practice, especially when dealing with unfamiliar code. It can trace what happened, summarize it, and point you in the right direction. That can save a lot of time, particularly when stepping through complex or legacy code.</p><p>We’ll be releasing a preview version of this feature as an optional <a href="https://docs.undo.io/Addons.html">add-on</a> soon. Stay tuned!</p><p><strong>Interested in trying Undo on your code? Get a free trial below.</strong></p><blockquote><a href="https://undo.io/udb-free-trial/">https://undo.io/udb-free-trial/</a></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3835642ecc61" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Undo × MCP: Time Traveling With Your AI Code Assistant]]></title>
            <link>https://undo-bytes.medium.com/undo-mcp-time-traveling-with-your-ai-code-assistant-40d5a065733b?source=rss-bda2af557098------2</link>
            <guid isPermaLink="false">https://medium.com/p/40d5a065733b</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[mcps]]></category>
            <dc:creator><![CDATA[Undo Bytes]]></dc:creator>
            <pubDate>Thu, 10 Jul 2025 16:30:29 GMT</pubDate>
            <atom:updated>2025-07-10T16:30:29.890Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Author: Mark Williamson, CTO at Undo</em></p><h3>Why your AI code assistant needs a time machine</h3><p><strong>AI code assistants </strong>based on LLMs (<a href="https://en.wikipedia.org/wiki/Large_language_model">Large Language Models</a>), such as <a href="https://www.anthropic.com/claude-code">Claude Code</a>, <a href="https://openai.com/codex/">OpenAI Codex</a> and <a href="https://github.com/features/copilot">GitHub Copilot</a>, are popping up everywhere today and enabling us to write code faster than ever. They can also help out by analyzing source code, logs and output from other tools to help find bugs.</p><p>But, clever though they are, there is critical information they lack:</p><ul><li>They can’t know the dynamic behavior of the software just by looking at the source code (they don’t have access to the inputs, variable values, etc).</li><li>Logs capture some dynamic behavior but only if you have them in the right place. If you don’t, you and the LLM need to go around tedious cycles of editing, rebuilding and reproducing the issue.</li></ul><p>Looked at another way, LLMs can be brilliant when they have <em>enough</em> of the <em>right</em> context. But if they can’t see how your program actually behaves then they don’t have this and can’t get it.</p><p>We think time travel debugging is the right technology to close this gap.</p><p><a href="https://undo.io/"><strong>Undo</strong></a> is our time travel debugger — a suite of tools that can record unmodified Linux software, then replay and debug it to understand how it went wrong (or just how it works). You get an immutable recording of everything the process did, at machine instruction granularity.</p><p>It works with C, C++, Rust, Go and Java (with more languages to come in future).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*b4FlXbiAHRkvrLcGkMPYfw.png" /></figure><h3>Quick start for impatient people</h3><p><em>If you don’t have Undo yet, </em><a href="https://undo.io/udb-free-trial/"><em>go get it</em></a><em>, then return to this section!</em></p><p>This article is about an MCP (<a href="https://modelcontextprotocol.io/introduction">Model Context Protocol</a>) server integration to use Undo’s <a href="https://undo.io/products/udb/">UDB</a> debugger with AI coding assistants. If you’re already a user of Undo + AI, you can try our first proof of concept right now, then return to the article when you’re ready.</p><p><strong><em>Caveat: </em></strong><em>This is a tech preview, it’s bleeding edge and won’t work in all cases, but it should already be useful.</em></p><p>If you have a recent installation of Undo (v8.2.2 or higher should work, for older versions YMMV) then you can activate our AI extension at the UDB prompt using:</p><pre>extend explain</pre><p>This will download and install the explain <a href="https://docs.undo.io/Addons.html">extension</a>. Now you have two new commands:</p><ul><li>uexperimental mcp serve – Starts an MCP server on localhost:8000 for you to access from an external coding agent of your choice. This will turn over control of your debug session to the external coding agent (when the AI agent has disconnected you can simply quit the server with Ctrl-C and return to using UDB as a normal debugger.).</li><li>explain – Ask questions about the program you’re debugging (requires a working installation of <a href="https://www.anthropic.com/claude-code">Claude Code</a>). Behind the scenes, this turns over control to Claude Code via MCP, then returns control to you once your question has been answered.</li></ul><p>Good questions for the explain command ask for information about the program’s behavior or ask for help with reverse navigation. For instance:</p><p><em>What went wrong in this program?</em></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FmJL_LQ8cWoc%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DmJL_LQ8cWoc&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FmJL_LQ8cWoc%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/c841bb27ad83e85564941108056f2270/href">https://medium.com/media/c841bb27ad83e85564941108056f2270/href</a></iframe><p><em>What does the current backtrace mean?</em></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FkU3v9hH8RQI&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DkU3v9hH8RQI&amp;image=http%3A%2F%2Fi.ytimg.com%2Fvi%2FkU3v9hH8RQI%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/321b92a5fc58e4297c75b28163a1915e/href">https://medium.com/media/321b92a5fc58e4297c75b28163a1915e/href</a></iframe><p><em>Get me back out of libc.</em></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F_qykIjhD62o%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D_qykIjhD62o&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F_qykIjhD62o%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/96ad1ba9c01290097b5dc9e819d89a73/href">https://medium.com/media/96ad1ba9c01290097b5dc9e819d89a73/href</a></iframe><p>Successive invocations of explain reuse the underlying Claude session, so you can have an ongoing dialogue during a single debug session.</p><h3>Deep dive</h3><h3>What is Undo?</h3><p>Undo provides software time travel. It can record the history of an x86 or ARM64 process, running on Linux, then wind back and forth through it down to machine instruction precision.</p><p>To do this, the <em>Undo Engine</em> instruments the process being recorded. The program requires no modification or special compilation — Undo injects JIT instrumentation at runtime to capture all the non-deterministic inputs to the program. Once this is done, the program can run as normal, while the Undo Engine captures every piece of non-deterministic information that flows into the program.</p><p>This includes:</p><ul><li>System call results.</li><li>Thread scheduling.</li><li>Special machine instructions (e.g. x86’s rdtsc to read the time stamp counter).</li><li>Shared memory with other processes or hardware devices.</li><li>And so on…</li></ul><p>With this information Undo can store the program’s execution into a portable recording file, which can be replayed any time — even on another machine. You can replay forward to see how the original execution unfolded or use time travel to rewind and jump around; ask questions like “When was the last time this function ran?” or “When did this variable get assigned this value?”.</p><p>On top of this machine-level capability, Undo supplies debug interfaces for C, C++, Rust, Go, Java, Kotlin and Scala.</p><p>With this available intermittent bugs can be recorded once and analyzed at will, memory corruptions become a matter of winding back to the bad write and complex code can be understood quickly by stepping backwards to see how execution unfolded.</p><h3>Why does this matter to an AI?</h3><p>AI based on Large Language Models (LLMs) is transforming how development is done. Code Assistants now generate considerable amounts of code in the real world. Agentic coding allows developers to assign work to an LLM and have it complete development tasks autonomously.</p><p>They’re also really good at digesting a big codebase and answering questions. What they’re less good at is understanding the dynamic behavior of a program — the reasoning required to infer what’s actually happened in the subtle corner cases that make for some of the hardest bugs.</p><p>Without enough information to tether them to what the program really did they’re also inclined to hallucinate a confident-but-wrong answer:</p><p>Undo recordings provide a complete trace of everything a program did when a bug occurred. There’s no need to guess — if the issue is captured in a recording then it can be understood. This provides a <strong>ground truth</strong> that can be used to guide and verify an LLM’s reasoning. With the information contained in a recording, AI coding agents can be smarter and more efficient.</p><h3>What we’ve built so far</h3><p>We’ve released an add-on extension called explain, which integrates with our UDB debugger. This is a tech preview but it can be used today to provide real assistance to debugging workflows.</p><p>This is shipped via our public Addons repository, so if you have a recent release of UDB you should be able to run:</p><pre>extend explain</pre><p>And get access to the command (put it in your .udbinit file to make it available in every session).</p><p>The extension provides a general-purpose MCP server (to integrate our UDB debugger with arbitrary AI agents) and a tightly-integrated explain command, which uses Claude Code behind the scenes to provide agentic reasoning.</p><p><em>To get this set up, read the “</em><a href="https://undo.io/resources/time-travelling-with-your-ai-buddy/#quick-start"><em>Quick start for impatient people</em></a><em>“. (Give yourself a pat on the back for being patient the first time around)</em></p><p>The MCP server makes it possible to turn over control of the UDB debugger to an AI agent. This makes the context of a time travel debug session available to any agent that supports MCP, giving it the ability to reason about the runtime behavior of a program:</p><figure><img alt="Claude Code reasoning about information retrieved from the UDB MCP server." src="https://cdn-images-1.medium.com/max/1024/1*qnfFdlQJ1Gl4UoJuPclIqw.png" /></figure><p>The explain command provides an AI assistant within the debugger yourself – you can ask it questions about the code and its behavior, interleaved with your normal debugging commands.</p><p>This workflow makes use of an external coding agent (currently it supports Claude Code via <a href="https://docs.anthropic.com/en/docs/claude-code/sdk">its SDK</a> but we expect to support others in the future). Behind the scenes, the command activates an MCP server and passes connection details, plus your question, to the coding agent — it can then make full use of its configured tools, including UDB itself, to assist you.</p><figure><img alt="The explain command reasoning about a question the user asked, within the UDB debugger." src="https://cdn-images-1.medium.com/max/1024/1*SySSX4X1JnnP7PlOKtxCoQ.png" /></figure><p>In either case, the extension supplies something resembling conventional debugger functionality to the LLM. To help the LLM get the best possible value from these we’ve restricted the commands available and modified them slightly to impedance match better with the AI’s expectations.</p><p>This produces some impressive results — explain can solve some bugs and provide helpful assistance in understanding others, though like any AI it can make mistakes. However, because it’s a human-like interface we still consider this a <em>shallow</em> integration. Making the full power of time travel debugging available to an LLM needs a <em>deep</em> match between the strengths of the two technologies.</p><h3>What we’re building next</h3><p>The real value in combining time travel debugging with AI is going to be unlocked as we integrate more and more deeply with the LLM’s capabilities.</p><p>Interactive debuggers are a human-centric user interface — they provide a precise interface for moving through program execution and inspecting the detailed state of the program. We don’t think that’s ultimately going to be the most powerful interface for an LLM.</p><p>Modern AIs excel at digesting and sifting text (often using separate agents to offload some processing and while preserving their context windows) so we’re working towards a query-oriented interface that’s specialized for LLM use. The tricky question is: given a recording of <em>everything</em> the program did, what are the most valuable queries we can provide?</p><p>That’s what we’re working on right now. The early signs here are very positive — we’re already seeing more successful investigation of real world bugs, with lower token costs. We’re looking forward to sharing more details in the near future.</p><h3>Conclusions</h3><p>The capabilities of time travel debugging mesh well with the abilities of code assistants: you can capture the complete history of a program and then use AI to help sift through for the interesting parts.</p><p>The explain extension makes it possible to get AI assistance within a debug session, automating routine parts of the work and suggesting theories. Please let us know what you’re able to do with it, or if you have suggestions!</p><p>We’re going to keep working to create an AI-native interface to time travel debugging, to make code assistants smarter and more efficient — watch this space.</p><p>In the meantime, why not try Undo for free?</p><blockquote><a href="https://undo.io/udb-free-trial/">https://undo.io/udb-free-trial/</a></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=40d5a065733b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comparison of ThreadSanitizer and Thread Fuzzing]]></title>
            <link>https://undo-bytes.medium.com/comparison-of-threadsanitizer-and-thread-fuzzing-55408142e12b?source=rss-bda2af557098------2</link>
            <guid isPermaLink="false">https://medium.com/p/55408142e12b</guid>
            <category><![CDATA[sants]]></category>
            <category><![CDATA[debugging]]></category>
            <category><![CDATA[thread-sanitizer]]></category>
            <category><![CDATA[multi-threaded]]></category>
            <category><![CDATA[fuzzing]]></category>
            <dc:creator><![CDATA[Undo Bytes]]></dc:creator>
            <pubDate>Mon, 12 May 2025 20:35:46 GMT</pubDate>
            <atom:updated>2025-05-12T20:35:46.670Z</atom:updated>
            <content:encoded><![CDATA[<p><strong><em>Author: </em></strong><em>Gareth Rees, Senior Software Engineer at Undo</em></p><p>ThreadSanitizer and Thread Fuzzing are two tools for detecting data races in multi-threaded code. This article compares the tools.</p><h3>ThreadSanitizer</h3><p><a href="https://clang.llvm.org/docs/ThreadSanitizer.html">ThreadSanitizer</a> is a compiler extension, originally developed by Google, that has been added to various compilers, including Clang/LLVM and GCC, where it can be enabled using the -fsanitize=thread option.</p><p>It works by maintaining, for each word of memory allocated by the program, a set of <em>N</em> “shadow words”, where <em>N</em> is 2, 4, or 8, depending on the configuration (the larger the set of shadow words, the more accurate the analysis but the greater the memory overhead). The shadow words represent recent accesses to the word, or parts of the word, and are managed using random eviction. Each shadow word contains the thread that accessed the memory, the “epoch” of the access, the bytes accessed, and whether the access was read or write. The epoch is a global counter which is incremented when there is a synchronization between threads.</p><p>When the -fsanitize=thread option is provided, the compiler turns every access to memory by the compiled program into a call to a runtime function that updates the shadow words, and identifies cases where two threads accessed the same bytes in the same epoch. See the <a href="https://github.com/google/sanitizers/wiki/ThreadSanitizerAlgorithm">ThreadSanitizer algorithm</a> documentation for details.</p><h3>Thread Fuzzing</h3><p><a href="https://docs.undo.io/ThreadFuzzing.html">Thread Fuzzing</a> is a feature of Undo’s <a href="https://docs.undo.io/Glossary.html#term-LiveRecorder">LiveRecorder</a> product. LiveRecorder records the runtime behavior of a program and saves it as an Undo recording so that it can later be replayed in a debugger. LiveRecorder allows only one thread to run at a time, by taking a lock in that thread, and letting all other threads block on the lock, but it regularly releases the lock to give other threads the opportunity to claim it and run.</p><p>When Thread Fuzzing is enabled, LiveRecorder varies the timing with which the lock is released and other threads may run. There are several fuzzing strategies which can be configured: see the <a href="https://docs.undo.io/ThreadFuzzing.html#fuzzing-modes">fuzzing modes</a> documentation for details.</p><h3>Comparison</h3><h4>Build configuration</h4><p>ThreadSanitizer requires a special build configuration, since the program must be compiled with the -fsanitize=thread option.</p><p>Thread Fuzzing can be applied to any program, and does not require a special build configuration.</p><h4>Runtime configuration</h4><p>ThreadSanitizer allocates the shadow words at locations determined only by the address of the original words, so that no memory accesses are required to find the shadow words. This means that it may not run under Address-Space Layout Randomization (ASLR), resulting in runtime failures like this:</p><p>FATAL: ThreadSanitizer: unexpected memory mapping 0x6395c9526000-0x6395c9527000</p><p>If this affects your program, you need to run it with ASLR disabled, for example, by running the program under setarch --addr-no-randomize, or by calling personality() with ADDR_NO_RANDOMIZE.</p><p>Thread Fuzzing works both with ASLR and without ASLR.</p><h4>Performance</h4><p>The <a href="https://clang.llvm.org/docs/ThreadSanitizer.html">Clang documentation</a> says that “typical slowdown introduced by ThreadSanitizer is about 5×–15×.”</p><p>The slowdown due to Thread Fuzzing depends on the program. There is a slowdown due to the LiveRecorder recording overhead: this varies according to the type of workload. If the program is mostly working in its own private memory the slowdown may be as low as 1.5×, but if the program makes extensive use of shared memory then the slowdown can be much larger, as much as 50× if all memory is shared. This overhead needs to be multiplied by the slowdown due to serialization of threads, which is roughly proportional to the parallel workload: if the program keeps <em>n</em> threads busy when run natively, then the slowdown under LiveRecorder should be multiplied by <em>n</em>. For example, a program that keeps 4 threads busy, using mainly private memory and a small amount of shared memory, might see a slowdown of 2 (execution overhead) × 4 (threading overhead) = 8 times.</p><h4>Memory Usage</h4><p>The <a href="https://clang.llvm.org/docs/ThreadSanitizer.html">Clang documentation</a> says that “typical memory overhead introduced by ThreadSanitizer is about 5×–10×.”</p><p>The memory overhead introduced by LiveRecorder is usually less than 2×. Thread Fuzzing adds no further memory overhead.</p><h4>Classes of bugs detected</h4><p>ThreadSanitizer reports data races regardless of whether they cause a program failure. This catches races as soon as they occur, instead of waiting for incorrect results to propagate through the program until it crashes or asserts (if it ever does).</p><p>Data races reported by ThreadSanitizer may in some cases be false positives. For example, consider a program in which increment_count() is called from multiple threads:</p><pre>unsigned count = 0<br><br>void<br>increment_count(void)<br>{<br>    ++count;<br>}</pre><p>ThreadSanitizer correctly reports this as a data race since an increment is not an atomic operation, but a read followed by a write. However, if the program uses count only as a <em>lower bound</em> on the number of times that increment_count() was called, then the code is safe. ThreadSanitizer provides mechanisms for suppressing false positives, either by using a <a href="https://github.com/google/sanitizers/wiki/ThreadSanitizerSuppressions">suppression file</a> or by <a href="https://clang.llvm.org/docs/ThreadSanitizer.html#attribute-no-sanitize-thread">annotating the source</a>.</p><p>Thread Fuzzing reproduces data races, locking errors, and deadlocks. It does not reproduce races related to weak memory model semantics (out-of-order updates) on the ARM64 architecture. It does not detect or report errors: it is up to the program to bring data races to the attention of developers, for example, by crashing or asserting. It only reproduces error conditions that can <em>occur</em> in practice: that is, there are no false positives.</p><h4>Interpreting the results</h4><p>ThreadSanitizer emits its findings as reports giving backtraces for the racing threads. For example, consider these functions implementing lockless push and pop operations on a linked list:</p><pre>static void<br>s_push(list_t *item)<br>{<br>    list_t *tmp = __atomic_load_n(&amp;list_head.next, __ATOMIC_ACQUIRE);<br>    /* (A) */ item-&gt;next = tmp;    __atomic_store_n(&amp;list_head.next, item, __ATOMIC_RELEASE);<br>}<br><br>static void *<br>s_pop(void)<br>{<br>   list_t *item = __atomic_load_n(&amp;list_head.next, __ATOMIC_ACQUIRE); <br>   if (!item) <br>   { <br>       return NULL; <br>   } <br>   /* (B) */ __atomic_store_n(&amp;list_head.next, item-&gt;next, __ATOMIC_RELEASE); <br>   return item;<br>}<br><br>static void *<br>s_consumer(void *arg)<br>{ <br>   for (;;) <br>   { <br>       list_t *item = s_pop(); <br>       if (item) <br>       { <br>           free(item); <br>       } <br>    } <br>    return NULL;<br>}</pre><p>When this code is compiled with ThreadSanitizer we get the following report:</p><pre>==================<br>WARNING: ThreadSanitizer: data race (pid=945926)<br>   Read of size 8 at 0x72040008b1e0 by thread T2:<br>     #0 s_pop examples/linked-list.c:65 (linked-list+0x1401)<br>     #1 s_consumer examples/linked-list.c:117 (linked-list+0x1543)<br>  <br>  Previous write of size 8 at 0x72040008b1e0 by thread T1: <br>    #0 s_push examples/linked-list.c:43 (linked-list+0x1388) <br>    #1 s_producer examples/linked-list.c:100 (linked-list+0x1508)<br>  <br>  Location is heap block of size 16 at 0x72040008b1e0 allocated by thread T1: <br>    #0 malloc src/libsanitizer/tsan/tsan_interceptors_posix.cpp:665 (libtsan.so.2+0x54b3f) <br>    #1 s_producer examples/linked-list.c:99 (linked-list+0x14f8) <br>  <br>  Thread T2 (tid=945929, running) created by main thread at: <br>    #0 pthread_create src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1022 (libtsan.so.2+0x5ac1a) <br>    #1 main examples/linked-list.c:146 (linked-list+0x1651) <br>  <br>  Thread T1 (tid=945928, running) created by main thread at: <br>    #0 pthread_create src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1022 (libtsan.so.2+0x5ac1a) <br>    #1 main examples/linked-list.c:145 (linked-list+0x1634)<br><br>SUMMARY: ThreadSanitizer: data race examples/linked-list.c:65 in s_pop<br>==================</pre><p>The developer reading the report must use pure deduction to figure out the cause of the race. In this case it is not too hard to see that the write of item-&gt;next at (A) is racing with the read of item-&gt;next at (B) and that a lock is needed to avoid interleaving of pushes and pops. However, in more complex cases it may not be so easy to deduce the cause.</p><p>Thread Fuzzing does not detect or report data races, but LiveRecorder saves an Undo recording of the program’s behavior, which allows a failure to be reproduced by replaying an instruction-precise trace of the program’s behavior, allowing all of memory to be inspected at any point in time. In the example below, the program crashes with a segmentation fault and live-record saves an Undo recording:</p><pre>$ live-record --thread-fuzzing ./linked-list<br>live-record: Termination recording will be written to <br>linked-list-968476-2025-03-18T16-40-45.616.undo<br>live-record: Maximum event log size is 1G.<br>live-record: Saving to <br>linked-list-968476-2025-03-18T16-40-45.616.undo ...<br>live-record: Saving.. 100%<br><br>live-record: Termination recording written to <br>linked-list-968476-2025-03-18T16-40-45.616.undo<br>live-record: Detaching...<br>Segmentation fault (core dumped)</pre><p>We can load the Undo recording into UDB:</p><pre>$ udb -q linked-list-968476-2025-03-18T16-40-45.616.undo<br>0x00005bad0e0f6120 in _start ()<br><br>The debugged program is at the beginning of recorded history. Start debugging<br>from here or, to proceed towards the end, use: <br>continue - to replay from the beginning <br>ugo end - to jump straight to the end of history</pre><p>The crash is at the end of history, so we jump there:</p><pre>start 1&gt; ugo end<br>[New Thread 968476.968506]<br>[New Thread 968476.968507]<br>[Switching to Thread 968476.968507]<br>0x00005bad0e0f625e in s_pop () at examples/linked-list.c:65<br>65 __atomic_store_n(&amp;list_head.next, item-&gt;next, <br>__ATOMIC_RELEASE);<br>end 15,289,385&gt; bt<br>#0 0x00005bad0e0f625e in s_pop () at examples/linked-list.c:65<br>#1 0x00005bad0e0f632d in s_consumer (arg=0x0) at <br>examples/linked-list.c:117<br>#2 0x0000759b4b49caa4 in start_thread (arg=&lt;optimised out&gt;) at <br>./nptl/pthread_create.c:447<br>#3 0x0000759b4b529c3c in clone3 () at <br>../sysdeps/unix/sysv/linux/x86_64/clone3.S:78</pre><p>The reason for the crash is that item points at unmapped memory:</p><pre>end 15,289,385&gt; p item-&gt;next<br>Cannot access memory at address 0x759c1db41545<br>end 15,289,385&gt; p item<br>$1 = (list_t *) 0x759c1db41545<br>end 15,289,385&gt; p *item<br>Cannot access memory at address 0x759c1db41545</pre><p>We can run backwards to see how item got this bad value:</p><pre>end 15,289,385&gt; last item<br>Searching backward for changes to 0x759b4a98ee88-0x759b4a98ee90 for the<br>expression: <br>  item<br><br>Thread 3 &quot;linked-list&quot; hit Hardware watchpoint -25: *(list_t * *) 0x759b4a98ee88<br><br>Was = (list_t *) 0x759c1db41545<br>Now = (list_t *) 0xffffffffffffff88<br>0x00005bad0e0f6248 in s_pop () at examples/linked-list.c:58<br>58 list_t *item = __atomic_load_n(&amp;list_head.next, <br>__ATOMIC_ACQUIRE);<br>end 15,289,385&gt; p list_head.next<br>$3 = (struct list *) 0x759c1db41545<br>end 15,289,385&gt; p *list_head.next<br>Cannot access memory at address 0x759c1db41545</pre><p>The bad value was loaded from list_head.next. Running backwards again to find out how this value became bad:</p><pre>end 15,289,385&gt; last list_head.next<br>Searching backward for changes to 0x5bad0e0f9030-0x5bad0e0f9038 for the<br>expression: <br>  list_head.next<br>Enable debuginfod for this session? (y or [n]) n<br><br>Thread 3 &quot;linked-list&quot; hit Hardware watchpoint -28: *(struct list * *) 0x5bad0e0f9030<br><br>Was = (struct list *) 0x759c1db41545<br>Now = (struct list *) 0x759b44005190<br>s_pop () at examples/linked-list.c:65<br>65 __atomic_store_n(&amp;list_head.next, item-&gt;next, __ATOMIC_RELEASE);<br>99% 15,289,375&gt; bt<br>#0 s_pop () at examples/linked-list.c:65<br>#1 0x00005bad0e0f632d in s_consumer (arg=0x0) at examples/linked-list.c:117<br>#2 0x0000759b4b49caa4 in start_thread (arg=&lt;optimised out&gt;) at ./nptl/pthread_create.c:447<br>#3 0x0000759b4b529c3c in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:78<br>99% 15,289,375&gt; p item<br>$4 = (list_t *) 0x759b44005190<br>99% 15,289,375&gt; p *item<br>$5 = {next = 0x759c1db41545, data = 0x0}<br>99% 15,289,375&gt; p *item-&gt;next<br>Cannot access memory at address 0x759c1db41545</pre><p>Running backwards a third time:</p><pre>99% 15,289,375&gt; last item-&gt;next<br>Searching backward for changes to 0x759b44005190-0x759b44005198 for the<br>expression: <br>  item-&gt;next<br><br>Thread 3 &quot;linked-list&quot; hit Hardware watchpoint -36: *(struct list * *) 0x759b44005190<br><br>Was = (struct list *) 0x759c1db41545<br>Now = (struct list *) 0x759b44005270<br>0x0000759b4b4ab2f6 in _int_free (av=0x759b44000030, p=&lt;optimised out&gt;, have_lock=0) at ./malloc/malloc.c:4619<br>4619 ./malloc/malloc.c: No such file or directory.<br>99% 15,283,100&gt; bt<br>#0 0x0000759b4b4ab2f6 in _int_free (av=0x759b44000030, p=&lt;optimised out&gt;, have_lock=0) <br>     at ./malloc/malloc.c:4619<br>#1 0x0000759b4b4addae in __GI___libc_free (mem=0x759b44005190) at ./malloc/malloc.c:3398<br>#2 0x00005bad0e0f634b in s_consumer (arg=0x0) at examples/linked-list.c:128<br>#3 0x0000759b4b49caa4 in start_thread (arg=&lt;optimised out&gt;) at ./nptl/pthread_create.c:447<br>#4 0x0000759b4b529c3c in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:78</pre><p>So the crash was caused by a use-after-free and we are at the location of the free in the debugger ready for further investigation of the cause.</p><h3>Conclusion</h3><p>Each tool has its own strengths and weaknesses. ThreadSanitizer detects data races directly, which is effective if the program omits to check the consistency of its own data structures. Thread Fuzzing varies the scheduling of threads, which can be effective when a data race occurs only under unusual conditions, and produces an Undo recording that makes it possible to reproduce the failure in a debugger, which is effective when the cause of a race is complex.</p><p>It makes sense to use both tools and take advantage of each one in the cases where it is best suited. For example, you might run the product first under ThreadSanitizer and fix the easy-to-reproduce races, then run the product under Thread Fuzzing to vary the scheduling of threads and discover hard-to-reproduce races, or capture recordings of races which can’t be solved by pure deduction from the ThreadSanitizer report.</p><p>Interested in trying Thread Fuzzing? You can sign up for a free trial of Undo using the button below.</p><h4><a href="https://undo.io/udb-free-trial">Try now</a></h4><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=55408142e12b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Common Sense Guide to Symbols and Debug Info]]></title>
            <link>https://undo-bytes.medium.com/a-common-sense-guide-to-symbols-and-debug-info-6f9f3cc65045?source=rss-bda2af557098------2</link>
            <guid isPermaLink="false">https://medium.com/p/6f9f3cc65045</guid>
            <dc:creator><![CDATA[Undo Bytes]]></dc:creator>
            <pubDate>Thu, 27 Mar 2025 18:03:59 GMT</pubDate>
            <atom:updated>2025-03-27T18:03:59.146Z</atom:updated>
            <content:encoded><![CDATA[<p>In this blog, Isa Smith, Staff Software Engineer, explains the difference between symbols and debug info, and suggests some ways to track down your “debug symbols”.</p><p>This isn’t a comprehensive guide to anything, and doesn’t talk about the innards of DWARF, ELF, binutils, GDB. It’s intended to be a friendlier guide than the many more rigorous guides.</p><p>Two essential resources for the absolute truth are:</p><p><a href="https://sourceware.org/gdb/current/onlinedocs/gdb.html/Separate-Debug-Files.html">https://sourceware.org/gdb/current/onlinedocs/gdb.html/Separate-Debug-Files.html</a></p><p><a href="https://gcc.gnu.org/wiki/DebugFission">https://gcc.gnu.org/wiki/DebugFission</a></p><p>The information here isn’t specific to <a href="https://undo.io/">Undo’s</a> tools, except for a couple of mentions and a section at the very end. The impact on Undo’s tools is nearly identical to live debugging or loading a core file, so although you see <a href="https://undo.io/products/udb/">UDB</a> in the screenshots, this is just as relevant for plain GDB.</p><h3>Who is this for?</h3><p>Anyone who has encountered this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/417/1*VfzK_KMt5-HvrAQl3d1WKA.png" /></figure><p>More specifically, you’ll get this when you’re trying to debug a stripped binary: one without debug info or symbols. You’re most likely to encounter this when you’ve built on one machine then run the binary on another. Four cases where this may happen are:</p><ul><li>Normal interactive debugging on a remote host</li><li>Debugging a core file generated somewhere else</li><li>Using gdbserver on a remote host</li><li>Debugging an Undo recording</li></ul><h3>What are “symbols”? What is “debug info”? What are “debug symbols”?</h3><p>Put simply, symbols are the names and addresses of functions and variables in your program. Debug info is all the extra information needed to tie your machine code to your source code. For example, line information (what machine code addresses correspond to this source line) or local variable info (what register is this variable in when we are at this address?).</p><p>Symbols are included in executables by default. Debug info is not, and you need to pass -g to the compiler to get it.</p><p>If you try to debug a fully stripped program, your backtraces will look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/417/1*VfzK_KMt5-HvrAQl3d1WKA.png" /></figure><p>Most stack frames show only a disassembly address, with the occasional symbol name scattered in.</p><p>If you only have symbols, it’s better, but it’s still not going to give you a good experience. You can see what function you’re in and set some more useful breakpoints, but source code debugging is unavailable:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wFM4YShdjOLFXgBzsbTZyA.png" /></figure><p>If you have symbols and debug info, you’re in the happy place:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zMPVryfJ421IqdipZHrl1g.png" /></figure><p>I don’t have a strict definition for the phrase “debug symbols”, as it’s not a strictly defined concept. It seems to refer to “everything you need for debugging” — that being both symbols and debug info. You can’t actually have debug info without symbols, so it’s a slightly odd phrase. I suspect people use it because for a developer hoping to debug a program, the difference between symbols and debug info is pretty irrelevant. You really need both. Unless you can debug compiled machine code as easily as source code… in which case you have achieved oneness with the computer and you don’t need to be here. Well done.</p><h3>Why is this happening to me?</h3><p>To answer this, let’s first say what has happened. The debug information and/or symbols for your program have been removed (or stripped) from the binary on the system you’re trying to debug on. This can happen at compile time or as a post-processing step after compilation.</p><p>There are some very good reasons people choose to do this:</p><ol><li>Size</li><li>Secrecy</li></ol><p>On size, consider the gdb binary itself</p><p>Unstripped : 288MB</p><p>Debuginfo stripped : 14MB</p><p>Symbols and debuginfo stripped : 12MB</p><p>If you add this size to the sizes of any shared objects, you can end up with a very large project, and you almost can guarantee that an end user somewhere will have a slow enough data transfer to be annoyed by all this useless information.</p><p>Note that I’m only talking about disk space here. Neither symbols nor debug info are loaded into memory when the program executes. They’re not needed at run time so would be a waste of RAM. An exception to this is dynamic symbols in the .dynsym and .dymstr sections. These are needed at runtime by the dynamic linker. This is why some symbol names still appear (and are breakpointable) in a fully stripped binary.</p><p>Symbol names take a lot less space, but they do give away information about how your program works. It’s much more difficult to decipher any meaning from a large amount of totally context-free machine code.</p><p>Some vendors (including Undo) choose to include symbols and debug information in their shipped binaries. In our case the debug info is relatively small and nothing valuable can be inferred from symbol names or filenames. More importantly, we find being able to debug our code on a remote site to be very useful. The cost-benefit analysis is in favor of shipping everything.</p><h3>Where are my symbols?</h3><p>Ideally this is a question to ask your build system team. They may have a really great answer (more in a moment). However, this isn’t always possible. Maybe they don’t respond quickly, or you don’t want to bother them, or they have other priorities at the moment. I’ve had to sniff out debug information from a fair number of customer environments, so I’ll share how I do it.</p><p>Note: It’s worth knowing that debug information is in a format called DWARF, which is composed largely of offsets. Everything is defined as an offset from everything else. For this reason you need the <strong>exact</strong> debug info for your binary. Anything “close” just won’t work, at all, and will waste your time.</p><p>It’s also possible that you don’t have any symbols or debug info anywhere in your build. It’s usually <em>somewhere</em>, but this is another good way to waste time.</p><p>Caveats aside, if you’re sure you’ve got the same build that your debugged process came from, let’s have a look.</p><p>The first step is to see if there’s an unstripped version of the binary in the build tree. Use find &lt;build tree&gt; -name “mybinary”, and then file to check.</p><p>If you see this output:</p><p>$ file mybinary_unstripped</p><p>mybinary_unstripped: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1d904bc819c5b7030ec0e2b54ee6034acbea4194, for GNU/Linux 3.2.0, <strong>with debug_info, not stripped</strong></p><p>This is ideal and can be used for debugging.</p><p>If your program doesn’t include any shared objects, you’re done. If it does, you need to find the debug symbols for them too if you want to debug them. I suggest finding the debug info for a couple of shared objects: that should give you enough information about the structure of the build. I’ll explain what to do with this in a moment.</p><p>If you see this output:</p><p>$ file mybinary_strippeddebug</p><p>mybinary_strippeddebug: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1d904bc819c5b7030ec0e2b54ee6034acbea4194, for GNU/Linux 3.2.0, <strong>not stripped</strong></p><p>$ file mybinary_fullystripped</p><p>mybinary_fullystripped: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1d904bc819c5b7030ec0e2b54ee6034acbea4194, for GNU/Linux 3.2.0, <strong>stripped</strong></p><p>These mean you still have hunting to do.</p><p>Try searching the project for *.debug, *.dwo, *.dwp files. These are various different ways that separate debug files can be stored. In many cases, there will be a link embedded in the stripped binaries (either the .gnu_debuglink section or the .debug_str section for DWO) which gdb will be able to follow and load the corresponding symbol file.</p><p>If you haven’t found anything yet, it’s possible your project isn’t built with debug info, in which case you’re back to speaking to your build team or investigating your build infrastructure code. Look for “strip”, “objcopy” and “split-dwarf”.</p><p>If you’ve managed to find some debug info, great! The next step is to tell GDB about it.</p><h3>Loading the symbols</h3><p>GDB has many commands for loading symbol files once you’ve located them, for example set debug-file-directory, set solib-search-path, symbol-file, add-symbol-fileetc.. Unfortunately, each command is designed for a different scenario, and it’s not obvious which command applies in which scenario.</p><p>Rather than going through the scenario each command goes with, I will introduce the multi-tool of symbol loading: add-symbol-file.</p><p>This isn’t always the simplest way, but it is the most reliable. It can cope with everything from a simple executable to more complex and surprising situations.</p><p>The command works like this:</p><p>add-symbol-file symbolfile -o offset</p><p>You get the offset from the memory map of the process. In GDB, use info proc mappings – you need the lowest memory address of the library or executable you’re debugging. For example, if you are loading the symbols for libcool.so in this output:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WC3ExnPYQ04nFnD5lCtEFA.png" /></figure><p>Supposing your debug symbols were at /home/work/ismith/project/build/dbg/libcool. so you would use:</p><p>add-symbol-file /home/work/ismith/project/build/dbg/libcool.so -o 0x7ffff7f29000</p><h3>Can’t I just debug the unstripped binary?</h3><p>Yes. If this is an option for you, you can absolutely debug the unstripped binary. If you want to debug on-target you’ll need the source code as well. This may not be an option if:</p><ul><li>There isn’t enough disk space on a small target device</li><li>You’re not allowed to put symbols/source there — e.g. don’t copy your secret project to AWS</li></ul><p>But if it is an option, go for it. As previously mentioned, debug info is neither required nor used in any way by a running process. It will not affect its behavior — on the other hand, attaching a debugger to it will massively affect its timing behavior which may cause problems. This isn’t due to the debug info though.</p><h3>I looked for the debug builds and I found dbg, rel, dbgrel, dbgopt, reldbg, relwithdbginfo, which one do I use?</h3><p>It’s common to have a lot of different build targets in a project. Many build systems provide several targets by default and your project may have built on these. This is because there are some orthogonal choices when making a build:</p><ol><li>Build with or without debug info (-g options)</li><li>What optimization level to use (-O options)</li><li>Project-specifc “debug” modes by defining DEBUG or similar (-D compiler options). Might enable extra checking or extended error messages for developers.</li></ol><p>If you have lots of build targets then you probably have a partial (or even full) cross product of these options. For best debugging, you want low optimization and debug info, and probably with as many -DDEBUG type options as you can. With high optimization levels, some source code lines have no corresponding machine code, and others will have machine code reordered to be most efficient. So when debugging, you’ll encounter optimized out variables (https://undo.io/resources/value-optimized-out-reverse-debugging-rescue/) and experience the current location jumping around when you step.</p><p>Often “debug builds”, with 2 and 3, are too slow to be used for anything large, so you’ll have to debug optimized code. This is painful to begin with but there are certain tricks and reflexes you’ll learn that make it pretty easy. Then debugging low optimization code becomes a special treat.</p><p>If you’re using UDB, the ugo undo or uu command is very useful, as it takes you back to your previous place. So if a next took you too far, you can quickly go back and try a step instead, or even stepi through the assembly code.</p><h3>Symbol files and Undo</h3><p>The issue of locating and loading separate debug symbols is a subject close to our hearts at Undo. Our technology records processes in a minimally invasive way — this means we can, for example, record a critical process on a network switch without interfering with its operation. In such a scenario we almost always record a release build with separate debug symbols. Since the replay part of the technology is a debugger (UDB) we absolutely need to find those symbols and get them loaded.</p><p>On the other hand, if the symbols are available on the device, Undo (LiveRecorder or UDB) will find them and add them to the recording. At replay time, Undo constructs a sysroot inside /tmp and uses set sysroot to point to it.</p><p>All of our customers have complex environments, and many have to deal with separate symbols/debug info. We offer a “Debug Info Service” which can serve symbols via debuginfod, or we can work with you to use existing scripts or mechanisms with Undo for a <strong>one-step load and debug experience.</strong></p><p>Interested in finding out more? Book a demo: <a href="https://calendly.com/undo-time-travel-debugging/30min">https://calendly.com/undo-time-travel-debugging/30min</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6f9f3cc65045" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Making C++ Safer]]></title>
            <link>https://undo-bytes.medium.com/making-c-safer-34bb9357815e?source=rss-bda2af557098------2</link>
            <guid isPermaLink="false">https://medium.com/p/34bb9357815e</guid>
            <category><![CDATA[developer-tools]]></category>
            <category><![CDATA[engineering]]></category>
            <dc:creator><![CDATA[Undo Bytes]]></dc:creator>
            <pubDate>Thu, 27 Feb 2025 10:51:05 GMT</pubDate>
            <atom:updated>2025-02-27T10:51:05.409Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vMNaW6uvim4PMZX-geZE8Q.png" /></figure><p>Would you start a new project in C++ these days? Many people would say no, they’d use something else; usually they cite Rust. Or Go, or Python or anything “memory safe”, which these days effectively means anything that isn’t C or C++.</p><p>A few years ago this kind of discussion was for language nerds hanging out on Hacker News; now it’s about geopolitics! States are working ever harder to hack each others’ systems, and the majority of critical infrastructure code is written in C++ (or plain C), and this can be a gift to hackers. The US government is now urging programmers to drop C and C++. There were some very misleading headlines recently, saying <em>“Whitehouse mandates companies stop using C++ by 2026”</em>. Of course, they didn’t say this, because it would be nuts — they actually strongly encouraged software companies to <em>produce a memory safety roadmap</em> by 2026. The argument around should we use C++ or not is moot — there are an estimated <a href="http://www.tomazos.com/howmuchcpp.pdf">10 billion lines of C++ code</a> in production today; rewriting it all will take decades.</p><p>I believe that over time C++ will become a lot saf<em>er</em>, maybe even some kind of ‘safe’. Competition is good: Clang was the best thing to happen to GCC, and Rust might turn out to be the best thing to happen to C++. That journey has already begun, with proposals for the evolution of the language including <a href="https://isocpp.org/files/papers/P2900R6.pdf">Contracts</a> and <a href="https://github.com/BjarneStroustrup/profiles">Profiles</a>, and simply changing some of the defaults in C++26. While the language custodians work to make the language itself safer, what can you do today?</p><h3><strong>Follow the Core Guidelines</strong></h3><p>Even today, most C++ doesn’t need to be nearly as unsafe as it is. If everyone had followed the existing <a href="https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines">Core Guidelines</a>, we’d already be in much better shape. The guidelines are a set of simple rules which, if followed, make C++ a lot safer, as well as easier to read and reason about, and more consistent. They are maintained by Bjarne Stroustrup and Herb Sutter, and they cover issues such as interfaces, resource and memory management, and concurrency. For example: RAII (Resource Allocation Is Initialization) can be used to make many simple resource safety mistakes, such as a leak or use-after-free, no longer possible. When writing or reviewing new code, or changing existing code, follow them. As discussed however, we can’t just rewrite all that code, so how do we live with all that existing code that doesn’t follow the guidelines?</p><h3><strong>Bounds checking by default</strong></h3><p>There is simply no excuse for many (most?) of the safety violations we see today: stupid out-of-bounds errors accessing arrays, vectors, strings, etc. Google recently <a href="https://security.googleblog.com/2024/11/retrofitting-spatial-safety-to-hundreds.html">published a blog</a> where they report enabling bounds checking and other hardening resulted in a <em>performance impact of just 0.3%</em>. Just enable such bounds checking by default. See <a href="https://libcxx.llvm.org/Hardening.html">here</a> for how to do so with Clang/LLVM, and <a href="https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Options-Hardening-Guide-for-C-and-C++.html#precondition-checks-for-c-standard-library-calls">here</a> for GCC. If you determine by profiling that some performance-critical piece of code is being materially slowed down by this, you can always disable the bounds checking for that one bit of problematic code. (My bet is that you won’t though.) Chances are that C++26 will enable such bounds checking by default anyway, but why wait?</p><h3>Stop ignoring those flaky tests</h3><p>Those flaky test failures are telling you something, whether or not you’re writing in a memory safe language, but with memory unsafe languages a good proportion of them will be memory errors. This is actually part of something bigger — memory safety is just one kind of vulnerability, and the same concerns that are driving the memory safety debate are affected by undiagnosed, flaky tests.</p><p>Fixing a flaky-test problem takes commitment, but it’s well worth it. Engineers from Google have written a lot on it, as we at Undo published a handy <a href="https://undo.io/resources/7-steps-fix-flaky-tests/">7-step guide</a>.</p><h3>Use the tools: sanitizers</h3><p>Use the sanitizers, particularly ThreadSanitizer, AddressSanitizer, and Undefined Behavior Sanitizer. Your CI should run a complete run of the tests with sanitizers enabled. While this won’t catch all memory safety problems, it will catch many of them. Hunt down all the failures, be very wary of dismissing them as false positives. (See <em>flaky tests</em> above.)</p><p>They’re super easy to use these days — you just need to pass the option -fsanitize=address, -fsanitize=thread or -fsanitize=undefined when you compile (either gcc or clang), and that’s it. There are lots of options you can tweak, but to get started, the defaults work fine. See <a href="https://blog.trailofbits.com/2024/05/16/understanding-addresssanitizer-better-memory-safety-for-your-code/">this article</a> for more details.</p><h3>Use the tools: static analyzers</h3><p>Many of the memory safety bugs that lead to security vulnerabilities can be identified by modern static analysis. Even the best static analyzers suffer from generating false positives, and if you haven’t run them before the list of warnings can be daunting. The good ones will help you prioritize which ones to look into first and/or can be configured to only show you issues in new/changed code. Static analysis is one of those tooling categories where if you want quality you need to pay for it — the cost of developing and maintaining them mean it’s difficult for traditional open source models to work. <a href="https://www.blackduck.com/static-analysis-tools-sast/coverity.html">Coverity</a>, <a href="https://www.perforce.com/products/klocwork">Klocwork</a> and <a href="https://www.sonarsource.com/products/sonarqube/">SonarQube</a> are popular options. If you want to start with open source, you could try <a href="https://github.com/danmar/cppcheck">cppcheck</a>.</p><h3>Use the tools: fuzzing</h3><p>Fuzzing typically uncovers all kinds of edge cases you didn’t think about. Many of them will be safety concerns. Like static analysis, you can get a bewildering number of failures, but if you run using fuzzing and time travel debugging it is usually pretty trivial to root cause and fix the issue when you can step back through a recording.</p><p>There are good free and commercial offerings. For example, Google’s <a href="https://github.com/google/fuzztest">FuzzTest</a> is free, flexible and fairly easy to use. It <em>will</em> find bugs in your code that you didn’t know about. It is built on top of AddressSanitizer (see above). On the commercial side, BlackDuck’s <a href="https://www.blackduck.com/fuzz-testing/defensics.html">Defensics</a> is very good, as is Code Intelligence’s <a href="https://www.code-intelligence.com/">CI-Fuzz</a>.</p><h3>Stop papering over the cracks</h3><p>When you smell smoke, you act. If a test is failing or a bug is reported and a small change makes it work but you don’t understand why, spend the time to do the root-cause analysis. When your code is behaving in a way you don’t understand, it’s telling you something. If you don’t keep pulling on that thread to properly understand it, chances are that you are leaving a safety vulnerability that may later be discovered by malign actors — or maybe it already has been!</p><p>Again, a <a href="https://undo.io/resources/thread-fuzzing-wild/">time travel debugger</a> can make this much easier.</p><h3>Greater than the sum of its parts</h3><p>Finally, do all of the above <em>together</em>. Sanitizers and fuzzing are a powerful combination, and when combined with time travel debugging you’d be surprised at how quickly you can squash all of the problems you find, one by one. When fuzzing throws up an error sometimes the root cause is obvious (you forgot to sanitize that user input), but other times the reason for the bad behavior is anything but obvious — combining fuzzing with sanitizers can really help narrow it down, and/or feed the fuzz failure into a time travel recording.</p><h3>Conclusion</h3><p>Buggy code is unsafe. Code we don’t understand is unsafe. Memory unsafe languages like C++ make us especially vulnerable, so when using them we must compensate. However, it is important to remember that memory safe languages are no panacea. If you use good software engineering practices and make best use of the tooling available, you will produce safer, better code, whatever language you’re using. In fact, a C++ codebase managed by a team that does all this is likely to be safer than the equivalent Rust by a team that ignores it. And not just safer, but also more pleasant and productive to work on.</p><h3>Oh, one last thing: AI-generated code</h3><p>No software article these days is complete without a reference to AI-generated code. AI can be a boon for developer productivity. But without care, it can be a nightmare for safety. Lots of AI-generated code without good practices and tooling threatens to be a recipe for disaster. Particularly if you’re writing systems code, then it is incumbent on you fully to understand the code written by you, or the AI on your behalf. Just staring at it and then accepting is not enough — you must use thorough testing, fuzzing, sanitizers, and most importantly make sure that you fully understand what the code is really doing.</p><p>I’d love to know your thoughts on the topic of C++ safety. Please connect and message me on <a href="https://www.linkedin.com/in/gregthelaw">LinkedIn</a> to let me know.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=34bb9357815e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[“How Did This Variable Get That Value?”]]></title>
            <link>https://undo-bytes.medium.com/how-did-this-variable-get-that-value-1585a0bc1165?source=rss-bda2af557098------2</link>
            <guid isPermaLink="false">https://medium.com/p/1585a0bc1165</guid>
            <category><![CDATA[cpp]]></category>
            <category><![CDATA[cplusplus]]></category>
            <dc:creator><![CDATA[Undo Bytes]]></dc:creator>
            <pubDate>Fri, 20 Jan 2023 16:30:43 GMT</pubDate>
            <atom:updated>2023-01-20T16:30:43.554Z</atom:updated>
            <content:encoded><![CDATA[<p>It’s quite common when diagnosing an issue in an application to come across an unexpected value in a variable or data structure, and to wonder: <em>“How did that value get there?”</em></p><p>With time travel debugging, it is now possible to find this out in a quick and efficient way: Undo’s LiveRecorder recently introduced the lastcommand to help C++ developers answer precisely that question.</p><ul><li>The last command jumps to the last time in execution history when the value of a variable or expression was modified.</li><li>You can keep repeating last to keep jumping back to previous times that the value changed.</li></ul><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FdPVj3hXsD0U%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdPVj3hXsD0U&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FdPVj3hXsD0U%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/5e26171ea9f1b26bbaa4f9f2e960955f/href">https://medium.com/media/5e26171ea9f1b26bbaa4f9f2e960955f/href</a></iframe><p>Learn more about <a href="https://undo.io/solutions/products/live-recorder/">how to save time debugging complex C++ applications with LiveRecorder</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1585a0bc1165" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>