--- Begin Message ---
- From: Jakub Bogusz <qboosh at pld-linux dot org>
- To: hjl at lucon dot org
- Date: Thu, 19 Feb 2004 20:08:15 +0100
- Subject: binutils 2.14.90.0.8 - ld crash when cross-linking PE DLL on 64-bit archs
Hello,
When trying to make cross mingw32 environment on 64-bit archs
(alpha-linux, amd64-linux), ld (binutils 2.14.90.0.8 tested) catches
SEGV on the following command:
| ld --dll -Bdynamic -e _DllMainCRTStartup@12 -o mingwthrd_dummy.exe
| ./dllcrt2.o -Lmingwex -L. -L/usr/lib/gcc-lib/i386-mingw32/3.3.2
| L/usr/lib/gcc-lib/i386-mingw32/3.3.2/../../../../i386-mingw32/lib
| --base-file=mingwthrd.base mingwthrd.exp --image-base 0x6FBC0000 --entry
| _DllMainCRTStartup@12 mthr.o mthr_init.o -lmingw32 -lgcc -lmoldname
| -lmingwex -lmsvcrt -luser32 -lkernel32 -ladvapi32 -lshell32 -lmingw32
| -lgcc -lmoldname -lmingwex -lmsvcrt
SEGV appears inside malloc(), after corruption of some malloc structs.
Using ElectricFence I found that ld writes past allocated area in
fill_edata() function (ld/pe-dll.c).
edata_d is pointer to allocated edata_sz bytes, where edata_sz is
calculated by:
| /* OK, now we can allocate some memory. */
| edata_sz = (40 /* directory */
| + 4 * export_table_size /* addresses */
| + 4 * count_exported_byname /* name ptrs */
| + 2 * count_exported_byname /* ordinals */
| + name_table_size + strlen (dll_name) + 1);
but then pointers to edata areas are calculated inconsequently:
| unsigned char *edirectory;
| unsigned long *eaddresses;
| unsigned long *enameptrs;
| unsigned short *eordinals;
| unsigned char *enamestr;
[...]
| edata_d = xmalloc (edata_sz);
|
| /* Note use of array pointer math here. */
| edirectory = edata_d;
| eaddresses = (unsigned long *) (edata_d + 40);
| enameptrs = eaddresses + export_table_size;
| eordinals = (unsigned short *) (enameptrs + count_exported_byname);
| enamestr = (char *) (eordinals + count_exported_byname);
These pointers are wrong if sizeof(unsigned long)!=4 or
sizeof(unsigned short)!=2.
On alpha-linux and amd64-linux it's the first case (long is 8-byte long).
I'm attaching the patch I've used - it changes unsinged long to uint32_t
in above pointers. It works for me on alpha-linux and amd64-linux, doesn't
break anything on x86-linux and sparc-linux.
I don't know what is binutils policy on <stdint.h> - is using uint32_t
OK, or binutils has some other name for exactly 32-bit long type.
unsigned shorts above probably should be changed to uint16_t (or
equivalent), but I didn't touch it as all archs I'm using have 16-bit
unsigned short.
--
Jakub Bogusz http://cyber.cs.net.pl/~qboosh/
PLD Team http://www.pld-linux.org/
--- binutils-2.14.90.0.8/ld/pe-dll.c.orig 2004-01-14 21:07:52.000000000 +0000
+++ binutils-2.14.90.0.8/ld/pe-dll.c 2004-02-18 21:56:38.000000000 +0000
@@ -25,6 +25,7 @@
#include "libiberty.h"
#include "safe-ctype.h"
+#include <stdint.h>
#include <time.h>
#include "ld.h"
@@ -916,8 +917,8 @@
{
int s, hint;
unsigned char *edirectory;
- unsigned long *eaddresses;
- unsigned long *enameptrs;
+ uint32_t *eaddresses;
+ uint32_t *enameptrs;
unsigned short *eordinals;
unsigned char *enamestr;
time_t now;
@@ -928,7 +929,7 @@
/* Note use of array pointer math here. */
edirectory = edata_d;
- eaddresses = (unsigned long *) (edata_d + 40);
+ eaddresses = (uint32_t *) (edata_d + 40);
enameptrs = eaddresses + export_table_size;
eordinals = (unsigned short *) (enameptrs + count_exported_byname);
enamestr = (char *) (eordinals + count_exported_byname);
--- End Message ---