@bignose all of the undefined references seem to be C API symbols. I don't understand what hardening is, why it might be enabled, or what I am supposed to do about it. Do you have any insight and/or guidance?
I don't understand what hardening is
Ah, my apologies for assuming the context. This is referring to the “hardening” of an executable against common security vulnerabilities, by enabling specific improvements – the “hardening options” – when invoking the C-language compiler.
$ x86_64-linux-gnu-gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-Bsymbolic-functions -Wl,-z,relro -pie -Wdate-time -D_FORTIFY_SOURCE=2 build/temp.linux-x86_64-3.5/coverage/ctracer/datastack.o build/temp.linux-x86_64-3.5/coverage/ctracer/filedisp.o build/temp.linux-x86_64-3.5/coverage/ctracer/module.o build/temp.linux-x86_64-3.5/coverage/ctracer/tracer.o -lpython3.5m -o build/lib.linux-x86_64-3.5/coverage/tracer.cpython-35m-x86_64-linux-gnu.so
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/Scrt1.o: In function`_start':(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1exit status
resolves most linkage issues, it fails because main is missing, which makes sense because this is a library and not a standalone executable. I'm not sure I understand why a shared library would need pie in addition to PIC ?
Unless I missed something the shared library seems to be properly hardened by default:
$ hardening-check build/lib.linux-x86_64-3.5/coverage/tracer.cpython-35m-x86_64-linux-gnu.so
Position Independent Executable: no, regular shared library (ignored)
Stack protected: yes
Fortify Source functions: unknown, no protectable libc functions used
Read-only relocations: yes
Immediate binding: no, not found!
This problem doesn't appear to have anything to do with coverage, as such, as any extension module would have the same problem: you're (indirectly) passing -pie to the link of a shared library. -pie only makes sense on the final link of an executable, which is why it insists there is a 'main' to reference, as well as that all symbols are resolved.
Extension modules are shared libraries. They intentionally leave a lot of symbols unresolved -- all symbols that would be satisfied by the python executable they are loaded into. It's not a good idea to dynamically link extension modules to (e.g.) libpython3.5 to resolve those symbols, as that library only exists when Python is built with --enable-shared (and it might not be). It's also useless: either the libpythonX.Y shared library will already have been loaded by the python process, or it shouldn't be loaded.
The equivalent of -pie for shared libraries is -fPIC, which is already the default (because of how they work, shared libraries have to be linked position-independently, which means all names go through a runtime lookup table, just like PIE). There's also -fPIE, but I believe code generated that way can't be linked into a shared library.