diff options
author | Kai Wang <kaiw@FreeBSD.org> | 2014-01-15 08:43:20 +0000 |
---|---|---|
committer | Kai Wang <kaiw@FreeBSD.org> | 2014-01-15 08:43:20 +0000 |
commit | 5265ace0e440a23fb522c516f4ee20f43eaed2b3 (patch) | |
tree | 13068447bb61372f7540b168e913b8eb88ef7578 /ld/ld_script.c |
Diffstat (limited to 'ld/ld_script.c')
-rw-r--r-- | ld/ld_script.c | 746 |
1 files changed, 746 insertions, 0 deletions
diff --git a/ld/ld_script.c b/ld/ld_script.c new file mode 100644 index 0000000000000..1e3d104374633 --- /dev/null +++ b/ld/ld_script.c @@ -0,0 +1,746 @@ +/*- + * 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" + +ELFTC_VCSID("$Id: ld_script.c 2881 2013-01-09 22:46:54Z 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); +} + +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; + } + 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); +} |