1 /**
2  * Simplifies working with shared ELF objects of the current process.
3  *
4  * Reference: http://www.dwarfstd.org/
5  *
6  * Copyright: Copyright Digital Mars 2015 - 2018.
7  * License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
8  * Authors:   Martin Kinkelin
9  * Source: $(DRUNTIMESRC core/internal/elf/dl.d)
10  */
11 
12 module core.internal.elf.dl;
13 
14 version (linux)
15 {
16     import core.sys.linux.link;
17     version = LinuxOrBSD;
18 }
19 else version (FreeBSD)
20 {
21     import core.sys.freebsd.sys.link_elf;
22     version = LinuxOrBSD;
23 }
24 else version (DragonFlyBSD)
25 {
26     import core.sys.dragonflybsd.sys.link_elf;
27     version = LinuxOrBSD;
28 }
29 else version (NetBSD)
30 {
31     import core.sys.netbsd.sys.link_elf;
32     version = LinuxOrBSD;
33 }
34 else version (OpenBSD)
35 {
36     import core.sys.openbsd.sys.link_elf;
37     version = LinuxOrBSD;
38 }
39 else version (Solaris)
40 {
41     import core.sys.solaris.link;
42     version = LinuxOrBSD;
43 }
44 
45 version (LinuxOrBSD):
46 
47 alias Elf_Ehdr = ElfW!"Ehdr";
48 alias Elf_Phdr = ElfW!"Phdr";
49 
50 /**
51  * Enables iterating over the process' currently loaded shared objects.
52  */
53 struct SharedObjects
54 {
55 @nogc nothrow:
56     ///
57     alias Callback = int delegate(SharedObject);
58 
59     ///
60     static int opApply(scope Callback dg)
61     {
62         extern(C) int nativeCallback(dl_phdr_info* info, size_t, void* data)
63         {
64             auto dg = *cast(Callback*) data;
65             return dg(SharedObject(*info));
66         }
67 
68         return dl_iterate_phdr(&nativeCallback, &dg);
69     }
70 }
71 
72 /**
73  * A loaded shared ELF object/binary, i.e., executable or shared library.
74  */
75 struct SharedObject
76 {
77 @nogc nothrow:
78     /// Returns the executable of the current process.
79     static SharedObject thisExecutable()
80     {
81         foreach (object; SharedObjects)
82             return object; // first object
83         assert(0);
84     }
85 
86     /**
87      * Tries to find the shared object containing the specified address in one of its segments.
88      * Returns: True on success.
89      */
90     static bool findForAddress(const scope void* address, out SharedObject result)
91     {
92         version (linux)        enum IterateManually = true;
93         else version (NetBSD)  enum IterateManually = true;
94         else version (OpenBSD) enum IterateManually = true;
95         else version (Solaris) enum IterateManually = true;
96         else                   enum IterateManually = false;
97 
98         static if (IterateManually)
99         {
100             foreach (object; SharedObjects)
101             {
102                 const(Elf_Phdr)* segment;
103                 if (object.findSegmentForAddress(address, segment))
104                 {
105                     result = object;
106                     return true;
107                 }
108             }
109             return false;
110         }
111         else
112         {
113             return !!_rtld_addr_phdr(address, &result.info);
114         }
115     }
116 
117     /// OS-dependent info structure.
118     dl_phdr_info info;
119 
120     /// Returns the base address of the object.
121     @property void* baseAddress() const
122     {
123         return cast(void*) info.dlpi_addr;
124     }
125 
126     /// Returns the name of (usually: path to) the object. Null-terminated.
127     const(char)[] name() const
128     {
129         import core.stdc.string : strlen;
130 
131         const(char)* cstr = info.dlpi_name;
132 
133         // the main executable has an empty name
134         if (cstr[0] == 0)
135             cstr = getprogname();
136 
137         return cstr[0 .. strlen(cstr)];
138     }
139 
140     /**
141      * Tries to fill the specified buffer with the path to the ELF file,
142      * according to the /proc/<PID>/maps file.
143      *
144      * Returns: The filled slice (null-terminated), or null if an error occurs.
145      */
146     char[] getPath(size_t N)(ref char[N] buffer) const
147     if (N > 1)
148     {
149         import core.stdc.stdio, core.stdc.string, core.sys.posix.unistd;
150 
151         char[N + 128] lineBuffer = void;
152 
153         snprintf(lineBuffer.ptr, lineBuffer.length, "/proc/%d/maps", getpid());
154         auto file = fopen(lineBuffer.ptr, "r");
155         if (!file)
156             return null;
157         scope(exit) fclose(file);
158 
159         const thisBase = cast(ulong) baseAddress();
160         ulong startAddress;
161 
162         // prevent overflowing `buffer` by specifying the max length in the scanf format string
163         enum int maxPathLength = N - 1;
164         enum scanFormat = "%llx-%*llx %*s %*s %*s %*s %" ~ maxPathLength.stringof ~ "s";
165 
166         while (fgets(lineBuffer.ptr, lineBuffer.length, file))
167         {
168             if (sscanf(lineBuffer.ptr, scanFormat.ptr, &startAddress, buffer.ptr) == 2 &&
169                 startAddress == thisBase)
170                 return buffer[0 .. strlen(buffer.ptr)];
171         }
172 
173         return null;
174     }
175 
176     /// Iterates over this object's segments.
177     int opApply(scope int delegate(ref const Elf_Phdr) @nogc nothrow dg) const
178     {
179         foreach (ref phdr; info.dlpi_phdr[0 .. info.dlpi_phnum])
180         {
181             const r = dg(phdr);
182             if (r != 0)
183                 return r;
184         }
185         return 0;
186     }
187 
188     /**
189      * Tries to find the segment containing the specified address.
190      * Returns: True on success.
191      */
192     bool findSegmentForAddress(const scope void* address, out const(Elf_Phdr)* result) const
193     {
194         if (address < baseAddress)
195             return false;
196 
197         foreach (ref phdr; this)
198         {
199             const begin = baseAddress + phdr.p_vaddr;
200             if (cast(size_t)(address - begin) < phdr.p_memsz)
201             {
202                 result = &phdr;
203                 return true;
204             }
205         }
206         return false;
207     }
208 }
209 
210 private @nogc nothrow:
211 
212 version (linux)
213 {
214     // TODO: replace with a fixed core.sys.linux.config._GNU_SOURCE
215     version (CRuntime_Bionic) {} else version = Linux_Use_GNU;
216 }
217 
218 version (Linux_Use_GNU)
219 {
220     const(char)* getprogname()
221     {
222         import core.sys.linux.errno;
223         return program_invocation_name;
224     }
225 }
226 else // Bionic, BSDs
227 {
228     extern(C) const(char)* getprogname();
229 }
230 
231 unittest
232 {
233     import core.stdc.stdio;
234 
235     char[512] buffer = void;
236     foreach (object; SharedObjects)
237     {
238         const name = object.name();
239         assert(name.length);
240         const path = object.getPath(buffer);
241 
242         printf("DSO name: %s\n", name.ptr);
243         printf("    path: %s\n", path ? path.ptr : "");
244         printf("    base: %p\n", object.baseAddress);
245     }
246 }