| 1 | /* pseudo-reloc.c |
| 2 | |
| 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> |
| 6 | 	 |
| 7 | THIS SOFTWARE IS NOT COPYRIGHTED |
| 8 | |
| 9 | This source code is offered for use in the public domain. You may |
| 10 | use, modify or distribute it freely. |
| 11 | |
| 12 | This code is distributed in the hope that it will be useful but |
| 13 | WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY |
| 14 | DISCLAMED. This includes but is not limited to warranties of |
| 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| 16 | */ |
| 17 | |
| 18 | #include <windows.h> |
| 19 | #include <stdio.h> |
| 20 | #include <stdlib.h> |
| 21 | #include <stdarg.h> |
| 22 | #include <memory.h> |
| 23 | #include <internal.h> |
| 24 | #include <stdint.h> |
| 25 | |
| 26 | #if defined(__CYGWIN__) |
| 27 | #include <wchar.h> |
| 28 | #include <ntdef.h> |
| 29 | #include <sys/cygwin.h> |
| 30 | /* copied from winsup.h */ |
| 31 | # define NO_COPY __attribute__((nocommon)) __attribute__((section(".data_cygwin_nocopy"))) |
| 32 | /* custom status code: */ |
| 33 | #define STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION ((NTSTATUS) 0xe0000269) |
| 34 | #define SHORT_MSG_BUF_SZ 128 |
| 35 | #else |
| 36 | # define NO_COPY |
| 37 | #endif |
| 38 | |
| 39 | #ifdef __GNUC__ |
| 40 | #define ATTRIBUTE_NORETURN __attribute__ ((noreturn)) |
| 41 | #else |
| 42 | #define ATTRIBUTE_NORETURN |
| 43 | #endif |
| 44 | |
| 45 | #ifndef __MINGW_LSYMBOL |
| 46 | #define __MINGW_LSYMBOL(sym) sym |
| 47 | #endif |
| 48 | |
| 49 | extern char __RUNTIME_PSEUDO_RELOC_LIST__; |
| 50 | extern char __RUNTIME_PSEUDO_RELOC_LIST_END__; |
| 51 | extern IMAGE_DOS_HEADER __ImageBase; |
| 52 | |
| 53 | void _pei386_runtime_relocator (void); |
| 54 | |
| 55 | /* v1 relocation is basically: |
| 56 | * *(base + .target) += .addend |
| 57 | * where (base + .target) is always assumed to point |
| 58 | * to a DWORD (4 bytes). |
| 59 | */ |
| 60 | typedef struct { |
| 61 | DWORD addend; |
| 62 | DWORD target; |
| 63 | } runtime_pseudo_reloc_item_v1; |
| 64 | |
| 65 | /* v2 relocation is more complex. In effect, it is |
| 66 | * *(base + .target) += *(base + .sym) - (base + .sym) |
| 67 | * with care taken in both reading, sign extension, and writing |
| 68 | * because .flags may indicate that (base + .target) may point |
| 69 | * to a BYTE, WORD, DWORD, or QWORD (w64). |
| 70 | */ |
| 71 | typedef struct { |
| 72 | DWORD sym; |
| 73 | DWORD target; |
| 74 | DWORD flags; |
| 75 | } runtime_pseudo_reloc_item_v2; |
| 76 | |
| 77 | typedef struct { |
| 78 | DWORD magic1; |
| 79 | DWORD magic2; |
| 80 | DWORD version; |
| 81 | } runtime_pseudo_reloc_v2; |
| 82 | |
| 83 | static void ATTRIBUTE_NORETURN |
| 84 | __report_error (const char *msg, ...) |
| 85 | { |
| 86 | #ifdef __CYGWIN__ |
| 87 | /* This function is used to print short error messages |
| 88 | * to stderr, which may occur during DLL initialization |
| 89 | * while fixing up 'pseudo' relocations. This early, we |
| 90 | * may not be able to use cygwin stdio functions, so we |
| 91 | * use the win32 WriteFile api. This should work with both |
| 92 | * normal win32 console IO handles, redirected ones, and |
| 93 | * cygwin ptys. |
| 94 | */ |
| 95 | char buf[SHORT_MSG_BUF_SZ]; |
| 96 | wchar_t module[PATH_MAX]; |
| 97 | char * posix_module = NULL; |
| 98 | static const char UNKNOWN_MODULE[] = "<unknown module>: "; |
| 99 | static const size_t UNKNOWN_MODULE_LEN = sizeof (UNKNOWN_MODULE) - 1; |
| 100 | static const char CYGWIN_FAILURE_MSG[] = "Cygwin runtime failure: "; |
| 101 | static const size_t CYGWIN_FAILURE_MSG_LEN = sizeof (CYGWIN_FAILURE_MSG) - 1; |
| 102 | DWORD len; |
| 103 | DWORD done; |
| 104 | va_list args; |
| 105 | HANDLE errh = GetStdHandle (STD_ERROR_HANDLE); |
| 106 | ssize_t modulelen = GetModuleFileNameW (NULL, module, PATH_MAX); |
| 107 | |
| 108 | if (errh == INVALID_HANDLE_VALUE) |
| 109 | cygwin_internal (CW_EXIT_PROCESS, |
| 110 | STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION, |
| 111 | 1); |
| 112 | |
| 113 | if (modulelen > 0) |
| 114 | posix_module = cygwin_create_path (CCP_WIN_W_TO_POSIX, module); |
| 115 | |
| 116 | va_start (args, msg); |
| 117 | len = (DWORD) vsnprintf (buf, SHORT_MSG_BUF_SZ, msg, args); |
| 118 | va_end (args); |
| 119 | buf[SHORT_MSG_BUF_SZ-1] = '\0'; /* paranoia */ |
| 120 | |
| 121 | if (posix_module) |
| 122 | { |
| 123 | WriteFile (errh, (PCVOID)CYGWIN_FAILURE_MSG, |
| 124 | CYGWIN_FAILURE_MSG_LEN, &done, NULL); |
| 125 | WriteFile (errh, (PCVOID)posix_module, |
| 126 | strlen(posix_module), &done, NULL); |
| 127 | WriteFile (errh, (PCVOID)": ", 2, &done, NULL); |
| 128 | WriteFile (errh, (PCVOID)buf, len, &done, NULL); |
| 129 | free (posix_module); |
| 130 | } |
| 131 | else |
| 132 | { |
| 133 | WriteFile (errh, (PCVOID)CYGWIN_FAILURE_MSG, |
| 134 | CYGWIN_FAILURE_MSG_LEN, &done, NULL); |
| 135 | WriteFile (errh, (PCVOID)UNKNOWN_MODULE, |
| 136 | UNKNOWN_MODULE_LEN, &done, NULL); |
| 137 | WriteFile (errh, (PCVOID)buf, len, &done, NULL); |
| 138 | } |
| 139 | WriteFile (errh, (PCVOID)"\n", 1, &done, NULL); |
| 140 | |
| 141 | cygwin_internal (CW_EXIT_PROCESS, |
| 142 | STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION, |
| 143 | 1); |
| 144 | __builtin_unreachable (); |
| 145 | #else |
| 146 | va_list argp; |
| 147 | va_start (argp, msg); |
| 148 | # ifdef __MINGW64_VERSION_MAJOR |
| 149 | fprintf (stderr, "Mingw-w64 runtime failure:\n"); |
| 150 | vfprintf (stderr, msg, argp); |
| 151 | # else |
| 152 | fprintf (stderr, "Mingw runtime failure:\n"); |
| 153 | vfprintf (stderr, msg, argp); |
| 154 | #endif |
| 155 | va_end (argp); |
| 156 | abort (); |
| 157 | #endif |
| 158 | } |
| 159 | |
| 160 | /* For mingw-w64 we have additional helpers to get image information |
| 161 | on runtime. This allows us to cache for pseudo-relocation pass |
| 162 | the temporary access of code/read-only sections. |
| 163 | This step speeds up pseudo-relocation pass. */ |
| 164 | #ifdef __MINGW64_VERSION_MAJOR |
| 165 | extern int __mingw_GetSectionCount (void); |
| 166 | extern PIMAGE_SECTION_HEADER __mingw_GetSectionForAddress (LPVOID p); |
| 167 | extern PBYTE _GetPEImageBase (void); |
| 168 | |
| 169 | typedef struct sSecInfo { |
| 170 | /* Keeps altered section flags, or zero if nothing was changed. */ |
| 171 | DWORD old_protect; |
| 172 | PVOID base_address; |
| 173 | SIZE_T region_size; |
| 174 | PBYTE sec_start; |
| 175 | PIMAGE_SECTION_HEADER hash; |
| 176 | } sSecInfo; |
| 177 | |
| 178 | static sSecInfo *the_secs = NULL; |
| 179 | static int maxSections = 0; |
| 180 | |
| 181 | static void |
| 182 | mark_section_writable (LPVOID addr) |
| 183 | { |
| 184 | MEMORY_BASIC_INFORMATION b; |
| 185 | PIMAGE_SECTION_HEADER h; |
| 186 | int i; |
| 187 | |
| 188 | for (i = 0; i < maxSections; i++) |
| 189 | { |
| 190 | if (the_secs[i].sec_start <= ((LPBYTE) addr) |
| 191 | && ((LPBYTE) addr) < (the_secs[i].sec_start + the_secs[i].hash->Misc.VirtualSize)) |
| 192 | return; |
| 193 | } |
| 194 | h = __mingw_GetSectionForAddress (addr); |
| 195 | if (!h) |
| 196 | { |
| 197 | __report_error ("Address %p has no image-section", addr); |
| 198 | } |
| 199 | the_secs[i].hash = h; |
| 200 | the_secs[i].old_protect = 0; |
| 201 | the_secs[i].sec_start = _GetPEImageBase () + h->VirtualAddress; |
| 202 | |
| 203 | if (!VirtualQuery (the_secs[i].sec_start, &b, sizeof(b))) |
| 204 | { |
| 205 | __report_error (" VirtualQuery failed for %d bytes at address %p", |
| 206 | 		 (int) h->Misc.VirtualSize, the_secs[i].sec_start); |
| 207 | } |
| 208 | |
| 209 | if (b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE |
| 210 | && b.Protect != PAGE_EXECUTE_WRITECOPY && b.Protect != PAGE_WRITECOPY) |
| 211 | { |
| 212 | ULONG new_protect; |
| 213 | if (b.Protect == PAGE_READONLY) |
| 214 | new_protect = PAGE_READWRITE; |
| 215 | else |
| 216 | new_protect = PAGE_EXECUTE_READWRITE; |
| 217 | the_secs[i].base_address = b.BaseAddress; |
| 218 | the_secs[i].region_size = b.RegionSize; |
| 219 | if (!VirtualProtect (b.BaseAddress, b.RegionSize, |
| 220 | 			 new_protect, |
| 221 | 			 &the_secs[i].old_protect)) |
| 222 | 	__report_error (" VirtualProtect failed with code 0x%x", |
| 223 | 	 (int) GetLastError ()); |
| 224 | } |
| 225 | ++maxSections; |
| 226 | return; |
| 227 | } |
| 228 | |
| 229 | static void |
| 230 | restore_modified_sections (void) |
| 231 | { |
| 232 | int i; |
| 233 | DWORD oldprot; |
| 234 | |
| 235 | for (i = 0; i < maxSections; i++) |
| 236 | { |
| 237 | if (the_secs[i].old_protect == 0) |
| 238 | continue; |
| 239 | VirtualProtect (the_secs[i].base_address, the_secs[i].region_size, |
| 240 | the_secs[i].old_protect, &oldprot); |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | #endif /* __MINGW64_VERSION_MAJOR */ |
| 245 | |
| 246 | /* This function temporarily marks the page containing addr |
| 247 | * writable, before copying len bytes from *src to *addr, and |
| 248 | * then restores the original protection settings to the page. |
| 249 | * |
| 250 | * Using this function eliminates the requirement with older |
| 251 | * pseudo-reloc implementations, that sections containing |
| 252 | * pseudo-relocs (such as .text and .rdata) be permanently |
| 253 | * marked writable. This older behavior sabotaged any memory |
| 254 | * savings achieved by shared libraries on win32 -- and was |
| 255 | * slower, too. However, on cygwin as of binutils 2.20 the |
| 256 | * .text section is still marked writable, and the .rdata section |
| 257 | * is folded into the (writable) .data when --enable-auto-import. |
| 258 | */ |
| 259 | static void |
| 260 | __write_memory (void *addr, const void *src, size_t len) |
| 261 | { |
| 262 | if (!len) |
| 263 | return; |
| 264 | |
| 265 | #ifdef __MINGW64_VERSION_MAJOR |
| 266 | /* Mark the section writable once, and unset it in |
| 267 | * restore_modified_sections */ |
| 268 | mark_section_writable ((LPVOID) addr); |
| 269 | #else |
| 270 | MEMORY_BASIC_INFORMATION b; |
| 271 | DWORD oldprot = 0; |
| 272 | int call_unprotect = 0; |
| 273 | |
| 274 | if (!VirtualQuery (addr, &b, sizeof(b))) |
| 275 | { |
| 276 | __report_error (" VirtualQuery failed for %d bytes at address %p", |
| 277 | 		 (int) sizeof(b), addr); |
| 278 | } |
| 279 | |
| 280 | /* Temporarily allow write access to read-only protected memory. */ |
| 281 | if (b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE |
| 282 | && b.Protect != PAGE_WRITECOPY && b.Protect != PAGE_EXECUTE_WRITECOPY) |
| 283 | { |
| 284 | call_unprotect = 1; |
| 285 | VirtualProtect (b.BaseAddress, b.RegionSize, PAGE_EXECUTE_READWRITE, |
| 286 | 		 &oldprot); |
| 287 | } |
| 288 | #endif |
| 289 | |
| 290 | /* write the data. */ |
| 291 | memcpy (addr, src, len); |
| 292 | |
| 293 | #ifndef __MINGW64_VERSION_MAJOR |
| 294 | /* Restore original protection. */ |
| 295 | if (call_unprotect |
| 296 | && b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE |
| 297 | && b.Protect != PAGE_WRITECOPY && b.Protect != PAGE_EXECUTE_WRITECOPY) |
| 298 | VirtualProtect (b.BaseAddress, b.RegionSize, oldprot, &oldprot); |
| 299 | #endif |
| 300 | } |
| 301 | |
| 302 | #define RP_VERSION_V1 0 |
| 303 | #define RP_VERSION_V2 1 |
| 304 | |
| 305 | static void |
| 306 | do_pseudo_reloc (void * start, void * end, void * base) |
| 307 | { |
| 308 | ptrdiff_t addr_imp, reldata; |
| 309 | ptrdiff_t reloc_target = (ptrdiff_t) ((char *)end - (char*)start); |
| 310 | runtime_pseudo_reloc_v2 *v2_hdr = (runtime_pseudo_reloc_v2 *) start; |
| 311 | runtime_pseudo_reloc_item_v2 *r; |
| 312 | unsigned int bits; |
| 313 | |
| 314 | /* A valid relocation list will contain at least one entry, and |
| 315 | * one v1 data structure (the smallest one) requires two DWORDs. |
| 316 | * So, if the relocation list is smaller than 8 bytes, bail. |
| 317 | */ |
| 318 | if (reloc_target < 8) |
| 319 | return; |
| 320 | |
| 321 | /* Check if this is the old pseudo relocation version. */ |
| 322 | /* There are two kinds of v1 relocation lists: |
| 323 | * 1) With a (v2-style) version header. In this case, the |
| 324 | * first entry in the list is a 3-DWORD structure, with |
| 325 | * value: |
| 326 | * { 0, 0, RP_VERSION_V1 } |
| 327 | * In this case, we skip to the next entry in the list, |
| 328 | * knowing that all elements after the head item can |
| 329 | * be cast to runtime_pseudo_reloc_item_v1. |
| 330 | * 2) Without a (v2-style) version header. In this case, the |
| 331 | * first element in the list IS an actual v1 relocation |
| 332 | * record, which is two DWORDs. Because there will never |
| 333 | * be a case where a v1 relocation record has both |
| 334 | * addend == 0 and target == 0, this case will not be |
| 335 | * confused with the prior one. |
| 336 | * All current binutils, when generating a v1 relocation list, |
| 337 | * use the second (e.g. original) form -- that is, without the |
| 338 | * v2-style version header. |
| 339 | */ |
| 340 | if (reloc_target >= 12 |
| 341 | && v2_hdr->magic1 == 0 && v2_hdr->magic2 == 0 |
| 342 | && v2_hdr->version == RP_VERSION_V1) |
| 343 | { |
| 344 | /* We have a list header item indicating that the rest |
| 345 | * of the list contains v1 entries. Move the pointer to |
| 346 | * the first true v1 relocation record. By definition, |
| 347 | * that v1 element will not have both addend == 0 and |
| 348 | * target == 0 (and thus, when interpreted as a |
| 349 | * runtime_pseudo_reloc_v2, it will not have both |
| 350 | * magic1 == 0 and magic2 == 0). |
| 351 | */ |
| 352 | v2_hdr++; |
| 353 | } |
| 354 | |
| 355 | if (v2_hdr->magic1 != 0 || v2_hdr->magic2 != 0) |
| 356 | { |
| 357 | /************************* |
| 358 | * Handle v1 relocations * |
| 359 | *************************/ |
| 360 | runtime_pseudo_reloc_item_v1 * o; |
| 361 | for (o = (runtime_pseudo_reloc_item_v1 *) v2_hdr; |
| 362 | 	 o < (runtime_pseudo_reloc_item_v1 *)end; |
| 363 | o++) |
| 364 | 	{ |
| 365 | 	 DWORD newval; |
| 366 | 	 reloc_target = (ptrdiff_t) base + o->target; |
| 367 | 	 newval = (*((DWORD*) reloc_target)) + o->addend; |
| 368 | 	 __write_memory ((void *) reloc_target, &newval, sizeof(DWORD)); |
| 369 | 	} |
| 370 | return; |
| 371 | } |
| 372 | |
| 373 | /* If we got this far, then we have relocations of version 2 or newer */ |
| 374 | |
| 375 | /* Check if this is a known version. */ |
| 376 | if (v2_hdr->version != RP_VERSION_V2) |
| 377 | { |
| 378 | __report_error (" Unknown pseudo relocation protocol version %d.\n", |
| 379 | 		 (int) v2_hdr->version); |
| 380 | } |
| 381 | |
| 382 | /************************* |
| 383 | * Handle v2 relocations * |
| 384 | *************************/ |
| 385 | |
| 386 | /* Walk over header. */ |
| 387 | r = (runtime_pseudo_reloc_item_v2 *) &v2_hdr[1]; |
| 388 | |
| 389 | for (; r < (runtime_pseudo_reloc_item_v2 *) end; r++) |
| 390 | { |
| 391 | /* location where new address will be written */ |
| 392 | reloc_target = (ptrdiff_t) base + r->target; |
| 393 | |
| 394 | /* get sym pointer. It points either to the iat entry |
| 395 | * of the referenced element, or to the stub function. |
| 396 | */ |
| 397 | addr_imp = (ptrdiff_t) base + r->sym; |
| 398 | addr_imp = *((ptrdiff_t *) addr_imp); |
| 399 | |
| 400 | /* read existing relocation value from image, casting to the |
| 401 | * bitsize indicated by the 8 LSBs of flags. If the value is |
| 402 | * negative, manually sign-extend to ptrdiff_t width. Raise an |
| 403 | * error if the bitsize indicated by the 8 LSBs of flags is not |
| 404 | * supported. |
| 405 | */ |
| 406 | switch ((r->flags & 0xff)) |
| 407 | { |
| 408 | case 8: |
| 409 | 	 reldata = (ptrdiff_t) (*((unsigned char *)reloc_target)); |
| 410 | 	 if ((reldata & 0x80) != 0) |
| 411 | 	 reldata |= ~((ptrdiff_t) 0xff); |
| 412 | 	 break; |
| 413 | 	 case 16: |
| 414 | 	 reldata = (ptrdiff_t) (*((unsigned short *)reloc_target)); |
| 415 | 	 if ((reldata & 0x8000) != 0) |
| 416 | 	 reldata |= ~((ptrdiff_t) 0xffff); |
| 417 | 	 break; |
| 418 | 	 case 32: |
| 419 | 	 reldata = (ptrdiff_t) (*((unsigned int *)reloc_target)); |
| 420 | #ifdef _WIN64 |
| 421 | 	 if ((reldata & 0x80000000) != 0) |
| 422 | 	 reldata |= ~((ptrdiff_t) 0xffffffff); |
| 423 | #endif |
| 424 | 	 break; |
| 425 | #ifdef _WIN64 |
| 426 | 	 case 64: |
| 427 | 	 reldata = (ptrdiff_t) (*((unsigned long long *)reloc_target)); |
| 428 | 	 break; |
| 429 | #endif |
| 430 | 	 default: |
| 431 | 	 reldata=0; |
| 432 | 	 __report_error (" Unknown pseudo relocation bit size %d.\n", |
| 433 | 		 (int) (r->flags & 0xff)); |
| 434 | 	 break; |
| 435 | } |
| 436 | |
| 437 | /* Adjust the relocation value */ |
| 438 | reldata -= ((ptrdiff_t) base + r->sym); |
| 439 | reldata += addr_imp; |
| 440 | |
| 441 | bits = r->flags & 0xff; |
| 442 | if (bits < sizeof(ptrdiff_t)*8) |
| 443 | { |
| 444 | /* Check for overflows. We don't know if the target address is |
| 445 | * interpreted as a relative offset or as a truncated absolute |
| 446 | * address - to avoid false positives, allow offsets within the |
| 447 | * whole range of signed and unsigned N bits numbers, but error |
| 448 | * out for anything outside of that. Thus for relative offsets, |
| 449 | * this won't catch offsets that are only barely too large. */ |
| 450 | ptrdiff_t max_unsigned = (1LL << bits) - 1; |
| 451 | ptrdiff_t min_signed = UINTPTR_MAX << (bits - 1); |
| 452 | if (reldata > max_unsigned || reldata < min_signed) |
| 453 | 	 __report_error ("%d bit pseudo relocation at %p out of range, " |
| 454 | "targeting %p, yielding the value %p.\n", |
| 455 | bits, reloc_target, addr_imp, reldata); |
| 456 | } |
| 457 | |
| 458 | /* Write the new relocation value back to *reloc_target */ |
| 459 | switch ((r->flags & 0xff)) |
| 460 | 	{ |
| 461 | case 8: |
| 462 | __write_memory ((void *) reloc_target, &reldata, 1); |
| 463 | 	 break; |
| 464 | 	 case 16: |
| 465 | __write_memory ((void *) reloc_target, &reldata, 2); |
| 466 | 	 break; |
| 467 | 	 case 32: |
| 468 | __write_memory ((void *) reloc_target, &reldata, 4); |
| 469 | 	 break; |
| 470 | #ifdef _WIN64 |
| 471 | 	 case 64: |
| 472 | __write_memory ((void *) reloc_target, &reldata, 8); |
| 473 | 	 break; |
| 474 | #endif |
| 475 | 	} |
| 476 | } |
| 477 | } |
| 478 | |
| 479 | __attribute__((used)) /* required due to GNU LD bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30343 */ |
| 480 | void |
| 481 | _pei386_runtime_relocator (void) |
| 482 | { |
| 483 | static NO_COPY int was_init = 0; |
| 484 | #ifdef __MINGW64_VERSION_MAJOR |
| 485 | int mSecs; |
| 486 | #endif /* __MINGW64_VERSION_MAJOR */ |
| 487 | |
| 488 | if (was_init) |
| 489 | return; |
| 490 | ++was_init; |
| 491 | #ifdef __MINGW64_VERSION_MAJOR |
| 492 | mSecs = __mingw_GetSectionCount (); |
| 493 | the_secs = (sSecInfo *) alloca (sizeof (sSecInfo) * (size_t) mSecs); |
| 494 | maxSections = 0; |
| 495 | #endif /* __MINGW64_VERSION_MAJOR */ |
| 496 | |
| 497 | do_pseudo_reloc (&__RUNTIME_PSEUDO_RELOC_LIST__, |
| 498 | 		 &__RUNTIME_PSEUDO_RELOC_LIST_END__, |
| 499 | 		 &__ImageBase |
| 500 | 		 ); |
| 501 | #ifdef __MINGW64_VERSION_MAJOR |
| 502 | restore_modified_sections (); |
| 503 | #endif /* __MINGW64_VERSION_MAJOR */ |
| 504 | } |