1/* Support for 32-bit i386 NLM (NetWare Loadable Module)
2   Copyright 1993, 1994, 2000, 2001, 2002, 2003
3   Free Software Foundation, Inc.
4
5This file is part of BFD, the Binary File Descriptor library.
6
7This program is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 2 of the License, or
10(at your option) any later version.
11
12This program is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with this program; if not, write to the Free Software
19Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
20
21#include "bfd.h"
22#include "sysdep.h"
23#include "libbfd.h"
24
25#define ARCH_SIZE 32
26
27#include "nlm/i386-ext.h"
28#define Nlm_External_Fixed_Header	Nlm32_i386_External_Fixed_Header
29
30#include "libnlm.h"
31
32static bfd_boolean nlm_i386_read_reloc
33  PARAMS ((bfd *, nlmNAME(symbol_type) *, asection **, arelent *));
34static bfd_boolean nlm_i386_write_import
35  PARAMS ((bfd *, asection *, arelent *));
36static bfd_boolean nlm_i386_mangle_relocs
37  PARAMS ((bfd *, asection *, const PTR, bfd_vma, bfd_size_type));
38static bfd_boolean nlm_i386_read_import
39  PARAMS ((bfd *, nlmNAME(symbol_type) *));
40static bfd_boolean nlm_i386_write_external
41  PARAMS ((bfd *, bfd_size_type, asymbol *, struct reloc_and_sec *));
42
43/* Adjust the reloc location by an absolute value.  */
44
45static reloc_howto_type nlm_i386_abs_howto =
46  HOWTO (0,			/* type */
47	 0,			/* rightshift */
48	 2,			/* size (0 = byte, 1 = short, 2 = long) */
49	 32,			/* bitsize */
50	 FALSE,			/* pc_relative */
51	 0,			/* bitpos */
52	 complain_overflow_bitfield, /* complain_on_overflow */
53	 0,			/* special_function */
54	 "32",			/* name */
55	 TRUE,			/* partial_inplace */
56	 0xffffffff,		/* src_mask */
57	 0xffffffff,		/* dst_mask */
58	 FALSE);		/* pcrel_offset */
59
60/* Adjust the reloc location by a PC relative displacement.  */
61
62static reloc_howto_type nlm_i386_pcrel_howto =
63  HOWTO (1,			/* type */
64	 0,			/* rightshift */
65	 2,			/* size (0 = byte, 1 = short, 2 = long) */
66	 32,			/* bitsize */
67	 TRUE,			/* pc_relative */
68	 0,			/* bitpos */
69	 complain_overflow_signed, /* complain_on_overflow */
70	 0,			/* special_function */
71	 "DISP32",		/* name */
72	 TRUE,			/* partial_inplace */
73	 0xffffffff,		/* src_mask */
74	 0xffffffff,		/* dst_mask */
75	 TRUE);			/* pcrel_offset */
76
77/* Read a NetWare i386 reloc.  */
78
79static bfd_boolean
80nlm_i386_read_reloc (abfd, sym, secp, rel)
81     bfd *abfd;
82     nlmNAME(symbol_type) *sym;
83     asection **secp;
84     arelent *rel;
85{
86  bfd_byte temp[4];
87  bfd_vma val;
88  const char *name;
89
90  if (bfd_bread (temp, (bfd_size_type) sizeof (temp), abfd) != sizeof (temp))
91    return FALSE;
92
93  val = bfd_get_32 (abfd, temp);
94
95  /* The value is an offset into either the code or data segment.
96     This is the location which needs to be adjusted.
97
98     If this is a relocation fixup rather than an imported symbol (the
99     sym argument is NULL) then the high bit is 0 if the location
100     needs to be adjusted by the address of the data segment, or 1 if
101     the location needs to be adjusted by the address of the code
102     segment.  If this is an imported symbol, then the high bit is 0
103     if the location is 0 if the location should be adjusted by the
104     offset to the symbol, or 1 if the location should adjusted by the
105     absolute value of the symbol.
106
107     The second most significant bit is 0 if the value is an offset
108     into the data segment, or 1 if the value is an offset into the
109     code segment.
110
111     All this translates fairly easily into a BFD reloc.  */
112
113  if (sym == NULL)
114    {
115      if ((val & NLM_HIBIT) == 0)
116	name = NLM_INITIALIZED_DATA_NAME;
117      else
118	{
119	  name = NLM_CODE_NAME;
120	  val &=~ NLM_HIBIT;
121	}
122      rel->sym_ptr_ptr = bfd_get_section_by_name (abfd, name)->symbol_ptr_ptr;
123      rel->howto = &nlm_i386_abs_howto;
124    }
125  else
126    {
127      /* In this case we do not need to set the sym_ptr_ptr field.  */
128      rel->sym_ptr_ptr = NULL;
129      if ((val & NLM_HIBIT) == 0)
130	rel->howto = &nlm_i386_pcrel_howto;
131      else
132	{
133	  rel->howto = &nlm_i386_abs_howto;
134	  val &=~ NLM_HIBIT;
135	}
136    }
137
138  if ((val & (NLM_HIBIT >> 1)) == 0)
139    *secp = bfd_get_section_by_name (abfd, NLM_INITIALIZED_DATA_NAME);
140  else
141    {
142      *secp = bfd_get_section_by_name (abfd, NLM_CODE_NAME);
143      val &=~ (NLM_HIBIT >> 1);
144    }
145
146  rel->address = val;
147  rel->addend = 0;
148
149  return TRUE;
150}
151
152/* Write a NetWare i386 reloc.  */
153
154static bfd_boolean
155nlm_i386_write_import (abfd, sec, rel)
156     bfd *abfd;
157     asection *sec;
158     arelent *rel;
159{
160  asymbol *sym;
161  bfd_vma val;
162  bfd_byte temp[4];
163
164  /* NetWare only supports two kinds of relocs.  We should check
165     special_function here, as well, but at the moment coff-i386
166     relocs uses a special_function which does not affect what we do
167     here.  */
168  if (rel->addend != 0
169      || rel->howto == NULL
170      || rel->howto->rightshift != 0
171      || rel->howto->size != 2
172      || rel->howto->bitsize != 32
173      || rel->howto->bitpos != 0
174      || rel->howto->src_mask != 0xffffffff
175      || rel->howto->dst_mask != 0xffffffff)
176    {
177      bfd_set_error (bfd_error_invalid_operation);
178      return FALSE;
179    }
180
181  sym = *rel->sym_ptr_ptr;
182
183  /* The value we write out is the offset into the appropriate
184     segment.  This offset is the section vma, adjusted by the vma of
185     the lowest section in that segment, plus the address of the
186     relocation.  */
187  val = bfd_get_section_vma (abfd, sec) + rel->address;
188
189  /* The second most significant bit is 0 if the value is an offset
190     into the data segment, or 1 if the value is an offset into the
191     code segment.  */
192  if (bfd_get_section_flags (abfd, sec) & SEC_CODE)
193    {
194      val -= nlm_get_text_low (abfd);
195      val |= NLM_HIBIT >> 1;
196    }
197  else
198    val -= nlm_get_data_low (abfd);
199
200  if (! bfd_is_und_section (bfd_get_section (sym)))
201    {
202      /* NetWare only supports absolute internal relocs.  */
203      if (rel->howto->pc_relative)
204	{
205	  bfd_set_error (bfd_error_invalid_operation);
206	  return FALSE;
207	}
208
209      /* The high bit is 1 if the reloc is against the code section, 0
210	 if against the data section.  */
211      if (bfd_get_section_flags (abfd, bfd_get_section (sym)) & SEC_CODE)
212	val |= NLM_HIBIT;
213    }
214  else
215    {
216      /* The high bit is 1 if this is an absolute reloc, 0 if it is PC
217	 relative.  */
218      if (! rel->howto->pc_relative)
219	val |= NLM_HIBIT;
220      else
221	{
222	  /* PC relative relocs on NetWare must be pcrel_offset.  */
223	  if (! rel->howto->pcrel_offset)
224	    {
225	      bfd_set_error (bfd_error_invalid_operation);
226	      return FALSE;
227	    }
228	}
229    }
230
231  bfd_put_32 (abfd, val, temp);
232  if (bfd_bwrite (temp, (bfd_size_type) sizeof (temp), abfd) != sizeof (temp))
233    return FALSE;
234
235  return TRUE;
236}
237
238/* I want to be able to use objcopy to turn an i386 a.out or COFF file
239   into a NetWare i386 module.  That means that the relocs from the
240   source file have to be mapped into relocs that apply to the target
241   file.  This function is called by nlm_set_section_contents to give
242   it a chance to rework the relocs.
243
244   This is actually a fairly general concept.  However, this is not a
245   general implementation.  */
246
247static bfd_boolean
248nlm_i386_mangle_relocs (abfd, sec, data, offset, count)
249     bfd *abfd;
250     asection *sec;
251     const PTR data;
252     bfd_vma offset;
253     bfd_size_type count;
254{
255  arelent **rel_ptr_ptr, **rel_end;
256
257  rel_ptr_ptr = sec->orelocation;
258  rel_end = rel_ptr_ptr + sec->reloc_count;
259  for (; rel_ptr_ptr < rel_end; rel_ptr_ptr++)
260    {
261      arelent *rel;
262      asymbol *sym;
263      bfd_vma addend;
264
265      rel = *rel_ptr_ptr;
266      sym = *rel->sym_ptr_ptr;
267
268      /* Note that no serious harm will ensue if we fail to change a
269	 reloc.  We will wind up failing in nlm_i386_write_import.  */
270
271      /* Make sure this reloc is within the data we have.  We only 4
272	 byte relocs here, so we insist on having 4 bytes.  */
273      if (rel->address < offset
274	  || rel->address + 4 > offset + count)
275	continue;
276
277      /* NetWare doesn't support reloc addends, so we get rid of them
278	 here by simply adding them into the object data.  We handle
279	 the symbol value, if any, the same way.  */
280      addend = rel->addend + sym->value;
281
282      /* The value of a symbol is the offset into the section.  If the
283	 symbol is in the .bss segment, we need to include the size of
284	 the data segment in the offset as well.  Fortunately, we know
285	 that at this point the size of the data section is in the NLM
286	 header.  */
287      if (((bfd_get_section_flags (abfd, bfd_get_section (sym))
288	    & SEC_LOAD) == 0)
289	  && ((bfd_get_section_flags (abfd, bfd_get_section (sym))
290	       & SEC_ALLOC) != 0))
291	addend += nlm_fixed_header (abfd)->dataImageSize;
292
293      if (addend != 0
294	  && rel->howto != NULL
295	  && rel->howto->rightshift == 0
296	  && rel->howto->size == 2
297	  && rel->howto->bitsize == 32
298	  && rel->howto->bitpos == 0
299	  && rel->howto->src_mask == 0xffffffff
300	  && rel->howto->dst_mask == 0xffffffff)
301	{
302	  bfd_vma val;
303
304	  val = bfd_get_32 (abfd, (bfd_byte *) data + rel->address - offset);
305	  val += addend;
306	  bfd_put_32 (abfd, val, (bfd_byte *) data + rel->address - offset);
307	  rel->addend = 0;
308	}
309
310      /* NetWare uses a reloc with pcrel_offset set.  We adjust
311	 pc_relative relocs accordingly.  We are going to change the
312	 howto field, so we can only do this if the current one is
313	 compatible.  We should check special_function here, but at
314	 the moment coff-i386 uses a special_function which does not
315	 affect what we are doing here.  */
316      if (rel->howto != NULL
317	  && rel->howto->pc_relative
318	  && ! rel->howto->pcrel_offset
319	  && rel->howto->rightshift == 0
320	  && rel->howto->size == 2
321	  && rel->howto->bitsize == 32
322	  && rel->howto->bitpos == 0
323	  && rel->howto->src_mask == 0xffffffff
324	  && rel->howto->dst_mask == 0xffffffff)
325	{
326	  bfd_vma val;
327
328	  /* When pcrel_offset is not set, it means that the negative
329	     of the address of the memory location is stored in the
330	     memory location.  We must add it back in.  */
331	  val = bfd_get_32 (abfd, (bfd_byte *) data + rel->address - offset);
332	  val += rel->address;
333	  bfd_put_32 (abfd, val, (bfd_byte *) data + rel->address - offset);
334
335	  rel->howto = &nlm_i386_pcrel_howto;
336	}
337    }
338
339  return TRUE;
340}
341
342/* Read a NetWare i386 import record */
343static bfd_boolean
344nlm_i386_read_import (abfd, sym)
345     bfd *abfd;
346     nlmNAME(symbol_type) *sym;
347{
348  struct nlm_relent *nlm_relocs;	/* relocation records for symbol */
349  bfd_size_type rcount;			/* number of relocs */
350  bfd_byte temp[NLM_TARGET_LONG_SIZE];	/* temporary 32-bit value */
351  unsigned char symlength;		/* length of symbol name */
352  char *name;
353
354  if (bfd_bread ((PTR) &symlength, (bfd_size_type) sizeof (symlength), abfd)
355      != sizeof (symlength))
356    return FALSE;
357  sym -> symbol.the_bfd = abfd;
358  name = bfd_alloc (abfd, (bfd_size_type) symlength + 1);
359  if (name == NULL)
360    return FALSE;
361  if (bfd_bread (name, (bfd_size_type) symlength, abfd) != symlength)
362    return FALSE;
363  name[symlength] = '\0';
364  sym -> symbol.name = name;
365  sym -> symbol.flags = 0;
366  sym -> symbol.value = 0;
367  sym -> symbol.section = bfd_und_section_ptr;
368  if (bfd_bread ((PTR) temp, (bfd_size_type) sizeof (temp), abfd)
369      != sizeof (temp))
370    return FALSE;
371  rcount = H_GET_32 (abfd, temp);
372  nlm_relocs = ((struct nlm_relent *)
373		bfd_alloc (abfd, rcount * sizeof (struct nlm_relent)));
374  if (!nlm_relocs)
375    return FALSE;
376  sym -> relocs = nlm_relocs;
377  sym -> rcnt = 0;
378  while (sym -> rcnt < rcount)
379    {
380      asection *section;
381
382      if (! nlm_i386_read_reloc (abfd, sym, &section, &nlm_relocs -> reloc))
383	return FALSE;
384      nlm_relocs -> section = section;
385      nlm_relocs++;
386      sym -> rcnt++;
387    }
388  return TRUE;
389}
390
391/* Write out an external reference.  */
392
393static bfd_boolean
394nlm_i386_write_external (abfd, count, sym, relocs)
395     bfd *abfd;
396     bfd_size_type count;
397     asymbol *sym;
398     struct reloc_and_sec *relocs;
399{
400  unsigned int i;
401  bfd_byte len;
402  unsigned char temp[NLM_TARGET_LONG_SIZE];
403
404  len = strlen (sym->name);
405  if ((bfd_bwrite (&len, (bfd_size_type) sizeof (bfd_byte), abfd)
406       != sizeof (bfd_byte))
407      || bfd_bwrite (sym->name, (bfd_size_type) len, abfd) != len)
408    return FALSE;
409
410  bfd_put_32 (abfd, count, temp);
411  if (bfd_bwrite (temp, (bfd_size_type) sizeof (temp), abfd) != sizeof (temp))
412    return FALSE;
413
414  for (i = 0; i < count; i++)
415    {
416      if (! nlm_i386_write_import (abfd, relocs[i].sec, relocs[i].rel))
417	return FALSE;
418    }
419
420  return TRUE;
421}
422
423#include "nlmswap.h"
424
425static const struct nlm_backend_data nlm32_i386_backend =
426{
427  "NetWare Loadable Module\032",
428  sizeof (Nlm32_i386_External_Fixed_Header),
429  0,	/* optional_prefix_size */
430  bfd_arch_i386,
431  0,
432  FALSE,
433  0,	/* backend_object_p */
434  0,	/* write_prefix_func */
435  nlm_i386_read_reloc,
436  nlm_i386_mangle_relocs,
437  nlm_i386_read_import,
438  nlm_i386_write_import,
439  0,	/* set_public_section */
440  0,	/* get_public_offset */
441  nlm_swap_fixed_header_in,
442  nlm_swap_fixed_header_out,
443  nlm_i386_write_external,
444  0,	/* write_export */
445};
446
447#define TARGET_LITTLE_NAME		"nlm32-i386"
448#define TARGET_LITTLE_SYM		nlmNAME(i386_vec)
449#define TARGET_BACKEND_DATA		&nlm32_i386_backend
450
451#include "nlm-target.h"
452