Re: [FYI] Inlining support, rough patch

Daniel Jacobowitz
2009-04-23T12:48:51Z

On Thu, Apr 23, 2009 at 12:03:32AM +0200, Mark Kettenis wrote:
> I firmly believe that if we want to add the capability to unwind
> through inlined functions, this fundamental principle should hold for
> inlined functions as well. This means that if we can detect that the
> current register state describes a process executing an inlined
> function we should faithfully reconstruct the register state for the
> call site of that inlined function. If I understand things correctly,
> the DW_TAG_inlined_subroutine tag provides information about the call
> site, which gives us the unwound program counter. But in order to
> reconstruct the complete register state, we need more information.
> The only viable source of that information is something like DWARF
> CFI; you don't stand a chance of doing a proper job here by doing
> instruction analysis.

DWARF CFI is not going to help with this; it only deals with 'real'
(i.e. not inlined) functions. There's no saved register state
from the virtual entry point. There isn't even an indicator
of where inlining occurs. Are you suggesting enhancing
the CFI information? I suspect the extra register state
would be generally unretrievable.

DWARF .debug_info is more helpful, but you still can't get a PC for
the call site; just the file and line number. I was able to
reconstruct a somewhat reliable entry address based on the block tree,
which may include DW_AT_entry_pc for the inlined function, but this
concept is quite fuzzy; there may not be a single entry point for the
inlined blocks. This leads to wrong scopes and wrong display of local
variables if you claim the outermost function is at the "unwound"
PC. For instance you'll get DW_AT_frame_base wrong if it's a
location list, and the inlined function pushes callee arguments
onto the stack.

In either case there's no instruction analysis involved.

An example might help. I'll use x86 - the nice thing about x86 for
this is that scheduling (particularly post-register-allocation) is
not too important, so blocks tend to be contiguous. That improves
debugger behavior and makes examples a heck of a lot easier to
read. Suppose you compile this:

inline int func1(void)
{
bar ();
return x * y;
}

inline int func2(void)
{
return x * func1 ();
}

You get this:

00000050 <func2>:
50: 55 push %ebp
51: 89 e5 mov %esp,%ebp
53: 83 ec 08 sub $0x8,%esp
56: e8 fc ff ff ff call 57 <func2+0x7>
57: R_386_PC32 bar
5b: a1 00 00 00 00 mov 0x0,%eax
5c: R_386_32 x
60: 0f af c0 imul %eax,%eax
63: 0f af 05 00 00 00 00 imul 0x0,%eax
66: R_386_32 y
6a: c9 leave
6b: c3 ret
6c: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi

00000000 00000010 ffffffff CIE
Version: 1
Augmentation: ""
Code alignment factor: 1
Data alignment factor: -4
Return address column: 8

DW_CFA_def_cfa: r4 (esp) ofs 4
DW_CFA_offset: r8 (eip) at cfa-4
DW_CFA_nop
DW_CFA_nop

00000074 00000014 00000000 FDE cie=00000000 pc=00000050..0000006c
DW_CFA_advance_loc: 1 to 00000051
DW_CFA_def_cfa_offset: 8
DW_CFA_offset: r5 (ebp) at cfa-8
DW_CFA_advance_loc: 2 to 00000053
DW_CFA_def_cfa_register: r5 (ebp)

<1><8c>: Abbrev Number: 8 (DW_TAG_subprogram)
<8d> DW_AT_external : 1
<8e> DW_AT_name : (indirect string, offset: 0x73): func1
<92> DW_AT_decl_file : 1
<93> DW_AT_decl_line : 24
<94> DW_AT_prototyped : 1
<95> DW_AT_type : <0x9a>
<99> DW_AT_inline : 3 (declared as inline and inlined)
<1><b2>: Abbrev Number: 10 (DW_TAG_subprogram)
<b3> DW_AT_external : 1
<b4> DW_AT_name : (indirect string, offset: 0x79): func2
<b8> DW_AT_decl_file : 1
<b9> DW_AT_decl_line : 30
<ba> DW_AT_prototyped : 1
<bb> DW_AT_type : <0x9a>
<bf> DW_AT_inline : 3 (declared as inline and inlined)
<c0> DW_AT_sibling : <0xc6>
<2><c4>: Abbrev Number: 4 (DW_TAG_lexical_block)
<1><c6>: Abbrev Number: 6 (DW_TAG_subprogram)
<c7> DW_AT_abstract_origin: <0xb2>
<cb> DW_AT_low_pc : 0x50
<cf> DW_AT_high_pc : 0x6c
<d3> DW_AT_frame_base : 0xb0 (location list)
<d7> DW_AT_sibling : <0xeb>
<2><db>: Abbrev Number: 7 (DW_TAG_inlined_subroutine)
<dc> DW_AT_abstract_origin: <0x8c>
<e0> DW_AT_low_pc : 0x56
<e4> DW_AT_high_pc : 0x6a
<e8> DW_AT_call_file : 1

So at PC == 0x5b, we know that we're in func2 inlined into func1.
Location lists in func1 don't depend on the PC in this example,
but they would if -fomit-frame-pointer and bar took stack arguments.
So we have to use the correct PC to look up DW_AT_frame_base; func1's
state at 0x56 and 0x5b are not necessarily the same. When blocks
interleave due to scheduling after inlining, you also lose track of
which local variables have values and which are unavailable.

--
Daniel Jacobowitz
CodeSourcery