%{ /* Parser for linker scripts. Copyright (C) 2001-2011 Red Hat, Inc. This file is part of elfutils. Written by Ulrich Drepper , 2001. This file is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. elfutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include /* The error handler. */ static void yyerror (const char *s); /* Some helper functions we need to construct the data structures describing information from the file. */ static struct expression *new_expr (int tag); static struct input_section_name *new_input_section_name (const char *name, bool sort_flag); static struct input_rule *new_input_rule (int tag); static struct output_rule *new_output_rule (int tag); static struct assignment *new_assignment (const char *variable, struct expression *expression, bool provide_flag); static void new_segment (int mode, struct output_rule *output_rule); static struct filename_list *new_filename_listelem (const char *string); static void add_inputfiles (struct filename_list *fnames); static struct id_list *new_id_listelem (const char *str); static struct filename_list *mark_as_needed (struct filename_list *listp); static struct version *new_version (struct id_list *local, struct id_list *global); static struct version *merge_versions (struct version *one, struct version *two); static void add_versions (struct version *versions); extern int yylex (void); %} %union { uintmax_t num; enum expression_tag op; char *str; struct expression *expr; struct input_section_name *sectionname; struct filemask_section_name *filemask_section_name; struct input_rule *input_rule; struct output_rule *output_rule; struct assignment *assignment; struct filename_list *filename_list; struct version *version; struct id_list *id_list; } %token kADD_OP %token kALIGN %token kAS_NEEDED %token kENTRY %token kEXCLUDE_FILE %token kFILENAME %token kGLOBAL %token kGROUP %token kID %token kINPUT %token kINTERP %token kKEEP %token kLOCAL %token kMODE %token kMUL_OP %token kNUM %token kOUTPUT_FORMAT %token kPAGESIZE %token kPROVIDE %token kSEARCH_DIR %token kSEGMENT %token kSIZEOF_HEADERS %token kSORT %token kVERSION %token kVERSION_SCRIPT %left '|' %left '&' %left ADD_OP %left MUL_OP '*' %type kADD_OP %type kMUL_OP %type filename_id %type filename_id_star %type exclude_opt %type expr %type sort_opt_name %type sectionname %type inputsection %type inputsections %type outputsection %type outputsections %type assignment %type filename_id_list %type filename_id_listelem %type versionlist %type version %type version_stmt_list %type version_stmt %type filename_id_star_list %expect 16 %% script_or_version: file | kVERSION_SCRIPT versionlist { add_versions ($2); } ; file: file content | content ; content: kENTRY '(' kID ')' ';' { if (likely (ld_state.entry == NULL)) ld_state.entry = $3; } | kSEARCH_DIR '(' filename_id ')' ';' { ld_new_searchdir ($3); } | kPAGESIZE '(' kNUM ')' ';' { if (likely (ld_state.pagesize == 0)) ld_state.pagesize = $3; } | kINTERP '(' filename_id ')' ';' { if (likely (ld_state.interp == NULL) && ld_state.file_type != dso_file_type) ld_state.interp = $3; } | kSEGMENT kMODE '{' outputsections '}' { new_segment ($2, $4); } | kSEGMENT error '{' outputsections '}' { fputs_unlocked (gettext ("mode for segment invalid\n"), stderr); new_segment (0, $4); } | kGROUP '(' filename_id_list ')' { /* First little optimization. If there is only one file in the group don't do anything. */ if ($3 != $3->next) { $3->next->group_start = 1; $3->group_end = 1; } add_inputfiles ($3); } | kINPUT '(' filename_id_list ')' { add_inputfiles ($3); } | kAS_NEEDED '(' filename_id_list ')' { add_inputfiles (mark_as_needed ($3)); } | kVERSION '{' versionlist '}' { add_versions ($3); } | kOUTPUT_FORMAT '(' filename_id ')' { /* XXX TODO */ } ; outputsections: outputsections outputsection { $2->next = $1->next; $$ = $1->next = $2; } | outputsection { $$ = $1; } ; outputsection: assignment ';' { $$ = new_output_rule (output_assignment); $$->val.assignment = $1; } | kID '{' inputsections '}' { $$ = new_output_rule (output_section); $$->val.section.name = $1; $$->val.section.input = $3->next; if (ld_state.strip == strip_debug && ebl_debugscn_p (ld_state.ebl, $1)) $$->val.section.ignored = true; else $$->val.section.ignored = false; $3->next = NULL; } | kID ';' { /* This is a short cut for "ID { *(ID) }". */ $$ = new_output_rule (output_section); $$->val.section.name = $1; $$->val.section.input = new_input_rule (input_section); $$->val.section.input->next = NULL; $$->val.section.input->val.section = (struct filemask_section_name *) obstack_alloc (&ld_state.smem, sizeof (struct filemask_section_name)); $$->val.section.input->val.section->filemask = NULL; $$->val.section.input->val.section->excludemask = NULL; $$->val.section.input->val.section->section_name = new_input_section_name ($1, false); $$->val.section.input->val.section->keep_flag = false; if (ld_state.strip == strip_debug && ebl_debugscn_p (ld_state.ebl, $1)) $$->val.section.ignored = true; else $$->val.section.ignored = false; } ; assignment: kID '=' expr { $$ = new_assignment ($1, $3, false); } | kPROVIDE '(' kID '=' expr ')' { $$ = new_assignment ($3, $5, true); } ; inputsections: inputsections inputsection { $2->next = $1->next; $$ = $1->next = $2; } | inputsection { $$ = $1; } ; inputsection: sectionname { $$ = new_input_rule (input_section); $$->val.section = $1; } | kKEEP '(' sectionname ')' { $3->keep_flag = true; $$ = new_input_rule (input_section); $$->val.section = $3; } | assignment ';' { $$ = new_input_rule (input_assignment); $$->val.assignment = $1; } ; sectionname: filename_id_star '(' exclude_opt sort_opt_name ')' { $$ = (struct filemask_section_name *) obstack_alloc (&ld_state.smem, sizeof (*$$)); $$->filemask = $1; $$->excludemask = $3; $$->section_name = $4; $$->keep_flag = false; } ; sort_opt_name: kID { $$ = new_input_section_name ($1, false); } | kSORT '(' kID ')' { $$ = new_input_section_name ($3, true); } ; exclude_opt: kEXCLUDE_FILE '(' filename_id ')' { $$ = $3; } | { $$ = NULL; } ; expr: kALIGN '(' expr ')' { $$ = new_expr (exp_align); $$->val.child = $3; } | '(' expr ')' { $$ = $2; } | expr '*' expr { $$ = new_expr (exp_mult); $$->val.binary.left = $1; $$->val.binary.right = $3; } | expr kMUL_OP expr { $$ = new_expr ($2); $$->val.binary.left = $1; $$->val.binary.right = $3; } | expr kADD_OP expr { $$ = new_expr ($2); $$->val.binary.left = $1; $$->val.binary.right = $3; } | expr '&' expr { $$ = new_expr (exp_and); $$->val.binary.left = $1; $$->val.binary.right = $3; } | expr '|' expr { $$ = new_expr (exp_or); $$->val.binary.left = $1; $$->val.binary.right = $3; } | kNUM { $$ = new_expr (exp_num); $$->val.num = $1; } | kID { $$ = new_expr (exp_id); $$->val.str = $1; } | kSIZEOF_HEADERS { $$ = new_expr (exp_sizeof_headers); } | kPAGESIZE { $$ = new_expr (exp_pagesize); } ; filename_id_list: filename_id_list comma_opt filename_id_listelem { $3->next = $1->next; $$ = $1->next = $3; } | filename_id_listelem { $$ = $1; } ; comma_opt: ',' | ; filename_id_listelem: kGROUP '(' filename_id_list ')' { /* First little optimization. If there is only one file in the group don't do anything. */ if ($3 != $3->next) { $3->next->group_start = 1; $3->group_end = 1; } $$ = $3; } | kAS_NEEDED '(' filename_id_list ')' { $$ = mark_as_needed ($3); } | filename_id { $$ = new_filename_listelem ($1); } ; versionlist: versionlist version { $2->next = $1->next; $$ = $1->next = $2; } | version { $$ = $1; } ; version: '{' version_stmt_list '}' ';' { $2->versionname = ""; $2->parentname = NULL; $$ = $2; } | filename_id '{' version_stmt_list '}' ';' { $3->versionname = $1; $3->parentname = NULL; $$ = $3; } | filename_id '{' version_stmt_list '}' filename_id ';' { $3->versionname = $1; $3->parentname = $5; $$ = $3; } ; version_stmt_list: version_stmt_list version_stmt { $$ = merge_versions ($1, $2); } | version_stmt { $$ = $1; } ; version_stmt: kGLOBAL filename_id_star_list { $$ = new_version (NULL, $2); } | kLOCAL filename_id_star_list { $$ = new_version ($2, NULL); } ; filename_id_star_list: filename_id_star_list filename_id_star ';' { struct id_list *newp = new_id_listelem ($2); newp->next = $1->next; $$ = $1->next = newp; } | filename_id_star ';' { $$ = new_id_listelem ($1); } ; filename_id: kFILENAME { $$ = $1; } | kID { $$ = $1; } ; filename_id_star: filename_id { $$ = $1; } | '*' { $$ = NULL; } ; %% static void yyerror (const char *s) { error (0, 0, (ld_scan_version_script ? gettext ("while reading version script '%s': %s at line %d") : gettext ("while reading linker script '%s': %s at line %d")), ldin_fname, gettext (s), ldlineno); } static struct expression * new_expr (int tag) { struct expression *newp = (struct expression *) obstack_alloc (&ld_state.smem, sizeof (*newp)); newp->tag = tag; return newp; } static struct input_section_name * new_input_section_name (const char *name, bool sort_flag) { struct input_section_name *newp = (struct input_section_name *) obstack_alloc (&ld_state.smem, sizeof (*newp)); newp->name = name; newp->sort_flag = sort_flag; return newp; } static struct input_rule * new_input_rule (int tag) { struct input_rule *newp = (struct input_rule *) obstack_alloc (&ld_state.smem, sizeof (*newp)); newp->tag = tag; newp->next = newp; return newp; } static struct output_rule * new_output_rule (int tag) { struct output_rule *newp = (struct output_rule *) memset (obstack_alloc (&ld_state.smem, sizeof (*newp)), '\0', sizeof (*newp)); newp->tag = tag; newp->next = newp; return newp; } static struct assignment * new_assignment (const char *variable, struct expression *expression, bool provide_flag) { struct assignment *newp = (struct assignment *) obstack_alloc (&ld_state.smem, sizeof (*newp)); newp->variable = variable; newp->expression = expression; newp->sym = NULL; newp->provide_flag = provide_flag; /* Insert the symbol into a hash table. We will later have to matc*/ return newp; } static void new_segment (int mode, struct output_rule *output_rule) { struct output_segment *newp; newp = (struct output_segment *) obstack_alloc (&ld_state.smem, sizeof (*newp)); newp->mode = mode; newp->next = newp; newp->output_rules = output_rule->next; output_rule->next = NULL; /* Enqueue the output segment description. */ if (ld_state.output_segments == NULL) ld_state.output_segments = newp; else { newp->next = ld_state.output_segments->next; ld_state.output_segments = ld_state.output_segments->next = newp; } /* If the output file should be stripped of all symbol set the flag in the structures of all output sections. */ if (mode == 0 && ld_state.strip == strip_all) { struct output_rule *runp; for (runp = newp->output_rules; runp != NULL; runp = runp->next) if (runp->tag == output_section) runp->val.section.ignored = true; } } static struct filename_list * new_filename_listelem (const char *string) { struct filename_list *newp; /* We use calloc and not the obstack since this object can be freed soon. */ newp = (struct filename_list *) xcalloc (1, sizeof (*newp)); newp->name = string; newp->next = newp; return newp; } static struct filename_list * mark_as_needed (struct filename_list *listp) { struct filename_list *runp = listp; do { runp->as_needed = true; runp = runp->next; } while (runp != listp); return listp; } static void add_inputfiles (struct filename_list *fnames) { assert (fnames != NULL); if (ld_state.srcfiles == NULL) ld_state.srcfiles = fnames; else { struct filename_list *first = ld_state.srcfiles->next; ld_state.srcfiles->next = fnames->next; fnames->next = first; ld_state.srcfiles->next = fnames; } } static _Bool special_char_p (const char *str) { while (*str != '\0') { if (__builtin_expect (*str == '*', 0) || __builtin_expect (*str == '?', 0) || __builtin_expect (*str == '[', 0)) return true; ++str; } return false; } static struct id_list * new_id_listelem (const char *str) { struct id_list *newp; newp = (struct id_list *) obstack_alloc (&ld_state.smem, sizeof (*newp)); if (str == NULL) newp->u.id_type = id_all; else if (__builtin_expect (special_char_p (str), false)) newp->u.id_type = id_wild; else newp->u.id_type = id_str; newp->id = str; newp->next = newp; return newp; } static struct version * new_version (struct id_list *local, struct id_list *global) { struct version *newp; newp = (struct version *) obstack_alloc (&ld_state.smem, sizeof (*newp)); newp->next = newp; newp->local_names = local; newp->global_names = global; newp->versionname = NULL; newp->parentname = NULL; return newp; } static struct version * merge_versions (struct version *one, struct version *two) { assert (two->local_names == NULL || two->global_names == NULL); if (two->local_names != NULL) { if (one->local_names == NULL) one->local_names = two->local_names; else { two->local_names->next = one->local_names->next; one->local_names = one->local_names->next = two->local_names; } } else { if (one->global_names == NULL) one->global_names = two->global_names; else { two->global_names->next = one->global_names->next; one->global_names = one->global_names->next = two->global_names; } } return one; } static void add_id_list (const char *versionname, struct id_list *runp, _Bool local) { struct id_list *lastp = runp; if (runp == NULL) /* Nothing to do. */ return; /* Convert into a simple single-linked list. */ runp = runp->next; assert (runp != NULL); lastp->next = NULL; do if (runp->u.id_type == id_str) { struct id_list *curp; struct id_list *defp; unsigned long int hval = elf_hash (runp->id); curp = runp; runp = runp->next; defp = ld_version_str_tab_find (&ld_state.version_str_tab, hval, curp); if (defp != NULL) { /* There is already a version definition for this symbol. */ while (strcmp (defp->u.s.versionname, versionname) != 0) { if (defp->next == NULL) { /* No version like this so far. */ defp->next = curp; curp->u.s.local = local; curp->u.s.versionname = versionname; curp->next = NULL; defp = NULL; break; } defp = defp->next; } if (defp != NULL && defp->u.s.local != local) error (EXIT_FAILURE, 0, versionname[0] == '\0' ? gettext ("\ symbol '%s' is declared both local and global for unnamed version '%s'") : gettext ("\ symbol '%s' is declared both local and global for version '%s'"), runp->id, versionname); } else { /* This is the first version definition for this symbol. */ ld_version_str_tab_insert (&ld_state.version_str_tab, hval, curp); curp->u.s.local = local; curp->u.s.versionname = versionname; curp->next = NULL; } } else if (runp->u.id_type == id_all) { if (local) { if (ld_state.default_bind_global) error (EXIT_FAILURE, 0, gettext ("default visibility set as local and global")); ld_state.default_bind_local = true; } else { if (ld_state.default_bind_local) error (EXIT_FAILURE, 0, gettext ("default visibility set as local and global")); ld_state.default_bind_global = true; } runp = runp->next; } else { assert (runp->u.id_type == id_wild); /* XXX TBI */ abort (); } while (runp != NULL); } static void add_versions (struct version *versions) { struct version *lastp = versions; if (versions == NULL) return; /* Convert into a simple single-linked list. */ versions = versions->next; assert (versions != NULL); lastp->next = NULL; do { add_id_list (versions->versionname, versions->local_names, true); add_id_list (versions->versionname, versions->global_names, false); versions = versions->next; } while (versions != NULL); }