What is Remote Debugging With gdbserver-Free Embedded Linux Course In Hyderabad

PREV_LEC | NEXT_LEC

Remote Debugging With gdbserver

Debug code running on target hardware from your development host — a free embedded Linux course lecture

Most embedded Linux development happens on a cross toolchain: you write and compile code on a powerful host, but the program actually runs on a resource-constrained target board. Native GDB assumes the debugger and the debuggee live on the same machine, which breaks down immediately in this setup. This lecture, continuing our free linux kernel development course chapter on GDB, introduces gdbserver — the standard way to debug target-resident code from your host — and walks through the gotchas that catch people out the first time they try it.

Keywords

gdbserver cross debugging remote debugging strip Yocto Project free embedded linux course

What You Will Learn

  • Why native GDB doesn’t work for typical embedded cross-development
  • The role of gdbserver and how it splits work between target and host
  • Six practical differences between native and remote debugging you need to plan for
  • How the strip tool’s aggressiveness levels affect what you can debug later
  • How to add gdbserver to a Yocto Project target image

Prerequisites

  • Completed the previous lecture on debug build flags (-g, optimization levels)
  • A cross toolchain for your target architecture (e.g. from Yocto or Buildroot)
  • Basic familiarity with flashing/deploying images to embedded target hardware

Native vs Remote Debugging

When you compile and run code on the same machine — desktops, servers, or a self-hosted build — running GDB natively is straightforward: you launch gdb, tell it which binary to load, and it can start, stop, and step the process directly because it’s talking to a process on its own kernel.

Embedded development is different. The target CPU architecture (ARM, RISC-V, MIPS) usually doesn’t match your development host (x86_64), and even when it does, the target often lacks the storage, memory, or tooling to run a full debug session on its own. The practical answer is to split the job in two: a lightweight debug agent runs on the target, and the real GDB — with your source code, your symbols, and a comfortable terminal — runs on the host.

How gdbserver Works

gdbserver is that lightweight target-side agent. It loads and controls execution of the program being debugged, then talks to a full copy of GDB running on your host over a network connection or a serial (RS-232) link. gdbserver itself doesn’t understand source code or symbols — it just executes low-level debug operations (set a breakpoint at this address, read this memory, step one instruction) on behalf of the host GDB, which does all the source-level translation.

Remote Debug Session Architecture

[ Development Host ] [ Embedded Target ] +—————————+ +—————————+ | Source code + Makefiles | | Compiled binary | | Cross-compiled binary | | (no source, no symbols) | | | | | | arm-linux-gnueabi-gdb | | gdbserver :10000 ./app | | (knows symbols, source) | or serial| (executes low-level ops) | +—————————+ +—————————+

Debugging through gdbserver behaves almost, but not quite, like native debugging. Because two separate computers are now involved, there are several practical differences worth knowing before your first session:

  • You must start the target-side program under gdbserver first, then separately launch your cross GDB on the host — the order matters.
  • GDB and gdbserver must connect to each other explicitly before a session begins.
  • Host-side GDB needs to be told where to find debug symbols and source for shared libraries — it has no built-in knowledge of the target’s filesystem layout (covered in the next lecture as “sysroot”).
  • GDB’s run command does not work the way it does natively — the program is already loaded by gdbserver by the time you connect.
  • gdbserver terminates when the session ends, and must be restarted on the target for each new debug session.
  • gdbserver doesn’t support every feature native GDB has — for example, it can’t follow a child process after fork(), something we’ll return to later in this chapter.

Stripping Debug Symbols With strip

Debug symbols can inflate a binary’s size dramatically — sometimes by a factor of ten. You generally want full symbols on your host copy (for GDB to read) but a stripped copy on the target (to save flash/RAM). The strip tool from your cross toolchain controls exactly what gets removed:

OptionEffectWhen to use
--strip-all (default)Removes all symbol table and debug infoNormal applications and shared libraries
--strip-unneededRemoves symbols not needed for relocationKernel modules — --strip-all can break module loading
--strip-debugRemoves only debug info, keeps the symbol tableNiche cases where you still want nm-style symbol lookups on target

The one to remember is the kernel module exception: a default --strip-all pass on a .ko file will typically prevent it from loading, because module loading depends on symbol table entries that --strip-all removes. Use --strip-unneeded for modules instead.

# Correct for a regular application binary
$ arm-linux-gnueabi-strip --strip-all ep_myapp

# Correct for a kernel module — keeps what insmod needs
$ arm-linux-gnueabi-strip --strip-unneeded ep_mydriver.ko

