Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

      • Red Hat OpenShift AI
      • Red Hat Enterprise Linux AI
    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      Developer Sandbox
      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

How to add debug support for Go stripped binaries

April 3, 2024
Derek Parker Archana Ravindar
Related topics:
CompilersGo
Related products:
Red Hat Enterprise Linux

Share:

    A stripped binary is generally devoid of symbol and debug information. There could be many reasons why one would need to generate a stripped binary. Symbol information takes up a lot of storage space, so when there are space constraints, it makes sense to work with stripped binaries.

    In the case of Go, we can create a stripped binary either by using go build ldflags="-s -w" or by using the strip command. Debugging is the other side of development, and debugging a stripped binary can be challenging. Go is unique, and unlike other languages, we can still hack our way around to obtain some amount of symbol information from the binaries. This article focuses on how we support stripped binaries in Delve, the Go debugger. 

    Finding symbols in stripped binaries

    Go retains some information on symbols even within a stripped binary. This information is encoded in a section of the binary called the “PC Line Table” and can be found in the .gopclntab section in ELF binaries. This table contains metadata for all functions in the final binary, including information for symbolic stack traces and data for the garbage collector such as stack maps. This section remains even after removing DWARF debug information by specifically requesting it via the linker using -ldflags=”-s -w” or using the system strip command.

    Setting breakpoints in stripped binaries

    The first step towards enabling support for stripped binaries is to ensure the basic functionality of a debugger still works. One of those essential pieces is the ability to set a breakpoint using a function name. To do this, the debugger needs to translate the symbol name into an address in memory. One way of achieving this is by using the DWARF debug information which is encoded into the binary in various sections. However, in the case of a stripped binary that DWARF information would not be present. Now, even so, there are potential options for the debugger. If the system supports it, you may download separate debug information manually or by using a service such as debuginfod. If that is not a viable option, then we can instead use the .gopclntab section mentioned in the last paragraph.

    When Delve detects the absence of debug information and is unable to obtain it anywhere else, it will fall back to this section. Whereas Delve has custom parsers for DWARF information, the Go standard library provides us access to the information stored in the .gopclntab section. Using the debug/gosym package and knowledge of where the .gopclntab section lives within the binary, we can translate a function name such as main.main to a memory address, and voila! With that in hand, Delve is able to write the breakpoint instruction to that memory address and we have breakpoints. However we shall see later in this blog that in some cases, not all the available information is available in the .gopclntab section and we will still need to fall back on the DWARF section to get us all the answers. 

    Now, for functions which have not been inlined, this works great. But what about those which the compiler has chosen to optimize and inline? An inlined function presents different and unique problems for a debugger, which are typically easily overcome. Instead of resolving a function to a single memory address, it instead must be mapped to each location the function was inlined into. With the presence of DWARF, this is rather easy. DWARF can easily encode this information in a standardized way, allowing Delve to parse and retrieve that information and use it during a debug session. However, can we get that information out of the .gopclntab section? Turns out the answer is yes, but it’s not as straightforward as one might imagine.

    Inline function support in stripped binaries

    Being able to place a breakpoint on a function is a given with any debugger and Delve indeed supports this feature even in a stripped binary. However, if the function is inlined, additional steps need to be taken to extract the inline information so that it is able to place the breakpoint in that function. Inlining is quite a pervasive optimization that is frequently carried out in the Go compiler, so it is imperative that adequate support is provided in the debugger. 

    Figure 1 depicts the various steps to be added to providing this support in Delve, which are described in the following sections.

    Flowchart showing the steps for adding inline function support in Delve.
    Figure 1: The steps to provide Delve inline function support in stripped binaries.

    A: Placing breakpoint on a function in the regular case

    Usually in Delve, the process to place a breakpoint is to first identify the location of the breakpoint requested and then proceed to put a breakpoint in that location. When we are trying to place a breakpoint on a function, findFuncCandidates is the method that is invoked to get this information. This internally queries BI (binary information) functions that encodes this information. This works even in the case of a stripped binary due to the usage of the .pclntab section data. 

    B: If the stripped binary contains inlined functions

    If the stripped binary contains inlined functions, the information exposed via the standard library debug/gosym package does not include information on these functions. This information is present in the .pclntab section via a inline tree but isn’t parsed by that package. The solution to this problem is to adapt the code defined in the govulncheck package gosym, which does parse and expose the information stored in the inline tree. This information is used to build the inlined functions list that was earlier missing from the BI functions data structure. Once this missing information has been added back to the original list of functions, it is now easy to identify the location(s) of the inlined functions that the debugger queried upon with the function name, so that the breakpoint(s) can be placed. This is what is depicted as B in the flowchart. 

    B.1: Use a modified debug/gosym package

    The govulncheck package is used to detect uses of known vulnerabilities in Go programs hence it needs to track which vulnerable functions and methods are called during program execution. For our purposes we need to adapt the code mainly in gosym/pclntab.go, gosym/symtab.go and gosym/additions.go. The code in gosym/symtab.go and gosym/pclntab.go helps to construct the line table which is the central data structure needed to access inlining information. The main method that extracts the inlining information into a data structure is the InlineTree, which is implemented in gosym/additions.go.

    If we look at the writeFuncs method in the upstream Go compiler code pcln.go, all the function information (including inlined ones) is emitted with respect to the basic symbol go:func.*. The key to getting the InlineTree to work is to correctly find the offset of the go:func.* symbol in a stripped binary. The offset of this symbol is important because the inline tree is stored relative to it within the binary. We will focus on this in section B.2 of this post.

     B.2: Access InlineTree stored in the pclntab Table 

    The code in the govulncheck gosym package would work as is for a binary that is not stripped, since it assumes the presence of the .symtab section and works its way from there to access symbol information pertaining to go:func.* and get the necessary offset. However, in the case of a stripped binary, the .symtab section is not present, requiring us to jump through a few more hoops to get the information we need. 

    For this purpose, we access the Go rodata section which is the read only data section consisting of constant values and instructions. All information pertaining to functions are stored in the modules, one for each function, that are connected together in the moduledata structure. The moduledata is organized in the following format wherein the gofunc symbol is stored right next to the rodata address. We can now access the gofunc symbol from the image by doing a simple pointer arithmetic operation to access the next member:

    type moduledata struct {
    
         ...
    
           rodata uintptr
    
           gofunc uintptr
    
           ...
    
      }

    Once we access the go:func.* symbol, it is easy to construct all the other parameters required to invoke InlineTree, such as the go:func.* symbol value (goFuncvalue) from the moduledata structure, baseaddr (which is the address of the memory region containing goFuncValue) and the reader pointer stationed at the start of the region. Invoking InlineTree with these parameters would then give us all the missing functions that were inlined and completes the BI functions structure which can then be queried to give us the location(s) to place the breakpoint. 

    You can find more details on GitHub: https://github.com/go-delve/delve/pull/3549

    Related Posts

    • Using Delve to debug Go programs on Red Hat Enterprise Linux

    • Build a Go application using OpenShift Pipelines

    • Possible issues with debugging and inspecting compiler-optimized binaries

    • How debugging Go programs with Delve and eBPF is faster

    • Build a Go application using OpenShift Pipelines

    • Go and FIPS 140-2 on Red Hat Enterprise Linux

    Recent Posts

    • Supercharging AI isolation: microVMs with RamaLama & libkrun

    • Simplify multi-VPC connectivity with amazon.aws 9.0.0

    • How HaProxy router settings affect middleware applications

    • Fly Eagle(3) fly: Faster inference with vLLM & speculative decoding

    • Kafka Monthly Digest: June 2025

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2025 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue