Search code examples
gox86disassemblystripobjdump

_cgo_topofstack@@Base in a stripped binary


What does _cgo_topofstack@@Base mean in the context of a stripped binary coming from Go?

$ cat simple.go
package main
import
(
    "net"
    "time"
    "strconv"
)

func main() {
    tcpAddr, _ := net.ResolveTCPAddr("tcp4", ":7777")
    listener, _ := net.ListenTCP("tcp", tcpAddr)
    conn, _ := listener.Accept()
    daytime := time.Now().String()+strconv.Itoa(0xdeadface)
    conn.Write([]byte(daytime))
}

The code is supposed to be stripped - what does _cgo_topofstack@@Base mean?

$ go build -gcflags=-l -ldflags "-s -w" -o simple_wo_symbols simple.go
$ objdump -D -S simple_wo_symbols > simple_wo_symbols.human
$ sed -n "198899,198904p" simple_wo_symbols.human
  4b9860:   e8 db c1 fb ff          callq  475a40 <_cgo_topofstack@@Base+0xe4c0>
  4b9865:   48 8b 44 24 18          mov    0x18(%rsp),%rax
  4b986a:   48 89 44 24 70          mov    %rax,0x70(%rsp)
  4b986f:   48 8b 4c 24 20          mov    0x20(%rsp),%rcx
  4b9874:   48 89 4c 24 40          mov    %rcx,0x40(%rsp)
  4b9879:   ba ce fa ad de          mov    $0xdeadface,%edx

EDIT (better specification of the question):

  • why does this symbol exist in a stripped binary?
  • ratify peter-cordes claim: called function is completely unrelated to the function at _cgo_topofstack@@Base, and it is an objdump (weird?) thing to add this (irrelevant and redundant) info
  • maybe related to this(?): is there a Go-idiomatic way of stripping?!

Solution

  • _cgo_topofstack@@Base is a symbol that does still exist for some reason in your stripped binary. Your call is to an address 0xe4c0 beyond that, whatever function lives there, completely unrelated to the actual _cgo_topofstack code.

    It's normal for disassemblers to describe addresses as symbol+offset.

    That style makes sense for data arrays (e.g. compiling something like x = global_array[10] into a load from global_array+40, if the symbol for global_array is still around), and for jumps within functions. It's usually not helpful for cases like this, other than to let you see what's nearby, and to have smaller numbers to look at.

    Instead of implementing fancy logic to decide whether or not to bother printing a symbol+offset version of an address, instead of just the numeric absolute address, it's much easier (and no risk of being wrong) for assemblers to just always do it. Search backward from the address and take the first symbol found. Or for addresses before the first symbol in a section, print as foo - 0x.... It's up to humans to use judgement and experience to make sense of the output, especially when looking at disassembly of stripped binaries.

    (There isn't a flag a disassembler can look at to detect a stripped binary or not; detecting this would be a matter of a heuristic like noticing that most direct call targets are to addresses without their own symbol.)

    AFAIK, GNU Binutils objdump doesn't have an option not to print symbolic versions of addresses. --no-addresses does something different.


    I'm not sure what the @@Base is about. It doesn't seem to be unique to Go, though. On my x86-64 Arch GNU/Linux system, objdump -d /bin/ls (which is a stripped PIE executable) shows a lot of addresses as things like 22d60 <_obstack_memory_used@@Base+0xc2a0>. So that's the symbol that happened to be last before the bulk of the code for that program.

    Other cases of @@ include glibc symbol ABI versioning in that same binary, e.g. 23298 <optarg@@GLIBC_2.2.5>. This Arch Linux binary was compiled on an up-to-date Arch Linux system, not actually linked against an ancient glibc 2.2.5, but I think that means optarg's type or something hasn't changed since glibc 2.2.5. And probably not since earlier, but 2.2.5 might have been when glibc started naming symbols this way. Take this paragraph with a big grain of salt because I don't really know how libc.so arranges for ld to substitute symbol names like stderr with these @@ versioned names, or the history of this.