1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright (c) 1988 AT&T 24 * All Rights Reserved 25 * 26 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 27 * Use is subject to license terms. 28 */ 29 #pragma ident "%Z%%M% %I% %E% SMI" 30 31 /* 32 * Map file parsing and input section to output segment mapping. 33 */ 34 #include <stdio.h> 35 #include <string.h> 36 #include <debug.h> 37 #include "msg.h" 38 #include "_libld.h" 39 40 /* 41 * Each time a section is placed, the function set_addralign() 42 * is called. This function performs: 43 * 44 * . if the section is from an external file, check if this is empty or not. 45 * If not, we know the segment this section will belong needs a program 46 * header. (Of course, the program is needed only if this section falls 47 * into a loadable segment.) 48 * . compute the Least Common Multiplier for setting the segment alignment. 49 */ 50 static void 51 set_addralign(Ofl_desc *ofl, Os_desc *osp, Is_desc *isp) 52 { 53 Shdr * shdr = isp->is_shdr; 54 55 /* A discarded section has no influence on the output */ 56 if (isp->is_flags & FLG_IS_DISCARD) 57 return; 58 59 /* 60 * If this section has data or will be assigned data 61 * later, mark this segment not-empty. 62 */ 63 if ((shdr->sh_size != 0) || 64 ((isp->is_flags & FLG_IS_EXTERNAL) == 0)) 65 osp->os_sgdesc->sg_flags |= FLG_SG_PHREQ; 66 67 if ((ofl->ofl_dtflags_1 & DF_1_NOHDR) && 68 (osp->os_sgdesc->sg_phdr).p_type != PT_LOAD) 69 return; 70 71 osp->os_sgdesc->sg_addralign = 72 ld_lcm(osp->os_sgdesc->sg_addralign, shdr->sh_addralign); 73 } 74 75 /* 76 * Append an input section to an output section 77 * 78 * entry: 79 * ofl - File descriptor 80 * isp - Input section descriptor 81 * osp - Output section descriptor 82 * mstr_only - True if should only append to the merge string section 83 * list. 84 * 85 * exit: 86 * - If mstr_only is not true, the input section is appended to the 87 * end of the output section's list of input sections (os_isdescs). 88 * - If the input section is a candidate for string table merging, 89 * then it is appended to the output section's list of merge 90 * candidates (os_mstridescs). 91 * 92 * On success, returns True (1). On failure, False (0). 93 */ 94 int 95 ld_append_isp(Ofl_desc * ofl, Os_desc *osp, Is_desc *isp, int mstr_only) 96 { 97 if (!mstr_only && (list_appendc(&(osp->os_isdescs), isp) == 0)) 98 return (0); 99 100 /* 101 * To be mergeable: 102 * - The SHF_MERGE|SHF_STRINGS flags must be set 103 * - String table compression must not be disabled (-znocompstrtab) 104 * - It must not be the generated section being built to 105 * replace the sections on this list. 106 */ 107 if (((isp->is_shdr->sh_flags & (SHF_MERGE | SHF_STRINGS)) != 108 (SHF_MERGE | SHF_STRINGS)) || 109 ((ofl->ofl_flags1 & FLG_OF1_NCSTTAB) != 0) || 110 ((isp->is_flags & FLG_IS_GNSTRMRG) != 0)) 111 return (1); 112 113 /* 114 * Skip sections with (sh_entsize > 1) or (sh_addralign > 1). 115 * 116 * sh_entsize: 117 * We are currently only able to merge string tables containing 118 * strings with 1-byte (char) characters. Support for wide 119 * characters will require our string table compression code 120 * to be extended to handle larger character sizes. 121 * 122 * sh_addralign: 123 * Alignments greater than 1 would require our string table 124 * compression code to insert null bytes to move each 125 * string to the required alignment. 126 */ 127 if ((isp->is_shdr->sh_entsize > 1) || 128 (isp->is_shdr->sh_addralign > 1)) { 129 DBG_CALL(Dbg_sec_unsup_strmerge(ofl->ofl_lml, isp)); 130 return (1); 131 } 132 133 if (aplist_append(&osp->os_mstrisdescs, isp, 134 AL_CNT_OS_MSTRISDESCS) == NULL) 135 return (0); 136 137 /* 138 * The SHF_MERGE|SHF_STRINGS flags tell us that the program that 139 * created the section intended it to be mergeable. The 140 * FLG_IS_INSTRMRG flag says that we have done validity testing 141 * and decided that it is safe to act on that hint. 142 */ 143 isp->is_flags |= FLG_IS_INSTRMRG; 144 145 return (1); 146 } 147 148 /* 149 * Place a section into the appropriate segment. 150 */ 151 Os_desc * 152 ld_place_section(Ofl_desc * ofl, Is_desc * isp, int ident, Word link) 153 { 154 Listnode * lnp1, * lnp2; 155 Ent_desc * enp; 156 Sg_desc * sgp; 157 Os_desc *osp; 158 Aliste idx1, idx2; 159 int os_ndx; 160 Shdr * shdr = isp->is_shdr; 161 Xword shflagmask, shflags = shdr->sh_flags; 162 Ifl_desc * ifl = isp->is_file; 163 164 /* 165 * Define any sections that must be thought of as referenced. These 166 * sections may not be referenced externaly in a manner ld(1) can 167 * discover, but they must be retained (ie. not removed by -zignore). 168 */ 169 static const Msg RefSecs[] = { 170 MSG_SCN_INIT, /* MSG_ORIG(MSG_SCN_INIT) */ 171 MSG_SCN_FINI, /* MSG_ORIG(MSG_SCN_FINI) */ 172 MSG_SCN_EX_RANGES, /* MSG_ORIG(MSG_SCN_EX_RANGES) */ 173 MSG_SCN_EX_SHARED, /* MSG_ORIG(MSG_SCN_EX_SHARED) */ 174 MSG_SCN_CTORS, /* MSG_ORIG(MSG_SCN_CTORS) */ 175 MSG_SCN_DTORS, /* MSG_ORIG(MSG_SCN_DTORS) */ 176 MSG_SCN_EHFRAME, /* MSG_ORIG(MSG_SCN_EHFRAME) */ 177 MSG_SCN_EHFRAME_HDR, /* MSG_ORIG(MSG_SCN_EHFRAME_HDR) */ 178 MSG_SCN_JCR, /* MSG_ORIG(MSG_SCN_JCR) */ 179 0 180 }; 181 182 DBG_CALL(Dbg_sec_in(ofl->ofl_lml, isp)); 183 184 if ((shflags & SHF_GROUP) || (shdr->sh_type == SHT_GROUP)) { 185 Group_desc * gdesc; 186 187 if ((gdesc = ld_get_group(ofl, isp)) == (Group_desc *)S_ERROR) 188 return ((Os_desc *)S_ERROR); 189 190 if (gdesc) { 191 DBG_CALL(Dbg_sec_group(ofl->ofl_lml, isp, gdesc)); 192 193 /* 194 * If this group is marked as discarded, then this 195 * section needs to be discarded. 196 */ 197 if (gdesc->gd_flags & GRP_FLG_DISCARD) { 198 isp->is_flags |= FLG_IS_DISCARD; 199 /* 200 * Since we're discarding the section, we 201 * can skip assigning it to an output section. 202 * The exception is that if the user 203 * specifies -z relaxreloc, then 204 * we need to assign the output section so 205 * that the sloppy relocation logic will have 206 * the information necessary to do its work. 207 */ 208 if (!(ofl->ofl_flags1 & FLG_OF1_RLXREL)) 209 return ((Os_desc *)0); 210 } 211 } 212 213 /* 214 * SHT_GROUP sections can only be included into relocatable 215 * objects. 216 */ 217 if (shdr->sh_type == SHT_GROUP) { 218 if ((ofl->ofl_flags & FLG_OF_RELOBJ) == 0) { 219 isp->is_flags |= FLG_IS_DISCARD; 220 return ((Os_desc *)0); 221 } 222 } 223 } 224 225 /* 226 * Always assign SHF_TLS sections to the DATA segment (and then the 227 * PT_TLS embedded inside of there). 228 */ 229 if (shflags & SHF_TLS) 230 shflags |= SHF_WRITE; 231 232 /* 233 * Traverse the entrance criteria list searching for a segment that 234 * matches the input section we have. If an entrance criterion is set 235 * then there must be an exact match. If we complete the loop without 236 * finding a segment, then sgp will be NULL. 237 */ 238 sgp = NULL; 239 for (LIST_TRAVERSE(&ofl->ofl_ents, lnp1, enp)) { 240 if (enp->ec_segment && 241 (enp->ec_segment->sg_flags & FLG_SG_DISABLED)) 242 continue; 243 if (enp->ec_type && (enp->ec_type != shdr->sh_type)) 244 continue; 245 if (enp->ec_attrmask && 246 /* LINTED */ 247 (enp->ec_attrmask & enp->ec_attrbits) != 248 (enp->ec_attrmask & shflags)) 249 continue; 250 if (enp->ec_name && (strcmp(enp->ec_name, isp->is_name) != 0)) 251 continue; 252 if (enp->ec_files.head) { 253 char *file; 254 int found = 0; 255 256 if (isp->is_file == 0) 257 continue; 258 259 for (LIST_TRAVERSE(&(enp->ec_files), lnp2, file)) { 260 const char *name = isp->is_file->ifl_name; 261 262 if (file[0] == '*') { 263 const char *basename; 264 265 basename = strrchr(name, '/'); 266 if (basename == NULL) 267 basename = name; 268 else if (basename[1] != '\0') 269 basename++; 270 271 if (strcmp(&file[1], basename) == 0) { 272 found++; 273 break; 274 } 275 } else { 276 if (strcmp(file, name) == 0) { 277 found++; 278 break; 279 } 280 } 281 } 282 if (!found) 283 continue; 284 } 285 break; 286 } 287 288 if ((sgp = enp->ec_segment) == 0) 289 sgp = ((Ent_desc *)(ofl->ofl_ents.tail->data))->ec_segment; 290 291 isp->is_basename = isp->is_name; 292 293 /* 294 * Strip out the % from the section name in all cases except when '-r' 295 * is used without '-M', and '-r' is used with '-M' without 296 * the ?O flag. 297 */ 298 if (((ofl->ofl_flags & FLG_OF_RELOBJ) && 299 (sgp->sg_flags & FLG_SG_ORDER)) || 300 !(ofl->ofl_flags & FLG_OF_RELOBJ)) { 301 char *cp; 302 303 if ((cp = strchr(isp->is_name, '%')) != NULL) { 304 char *name; 305 size_t size = (size_t)(cp - isp->is_name); 306 307 if ((name = libld_malloc(size + 1)) == 0) 308 return ((Os_desc *)S_ERROR); 309 (void) strncpy(name, isp->is_name, size); 310 cp = name + size; 311 *cp = '\0'; 312 isp->is_name = name; 313 } 314 isp->is_txtndx = enp->ec_ndx; 315 } 316 317 /* 318 * Assign a hash value now that the section name has been finalized. 319 */ 320 isp->is_namehash = sgs_str_hash(isp->is_name); 321 322 if (sgp->sg_flags & FLG_SG_ORDER) 323 enp->ec_flags |= FLG_EC_USED; 324 325 /* 326 * If the link is not 0, then the input section is going to be appended 327 * to the output section. The append occurs at the input section 328 * pointed to by the link. 329 */ 330 if (link != 0) { 331 osp = isp->is_file->ifl_isdesc[link]->is_osdesc; 332 333 /* 334 * If this is a COMDAT section, then see if this 335 * section is a keeper and/or if it is to be discarded. 336 */ 337 if (shdr->sh_type == SHT_SUNW_COMDAT) { 338 Listnode * clist; 339 Is_desc * cisp; 340 341 for (LIST_TRAVERSE(&(osp->os_comdats), clist, cisp)) { 342 if (strcmp(isp->is_basename, cisp->is_basename)) 343 continue; 344 345 isp->is_flags |= FLG_IS_DISCARD; 346 isp->is_osdesc = osp; 347 DBG_CALL(Dbg_sec_discarded(ofl->ofl_lml, 348 isp, cisp)); 349 return (0); 350 } 351 352 /* 353 * This is a new COMDAT section - so keep it. 354 */ 355 if (list_appendc(&(osp->os_comdats), isp) == 0) 356 return ((Os_desc *)S_ERROR); 357 } 358 359 /* 360 * Set alignment 361 */ 362 set_addralign(ofl, osp, isp); 363 364 if (ld_append_isp(ofl, osp, isp, 0) == 0) 365 return ((Os_desc *)S_ERROR); 366 367 isp->is_osdesc = osp; 368 sgp = osp->os_sgdesc; 369 370 DBG_CALL(Dbg_sec_added(ofl->ofl_lml, osp, sgp)); 371 return (osp); 372 } 373 374 /* 375 * Determine if section ordering is turned on. If so, return the 376 * appropriate os_txtndx. This information is derived from the 377 * Sg_desc->sg_segorder list that was built up from the Mapfile. 378 */ 379 os_ndx = 0; 380 if (sgp->sg_secorder) { 381 Aliste idx; 382 Sec_order *scop; 383 384 for (APLIST_TRAVERSE(sgp->sg_secorder, idx, scop)) { 385 if (strcmp(scop->sco_secname, isp->is_name) == 0) { 386 scop->sco_flags |= FLG_SGO_USED; 387 os_ndx = scop->sco_index; 388 break; 389 } 390 } 391 } 392 393 /* 394 * Mask of section header flags to ignore when 395 * matching sections. We are more strict with 396 * relocatable objects, ignoring only the order 397 * flags, and keeping sections apart if they differ 398 * otherwise. This follows the policy that sections 399 * in a relative object should only be merged if their 400 * flags are the same, and avoids destroying information 401 * prematurely. For final products however, we ignore all 402 * flags that do not prevent a merge. 403 */ 404 shflagmask = (ofl->ofl_flags & FLG_OF_RELOBJ) 405 ? ALL_SHF_ORDER : ALL_SHF_IGNORE; 406 407 /* 408 * Traverse the input section list for the output section we have been 409 * assigned. If we find a matching section simply add this new section. 410 */ 411 idx2 = 0; 412 for (APLIST_TRAVERSE(sgp->sg_osdescs, idx1, osp)) { 413 Shdr *_shdr = osp->os_shdr; 414 415 if ((ident == osp->os_scnsymndx) && 416 (ident != ld_targ.t_id.id_rel) && 417 (isp->is_namehash == osp->os_namehash) && 418 (shdr->sh_type != SHT_GROUP) && 419 (shdr->sh_type != SHT_SUNW_dof) && 420 ((shdr->sh_type == _shdr->sh_type) || 421 ((shdr->sh_type == SHT_SUNW_COMDAT) && 422 (_shdr->sh_type == SHT_PROGBITS))) && 423 ((shflags & ~shflagmask) == 424 (_shdr->sh_flags & ~shflagmask)) && 425 (strcmp(isp->is_name, osp->os_name) == 0)) { 426 /* 427 * If this is a COMDAT section, determine if this 428 * section is a keeper, and/or if it is to be discarded. 429 */ 430 if (shdr->sh_type == SHT_SUNW_COMDAT) { 431 Listnode * clist; 432 Is_desc * cisp; 433 434 for (LIST_TRAVERSE(&(osp->os_comdats), 435 clist, cisp)) { 436 if (strcmp(isp->is_basename, 437 cisp->is_basename)) 438 continue; 439 440 isp->is_flags |= FLG_IS_DISCARD; 441 isp->is_osdesc = osp; 442 DBG_CALL(Dbg_sec_discarded(ofl->ofl_lml, 443 isp, cisp)); 444 return (0); 445 } 446 447 /* 448 * This is a new COMDAT section - so keep it. 449 */ 450 if (list_appendc(&(osp->os_comdats), isp) == 0) 451 return ((Os_desc *)S_ERROR); 452 } 453 454 /* 455 * Set alignment 456 */ 457 set_addralign(ofl, osp, isp); 458 459 /* 460 * If this section is a non-empty TLS section indicate 461 * that a PT_TLS program header is required. 462 */ 463 if ((shflags & SHF_TLS) && shdr->sh_size && 464 ((ofl->ofl_flags & FLG_OF_RELOBJ) == 0)) 465 ofl->ofl_flags |= FLG_OF_TLSPHDR; 466 467 /* 468 * If is_txtndx is 0 then this section was not 469 * seen in mapfile, so put it at the end. 470 * If is_txtndx is not 0 and ?O is turned on 471 * then check to see where this section should 472 * be inserted. 473 */ 474 if ((sgp->sg_flags & FLG_SG_ORDER) && isp->is_txtndx) { 475 Listnode * tlist; 476 477 tlist = list_where(&(osp->os_isdescs), 478 isp->is_txtndx); 479 if (tlist != NULL) { 480 if (list_insertc(&(osp->os_isdescs), 481 isp, tlist) == 0) 482 return ((Os_desc *)S_ERROR); 483 } else { 484 if (list_prependc(&(osp->os_isdescs), 485 isp) == 0) 486 return ((Os_desc *)S_ERROR); 487 } 488 } else { 489 if (list_appendc(&(osp->os_isdescs), isp) == 0) 490 return ((Os_desc *)S_ERROR); 491 } 492 if (ld_append_isp(ofl, osp, isp, 1) == 0) 493 return ((Os_desc *)S_ERROR); 494 495 isp->is_osdesc = osp; 496 497 /* 498 * If this input section and file is associated to an 499 * artificially referenced output section, make sure 500 * they are marked as referenced also. This insures this 501 * input section and file isn't eliminated when -zignore 502 * is in effect. 503 * See -zignore comments when creating a new output 504 * section below. 505 */ 506 if (((ifl && 507 (ifl->ifl_flags & FLG_IF_IGNORE)) || DBG_ENABLED) && 508 (osp->os_flags & FLG_OS_SECTREF)) { 509 isp->is_flags |= FLG_IS_SECTREF; 510 if (ifl) 511 ifl->ifl_flags |= FLG_IF_FILEREF; 512 } 513 514 DBG_CALL(Dbg_sec_added(ofl->ofl_lml, osp, sgp)); 515 return (osp); 516 } 517 518 /* 519 * Do we need to worry about section ordering? 520 */ 521 if (os_ndx) { 522 if (osp->os_txtndx) { 523 if (os_ndx < osp->os_txtndx) 524 /* insert section here. */ 525 break; 526 else { 527 idx2 = idx1 + 1; 528 continue; 529 } 530 } else { 531 /* insert section here. */ 532 break; 533 } 534 } else if (osp->os_txtndx) { 535 idx2 = idx1 + 1; 536 continue; 537 } 538 539 /* 540 * If the new sections identifier is less than that of the 541 * present input section we need to insert the new section 542 * at this point. 543 */ 544 if (ident < osp->os_scnsymndx) 545 break; 546 547 idx2 = idx1 + 1; 548 } 549 550 /* 551 * We are adding a new output section. Update the section header 552 * count and associated string size. 553 */ 554 ofl->ofl_shdrcnt++; 555 if (st_insert(ofl->ofl_shdrsttab, isp->is_name) == -1) 556 return ((Os_desc *)S_ERROR); 557 558 /* 559 * Create a new output section descriptor. 560 */ 561 if ((osp = libld_calloc(sizeof (Os_desc), 1)) == 0) 562 return ((Os_desc *)S_ERROR); 563 if ((osp->os_shdr = libld_calloc(sizeof (Shdr), 1)) == 0) 564 return ((Os_desc *)S_ERROR); 565 566 /* 567 * We convert COMDAT sections to PROGBITS if this is the first 568 * section of a output section. 569 */ 570 if (shdr->sh_type == SHT_SUNW_COMDAT) { 571 Shdr * tshdr; 572 573 if ((tshdr = libld_malloc(sizeof (Shdr))) == 0) 574 return ((Os_desc *)S_ERROR); 575 *tshdr = *shdr; 576 isp->is_shdr = shdr = tshdr; 577 shdr->sh_type = SHT_PROGBITS; 578 if (list_appendc(&(osp->os_comdats), isp) == 0) 579 return ((Os_desc *)S_ERROR); 580 } 581 582 osp->os_shdr->sh_type = shdr->sh_type; 583 osp->os_shdr->sh_flags = shdr->sh_flags; 584 osp->os_shdr->sh_entsize = shdr->sh_entsize; 585 osp->os_name = isp->is_name; 586 osp->os_namehash = isp->is_namehash; 587 osp->os_txtndx = os_ndx; 588 osp->os_sgdesc = sgp; 589 590 if (ifl && (shdr->sh_type == SHT_PROGBITS)) { 591 /* 592 * Try to preserve the intended meaning of sh_link/sh_info. 593 * See the translate_link() in update.c. 594 */ 595 osp->os_shdr->sh_link = shdr->sh_link; 596 if (shdr->sh_flags & SHF_INFO_LINK) 597 osp->os_shdr->sh_info = shdr->sh_info; 598 } 599 600 /* 601 * When -zignore is in effect, user supplied sections and files that are 602 * not referenced from other sections, are eliminated from the object 603 * being produced. Some sections, although unreferenced, are special, 604 * and must not be eliminated. Determine if this new output section is 605 * one of those special sections, and if so mark it artificially as 606 * referenced. Any input section and file associated to this output 607 * section is also be marked as referenced, and thus won't be eliminated 608 * from the final output. 609 */ 610 if (ifl && ((ofl->ofl_flags1 & FLG_OF1_IGNPRC) || DBG_ENABLED)) { 611 const Msg *refsec; 612 613 for (refsec = RefSecs; *refsec; refsec++) { 614 if (strcmp(osp->os_name, MSG_ORIG(*refsec)) == 0) { 615 osp->os_flags |= FLG_OS_SECTREF; 616 617 if ((ifl->ifl_flags & FLG_IF_IGNORE) || 618 DBG_ENABLED) { 619 isp->is_flags |= FLG_IS_SECTREF; 620 ifl->ifl_flags |= FLG_IF_FILEREF; 621 } 622 break; 623 } 624 } 625 } 626 627 /* 628 * Setions of SHT_GROUP are added to the ofl->ofl_osgroups 629 * list - so that they can be updated as a group later. 630 */ 631 if (shdr->sh_type == SHT_GROUP) { 632 if (list_appendc(&ofl->ofl_osgroups, osp) == 0) 633 return ((Os_desc *)S_ERROR); 634 } 635 636 /* 637 * If this section is a non-empty TLS section indicate that a PT_TLS 638 * program header is required. 639 */ 640 if ((shflags & SHF_TLS) && shdr->sh_size && 641 ((ofl->ofl_flags & FLG_OF_RELOBJ) == 0)) 642 ofl->ofl_flags |= FLG_OF_TLSPHDR; 643 644 /* 645 * If a non-allocatable section is going to be put into a loadable 646 * segment then turn on the allocate bit for this section and warn the 647 * user that we have done so. This could only happen through the use 648 * of a mapfile. 649 */ 650 if ((sgp->sg_phdr.p_type == PT_LOAD) && 651 ((osp->os_shdr->sh_flags & SHF_ALLOC) == 0)) { 652 eprintf(ofl->ofl_lml, ERR_WARNING, MSG_INTL(MSG_SCN_NONALLOC), 653 ofl->ofl_name, osp->os_name); 654 osp->os_shdr->sh_flags |= SHF_ALLOC; 655 } 656 657 /* 658 * Retain this sections identifier for future comparisons when placing 659 * a section (after all sections have been processed this variable will 660 * be used to hold the sections symbol index as we don't need to retain 661 * the identifier any more). 662 */ 663 osp->os_scnsymndx = ident; 664 665 /* 666 * Set alignment 667 */ 668 set_addralign(ofl, osp, isp); 669 670 if (ld_append_isp(ofl, osp, isp, 0) == 0) 671 return ((Os_desc *)S_ERROR); 672 673 DBG_CALL(Dbg_sec_created(ofl->ofl_lml, osp, sgp)); 674 isp->is_osdesc = osp; 675 676 /* 677 * Insert the new section at the offset given by idx2. If no 678 * position for it was identified above, this will be index 0, 679 * causing the new section to be prepended to the beginning of 680 * the section list. Otherwise, it is the index following the section 681 * that was identified. 682 */ 683 if (aplist_insert(&sgp->sg_osdescs, osp, AL_CNT_SG_OSDESC, 684 idx2) == NULL) 685 return ((Os_desc *)S_ERROR); 686 return (osp); 687 } 688