-
-
Notifications
You must be signed in to change notification settings - Fork 411
core.internal.elf: Improve usability #2983
Changes from all commits
f943104
4d7bcd1
e3c8f38
c3f8b11
cd814db
e3bc484
bfca174
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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/<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()); | ||
| 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,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); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've originally asserted that |
||
|
|
||
| printf("DSO name: %s\n", name.ptr); | ||
| printf(" path: %s\n", path ? path.ptr : ""); | ||
| printf(" base: %p\n", object.baseAddress); | ||
| } | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E.g.: |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| /// 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"); | ||
| } | ||
| } | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E.g.: |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In FreeBSD, using
kinfo_getvmmapseems more suitable, this works even if /proc is not mounted.https://www.freebsd.org/cgi/man.cgi?query=kinfo_getvmmap
There was a problem hiding this comment.
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
libutildependency); I've just tried to write this in a portable way (e.g., no /proc/self/maps etc.).