features.h: support POSIX.1-2024
[newlib-cygwin.git] / winsup / cygwin / pseudo-reloc.cc
blob5a0eab936b586f694386cc0cf07c8d216e7ccf48
1 /* pseudo-reloc.cc
3 Contributed by Egor Duda <deo@logos-m.ru>
4 Modified by addition of runtime_pseudo_reloc version 2
5 by Kai Tietz <kai.tietz@onevision.com>
7 THIS SOFTWARE IS NOT COPYRIGHTED
9 This source code is offered for use in the public domain. You may
10 use, modify or distribute it freely.
12 This code is distributed in the hope that it will be useful but
13 WITHOUT ANY WARRANTY. ALL WARRENTIES, EXPRESS OR IMPLIED ARE HEREBY
14 DISCLAMED. This includes but is not limited to warrenties of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 #ifndef __CYGWIN__
19 # include "windows.h"
20 # define NO_COPY
21 #else
22 # include "winsup.h"
23 # include <sys/cygwin.h>
24 #endif
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <stdarg.h>
29 #include <memory.h>
31 #ifdef __GNUC__
32 #define ATTRIBUTE_NORETURN __attribute__ ((noreturn))
33 #else
34 #define ATTRIBUTE_NORETURN
35 #endif
37 #ifndef __MINGW_LSYMBOL
38 #define __MINGW_LSYMBOL(sym) sym
39 #endif
41 extern char __RUNTIME_PSEUDO_RELOC_LIST__;
42 extern char __RUNTIME_PSEUDO_RELOC_LIST_END__;
43 extern char __MINGW_LSYMBOL(_image_base__);
45 /* v1 relocation is basically:
46 * *(base + .target) += .addend
47 * where (base + .target) is always assumed to point
48 * to a DWORD (4 bytes).
50 typedef struct {
51 DWORD addend;
52 DWORD target;
53 } runtime_pseudo_reloc_item_v1;
55 /* v2 relocation is more complex. In effect, it is
56 * *(base + .target) += *(base + .sym) - (base + .sym)
57 * with care taken in both reading, sign extension, and writing
58 * because .flags may indicate that (base + .target) may point
59 * to a BYTE, WORD, DWORD, or QWORD (w64).
61 typedef struct {
62 DWORD sym;
63 DWORD target;
64 DWORD flags;
65 } runtime_pseudo_reloc_item_v2;
67 typedef struct {
68 DWORD magic1;
69 DWORD magic2;
70 DWORD version;
71 } runtime_pseudo_reloc_v2;
73 static void ATTRIBUTE_NORETURN
74 __report_error (const char *msg, ...)
76 #ifdef __CYGWIN__
77 /* This function is used to print short error messages
78 * to stderr, which may occur during DLL initialization
79 * while fixing up 'pseudo' relocations. This early, we
80 * may not be able to use cygwin stdio functions, so we
81 * use the win32 WriteFile api. This should work with both
82 * normal win32 console IO handles, redirected ones, and
83 * cygwin ptys.
85 char buf[128];
86 char *posix_module = NULL;
87 static const char UNKNOWN_MODULE[] = "<unknown module>: ";
88 static const char CYGWIN_FAILURE_MSG[] = "Cygwin runtime failure: ";
89 HANDLE errh = GetStdHandle (STD_ERROR_HANDLE);
90 va_list args;
92 /* FIXME: cleanup further to avoid old use of cygwin_internal */
93 if (errh == INVALID_HANDLE_VALUE)
94 cygwin_internal (CW_EXIT_PROCESS, STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION, 1);
96 posix_module = (char *) cygwin_create_path (CCP_WIN_W_TO_POSIX,
97 global_progname);
99 va_start (args, msg);
100 vsnprintf (buf, sizeof (buf), msg, args);
101 va_end (args);
102 buf[sizeof (buf) - 1] = '\0'; /* paranoia */
104 small_printf ("%s%s: %s\n", CYGWIN_FAILURE_MSG, posix_module ?: UNKNOWN_MODULE, buf);
105 if (posix_module)
106 free (posix_module);
108 cygwin_internal (CW_EXIT_PROCESS, STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION, 1);
109 /* not reached, but silences noreturn warning */
110 abort ();
111 #else
112 va_list argp;
113 va_start (argp, msg);
114 # ifdef __MINGW64_VERSION_MAJOR
115 fprintf (stderr, "Mingw-w64 runtime failure:\n");
116 # else
117 fprintf (stderr, "Mingw runtime failure:\n");
118 # endif
119 vfprintf (stderr, msg, argp);
120 va_end (argp);
121 abort ();
122 #endif
126 * This function automatically sets addr as PAGE_EXECUTE_READWRITE
127 * by deciding whether VirtualQuery for the addr is actually needed.
128 * And it assumes that it is called in LdrpCallInitRoutine.
129 * Hence not thread safe.
131 static void
132 auto_protect_for (void* addr)
134 static MEMORY_BASIC_INFORMATION mbi;
135 static bool state = false;
136 static DWORD oldprot;
138 if (!addr)
140 /* Restore original protection. */
141 if (!(mbi.Protect & (PAGE_EXECUTE_READWRITE | PAGE_READWRITE)))
142 VirtualProtect (mbi.BaseAddress, mbi.RegionSize, oldprot, &oldprot);
143 state = false;
144 return;
146 if (state)
148 /* We have valid region information. Are we still within this region?
149 If so, just leave. */
150 void *ptr = ((void*) ((ptrdiff_t) mbi.BaseAddress + mbi.RegionSize));
151 if (addr >= mbi.BaseAddress && addr < ptr)
152 return;
153 /* Otherwise, restore original protection and fall through to querying
154 and potentially changing next region. */
155 if (!(mbi.Protect & (PAGE_EXECUTE_READWRITE | PAGE_READWRITE)))
156 VirtualProtect (mbi.BaseAddress, mbi.RegionSize, oldprot, &oldprot);
158 else
159 state = true;
160 /* Query region and temporarily allow write access to read-only protected
161 memory. */
162 VirtualQuery (addr, &mbi, sizeof mbi);
163 if (!(mbi.Protect & (PAGE_EXECUTE_READWRITE | PAGE_READWRITE)))
164 VirtualProtect (mbi.BaseAddress, mbi.RegionSize,
165 PAGE_EXECUTE_READWRITE, &oldprot);
168 /* This function temporarily marks the page containing addr
169 * writable, before copying len bytes from *src to *addr, and
170 * then restores the original protection settings to the page.
172 * Using this function eliminates the requirement with older
173 * pseudo-reloc implementations, that sections containing
174 * pseudo-relocs (such as .text and .rdata) be permanently
175 * marked writable. This older behavior sabotaged any memory
176 * savings achieved by shared libraries on win32 -- and was
177 * slower, too. However, on cygwin as of binutils 2.20 the
178 * .text section is still marked writable, and the .rdata section
179 * is folded into the (writable) .data when --enable-auto-import.
181 static void
182 __write_memory (void *addr, const void *src, size_t len)
184 if (!len)
185 return;
186 /* Fix page protection for writing. */
187 auto_protect_for (addr);
188 /* write the data. */
189 memcpy (addr, src, len);
192 #define RP_VERSION_V1 0
193 #define RP_VERSION_V2 1
195 static void
196 do_pseudo_reloc (void * start, void * end, void * base)
198 ptrdiff_t addr_imp, reldata;
199 ptrdiff_t reloc_target = (ptrdiff_t) ((char *)end - (char*)start);
200 runtime_pseudo_reloc_v2 *v2_hdr = (runtime_pseudo_reloc_v2 *) start;
201 runtime_pseudo_reloc_item_v2 *r;
203 /* A valid relocation list will contain at least one entry, and
204 * one v1 data structure (the smallest one) requires two DWORDs.
205 * So, if the relocation list is smaller than 8 bytes, bail.
207 if (reloc_target < 8)
208 return;
210 /* Check if this is the old pseudo relocation version. */
211 /* There are two kinds of v1 relocation lists:
212 * 1) With a (v2-style) version header. In this case, the
213 * first entry in the list is a 3-DWORD structure, with
214 * value:
215 * { 0, 0, RP_VERSION_V1 }
216 * In this case, we skip to the next entry in the list,
217 * knowing that all elements after the head item can
218 * be cast to runtime_pseudo_reloc_item_v1.
219 * 2) Without a (v2-style) version header. In this case, the
220 * first element in the list IS an actual v1 relocation
221 * record, which is two DWORDs. Because there will never
222 * be a case where a v1 relocation record has both
223 * addend == 0 and target == 0, this case will not be
224 * confused with the prior one.
225 * All current binutils, when generating a v1 relocation list,
226 * use the second (e.g. original) form -- that is, without the
227 * v2-style version header.
229 if (reloc_target >= 12
230 && v2_hdr->magic1 == 0 && v2_hdr->magic2 == 0
231 && v2_hdr->version == RP_VERSION_V1)
233 /* We have a list header item indicating that the rest
234 * of the list contains v1 entries. Move the pointer to
235 * the first true v1 relocation record. By definition,
236 * that v1 element will not have both addend == 0 and
237 * target == 0 (and thus, when interpreted as a
238 * runtime_pseudo_reloc_v2, it will not have both
239 * magic1 == 0 and magic2 == 0).
241 v2_hdr++;
244 if (v2_hdr->magic1 != 0 || v2_hdr->magic2 != 0)
246 /*************************
247 * Handle v1 relocations *
248 *************************/
249 runtime_pseudo_reloc_item_v1 * o;
250 for (o = (runtime_pseudo_reloc_item_v1 *) v2_hdr;
251 o < (runtime_pseudo_reloc_item_v1 *)end;
252 o++)
254 DWORD newval;
255 reloc_target = (ptrdiff_t) base + o->target;
256 newval = (*((DWORD*) reloc_target)) + o->addend;
257 __write_memory ((void *) reloc_target, &newval, sizeof (DWORD));
259 /* Restore original protection. */
260 auto_protect_for (NULL);
261 return;
264 /* If we got this far, then we have relocations of version 2 or newer */
266 /* Check if this is a known version. */
267 if (v2_hdr->version != RP_VERSION_V2)
269 __report_error (" Unknown pseudo relocation protocol version %d.\n",
270 (int) v2_hdr->version);
271 return;
274 /*************************
275 * Handle v2 relocations *
276 *************************/
278 /* Walk over header. */
279 r = (runtime_pseudo_reloc_item_v2 *) &v2_hdr[1];
281 for (; r < (runtime_pseudo_reloc_item_v2 *) end; r++)
283 /* location where new address will be written */
284 reloc_target = (ptrdiff_t) base + r->target;
286 /* get sym pointer. It points either to the iat entry
287 * of the referenced element, or to the stub function.
289 addr_imp = (ptrdiff_t) base + r->sym;
290 addr_imp = *((ptrdiff_t *) addr_imp);
292 /* read existing relocation value from image, casting to the
293 * bitsize indicated by the 8 LSBs of flags. If the value is
294 * negative, manually sign-extend to ptrdiff_t width. Raise an
295 * error if the bitsize indicated by the 8 LSBs of flags is not
296 * supported.
298 switch ((r->flags & 0xff))
300 case 8:
301 reldata = (ptrdiff_t) (*((unsigned char *)reloc_target));
302 if ((reldata & 0x80) != 0)
303 reldata |= ~((ptrdiff_t) 0xff);
304 break;
305 case 16:
306 reldata = (ptrdiff_t) (*((unsigned short *)reloc_target));
307 if ((reldata & 0x8000) != 0)
308 reldata |= ~((ptrdiff_t) 0xffff);
309 break;
310 case 32:
311 reldata = (ptrdiff_t) (*((unsigned int *)reloc_target));
312 #if defined (__x86_64__) || defined (_WIN64)
313 if ((reldata & 0x80000000) != 0)
314 reldata |= ~((ptrdiff_t) 0xffffffff);
315 #endif
316 break;
317 #if defined (__x86_64__) || defined (_WIN64)
318 case 64:
319 reldata = (ptrdiff_t) (*((unsigned long long *)reloc_target));
320 break;
321 #endif
322 default:
323 reldata=0;
324 __report_error (" Unknown pseudo relocation bit size %d.\n",
325 (int) (r->flags & 0xff));
326 break;
329 /* Adjust the relocation value */
330 reldata -= ((ptrdiff_t) base + r->sym);
331 reldata += addr_imp;
333 /* Write the new relocation value back to *reloc_target */
334 switch ((r->flags & 0xff))
336 case 8:
337 __write_memory ((void *) reloc_target, &reldata, 1);
338 break;
339 case 16:
340 __write_memory ((void *) reloc_target, &reldata, 2);
341 break;
342 case 32:
343 #if defined (__CYGWIN__) && defined (__x86_64__)
344 if (reldata > (ptrdiff_t) __INT32_MAX__
345 || reldata < -((ptrdiff_t) __INT32_MAX__) - 1)
346 __report_error ("Invalid relocation. Offset %p at address %p "
347 "doesn't fit into 32 bits", reldata, reloc_target);
348 #endif
349 __write_memory ((void *) reloc_target, &reldata, 4);
350 break;
351 #if defined (__x86_64__) || defined (_WIN64)
352 case 64:
353 __write_memory ((void *) reloc_target, &reldata, 8);
354 break;
355 #endif
358 /* Restore original protection. */
359 auto_protect_for (NULL);
362 #ifdef __CYGWIN__
363 extern "C" void
364 _pei386_runtime_relocator (per_process *u)
366 if (u)
367 do_pseudo_reloc (u->pseudo_reloc_start, u->pseudo_reloc_end, u->image_base);
369 #else
370 extern "C" void
371 _pei386_runtime_relocator (void)
373 static NO_COPY int was_init = 0;
374 if (was_init)
375 return;
376 ++was_init;
377 do_pseudo_reloc (&__RUNTIME_PSEUDO_RELOC_LIST__,
378 &__RUNTIME_PSEUDO_RELOC_LIST_END__,
379 &__MINGW_LSYMBOL(_image_base__));
381 #endif