rust-lang / rust-lang/rustc_codegen_gcc
Dynamic binding mix-up between libgccjit and glibc
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 1.2k
- Forks
- 105
- Avg merge
- 8h 20m
- Merged PRs (30d)
- 14
Description
It appears that, due to how dynamic linking works, libgccjit might use obstack functions from glibc, which have a different ABI from those of its own. This causes a load from uninitialized memory and possibly leads to inexplicable assertion failures in rtl_ssa::function_info::~function_info.
A work-around is to load libgccjit.so early by adding it to LD_PRELOAD.
Symptoms
Uninitialized value use
During ./build.sh --release-sysroot, compiling core fails with ICE in rtl_ssa::function_info::~function_info. When I invoke rustc under valgrind, it reports an uninitialized load in bitmap_alloc:
$ valgrind --log-file=/tmp/valgrind.log --track-origins=yes rustc --crate-name core \
--edition=2021 sysroot_src/library/core/src/lib.rs --crate-type lib --emit=dep-info,\
metadata,link -C opt-level=1 -C embed-bitcode=no -C metadata=b70ce4ea62275032 \
-C extra-filename=-b70ce4ea62275032 --out-dir $rustc_codegen_gcc/build_sysroot/target\
/mips-unknown-linux-gnu/release/deps --target mips-unknown-linux-gnu -L dependency=\
$rustc_codegen_gcc/build_sysroot/target/mips-unknown-linux-gnu/release/deps -L \
dependency=$rustc_codegen_gcc/build_sysroot/target/release/deps -Clinker=rx-elf-gcc \
-Cpanic=abort -Csymbol-mangling-version=v0 -Cdebuginfo=2 -Clto=off -Zpanic-abort-tests \
-Zcodegen-backend=$rustc_codegen_gcc/target/debug/librustc_codegen_gcc.so \
--sysroot $rustc_codegen_gcc/build_sysroot/sysroot -Z force-unstable-if-unmarked \
-Cpanic=abort --verbose
==216420== Thread 5 opt core.c5e1582:
==216420== Conditional jump or move depends on uninitialised value(s)
==216420== at 0x10316D04: bitmap_alloc(bitmap_obstack*) (bitmap.cc:789)
==216420== by 0x1102EED2: gori_map::gori_map() (gimple-range-gori.cc:458)
==216420== by 0x1102EF2E: gori_compute::gori_compute(int) (gimple-range-gori.cc:650)
==216420== by 0x11024AB0: ranger_cache::ranger_cache(int) (gimple-range-cache.cc:871)
==216420== by 0x1101DBCB: gimple_ranger::gimple_ranger() (gimple-range.cc:43)
==216420== by 0x1101F240: enable_ranger(function*) (gimple-range.cc:586)
==216420== by 0x1105E98C: pass_walloca::execute(function*) (gimple-ssa-warn-alloca.cc:258)
==216420== by 0x107601A1: execute_one_pass(opt_pass*) (passes.cc:2637)
==216420== by 0x10760BC2: execute_pass_list_1(opt_pass*) (passes.cc:2737)
==216420== by 0x10760C05: execute_pass_list(function*, opt_pass*) (passes.cc:2748)
==216420== by 0x10399665: cgraph_node::analyze() (cgraphunit.cc:685)
==216420== by 0x1039C5FF: analyze_functions(bool) (cgraphunit.cc:1240)
==216420== Uninitialised value was created by a heap allocation
==216420== at 0x483FF01: operator new(unsigned long) (in /nix/store/02d3virjq9syfz8pfgihw03drdw6l1aw-valgrind-3.18.1/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==216420== by 0x1101F235: enable_ranger(function*) (gimple-range.cc:586)
==216420== by 0x1105E98C: pass_walloca::execute(function*) (gimple-ssa-warn-alloca.cc:258)
==216420== by 0x107601A1: execute_one_pass(opt_pass*) (passes.cc:2637)
==216420== by 0x10760BC2: execute_pass_list_1(opt_pass*) (passes.cc:2737)
==216420== by 0x10760C05: execute_pass_list(function*, opt_pass*) (passes.cc:2748)
==216420== by 0x10399665: cgraph_node::analyze() (cgraphunit.cc:685)
==216420== by 0x1039C5FF: analyze_functions(bool) (cgraphunit.cc:1240)
==216420== by 0x1039D3F8: symbol_table::finalize_compilation_unit() (cgraphunit.cc:2500)
==216420== by 0x10845187: compile_file() (toplev.cc:479)
==216420== by 0x102BA3E6: do_compile (toplev.cc:2158)
==216420== by 0x102BA3E6: toplev::main(int, char**) (toplev.cc:2310)
==216420== by 0x102EBA2E: gcc::jit::playback::context::compile() (jit-playback.cc:2354)
Dynamic binding difference
The above error could not be reproduced with a generated "reproducer". Using LD_DEBUG=bindings, I found that four obstack_* symbols were bound to libc.so.6 in rustc + librustc_codegen_gcc.so, whereas they were bound to libgccjit in the reproducer.
-binding file .../libgccjit.so [0] to .../libgccjit.so [0]: normal symbol `_obstack_newchunk'
-binding file .../libgccjit.so [0] to .../libgccjit.so [0]: normal symbol `_obstack_begin'
-binding file .../libgccjit.so [0] to .../libgccjit.so [0]: normal symbol `_obstack_free'
-binding file .../libgccjit.so [0] to .../libgccjit.so [0]: normal symbol `_obstack_memory_used'
+binding file .../libgccjit.so [0] to /nix/store/4s21k8k7p1mfik0b33r2spq5hq7774k1-glibc-2.33-108/lib/libc.so.6 [0]: normal symbol `_obstack_newchunk'
+binding file .../libgccjit.so [0] to /nix/store/4s21k8k7p1mfik0b33r2spq5hq7774k1-glibc-2.33-108/lib/libc.so.6 [0]: normal symbol `_obstack_begin'
+binding file .../libgccjit.so [0] to /nix/store/4s21k8k7p1mfik0b33r2spq5hq7774k1-glibc-2.33-108/lib/libc.so.6 [0]: normal symbol `_obstack_free'
+binding file .../libgccjit.so [0] to /nix/store/4s21k8k7p1mfik0b33r2spq5hq7774k1-glibc-2.33-108/lib/libc.so.6 [0]: normal symbol `_obstack_memory_used'
When using rustc + librustc_codegen_gcc.so, we can force them to be bound to libgccjit by passing LD_PRELOAD=$libgccjit_path/libgccjit.so.
rustc + librustc_codegen_gcc.so doesn't ICE in rtl_ssa::function_info::~function_info when LD_PRELOAD=$libgccjit_path/libgccjit.so is present. (Instead it ICEs somewhere else, which I think is a separate issue.)
ICE in rtl_ssa::function_info::~function_info
This ICE doesn't occur with LD_PRELOAD=$libgccjit_path/libgccjit.so, which suggests connection to the dynamic binding mix-up.
$ env CG_GCCJIT_DUMP_TO_FILE=1 rustc --crate-name core [...]
libgccjit.so: warning: RX FPU instructions do not support NaNs and infinities
during RTL pass: fwprop2
libgccjit.so: /tmp/gccjit_dumps/core.c5e1582f-cgu.0.c:568:0: error: libgccjit.so: warning: in ~function_info, at rtl-ssa/functions.cc:58
0x7f18e7f5e561 rtl_ssa::function_info::~function_info()
../../gcc/rtl-ssa/functions.cc:58
0x7f18e8cc56ab fwprop_done
../../gcc/fwprop.cc:918
0x7f18e8cc56ab fwprop
../../gcc/fwprop.cc:1005
Please submit a full bug report,
with preprocessed source if appropriate.
Please include the complete backtrace with any bug report.
See <https://gcc.gnu.org/bugs/> for instructions.
obstack_begin@libc.so.6 leaves the upper half of obstack::alignment_mask uninitialized
// gcc/include/obstack.h
#define obstack_specify_allocation(h, size, alignment, chunkfun, freefun) \
_obstack_begin ((h), (size), (alignment), \
_OBSTACK_CAST (void *(*) (size_t), chunkfun), \
_OBSTACK_CAST (void (*) (void *), freefun))
// gcc/gcc/bitmap.cc
void
bitmap_obstack_initialize (bitmap_obstack *bit_obstack)
{
if (!bit_obstack)
{
if (bitmap_default_obstack_depth++)
return;
bit_obstack = &bitmap_default_obstack;
}
#if !defined(__GNUC__) || (__GNUC__ < 2)
#define __alignof__(type) 0
#endif
bit_obstack->elements = NULL;
bit_obstack->heads = NULL;
obstack_specify_allocation (&bit_obstack->obstack, OBSTACK_CHUNK_SIZE,
__alignof__ (bitmap_element),
obstack_chunk_alloc,
obstack_chunk_free);
}
_obstack_begin there seems to initialize different parts of obstack::alignment_mask depending on whether the LD_PRELOAD is present. get_vbits (Valgrind manual) shows the defined-ness of a specified memory range.
------ With LD_PRELOAD=$libgccjit_path/libgccjit.so -------
(gdb) s
range_def_chain::range_def_chain (this=this@entry=0x1f4f1960) at ../../gcc/gimple-range-gori.cc:172
172 range_def_chain::range_def_chain ()
(gdb) n
174 bitmap_obstack_initialize (&m_bitmaps);
(gdb)
175 m_def_chain.create (0);
(gdb) mo get_vbits 0x1f4f1970 0x58
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
chunk_size chunk object_base next_free
00000000 00000000 ffffffff ffffffff 00000000 00000000 00000000 00000000
chunk_limit temp alignment_mask chunkfun
00000000 00000000 ffffffff ffffffff f8ffffff ffffffff
freefun extra_arg (bitfield)
------ Without LD_PRELOAD -------
range_def_chain::range_def_chain (this=this@entry=0x2349af60) at ../../gcc/gimple-range-gori.cc:175
175 m_def_chain.create (0);
(gdb) mo get_vbits 0x2349af08 0x58
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
chunk_size chunk object_base next_free
00000000 00000000 ffffffff ffffffff 00000000 ffffffff 00000000 00000000
chunk_limit temp alignment_mask chunkfun
00000000 00000000 ffffffff ffffffff f8ffffff ffffffff
freefun extra_arg (bitfield)
// n.b. 00 = initialized, ff = uninitialized)
Analysis
obstack.h
Both gcc and glibc provide their own versions of obstack.h with a notable difference in the alignment_mask field, explaining why the glibc version of _obstack_begin didn't fully initialize it:
// gcc/include/obstack.h
#ifndef _OBSTACK_INTERFACE_VERSION
# define _OBSTACK_INTERFACE_VERSION 2
#endif
#if _OBSTACK_INTERFACE_VERSION == 1
/* For binary compatibility with obstack version 1, which used "int"
and "long" for these two types. */
# define _OBSTACK_SIZE_T unsigned int
# define _CHUNK_SIZE_T unsigned long
# define _OBSTACK_CAST(type, expr) ((type) (expr))
#else
/* Version 2 with sane types, especially for 64-bit hosts. */
# define _OBSTACK_SIZE_T size_t
# define _CHUNK_SIZE_T size_t
# define _OBSTACK_CAST(type, expr) (expr)
#endif
struct obstack
{
_CHUNK_SIZE_T chunk_size;
struct _obstack_chunk *chunk;
char *object_base;
char *next_free;
char *chunk_limit;
union
{
_OBSTACK_SIZE_T i;
void *p;
} temp;
_OBSTACK_SIZE_T alignment_mask; // <---------------------------------------
union
{
void *(*plain) (size_t);
void *(*extra) (void *, size_t);
} chunkfun;
union
{
void (*plain) (void *);
void (*extra) (void *, void *);
} freefun;
void *extra_arg;
unsigned use_extra_arg : 1;
unsigned maybe_empty_object : 1;
unsigned alloc_failed : 1;
};
// /nix/store/b3pqkywjwzqpq4vi5yffncdbywwpbrmk-glibc-2.33-117-dev/include/obstack.h
struct obstack
{
long chunk_size;
struct _obstack_chunk *chunk;
char *object_base;
char *next_free;
char *chunk_limit;
union
{
PTR_INT_TYPE tempint;
void *tempptr;
} temp;
int alignment_mask; // <---------------------------------------------------
struct _obstack_chunk *(*chunkfun) (void *, long);
void (*freefun) (void *, struct _obstack_chunk *);
void *extra_arg;
unsigned use_extra_arg : 1;
unsigned maybe_empty_object : 1;
unsigned alloc_failed : 1;
};
(_OBSTACK_INTERFACE_VERSION=1 is not defined anywhere. If it were, the above definitions would match more closely.)
Version
- gcc:
5a50919bab958687c76f61cc1a1f52345ba2c3bc- configured using
env NIX_HARDENING_ENABLE='fortify stackprotector pic strictoverflow relro bindnow' nix-shell -p bison flex gmp mpfr libmpc libelf gcc_latest valgrindand../configure --target rx-elf --enable-languages=c,jit --enable-host-shared --disable-werror --disable-multilib --disable-libssp --enable-valgrind-annotations
- configured using
- rustc_codegen_gcc:
a22e15b954d2be638ab0b15cee6e93cf301dc159+ minor fixes forrx-elftarget
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading gcc/include/obstack.h and the glibc obstack definition described in the report. Reproduce with ./build.sh --release-sysroot, Valgrind, and LD_DEBUG=bindings, comparing behavior with and without LD_PRELOAD of libgccjit.so. The issue does not specify a concrete code change or completion test, so the required fix remains to be determined.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, cpp, rust
- Domain
- backend, compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100