agiledragon / gomonkey

gomonkey is a library to make monkey patching in unit tests easy
MIT License
2k stars 179 forks source link

Can you give some suggestion if I want to support an arm64 system #39

Open cist opened 3 years ago

cist commented 3 years ago

hello, I think the gomonkey is so cool, But in reality, I need Gomonkey to support the ARM64 system, so could you give me some suggestions on how I can adapt it? thanks.

agiledragon commented 3 years ago

It is more complex than amd64. The length of the arm64 instruction is only 32 bits, and the instruction cannot store such a large immediate value. You must first load the parameter from the stack to the r27 register, and then b r27. Remember to replace instructions with instruction codes.

sirkon commented 3 years ago

I can assemble seemingly correct machine code, something like what

MOVZ x27, <v0>
MOVK x27, <v1>, LSL #16
MOVK x27, <v2>, LSL #32
LDR  x28, [x27]
BR   x28

produces.

Yet patching just doesn't work: got permission denied. Tried that printf '\x07' without much success.

sirkon commented 3 years ago
// jmp_arm64.s

package gomonkey

import "unsafe"

// Reference instruction set:
//
// MOVZ x27, 0xffff, LSL #16
// MOVK x27, 0x1234, LSL #32
// LDR  x28, [x27]
// BR   x28
//
// Copy test data into so.s and then
//
//   clang -c so.s -o so.o
//   objdump-d so.o
//
// You will see something like
//
// 0000000000000000 <ltmp0>:
//     0: fb ff bf d2    mov  x27, #4294901760
//     4: 9b 46 c2 f2    movk x27, #4660, lsl #32
//     8: 7c 03 40 f9    ldr  x28, [x27]
//     c: 80 03 1f d6    br   x28

// Assembles a jump to a function value
func buildJmpDirective(to uintptr) []byte {
    var res []byte

    // as you probably know there's no generic way to set a direct
    // ("immediate") 64-bit value in one instruction. The easiest
    // method is to split qword into 4 words. In our case, we can
    // just map a number into [4]uint and then iterate over nthem
    var buf [4]uint16
    *(*uint64)(unsafe.Pointer(&buf[0])) = uint64(to)

    // now assemble a sequence of `MOVx x0, <vi>, LSL i*16 instructions,
    // where the first of the sequence is
    //   MOVZ x27, vN, LSL #16*N
    // and the rest are
    //   MOVK x27, vi, LSL #18*i
    // where vX denotes a word that is not zero and X is its index in
    // their sequence
    //
    // Example: if we are to set value 0xffff_0000_00ff_0000
    // then the sequence of instructions is
    //    MOVZ x27, 0x00ff, LSL #16
    //    MOVK x27, 0xffff, LSL #48
    var thereWerentNonZeroes bool
    for i := range buf {
        if buf[i] == 0 {
            continue
        }
        if !thereWerentNonZeroes {
            res = append(res, armv8ImmediateMovz(27, 0, buf[i])...)
            thereWerentNonZeroes = true
        } else {
            res = append(res, arm8ImmediateMovk(27, i, buf[i])...)
        }
    }

    // assemble the rest:
    //  LDR x28, [x27]
    //  BR  x28
    res = append(res, 0x7c, 0x03, 0x40, 0xf9)
    res = append(res, 0x80, 0x03, 0x1f, 0xd6)

    return res
}

func armv8ImmediateMovz(regN, shift int, value uint16) []byte {
    return movx(0b10, regN, shift, value)
}

func arm8ImmediateMovk(regN, shift int, value uint16) []byte {
    return movx(0b11, regN, shift, value)
}

func movx(movX, regN, shift int, value uint16) []byte {
    var val uint32

    val = 0b10010010 << 24
    val |= 0b10000000 << 16
    val |= uint32(movX&0x3) << 29
    val |= uint32(shift&3) << 21
    val |= uint32(value) << 5
    val |= uint32(regN)

    var res [4]byte
    *(*uint32)(unsafe.Pointer(&res[0])) = val

    return res[:]
}
sirkon commented 3 years ago

It looks like this is the reason: https://developer.apple.com/forums/thread/672804

A bit different, but I guess the root of problem is in it.

agiledragon commented 3 years ago

Arm64 is fully supported in the v2.2.0.

arfan commented 2 years ago

I still get permission denied in M1

congziqi77 commented 2 years ago

even if I use the latest version,it is also permission denied in M1

agiledragon commented 11 months ago

v2.11.0 has been released!