1150 lines
35 KiB
C++
1150 lines
35 KiB
C++
/* annobin - a gcc plugin for annotating binary files.
|
|
Copyright (c) 2017 Red Hat.
|
|
Created by Nick Clifton.
|
|
|
|
This 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, or (at your
|
|
option) any later version.
|
|
|
|
It 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. */
|
|
|
|
#include "annobin.h"
|
|
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <intl.h>
|
|
|
|
/* The version of the annotation specification supported by this plugin. */
|
|
#define SPEC_VERSION 3
|
|
|
|
/* Required by the GCC plugin API. */
|
|
int plugin_is_GPL_compatible;
|
|
|
|
/* True if this plugin is enabled. Disabling is permitted so that build
|
|
systems can globally enable the plugin, and then have specific build
|
|
targets that disable the plugin because they do not want it. */
|
|
static bool enabled = true;
|
|
|
|
/* True if the symbols used to map addresses to file names should be global.
|
|
On some architectures these symbols have to be global so that they will
|
|
be preserved in object files. But doing so can prevent the build-id
|
|
mechanism from working, since the symbols contain build-date information. */
|
|
static bool global_file_name_symbols = false;
|
|
|
|
/* True if notes about the stack usage should be included. Doing can be useful
|
|
if stack overflow problems need to be diagnosed, but they do increase the size
|
|
of the note section quite a lot. */
|
|
bool annobin_enable_stack_size_notes = false;
|
|
unsigned long annobin_total_static_stack_usage = 0;
|
|
unsigned long annobin_max_stack_size = 0;
|
|
|
|
/* If a function's static stack size requirement is greater than STACK_THRESHOLD
|
|
then a function specific note will be generated indicating the amount of stack
|
|
that it needs. */
|
|
#define DEFAULT_THRESHOLD (10240)
|
|
static unsigned long stack_threshold = DEFAULT_THRESHOLD;
|
|
|
|
/* Internal variable, used by target specific parts of the annobin plugin as well
|
|
as this generic part. True if the object file being generated is for a 64-bit
|
|
target. */
|
|
bool annobin_is_64bit = false;
|
|
|
|
/* True if notes in the .note.gnu.property section should be produced. */
|
|
static bool annobin_enable_dynamic_notes = true;
|
|
|
|
/* True if notes in the .gnu.build.attributes section should be produced. */
|
|
static bool annobin_enable_static_notes = true;
|
|
|
|
static unsigned int annobin_note_count = 0;
|
|
static unsigned int global_GOWall_options = 0;
|
|
static int global_stack_prot_option = -1;
|
|
#ifdef flag_stack_clash_protection
|
|
static int global_stack_clash_option = -1;
|
|
#endif
|
|
static int global_pic_option = -1;
|
|
static int global_short_enums = -1;
|
|
static char * compiler_version = NULL;
|
|
static unsigned verbose_level = 0;
|
|
static char * annobin_current_filename = NULL;
|
|
static char * annobin_current_endname = NULL;
|
|
static unsigned char annobin_version = 3; /* NB. Keep in sync with version_string below. */
|
|
static const char * version_string = N_("Version 3");
|
|
static const char * help_string = N_("Supported options:\n\
|
|
disable Disable this plugin\n\
|
|
enable Enable this plugin\n\
|
|
help Print out this information\n\
|
|
version Print out the version of the plugin\n\
|
|
verbose Be talkative about what is going on\n\
|
|
[no-]dynamic-notes Do [do not] create dynamic notes (default: do)\n\
|
|
[no-]static-notes Do [do not] create static notes (default: do)\n\
|
|
[no-]global-file-syms Create global [or local] file name symbols (default: local)\n\
|
|
[no-]stack-size-notes Do [do not] create stack size notes (default: do not)\n\
|
|
stack-threshold=N Only create function specific stack size notes when the size is > N.");
|
|
|
|
static struct plugin_info annobin_info =
|
|
{
|
|
version_string,
|
|
help_string
|
|
};
|
|
|
|
/* Create a symbol name to represent the sources we are annotating.
|
|
Since there can be multiple input files, we choose the main output
|
|
filename (stripped of any path prefixes). Since filenames can
|
|
contain characters that symbol names do not (eg '-') we have to
|
|
allocate our own name. */
|
|
|
|
static void
|
|
init_annobin_current_filename (void)
|
|
{
|
|
char * name;
|
|
unsigned i;
|
|
|
|
if (annobin_current_filename != NULL
|
|
|| main_input_filename == NULL)
|
|
return;
|
|
|
|
name = (char *) lbasename (main_input_filename);
|
|
|
|
if (strlen (name) == 0)
|
|
{
|
|
/* The name can be empty if we are receiving the source code
|
|
from a pipe. In this case, we invent our own name. */
|
|
name = (char *) "piped_input";
|
|
}
|
|
|
|
if (global_file_name_symbols)
|
|
name = strcpy ((char *) xmalloc (strlen (name) + 20), name);
|
|
else
|
|
name = xstrdup (name);
|
|
|
|
/* Convert any non-symbolic characters into underscores. */
|
|
for (i = strlen (name); i--;)
|
|
{
|
|
char c = name[i];
|
|
|
|
if (! ISALNUM (c) && c != '_' && c != '.' && c != '$')
|
|
name[i] = '_';
|
|
else if (i == 0 && ISDIGIT (c))
|
|
name[i] = '_';
|
|
}
|
|
|
|
if (global_file_name_symbols)
|
|
{
|
|
/* A program can have multiple source files with the same name.
|
|
Or indeed the same source file can be included multiple times.
|
|
Or a library can be built from a sources which include file names
|
|
that match application file names. Whatever the reason, we need
|
|
to be ensure that we generate unique global symbol names. So we
|
|
append the time to the symbol name. This will of course break
|
|
the functionality of build-ids. That is why this option is off
|
|
by default. */
|
|
struct timeval tv;
|
|
|
|
if (gettimeofday (& tv, NULL))
|
|
{
|
|
annobin_inform (0, "ICE: unable to get time of day.");
|
|
tv.tv_sec = tv.tv_usec = 0;
|
|
}
|
|
sprintf (name + strlen (name),
|
|
"_%8.8lx_%8.8lx", (long) tv.tv_sec, (long) tv.tv_usec);
|
|
}
|
|
|
|
annobin_current_filename = name;
|
|
annobin_current_endname = concat (annobin_current_filename, "_end", NULL);
|
|
}
|
|
|
|
void
|
|
annobin_inform (unsigned level, const char * format, ...)
|
|
{
|
|
va_list args;
|
|
|
|
if (level > 0 && level > verbose_level)
|
|
return;
|
|
|
|
fflush (stdout);
|
|
fprintf (stderr, "annobin: ");
|
|
if (annobin_current_filename == NULL)
|
|
init_annobin_current_filename ();
|
|
if (annobin_current_filename)
|
|
fprintf (stderr, "%s: ", annobin_current_filename);
|
|
va_start (args, format);
|
|
vfprintf (stderr, format, args);
|
|
va_end (args);
|
|
putc ('\n', stderr);
|
|
}
|
|
|
|
void
|
|
annobin_output_note (const char * name,
|
|
unsigned namesz,
|
|
bool name_is_string,
|
|
const char * name_description,
|
|
const char * desc1,
|
|
const char * desc2,
|
|
unsigned descsz,
|
|
bool desc_is_string,
|
|
unsigned type)
|
|
{
|
|
unsigned i;
|
|
|
|
if (asm_out_file == NULL)
|
|
return;
|
|
|
|
if (type == NT_GNU_BUILD_ATTRIBUTE_FUNC
|
|
|| type == NT_GNU_BUILD_ATTRIBUTE_OPEN)
|
|
{
|
|
fprintf (asm_out_file, "\t.pushsection %s\n", GNU_BUILD_ATTRS_SECTION_NAME);
|
|
}
|
|
|
|
if (name == NULL)
|
|
{
|
|
if (namesz)
|
|
annobin_inform (0, "ICE: null name with non-zero size");
|
|
fprintf (asm_out_file, "\t.dc.l 0\t\t%s no name\n", ASM_COMMENT_START);
|
|
}
|
|
else if (name_is_string)
|
|
{
|
|
if (strlen ((char *) name) != namesz - 1)
|
|
annobin_inform (0, "ICE: name string '%s' does not match name size %d", name, namesz);
|
|
fprintf (asm_out_file, "\t.dc.l %u \t%s namesz = strlen (%s)\n", namesz, ASM_COMMENT_START, (char *) name);
|
|
}
|
|
else
|
|
fprintf (asm_out_file, "\t.dc.l %u\t\t%s size of name\n", namesz, ASM_COMMENT_START);
|
|
|
|
if (desc1 == NULL)
|
|
{
|
|
if (descsz)
|
|
annobin_inform (0, "ICE: null desc1 with non-zero size");
|
|
if (desc2 != NULL)
|
|
annobin_inform (0, "ICE: non-null desc2 with null desc1");
|
|
|
|
fprintf (asm_out_file, "\t.dc.l 0\t\t%s no description\n", ASM_COMMENT_START);
|
|
}
|
|
else if (desc_is_string)
|
|
{
|
|
switch (descsz)
|
|
{
|
|
case 0:
|
|
annobin_inform (0, "ICE: zero descsz with string description");
|
|
break;
|
|
case 4:
|
|
if (annobin_is_64bit || desc2 != NULL)
|
|
annobin_inform (0, "ICE: descz too small");
|
|
if (desc1 == NULL)
|
|
annobin_inform (0, "ICE: descz too big");
|
|
break;
|
|
case 8:
|
|
if (annobin_is_64bit)
|
|
{
|
|
if (desc2 != NULL)
|
|
annobin_inform (0, "ICE: descz too small");
|
|
}
|
|
else
|
|
{
|
|
if (desc1 == NULL || desc2 == NULL)
|
|
annobin_inform (0, "ICE: descz too big");
|
|
}
|
|
break;
|
|
case 16:
|
|
if (! annobin_is_64bit || desc1 == NULL || desc2 == NULL)
|
|
annobin_inform (0, "ICE: descz too big");
|
|
break;
|
|
default:
|
|
annobin_inform (0, "ICE: description string size (%d) does not match address size", descsz);
|
|
break;
|
|
}
|
|
|
|
fprintf (asm_out_file, "\t.dc.l %u%s%s descsz = sizeof (address%s)\n",
|
|
descsz, descsz < 10 ? "\t\t" : "\t", ASM_COMMENT_START, desc2 == NULL ? "" : "es");
|
|
}
|
|
else
|
|
{
|
|
if (desc2 != NULL)
|
|
annobin_inform (0, "ICE: second description not empty for non-string description");
|
|
|
|
fprintf (asm_out_file, "\t.dc.l %u\t\t%s size of description\n", descsz, ASM_COMMENT_START);
|
|
}
|
|
|
|
fprintf (asm_out_file, "\t.dc.l %#x\t%s type = %s\n", type, ASM_COMMENT_START,
|
|
type == NT_GNU_BUILD_ATTRIBUTE_OPEN ? "OPEN" :
|
|
type == NT_GNU_BUILD_ATTRIBUTE_FUNC ? "FUNC" :
|
|
type == NT_GNU_PROPERTY_TYPE_0 ? "PROPERTY_TYPE_0" : "*UNKNOWN*");
|
|
|
|
if (name)
|
|
{
|
|
if (name_is_string)
|
|
{
|
|
fprintf (asm_out_file, "\t.asciz \"%s\"", (char *) name);
|
|
}
|
|
else
|
|
{
|
|
fprintf (asm_out_file, "\t.dc.b");
|
|
for (i = 0; i < namesz; i++)
|
|
fprintf (asm_out_file, " %#x%c",
|
|
((unsigned char *) name)[i],
|
|
i < (namesz - 1) ? ',' : ' ');
|
|
}
|
|
|
|
fprintf (asm_out_file, "\t%s name (%s)\n",
|
|
ASM_COMMENT_START, name_description);
|
|
|
|
if (namesz % 4)
|
|
{
|
|
fprintf (asm_out_file, "\t.dc.b");
|
|
while (namesz % 4)
|
|
{
|
|
namesz++;
|
|
fprintf (asm_out_file, " 0%c", namesz % 4 ? ',' : ' ');
|
|
}
|
|
fprintf (asm_out_file, "\t%s Padding\n", ASM_COMMENT_START);
|
|
}
|
|
}
|
|
|
|
if (desc1)
|
|
{
|
|
if (desc_is_string)
|
|
{
|
|
/* The DESCRIPTION string is the name of a symbol. We want to produce
|
|
a reference to this symbol of the appropriate size for the target
|
|
architecture. */
|
|
if (annobin_is_64bit)
|
|
fprintf (asm_out_file, "\t.quad %s", (char *) desc1);
|
|
else
|
|
fprintf (asm_out_file, "\t.dc.l %s", (char *) desc1);
|
|
|
|
if (desc2)
|
|
{
|
|
fprintf (asm_out_file, "\n");
|
|
if (annobin_is_64bit)
|
|
fprintf (asm_out_file, "\t.quad %s", (char *) desc2);
|
|
else
|
|
fprintf (asm_out_file, "\t.dc.l %s", (char *) desc2);
|
|
}
|
|
|
|
fprintf (asm_out_file, "\t%s description (symbol name)\n", ASM_COMMENT_START);
|
|
}
|
|
else
|
|
{
|
|
fprintf (asm_out_file, "\t.dc.b");
|
|
|
|
for (i = 0; i < descsz; i++)
|
|
{
|
|
fprintf (asm_out_file, " %#x", ((unsigned char *) desc1)[i]);
|
|
if (i == (descsz - 1))
|
|
fprintf (asm_out_file, "\t%s description\n", ASM_COMMENT_START);
|
|
else if ((i % 8) == 7)
|
|
fprintf (asm_out_file, "\t%s description\n\t.dc.b", ASM_COMMENT_START);
|
|
else
|
|
fprintf (asm_out_file, ",");
|
|
}
|
|
|
|
if (descsz % 4)
|
|
{
|
|
fprintf (asm_out_file, "\t.dc.b");
|
|
while (descsz % 4)
|
|
{
|
|
descsz++;
|
|
fprintf (asm_out_file, " 0%c", descsz % 4 ? ',' : ' ');
|
|
}
|
|
fprintf (asm_out_file, "\t%s Padding\n", ASM_COMMENT_START);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type == NT_GNU_BUILD_ATTRIBUTE_FUNC
|
|
|| type == NT_GNU_BUILD_ATTRIBUTE_OPEN)
|
|
{
|
|
fprintf (asm_out_file, "\t.popsection\n");
|
|
fflush (asm_out_file);
|
|
}
|
|
|
|
fprintf (asm_out_file, "\n");
|
|
|
|
++ annobin_note_count;
|
|
}
|
|
|
|
void
|
|
annobin_output_bool_note (const char bool_type,
|
|
const bool bool_value,
|
|
const char * name_description,
|
|
const char * start,
|
|
const char * end,
|
|
unsigned note_type)
|
|
{
|
|
char buffer [6];
|
|
|
|
sprintf (buffer, "GA%c%c",
|
|
bool_value ? GNU_BUILD_ATTRIBUTE_TYPE_BOOL_TRUE : GNU_BUILD_ATTRIBUTE_TYPE_BOOL_FALSE,
|
|
bool_type);
|
|
|
|
/* Include the NUL byte at the end of the name "string".
|
|
This is required by the ELF spec. */
|
|
annobin_output_note (buffer, strlen (buffer) + 1, false, name_description,
|
|
start, end,
|
|
start == NULL ? 0 : (annobin_is_64bit ? (end == NULL ? 8 : 16) : (end == NULL ? 4: 8)),
|
|
true, note_type);
|
|
}
|
|
|
|
void
|
|
annobin_output_string_note (const char string_type,
|
|
const char * string,
|
|
const char * name_description,
|
|
const char * start,
|
|
const char * end,
|
|
unsigned note_type)
|
|
{
|
|
unsigned int len = strlen (string);
|
|
char * buffer;
|
|
|
|
buffer = (char *) xmalloc (len + 5);
|
|
|
|
sprintf (buffer, "GA%c%c%s", GNU_BUILD_ATTRIBUTE_TYPE_STRING, string_type, string);
|
|
|
|
annobin_output_note (buffer, len + 5, true, name_description,
|
|
start, end,
|
|
start == NULL ? 0 : (annobin_is_64bit ? (end == NULL ? 8 : 16) : (end == NULL ? 4 : 8)),
|
|
true, note_type);
|
|
}
|
|
|
|
void
|
|
annobin_output_numeric_note (const char numeric_type,
|
|
unsigned long value,
|
|
const char * name_description,
|
|
const char * start,
|
|
const char * end,
|
|
unsigned note_type)
|
|
{
|
|
unsigned i;
|
|
char buffer [32];
|
|
|
|
sprintf (buffer, "GA%c%c", GNU_BUILD_ATTRIBUTE_TYPE_NUMERIC, numeric_type);
|
|
|
|
if (value == 0)
|
|
{
|
|
/* We need to record *two* zero bytes for a zero value. One for
|
|
the value itself and one as a NUL terminator, since this is a
|
|
name field... */
|
|
buffer [4] = buffer [5] = 0;
|
|
i = 5;
|
|
}
|
|
else
|
|
{
|
|
for (i = 4; i < sizeof buffer; i++)
|
|
{
|
|
buffer[i] = value & 0xff;
|
|
/* Note - The name field in ELF Notes must be NUL terminated, even if,
|
|
like here, it is not really being used as a name. Hence the test
|
|
for value being zero is performed here, rather than after the shift. */
|
|
if (value == 0)
|
|
break;
|
|
value >>= 8;
|
|
}
|
|
}
|
|
|
|
/* If the value needs more than 8 bytes, consumers are unlikely to be able
|
|
to handle it. */
|
|
if (i > 12)
|
|
annobin_inform (0, "ICE: Numeric value for %s too big to fit into 8 bytes\n", name_description);
|
|
if (value)
|
|
annobin_inform (0, "ICE: Unable to record numeric value in note %s\n", name_description);
|
|
|
|
annobin_output_note (buffer, i + 1, false, name_description,
|
|
start, end,
|
|
start == NULL ? 0 : (annobin_is_64bit ? (end == NULL ? 8 : 16) : (end == NULL ? 4 : 8)),
|
|
true, note_type);
|
|
}
|
|
|
|
static int
|
|
compute_pic_option (void)
|
|
{
|
|
if (flag_pie > 1)
|
|
return 4;
|
|
if (flag_pie)
|
|
return 3;
|
|
if (flag_pic > 1)
|
|
return 2;
|
|
if (flag_pic)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Compute a numeric value representing the settings/levels of
|
|
the -O and -g options, and whether -Wall has been used. This
|
|
is to help verify the recommended hardening options for binaries.
|
|
The format of the number is as follows:
|
|
|
|
bits 0 - 2 : debug type (from enum debug_info_type)
|
|
bit 3 : with GNU extensions
|
|
bits 4 - 5 : debug level (from enum debug_info_levels)
|
|
bits 6 - 8 : DWARF version level
|
|
bits 9 - 10 : optimization level
|
|
bit 11 : -Os
|
|
bit 12 : -Ofast
|
|
bit 13 : -Og
|
|
bit 14 : -Wall. */
|
|
|
|
static unsigned int
|
|
compute_GOWall_options (void)
|
|
{
|
|
unsigned int val, i;
|
|
|
|
/* FIXME: Keep in sync with changes to gcc/flag-types.h:enum debug_info_type. */
|
|
if (write_symbols > VMS_AND_DWARF2_DEBUG)
|
|
{
|
|
annobin_inform (0, "ICE: unknown debug info type %d\n", write_symbols);
|
|
val = 0;
|
|
}
|
|
else
|
|
val = write_symbols;
|
|
|
|
if (use_gnu_debug_info_extensions)
|
|
val |= (1 << 3);
|
|
|
|
if (debug_info_level > DINFO_LEVEL_VERBOSE)
|
|
annobin_inform (0, "ICE: unknown debug info level %d\n", debug_info_level);
|
|
else
|
|
val |= (debug_info_level << 4);
|
|
|
|
if (dwarf_version < 0 || dwarf_version > 7)
|
|
annobin_inform (0, "ICE: unknown dwarf version level %d\n", dwarf_version);
|
|
else
|
|
val |= (dwarf_version << 6);
|
|
|
|
if (optimize > 3)
|
|
val |= (3 << 9);
|
|
else
|
|
val |= (optimize << 9);
|
|
|
|
/* FIXME: It should not be possible to enable more than one of -Os/-Of/-Og,
|
|
so the tests below could be simplified. */
|
|
if (optimize_size)
|
|
val |= (1 << 11);
|
|
if (optimize_fast)
|
|
val |= (1 << 12);
|
|
if (optimize_debug)
|
|
val |= (1 << 13);
|
|
|
|
/* Unfortunately -Wall is not recorded by gcc. So we have to scan the
|
|
command line... */
|
|
for (i = 0; i < save_decoded_options_count; i++)
|
|
{
|
|
if (save_decoded_options[i].opt_index == OPT_Wall)
|
|
{
|
|
val |= (1 << 14);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static void
|
|
record_GOW_settings (unsigned int gow, bool local, const char * cname, const char * aname, const char * aname_end)
|
|
{
|
|
char buffer [128];
|
|
unsigned i;
|
|
|
|
(void) sprintf (buffer, "GA%cGOW", GNU_BUILD_ATTRIBUTE_TYPE_NUMERIC);
|
|
|
|
for (i = 7; i < sizeof buffer; i++)
|
|
{
|
|
buffer[i] = gow & 0xff;
|
|
/* Note - The name field in ELF Notes must be NUL terminated, even if,
|
|
like here, it is not really being used as a name. Hence the test
|
|
for value being zero is performed here, rather than after the shift. */
|
|
if (gow == 0)
|
|
break;
|
|
gow >>= 8;
|
|
}
|
|
|
|
if (local)
|
|
{
|
|
annobin_inform (1, "Record a change in -g/-O/-Wall status for %s", cname);
|
|
annobin_output_note (buffer, i + 1, false, "numeric: -g/-O/-Wall",
|
|
aname, aname_end, annobin_is_64bit ? 16 : 8, true,
|
|
NT_GNU_BUILD_ATTRIBUTE_FUNC);
|
|
}
|
|
else
|
|
{
|
|
annobin_inform (1, "Record status of -g/-O/-Wall");
|
|
annobin_output_note (buffer, i + 1, false, "numeric: -g/-O/-Wall",
|
|
NULL, NULL, 0, false, NT_GNU_BUILD_ATTRIBUTE_OPEN);
|
|
}
|
|
}
|
|
|
|
#ifdef flag_stack_clash_protection
|
|
static void
|
|
record_stack_clash_note (const char * start, const char * end, int type)
|
|
{
|
|
char buffer [128];
|
|
unsigned len = sprintf (buffer, "GA%cstack_clash",
|
|
flag_stack_clash_protection
|
|
? GNU_BUILD_ATTRIBUTE_TYPE_BOOL_TRUE
|
|
: GNU_BUILD_ATTRIBUTE_TYPE_BOOL_FALSE);
|
|
|
|
annobin_output_note (buffer, len + 1, true, "bool: -fstack-clash-protection status",
|
|
start, end,
|
|
start == NULL ? 0 : (annobin_is_64bit ? (end == NULL ? 8 : 16) : (end == NULL ? 4: 8)),
|
|
true, type);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
annobin_create_function_notes (void * gcc_data, void * user_data)
|
|
{
|
|
const char * cname = current_function_name ();
|
|
const char * aname = function_asm_name ();
|
|
const char * aname_end;
|
|
const char * saved_aname_end;
|
|
unsigned int count;
|
|
|
|
if (! annobin_enable_static_notes)
|
|
return;
|
|
|
|
if (asm_out_file == NULL)
|
|
return;
|
|
|
|
if (cname == NULL)
|
|
{
|
|
if (aname == NULL)
|
|
{
|
|
/* Can this happen ? */
|
|
annobin_inform (0, "ICE: function name not available");
|
|
return;
|
|
}
|
|
cname = aname;
|
|
}
|
|
else if (aname == NULL)
|
|
aname = cname;
|
|
|
|
saved_aname_end = aname_end = concat (aname, "_end", NULL);
|
|
count = annobin_note_count;
|
|
|
|
annobin_target_specific_function_notes (aname, aname_end);
|
|
|
|
if (count > annobin_note_count)
|
|
{
|
|
free ((void *) aname_end);
|
|
aname = aname_end = NULL;
|
|
}
|
|
|
|
if (global_stack_prot_option != flag_stack_protect)
|
|
{
|
|
annobin_inform (1, "Recording change in stack protection status for %s (from %d to %d)",
|
|
cname, global_stack_prot_option, flag_stack_protect);
|
|
|
|
annobin_output_numeric_note (GNU_BUILD_ATTRIBUTE_STACK_PROT, flag_stack_protect,
|
|
"numeric: -fstack-protector status",
|
|
aname, aname_end, NT_GNU_BUILD_ATTRIBUTE_FUNC);
|
|
|
|
if (aname != NULL)
|
|
aname = aname_end = NULL;
|
|
}
|
|
|
|
#ifdef flag_stack_clash_protection
|
|
if (global_stack_clash_option != flag_stack_clash_protection)
|
|
{
|
|
annobin_inform (1, "Recording change in stack clash protection status for %s (from %d to %d)",
|
|
cname, global_stack_clash_option, flag_stack_clash_protection);
|
|
|
|
record_stack_clash_note (aname, aname_end, NT_GNU_BUILD_ATTRIBUTE_FUNC);
|
|
|
|
if (aname != NULL)
|
|
aname = aname_end = NULL;
|
|
}
|
|
#endif
|
|
|
|
if (global_pic_option != compute_pic_option ())
|
|
{
|
|
annobin_inform (1, "Recording change in PIC status for %s", cname);
|
|
annobin_output_numeric_note (GNU_BUILD_ATTRIBUTE_PIC, compute_pic_option (),
|
|
"numeric: pic type", aname, aname_end,
|
|
NT_GNU_BUILD_ATTRIBUTE_FUNC);
|
|
if (aname != NULL)
|
|
aname = aname_end = NULL;
|
|
}
|
|
|
|
if (global_GOWall_options != compute_GOWall_options ())
|
|
{
|
|
record_GOW_settings (compute_GOWall_options (), true, cname, aname, aname_end);
|
|
|
|
if (aname != NULL)
|
|
aname = aname_end = NULL;
|
|
}
|
|
|
|
if (global_short_enums != flag_short_enums)
|
|
{
|
|
annobin_inform (1, "Recording change in enum size for %s", cname);
|
|
annobin_output_bool_note (GNU_BUILD_ATTRIBUTE_SHORT_ENUM, flag_short_enums,
|
|
flag_short_enums ? "bool: short-enums: on" : "bool: short-enums: off",
|
|
aname, aname_end, NT_GNU_BUILD_ATTRIBUTE_FUNC);
|
|
if (aname != NULL)
|
|
aname = aname_end = NULL;
|
|
}
|
|
|
|
if (annobin_enable_stack_size_notes && flag_stack_usage_info)
|
|
{
|
|
if ((unsigned long) current_function_static_stack_size > stack_threshold)
|
|
{
|
|
annobin_inform (1, "Recording stack usage of %lu for %s",
|
|
current_function_static_stack_size, cname);
|
|
|
|
annobin_output_numeric_note (GNU_BUILD_ATTRIBUTE_STACK_SIZE,
|
|
current_function_static_stack_size,
|
|
"numeric: stack-size",
|
|
aname, aname_end,
|
|
NT_GNU_BUILD_ATTRIBUTE_FUNC);
|
|
if (aname != NULL)
|
|
aname = aname_end = NULL;
|
|
}
|
|
|
|
annobin_total_static_stack_usage += current_function_static_stack_size;
|
|
|
|
if ((unsigned long) current_function_static_stack_size > annobin_max_stack_size)
|
|
annobin_max_stack_size = current_function_static_stack_size;
|
|
}
|
|
|
|
if (annobin_note_count > count)
|
|
{
|
|
// /* FIXME: This assumes that the function is in the .text section... */
|
|
// fprintf (asm_out_file, "\t.pushsection .text\n");
|
|
fprintf (asm_out_file, "%s:\n", saved_aname_end);
|
|
// fprintf (asm_out_file, "\t.popsection\n");
|
|
}
|
|
|
|
free ((void *) saved_aname_end);
|
|
}
|
|
|
|
static void
|
|
record_fortify_level (int level)
|
|
{
|
|
char buffer [128];
|
|
unsigned len = sprintf (buffer, "GA%cFORTIFY", GNU_BUILD_ATTRIBUTE_TYPE_NUMERIC);
|
|
|
|
buffer[++len] = level;
|
|
buffer[++len] = 0;
|
|
annobin_output_note (buffer, len + 1, false, "FORTIFY SOURCE level",
|
|
NULL, NULL, 0, false, NT_GNU_BUILD_ATTRIBUTE_OPEN);
|
|
annobin_inform (1, "Record a FORTIFY SOURCE level of %d", level);
|
|
}
|
|
|
|
static void
|
|
record_glibcxx_assertions (bool on)
|
|
{
|
|
char buffer [128];
|
|
unsigned len = sprintf (buffer, "GA%cGLIBCXX_ASSERTIONS",
|
|
on ? GNU_BUILD_ATTRIBUTE_TYPE_BOOL_TRUE : GNU_BUILD_ATTRIBUTE_TYPE_BOOL_FALSE);
|
|
|
|
annobin_output_note (buffer, len + 1, false, "_GLIBCXX_ASSERTIONS defined",
|
|
NULL, NULL, 0, false, NT_GNU_BUILD_ATTRIBUTE_OPEN);
|
|
annobin_inform (1, "Record a _GLIBCXX_ASSERTIONS as %s", on ? "defined" : "not defined");
|
|
}
|
|
|
|
static void
|
|
annobin_create_global_notes (void * gcc_data, void * user_data)
|
|
{
|
|
int i;
|
|
char buffer [1024]; /* FIXME: Is this enough ? */
|
|
|
|
if (! annobin_enable_static_notes)
|
|
return;
|
|
|
|
if (asm_out_file == NULL)
|
|
{
|
|
/* This happens during LTO compilation. Compilation is triggered
|
|
before any output file has been opened. Since we do not have
|
|
the file handle we cannot emit any notes. On the other hand,
|
|
the recompilation process will repeat later on with a real
|
|
output file and so the notes can be generated then. */
|
|
annobin_inform (1, "Output file not available - unable to generate notes");
|
|
return;
|
|
}
|
|
|
|
/* Record global information.
|
|
Note - we do this here, rather than in plugin_init() as some
|
|
information, PIC status or POINTER_SIZE, may not be initialised
|
|
until after the target backend has had a chance to process its
|
|
command line options, and this happens *after* plugin_init. */
|
|
|
|
/* Compute the default data size. */
|
|
switch (POINTER_SIZE)
|
|
{
|
|
case 16:
|
|
case 32:
|
|
annobin_is_64bit = false; break;
|
|
case 64:
|
|
annobin_is_64bit = true; break;
|
|
default:
|
|
annobin_inform (0, _("Unknown target pointer size: %d"), POINTER_SIZE);
|
|
}
|
|
|
|
if (annobin_enable_stack_size_notes)
|
|
/* We must set this flag in order to obtain per-function stack usage info. */
|
|
flag_stack_usage_info = 1;
|
|
|
|
global_stack_prot_option = flag_stack_protect;
|
|
#ifdef flag_stack_clash_protection
|
|
global_stack_clash_option = flag_stack_clash_protection;
|
|
#endif
|
|
global_pic_option = compute_pic_option ();
|
|
global_short_enums = flag_short_enums;
|
|
global_GOWall_options = compute_GOWall_options ();
|
|
|
|
/* Output a file name symbol to be referenced by the notes... */
|
|
if (annobin_current_filename == NULL)
|
|
init_annobin_current_filename ();
|
|
if (annobin_current_filename == NULL)
|
|
{
|
|
annobin_inform (0, "ICE: Could not find output filename");
|
|
/* We need a filename, so invent one. */
|
|
annobin_current_filename = (char *) "unknown_source";
|
|
}
|
|
|
|
/* Create a symbol for this compilation unit. */
|
|
if (global_file_name_symbols)
|
|
fprintf (asm_out_file, ".global %s\n", annobin_current_filename);
|
|
fprintf (asm_out_file, ".type %s STT_OBJECT\n", annobin_current_filename);
|
|
fprintf (asm_out_file, ".size %s, %s - %s\n",annobin_current_filename, annobin_current_endname, annobin_current_filename);
|
|
fprintf (asm_out_file, "%s:\n", annobin_current_filename);
|
|
|
|
/* Create the static notes section. */
|
|
#ifdef OLD_GAS
|
|
/* GAS prior to version 2.27 did not support setting section flags via a numeric value. */
|
|
fprintf (asm_out_file, "\t.pushsection %s, \"\", %%note\n",
|
|
GNU_BUILD_ATTRS_SECTION_NAME);
|
|
#else
|
|
fprintf (asm_out_file, "\t.pushsection %s, \"%#x\", %%note\n",
|
|
GNU_BUILD_ATTRS_SECTION_NAME, SHF_GNU_BUILD_NOTE);
|
|
#endif
|
|
fprintf (asm_out_file, "\t.balign 4\n");
|
|
|
|
/* Output the version of the specification supported. */
|
|
sprintf (buffer, "%dp%d", SPEC_VERSION, annobin_version);
|
|
annobin_output_string_note (GNU_BUILD_ATTRIBUTE_VERSION, buffer,
|
|
"string: version",
|
|
annobin_current_filename,
|
|
annobin_current_endname,
|
|
NT_GNU_BUILD_ATTRIBUTE_OPEN);
|
|
|
|
/* Record the version of the compiler. */
|
|
annobin_output_string_note (GNU_BUILD_ATTRIBUTE_TOOL, compiler_version,
|
|
"string: build-tool", NULL, NULL, NT_GNU_BUILD_ATTRIBUTE_OPEN);
|
|
|
|
/* Record optimization level, -W setting and -g setting */
|
|
record_GOW_settings (global_GOWall_options, false, NULL, NULL, NULL);
|
|
|
|
/* Record -fstack-protector option. */
|
|
annobin_output_numeric_note (GNU_BUILD_ATTRIBUTE_STACK_PROT, global_stack_prot_option,
|
|
"numeric: -fstack-protector status",
|
|
NULL, NULL, NT_GNU_BUILD_ATTRIBUTE_OPEN);
|
|
|
|
#ifdef flag_stack_clash_protection
|
|
/* Record -fstack-clash-protection option. */
|
|
record_stack_clash_note (NULL, NULL, NT_GNU_BUILD_ATTRIBUTE_OPEN);
|
|
#endif
|
|
|
|
/* Look for -D _FORTIFY_SOURCE=<n> on the original gcc command line.
|
|
Scan backwards so that we record the last version of the option,
|
|
should multiple versions be set. */
|
|
bool fortify_level_recorded = false;
|
|
bool glibcxx_assertions_recorded = false;
|
|
|
|
for (i = save_decoded_options_count; i--;)
|
|
{
|
|
if (save_decoded_options[i].opt_index == OPT_D)
|
|
{
|
|
if (save_decoded_options[i].arg == NULL)
|
|
continue;
|
|
|
|
if (strncmp (save_decoded_options[i].arg, "_FORTIFY_SOURCE=", strlen ("_FORTIFY_SOURCE=")) == 0)
|
|
{
|
|
int level = atoi (save_decoded_options[i].arg + strlen ("_FORTIFY_SOURCE="));
|
|
|
|
if (level < 0 || level > 3)
|
|
{
|
|
annobin_inform (0, "Unexpected value for FORIFY SOURCE: %s",
|
|
save_decoded_options[i].arg);
|
|
level = 0;
|
|
}
|
|
|
|
if (! fortify_level_recorded)
|
|
{
|
|
record_fortify_level (level);
|
|
fortify_level_recorded = true;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (strncmp (save_decoded_options[i].arg, "_GLIBCXX_ASSERTIONS", strlen ("_GLIBCXX_ASSERTIONS")) == 0)
|
|
{
|
|
if (! glibcxx_assertions_recorded)
|
|
{
|
|
record_glibcxx_assertions (true);
|
|
glibcxx_assertions_recorded = true;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
else if (save_decoded_options[i].opt_index == OPT_fpreprocessed)
|
|
{
|
|
/* Preprocessed sources *might* have had -D_FORTIFY_SOURCE=<n>
|
|
applied, but we cannot tell from here. Well not without a
|
|
deep inspection of the preprocessed sources. So instead we
|
|
record a level of -1 to let the user known that we do not know.
|
|
Note: preprocessed sources includes the use of --save-temps. */
|
|
record_fortify_level (-1);
|
|
fortify_level_recorded = true;
|
|
record_glibcxx_assertions (false); /* FIXME: need a tri-state value... */
|
|
glibcxx_assertions_recorded = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (! fortify_level_recorded)
|
|
record_fortify_level (0);
|
|
|
|
if (! glibcxx_assertions_recorded)
|
|
record_glibcxx_assertions (false);
|
|
|
|
/* Record the PIC status. */
|
|
annobin_output_numeric_note (GNU_BUILD_ATTRIBUTE_PIC, global_pic_option,
|
|
"numeric: PIC", NULL, NULL, NT_GNU_BUILD_ATTRIBUTE_OPEN);
|
|
|
|
/* Record enum size. */
|
|
annobin_output_bool_note (GNU_BUILD_ATTRIBUTE_SHORT_ENUM, global_short_enums,
|
|
global_short_enums ? "bool: short-enums: on" : "bool: short-enums: off",
|
|
NULL, NULL, NT_GNU_BUILD_ATTRIBUTE_OPEN);
|
|
|
|
/* Record target specific notes. */
|
|
annobin_record_global_target_notes ();
|
|
|
|
fprintf (asm_out_file, "\t.popsection\n");
|
|
fflush (asm_out_file);
|
|
}
|
|
|
|
static void
|
|
annobin_create_loader_notes (void * gcc_data, void * user_data)
|
|
{
|
|
if (asm_out_file == NULL)
|
|
return;
|
|
|
|
/* FIXME: This assumes that functions are being placed into the .text section. */
|
|
fprintf (asm_out_file, "\t.pushsection .text\n");
|
|
fprintf (asm_out_file, "%s:\n", annobin_current_endname);
|
|
fprintf (asm_out_file, "\t.popsection\n");
|
|
|
|
if (! annobin_enable_dynamic_notes)
|
|
return;
|
|
|
|
if (annobin_enable_stack_size_notes && annobin_total_static_stack_usage)
|
|
{
|
|
annobin_inform (1, "Recording total static usage of %ld", annobin_total_static_stack_usage);
|
|
|
|
fprintf (asm_out_file, "\t.pushsection %s\n", GNU_BUILD_ATTRS_SECTION_NAME);
|
|
annobin_output_numeric_note (GNU_BUILD_ATTRIBUTE_STACK_SIZE, annobin_total_static_stack_usage,
|
|
"numeric: stack-size", NULL, NULL, NT_GNU_BUILD_ATTRIBUTE_OPEN);
|
|
fprintf (asm_out_file, "\t.popsection\n");
|
|
}
|
|
|
|
annobin_target_specific_loader_notes ();
|
|
}
|
|
|
|
static bool
|
|
parse_args (unsigned argc, struct plugin_argument * argv)
|
|
{
|
|
while (argc--)
|
|
{
|
|
char * key = argv[argc].key;
|
|
|
|
while (*key == '-')
|
|
++ key;
|
|
|
|
/* These options allow the plugin to be enabled/disabled by a build
|
|
system without having to change the option that loads the plugin
|
|
itself. */
|
|
if (strcmp (key, "disable") == 0)
|
|
enabled = false;
|
|
|
|
else if (strcmp (key, "enable") == 0)
|
|
enabled = true;
|
|
|
|
else if (strcmp (key, "help") == 0)
|
|
annobin_inform (0, help_string);
|
|
|
|
else if (strcmp (key, "version") == 0)
|
|
annobin_inform (0, version_string);
|
|
|
|
else if (strcmp (key, "verbose") == 0)
|
|
verbose_level ++;
|
|
|
|
else if (strcmp (key, "global-file-syms") == 0)
|
|
global_file_name_symbols = true;
|
|
else if (strcmp (key, "no-global-file-syms") == 0)
|
|
global_file_name_symbols = false;
|
|
|
|
else if (strcmp (key, "stack-size-notes") == 0)
|
|
annobin_enable_stack_size_notes = true;
|
|
else if (strcmp (key, "no-stack-size-notes") == 0)
|
|
annobin_enable_stack_size_notes = false;
|
|
|
|
else if (strcmp (key, "dynamic-notes") == 0)
|
|
annobin_enable_dynamic_notes = true;
|
|
else if (strcmp (key, "no-dynamic-notes") == 0)
|
|
annobin_enable_dynamic_notes = false;
|
|
|
|
else if (strcmp (key, "static-notes") == 0)
|
|
annobin_enable_static_notes = true;
|
|
else if (strcmp (key, "no-static-notes") == 0)
|
|
annobin_enable_static_notes = false;
|
|
|
|
else if (strcmp (key, "stack-threshold") == 0)
|
|
{
|
|
stack_threshold = strtoul (argv[argc].value, NULL, 0);
|
|
if (stack_threshold == 0)
|
|
stack_threshold = DEFAULT_THRESHOLD;
|
|
}
|
|
|
|
else
|
|
{
|
|
annobin_inform (0, "unrecognised option: %s", argv[argc].key);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int
|
|
plugin_init (struct plugin_name_args * plugin_info,
|
|
struct plugin_gcc_version * version)
|
|
{
|
|
if (!plugin_default_version_check (version, & gcc_version))
|
|
{
|
|
bool fail = false;
|
|
|
|
if (strcmp (version->basever, gcc_version.basever))
|
|
{
|
|
annobin_inform (0, _("Error: plugin built for compiler version (%s) but run with compiler version (%s)"),
|
|
version->basever, gcc_version.basever);
|
|
fail = true;
|
|
}
|
|
|
|
/* Since the plugin is not part of the gcc project, it is entirely
|
|
likely that it has been built on a different day. This is not
|
|
a showstopper however, since compatibility will be retained as
|
|
long as the correct headers were used. */
|
|
if (strcmp (version->datestamp, gcc_version.datestamp))
|
|
annobin_inform (1, _("Plugin datestamp (%s) is different from compiler datestamp (%s)"),
|
|
version->datestamp, gcc_version.datestamp);
|
|
|
|
/* Unlikely, but also not serious. */
|
|
if (strcmp (version->devphase, gcc_version.devphase))
|
|
annobin_inform (1, _("Plugin built for compiler development phase (%s) not (%s)"),
|
|
version->devphase, gcc_version.devphase);
|
|
|
|
/* Theoretically this could be a problem, in practice it probably isn't. */
|
|
if (strcmp (version->revision, gcc_version.revision))
|
|
annobin_inform (1, _("Warning: plugin built for compiler revision (%s) not (%s)"),
|
|
version->revision, gcc_version.revision);
|
|
|
|
if (strcmp (version->configuration_arguments, gcc_version.configuration_arguments))
|
|
{
|
|
const char * plugin_target;
|
|
const char * gcc_target;
|
|
const char * plugin_target_end;
|
|
const char * gcc_target_end;
|
|
|
|
/* The entire configuration string can be very verbose,
|
|
so try to catch the case of compiler and plugin being
|
|
built for different targets and tell the user just that. */
|
|
plugin_target = strstr (version->configuration_arguments, "target=");
|
|
gcc_target = strstr (gcc_version.configuration_arguments, "target=");
|
|
if (plugin_target)
|
|
{
|
|
plugin_target += 7; /* strlen ("target=") */
|
|
plugin_target_end = strchr (plugin_target, ' ');
|
|
}
|
|
else
|
|
{
|
|
plugin_target = "native";
|
|
plugin_target_end = gcc_target + 6; /* strlen ("native") */
|
|
}
|
|
if (gcc_target)
|
|
{
|
|
gcc_target += 7;
|
|
gcc_target_end = strchr (gcc_target, ' ');
|
|
}
|
|
else
|
|
{
|
|
gcc_target = "native";
|
|
gcc_target_end = gcc_target + 6;
|
|
}
|
|
|
|
if (plugin_target_end
|
|
&& gcc_target_end
|
|
&& strncmp (plugin_target, gcc_target, plugin_target_end - plugin_target))
|
|
{
|
|
annobin_inform (0, _("Error: plugin run on a %.*s compiler but built on a %.*s compiler"),
|
|
plugin_target_end - plugin_target, plugin_target,
|
|
gcc_target_end - gcc_target, gcc_target);
|
|
fail = true;
|
|
}
|
|
else
|
|
{
|
|
annobin_inform (1, _("Plugin run on a compiler configured as (%s) not (%s)"),
|
|
version->configuration_arguments, gcc_version.configuration_arguments);
|
|
}
|
|
}
|
|
|
|
if (fail)
|
|
return 1;
|
|
}
|
|
|
|
if (! parse_args (plugin_info->argc, plugin_info->argv))
|
|
{
|
|
annobin_inform (1, _("failed to parse arguments to the plugin"));
|
|
return 1;
|
|
}
|
|
|
|
if (! enabled)
|
|
return 0;
|
|
|
|
if (! annobin_enable_dynamic_notes && ! annobin_enable_static_notes)
|
|
{
|
|
annobin_inform (1, _("nothing to be done"));
|
|
return 0;
|
|
}
|
|
|
|
/* Record global compiler options. */
|
|
compiler_version = (char *) xmalloc (strlen (version->basever) + strlen (version->datestamp) + 6);
|
|
sprintf (compiler_version, "gcc %s %s", version->basever, version->datestamp);
|
|
|
|
annobin_save_target_specific_information ();
|
|
|
|
register_callback (plugin_info->base_name,
|
|
PLUGIN_INFO,
|
|
NULL,
|
|
& annobin_info);
|
|
|
|
register_callback ("annobin: Generate global annotations",
|
|
PLUGIN_START_UNIT,
|
|
annobin_create_global_notes,
|
|
NULL);
|
|
|
|
register_callback ("annobin: Generate per-function annotations",
|
|
PLUGIN_ALL_PASSES_END,
|
|
annobin_create_function_notes,
|
|
NULL);
|
|
|
|
register_callback ("annobin: Generate final annotations",
|
|
PLUGIN_FINISH_UNIT,
|
|
annobin_create_loader_notes,
|
|
NULL);
|
|
return 0;
|
|
}
|