summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler/cmm/CmmLayoutStack.hs62
-rw-r--r--compiler/cmm/Debug.hs91
-rw-r--r--compiler/nativeGen/Dwarf/Types.hs6
-rw-r--r--rts/Exception.cmm1
-rw-r--r--testsuite/driver/testglobals.py6
-rw-r--r--testsuite/driver/testlib.py6
-rw-r--r--testsuite/mk/test.mk14
-rw-r--r--testsuite/tests/codeGen/should_compile/Makefile5
-rw-r--r--testsuite/tests/codeGen/should_compile/T14999.cmm11
-rw-r--r--testsuite/tests/codeGen/should_compile/T14999.stdout16
-rw-r--r--testsuite/tests/codeGen/should_compile/all.T5
11 files changed, 185 insertions, 38 deletions
diff --git a/compiler/cmm/CmmLayoutStack.hs b/compiler/cmm/CmmLayoutStack.hs
index d2525d1ffd..1d6c209953 100644
--- a/compiler/cmm/CmmLayoutStack.hs
+++ b/compiler/cmm/CmmLayoutStack.hs
@@ -577,15 +577,8 @@ makeFixupBlock dflags sp0 l stack tscope assigs
| otherwise = do
tmp_lbl <- newBlockId
let sp_off = sp0 - sm_sp stack
- maybeAddUnwind block
- | debugLevel dflags > 0
- = block `blockSnoc` CmmUnwind [(Sp, Just unwind_val)]
- | otherwise
- = block
- where unwind_val = cmmOffset dflags spExpr (sm_sp stack)
block = blockJoin (CmmEntry tmp_lbl tscope)
- ( maybeAddSpAdj dflags sp_off
- $ maybeAddUnwind
+ ( maybeAddSpAdj dflags sp0 sp_off
$ blockFromList assigs )
(CmmBranch l)
return (tmp_lbl, [block])
@@ -851,28 +844,7 @@ manifestSp dflags stackmaps stack0 sp0 sp_high
adj_pre_sp = mapExpDeep (areaToSp dflags sp0 sp_high area_off)
adj_post_sp = mapExpDeep (areaToSp dflags (sp0 - sp_off) sp_high area_off)
- -- Add unwind pseudo-instruction at the beginning of each block to
- -- document Sp level for debugging
- add_initial_unwind block
- | debugLevel dflags > 0
- = CmmUnwind [(Sp, Just sp_unwind)] `blockCons` block
- | otherwise
- = block
- where sp_unwind = CmmRegOff spReg (sp0 - wORD_SIZE dflags)
-
- -- Add unwind pseudo-instruction right before the Sp adjustment
- -- if there is one.
- add_adj_unwind block
- | debugLevel dflags > 0
- , sp_off /= 0
- = block `blockSnoc` CmmUnwind [(Sp, Just sp_unwind)]
- | otherwise
- = block
- where sp_unwind = CmmRegOff spReg (sp0 - wORD_SIZE dflags - sp_off)
-
- final_middle = maybeAddSpAdj dflags sp_off
- . add_adj_unwind
- . add_initial_unwind
+ final_middle = maybeAddSpAdj dflags sp0 sp_off
. blockFromList
. map adj_pre_sp
. elimStackStores stack0 stackmaps area_off
@@ -891,11 +863,33 @@ getAreaOff stackmaps (Young l) =
Nothing -> pprPanic "getAreaOff" (ppr l)
-maybeAddSpAdj :: DynFlags -> ByteOff -> Block CmmNode O O -> Block CmmNode O O
-maybeAddSpAdj _ 0 block = block
-maybeAddSpAdj dflags sp_off block = block `blockSnoc` adj
+maybeAddSpAdj
+ :: DynFlags -> ByteOff -> ByteOff -> Block CmmNode O O -> Block CmmNode O O
+maybeAddSpAdj dflags sp0 sp_off block =
+ add_initial_unwind $ add_adj_unwind $ adj block
where
- adj = CmmAssign spReg (cmmOffset dflags spExpr sp_off)
+ adj block
+ | sp_off /= 0
+ = block `blockSnoc` CmmAssign spReg (cmmOffset dflags spExpr sp_off)
+ | otherwise = block
+ -- Add unwind pseudo-instruction at the beginning of each block to
+ -- document Sp level for debugging
+ add_initial_unwind block
+ | debugLevel dflags > 0
+ = CmmUnwind [(Sp, Just sp_unwind)] `blockCons` block
+ | otherwise
+ = block
+ where sp_unwind = CmmRegOff spReg (sp0 - wORD_SIZE dflags)
+
+ -- Add unwind pseudo-instruction right after the Sp adjustment
+ -- if there is one.
+ add_adj_unwind block
+ | debugLevel dflags > 0
+ , sp_off /= 0
+ = block `blockSnoc` CmmUnwind [(Sp, Just sp_unwind)]
+ | otherwise
+ = block
+ where sp_unwind = CmmRegOff spReg (sp0 - wORD_SIZE dflags - sp_off)
{- Note [SP old/young offsets]
diff --git a/compiler/cmm/Debug.hs b/compiler/cmm/Debug.hs
index 044a00009c..da37495530 100644
--- a/compiler/cmm/Debug.hs
+++ b/compiler/cmm/Debug.hs
@@ -349,8 +349,8 @@ in addition to the usual beginning-of-block statement,
unwind Sp = Just Sp + 0;
I64[Sp - 8] = c2dD;
R1 = v :: P64;
- unwind Sp = Just Sp + 8;
Sp = Sp - 8;
+ unwind Sp = Just Sp + 8;
if (R1 & 7 != 0) goto c2dD; else goto c2dE;
The remaining blocks are simple,
@@ -392,10 +392,95 @@ The flow of unwinding information through the compiler is a bit convoluted:
* This unwind information is converted to DebugBlocks by Debug.cmmDebugGen
- * These DebugBlcosk are then converted to, e.g., DWARF unwinding tables
+ * These DebugBlocks are then converted to, e.g., DWARF unwinding tables
(by the Dwarf module) and emitted in the final object.
-See also: Note [Unwinding information in the NCG] in AsmCodeGen.
+See also:
+ Note [Unwinding information in the NCG] in AsmCodeGen,
+ Note [Unwind pseudo-instruction in Cmm],
+ Note [Debugging DWARF unwinding info].
+
+
+Note [Debugging DWARF unwinding info]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For debugging generated unwinding info I've found it most useful to dump the
+disassembled binary with objdump -D and dump the debug info with
+readelf --debug-dump=frames-interp.
+
+You should get something like this:
+
+ 0000000000000010 <stg_catch_frame_info>:
+ 10: 48 83 c5 18 add $0x18,%rbp
+ 14: ff 65 00 jmpq *0x0(%rbp)
+
+and:
+
+ Contents of the .debug_frame section:
+
+ 00000000 0000000000000014 ffffffff CIE "" cf=1 df=-8 ra=16
+ LOC CFA rbp rsp ra
+ 0000000000000000 rbp+0 v+0 s c+0
+
+ 00000018 0000000000000024 00000000 FDE cie=00000000 pc=000000000000000f..0000000000000017
+ LOC CFA rbp rsp ra
+ 000000000000000f rbp+0 v+0 s c+0
+ 000000000000000f rbp+24 v+0 s c+0
+
+To read it http://www.dwarfstd.org/doc/dwarf-2.0.0.pdf has a nice example in
+Appendix 5 (page 101 of the pdf) and more details in the relevant section.
+
+The key thing to keep in mind is that the value at LOC is the value from
+*before* the instruction at LOC executes. In other words it answers the
+question: if my $rip is at LOC, how do I get the relevant values given the
+values obtained through unwinding so far.
+
+If the readelf --debug-dump=frames-interp output looks wrong, it may also be
+useful to look at readelf --debug-dump=frames, which is closer to the
+information that GHC generated.
+
+It's also useful to dump the relevant Cmm with -ddump-cmm -ddump-opt-cmm
+-ddump-cmm-proc -ddump-cmm-verbose. Note [Unwind pseudo-instruction in Cmm]
+explains how to interpret it.
+
+Inside gdb there are a couple useful commands for inspecting frames.
+For example:
+
+ gdb> info frame <num>
+
+It shows the values of registers obtained through unwinding.
+
+Another useful thing to try when debugging the DWARF unwinding is to enable
+extra debugging output in GDB:
+
+ gdb> set debug frame 1
+
+This makes GDB produce a trace of its internal workings. Having gone this far,
+it's just a tiny step to run GDB in GDB. Make sure you install debugging
+symbols for gdb if you obtain it through a package manager.
+
+Keep in mind that the current release of GDB has an instruction pointer handling
+heuristic that works well for C-like languages, but doesn't always work for
+Haskell. See Note [Info Offset] in Dwarf.Types for more details.
+
+Note [Unwind pseudo-instruction in Cmm]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+One of the possible CmmNodes is a CmmUnwind pseudo-instruction. It doesn't
+generate any assembly, but controls what DWARF unwinding information gets
+generated.
+
+It's important to understand what ranges of code the unwind pseudo-instruction
+refers to.
+For a sequence of CmmNodes like:
+
+ A // starts at addr X and ends at addr Y-1
+ unwind Sp = Just Sp + 16;
+ B // starts at addr Y and ends at addr Z
+
+the unwind statement reflects the state after A has executed, but before B
+has executed. If you consult the Note [Debugging DWARF unwinding info], the
+LOC this information will end up in is Y.
-}
-- | A label associated with an 'UnwindTable'
diff --git a/compiler/nativeGen/Dwarf/Types.hs b/compiler/nativeGen/Dwarf/Types.hs
index 579ed0d256..25629448dd 100644
--- a/compiler/nativeGen/Dwarf/Types.hs
+++ b/compiler/nativeGen/Dwarf/Types.hs
@@ -415,6 +415,12 @@ pprFrameBlock (DwarfFrameBlock hasInfo uws0) =
-- Note that this will not prevent GDB from failing to look-up the
-- correct function name for the frame, as that uses the symbol table,
-- which we can not manipulate as easily.
+--
+-- There's a GDB patch to address this at [1]. At the moment of writing
+-- it's not merged, so I recommend building GDB with the patch if you
+-- care about unwinding. The hack above doesn't cover every case.
+--
+-- [1] https://sourceware.org/ml/gdb-patches/2018-02/msg00055.html
-- | Get DWARF register ID for a given GlobalReg
dwarfGlobalRegNo :: Platform -> GlobalReg -> Word8
diff --git a/rts/Exception.cmm b/rts/Exception.cmm
index 96d95e6f8c..8deecbb1e8 100644
--- a/rts/Exception.cmm
+++ b/rts/Exception.cmm
@@ -370,7 +370,6 @@ INFO_TABLE_RET(stg_catch_frame, CATCH_FRAME,
exceptions_blocked,handler))
return (P_ ret)
{
- unwind Sp = Sp + SIZEOF_StgCatchFrame;
return (ret);
}
diff --git a/testsuite/driver/testglobals.py b/testsuite/driver/testglobals.py
index f6831c9ad8..246f26ce9a 100644
--- a/testsuite/driver/testglobals.py
+++ b/testsuite/driver/testglobals.py
@@ -105,6 +105,12 @@ class TestConfig:
# Do we have SMP support?
self.have_smp = False
+ # Is gdb avaliable?
+ self.have_gdb = False
+
+ # Is readelf available?
+ self.have_readelf = False
+
# Are we testing an in-tree compiler?
self.in_tree_compiler = True
diff --git a/testsuite/driver/testlib.py b/testsuite/driver/testlib.py
index f2bb1c9f01..3bae76585a 100644
--- a/testsuite/driver/testlib.py
+++ b/testsuite/driver/testlib.py
@@ -413,6 +413,12 @@ def compiler_profiled( ):
def compiler_debugged( ):
return config.compiler_debugged
+def have_gdb( ):
+ return config.have_gdb
+
+def have_readelf( ):
+ return config.have_readelf
+
# ---
def high_memory_usage(name, opts):
diff --git a/testsuite/mk/test.mk b/testsuite/mk/test.mk
index 1eb8d49c4b..87d22b330b 100644
--- a/testsuite/mk/test.mk
+++ b/testsuite/mk/test.mk
@@ -90,6 +90,8 @@ GHC_PRIM_LIBDIR := $(subst library-dirs: ,,$(shell "$(GHC_PKG)" field ghc-prim l
HAVE_VANILLA := $(shell if [ -f $(subst \,/,$(GHC_PRIM_LIBDIR))/GHC/PrimopWrappers.hi ]; then echo YES; else echo NO; fi)
HAVE_DYNAMIC := $(shell if [ -f $(subst \,/,$(GHC_PRIM_LIBDIR))/GHC/PrimopWrappers.dyn_hi ]; then echo YES; else echo NO; fi)
HAVE_PROFILING := $(shell if [ -f $(subst \,/,$(GHC_PRIM_LIBDIR))/GHC/PrimopWrappers.p_hi ]; then echo YES; else echo NO; fi)
+HAVE_GDB := $(shell if gdb --version > /dev/null 2> /dev/null; then echo YES; else echo NO; fi)
+HAVE_READELF := $(shell if readelf --version > /dev/null 2> /dev/null; then echo YES; else echo NO; fi)
ifeq "$(HAVE_VANILLA)" "YES"
RUNTEST_OPTS += -e config.have_vanilla=True
@@ -135,6 +137,18 @@ else
RUNTEST_OPTS += -e config.unregisterised=False
endif
+ifeq "$(HAVE_GDB)" "YES"
+RUNTEST_OPTS += -e config.have_gdb=True
+else
+RUNTEST_OPTS += -e config.have_gdb=False
+endif
+
+ifeq "$(HAVE_READELF)" "YES"
+RUNTEST_OPTS += -e config.have_readelf=True
+else
+RUNTEST_OPTS += -e config.have_readelf=False
+endif
+
ifeq "$(GhcDynamicByDefault)" "YES"
RUNTEST_OPTS += -e config.ghc_dynamic_by_default=True
CABAL_MINIMAL_BUILD = --enable-shared --disable-library-vanilla
diff --git a/testsuite/tests/codeGen/should_compile/Makefile b/testsuite/tests/codeGen/should_compile/Makefile
index a8414384cf..e024788085 100644
--- a/testsuite/tests/codeGen/should_compile/Makefile
+++ b/testsuite/tests/codeGen/should_compile/Makefile
@@ -33,3 +33,8 @@ debug:
./debug
rm debug
+
+T14999:
+ '$(TEST_HC)' $(TEST_HC_OPTS) -O2 -g -c T14999.cmm -o T14999.o
+ gdb --batch -ex 'file T14999.o' -ex 'disassemble stg_catch_frame_info' --nx | tr -s '[:blank:]'
+ readelf --debug-dump=frames-interp T14999.o | tr -s '[:blank:]'
diff --git a/testsuite/tests/codeGen/should_compile/T14999.cmm b/testsuite/tests/codeGen/should_compile/T14999.cmm
new file mode 100644
index 0000000000..a3e283b0be
--- /dev/null
+++ b/testsuite/tests/codeGen/should_compile/T14999.cmm
@@ -0,0 +1,11 @@
+#define CATCH_FRAME 34
+
+#define SIZEOF_StgCatchFrame (SIZEOF_StgHeader+16)
+
+INFO_TABLE_RET(stg_catch_frame, CATCH_FRAME,
+ bits64 info_ptr, bits64 exceptions_blocked, gcptr handler)
+ return (gcptr ret)
+{
+ return (ret);
+}
+
diff --git a/testsuite/tests/codeGen/should_compile/T14999.stdout b/testsuite/tests/codeGen/should_compile/T14999.stdout
new file mode 100644
index 0000000000..4bca980a9a
--- /dev/null
+++ b/testsuite/tests/codeGen/should_compile/T14999.stdout
@@ -0,0 +1,16 @@
+Dump of assembler code for function stg_catch_frame_info:
+ 0x0000000000000010 <+0>: add $0x18,%rbp
+ 0x0000000000000014 <+4>: jmpq *0x0(%rbp)
+End of assembler dump.
+Contents of the .debug_frame section:
+
+00000000 0000000000000014 ffffffff CIE "" cf=1 df=-8 ra=16
+ LOC CFA rbp rsp ra
+0000000000000000 rbp+0 v+0 s c+0
+
+00000018 000000000000002c 00000000 FDE cie=00000000 pc=000000000000000f..0000000000000017
+ LOC CFA rbp rsp ra
+000000000000000f rbp+0 v+0 s c+0
+000000000000000f rbp+24 v+0 s c+0
+0000000000000014 rbp+0 v+0 s c+0
+
diff --git a/testsuite/tests/codeGen/should_compile/all.T b/testsuite/tests/codeGen/should_compile/all.T
index fb813a497a..9118b6c23b 100644
--- a/testsuite/tests/codeGen/should_compile/all.T
+++ b/testsuite/tests/codeGen/should_compile/all.T
@@ -38,3 +38,8 @@ test('T12355', normal, compile, [''])
test('T14626',
normal,
run_command, ['$MAKE -s --no-print-directory T14626'])
+test('T14999',
+ [when((arch('powerpc64') or arch('powerpc64le')), expect_broken(11261)),
+ unless(opsys('linux') and arch('x86_64') and have_gdb() and
+ have_readelf(), skip)],
+ run_command, ['$MAKE -s --no-print-directory T14999'])