summaryrefslogtreecommitdiff
path: root/usr/src/cmd/sgs/libld/common/order.c
blob: ad3fa467e242af5d53e88c7d22dc9d9488cf2efc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Processing of SHF_ORDERED sections.
 */
#include	<stdio.h>
#include	<fcntl.h>
#include	<link.h>
#include	<debug.h>
#include	"msg.h"
#include	"_libld.h"

/*
 * Section Ordering History/Background:
 *
 * There are two forms of section ordering, SHF_ORDERED, and SHF_LINK_ORDER.
 *
 * SHF_ORDERED was invented at Sun in order to support the PowerPC port
 * of Solaris 2.6, which used it for sorting tag words which describe
 * the state of callee saves registers for given PC ranges. It was defined
 * in the OS specific ELF section flag range. Some other values were defined
 * at the same time:
 *	SHF_EXCLUDE - Section is to be excluded from executables or shared
 *		objects, and only kept in relocatable object output.
 *	SHN_BEFORE/SHN_AFTER - Sections are placed before/after all other
 *		sections, in the order they are encountered by the linker.
 * Although initially conceived to support the PowerPC, the functionality
 * was implemented for all platforms, and was later used to manage C++
 * exceptions and stack unwinding. The PowerPC port was discontinued after
 * one release, but SHF_ORDERED lives on.
 *
 * SHF_LINK_ORDER was invented later by the wider ELF community, and is
 * therefore assigned a value in the generic ELF section flag range. It is
 * essentially a simpler version of SHF_ORDERED, dispensing with some
 * unnecessary features. The Solaris implementation of SHF_LINK_ORDER uses
 * SHF_EXCLUDE, and SHF_BEFORE/SHN_AFTER as well, but it appears that these
 * are still Solaris-only extensions not used by other implementations.
 * SHF_LINK_ORDER has superseded SHF_ORDERED. The older mechanism is
 * supported for the benefit of old pre-existing objects.
 *
 * -----
 *
 * SHF_ORDERED offers two distinct and separate abilities:
 *
 *	(1) To specify the output section
 *	(2) To optionally be sorted relative to other sorted sections,
 *		using a non-sorted section as a sort key.
 *
 * To  do this, it uses both the sh_link, and sh_info fields:
 *
 *    sh_link
 *	Specifies the output section to receive this input section.
 *	The sh_link field of an SHF_ORDERED section forms a linked list of
 *	sections, all of which must have identical section header flags
 *	(including SHF_ORDERED). The list is terminated by a final section
 *	with a sh_link that points at itself. All input sections in this list
 *	are assigned to the output section of the final section in the list.
 *	Hence, if a section points at itself, the effect is that it gets
 *	assigned to an output section in the usual default manner (i.e. an
 *	output section with the same name as the input). However, it can
 *	point at any arbitrary other section. This is a way to put a section
 *	with one name into an output section with a different name. It should
 *	be noted that this is of little value overall, because the link-editor
 *	already supports a more general feature for directing input sections
 *	to output sections: An input section named .text%foo will be sent to
 *	an output section named ".text", and this works for all sections,
 *	not just ordered ones.
 *
 *    sh_info
 *	If sh_info is in the range (1 <= value < shnum), then this input section
 *	is added to the group of sorted sections. The section referenced by
 *	sh_info must be unsorted, and is used as the sort key.
 *
 *	If sh_info is SHN_BEFORE or SHN_AFTER, it is put in the pre/post group,
 *	in the order it arrives (the before/after classes are not sorted).
 *
 *	If sh_info is "invalid" (typically 0), then this section is added to
 *	the group of non-sorted sections, and goes into the output file in the
 *	order it arrives. This is not a valuable feature, as the same effect
 *	can be achieved more simply by not setting SHF_ORDERED at all.
 *
 * SHF_LINK_ORDER is a simplification of SHF_ORDERED. It uses sh_link to specify
 * the section to use as a sort key and sh_info is set to 0. The standard
 * ".text%foo" mechanism is used to direct input sections to output sections,
 * and unordered sections indicate that by not setting SHF_LINK_ORDER.
 */


/*
 * A "keyshndx" is the section index for the unordered section that should
 * be used as a sort key for a ordered section. Verify that the given
 * keyshndx is valid.
 *
 * exit:
 *	Returns 0 if the keyshndx is valid. A non-zero DBG_ORDER_ code is
 *	returned if the keyshndx is not valid to describe the problem.
 */
