summaryrefslogtreecommitdiff
path: root/src/ldscript.y
diff options
context:
space:
mode:
Diffstat (limited to 'src/ldscript.y')
-rw-r--r--src/ldscript.y811
1 files changed, 811 insertions, 0 deletions
diff --git a/src/ldscript.y b/src/ldscript.y
new file mode 100644
index 0000000..c2f1971
--- /dev/null
+++ b/src/ldscript.y
@@ -0,0 +1,811 @@
+%{
+/* Parser for linker scripts.
+ Copyright (C) 2001-2011 Red Hat, Inc.
+ This file is part of Red Hat elfutils.
+ Written by Ulrich Drepper <drepper@redhat.com>, 2001.
+
+ Red Hat elfutils 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; version 2 of the License.
+
+ Red Hat 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 Red Hat elfutils; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA.
+
+ Red Hat elfutils is an included package of the Open Invention Network.
+ An included package of the Open Invention Network is a package for which
+ Open Invention Network licensees cross-license their patents. No patent
+ license is granted, either expressly or impliedly, by designation as an
+ included package. Should you wish to participate in the Open Invention
+ Network licensing program, please visit www.openinventionnetwork.com
+ <http://www.openinventionnetwork.com>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <assert.h>
+#include <error.h>
+#include <libintl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <system.h>
+#include <ld.h>
+
+/* 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 <str> kFILENAME
+%token kGLOBAL
+%token kGROUP
+%token <str> kID
+%token kINPUT
+%token kINTERP
+%token kKEEP
+%token kLOCAL
+%token <num> kMODE
+%token kMUL_OP
+%token <num> 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 <op> kADD_OP
+%type <op> kMUL_OP
+%type <str> filename_id
+%type <str> filename_id_star
+%type <str> exclude_opt
+%type <expr> expr
+%type <sectionname> sort_opt_name
+%type <filemask_section_name> sectionname
+%type <input_rule> inputsection
+%type <input_rule> inputsections
+%type <output_rule> outputsection
+%type <output_rule> outputsections
+%type <assignment> assignment
+%type <filename_list> filename_id_list
+%type <filename_list> filename_id_listelem
+%type <version> versionlist
+%type <version> version
+%type <version> version_stmt_list
+%type <version> version_stmt
+%type <id_list> 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")
+ : 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);
+}