Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.Sign up
cmd/link: dynamic linking cannot be stepped through using gdb #38378
What version of Go are you using (
I suspect that supporting this scenario is not something that can added easily or with tiny tweaks to the Go linker; some work will be required (possibly quite a lot of work).
First a bit of a digression on DWARF type generation in general:
DWARF type generation (e.g. creation of the part of DWARF .debug_info that relates to types of variables, function, etc) has an implicit assumption that the compiler has a complete picture of the type in question. Inside a DWARF type DIE (the unit of DWARF that describes a variable/value/function type) you can have references to other types, but those references have to be to some other well-defined DWARF type DIE -- you can't say something like "this struct type ABC has a field F refers to type X, where X is defined in some other shared library, please wait and look for it later".
As a result, most compilers handle this by emitting DWARF for a translation unit that is a complete picture of everything the compiler has seen referenced as part of the compile. So for example let's say you are compiling a C++ source file with a type like
For C/C++ it is a given that at the point we see a type T, we have seen textual definitions of all other types referenced by T. For the type above, that means we've seen the entire definition of "SomeStruct" (including its dependencies) and all the complex stuff that goes into "std::set".
When a C++ compiler emits DWARF for an object file whose source file refers to the type above, it creates DWARF types for everything referenced by that type. [Note: some C++ compilers will omit DWARF for types that are entirely unreferenced, but this is something of a grey area.]. This generally results in huge amounts of duplicate DWARF info, since many source files include the same large set of headers. The result is that you get huge object files, long compile times, and giant amounts of duplicated DWARF info in the final executable.
The Go compiler, in contrast, emits DWARF type info only for types defined in the package being compiled. For example, when compiling this package:
The Go compiler will not emit DWARF type info for "sync.Mutex" (since this type is not defined in package p). It will emit a mostly-complete DWARF type unit for "Q", but with references (via relocations) to the other types that it uses (in contrast to C/C++, where the compiler would emit DWARF type records for the transitive closure of the referents of Q). The Go compiler relies on the Go linker to combine together all of the type DIEs into a single consistent unit and resolve the references between them.
Doing things this way (as opposed to the C/C++ "emit DWARF for everything you have ever seen" strategy) is one of the reasons why Go builds are lightning fast, especially as compared with building a C/C++ program of equivalent size.
Inside the Go linker, a reachability analysis (also called "dead code") is performed at an early stage to collect up only those functions and variables that are actually used (transitively reachable from main.main). It then examines the set of reachable funcs/variables to see which types they use, and then finally it emits DWARF info for only those types. Consider this example:
Here there are three types declared, but only two are referenced by some reachable function (let's assume that no other function in the program refers to the type "mumble.NotUsed"). The linker will pick up on this fact during DWARF generation, and will not generate any DWARF for the unused type.
This results in DWARF type info that is much more compact than what you would get from (for example) a C++ compiler, which is a major benefit for Go users. So with the Go toolchain you get two huge benefits: A) super fast builds, and B) nice compact binaries.
Getting back to the -linkshared case:
When you run "go build -linkshared main.go", the compiler emits DWARF type information for things in main.go, and then hands things off to the Go linker. At that point however the linker is presented with an incomplete picture with respect to DWARF types -- there will be references/relocations in the main.go object file to types that are defined in the standard library... but the standard library has already been compiled and linked.
Thus in order for things to work properly, we would have to invent some way for the linker to dig into the contents of libstd.so's generated type info to find the set of stdlib types referenced by the transitive closure of types reachable from main.go, then emit that into the resulting executable. DWARF type generation would need to be changed so that any time once type refers to another, we can handle the case where the thing being referred to has to be excavated from an already-compiled shared library.
This seems possible, but would require some doing.
It is also worth noting that -linkshared is a pretty unusual case for most Go users, meaning that fixing problems like this have not been a big priority.
@thanm Thank you for your detailed reply.
@thanm I guess we don't want to introduce another flag. We could do something like the following:
Then the default case would work.