Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.
/ druntime Public archive
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 69 additions & 2 deletions src/core/internal/elf/dl.d
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand All @@ -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/<PID>/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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In FreeBSD, using kinfo_getvmmap seems more suitable, this works even if /proc is not mounted.
https://www.freebsd.org/cgi/man.cgi?query=kinfo_getvmmap

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that's worth it (incl. the libutil dependency); I've just tried to write this in a portable way (e.g., no /proc/self/maps etc.).

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
{
Expand Down Expand Up @@ -149,14 +193,37 @@ 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()
{
import core.sys.linux.errno;
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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've originally asserted that path isn't null (no error), but that failed for some Linux CI systems for LDC. I haven't bothered looking into what the issue is.


printf("DSO name: %s\n", name.ptr);
printf(" path: %s\n", path ? path.ptr : "");
printf(" base: %p\n", object.baseAddress);
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E.g.:

DSO name: runtime/druntime-test-runner-shared
    path: /home/martin/build-ldc/runtime/druntime-test-runner-shared
    base: 0x5589831e6000
DSO name: linux-vdso.so.1
    path: [vdso]
    base: 0x7ffdeb9f7000
DSO name: /home/martin/build-ldc/lib/libdruntime-ldc-unittest-shared.so.91
    path: /home/martin/build-ldc/lib/libdruntime-ldc-unittest-shared.so.2.0.91
    base: 0x7ff21166b000
DSO name: /lib/x86_64-linux-gnu/libgcc_s.so.1
    path: /lib/x86_64-linux-gnu/libgcc_s.so.1
    base: 0x7ff211453000
DSO name: /lib/x86_64-linux-gnu/libc.so.6
    path: /lib/x86_64-linux-gnu/libc-2.27.so
    base: 0x7ff211062000
DSO name: /lib64/ld-linux-x86-64.so.2
    path: /lib/x86_64-linux-gnu/ld-2.27.so
    base: 0x7ff211b53000
DSO name: /lib/x86_64-linux-gnu/libm.so.6
    path: /lib/x86_64-linux-gnu/libm-2.27.so
    base: 0x7ff210cc4000
DSO name: /lib/x86_64-linux-gnu/libpthread.so.0
    path: /lib/x86_64-linux-gnu/libpthread-2.27.so
    base: 0x7ff210aa5000
DSO name: /lib/x86_64-linux-gnu/librt.so.1
    path: /lib/x86_64-linux-gnu/librt-2.27.so
    base: 0x7ff21089d000
DSO name: /lib/x86_64-linux-gnu/libdl.so.2
    path: /lib/x86_64-linux-gnu/libdl-2.27.so
    base: 0x7ff210699000

98 changes: 77 additions & 21 deletions src/core/internal/elf/io.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function has been removed. These modules have just been added with v2.091 (#2330); at the time I originally worked on this (~1.5 years ago), there was no move in druntime (only in Phobos), so I've resorted to this little helper for findSectionHeaderByName().

/// 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;
}
}

Expand Down Expand Up @@ -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");
}
}
}
Copy link
Contributor Author

@kinke kinke Mar 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E.g.:

section   0                                 not mapped into memory
section   1 .interp                         0x55a0f1d27270 - 0x55a0f1d2728c
section   2 .note.ABI-tag                   0x55a0f1d2728c - 0x55a0f1d272ac
section   3 .note.gnu.build-id              0x55a0f1d272ac - 0x55a0f1d272d0
section   4 .gnu.hash                       0x55a0f1d272d0 - 0x55a0f1d273b0
section   5 .dynsym                         0x55a0f1d273b0 - 0x55a0f1d278d8
section   6 .dynstr                         0x55a0f1d278d8 - 0x55a0f1d280da
section   7 .gnu.version                    0x55a0f1d280da - 0x55a0f1d28148
section   8 .gnu.version_r                  0x55a0f1d28148 - 0x55a0f1d281a8
section   9 .rela.dyn                       0x55a0f1d281a8 - 0x55a0f1d28358
section  10 .rela.plt                       0x55a0f1d28358 - 0x55a0f1d284f0
section  11 .init                           0x55a0f1d284f0 - 0x55a0f1d28507
section  12 .plt                            0x55a0f1d28510 - 0x55a0f1d28630
section  13 .plt.got                        0x55a0f1d28630 - 0x55a0f1d28638
section  14 .text                           0x55a0f1d28640 - 0x55a0f1d28d72
section  15 .fini                           0x55a0f1d28d74 - 0x55a0f1d28d7d
section  16 .rodata                         0x55a0f1d28d80 - 0x55a0f1d28df0
section  17 .eh_frame_hdr                   0x55a0f1d28df0 - 0x55a0f1d28e9c
section  18 .eh_frame                       0x55a0f1d28ea0 - 0x55a0f1d291d8
section  19 .gcc_except_table               0x55a0f1d291d8 - 0x55a0f1d291f8
section  20 .tbss                           0x55a0f1f29cd8 - 0x55a0f1f29ce8
section  21 .init_array                     0x55a0f1f29cd8 - 0x55a0f1f29ce8
section  22 .fini_array                     0x55a0f1f29ce8 - 0x55a0f1f29cf8
section  23 .dynamic                        0x55a0f1f29cf8 - 0x55a0f1f29f28
section  24 .got                            0x55a0f1f29f28 - 0x55a0f1f2a000
section  25 .data                           0x55a0f1f2a000 - 0x55a0f1f2a058
section  26 __minfo                         0x55a0f1f2a058 - 0x55a0f1f2a060
section  27 .bss                            0x55a0f1f2a060 - 0x55a0f1f2a070
section  28 .comment                        not mapped into memory
section  29 .symtab                         not mapped into memory
section  30 .strtab                         not mapped into memory
section  31 .shstrtab                       not mapped into memory

3 changes: 0 additions & 3 deletions src/core/sys/linux/config.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
10 changes: 9 additions & 1 deletion src/core/sys/linux/link.d
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,15 @@ import core.sys.linux.dlfcn : Lmid_t;
import core.sys.linux.elf;

// <bits/elfclass.h>
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;
Expand Down
16 changes: 11 additions & 5 deletions src/core/sys/posix/config.d
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand Down
18 changes: 2 additions & 16 deletions src/rt/backtrace/elf.d
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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;
}
}