rust-lang / rust-lang/rustc_codegen_gcc

Dynamic binding mix-up between libgccjit and glibc

Open
#145 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
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 valgrind and ../configure --target rx-elf --enable-languages=c,jit --enable-host-shared --disable-werror --disable-multilib --disable-libssp --enable-valgrind-annotations
  • rustc_codegen_gcc: a22e15b954d2be638ab0b15cee6e93cf301dc159 + minor fixes for rx-elf target

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.