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