inline static Word
is_keyshndx_ok(Ifl_desc *ifl, Word keyshndx)
{
	if ((keyshndx == SHN_BEFORE) || (keyshndx == SHN_AFTER))
		return (0);

	/*
	 * Validate the key range.
	 */
	if ((keyshndx == 0) || (keyshndx >= ifl->ifl_shnum))
		return (DBG_ORDER_LINK_OUTRANGE);

	/*
	 * The section pointed to by keyshndx should not be an ordered section.
	 * Strictly speaking, we could test for SHF_ORDERED here instead of
	 * ALL_SHF_ORDER as the two ordering flags are not supposed to be
	 * mixed. Using ALL_SHF_ORDER costs the same and ensures that such
	 * mixing doesn't go undetected.
	 */
	if (ifl->ifl_isdesc[keyshndx]->is_shdr->sh_flags & ALL_SHF_ORDER)
		return (DBG_ORDER_INFO_ORDER);

	return (0);
}

/*
 * The sh_link field of an SHF_ORDERED section forms a linked list of
 * sections. The list is terminated by a final section with a sh_link
 * that points at itself. Given the index of an SHF_ORDERED section, find
 * the index of the final section in the list.
 *
 * entry:
 *	ofl - Output file descriptor
 *	ifl - Input file descriptor
 *	ndx - Section index of SHF_ORDERED section
 *	alt_os_name - Address of pointer to string. If the final section
 *		name is different than the section given by ndx, *alt_os_name
 *		will be updated with the name of the final section. The caller
 *		should initialize *alt_os_name to NULL before calling
 *		this routine.
 *
 * exit:
 *	On success: If the final section is different than the section
 *	given by ndx, then *alt_os_name is set to its name. TRUE is returned.
 *
 *	On failure, FALSE is returned.
 */
static Boolean
validate_shf_ordered_dest(Ofl_desc *ofl, Ifl_desc *ifl, Word ndx,
    const char **alt_os_name)
{
	Word	shnum = ifl->ifl_shnum;
	Word	isp1_ndx, isp2_ndx;
	Is_desc	*isp1, *isp2;
	int	error = 0;
	size_t	iter = 0;

	/*
	 * Traverse the list until we find the termination, or encounter
	 * an invalid condition in the object that prevents ordering.
	 */
	isp1_ndx = ndx;
	isp1 = ifl->ifl_isdesc[ndx];
	do {
		/*
		 * Obtain index of next section in list. Ensure it is in range.
		 */
		isp2_ndx = isp1->is_shdr->sh_link;
		if ((isp2_ndx == 0) || (isp2_ndx >= shnum)) {
			error = DBG_ORDER_LINK_OUTRANGE;
			break;
		}
		isp2 = ifl->ifl_isdesc[isp2_ndx];

		/* The section flags must match exactly */
		if (isp1->is_shdr->sh_flags != isp2->is_shdr->sh_flags) {
			/*
			 * The case where the next section in the list does
			 * not have the same ordered flag set as the original
			 * ordered section gets a unique error code. This
			 * provides more accurate/useful debugging diagnostics.
			 */
			error = ((isp2->is_flags & FLG_IS_ORDERED) == 0) ?
			    DBG_ORDER_LINK_ERROR : DBG_ORDER_FLAGS;
			break;
		}

		/*
		 * The sh_info field specifies the section index of an
		 * unorderd section which will be used as a sort key.
		 * Ensure it is in range. If not, we terminate the list
		 * at the current node instead of continuing on.
		 */
		if ((error = is_keyshndx_ok(ifl, isp2->is_shdr->sh_info)) != 0)
			break;

		/* If the section points at itself, it terminates the list */
		if (isp1_ndx == isp2_ndx)
			break;

		/*
		 * Advance to next section in list
		 */
		isp1_ndx = isp2_ndx;
		isp1 = isp2;

		/*
		 * If we loop more times than the input file has sections,
		 * we have encountered a malformed object in which the list
		 * of SHF_ORDERED sections has a cycle. This can only happen
		 * if the compiler generating the object has a bad bug.
		 */
		if (++iter >= shnum) {
			error = DBG_ORDER_CYCLIC;
			break;
		}
	/* CONSTANTCONDITION */
	} while (1);

	/*
	 * If we have found a problem, issue a debug diagnostic and map
	 * the output section to 0. This indicates that the section should
	 * remove the ordering flag and treat it as a standard section.
	 */
	if (error != 0) {
		isp2_ndx = 0;
		DBG_CALL(Dbg_sec_order_error(ofl->ofl_lml, ifl, ndx, error));
	}

	/* Report success */
	if (isp2_ndx != 0) {
		/*
		 * If the destination section is different than the input
		 * section, then set *alt_os_name to the destination name.
		 */
		if (isp2_ndx != ndx)
			*alt_os_name = ifl->ifl_isdesc[isp2_ndx]->is_name;
		return (TRUE);
	}

	/* If we get here, there is no valid destination */
	return (FALSE);
}

