[Linux-ia64] insmod bug causes kernel unwind failures for module text

From: Dave Anderson <anderson_at_redhat.com>
Date: 2002-07-26 03:38:27
There is a bug in the initialization of the unw_table_entry structures for
all kernel modules, such that any unwind operation that comes upon a kernel
module text address will fail to find its associated unwind info data in
the build_script() routine.  (Actually it won't be able to determine what
module it belongs to.)

The reason that the unwind fails is due to the faulty initialization of
each module's unw_table structure, specifically the start and end fields.
For example, here's a typical module unw_table structure, in which the
"start" field is less than its "segment_base", and the "end" field is not
even in the module's address space:

  crash> unw_table 000000116cd5008
  struct unw_table {
    next = 0xe000000116cd51e8,
    name = 0xa000000000150b30 "keybdev",
    gp = 0xa000000000150ed8,
    segment_base = 0xa0000000001500c0,
    start = 0xa000000000150000,
    end = 0xa00000000014f740,
    array = 0xa000000000150a50,
    length = 0x7
  }

The build_script() routine can never find a module in which the target
IP fits between start and end, so it bails out thinking it's come upon
a leaf routine.

As it turns out, the real problem is due to the contents of the array of
unw_table_entry structures, the first and last of which are used to
calculate the start and end fields:

  static void
  init_unwind_table (struct unw_table *table, const char *name,
        unsigned long segment_base, unsigned long gp,
        const void *table_start, const void *table_end)
  {
          const struct unw_table_entry *start = table_start, *end =
table_end;

          table->name = name;
          table->segment_base = segment_base;
          table->gp = gp;
===>      table->start = segment_base + start[0].start_offset;
===>      table->end = segment_base + end[-1].end_offset;
          table->array = start;
          table->length = end - start;
  }

The "start[0].start_offset" and "end[-1].end_offset" values contain
invalid negative numbers.  Using the keybdev.o example above, here's what
its 7 unw_table_entry structures look like:

  crash> unw_table_entry 0xa000000000150a50 7
  struct unw_table_entry {
    start_offset = 0xffffffffffffff40,
    end_offset = 0xfffffffffffffcb0,
    info_offset = 0xfffffffffffff680
  }
  struct unw_table_entry {
    start_offset = 0xfffffffffffffca0,
    end_offset = 0xfffffffffffffb60,
    info_offset = 0xfffffffffffff660
  }
  struct unw_table_entry {
    start_offset = 0xfffffffffffffb60,
    end_offset = 0xfffffffffffffa40,
    info_offset = 0xfffffffffffff640
  }
  struct unw_table_entry {
    start_offset = 0xfffffffffffffa40,
    end_offset = 0xfffffffffffff890,
    info_offset = 0xfffffffffffff620
  }
  struct unw_table_entry {
    start_offset = 0xfffffffffffff880,
    end_offset = 0xfffffffffffff810,
    info_offset = 0xfffffffffffff600
  }
  struct unw_table_entry {
    start_offset = 0xfffffffffffff800,
    end_offset = 0xfffffffffffff6f0,
    info_offset = 0xfffffffffffff5e8
  }
  struct unw_table_entry {
    start_offset = 0xfffffffffffff6e0,
    end_offset = 0xfffffffffffff680,
    info_offset = 0xfffffffffffff5c8
  }

The faulty negative numbers are incorrectly calculated and initialized
by insmod.  David Mosberger pointed me in the proper location, where
there is a bug in obj/obj_ia64.c, in the arch_apply_relocation() routine,
as it applies to .IA_64.unwind relocations:

    case R_IA64_SEGREL32LSB :   /* @segrel(sym + add), data4 LSB */
    case R_IA64_SEGREL64LSB :   /* @segrel(sym + add), data8 LSB */
        if (targsec->header.sh_type & SHT_NOBITS)
            v = ifile->bss - v;
        else if (targsec->header.sh_flags & SHF_EXECINSTR)
            v = ifile->text - v;
        else
            v = ifile->data - v;
        if (r_info == R_IA64_SEGREL32LSB)
            COPY_32LSB(loc, v);
        else
            COPY_64LSB(loc, v);
        break;

In the case of .IA_64.unwind section data, each reallocation operation
mistakenly falls into the default path above where it does the
"v = ifile->data - v;" calculation.

What it should do, in the case of unw_table_entry data, is shown in the
patch below, which assigns offset values that the kernel init_unwind_table()
expects to find:

--- obj/obj_ia64.c.orig Thu Jul 25 12:31:58 2002
+++ obj/obj_ia64.c      Thu Jul 25 12:22:08 2002
@@ -915,6 +915,8 @@
            v = ifile->bss - v;
        else if (targsec->header.sh_flags & SHF_EXECINSTR)
            v = ifile->text - v;
+       else if (targsec->header.sh_type == SHT_IA_64_UNWIND)
+           v = v - ifile->text;
        else
            v = ifile->data - v;
        if (r_info == R_IA64_SEGREL32LSB)

although it doesn't address David M's questioning the SHF_EXECINSTR
calculation as well -- or the others for that matter.  Should those be
flipped as well?

Dave Anderson
Received on Thu Jul 25 10:35:45 2002

This archive was generated by hypermail 2.1.8 : 2005-08-02 09:20:09 EST