/*- * Copyright (c) 2011-2013 Kai Wang * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "ld.h" #include "ld_exp.h" #include "ld_options.h" #include "ld_script.h" #include "ld_file.h" #include "ld_symbols.h" #include "ld_output.h" ELFTC_VCSID("$Id: ld_script.c 3281 2015-12-11 21:39:23Z kaiwang27 $"); static void _input_file_add(struct ld *ld, struct ld_script_input_file *ldif); static void _overlay_section_free(void *ptr); static struct ld_script_variable *_variable_find(struct ld *ld, char *name); #define _variable_add(v) \ HASH_ADD_KEYPTR(hh, ld->ld_scp->lds_v, (v)->ldv_name, \ strlen((v)->ldv_name), (v)) struct ld_script_cmd * ld_script_assert(struct ld *ld, struct ld_exp *exp, char *msg) { struct ld_script_assert *a; if ((a = calloc(1, sizeof(*a))) == NULL) ld_fatal_std(ld, "calloc"); a->lda_exp = exp; a->lda_msg = msg; return (ld_script_cmd(ld, LSC_ASSERT, a)); } struct ld_script_assign * ld_script_assign(struct ld *ld, struct ld_exp *var, enum ld_script_assign_op op, struct ld_exp *val, unsigned provide, unsigned hidden) { struct ld_script_assign *lda; struct ld_script_variable *ldv; if ((lda = calloc(1, sizeof(*lda))) == NULL) ld_fatal_std(ld, "calloc"); lda->lda_var = var; lda->lda_op = op; lda->lda_val = val; lda->lda_provide = provide; if ((ldv = _variable_find(ld, var->le_name)) == NULL) { ldv = calloc(1, sizeof(*ldv)); if ((ldv->ldv_name = strdup(var->le_name)) == NULL) ld_fatal_std(ld, "strdup"); _variable_add(ldv); if (*var->le_name != '.') ld_symbols_add_variable(ld, ldv, provide, hidden); } return (lda); } void ld_script_assign_dump(struct ld *ld, struct ld_script_assign *lda) { printf("%16s", ""); printf("0x%016jx ", (uintmax_t) lda->lda_res); if (lda->lda_provide) printf("PROVIDE("); ld_exp_dump(ld, lda->lda_var); switch (lda->lda_op) { case LSAOP_ADD_E: printf(" += "); break; case LSAOP_AND_E: printf(" &= "); break; case LSAOP_DIV_E: printf(" /= "); break; case LSAOP_E: printf(" = "); break; case LSAOP_LSHIFT_E: printf(" <<= "); break; case LSAOP_MUL_E: printf(" *= "); break; case LSAOP_OR_E: printf(" |= "); break; case LSAOP_RSHIFT_E: printf(" >>= "); break; case LSAOP_SUB_E: printf(" -= "); break; default: ld_fatal(ld, "internal: unknown assignment op: %d", lda->lda_op); } ld_exp_dump(ld, lda->lda_val); if (lda->lda_provide) printf(")"); printf("\n"); } void ld_script_assign_free(struct ld_script_assign *lda) { if (lda == NULL) return; ld_exp_free(lda->lda_var); ld_exp_free(lda->lda_val); free(lda); } static void _update_variable_section(struct ld *ld, struct ld_script_variable *ldv) { struct ld_output_section *os, *last; if (ldv->ldv_os_base) { /* Get base address of the section. */ STAILQ_FOREACH(os, &ld->ld_output->lo_oslist, os_next) { if (strcmp(os->os_name, ldv->ldv_os_base) == 0) { ldv->ldv_base = os->os_addr; ldv->ldv_os_ref = ldv->ldv_os_base; ldv->ldv_os_base = 0; break; } } } if (ldv->ldv_os_ref) { /* Bind the symbol to the last section. */ last = 0; STAILQ_FOREACH(os, &ld->ld_output->lo_oslist, os_next) { if (! os->os_empty) last = os; if (strcmp(os->os_name, ldv->ldv_os_ref) == 0) { if (last) { ldv->ldv_symbol->lsb_shndx = elf_ndxscn(last->os_scn); } ldv->ldv_os_ref = 0; break; } } } } void ld_script_process_assign(struct ld *ld, struct ld_script_assign *lda) { struct ld_state *ls; struct ld_exp *var; struct ld_script_variable *ldv; ls = &ld->ld_state; var = lda->lda_var; ldv = _variable_find(ld, var->le_name); assert(ldv != NULL); ldv->ldv_val = ld_exp_eval(ld, lda->lda_val); if (*var->le_name == '.') { /* * TODO: location counter is allowed to move backwards * outside output section descriptor, as long as the * move will not cause overlapping LMA's. */ if ((uint64_t) ldv->ldv_val < ls->ls_loc_counter) ld_fatal(ld, "cannot move location counter backwards" " from %#jx to %#jx", (uintmax_t) ls->ls_loc_counter, (uintmax_t) ldv->ldv_val); ls->ls_loc_counter = (uint64_t) ldv->ldv_val; } else if (ldv->ldv_symbol != NULL) { _update_variable_section(ld, ldv); ldv->ldv_symbol->lsb_value = ldv->ldv_val + ldv->ldv_base; } lda->lda_res = ldv->ldv_val; } void ld_script_process_entry(struct ld *ld, char *name) { if (ld->ld_scp->lds_entry_point != NULL) { free(ld->ld_scp->lds_entry_point); ld->ld_scp->lds_entry_point = NULL; } ld->ld_scp->lds_entry_point = strdup(name); if (ld->ld_scp->lds_entry_point == NULL) ld_fatal_std(ld, "strdup"); } int64_t ld_script_variable_value(struct ld *ld, char *name) { struct ld_script_variable *ldv; struct ld_state *ls; ls = &ld->ld_state; if (*name == '.') return (ls->ls_loc_counter); ldv = _variable_find(ld, name); assert(ldv != NULL); return (ldv->ldv_val); } struct ld_script_cmd * ld_script_cmd(struct ld *ld, enum ld_script_cmd_type type, void *cmd) { struct ld_script_cmd *ldc; if ((ldc = calloc(1, sizeof(*ldc))) == NULL) ld_fatal_std(ld, "calloc"); ldc->ldc_type = type; ldc->ldc_cmd = cmd; return (ldc); } void ld_script_cmd_insert(struct ld_script_cmd_head *head, struct ld_script_cmd *ldc) { STAILQ_INSERT_TAIL(head, ldc, ldc_next); } static void _overlay_section_free(void *ptr) { struct ld_script_cmd *c, *_c; struct ld_script_sections_overlay_section *ldos; ldos = ptr; if (ldos == NULL) return; free(ldos->ldos_name); ld_script_list_free(ldos->ldos_phdr, free); ld_exp_free(ldos->ldos_fill); STAILQ_FOREACH_SAFE(c, &ldos->ldos_c, ldc_next, _c) { STAILQ_REMOVE(&ldos->ldos_c, c, ld_script_cmd, ldc_next); ld_script_cmd_free(c); } free(ldos); } void ld_script_cmd_free(struct ld_script_cmd *ldc) { struct ld_script_cmd *c, *_c; struct ld_script_assert *lda; struct ld_script_sections *ldss; struct ld_script_sections_output *ldso; struct ld_script_sections_output_data *ldod; struct ld_script_sections_output_input *ldoi; struct ld_script_sections_overlay *ldso2; switch (ldc->ldc_type) { case LSC_ASSERT: lda = ldc->ldc_cmd; ld_exp_free(lda->lda_exp); free(lda->lda_msg); free(lda); break; case LSC_ASSIGN: ld_script_assign_free(ldc->ldc_cmd); break; case LSC_ENTRY: free(ldc->ldc_cmd); break; case LSC_SECTIONS: ldss = ldc->ldc_cmd; STAILQ_FOREACH_SAFE(c, &ldss->ldss_c, ldc_next, _c) { STAILQ_REMOVE(&ldss->ldss_c, c, ld_script_cmd, ldc_next); ld_script_cmd_free(c); } free(ldss); break; case LSC_SECTIONS_OUTPUT: ldso = ldc->ldc_cmd; free(ldso->ldso_name); free(ldso->ldso_type); ld_exp_free(ldso->ldso_vma); ld_exp_free(ldso->ldso_lma); ld_exp_free(ldso->ldso_align); ld_exp_free(ldso->ldso_subalign); free(ldso->ldso_constraint); free(ldso->ldso_region); free(ldso->ldso_lma_region); ld_script_list_free(ldso->ldso_phdr, free); ld_exp_free(ldso->ldso_fill); STAILQ_FOREACH_SAFE(c, &ldso->ldso_c, ldc_next, _c) { STAILQ_REMOVE(&ldso->ldso_c, c, ld_script_cmd, ldc_next); ld_script_cmd_free(c); } free(ldso); break; case LSC_SECTIONS_OUTPUT_DATA: ldod = ldc->ldc_cmd; ld_exp_free(ldod->ldod_exp); free(ldod); break; case LSC_SECTIONS_OUTPUT_INPUT: ldoi = ldc->ldc_cmd; ld_wildcard_free(ldoi->ldoi_ar); ld_wildcard_free(ldoi->ldoi_file); ld_script_list_free(ldoi->ldoi_exclude, ld_wildcard_free); ld_script_list_free(ldoi->ldoi_sec, ld_wildcard_free); free(ldoi); break; case LSC_SECTIONS_OVERLAY: ldso2 = ldc->ldc_cmd; ld_exp_free(ldso2->ldso_vma); ld_exp_free(ldso2->ldso_lma); free(ldso2->ldso_region); ld_script_list_free(ldso2->ldso_phdr, free); ld_exp_free(ldso2->ldso_fill); ld_script_list_free(ldso2->ldso_s, _overlay_section_free); free(ldso2); break; default: break; } free(ldc); } void ld_script_extern(struct ld *ld, struct ld_script_list *list) { struct ld_script_list *ldl; ldl = list; while (ldl != NULL) { ld_symbols_add_extern(ld, ldl->ldl_entry); ldl = ldl->ldl_next; } ld_script_list_free(list, free); } void ld_script_group(struct ld *ld, struct ld_script_list *list) { struct ld_script_list *ldl; ld->ld_state.ls_group_level++; if (ld->ld_state.ls_group_level > LD_MAX_NESTED_GROUP) ld_fatal(ld, "too many nested archive groups"); ldl = list; while (ldl != NULL) { _input_file_add(ld, ldl->ldl_entry); ldl = ldl->ldl_next; } ld->ld_state.ls_group_level--; ld_script_list_free(list, free); } void ld_script_init(struct ld *ld) { ld->ld_scp = calloc(1, sizeof(*ld->ld_scp)); if (ld->ld_scp == NULL) ld_fatal_std(ld, "calloc"); STAILQ_INIT(&ld->ld_scp->lds_a); STAILQ_INIT(&ld->ld_scp->lds_c); STAILQ_INIT(&ld->ld_scp->lds_n); STAILQ_INIT(&ld->ld_scp->lds_p); STAILQ_INIT(&ld->ld_scp->lds_r); STAILQ_INIT(&ld->ld_scp->lds_vn); ld_script_parse_internal(); } void ld_script_cleanup(struct ld *ld) { struct ld_script *lds; struct ld_script_phdr *p, *_p; struct ld_script_region *r, *_r; struct ld_script_region_alias *a, *_a; struct ld_script_nocrossref *n, *_n; struct ld_script_cmd *c, *_c; struct ld_script_variable *v, *_v; if (ld->ld_scp == NULL) return; lds = ld->ld_scp; if (lds->lds_entry_point != NULL) { free(lds->lds_entry_point); lds->lds_entry_point = NULL; } STAILQ_FOREACH_SAFE(p, &lds->lds_p, ldsp_next, _p) { STAILQ_REMOVE(&lds->lds_p, p, ld_script_phdr, ldsp_next); free(p->ldsp_name); free(p->ldsp_type); ld_exp_free(p->ldsp_addr); free(p); } STAILQ_FOREACH_SAFE(r, &lds->lds_r, ldsr_next, _r) { STAILQ_REMOVE(&lds->lds_r, r, ld_script_region, ldsr_next); free(r->ldsr_name); free(r->ldsr_attr); ld_exp_free(r->ldsr_origin); ld_exp_free(r->ldsr_len); free(r); } STAILQ_FOREACH_SAFE(a, &lds->lds_a, ldra_next, _a) { STAILQ_REMOVE(&lds->lds_a, a, ld_script_region_alias, ldra_next); free(a->ldra_alias); free(a->ldra_region); free(a); } STAILQ_FOREACH_SAFE(n, &lds->lds_n, ldn_next, _n) { STAILQ_REMOVE(&lds->lds_n, n, ld_script_nocrossref, ldn_next); ld_script_list_free(n->ldn_l, free); free(n); } STAILQ_FOREACH_SAFE(c, &lds->lds_c, ldc_next, _c) { STAILQ_REMOVE(&lds->lds_c, c, ld_script_cmd, ldc_next); ld_script_cmd_free(c); } if (lds->lds_v != NULL) { HASH_ITER(hh, lds->lds_v, v, _v) { HASH_DEL(lds->lds_v, v); free(v->ldv_name); free(v); } lds->lds_v = NULL; } } void ld_script_input(struct ld *ld, struct ld_script_list *list) { struct ld_script_list *ldl; ld->ld_state.ls_search_dir = 1; ldl = list; while (ldl != NULL) { _input_file_add(ld, ldl->ldl_entry); ldl = ldl->ldl_next; } ld->ld_state.ls_search_dir = 0; ld_script_list_free(list, free); } struct ld_script_input_file * ld_script_input_file(struct ld *ld, unsigned as_needed, void *in) { struct ld_script_input_file *ldif; if ((ldif = calloc(1, sizeof(*ldif))) == NULL) ld_fatal_std(ld, "calloc"); ldif->ldif_as_needed = as_needed; if (as_needed) ldif->ldif_u.ldif_ldl = in; else ldif->ldif_u.ldif_name = in; return (ldif); } struct ld_script_list * ld_script_list(struct ld *ld, struct ld_script_list *list, void *entry) { struct ld_script_list *ldl; if ((ldl = malloc(sizeof(*ldl))) == NULL) ld_fatal_std(ld, "malloc"); ldl->ldl_entry = entry; ldl->ldl_next = list; return (ldl); } void ld_script_list_free(struct ld_script_list *list, void (*_free)(void *ptr)) { struct ld_script_list *ldl; if (list == NULL) return; do { ldl = list; list = ldl->ldl_next; if (ldl->ldl_entry) _free(ldl->ldl_entry); free(ldl); } while (list != NULL); } struct ld_script_list * ld_script_list_reverse(struct ld_script_list *list) { struct ld_script_list *root, *next; root = NULL; while (list != NULL) { next = list->ldl_next; list->ldl_next = root; root = list; list = next; } return (root); } void ld_script_nocrossrefs(struct ld *ld, struct ld_script_list *list) { struct ld_script_nocrossref *ldn; if ((ldn = calloc(1, sizeof(*ldn))) == NULL) ld_fatal_std(ld, "calloc"); ldn->ldn_l = list; STAILQ_INSERT_TAIL(&ld->ld_scp->lds_n, ldn, ldn_next); } struct ld_script_phdr * ld_script_phdr(struct ld *ld, char *name, char *type, unsigned filehdr, unsigned phdrs, struct ld_exp *addr, unsigned flags) { struct ld_script_phdr *ldsp; if ((ldsp = calloc(1, sizeof(*ldsp))) == NULL) ld_fatal_std(ld, "calloc"); ldsp->ldsp_name = name; ldsp->ldsp_type = type; ldsp->ldsp_filehdr = filehdr; ldsp->ldsp_phdrs = phdrs; ldsp->ldsp_addr = addr; ldsp->ldsp_flags = flags; return (ldsp); } struct ld_script_region * ld_script_region(struct ld *ld, char *name, char *attr, struct ld_exp *origin, struct ld_exp *len) { struct ld_script_region *ldsr; if ((ldsr = malloc(sizeof(*ldsr))) == NULL) ld_fatal_std(ld, "malloc"); ldsr->ldsr_name = name; ldsr->ldsr_attr = attr; ldsr->ldsr_origin = origin; ldsr->ldsr_len = len; return (ldsr); } void ld_script_region_alias(struct ld *ld, char *alias, char *region) { struct ld_script_region_alias *ldra; if ((ldra = calloc(1, sizeof(*ldra))) == NULL) ld_fatal_std(ld, "calloc"); ldra->ldra_alias = alias; ldra->ldra_region = region; STAILQ_INSERT_TAIL(&ld->ld_scp->lds_a, ldra, ldra_next); } void ld_script_version_add_node(struct ld *ld, char *ver, void *head, char *depend) { struct ld_script_version_node *ldvn; if ((ldvn = calloc(1, sizeof(*ldvn))) == NULL) ld_fatal_std(ld, "calloc"); ldvn->ldvn_name = ver; if (ldvn->ldvn_name == NULL) { /* * Version name can be omitted only when this is the only * node in the version script. */ if (ld->ld_scp->lds_vn_name_omitted || !STAILQ_EMPTY(&ld->ld_scp->lds_vn)) ld_fatal(ld, "version script can only have one " "version node that is without a version name"); ld->ld_scp->lds_vn_name_omitted = 1; } ldvn->ldvn_dep = depend; ldvn->ldvn_e = head; STAILQ_INSERT_TAIL(&ld->ld_scp->lds_vn, ldvn, ldvn_next); } struct ld_script_version_entry * ld_script_version_alloc_entry(struct ld *ld, char *sym, void *extern_block) { struct ld_state *ls; struct ld_script_version_entry *ldve; int ignore; char *p; ls = &ld->ld_state; if ((ldve = calloc(1, sizeof(*ldve))) == NULL) ld_fatal_std(ld, "calloc"); ldve->ldve_sym = sym; ldve->ldve_local = ls->ls_version_local; ldve->ldve_list = extern_block; if (ldve->ldve_sym == NULL) return (ldve); ignore = 0; for (p = ldve->ldve_sym; *p != '\0'; p++) { switch (*p) { case '\\': /* Ignore the next char */ ignore = 1; break; case '?': case '*': case '[': if (!ignore) { ldve->ldve_glob = 1; goto done; } else ignore = 0; } } done: return (ldve); } void * ld_script_version_link_entry(struct ld *ld, struct ld_script_version_entry_head *head, struct ld_script_version_entry *ldve) { if (ldve == NULL) return (head); if (head == NULL) { if ((head = calloc(1, sizeof(*head))) == NULL) ld_fatal_std(ld, "calloc"); STAILQ_INIT(head); } if (ldve->ldve_list != NULL) { STAILQ_CONCAT(head, ldve->ldve_list); free(ldve->ldve_list); free(ldve); } else STAILQ_INSERT_TAIL(head, ldve, ldve_next); return (head); } void ld_script_version_set_lang(struct ld * ld, struct ld_script_version_entry_head *head, char *lang) { struct ld_script_version_entry *ldve; enum ld_script_version_lang vl; vl = VL_C; if (!strcasecmp(lang, "c")) vl = VL_C; else if (!strcasecmp(lang, "c++")) vl = VL_CPP; else if (!strcasecmp(lang, "java")) vl = VL_JAVA; else ld_warn(ld, "unrecognized language `%s' in version script", lang); STAILQ_FOREACH(ldve, head, ldve_next) { /* Do not overwrite lang set by inner extern blocks. */ if (!ldve->ldve_lang_set) { ldve->ldve_lang = vl; ldve->ldve_lang_set = 1; } } } static void _input_file_add(struct ld *ld, struct ld_script_input_file *ldif) { struct ld_state *ls; struct ld_script_list *ldl; unsigned old_as_needed; ls = &ld->ld_state; if (!ldif->ldif_as_needed) { ld_file_add(ld, ldif->ldif_u.ldif_name, LFT_UNKNOWN); free(ldif->ldif_u.ldif_name); } else { old_as_needed = ls->ls_as_needed; ls->ls_as_needed = 1; ldl = ldif->ldif_u.ldif_ldl; while (ldl != NULL) { ld_file_add(ld, ldl->ldl_entry, LFT_UNKNOWN); ldl = ldl->ldl_next; } ls->ls_as_needed = old_as_needed; ld_script_list_free(ldif->ldif_u.ldif_ldl, free); } } static struct ld_script_variable * _variable_find(struct ld *ld, char *name) { struct ld_script_variable *ldv; HASH_FIND_STR(ld->ld_scp->lds_v, name, ldv); return (ldv); }