/* * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.max.vm.compiler.target.amd64; import com.oracle.max.asm.target.amd64.*; import com.oracle.max.cri.intrinsics.*; import com.sun.cri.ci.*; import com.sun.max.annotate.*; import com.sun.max.lang.*; import com.sun.max.unsafe.*; import com.sun.max.vm.*; import com.sun.max.vm.compiler.*; import com.sun.max.vm.compiler.target.*; import com.sun.max.vm.runtime.*; import com.sun.max.vm.stack.*; import com.sun.max.vm.stack.amd64.*; /** * A utility class factoring out code common to all AMD64 target method. */ public final class AMD64TargetMethodUtil { /** * Opcode of a RIP-relative call instruction. */ public static final int RIP_CALL = 0xe8; /** * Opcode of a register-based call instruction. */ public static final int REG_CALL = 0xff; /** * Opcode of a RIP-relative jump instruction. */ public static final int RIP_JMP = 0xe9; /** * Opcode of a (near) return instruction. */ public static final int RET = 0xc3; /** * Size (in bytes) of a RIP-relative call instruction. */ public static final int RIP_CALL_INSTRUCTION_SIZE = 5; /** * Lock to avoid race on concurrent icache invalidation when patching target methods. */ private static final Object PatchingLock = new Object(); // JavaMonitorManager.newVmLock("PATCHING_LOCK"); public static int registerReferenceMapSize() { return UnsignedMath.divide(AMD64.cpuRegisters.length, Bytes.WIDTH); } public static boolean isPatchableCallSite(CodePointer callSite) { // We only update the disp of the call instruction. // The compiler(s) ensure that disp of the call be aligned to a word boundary. // This may cause up to 7 nops to be inserted before a call. final Address callSiteAddress = callSite.toAddress(); final Address endOfCallSite = callSiteAddress.plus(RIP_CALL_INSTRUCTION_LENGTH - 1); return callSiteAddress.plus(1).isWordAligned() ? true : // last byte of call site: callSiteAddress.roundedDownBy(8).equals(endOfCallSite.roundedDownBy(8)); } /** * Gets the target of a 32-bit relative CALL instruction. * * @param tm the method containing the CALL instruction * @param callPos the offset within the code of {@code targetMethod} of the CALL * @return the absolute target address of the CALL */ public static CodePointer readCall32Target(TargetMethod tm, int callPos) { final CodePointer callSite = tm.codeAt(callPos); int disp32; if (MaxineVM.isHosted()) { final byte[] code = tm.code(); assert code[0] == (byte) RIP_CALL; disp32 = (code[callPos + 4] & 0xff) << 24 | (code[callPos + 3] & 0xff) << 16 | (code[callPos + 2] & 0xff) << 8 | (code[callPos + 1] & 0xff) << 0; } else { final Pointer callSitePointer = callSite.toPointer(); assert callSitePointer.readByte(0) == (byte) RIP_CALL // deopt might replace the first call in a method with a jump (redirection) || (callSitePointer.readByte(0) == (byte) RIP_JMP && callPos == 0) : callSitePointer.readByte(0); disp32 = callSitePointer.readInt(1); } return callSite.plus(RIP_CALL_INSTRUCTION_LENGTH).plus(disp32); } /** * Patches the offset operand of a 32-bit relative CALL instruction. * * @param tm the method containing the CALL instruction * @param callOffset the offset within the code of {@code targetMethod} of the CALL to be patched * @param target the absolute target address of the CALL * @return the target of the call prior to patching */ public static CodePointer fixupCall32Site(TargetMethod tm, int callOffset, CodePointer target) { CodePointer callSite = tm.codeAt(callOffset); if (!isPatchableCallSite(callSite)) { // Every call site that is fixed up here might also be patched later. To avoid failed patching, // check for alignment of call site also here. // TODO(cwi): This is a check that I would like to have, however, T1X does not ensure proper alignment yet when it stitches together templates that contain calls. // FatalError.unexpected(" invalid patchable call site: " + targetMethod + "+" + offset + " " + callSite.toHexString()); } long disp64 = target.toLong() - callSite.plus(RIP_CALL_INSTRUCTION_LENGTH).toLong(); int disp32 = (int) disp64; int oldDisp32; FatalError.check(disp64 == disp32, "Code displacement out of 32-bit range"); if (MaxineVM.isHosted()) { final byte[] code = tm.code(); oldDisp32 = (code[callOffset + 4] & 0xff) << 24 | (code[callOffset + 3] & 0xff) << 16 | (code[callOffset + 2] & 0xff) << 8 | (code[callOffset + 1] & 0xff) << 0; if (oldDisp32 != disp32) { code[callOffset] = (byte) RIP_CALL; code[callOffset + 1] = (byte) disp32; code[callOffset + 2] = (byte) (disp32 >> 8); code[callOffset + 3] = (byte) (disp32 >> 16); code[callOffset + 4] = (byte) (disp32 >> 24); } } else { final Pointer callSitePointer = callSite.toPointer(); oldDisp32 = (callSitePointer.readByte(4) & 0xff) << 24 | (callSitePointer.readByte(3) & 0xff) << 16 | (callSitePointer.readByte(2) & 0xff) << 8 | (callSitePointer.readByte(1) & 0xff) << 0; if (oldDisp32 != disp32) { callSitePointer.writeByte(0, (byte) RIP_CALL); callSitePointer.writeByte(1, (byte) disp32); callSitePointer.writeByte(2, (byte) (disp32 >> 8)); callSitePointer.writeByte(3, (byte) (disp32 >> 16)); callSitePointer.writeByte(4, (byte) (disp32 >> 24)); } } return callSite.plus(RIP_CALL_INSTRUCTION_LENGTH).plus(oldDisp32); } private static final int RIP_CALL_INSTRUCTION_LENGTH = 5; private static final int RIP_JMP_INSTRUCTION_LENGTH = 5; /** * Thread safe patching of the displacement field in a direct call. * * @return the target of the call prior to patching */ public static CodePointer mtSafePatchCallDisplacement(TargetMethod tm, CodePointer callSite, CodePointer target) { if (!isPatchableCallSite(callSite)) { throw FatalError.unexpected(" invalid patchable call site: " + callSite.toHexString()); } final Pointer callSitePointer = callSite.toPointer(); long disp64 = target.toLong() - callSite.plus(RIP_CALL_INSTRUCTION_LENGTH).toLong(); int disp32 = (int) disp64; FatalError.check(disp64 == disp32, "Code displacement out of 32-bit range"); int oldDisp32 = callSitePointer.readInt(1); if (oldDisp32 != disp64) { synchronized (PatchingLock) { // Just to prevent concurrent writing and invalidation to the same instruction cache line // (although the lock excludes ALL concurrent patching) callSitePointer.writeInt(1, disp32); // Don't need icache invalidation to be correct (see AMD64's Architecture Programmer Manual Vol.2, p173 on self-modifying code) } } return callSite.plus(RIP_CALL_INSTRUCTION_LENGTH).plus(oldDisp32); } /** * Patches a position in a target method with a direct jump to a given target address. * * @param tm the target method to be patched * @param pos the position in {@code tm} at which to apply the patch * @param target the target of the jump instruction being patched in */ public static void patchWithJump(TargetMethod tm, int pos, CodePointer target) { // We must be at a global safepoint to safely patch TargetMethods FatalError.check(VmOperation.atSafepoint(), "should only be patching entry points when at a safepoint"); final Pointer patchSite = tm.codeAt(pos).toPointer(); long disp64 = target.toLong() - patchSite.plus(RIP_JMP_INSTRUCTION_LENGTH).toLong(); int disp32 = (int) disp64; FatalError.check(disp64 == disp32, "Code displacement out of 32-bit range"); patchSite.writeByte(0, (byte) RIP_JMP); patchSite.writeByte(1, (byte) disp32); patchSite.writeByte(2, (byte) (disp32 >> 8)); patchSite.writeByte(3, (byte) (disp32 >> 16)); patchSite.writeByte(4, (byte) (disp32 >> 24)); } /** * Indicate with the instruction in a target method at a given position is a jump to a specified destination. * Used in particular for testing if the entry points of a target method were patched to jump to a trampoline. * * @param tm a target method * @param pos byte index relative to the start of the method to a call site * @param jumpTarget target to compare with the target of the assumed jump instruction * @return {@code true} if the instruction is a jump to the target, false otherwise */ public static boolean isJumpTo(TargetMethod tm, int pos, CodePointer jumpTarget) { final Pointer jumpSite = tm.codeAt(pos).toPointer(); if (jumpSite.readByte(0) == (byte) RIP_JMP) { final int disp32 = jumpSite.readInt(1); final Pointer target = jumpSite.plus(RIP_CALL_INSTRUCTION_LENGTH).plus(disp32); return jumpTarget.toPointer().equals(target); } return false; } // Disable instance creation. private AMD64TargetMethodUtil() { } @HOSTED_ONLY public static boolean atFirstOrLastInstruction(StackFrameCursor current) { // check whether the current ip is at the first instruction or a return // which means the stack pointer has not been adjusted yet (or has already been adjusted back) TargetMethod tm = current.targetMethod(); CodePointer entryPoint = tm.callEntryPoint.equals(CallEntryPoint.C_ENTRY_POINT) ? CallEntryPoint.C_ENTRY_POINT.in(tm) : CallEntryPoint.OPTIMIZED_ENTRY_POINT.in(tm); return entryPoint.equals(current.vmIP()) || current.stackFrameWalker().readByte(current.vmIP().toAddress(), 0) == RET; } @HOSTED_ONLY public static boolean acceptStackFrameVisitor(StackFrameCursor current, StackFrameVisitor visitor) { AdapterGenerator generator = AdapterGenerator.forCallee(current.targetMethod()); Pointer sp = current.sp(); // Only during a stack walk in the context of the Inspector can execution // be anywhere other than at a safepoint. if (atFirstOrLastInstruction(current) || (generator != null && generator.inPrologue(current.vmIP(), current.targetMethod()))) { sp = sp.minus(current.targetMethod().frameSize()); } StackFrameWalker stackFrameWalker = current.stackFrameWalker(); StackFrame stackFrame = new AMD64JavaStackFrame(stackFrameWalker.calleeStackFrame(), current.targetMethod(), current.vmIP().toPointer(), sp, sp); return visitor.visitFrame(stackFrame); } public static VMFrameLayout frameLayout(TargetMethod tm) { return new OptoStackFrameLayout(tm.frameSize(), true, AMD64.rsp); } /** * Advances the stack walker such that {@code current} becomes the callee. * * @param current the frame just visited by the current stack walk * @param csl the layout of the callee save area in {@code current} * @param csa the address of the callee save area in {@code current} */ public static void advance(StackFrameCursor current, CiCalleeSaveLayout csl, Pointer csa) { assert csa.isZero() == (csl == null); TargetMethod tm = current.targetMethod(); Pointer sp = current.sp(); Pointer ripPointer = sp.plus(tm.frameSize()); if (MaxineVM.isHosted()) { // Only during a stack walk in the context of the Inspector can execution // be anywhere other than at a safepoint. AdapterGenerator generator = AdapterGenerator.forCallee(current.targetMethod()); if (generator != null && generator.advanceIfInPrologue(current)) { return; } if (atFirstOrLastInstruction(current)) { ripPointer = sp; } } StackFrameWalker sfw = current.stackFrameWalker(); Pointer callerIP = sfw.readWord(ripPointer, 0).asPointer(); Pointer callerSP = ripPointer.plus(Word.size()); // Skip return instruction pointer on stack Pointer callerFP; if (!csa.isZero() && csl.contains(AMD64.rbp.number)) { // Read RBP from the callee save area callerFP = sfw.readWord(csa, csl.offsetOf(AMD64.rbp)).asPointer(); } else { // Propagate RBP unchanged callerFP = current.fp(); } current.setCalleeSaveArea(csl, csa); boolean wasDisabled = SafepointPoll.disable(); sfw.advance(callerIP, callerSP, callerFP); if (!wasDisabled) { SafepointPoll.enable(); } } public static Pointer returnAddressPointer(StackFrameCursor frame) { TargetMethod tm = frame.targetMethod(); Pointer sp = frame.sp(); return sp.plus(tm.frameSize()); } public static int callInstructionSize(byte[] code, int pos) { if ((code[pos] & 0xFF) == RIP_CALL) { return RIP_CALL_INSTRUCTION_SIZE; } if ((code[pos] & 0xff) == REG_CALL) { return 2; } if ((code[pos + 1] & 0xff) == REG_CALL) { return 3; } return -1; } }