/*
 * Called when an ordered section has a problem that prevents ordering.
 * The order flag is removed, and then the section is placed as an
 * unsorted section.
 */
static uintptr_t
place_unordered(Ofl_desc *ofl, Is_desc *isp, Place_path_info *path_info)
{
	isp->is_flags &= ~FLG_IS_ORDERED;
	if (isp->is_osdesc == NULL)
		return ((uintptr_t)ld_place_section(ofl, isp, path_info,
		    isp->is_keyident, NULL));
	return ((uintptr_t)isp->is_osdesc);
}

/*
 * Process ordered input section. Called from process_elf() after
 * all the non-ordered sections have been placed.
 *
 * entry:
 *	ofl - Output file descriptor
 *	ifl - Input file descriptor
 *	ndx - Section index of SHF_ORDERED section
 *
 * exit:
 */
uintptr_t
ld_process_ordered(Ofl_desc *ofl, Ifl_desc *ifl, Place_path_info *path_info,
    Word ndx)
{
	Is_desc		*isp2, *isp = ifl->ifl_isdesc[ndx];
	Xword		shflags = isp->is_shdr->sh_flags;
	const char	*alt_os_name = NULL;
	Word		keyshndx;
	Os_desc		*osp;
	int		error = 0;

	/*
	 * Obtain the sort key section index for this ordered section.
	 * SHF_ORDERED uses sh_info, while SHF_LINK_ORDER uses sh_link.
	 * In order for this function to be called, one of SHF_ORDERED
	 * or SHF_LINK_ORDER must be set. Testing for one implies the
	 * state of the other.
	 */
	keyshndx = (shflags & SHF_ORDERED) ?
	    isp->is_shdr->sh_info : isp->is_shdr->sh_link;

	/*
	 * Validate the sort key section index. If something is wrong,
	 * fall back to treating it as a non-ordered section.
	 */
	if ((error = is_keyshndx_ok(ifl, keyshndx)) != 0) {
		DBG_CALL(Dbg_sec_order_error(ofl->ofl_lml, ifl, ndx, error));
		return (place_unordered(ofl, isp, path_info));
	}

	/*
	 * If SHF_ORDERED is in effect, validate the destination section
	 * name given by sh_link, and set alt_os_name to the name of the
	 * destination if it differs from the section being processed.
	 */
	if ((shflags & SHF_ORDERED) &&
	    (validate_shf_ordered_dest(ofl, ifl, ndx, &alt_os_name) == FALSE))
		return (place_unordered(ofl, isp, path_info));

	/*
	 * Place the section into its output section. It's possible that this
	 * section is discarded (possibly because it's defined COMDAT), in
	 * which case we're done.
	 */
	if ((osp = isp->is_osdesc) == NULL) {
		osp = ld_place_section(ofl, isp, path_info, isp->is_keyident,
		    alt_os_name);
		if ((osp == (Os_desc *)S_ERROR) || (osp == NULL))
			return ((uintptr_t)osp);
	}

	/*
	 * If the output section is not yet on the ordered list, place it on
	 * the list.
	 */
	if (aplist_test(&ofl->ofl_ordered, osp, AL_CNT_OFL_ORDERED) ==
	    ALE_ALLOCFAIL)
		return ((uintptr_t)S_ERROR);

	/*
	 * Output section has been found - set up its sorting information.
	 */
	if ((keyshndx != SHN_BEFORE) && (keyshndx != SHN_AFTER)) {
		Os_desc		*osp2;

		isp2 = ifl->ifl_isdesc[keyshndx];
		if (isp2->is_flags & FLG_IS_DISCARD) {
			ld_eprintf(ofl, ERR_FATAL, MSG_INTL(MSG_FIL_BADORDREF),
			    ifl->ifl_name, EC_WORD(isp->is_scnndx),
			    isp->is_name, EC_WORD(isp2->is_scnndx),
			    isp2->is_name);
			return (S_ERROR);
		}

		/*
		 * Indicate that this ordered input section will require a
		 * sort key.  Propagate the key requirement through to the
		 * associated output section, segment and file, to trigger
		 * the sort key creation.  See ld_sec_validate();
		 */
		isp2->is_flags |= FLG_IS_KEY;

		osp2 = isp2->is_osdesc;
		osp2->os_flags |= FLG_OS_KEY;
		osp2->os_sgdesc->sg_flags |= FLG_SG_KEY;

		ofl->ofl_flags |= FLG_OF_KEY;
	}

	return ((uintptr_t)osp);
}

