From 8bcc545f5a7683cbe3b41f0e8e129d99e0635fca Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Tue, 10 Mar 2020 01:57:25 +0100 Subject: [PATCH 1/9] core.internal.elf.dl: Account for Android/Bionic special case --- src/core/internal/elf/dl.d | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/internal/elf/dl.d b/src/core/internal/elf/dl.d index 25e720a4cb..39c366b3b3 100644 --- a/src/core/internal/elf/dl.d +++ b/src/core/internal/elf/dl.d @@ -149,6 +149,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 +162,7 @@ version (linux) return program_invocation_name; } } -else +else // Bionic, BSDs { extern(C) const(char)* getprogname(); } From 54b685d0ceac5515ded726fa795d200255edc243 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Tue, 10 Mar 2020 01:59:00 +0100 Subject: [PATCH 2/9] core.internal.elf.dl: Add method SharedObject.getPath() As alternative to get ahold of the ELF file path in case name() doesn't return a full path. --- src/core/internal/elf/dl.d | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/core/internal/elf/dl.d b/src/core/internal/elf/dl.d index 39c366b3b3..53d26ca4d6 100644 --- a/src/core/internal/elf/dl.d +++ b/src/core/internal/elf/dl.d @@ -98,7 +98,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 +112,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 { From 37668d517a081c0c02db164df750c62dc1d1751c Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Tue, 10 Mar 2020 02:15:24 +0100 Subject: [PATCH 3/9] core.internal.elf.dl: Add convenience factory SharedObject.thisExecutable() --- src/core/internal/elf/dl.d | 8 ++++++++ src/rt/backtrace/elf.d | 18 ++---------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/core/internal/elf/dl.d b/src/core/internal/elf/dl.d index 53d26ca4d6..a20d7adec6 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. 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; } } From 2ed67d1475e56a4f57f1f88107444837c7d9f627 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Tue, 10 Mar 2020 02:17:02 +0100 Subject: [PATCH 4/9] core.internal.elf.io: Enable convenience method for iterating over named sections E.g., this allows to conveniently scan for multiple named sections in a single pass. --- src/core/internal/elf/io.d | 72 +++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/src/core/internal/elf/io.d b/src/core/internal/elf/io.d index 48df1dc7c5..8cbeba4c4b 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; + } + + /// name: null-terminated + alias Callback = int delegate(size_t index, const(char)[] name, ElfSectionHeader sectionHeader); - const stringSectionHeader = ElfSectionHeader(this, ehdr.e_shstrndx); - const stringSection = ElfSection(this, stringSectionHeader); + /// + int opApply(scope Callback dg) + { + const stringSectionHeader = ElfSectionHeader(*file, file.ehdr.e_shstrndx); + const stringSection = ElfSection(*file, stringSectionHeader); - foreach (i; 0 .. ehdr.e_shnum) + 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; } } From ea2bdf016f46da86b7ad15977e0702b1d8416630 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Tue, 10 Mar 2020 03:20:16 +0100 Subject: [PATCH 5/9] Add some core.internal.elf unittests --- src/core/internal/elf/dl.d | 17 +++++++++++++++++ src/core/internal/elf/io.d | 26 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/core/internal/elf/dl.d b/src/core/internal/elf/dl.d index a20d7adec6..7db21a763f 100644 --- a/src/core/internal/elf/dl.d +++ b/src/core/internal/elf/dl.d @@ -210,3 +210,20 @@ 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 8cbeba4c4b..eeb94e8e9f 100644 --- a/src/core/internal/elf/io.d +++ b/src/core/internal/elf/io.d @@ -315,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"); + } + } +} From afbf0f151f20dd57eea30fc30b59d824d7bc74ca Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Tue, 10 Mar 2020 22:09:16 +0100 Subject: [PATCH 6/9] Revamp rt.sections_android, switching to parsing the ELF headers ... instead of relying on magic compiler symbols `_tls{start,end}` and the apparently problematic magic linker symbol `_end`. Also eagerly allocate the TLS per-thread-block to speed-up TLS lookup. --- src/rt/sections_android.d | 171 ++++++++++++++++++++++++++------------ 1 file changed, 118 insertions(+), 53 deletions(-) diff --git a/src/rt/sections_android.d b/src/rt/sections_android.d index bc4674e445..e4078abb12 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,33 @@ 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); + + // exclude static TLS range + if (_staticTLSRange.length) + { + safeAssert(start == _staticTLSRange.ptr, + "static TLS range expected to be at start of data segment"); + start += _staticTLSRange.length; + } + + // 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 +124,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 +137,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) { - pary = cast(void[]*).calloc(1, (void[]).sizeof); + 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); + 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 +256,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; } } From e89f60f19ea4b21306113c518ecd70af70db1848 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Wed, 11 Mar 2020 21:40:35 +0100 Subject: [PATCH 7/9] rt.sections_android: Don't bother excluding static TLS data from GC scanning As it apparently doesn't always end up at the start of the data segment, e.g., with the gold linker, see https://github.com/ldc-developers/ldc/issues/3350#issuecomment-597735906. It should be rather small in most cases, especially in comparison to the ranges on the heap, so just don't bother. --- src/rt/sections_android.d | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/rt/sections_android.d b/src/rt/sections_android.d index e4078abb12..521a97bc0c 100644 --- a/src/rt/sections_android.d +++ b/src/rt/sections_android.d @@ -95,14 +95,6 @@ void initSections() nothrow @nogc void* end = start + phdr.p_memsz; debug(PRINTF) printf("data segment: %p - %p\n", start, end); - // exclude static TLS range - if (_staticTLSRange.length) - { - safeAssert(start == _staticTLSRange.ptr, - "static TLS range expected to be at start of data segment"); - start += _staticTLSRange.length; - } - // pointer-align up enum mask = size_t.sizeof - 1; start = cast(void*) ((cast(size_t)start + mask) & ~mask); From 2e95bb5b45bf7e3136fac56607d4377b0538ac74 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Sat, 14 Mar 2020 16:07:56 +0100 Subject: [PATCH 8/9] Android: Fix __WORDSIZE for 64-bit targets --- src/core/sys/linux/config.d | 3 --- src/core/sys/posix/config.d | 16 +++++++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) 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/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) { From ff89130cd1ea9d49911aa41318a2bea5fbffa277 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Sat, 14 Mar 2020 16:09:08 +0100 Subject: [PATCH 9/9] Android: Fix Elf_Symndx for 64-bit targets --- src/core/sys/linux/link.d | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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;