diff --git a/src/core/internal/elf/dl.d b/src/core/internal/elf/dl.d index 25e720a4cb..7db21a763f 100644 --- a/src/core/internal/elf/dl.d +++ b/src/core/internal/elf/dl.d @@ -60,6 +60,14 @@ struct SharedObjects struct SharedObject { @nogc nothrow: + /// Returns the executable of the current process. + static SharedObject thisExecutable() + { + foreach (object; SharedObjects) + return object; // first object + assert(0); + } + /** * Tries to find the shared object containing the specified address in one of its segments. * Returns: True on success. @@ -98,7 +106,7 @@ struct SharedObject return cast(void*) info.dlpi_addr; } - /// Returns the name of (usually: path to) the object. + /// Returns the name of (usually: path to) the object. Null-terminated. const(char)[] name() const { import core.stdc.string : strlen; @@ -112,6 +120,42 @@ struct SharedObject return cstr[0 .. strlen(cstr)]; } + /** + * Tries to fill the specified buffer with the path to the ELF file, + * according to the /proc//maps file. + * + * Returns: The filled slice (null-terminated), or null if an error occurs. + */ + char[] getPath(size_t N)(ref char[N] buffer) const + if (N > 1) + { + import core.stdc.stdio, core.stdc.string, core.sys.posix.unistd; + + char[N + 128] lineBuffer = void; + + snprintf(lineBuffer.ptr, lineBuffer.length, "/proc/%d/maps", getpid()); + auto file = fopen(lineBuffer.ptr, "r"); + if (!file) + return null; + scope(exit) fclose(file); + + const thisBase = cast(ulong) baseAddress(); + ulong startAddress; + + // prevent overflowing `buffer` by specifying the max length in the scanf format string + enum int maxPathLength = N - 1; + enum scanFormat = "%llx-%*llx %*s %*s %*s %*s %" ~ maxPathLength.stringof ~ "s"; + + while (fgets(lineBuffer.ptr, lineBuffer.length, file)) + { + if (sscanf(lineBuffer.ptr, scanFormat.ptr, &startAddress, buffer.ptr) == 2 && + startAddress == thisBase) + return buffer[0 .. strlen(buffer.ptr)]; + } + + return null; + } + /// Iterates over this object's segments. int opApply(scope int delegate(ref const Elf_Phdr) @nogc nothrow dg) const { @@ -149,6 +193,12 @@ struct SharedObject private @nogc nothrow: version (linux) +{ + // TODO: replace with a fixed core.sys.linux.config.__USE_GNU + version (CRuntime_Bionic) {} else version = Linux_Use_GNU; +} + +version (Linux_Use_GNU) { const(char)* getprogname() { @@ -156,7 +206,24 @@ version (linux) return program_invocation_name; } } -else +else // Bionic, BSDs { extern(C) const(char)* getprogname(); } + +unittest +{ + import core.stdc.stdio; + + char[512] buffer = void; + foreach (object; SharedObjects) + { + const name = object.name(); + assert(name.length); + const path = object.getPath(buffer); + + printf("DSO name: %s\n", name.ptr); + printf(" path: %s\n", path ? path.ptr : ""); + printf(" base: %p\n", object.baseAddress); + } +} diff --git a/src/core/internal/elf/io.d b/src/core/internal/elf/io.d index 48df1dc7c5..eeb94e8e9f 100644 --- a/src/core/internal/elf/io.d +++ b/src/core/internal/elf/io.d @@ -13,6 +13,7 @@ module core.internal.elf.io; version (Posix): +import core.lifetime : move; import core.sys.posix.fcntl; import core.sys.posix.sys.mman; import core.sys.posix.unistd; @@ -134,41 +135,70 @@ template ElfIO(Elf_Ehdr, Elf_Shdr, ubyte ELFCLASS) return true; } + /** + * Returns a struct to iterate over the named sections. + * Examples: + * -------------------- + * foreach (index, name, sectionHeader; elfFile.namedSections) ... + * -------------------- + */ + NamedSections namedSections() const + { + return NamedSections(this); + } + /** * Tries to find the header of the section with the specified name. * Returns: True on success. */ bool findSectionHeaderByName(const(char)[] sectionName, out ElfSectionHeader header) const { - const index = findSectionIndexByName(sectionName); - if (index == -1) - return false; - header = ElfSectionHeader(this, index); - return true; + foreach (index, name, sectionHeader; namedSections) + { + if (name == sectionName) + { + header = move(sectionHeader); + return true; + } + } + return false; } + } - /** - * Tries to find the index of the section with the specified name. - * Returns: -1 if not found, otherwise 0-based section index. - */ - size_t findSectionIndexByName(const(char)[] sectionName) const + /// Enables iterating over an ELF file's (named) sections. + struct NamedSections + { + @nogc nothrow: + private const(ElfFile)* file; + + private this(ref const ElfFile file) { - import core.stdc.string : strlen; + this.file = &file; + } - const stringSectionHeader = ElfSectionHeader(this, ehdr.e_shstrndx); - const stringSection = ElfSection(this, stringSectionHeader); + /// name: null-terminated + alias Callback = int delegate(size_t index, const(char)[] name, ElfSectionHeader sectionHeader); - foreach (i; 0 .. ehdr.e_shnum) + /// + int opApply(scope Callback dg) + { + const stringSectionHeader = ElfSectionHeader(*file, file.ehdr.e_shstrndx); + const stringSection = ElfSection(*file, stringSectionHeader); + + foreach (i; 0 .. file.ehdr.e_shnum) { - auto sectionHeader = ElfSectionHeader(this, i); - auto pCurrentName = cast(const(char)*) (stringSection.data.ptr + sectionHeader.sh_name); - auto currentName = pCurrentName[0 .. strlen(pCurrentName)]; - if (sectionName == currentName) - return i; + import core.stdc.string : strlen; + + auto sectionHeader = ElfSectionHeader(*file, i); + auto sectionName = cast(const(char)*) (stringSection.data.ptr + sectionHeader.sh_name); + const nameLen = strlen(sectionName); + + const r = dg(i, sectionName[0 .. nameLen], move(sectionHeader)); + if (r != 0) + return r; } - // not found - return -1; + return 0; } } @@ -285,3 +315,29 @@ private struct MMapRegion(T) private ubyte[] mappedRegion; const(T)* data; } + +version (LinuxOrBSD) +unittest +{ + import core.internal.elf.dl, core.stdc.stdio; + + SharedObject exe = SharedObject.thisExecutable(); + + ElfFile file; + bool success = ElfFile.open(exe.name.ptr, file); + assert(success, "cannot open ELF file"); + + foreach (index, name, sectionHeader; file.namedSections) + { + printf("section %3d %-32s", cast(int) index, name.ptr); + if (const offset = sectionHeader.shdr.sh_addr) + { + auto beg = exe.baseAddress + offset; + printf("%p - %p\n", beg, beg + sectionHeader.shdr.sh_size); + } + else + { + printf("not mapped into memory\n"); + } + } +} diff --git a/src/core/sys/linux/config.d b/src/core/sys/linux/config.d index 96126674b0..03d3e17e16 100644 --- a/src/core/sys/linux/config.d +++ b/src/core/sys/linux/config.d @@ -27,6 +27,3 @@ deprecated("use _DEFAULT_SOURCE") enum __USE_MISC = _DEFAULT_SOURCE; enum __USE_ATFILE = _ATFILE_SOURCE; enum __USE_GNU = _GNU_SOURCE; - -// Available in bionic from API 21 -version (CRuntime_Bionic) enum __WORDSIZE = 32; diff --git a/src/core/sys/linux/link.d b/src/core/sys/linux/link.d index e242d2b287..4d7eb1eb7d 100644 --- a/src/core/sys/linux/link.d +++ b/src/core/sys/linux/link.d @@ -31,7 +31,15 @@ import core.sys.linux.dlfcn : Lmid_t; import core.sys.linux.elf; // -version (X86_Any) +version (Android) +{ + alias __WORDSIZE __ELF_NATIVE_CLASS; + version (D_LP64) + alias uint64_t Elf_Symndx; + else + alias uint32_t Elf_Symndx; +} +else version (X86_Any) { // http://sourceware.org/git/?p=glibc.git;a=blob;f=bits/elfclass.h alias __WORDSIZE __ELF_NATIVE_CLASS; diff --git a/src/core/sys/posix/config.d b/src/core/sys/posix/config.d index 9ac42a130a..20e711cb72 100644 --- a/src/core/sys/posix/config.d +++ b/src/core/sys/posix/config.d @@ -61,9 +61,9 @@ version (CRuntime_Glibc) enum __USE_REENTRANT = _REENTRANT; version (D_LP64) - enum __WORDSIZE=64; + enum __WORDSIZE = 64; else - enum __WORDSIZE=32; + enum __WORDSIZE = 32; } else version (CRuntime_Musl) { @@ -109,13 +109,19 @@ else version (CRuntime_UClibc) enum __USE_REENTRANT = _REENTRANT; version (D_LP64) - enum __WORDSIZE=64; + enum __WORDSIZE = 64; else - enum __WORDSIZE=32; + enum __WORDSIZE = 32; } else version (CRuntime_Bionic) { - enum __USE_GNU = false; + enum _GNU_SOURCE = false; + enum __USE_GNU = _GNU_SOURCE; + + version (D_LP64) + enum __WORDSIZE = 64; + else + enum __WORDSIZE = 32; } else version (OpenBSD) { diff --git a/src/rt/backtrace/elf.d b/src/rt/backtrace/elf.d index 837dde877d..4747f50c8b 100644 --- a/src/rt/backtrace/elf.d +++ b/src/rt/backtrace/elf.d @@ -38,13 +38,7 @@ struct Image static Image openSelf() { - const(char)* selfPath; - foreach (object; SharedObjects) - { - // the first object is the main binary - selfPath = object.name().ptr; - break; - } + const(char)* selfPath = SharedObject.thisExecutable().name().ptr; Image image; if (!ElfFile.open(selfPath, image.file)) @@ -80,14 +74,6 @@ struct Image if (!isDynamicSharedObject) return 0; - size_t base = 0; - foreach (object; SharedObjects) - { - // only take the first address as this will be the main binary - base = cast(size_t) object.baseAddress; - break; - } - - return base; + return cast(size_t) SharedObject.thisExecutable().baseAddress; } } diff --git a/src/rt/sections_android.d b/src/rt/sections_android.d index bc4674e445..521a97bc0c 100644 --- a/src/rt/sections_android.d +++ b/src/rt/sections_android.d @@ -12,16 +12,15 @@ module rt.sections_android; version (CRuntime_Bionic): -version (X86) version = X86_Any; -version (X86_64) version = X86_Any; - // debug = PRINTF; debug(PRINTF) import core.stdc.stdio; -import core.stdc.stdlib : malloc, free; -import rt.deh, rt.minfo; +import core.internal.elf.dl : SharedObject; import core.sys.posix.pthread; -import core.stdc.stdlib : calloc; +import core.stdc.stdlib : calloc, malloc, free; import core.stdc.string : memcpy; +import rt.deh; +import rt.minfo; +import rt.util.utility : safeAssert; struct SectionGroup { @@ -66,6 +65,12 @@ void initSections() nothrow @nogc { pthread_key_create(&_tlsKey, null); + SharedObject object; + const success = SharedObject.findForAddress(&_sections, object); + safeAssert(success, "cannot find ELF object"); + + _staticTLSRange = getStaticTLSRange(object); + version (LDC) { auto mbeg = cast(immutable ModuleInfo**)&__start___minfo; @@ -78,11 +83,25 @@ void initSections() nothrow @nogc } _sections.moduleGroup = ModuleGroup(mbeg[0 .. mend - mbeg]); - auto pbeg = cast(void*)&_tlsend; - auto pend = cast(void*)&__bss_end__; - // _tlsend is a 32-bit int and may not be 64-bit void*-aligned, so align pbeg. - version (D_LP64) pbeg = cast(void*)(cast(size_t)(pbeg + 7) & ~cast(size_t)7); - _sections._gcRanges[0] = pbeg[0 .. pend - pbeg]; + // iterate over ELF segments to determine data segment range + import core.sys.linux.elf; + foreach (ref phdr; object) + { + if (phdr.p_type == PT_LOAD && (phdr.p_flags & PF_W)) // writeable data segment + { + safeAssert(_sections._gcRanges[0] is null, "expected a single data segment"); + + void* start = object.baseAddress + phdr.p_vaddr; + void* end = start + phdr.p_memsz; + debug(PRINTF) printf("data segment: %p - %p\n", start, end); + + // pointer-align up + enum mask = size_t.sizeof - 1; + start = cast(void*) ((cast(size_t)start + mask) & ~mask); + + _sections._gcRanges[0] = start[0 .. end-start]; + } + } } void finiSections() nothrow @nogc @@ -97,8 +116,8 @@ void[]* initTLSRanges() nothrow @nogc void finiTLSRanges(void[]* rng) nothrow @nogc { - .free(rng.ptr); - .free(rng); + free(rng.ptr); + free(rng); } void scanTLSRanges(void[]* rng, scope void delegate(void* pbeg, void* pend) nothrow dg) nothrow @@ -110,68 +129,118 @@ void scanTLSRanges(void[]* rng, scope void delegate(void* pbeg, void* pend) noth * .tbss/.tdata ELF sections, which are marked with the SHF_TLS/STT_TLS * flags. So instead we roll our own by keeping TLS data in the * .tdata/.tbss sections but removing the SHF_TLS/STT_TLS flags, and - * access the TLS data using this function and the _tlsstart/_tlsend - * symbols as delimiters. + * access the TLS data using this function. * * This function is called by the code emitted by the compiler. It * is expected to translate an address in the TLS static data to * the corresponding address in the TLS dynamic per-thread data. */ - -extern(C) void* __tls_get_addr( void* p ) nothrow @nogc +extern(C) void* __tls_get_addr(void* p) nothrow @nogc { debug(PRINTF) printf(" __tls_get_addr input - %p\n", p); - immutable offset = cast(size_t)(p - cast(void*)&_tlsstart); - auto tls = getTLSBlockAlloc(); - assert(offset < tls.length); - return tls.ptr + offset; + const offset = cast(size_t) (p - _staticTLSRange.ptr); + assert(offset < _staticTLSRange.length, + "invalid TLS address or initSections() not called yet"); + // The following would only be safe if no TLS variables are accessed + // before calling initTLSRanges(): + //return (cast(void[]*) pthread_getspecific(_tlsKey)).ptr + offset; + return getTLSBlock().ptr + offset; } private: __gshared pthread_key_t _tlsKey; +__gshared void[] _staticTLSRange; +__gshared SectionGroup _sections; ref void[] getTLSBlock() nothrow @nogc { - auto pary = cast(void[]*)pthread_getspecific(_tlsKey); - if (pary is null) + auto pary = cast(void[]*) pthread_getspecific(_tlsKey); + + version (LDC) + { + import ldc.intrinsics; + const isUninitialized = llvm_expect(pary is null, false); + } + else + const isUninitialized = pary is null; + + if (isUninitialized) { - pary = cast(void[]*).calloc(1, (void[]).sizeof); + pary = cast(void[]*) calloc(1, (void[]).sizeof); + safeAssert(pary !is null, "cannot allocate TLS block slice"); + if (pthread_setspecific(_tlsKey, pary) != 0) { import core.stdc.stdio; perror("pthread_setspecific failed with"); assert(0); } + + safeAssert(_staticTLSRange.ptr !is null, "initSections() not called yet"); + if (const size = _staticTLSRange.length) + { + auto p = malloc(size); + safeAssert(p !is null, "cannot allocate TLS block"); + memcpy(p, _staticTLSRange.ptr, size); + *pary = p[0 .. size]; + } } + return *pary; } -ref void[] getTLSBlockAlloc() nothrow @nogc +void[] getStaticTLSRange(const ref SharedObject object) nothrow @nogc { - auto pary = &getTLSBlock(); - if (!pary.length) + import core.internal.elf.io; + + const(char)[] path = object.name(); + char[512] pathBuffer = void; + if (path[0] != '/') { - auto pbeg = cast(void*)&_tlsstart; - auto pend = cast(void*)&_tlsend; - auto p = .malloc(pend - pbeg); - memcpy(p, pbeg, pend - pbeg); - *pary = p[0 .. pend - pbeg]; + path = object.getPath(pathBuffer); + safeAssert(path !is null, "cannot get path of ELF object"); } - return *pary; -} + debug(PRINTF) printf("ELF file path: %s\n", path.ptr); -__gshared SectionGroup _sections; + ElfFile file; + const success = ElfFile.open(path.ptr, file); + safeAssert(success, "cannot open ELF file"); + + void* start, end; + foreach (index, name, sectionHeader; file.namedSections) + { + if (name == ".tdata" || name == ".tbss") + { + void* sectionStart = object.baseAddress + sectionHeader.sh_addr; + void* sectionEnd = sectionStart + sectionHeader.sh_size; + debug(PRINTF) printf("section %s: %p - %p\n", name.ptr, sectionStart, sectionEnd); + + if (!start) + { + start = sectionStart; + end = sectionEnd; + } + else + { + safeAssert(sectionStart == end, "expected .tdata and .tbss sections to be contiguous"); + end = sectionEnd; + break; // we've found both sections + } + } + } + + // return an empty but non-null slice if there's no TLS data + return start ? start[0 .. end-start] : object.baseAddress[0..0]; +} extern(C) { - /* Symbols created by the compiler/linker and inserted into the - * object file that 'bracket' sections. + /* Symbols created by the linker and inserted into the object file that + * 'bracket' sections. */ extern __gshared { - void* __start_deh; - void* __stop_deh; version (LDC) { void* __start___minfo; @@ -179,22 +248,10 @@ extern(C) } else { + void* __start_deh; + void* __stop_deh; void* __start_minfo; void* __stop_minfo; } - - version (X86_Any) - { - // the x86 linker scripts don't provide __bss_end__; use _end instead - size_t _end; - alias __bss_end__ = _end; - } - else - { - size_t __bss_end__; - } - - int _tlsstart; - int _tlsend; } }