/*
 * Traverse all segments looking for section ordering information that hasn't
 * been used.  If found give a warning message to the user.  Also, check if
 * there are any ordered key sections, and if so set up sort key values.
 */
void
ld_sec_validate(Ofl_desc *ofl)
{
	Aliste		idx1;
	Sg_desc		*sgp;
	Word 		key = 1;

	for (APLIST_TRAVERSE(ofl->ofl_segs, idx1, sgp)) {
		Sec_order	*scop;
		Os_desc		*osp;
		Aliste		idx2;

		for (ALIST_TRAVERSE(sgp->sg_os_order, idx2, scop)) {
			if ((scop->sco_flags & FLG_SGO_USED) == 0) {
				ld_eprintf(ofl, ERR_WARNING,
				    MSG_INTL(MSG_MAP_SECORDER),
				    sgp->sg_name, scop->sco_secname);
			}
		}
		if ((sgp->sg_flags & FLG_SG_KEY) == 0)
			continue;

		for (APLIST_TRAVERSE(sgp->sg_osdescs, idx2, osp)) {
			Aliste	idx3;
			Is_desc	*isp;

			if ((osp->os_flags & FLG_OS_KEY) == 0)
				continue;

			/*
			 * The input sections used as sort keys are required
			 * to be unordered, so we only have to look at the
			 * DEFAULT list of input sections.
			 */
			for (APLIST_TRAVERSE(osp->os_isdescs[OS_ISD_DEFAULT],
			    idx3, isp)) {
				if (isp->is_flags & FLG_IS_KEY)
					isp->is_keyident = key++;
			}
		}
	}
}

static int
comp(const void *ss1, const void *ss2)
{
	Is_desc		*s1 = *((Is_desc **)ss1);
	Is_desc		*s2 = *((Is_desc **)ss2);
	Is_desc		*i1, *i2;
	Word		ndx1, ndx2;

	if (s1->is_shdr->sh_flags & SHF_ORDERED)
		ndx1 = s1->is_shdr->sh_info;
	else
		ndx1 = s1->is_shdr->sh_link;

	if (s2->is_shdr->sh_flags & SHF_ORDERED)
		ndx2 = s2->is_shdr->sh_info;
	else
		ndx2 = s2->is_shdr->sh_link;

	i1 = s1->is_file->ifl_isdesc[ndx1];
	i2 = s2->is_file->ifl_isdesc[ndx2];

	if (i1->is_keyident > i2->is_keyident)
		return (1);
	if (i1->is_keyident < i2->is_keyident)
		return (-1);
	return (0);
}

/*
 * Sort ordered input sections
 */
uintptr_t
ld_sort_ordered(Ofl_desc *ofl)
{
	Aliste	idx1;
	Os_desc *osp;

	DBG_CALL(Dbg_sec_order_list(ofl, 0));

	for (APLIST_TRAVERSE(ofl->ofl_ordered, idx1, osp)) {
		APlist	*ap_list = osp->os_isdescs[OS_ISD_ORDERED];
		Aliste	apl_nitems = aplist_nitems(ap_list);

		/*
		 * If this output section has a non-empty list of ordered
		 * input sections, sort their APlist in place into their
		 * final order.
		 */
		if (apl_nitems != 0)
			qsort((char *)ap_list->apl_data, apl_nitems,
			    sizeof (Is_desc *), comp);
	}
	DBG_CALL(Dbg_sec_order_list(ofl, 1));
	return (0);
}