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