insmod bug causes kernel unwind failures for module text
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_ia64.c Thu Jul 25 12:22:08 2002 renamed from obj_ia64.c.orig Thu Jul 25 12:31:58 2002 +2 -0
@@ -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
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line · Dave Anderson
Dave> There is a bug in the initialization of the unw_table_entry Dave> structures for all kernel modules, such that any unwind Dave> operation that comes upon a kernel module text address will Dave> fail to find its associated unwind info data in the Dave> build_script() routine. (Actually it won't be able to Dave> determine what module it belongs to.)
Has this been resolved?
I don't recall seeing any followups.
--david
Quoted 3 lines · David Mosberger
Has this been resolved?
I don't recall seeing any followups.
I am working on it right now.