mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2026-08-27 00:26:02 -04:00
Sync libiberty sources with gcc master version
This commit is contained in:
parent
e0f0649602
commit
8872d02b0f
11 changed files with 521 additions and 128 deletions
|
|
@ -64,14 +64,14 @@ extern "C" {
|
|||
/* Disable a limit on the depth of recursion in mangled strings.
|
||||
Note if this limit is disabled then stack exhaustion is possible when
|
||||
demangling pathologically complicated strings. Bug reports about stack
|
||||
exhaustion when the option is enabled will be rejected. */
|
||||
#define DMGL_NO_RECURSE_LIMIT (1 << 18)
|
||||
exhaustion when the option is enabled will be rejected. */
|
||||
#define DMGL_NO_RECURSE_LIMIT (1 << 18)
|
||||
|
||||
/* If DMGL_NO_RECURSE_LIMIT is not enabled, then this is the value used as
|
||||
the maximum depth of recursion allowed. It should be enough for any
|
||||
real-world mangled name. */
|
||||
#define DEMANGLE_RECURSION_LIMIT 2048
|
||||
|
||||
|
||||
/* Enumeration of possible demangling styles.
|
||||
|
||||
Lucid and ARM styles are still kept logically distinct, even though
|
||||
|
|
@ -435,6 +435,8 @@ enum demangle_component_type
|
|||
DEMANGLE_COMPONENT_DEFAULT_ARG,
|
||||
/* An unnamed type. */
|
||||
DEMANGLE_COMPONENT_UNNAMED_TYPE,
|
||||
/* An unnamed enum. */
|
||||
DEMANGLE_COMPONENT_UNNAMED_ENUM,
|
||||
/* A transactional clone. This has one subtree, the encoding for
|
||||
which it is providing alternative linkage. */
|
||||
DEMANGLE_COMPONENT_TRANSACTION_CLONE,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* Function declarations for libiberty.
|
||||
|
||||
Copyright (C) 1997-2026 Free Software Foundation, Inc.
|
||||
|
||||
|
||||
Note - certain prototypes declared in this header file are for
|
||||
functions whoes implementation copyright does not belong to the
|
||||
FSF. Those prototypes are present in this file for reference
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street - Fifth Floor,
|
||||
Boston, MA 02110-1301, USA.
|
||||
|
||||
|
||||
Written by Cygnus Support, 1994.
|
||||
|
||||
The libiberty library provides a number of functions which are
|
||||
|
|
@ -108,7 +108,7 @@ extern int countargv (char * const *);
|
|||
#if defined (__GNU_LIBRARY__ ) || defined (__linux__) \
|
||||
|| defined (__FreeBSD__) || defined (__OpenBSD__) || defined (__NetBSD__) \
|
||||
|| defined (__CYGWIN__) || defined (__CYGWIN32__) || defined (__MINGW32__) \
|
||||
|| defined (__DragonFly__) || defined (HAVE_DECL_BASENAME)
|
||||
|| defined (__DragonFly__) || defined (HAVE_DECL_BASENAME)
|
||||
extern char *basename (const char *) ATTRIBUTE_RETURNS_NONNULL ATTRIBUTE_NONNULL(1);
|
||||
#else
|
||||
/* Do not allow basename to be used if there is no prototype seen. We
|
||||
|
|
@ -232,7 +232,7 @@ extern char * getpwd (void);
|
|||
#ifdef __MINGW32__
|
||||
/* Forward declaration to avoid #include <sys/time.h>. */
|
||||
struct timeval;
|
||||
extern int gettimeofday (struct timeval *, void *);
|
||||
extern int gettimeofday (struct timeval *, void *);
|
||||
#endif
|
||||
|
||||
/* Get the amount of time the process has run, in microseconds. */
|
||||
|
|
@ -482,7 +482,7 @@ extern struct pex_obj *pex_init (int flags, const char *pname,
|
|||
/* Capture stderr to a pipe. The output can be read by
|
||||
calling pex_read_err and reading from the returned
|
||||
FILE object. This flag may be specified only for
|
||||
the last program in a pipeline.
|
||||
the last program in a pipeline.
|
||||
|
||||
This flag is supported only on Unix and Windows. */
|
||||
#define PEX_STDERR_TO_PIPE 0x40
|
||||
|
|
|
|||
|
|
@ -1,3 +1,57 @@
|
|||
2025-11-30 Jose E. Marchesi <jose.marchesi@oracle.com>
|
||||
|
||||
* simple-object-mach-o.c
|
||||
(simple_object_mach_o_segment): Handle non-LTO sections.
|
||||
|
||||
2025-11-06 Peter Damianov <peter0x44@disroot.org>
|
||||
|
||||
PR target/122472
|
||||
* simple-object-coff.c (struct external_filehdr_bigobj): New
|
||||
structure for BigObj file header.
|
||||
(bigobj_magic): New constant for BigObj magic bytes.
|
||||
(struct external_syment_bigobj): New structure for BigObj
|
||||
20-byte symbol table entries.
|
||||
(union external_auxent_bigobj): New union for BigObj 20-byte
|
||||
auxiliary symbol entries.
|
||||
(struct simple_object_coff_read): Add is_bigobj flag and make
|
||||
nscns 32-bit to support both formats.
|
||||
(struct simple_object_coff_attributes): Add is_bigobj flag.
|
||||
(simple_object_coff_match): Add BigObj format detection.
|
||||
(simple_object_coff_read_strtab): Use format-specific symbol
|
||||
size when calculating string table offset.
|
||||
(simple_object_coff_attributes_merge): Check is_bigobj flag.
|
||||
(simple_object_coff_write_filehdr_bigobj): New function.
|
||||
(simple_object_coff_write_to_file): Add logic for writing
|
||||
BigObj vs regular COFF format with appropriate symbol
|
||||
and auxiliary entry structures.
|
||||
|
||||
2025-11-03 Sam James <sam@gentoo.org>
|
||||
|
||||
* configure: Regenerate.
|
||||
|
||||
2025-10-02 H.J. Lu <hjl.tools@gmail.com>
|
||||
|
||||
* aclocal.m4: Regenerated.
|
||||
* configure: Likewise.
|
||||
* configure.ac: Synced from binutils-gdb.
|
||||
|
||||
2025-08-17 Nathaniel Shead <nathanieloshead@gmail.com>
|
||||
|
||||
PR c++/120503
|
||||
PR c++/120824
|
||||
* cp-demangle.c (d_unnamed_enum): New function.
|
||||
(d_unqualified_name): Call it.
|
||||
(cplus_demangle_type): Handle unscoped unnamed types
|
||||
(Ue, Ul, etc.)
|
||||
(d_count_templates_scopes): Handle unnamed enums.
|
||||
(d_find_pack): Likewise.
|
||||
(d_print_comp_inner): Print unnamed enums.
|
||||
* testsuite/demangle-expected: Add tests.
|
||||
|
||||
2025-08-13 Jakub Jelinek <jakub@redhat.com>
|
||||
|
||||
* cp-demangle.c (d_encoding): Fix a comment typo, whaever -> whatever.
|
||||
|
||||
2025-08-06 Matthieu Longo <matthieu.longo@arm.com>
|
||||
|
||||
* testsuite/test-doubly-linked-list.c: disable debug logging on
|
||||
|
|
|
|||
|
|
@ -510,7 +510,6 @@ aclocal_deps = \
|
|||
$(srcdir)/../config/cet.m4 \
|
||||
$(srcdir)/../config/enable.m4 \
|
||||
$(srcdir)/../config/gcc-plugin.m4 \
|
||||
$(srcdir)/../config/mmap.m4 \
|
||||
$(srcdir)/../config/no-executables.m4 \
|
||||
$(srcdir)/../config/override.m4 \
|
||||
$(srcdir)/../config/picflag.m4 \
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ dnl On some versions of SunOS4 at least, strncmp reads a word at a time
|
|||
dnl but erroneously reads past the end of strings. This can cause
|
||||
dnl a SEGV in some cases.
|
||||
AC_DEFUN([libiberty_AC_FUNC_STRNCMP],
|
||||
[AC_REQUIRE([GCC_AC_FUNC_MMAP])
|
||||
[AC_REQUIRE([AC_FUNC_MMAP])
|
||||
AC_CACHE_CHECK([for working strncmp], ac_cv_func_strncmp_works,
|
||||
[AC_TRY_RUN([
|
||||
/* Test by Jim Wilson and Kaveh Ghazi.
|
||||
|
|
|
|||
1
libiberty/aclocal.m4
vendored
1
libiberty/aclocal.m4
vendored
|
|
@ -18,7 +18,6 @@ m4_include([../config/clang-plugin.m4])
|
|||
m4_include([../config/enable.m4])
|
||||
m4_include([../config/gcc-plugin.m4])
|
||||
m4_include([../config/hwcaps.m4])
|
||||
m4_include([../config/mmap.m4])
|
||||
m4_include([../config/no-executables.m4])
|
||||
m4_include([../config/override.m4])
|
||||
m4_include([../config/picflag.m4])
|
||||
|
|
|
|||
5
libiberty/configure
vendored
5
libiberty/configure
vendored
|
|
@ -7997,9 +7997,6 @@ if test x$gcc_no_link = xyes; then
|
|||
fi
|
||||
fi
|
||||
if test "x${ac_cv_func_mmap_fixed_mapped}" != xno; then
|
||||
save_ASAN_OPTIONS="$ASAN_OPTIONS"
|
||||
ASAN_OPTIONS=detect_leaks=0
|
||||
export ASAN_OPTIONS
|
||||
|
||||
for ac_func in getpagesize
|
||||
do :
|
||||
|
|
@ -8178,8 +8175,6 @@ $as_echo "#define HAVE_MMAP 1" >>confdefs.h
|
|||
fi
|
||||
rm -f conftest.mmap conftest.txt
|
||||
|
||||
ASAN_OPTIONS="$save_ASAN_OPTIONS"
|
||||
|
||||
fi
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for working strncmp" >&5
|
||||
|
|
|
|||
|
|
@ -499,6 +499,8 @@ static struct demangle_component *d_lambda (struct d_info *);
|
|||
|
||||
static struct demangle_component *d_unnamed_type (struct d_info *);
|
||||
|
||||
static struct demangle_component *d_unnamed_enum (struct d_info *);
|
||||
|
||||
static struct demangle_component *
|
||||
d_clone_suffix (struct d_info *, struct demangle_component *);
|
||||
|
||||
|
|
@ -1424,7 +1426,7 @@ d_encoding (struct d_info *di, int top_level)
|
|||
|
||||
/* If this is a non-top-level local-name, clear the
|
||||
return type, so it doesn't confuse the user by
|
||||
being confused with the return type of whaever
|
||||
being confused with the return type of whatever
|
||||
this is nested within. */
|
||||
if (!top_level && dc->type == DEMANGLE_COMPONENT_LOCAL_NAME
|
||||
&& ftype->type == DEMANGLE_COMPONENT_FUNCTION_TYPE)
|
||||
|
|
@ -1799,6 +1801,9 @@ d_unqualified_name (struct d_info *di, struct demangle_component *scope,
|
|||
{
|
||||
switch (d_peek_next_char (di))
|
||||
{
|
||||
case 'e':
|
||||
ret = d_unnamed_enum (di);
|
||||
break;
|
||||
case 'l':
|
||||
ret = d_lambda (di);
|
||||
break;
|
||||
|
|
@ -2728,13 +2733,20 @@ cplus_demangle_type (struct d_info *di)
|
|||
break;
|
||||
|
||||
case 'U':
|
||||
d_advance (di, 1);
|
||||
ret = d_source_name (di);
|
||||
if (d_peek_char (di) == 'I')
|
||||
ret = d_make_comp (di, DEMANGLE_COMPONENT_TEMPLATE, ret,
|
||||
d_template_args (di));
|
||||
ret = d_make_comp (di, DEMANGLE_COMPONENT_VENDOR_TYPE_QUAL,
|
||||
cplus_demangle_type (di), ret);
|
||||
peek = d_peek_next_char (di);
|
||||
if (IS_DIGIT (peek))
|
||||
{
|
||||
d_advance (di, 1);
|
||||
ret = d_source_name (di);
|
||||
if (d_peek_char (di) == 'I')
|
||||
ret = d_make_comp (di, DEMANGLE_COMPONENT_TEMPLATE, ret,
|
||||
d_template_args (di));
|
||||
ret = d_make_comp (di, DEMANGLE_COMPONENT_VENDOR_TYPE_QUAL,
|
||||
cplus_demangle_type (di), ret);
|
||||
}
|
||||
else
|
||||
/* Could be a closure type or an unnamed enum. */
|
||||
ret = d_unqualified_name (di, NULL, NULL);
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
|
|
@ -4090,6 +4102,33 @@ d_unnamed_type (struct d_info *di)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* <unnamed-enum-name> ::= Ue <underlying type> <enumerator source-name> */
|
||||
|
||||
static struct demangle_component *
|
||||
d_unnamed_enum (struct d_info *di)
|
||||
{
|
||||
if (! d_check_char (di, 'U'))
|
||||
return NULL;
|
||||
if (! d_check_char (di, 'e'))
|
||||
return NULL;
|
||||
|
||||
struct demangle_component *underlying = cplus_demangle_type (di);
|
||||
struct demangle_component *name = d_source_name (di);
|
||||
|
||||
struct demangle_component *ret = d_make_empty (di);
|
||||
if (ret)
|
||||
{
|
||||
ret->type = DEMANGLE_COMPONENT_UNNAMED_ENUM;
|
||||
d_left (ret) = underlying;
|
||||
d_right (ret) = name;
|
||||
}
|
||||
|
||||
if (! d_add_substitution (di, ret))
|
||||
return NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* <clone-suffix> ::= [ . <clone-type-identifier> ] [ . <nonnegative number> ]*
|
||||
*/
|
||||
|
||||
|
|
@ -4396,6 +4435,7 @@ d_count_templates_scopes (struct d_print_info *dpi,
|
|||
case DEMANGLE_COMPONENT_CHARACTER:
|
||||
case DEMANGLE_COMPONENT_NUMBER:
|
||||
case DEMANGLE_COMPONENT_UNNAMED_TYPE:
|
||||
case DEMANGLE_COMPONENT_UNNAMED_ENUM:
|
||||
case DEMANGLE_COMPONENT_STRUCTURED_BINDING:
|
||||
case DEMANGLE_COMPONENT_MODULE_NAME:
|
||||
case DEMANGLE_COMPONENT_MODULE_PARTITION:
|
||||
|
|
@ -4780,6 +4820,7 @@ d_find_pack (struct d_print_info *dpi,
|
|||
case DEMANGLE_COMPONENT_CHARACTER:
|
||||
case DEMANGLE_COMPONENT_FUNCTION_PARAM:
|
||||
case DEMANGLE_COMPONENT_UNNAMED_TYPE:
|
||||
case DEMANGLE_COMPONENT_UNNAMED_ENUM:
|
||||
case DEMANGLE_COMPONENT_DEFAULT_ARG:
|
||||
case DEMANGLE_COMPONENT_NUMBER:
|
||||
return NULL;
|
||||
|
|
@ -6258,6 +6299,14 @@ d_print_comp_inner (struct d_print_info *dpi, int options,
|
|||
d_append_char (dpi, '}');
|
||||
return;
|
||||
|
||||
case DEMANGLE_COMPONENT_UNNAMED_ENUM:
|
||||
d_append_string (dpi, "{enum:");
|
||||
d_print_comp (dpi, options, d_left (dc));
|
||||
d_append_string (dpi, "{");
|
||||
d_print_comp (dpi, options, d_right (dc));
|
||||
d_append_string (dpi, "}}");
|
||||
return;
|
||||
|
||||
case DEMANGLE_COMPONENT_CLONE:
|
||||
d_print_comp (dpi, options, d_left (dc));
|
||||
d_append_string (dpi, " [clone ");
|
||||
|
|
|
|||
|
|
@ -57,6 +57,32 @@ struct external_filehdr
|
|||
unsigned char f_flags[2]; /* flags */
|
||||
};
|
||||
|
||||
/* BigObj COFF file header. */
|
||||
|
||||
struct external_filehdr_bigobj
|
||||
{
|
||||
unsigned char sig1[2]; /* Must be 0x0000 */
|
||||
unsigned char sig2[2]; /* Must be 0xFFFF */
|
||||
unsigned char version[2]; /* Version, currently 2 */
|
||||
unsigned char machine[2]; /* Machine type */
|
||||
unsigned char timdat[4]; /* time & date stamp */
|
||||
unsigned char classid[16]; /* Magic GUID that identifies BigObj format */
|
||||
unsigned char sizeofdata[4]; /* Size of data (unused, set to 0) */
|
||||
unsigned char flags[4]; /* Flags (unused, set to 0) */
|
||||
unsigned char metadatasize[4]; /* Metadata size (unused, set to 0) */
|
||||
unsigned char metadataoffset[4]; /* Metadata offset (unused, set to 0) */
|
||||
unsigned char nscns[4]; /* number of sections (32-bit!) */
|
||||
unsigned char symptr[4]; /* file pointer to symtab */
|
||||
unsigned char nsyms[4]; /* number of symtab entries */
|
||||
};
|
||||
|
||||
/* The BigObj magic GUID (ClassID). */
|
||||
static const unsigned char bigobj_magic[16] =
|
||||
{
|
||||
0xC7, 0xA1, 0xBA, 0xD1, 0xEE, 0xBA, 0xA9, 0x4B,
|
||||
0xAF, 0x20, 0xFA, 0xF6, 0x6A, 0xA4, 0xDC, 0xB8
|
||||
};
|
||||
|
||||
/* Bits for filehdr f_flags field. */
|
||||
|
||||
#define F_EXEC (0x0002)
|
||||
|
|
@ -119,6 +145,28 @@ struct external_syment
|
|||
unsigned char e_numaux[1];
|
||||
};
|
||||
|
||||
/* BigObj COFF symbol table entry (20 bytes instead of 18). */
|
||||
|
||||
struct external_syment_bigobj
|
||||
{
|
||||
union
|
||||
{
|
||||
unsigned char e_name[E_SYMNMLEN];
|
||||
|
||||
struct
|
||||
{
|
||||
unsigned char e_zeroes[4];
|
||||
unsigned char e_offset[4];
|
||||
} e;
|
||||
} e;
|
||||
|
||||
unsigned char e_value[4];
|
||||
unsigned char e_scnum[4]; /* 32-bit section number! */
|
||||
unsigned char e_type[2];
|
||||
unsigned char e_sclass[1];
|
||||
unsigned char e_numaux[1];
|
||||
};
|
||||
|
||||
/* Length allowed for filename in aux sym format 4. */
|
||||
|
||||
#define E_FILNMLEN 18
|
||||
|
|
@ -149,6 +197,33 @@ union external_auxent
|
|||
} x_scn;
|
||||
};
|
||||
|
||||
/* BigObj auxiliary symbol (20 bytes to match symbol size). */
|
||||
|
||||
union external_auxent_bigobj
|
||||
{
|
||||
/* Aux sym format 4: file. */
|
||||
union
|
||||
{
|
||||
char x_fname[E_FILNMLEN];
|
||||
struct
|
||||
{
|
||||
unsigned char x_zeroes[4];
|
||||
unsigned char x_offset[4];
|
||||
} x_n;
|
||||
} x_file;
|
||||
/* Aux sym format 5: section. */
|
||||
struct
|
||||
{
|
||||
unsigned char x_scnlen[4]; /* section length */
|
||||
unsigned char x_nreloc[2]; /* # relocation entries */
|
||||
unsigned char x_nlinno[2]; /* # line numbers */
|
||||
unsigned char x_checksum[4]; /* section COMDAT checksum */
|
||||
unsigned char x_associated[2]; /* COMDAT assoc section index */
|
||||
unsigned char x_comdat[1]; /* COMDAT selection number */
|
||||
unsigned char x_pad[3]; /* Padding to 20 bytes */
|
||||
} x_scn;
|
||||
};
|
||||
|
||||
/* Symbol-related constants. */
|
||||
|
||||
#define IMAGE_SYM_DEBUG (-2)
|
||||
|
|
@ -168,8 +243,10 @@ struct simple_object_coff_read
|
|||
unsigned short magic;
|
||||
/* Whether the file is big-endian. */
|
||||
unsigned char is_big_endian;
|
||||
/* Whether this is BigObj format. */
|
||||
unsigned char is_bigobj;
|
||||
/* Number of sections. */
|
||||
unsigned short nscns;
|
||||
unsigned int nscns;
|
||||
/* File offset of symbol table. */
|
||||
off_t symptr;
|
||||
/* Number of symbol table entries. */
|
||||
|
|
@ -188,6 +265,8 @@ struct simple_object_coff_attributes
|
|||
unsigned short magic;
|
||||
/* Whether the file is big-endian. */
|
||||
unsigned char is_big_endian;
|
||||
/* Whether this is BigObj format. */
|
||||
unsigned char is_bigobj;
|
||||
/* Flags. */
|
||||
unsigned short flags;
|
||||
};
|
||||
|
|
@ -240,10 +319,12 @@ simple_object_coff_match (unsigned char header[SIMPLE_OBJECT_MATCH_HEADER_LEN],
|
|||
int is_big_endian;
|
||||
unsigned short (*fetch_16) (const unsigned char *);
|
||||
unsigned int (*fetch_32) (const unsigned char *);
|
||||
unsigned char hdrbuf[sizeof (struct external_filehdr)];
|
||||
unsigned char hdrbuf[sizeof (struct external_filehdr_bigobj)];
|
||||
unsigned short flags;
|
||||
struct simple_object_coff_read *ocr;
|
||||
unsigned short sig1, sig2;
|
||||
|
||||
/* Try regular COFF first. */
|
||||
c = sizeof (coff_magic) / sizeof (coff_magic[0]);
|
||||
magic_big = simple_object_fetch_big_16 (header);
|
||||
magic_little = simple_object_fetch_little_16 (header);
|
||||
|
|
@ -254,12 +335,64 @@ simple_object_coff_match (unsigned char header[SIMPLE_OBJECT_MATCH_HEADER_LEN],
|
|||
: coff_magic[i].magic == magic_little)
|
||||
break;
|
||||
}
|
||||
if (i >= c)
|
||||
|
||||
/* Check for BigObj if regular COFF didn't match. */
|
||||
sig1 = simple_object_fetch_little_16 (header);
|
||||
sig2 = simple_object_fetch_little_16 (header + 2);
|
||||
|
||||
if (i >= c && (sig1 != 0 || sig2 != 0xFFFF))
|
||||
{
|
||||
/* Not regular COFF and not BigObj. */
|
||||
*errmsg = NULL;
|
||||
*err = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (sig1 == 0 && sig2 == 0xFFFF)
|
||||
{
|
||||
/* This looks like BigObj. Verify the ClassID. */
|
||||
unsigned char bigobj_hdrbuf[sizeof (struct external_filehdr_bigobj)];
|
||||
|
||||
if (!simple_object_internal_read (descriptor, offset, bigobj_hdrbuf,
|
||||
sizeof bigobj_hdrbuf, errmsg, err))
|
||||
return NULL;
|
||||
|
||||
if (memcmp (bigobj_hdrbuf + offsetof (struct external_filehdr_bigobj,
|
||||
classid),
|
||||
bigobj_magic, 16) != 0)
|
||||
{
|
||||
*errmsg = NULL;
|
||||
*err = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* BigObj is always little-endian. */
|
||||
is_big_endian = 0;
|
||||
|
||||
ocr = XNEW (struct simple_object_coff_read);
|
||||
ocr->magic = simple_object_fetch_little_16
|
||||
(bigobj_hdrbuf
|
||||
+ offsetof (struct external_filehdr_bigobj, machine));
|
||||
ocr->is_big_endian = 0;
|
||||
ocr->is_bigobj = 1;
|
||||
ocr->nscns = simple_object_fetch_little_32
|
||||
(bigobj_hdrbuf
|
||||
+ offsetof (struct external_filehdr_bigobj, nscns));
|
||||
ocr->symptr = simple_object_fetch_little_32
|
||||
(bigobj_hdrbuf
|
||||
+ offsetof (struct external_filehdr_bigobj, symptr));
|
||||
ocr->nsyms = simple_object_fetch_little_32
|
||||
(bigobj_hdrbuf
|
||||
+ offsetof (struct external_filehdr_bigobj, nsyms));
|
||||
ocr->flags = simple_object_fetch_little_32
|
||||
(bigobj_hdrbuf
|
||||
+ offsetof (struct external_filehdr_bigobj, flags));
|
||||
ocr->scnhdr_offset = sizeof (struct external_filehdr_bigobj);
|
||||
|
||||
return (void *) ocr;
|
||||
}
|
||||
|
||||
/* Regular COFF. */
|
||||
is_big_endian = coff_magic[i].is_big_endian;
|
||||
|
||||
magic = is_big_endian ? magic_big : magic_little;
|
||||
|
|
@ -270,7 +403,7 @@ simple_object_coff_match (unsigned char header[SIMPLE_OBJECT_MATCH_HEADER_LEN],
|
|||
? simple_object_fetch_big_32
|
||||
: simple_object_fetch_little_32);
|
||||
|
||||
if (!simple_object_internal_read (descriptor, offset, hdrbuf, sizeof hdrbuf,
|
||||
if (!simple_object_internal_read (descriptor, offset, hdrbuf, sizeof (struct external_filehdr),
|
||||
errmsg, err))
|
||||
return NULL;
|
||||
|
||||
|
|
@ -285,6 +418,7 @@ simple_object_coff_match (unsigned char header[SIMPLE_OBJECT_MATCH_HEADER_LEN],
|
|||
ocr = XNEW (struct simple_object_coff_read);
|
||||
ocr->magic = magic;
|
||||
ocr->is_big_endian = is_big_endian;
|
||||
ocr->is_bigobj = 0;
|
||||
ocr->nscns = fetch_16 (hdrbuf + offsetof (struct external_filehdr, f_nscns));
|
||||
ocr->symptr = fetch_32 (hdrbuf
|
||||
+ offsetof (struct external_filehdr, f_symptr));
|
||||
|
|
@ -309,9 +443,13 @@ simple_object_coff_read_strtab (simple_object_read *sobj, size_t *strtab_size,
|
|||
unsigned char strsizebuf[4];
|
||||
size_t strsize;
|
||||
char *strtab;
|
||||
size_t sym_size;
|
||||
|
||||
strtab_offset = sobj->offset + ocr->symptr
|
||||
+ ocr->nsyms * sizeof (struct external_syment);
|
||||
/* Symbol size depends on format. */
|
||||
sym_size = ocr->is_bigobj ? sizeof (struct external_syment_bigobj)
|
||||
: sizeof (struct external_syment);
|
||||
|
||||
strtab_offset = sobj->offset + ocr->symptr + ocr->nsyms * sym_size;
|
||||
if (!simple_object_internal_read (sobj->descriptor, strtab_offset,
|
||||
strsizebuf, 4, errmsg, err))
|
||||
return NULL;
|
||||
|
|
@ -444,6 +582,7 @@ simple_object_coff_fetch_attributes (simple_object_read *sobj,
|
|||
ret = XNEW (struct simple_object_coff_attributes);
|
||||
ret->magic = ocr->magic;
|
||||
ret->is_big_endian = ocr->is_big_endian;
|
||||
ret->is_bigobj = ocr->is_bigobj;
|
||||
ret->flags = ocr->flags;
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -466,7 +605,9 @@ simple_object_coff_attributes_merge (void *todata, void *fromdata, int *err)
|
|||
struct simple_object_coff_attributes *from =
|
||||
(struct simple_object_coff_attributes *) fromdata;
|
||||
|
||||
if (to->magic != from->magic || to->is_big_endian != from->is_big_endian)
|
||||
if (to->magic != from->magic
|
||||
|| to->is_big_endian != from->is_big_endian
|
||||
|| to->is_bigobj != from->is_bigobj)
|
||||
{
|
||||
*err = 0;
|
||||
return "COFF object format mismatch";
|
||||
|
|
@ -500,6 +641,52 @@ simple_object_coff_start_write (void *attributes_data,
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Write out a BigObj COFF filehdr. */
|
||||
|
||||
static int
|
||||
simple_object_coff_write_filehdr_bigobj (simple_object_write *sobj,
|
||||
int descriptor,
|
||||
unsigned int nscns,
|
||||
size_t symtab_offset,
|
||||
unsigned int nsyms,
|
||||
const char **errmsg, int *err)
|
||||
{
|
||||
struct simple_object_coff_attributes *attrs =
|
||||
(struct simple_object_coff_attributes *) sobj->data;
|
||||
unsigned char hdrbuf[sizeof (struct external_filehdr_bigobj)];
|
||||
unsigned char *hdr;
|
||||
void (*set_16) (unsigned char *, unsigned short);
|
||||
void (*set_32) (unsigned char *, unsigned int);
|
||||
|
||||
hdr = &hdrbuf[0];
|
||||
|
||||
/* BigObj is always little-endian. */
|
||||
set_16 = simple_object_set_little_16;
|
||||
set_32 = simple_object_set_little_32;
|
||||
|
||||
memset (hdr, 0, sizeof (struct external_filehdr_bigobj));
|
||||
|
||||
/* Set BigObj signatures. */
|
||||
set_16 (hdr + offsetof (struct external_filehdr_bigobj, sig1), 0);
|
||||
set_16 (hdr + offsetof (struct external_filehdr_bigobj, sig2), 0xFFFF);
|
||||
set_16 (hdr + offsetof (struct external_filehdr_bigobj, version), 2);
|
||||
set_16 (hdr + offsetof (struct external_filehdr_bigobj, machine),
|
||||
attrs->magic);
|
||||
/* timdat left as zero. */
|
||||
/* Copy ClassID. */
|
||||
memcpy (hdr + offsetof (struct external_filehdr_bigobj, classid),
|
||||
bigobj_magic, 16);
|
||||
/* sizeofdata, flags, metadatasize, metadataoffset left as zero. */
|
||||
set_32 (hdr + offsetof (struct external_filehdr_bigobj, nscns), nscns);
|
||||
set_32 (hdr + offsetof (struct external_filehdr_bigobj, symptr),
|
||||
symtab_offset);
|
||||
set_32 (hdr + offsetof (struct external_filehdr_bigobj, nsyms), nsyms);
|
||||
|
||||
return simple_object_internal_write (descriptor, 0, hdrbuf,
|
||||
sizeof (struct external_filehdr_bigobj),
|
||||
errmsg, err);
|
||||
}
|
||||
|
||||
/* Write out a COFF filehdr. */
|
||||
|
||||
static int
|
||||
|
|
@ -618,14 +805,16 @@ simple_object_coff_write_to_file (simple_object_write *sobj, int descriptor,
|
|||
what 'gas' uses when told to assemble from stdin. */
|
||||
const char *source_filename = "fake";
|
||||
size_t sflen;
|
||||
union
|
||||
{
|
||||
struct external_syment sym;
|
||||
union external_auxent aux;
|
||||
} syms[2];
|
||||
size_t symsize;
|
||||
void (*set_16) (unsigned char *, unsigned short);
|
||||
void (*set_32) (unsigned char *, unsigned int);
|
||||
|
||||
/* Determine symbol size based on format. */
|
||||
if (attrs->is_bigobj)
|
||||
symsize = sizeof (struct external_syment_bigobj);
|
||||
else
|
||||
symsize = sizeof (struct external_syment);
|
||||
|
||||
set_16 = (attrs->is_big_endian
|
||||
? simple_object_set_big_16
|
||||
: simple_object_set_little_16);
|
||||
|
|
@ -637,7 +826,10 @@ simple_object_coff_write_to_file (simple_object_write *sobj, int descriptor,
|
|||
for (section = sobj->sections; section != NULL; section = section->next)
|
||||
++nscns;
|
||||
|
||||
scnhdr_offset = sizeof (struct external_filehdr);
|
||||
if (attrs->is_bigobj)
|
||||
scnhdr_offset = sizeof (struct external_filehdr_bigobj);
|
||||
else
|
||||
scnhdr_offset = sizeof (struct external_filehdr);
|
||||
offset = scnhdr_offset + nscns * sizeof (struct external_scnhdr);
|
||||
name_offset = 4;
|
||||
for (section = sobj->sections; section != NULL; section = section->next)
|
||||
|
|
@ -693,91 +885,198 @@ simple_object_coff_write_to_file (simple_object_write *sobj, int descriptor,
|
|||
symtab_offset = offset;
|
||||
/* Advance across space reserved for symbol table to locate
|
||||
start of string table. */
|
||||
offset += nsyms * sizeof (struct external_syment);
|
||||
offset += nsyms * symsize;
|
||||
|
||||
/* Write out file symbol. */
|
||||
memset (&syms[0], 0, sizeof (syms));
|
||||
strcpy ((char *)&syms[0].sym.e.e_name[0], ".file");
|
||||
set_16 (&syms[0].sym.e_scnum[0], IMAGE_SYM_DEBUG);
|
||||
set_16 (&syms[0].sym.e_type[0], IMAGE_SYM_TYPE);
|
||||
syms[0].sym.e_sclass[0] = IMAGE_SYM_CLASS_FILE;
|
||||
syms[0].sym.e_numaux[0] = 1;
|
||||
/* The name need not be nul-terminated if it fits into the x_fname field
|
||||
directly, but must be if it has to be placed into the string table. */
|
||||
sflen = strlen (source_filename);
|
||||
if (sflen <= E_FILNMLEN)
|
||||
memcpy (&syms[1].aux.x_file.x_fname[0], source_filename, sflen);
|
||||
else
|
||||
if (attrs->is_bigobj)
|
||||
{
|
||||
set_32 (&syms[1].aux.x_file.x_n.x_offset[0], name_offset);
|
||||
if (!simple_object_internal_write (descriptor, offset + name_offset,
|
||||
((const unsigned char *)
|
||||
source_filename),
|
||||
sflen + 1, &errmsg, err))
|
||||
return errmsg;
|
||||
name_offset += strlen (source_filename) + 1;
|
||||
}
|
||||
if (!simple_object_internal_write (descriptor, symtab_offset,
|
||||
(const unsigned char *) &syms[0],
|
||||
sizeof (syms), &errmsg, err))
|
||||
return errmsg;
|
||||
union
|
||||
{
|
||||
struct external_syment_bigobj sym;
|
||||
union external_auxent_bigobj aux;
|
||||
} syms[2];
|
||||
|
||||
/* Write the string table length, followed by the strings and section
|
||||
symbols in step with each other. */
|
||||
set_32 (strsizebuf, name_offset);
|
||||
if (!simple_object_internal_write (descriptor, offset, strsizebuf, 4,
|
||||
&errmsg, err))
|
||||
return errmsg;
|
||||
|
||||
name_offset = 4;
|
||||
secsym_offset = symtab_offset + sizeof (syms);
|
||||
memset (&syms[0], 0, sizeof (syms));
|
||||
set_16 (&syms[0].sym.e_type[0], IMAGE_SYM_TYPE);
|
||||
syms[0].sym.e_sclass[0] = IMAGE_SYM_CLASS_STATIC;
|
||||
syms[0].sym.e_numaux[0] = 1;
|
||||
secnum = 1;
|
||||
|
||||
for (section = sobj->sections; section != NULL; section = section->next)
|
||||
{
|
||||
size_t namelen;
|
||||
size_t scnsize;
|
||||
struct simple_object_write_section_buffer *buffer;
|
||||
|
||||
namelen = strlen (section->name);
|
||||
set_16 (&syms[0].sym.e_scnum[0], secnum++);
|
||||
scnsize = 0;
|
||||
for (buffer = section->buffers; buffer != NULL; buffer = buffer->next)
|
||||
scnsize += buffer->size;
|
||||
set_32 (&syms[1].aux.x_scn.x_scnlen[0], scnsize);
|
||||
if (namelen > SCNNMLEN)
|
||||
{
|
||||
set_32 (&syms[0].sym.e.e.e_zeroes[0], 0);
|
||||
set_32 (&syms[0].sym.e.e.e_offset[0], name_offset);
|
||||
if (!simple_object_internal_write (descriptor, offset + name_offset,
|
||||
((const unsigned char *)
|
||||
section->name),
|
||||
namelen + 1, &errmsg, err))
|
||||
return errmsg;
|
||||
name_offset += namelen + 1;
|
||||
}
|
||||
memset (&syms[0], 0, sizeof (syms));
|
||||
strcpy ((char *)&syms[0].sym.e.e_name[0], ".file");
|
||||
set_32 (&syms[0].sym.e_scnum[0], IMAGE_SYM_DEBUG);
|
||||
set_16 (&syms[0].sym.e_type[0], IMAGE_SYM_TYPE);
|
||||
syms[0].sym.e_sclass[0] = IMAGE_SYM_CLASS_FILE;
|
||||
syms[0].sym.e_numaux[0] = 1;
|
||||
/* The name need not be nul-terminated if it fits into the x_fname field
|
||||
directly, but must be if it has to be placed into the string table. */
|
||||
sflen = strlen (source_filename);
|
||||
if (sflen <= E_FILNMLEN)
|
||||
memcpy (&syms[1].aux.x_file.x_fname[0], source_filename, sflen);
|
||||
else
|
||||
{
|
||||
memcpy (&syms[0].sym.e.e_name[0], section->name,
|
||||
strlen (section->name));
|
||||
memset (&syms[0].sym.e.e_name[strlen (section->name)], 0,
|
||||
E_SYMNMLEN - strlen (section->name));
|
||||
set_32 (&syms[1].aux.x_file.x_n.x_offset[0], name_offset);
|
||||
if (!simple_object_internal_write (descriptor, offset + name_offset,
|
||||
((const unsigned char *)
|
||||
source_filename),
|
||||
sflen + 1, &errmsg, err))
|
||||
return errmsg;
|
||||
name_offset += strlen (source_filename) + 1;
|
||||
}
|
||||
|
||||
if (!simple_object_internal_write (descriptor, secsym_offset,
|
||||
if (!simple_object_internal_write (descriptor, symtab_offset,
|
||||
(const unsigned char *) &syms[0],
|
||||
sizeof (syms), &errmsg, err))
|
||||
return errmsg;
|
||||
secsym_offset += sizeof (syms);
|
||||
|
||||
/* Write the string table length, followed by the strings and section
|
||||
symbols in step with each other. */
|
||||
set_32 (strsizebuf, name_offset);
|
||||
if (!simple_object_internal_write (descriptor, offset, strsizebuf, 4,
|
||||
&errmsg, err))
|
||||
return errmsg;
|
||||
|
||||
name_offset = 4;
|
||||
secsym_offset = symtab_offset + sizeof (syms);
|
||||
memset (&syms[0], 0, sizeof (syms));
|
||||
set_16 (&syms[0].sym.e_type[0], IMAGE_SYM_TYPE);
|
||||
syms[0].sym.e_sclass[0] = IMAGE_SYM_CLASS_STATIC;
|
||||
syms[0].sym.e_numaux[0] = 1;
|
||||
secnum = 1;
|
||||
|
||||
for (section = sobj->sections; section != NULL; section = section->next)
|
||||
{
|
||||
size_t namelen;
|
||||
size_t scnsize;
|
||||
struct simple_object_write_section_buffer *buffer;
|
||||
|
||||
namelen = strlen (section->name);
|
||||
set_32 (&syms[0].sym.e_scnum[0], secnum++);
|
||||
scnsize = 0;
|
||||
for (buffer = section->buffers; buffer != NULL; buffer = buffer->next)
|
||||
scnsize += buffer->size;
|
||||
set_32 (&syms[1].aux.x_scn.x_scnlen[0], scnsize);
|
||||
if (namelen > SCNNMLEN)
|
||||
{
|
||||
set_32 (&syms[0].sym.e.e.e_zeroes[0], 0);
|
||||
set_32 (&syms[0].sym.e.e.e_offset[0], name_offset);
|
||||
if (!simple_object_internal_write (descriptor, offset + name_offset,
|
||||
((const unsigned char *)
|
||||
section->name),
|
||||
namelen + 1, &errmsg, err))
|
||||
return errmsg;
|
||||
name_offset += namelen + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy (&syms[0].sym.e.e_name[0], section->name,
|
||||
strlen (section->name));
|
||||
memset (&syms[0].sym.e.e_name[strlen (section->name)], 0,
|
||||
E_SYMNMLEN - strlen (section->name));
|
||||
}
|
||||
|
||||
if (!simple_object_internal_write (descriptor, secsym_offset,
|
||||
(const unsigned char *) &syms[0],
|
||||
sizeof (syms), &errmsg, err))
|
||||
return errmsg;
|
||||
secsym_offset += sizeof (syms);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Regular COFF. */
|
||||
union
|
||||
{
|
||||
struct external_syment sym;
|
||||
union external_auxent aux;
|
||||
} syms[2];
|
||||
|
||||
memset (&syms[0], 0, sizeof (syms));
|
||||
strcpy ((char *)&syms[0].sym.e.e_name[0], ".file");
|
||||
set_16 (&syms[0].sym.e_scnum[0], IMAGE_SYM_DEBUG);
|
||||
set_16 (&syms[0].sym.e_type[0], IMAGE_SYM_TYPE);
|
||||
syms[0].sym.e_sclass[0] = IMAGE_SYM_CLASS_FILE;
|
||||
syms[0].sym.e_numaux[0] = 1;
|
||||
/* The name need not be nul-terminated if it fits into the x_fname field
|
||||
directly, but must be if it has to be placed into the string table. */
|
||||
sflen = strlen (source_filename);
|
||||
if (sflen <= E_FILNMLEN)
|
||||
memcpy (&syms[1].aux.x_file.x_fname[0], source_filename, sflen);
|
||||
else
|
||||
{
|
||||
set_32 (&syms[1].aux.x_file.x_n.x_offset[0], name_offset);
|
||||
if (!simple_object_internal_write (descriptor, offset + name_offset,
|
||||
((const unsigned char *)
|
||||
source_filename),
|
||||
sflen + 1, &errmsg, err))
|
||||
return errmsg;
|
||||
name_offset += strlen (source_filename) + 1;
|
||||
}
|
||||
if (!simple_object_internal_write (descriptor, symtab_offset,
|
||||
(const unsigned char *) &syms[0],
|
||||
sizeof (syms), &errmsg, err))
|
||||
return errmsg;
|
||||
|
||||
/* Write the string table length, followed by the strings and section
|
||||
symbols in step with each other. */
|
||||
set_32 (strsizebuf, name_offset);
|
||||
if (!simple_object_internal_write (descriptor, offset, strsizebuf, 4,
|
||||
&errmsg, err))
|
||||
return errmsg;
|
||||
|
||||
name_offset = 4;
|
||||
secsym_offset = symtab_offset + sizeof (syms);
|
||||
memset (&syms[0], 0, sizeof (syms));
|
||||
set_16 (&syms[0].sym.e_type[0], IMAGE_SYM_TYPE);
|
||||
syms[0].sym.e_sclass[0] = IMAGE_SYM_CLASS_STATIC;
|
||||
syms[0].sym.e_numaux[0] = 1;
|
||||
secnum = 1;
|
||||
|
||||
for (section = sobj->sections; section != NULL; section = section->next)
|
||||
{
|
||||
size_t namelen;
|
||||
size_t scnsize;
|
||||
struct simple_object_write_section_buffer *buffer;
|
||||
|
||||
namelen = strlen (section->name);
|
||||
set_16 (&syms[0].sym.e_scnum[0], secnum++);
|
||||
scnsize = 0;
|
||||
for (buffer = section->buffers; buffer != NULL; buffer = buffer->next)
|
||||
scnsize += buffer->size;
|
||||
set_32 (&syms[1].aux.x_scn.x_scnlen[0], scnsize);
|
||||
if (namelen > SCNNMLEN)
|
||||
{
|
||||
set_32 (&syms[0].sym.e.e.e_zeroes[0], 0);
|
||||
set_32 (&syms[0].sym.e.e.e_offset[0], name_offset);
|
||||
if (!simple_object_internal_write (descriptor, offset + name_offset,
|
||||
((const unsigned char *)
|
||||
section->name),
|
||||
namelen + 1, &errmsg, err))
|
||||
return errmsg;
|
||||
name_offset += namelen + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy (&syms[0].sym.e.e_name[0], section->name,
|
||||
strlen (section->name));
|
||||
memset (&syms[0].sym.e.e_name[strlen (section->name)], 0,
|
||||
E_SYMNMLEN - strlen (section->name));
|
||||
}
|
||||
|
||||
if (!simple_object_internal_write (descriptor, secsym_offset,
|
||||
(const unsigned char *) &syms[0],
|
||||
sizeof (syms), &errmsg, err))
|
||||
return errmsg;
|
||||
secsym_offset += sizeof (syms);
|
||||
}
|
||||
}
|
||||
|
||||
if (!simple_object_coff_write_filehdr (sobj, descriptor, nscns,
|
||||
symtab_offset, nsyms, &errmsg, err))
|
||||
return errmsg;
|
||||
if (attrs->is_bigobj)
|
||||
{
|
||||
if (!simple_object_coff_write_filehdr_bigobj (sobj, descriptor, nscns,
|
||||
symtab_offset, nsyms,
|
||||
&errmsg, err))
|
||||
return errmsg;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!simple_object_coff_write_filehdr (sobj, descriptor, nscns,
|
||||
symtab_offset, nsyms, &errmsg, err))
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -617,7 +617,6 @@ simple_object_mach_o_segment (simple_object_read *sobj, off_t offset,
|
|||
char *name;
|
||||
off_t secoffset;
|
||||
size_t secsize;
|
||||
int l;
|
||||
|
||||
sechdr = secdata + i * sechdrsize;
|
||||
|
||||
|
|
@ -669,12 +668,15 @@ simple_object_mach_o_segment (simple_object_read *sobj, off_t offset,
|
|||
}
|
||||
}
|
||||
|
||||
memset (namebuf, 0, sizeof (namebuf));
|
||||
/* Copy the section name so we can append a null to make it into a
|
||||
c-string (Mach-o section names are not terminated). */
|
||||
memcpy (namebuf, sechdr + sectname_offset, MACH_O_NAME_LEN);
|
||||
namebuf[MACH_O_NAME_LEN] = '\0';
|
||||
name = &namebuf[0];
|
||||
/* Maybe override this if we have long section name extension. */
|
||||
if ((gnu_sections_found & SOMO_LONGN_PRESENT) != 0)
|
||||
{
|
||||
memcpy (namebuf, sechdr + sectname_offset, MACH_O_NAME_LEN);
|
||||
namebuf[MACH_O_NAME_LEN] = '\0';
|
||||
|
||||
name = &namebuf[0];
|
||||
if (strtab != NULL && name[0] == '_' && name[1] == '_')
|
||||
{
|
||||
unsigned long stringoffset;
|
||||
|
|
@ -696,19 +698,6 @@ simple_object_mach_o_segment (simple_object_read *sobj, off_t offset,
|
|||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Otherwise, make a name like __segment,__section as per the
|
||||
convention in mach-o asm. */
|
||||
name = &namebuf[0];
|
||||
memcpy (namebuf, (char *) sechdr + segname_offset, MACH_O_NAME_LEN);
|
||||
namebuf[MACH_O_NAME_LEN] = '\0';
|
||||
l = strlen (namebuf);
|
||||
namebuf[l] = ',';
|
||||
memcpy (namebuf + l + 1, (char *) sechdr + sectname_offset,
|
||||
MACH_O_NAME_LEN);
|
||||
namebuf[l + 1 + MACH_O_NAME_LEN] = '\0';
|
||||
}
|
||||
|
||||
simple_object_mach_o_section_info (omr->is_big_endian, is_32, sechdr,
|
||||
&secoffset, &secsize);
|
||||
|
|
|
|||
|
|
@ -1709,3 +1709,10 @@ void S::bar<5, int>(this S, int)
|
|||
|
||||
_ZNH1S3bazERKS_
|
||||
S::baz(this S const&)
|
||||
|
||||
_Z3fooUlvE_
|
||||
foo({lambda()#1})
|
||||
|
||||
# P2115R0 unnamed enums
|
||||
_Z3fooUei1ES_
|
||||
foo({enum:int{E}}, {enum:int{E}})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue