Hi,
I'm getting a couple cases where a relocation of DW_AT_low_pc and DW_AT_high_pc attribute is formed against a section that's either non-EXEC, or even non-ALLOC.
The case of non-EXEC is in .rodata of vmlinux. A single die that comes from arch/x86/kernel/trampoline_32.S has this property. So I shift my understanding of low_pc and high_pc from "we can expect PC to have this address" to "it's a place in address space".
Another case is basically any GRUB module. These have relocations of low_pc and high_pc formed against .moddeps, which is non-ALLOC section (which, if I understand things correctly, means it doesn't end up in address space at all).
So, is my ALLOC & EXEC expectation valid? If it's too strict, is any ALLOC section kosher, and GRUB modules are erroneous, or is any section at all kosher?
PM
The case of non-EXEC is in .rodata of vmlinux. A single die that comes from arch/x86/kernel/trampoline_32.S has this property. So I shift my understanding of low_pc and high_pc from "we can expect PC to have this address" to "it's a place in address space".
I looked at this case. It's an oddball, but valid. That code is in .rodata because it's not actually run there, but copied to different addresses at runtime. It's a kosher CU from as -g producing line info. These "PC" addresses will never map usefully from where a PC really is at runtime, but statically it is a kosher and useful mapping from where the code appears at its symbol. i.e., eu-addr2line -e vmlinux trampoline_data+0
This is sufficiently unusual that it might still warrant a warning. But if it looks otherwise kosher, e.g. is SHF_ALLOC and not SHF_WRITE, it could mention "might be nonexecuted code in rodata" or something.
Hmm, but looking at the source, it is sometimes in a writable section here (.cpuinit.data, depends on kernel config).
Other than this one case, it seems about as likely that it would be due to a build error as to an authentic oddball like this. But, there is nothing technically invalid (or even necessarily "suspicious") about it from a pure DWARF perspective.
Another case is basically any GRUB module. These have relocations of low_pc and high_pc formed against .moddeps, which is non-ALLOC section (which, if I understand things correctly, means it doesn't end up in address space at all).
I'm not sure I found the right things to look at, since I did not find any .moddeps sections where I looked. Can you point me to a particular example (rpm id + file name)?
So, is my ALLOC & EXEC expectation valid? If it's too strict, is any ALLOC section kosher, and GRUB modules are erroneous, or is any section at all kosher?
I can't see how a non-ALLOC section case could be kosher. But we need to look at what's going on in the actual cases to get more clear on it.
Thanks, Roland
Roland McGrath wrote:
The case of non-EXEC is in .rodata of vmlinux. A single die that comes from arch/x86/kernel/trampoline_32.S has this property. So I shift my understanding of low_pc and high_pc from "we can expect PC to have this address" to "it's a place in address space".
I looked at this case. It's an oddball, but valid. That code is in .rodata because it's not actually run there, but copied to different addresses at runtime. It's a kosher CU from as -g producing line info. These "PC" addresses will never map usefully from where a PC really is at runtime, but statically it is a kosher and useful mapping from where the code appears at its symbol. i.e., eu-addr2line -e vmlinux trampoline_data+0
This is sufficiently unusual that it might still warrant a warning. But if it looks otherwise kosher, e.g. is SHF_ALLOC and not SHF_WRITE, it could mention "might be nonexecuted code in rodata" or something.
Hmm, but looking at the source, it is sometimes in a writable section here (.cpuinit.data, depends on kernel config).
Other than this one case, it seems about as likely that it would be due to a build error as to an authentic oddball like this. But, there is nothing technically invalid (or even necessarily "suspicious") about it from a pure DWARF perspective.
Thanks for the analysis, will implement this tomorrow.
Another case is basically any GRUB module. These have relocations of low_pc and high_pc formed against .moddeps, which is non-ALLOC section (which, if I understand things correctly, means it doesn't end up in address space at all).
I'm not sure I found the right things to look at, since I did not find any .moddeps sections where I looked. Can you point me to a particular example (rpm id + file name)?
One of the modules was this one: ./i586/gr/ub/grub2-debuginfo-1.98-0.5.20080827svn.fc11.i586/usr/lib/debug/usr/lib/grub2/i386-pc/sleep.mod.debug.bz2
And the relevant message from my locally hacked version: warning: .rel.debug_info: offset 0x50b: associated section #13 isn't SHF_ALLOC.
It tells something different on the branch, I didn't commit this change yet, but it should give some sort of diagnostic for that relocation.
Thanks, PM
One of the modules was this one: ./i586/gr/ub/grub2-debuginfo-1.98-0.5.20080827svn.fc11.i586/usr/lib/debug/usr/lib/grub2/i386-pc/sleep.mod.debug.bz2
And the relevant message from my locally hacked version: warning: .rel.debug_info: offset 0x50b: associated section #13 isn't SHF_ALLOC.
That's:
<compile_unit offset=[0x4f9] producer="GNU C 4.4.0 20090219 (Red Hat 4.4.0-0.21)" language=C89 name="mod-sleep.c" comp_dir="/usr/src/debug/grub2" low_pc=0x150 high_pc=0x7 stmt_list=0xaf> ...nothing but <base_type>s... </compile_unit>
The relocs for the low_pc and high_pc values are:
0x00000507 386_32 0000000000 .text 0x0000050b 386_32 0000000000 .moddeps
I looked into the grub2 build. This boils down to a source file equivalent to:
typedef int blah; __asm__ (".section .modname\n.string "" "foo" ""\n"); __asm__ (".section .moddeps\n.string "" "bar" ""\n");
So, this CU and all it produced being associated with a non-SHF_ALLOC section is kosher enough. Without some decl like the typedef, it doesn't produce the CU at all. I guess it decided it needed a CU because there were decls at all, but then pruned all the unused types. (Personally, I like producing a CU anyway even if it has no children at all--even for compiling /dev/null. You compiled the source file, so that's CU information even if nothing but name, comp_dir, language, producer.)
Since this CU contains only data, it really is fine that it's all in some specially-named non-allocated sections. That sort of thing is to be expected with anything like this using its own module loading relocation magic. But indeed no DW_FORM_addr or equivalent really ought to refer to any non-allocated section. It seems far less likely that a code section would be marked non-alloc.
Aside from any SHF_ALLOC check, this pair should be flagged because they are in two different sections. (I think I mentioned this before.) For not only low_pc+high_pc, but any begin+end pair in other header formats and whatnot that can have relocs, if either begin or end is relocated, it's bogus if its mate is not relocated against the same section.
The trouble comes from this part of the output:
gcc .text gcc .Ltext0: gcc #APP asm1 .section .modname asm1 .string "foo" asm1 asm2 .section .moddeps asm2 .string "bar" asm2 gcc .Letext0:
Then it uses .Ltext0 and .Letext0 in emitting .debug_info contents later. This is arguably the fault of the asm not using .pushsection/.popsection or whatnot. But my feeling is that it would be best to emit .Ltext0 only along with the first .text entry label, and .Letext0 only along with the last .text post-epilogue (.size et al). (That is how it does the labels for an alternate section when a function has __attribute__((section("foo"))).)
But there are further problems. Consider:
static const char name[] __attribute__ ((section (".modname"))) = "foo"; static const char deps[] __attribute__ ((section (".moddeps"))) = "bar";
This produces:
.text .Ltext0: .section .modname,"a",@progbits .type name, @object .size name, 4 name: .string "foo" .section .moddeps,"a",@progbits .type deps, @object .size deps, 4 deps: .string "bar" .text .Letext0:
as you would expect. And of course, the CU is useful since it describes "name" and "deps" and their types. But it gets a low_pc and high_pc of:
0x0000000000000015 X86_64_64 000000000000000000 +0 .text 0x000000000000001d X86_64_64 000000000000000000 +0 .text
That is harmless enough, but I think it would be better just to omit the low_pc and high_pc attributes when there are no PC addresses to talk about.
Now, worse:
static const char name[] __attribute__ ((section (".modname"))) = "foo"; static const char deps[] __attribute__ ((section (".moddeps"))) = "bar"; void __attribute__ ((section (".foo"))) foo (void) {}
This produces:
Relocation section [19] '.rela.debug_ranges' for section [18] '.debug_ranges' at offset 0xf08 contains 4 entries: Offset Type Value Addend Name 000000000000000000 X86_64_64 000000000000000000 +0 .text 0x0000000000000008 X86_64_64 000000000000000000 +0 .text 0x0000000000000010 X86_64_64 000000000000000000 +0 .foo 0x0000000000000018 X86_64_64 000000000000000000 +6 .foo
i.e., a spurious empty range in the discontiguous ranges. (But not in .debug_aranges!) Clearly here it should just omit the .Ltext0, .Letext0 entry. And in this case there is only one section actually used by PCs, so it can then drop back to just using low_pc/high_pc instead of the ranges attribute.
Finally (not a DWARF issue), take:
typedef int blah; void __attribute__ ((section (".foo"))) foo (void) {} __asm__ (".section .modname\n.string "" "foo" ""\n"); __asm__ (".section .moddeps\n.string "" "bar" ""\n"); void __attribute__ ((section (".foo"))) bar (void) {}
This produces:
.text .Ltext0: .section .foo,"ax",@progbits .globl foo .type foo, @function foo: .LFB0: ... .LFE0: .size foo, .-foo #APP .section .modname .string "foo"
.section .moddeps .string "bar"
#NO_APP .globl bar .type bar, @function bar: .LFB1: ... .LFE1: .size bar, .-bar .text .Letext0:
So that actually puts bar into the wrong section. Again, I don't really mind at all calling this the fault of the asm-writer. But it does speak to the robustness of close-binding things, i.e. emit fresh .section before each entry (only bother if there was any #APP, I guess), and emit all labels close-by like .LF[BE][01] here rather than like .L{,e}text0.
Also note that here it emits separate .debug_ranges and .debug_aranges entries for the foo range and the bar range, even though (modulo the wrong-section snafu) it knows they are together in a contiguous range, possibly modulo harmless alignment (though I see no .align here on x86). We'll make the compressor coalesce those adjacent ranges I guess, but it seems like it should be simple enough for the compiler to notice in the first place.
Thanks, Roland
elfutils-devel@lists.fedorahosted.org