From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 10179 invoked by alias); 12 Mar 2008 13:26:24 -0000 Received: (qmail 10166 invoked by uid 22791); 12 Mar 2008 13:26:21 -0000 X-Spam-Status: No, hits=-0.0 required=5.0 tests=AWL,BAYES_40,J_CHICKENPOX_54,J_CHICKENPOX_66,J_CHICKENPOX_72 X-Spam-Check-By: sourceware.org Received: from wildebeest.demon.nl (HELO gnu.wildebeest.org) (83.160.170.119) by sourceware.org (qpsmtpd/0.31) with ESMTP; Wed, 12 Mar 2008 13:25:59 +0000 Received: from dijkstra.wildebeest.org ([192.168.1.29]) by gnu.wildebeest.org with esmtp (Exim 4.63) (envelope-from ) id 1JZQxv-0006tE-4t for frysk@sourceware.org; Wed, 12 Mar 2008 14:25:56 +0100 Subject: [patch] Stack based unwind fallback for plt entries on x86 From: Mark Wielaard To: frysk@sourceware.org Content-Type: multipart/mixed; boundary="=-qTlJRG/+pcl1BhAJmPKx" Date: Wed, 12 Mar 2008 13:26:00 -0000 Message-Id: <1205328354.3369.26.camel@dijkstra.wildebeest.org> Mime-Version: 1.0 X-Mailer: Evolution 2.12.3 (2.12.3-3.fc8) X-Spam-Score: -4.4 (----) X-Virus-Checked: Checked by ClamAV on sourceware.org X-IsSubscribed: yes Mailing-List: contact frysk-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Post: List-Help: , Sender: frysk-owner@sourceware.org X-SW-Source: 2008-q1/txt/msg00145.txt.bz2 --=-qTlJRG/+pcl1BhAJmPKx Content-Type: text/plain Content-Transfer-Encoding: 7bit Content-length: 3402 Hi, This patch makes the TestLibFunctionStepFrame PASS (but see below) and makes nexting over shared library function calls (through plt entries) work on x86. To recap the problem. When there is no debug/unwind_frame info libunwind needs to guess how to do a step. The final fallback on x86 is using the frame pointer (ESB). There are two problems with this. First code compiled with -fomit-frame-pointer doesn't have frame pointers, so then we are chasing a possibly ghost frame. Second when stepping onto a plt entry there is no real frame setup, the plt entry is kind of a jump point to the real function to be called (and facilitates invoking the dynamic linker of course to resolve the "jump slot" first). This means that if you try to follow the frame pointer you are actually following into the frame above the "plt frame". This confuses the SteppingEngine terribly. This isn't a direct problem on x86_64 (at least for the SteppingEngine at this time) because libunwind never tries to do any frame pointer tricks there. So on x86_64 you currently cannot unwind, which is better than getting the wrong outer frame. (On x86_64 nexting works, but TestLibFunctionStepFrame doesn't PASS.) There are a couple of properties of x86 plt entries that help with resolving this issue. 1) They only occur as inner frames (think of them as tail-calls). 2) They are always invoked with a CALL instruction (as if it was a real function call). 3) If they are resolved they are just one JMP instruction, with the return address on the stack, otherwise they are a JMP, two PUSHs (selector and got table address) and a final JUMP (into ld). The patch tries to recognize this situation. Because we don't have much info to go on, we don't have any of the elf section info inside libunwind, it is a bit heuristic at the moment. It probes the stack for the pattern mentioned above, pulls out the return address and checks if it comes from a CALL instruction, then adjusts the stack and ip addresses for the cursor accordingly. I don't know if upstream will like this solution (it contains one probe into an address to see what the instruction there is, that could possibly not exist, which is fine for us since that is just an error access and we fall back, but when using libunwind in-process this could possibly be fatal with a really bad address). One change was made to the test. TestLibFunctionStepFrame used to step through all of the dynamic loader and made sure all possible instructions inside had a good backtrace. But there is one point that seems to have bad unwind info (and of dl_fixup, the last ret instruction). I filed #5917 to investigate this more. The test has been adjusted to just check the first few instructions of the actual PLT and ld and/or actual function entry. 2008-03-12 Mark Wielaard * TestLibFunctionStepFrame.java: Mark only unresolved on x86_64 and ppc. Only check first 24 steps (bug #5917). Tighter check for main and foo order. 2008-03-12 Mark Wielaard * Frame.java (toString): New method. 2007-03-12 Mark Wielaard * src/x86/Gstep.c (is_call_instr_at): New function. (init_stack_based_ret): New function. (unw_step): Try stack based unwind with call instr, before fallback to frame pointer. Committed, Mark --=-qTlJRG/+pcl1BhAJmPKx Content-Disposition: inline; filename=plt.patch Content-Type: text/x-patch; name=plt.patch; charset=UTF-8 Content-Transfer-Encoding: 7bit Content-length: 8931 diff --git a/frysk-core/frysk/stack/ChangeLog b/frysk-core/frysk/stack/ChangeLog index 8ff178e..47a781f 100644 --- a/frysk-core/frysk/stack/ChangeLog +++ b/frysk-core/frysk/stack/ChangeLog @@ -1,3 +1,13 @@ +2008-03-12 Mark Wielaard + + * TestLibFunctionStepFrame.java: Mark only unresolved on x86_64 + and ppc. Only check first 24 steps (bug #5917). Tighter check for + main and foo order. + +2008-03-12 Mark Wielaard + + * Frame.java (toString): New method. + 2008-03-10 Mark Wielaard * TestLibFunctionStepFrame.java: New test for bug #5259. diff --git a/frysk-core/frysk/stack/Frame.java b/frysk-core/frysk/stack/Frame.java index e676008..bee5511 100644 --- a/frysk-core/frysk/stack/Frame.java +++ b/frysk-core/frysk/stack/Frame.java @@ -1,6 +1,6 @@ // This file is part of the program FRYSK. // -// Copyright 2007, Red Hat Inc. +// Copyright 2007, 2008, Red Hat Inc. // // FRYSK is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by @@ -41,6 +41,7 @@ package frysk.stack; import java.io.File; import java.io.PrintWriter; +import java.io.StringWriter; import lib.dwfl.Dwfl; import lib.dwfl.DwflModule; @@ -192,7 +193,22 @@ public abstract class Frame { return "Unknown"; } } - + + /** + * Returns a plain string representation if this frame. + * This is similar to the result of calling toPrint() + * with both printParameters and fullPaths set to false. + */ + public String toString() { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + pw.print(this.getClass().getName()); + pw.print('['); + toPrint(pw, false, false); + pw.print(']'); + pw.flush(); + return sw.toString(); + } /** * Extracts OFFSET:LENGTH bytes of REGISTER storing them from diff --git a/frysk-core/frysk/stack/TestLibFunctionStepFrame.java b/frysk-core/frysk/stack/TestLibFunctionStepFrame.java index f1f8c59..d71f5ac 100644 --- a/frysk-core/frysk/stack/TestLibFunctionStepFrame.java +++ b/frysk-core/frysk/stack/TestLibFunctionStepFrame.java @@ -67,7 +67,7 @@ public class TestLibFunctionStepFrame { public void testStepIntoLibFunctionCall() { - if (unresolved(5259)) + if (unresolvedOnx8664(5259) || unresolvedOnPPC(5259)) return; String source = Config.getRootSrcDir() @@ -116,7 +116,7 @@ public class TestLibFunctionStepFrame // We are at the first plt intruction (for the first time). long firstPLTAddress = task.getPC(); Frame frame = StackFactory.createFrame(task); - assertFooAndMainOuterFrames("At first PLT address", frame); + assertFooAndMainOuterFrames("First entry in PLT", frame); // Count your steps. And bail out when there are too many. int steps = 1; @@ -125,12 +125,15 @@ public class TestLibFunctionStepFrame while (currentPC != addressLast && steps < 1000) { task.requestUnblock(this); - assertRunUntilStop("Do step: " + steps); + assertRunUntilStop("Do step: " + + (seenSecondCall ? "second" : "") + + steps); currentPC = task.getPC(); if (currentPC == address2) { seenSecondCall = true; + steps = 1; // When we step we should be at the first PLTAddress again. // One step and we should be at the first PLT instruction. task.requestUnblock(this); @@ -139,14 +142,18 @@ public class TestLibFunctionStepFrame assertEquals("Second time in PLT", currentPC, firstPLTAddress); frame = StackFactory.createFrame(task); - assertFooAndMainOuterFrames("Second time in PLT", frame); + assertFooAndMainOuterFrames("Second entry in PLT", frame); } - else if (currentPC != addressLast) + else if (currentPC != addressLast && steps < 24) { + // Only check first 24 frame = StackFactory.createFrame(task); - assertFooAndMainOuterFrames("Stepping, #" + steps + assertFooAndMainOuterFrames("Stepping " + + (seenSecondCall ? "second" : "") + + ", #" + steps + " through (plt) call", frame); } + steps++; } assertTrue("less than a thousand steps", steps < 1000); @@ -167,6 +174,7 @@ public class TestLibFunctionStepFrame assertTrue(message + " first inner frame should not be foo or main", ok); boolean foo_seen = false; + boolean main_seen = false; Frame outer = frame.getOuter(); while (ok && outer != null) { @@ -186,7 +194,11 @@ public class TestLibFunctionStepFrame boolean sym_is_main = name.indexOf("main") != -1; if (foo_seen && sym_is_main) - break; + { + // Hurray done! + main_seen = true; + break; + } if (! foo_seen && sym_is_main) { @@ -199,7 +211,7 @@ public class TestLibFunctionStepFrame outer = outer.getOuter(); } - ok = outer != null; + ok = ok && foo_seen && main_seen && outer != null; if (! ok) printBacktrace(frame); assertTrue(message diff --git a/frysk-imports/libunwind/ChangeLog b/frysk-imports/libunwind/ChangeLog index 6ecc99c..c34bbe9 100644 --- a/frysk-imports/libunwind/ChangeLog +++ b/frysk-imports/libunwind/ChangeLog @@ -1,3 +1,10 @@ +2007-03-12 Mark Wielaard + + * src/x86/Gstep.c (is_call_instr_at): New function. + (init_stack_based_ret): New function. + (unw_step): Try stack based unwind with call instr, before + fallback to frame pointer. + 2007-01-31 Mark Wielaard Fixup libunwind merge. diff --git a/frysk-imports/libunwind/src/x86/Gstep.c b/frysk-imports/libunwind/src/x86/Gstep.c index 618b8af..c9083cb 100644 --- a/frysk-imports/libunwind/src/x86/Gstep.c +++ b/frysk-imports/libunwind/src/x86/Gstep.c @@ -85,6 +85,72 @@ code_descriptor_trap (struct cursor *c, struct dwarf_loc *eip_loc_pointer) return 1; } +// A CALL instruction starts with 0xFF. +static int +is_call_instr_at (struct cursor *c, unw_word_t addr) +{ + int ret; + unw_word_t instr; + ret = dwarf_get (&c->dwarf, DWARF_LOC (addr, 0), &instr); + return ret >= 0 && ((instr & 0xff000000) == 0xff000000); +} + +// Checks whether this looks like a plt entry like cursor and returns +// the stack offset where the return address can be found, or -1 if +// not detected (also tries to make sure this is the inner most frame). +// When this function returns positively (zero included) addr will +// contain the return address. +static int +init_stack_based_ret (struct cursor *c, unw_word_t *addr) +{ + // See if this looks "clean", everything in actual registers + // which indicates this is most likely an inner most frame just + // initted. + int ret; + unw_word_t ip, cfa; + ret = dwarf_get (&c->dwarf, c->dwarf.loc[EIP], &ip); + if (ret < 0) + return ret; + + ret = dwarf_get (&c->dwarf, c->dwarf.loc[ESP], &cfa); + if (ret < 0) + return ret; + + if (c->sigcontext_format == X86_SCF_NONE + && DWARF_IS_REG_LOC(c->dwarf.loc[EAX]) + && DWARF_IS_REG_LOC(c->dwarf.loc[ECX]) + && DWARF_IS_REG_LOC(c->dwarf.loc[EDX]) + && DWARF_IS_REG_LOC(c->dwarf.loc[EBX]) + && DWARF_IS_REG_LOC(c->dwarf.loc[ESP]) + && DWARF_IS_REG_LOC(c->dwarf.loc[EBP]) + && DWARF_IS_REG_LOC(c->dwarf.loc[ESI]) + && DWARF_IS_REG_LOC(c->dwarf.loc[EDI]) + && DWARF_IS_REG_LOC(c->dwarf.loc[EIP]) + && DWARF_IS_REG_LOC(c->dwarf.loc[EFLAGS]) + && DWARF_IS_REG_LOC(c->dwarf.loc[TRAPNO]) + && ip == c->dwarf.ip && cfa == c->dwarf.cfa) + { + // See if one of the top 3 elements on the stack contains a + // return address. + int i; + for (i = 0; i <= 8; i += 4) + { + // fprintf(stderr, "trying %d\n", i); + ret = dwarf_get (&c->dwarf, DWARF_LOC (c->dwarf.cfa + i, 0), addr); + if (ret < 0) + return ret; + + // fprintf(stderr, "addr at %d: 0x%x\n", i, *addr); + // Sanity check the address, not too low, and must + // come from a call instruction. + if (*addr > 0 && is_call_instr_at(c, (*addr) - 5)) + return i; + } + } + + return -1; +} + PROTECTED int unw_step (unw_cursor_t *cursor) { @@ -92,6 +158,7 @@ unw_step (unw_cursor_t *cursor) int ret, i; struct dwarf_loc eip_loc; int eip_loc_set = 0; + unw_word_t addr; Debug (1, "(cursor=%p, ip=0x%08x)\n", c, (unsigned) c->dwarf.ip); @@ -202,6 +269,16 @@ unw_step (unw_cursor_t *cursor) c->dwarf.loc[TRAPNO] = DWARF_NULL_LOC; c->dwarf.loc[ST0] = DWARF_NULL_LOC; } + else if((ret = init_stack_based_ret(c, &addr)) >= 0) + { + // fprintf(stderr, "init_stack_based_ret() %d (0x%x)\n", ret, addr); + c->dwarf.cfa += ret; + c->dwarf.loc[ESP] = DWARF_LOC (c->dwarf.cfa, 0); + c->dwarf.loc[EIP] = DWARF_LOC (addr, 0); + c->dwarf.ret_addr_column = EIP; + c->dwarf.ip = addr; + return 1; + } else { ret = dwarf_get (&c->dwarf, c->dwarf.loc[EBP], &c->dwarf.cfa); --=-qTlJRG/+pcl1BhAJmPKx--