blob: ec58e21aa17a08550713940054f9effcb232f67c [file] [log] [blame]
%{
/* Parser for linker scripts.
Copyright (C) 2001-2011 Red Hat, Inc.
This file is part of elfutils.
Written by Ulrich Drepper <drepper@redhat.com>, 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 <http://www.gnu.org/licenses/>. */
#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);
}