Adding gdbserver to a Yocto Project Image

The Yocto Project builds a cross GDB for your host automatically as part of the SDK, but gdbserver has to be explicitly added to the target image. There are two common ways to do this in your layer’s local.conf or a custom recipe.

Add the package directly (note the required leading space before the package name):

IMAGE_INSTALL:append = " gdbserver"

Or pull in the broader debug tooling feature, which adds both gdbserver and strace in one go:

EXTRA_IMAGE_FEATURES = "debug-tweaks tools-debug"

Note the modern Yocto syntax uses :append rather than the older _append underscore form — recent Yocto releases (Scarthgap and later) require the colon syntax, so check your release’s migration notes if you’re working from older reference material.

Real-World Use Case

A common scenario: a sensor driver works fine on the bench but hangs intermittently once deployed on a battery-powered field unit. You can’t attach a full IDE to that unit, but if gdbserver is already baked into the production debug image (behind a maintenance mode, say), you can SSH in, launch gdbserver --attach on the misbehaving process, and connect your host GDB over the network without ever physically touching the board. We’ll cover the --attach workflow — “just-in-time debugging” — in a later lecture.

Common Mistakes and Troubleshooting

  • Trying run in a remote session — it either errors that the remote target doesn’t support it, or silently hangs on older GDB. Always start the program on the target via gdbserver first.
  • Stripping a kernel module with --strip-all — causes cryptic module load failures; use --strip-unneeded.
  • Mismatched GDB/gdbserver versions — can cause odd protocol behavior; build both from the same toolchain release where possible.
  • Forgetting gdbserver exits after each session — you must relaunch it on the target for every new debug run.

Best Practices

  • Keep a full-symbol copy of every binary on the host; only strip the copy that ships to the target.
  • Always use --strip-unneeded, never --strip-all, on kernel modules.
  • Build GDB and gdbserver from the same toolchain source to avoid protocol mismatches.
  • Bake gdbserver into debug/development images via tools-debug, and exclude it from production images to save space.

Security Considerations

gdbserver listening on a TCP port gives whoever can reach it full control over the target process, including arbitrary memory read/write. Never leave gdbserver reachable on a production network — bind it to a trusted interface only, use it over a serial console, or gate it behind a VPN/maintenance network when debugging deployed field units.

Summary and Key Takeaways

  • Native GDB assumes debugger and debuggee share a machine — embedded cross-development needs gdbserver instead.
  • gdbserver runs on the target and executes low-level operations; the real GDB with symbols and source runs on the host.
  • run doesn’t work as expected remotely, and gdbserver must be restarted for each session.
  • Use --strip-unneeded for kernel modules, --strip-all for regular application binaries.
  • Yocto needs gdbserver explicitly added via IMAGE_INSTALL:append or the tools-debug image feature.

Conclusion

gdbserver is the bridge that makes source-level debugging possible on real embedded hardware — understanding its split-brain model up front saves a lot of confusion the first time run doesn’t behave the way you expect. The next lecture in this free linux device drivers course covers the Buildroot side of this setup and walks through actually connecting GDB and gdbserver over both network and serial links.

FAQ

Can I use gdbserver without a cross toolchain, on a native x86 target?

Yes — gdbserver works the same way even when host and target architectures match; it’s still useful for attaching to processes over a network without disturbing the local terminal session.

Why doesn’t the GDB run command work with gdbserver?

Because gdbserver already loaded and is holding the program at its entry point by the time GDB connects — there’s no “fresh start” to trigger. Use continue instead.

Does gdbserver support every GDB feature?

No — notably it can’t follow a forked child process (only the parent, via follow-fork-mode), a limitation only present in remote sessions.

What happens if I strip a kernel module with –strip-all?

The module typically fails to load — modules depend on certain symbol table entries that –strip-all removes. Use –strip-unneeded for .ko files instead.

Is EXTRA_IMAGE_FEATURES = “tools-debug” safe for a production Yocto image?

Not recommended — it adds gdbserver and strace, both of which give significant introspection/control over the running system. Reserve it for development/debug image variants.

Do GDB and gdbserver have to be the exact same version?

Not strictly required, but mismatched versions can behave oddly or lack feature parity. Building both from the same toolchain source avoids the issue entirely.

Continue the Free Linux Device Drivers Course

Next: setting up Buildroot for gdbserver and connecting over network and serial.

Browse All Lectures Join EmbeddedPathashala
PREV_LEC | NEXT_LEC

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *