-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathasm_amd64.go
More file actions
118 lines (93 loc) · 3.2 KB
/
asm_amd64.go
File metadata and controls
118 lines (93 loc) · 3.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package redefine
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"math"
"unsafe"
"golang.org/x/arch/x86/x86asm"
)
const (
opcodeCALLabs = 0xff // CALL abs32
opcodeCALLrel = 0xe8 // CALL rel32
opcodeINT3 = 0xcc
opcodeJMP = 0xe9 // JMP rel32
)
// Cloned functions need to be within range of a signed 32-bit JMP.
const idealCloneDistance = 0
const maxCloneDistance = math.MaxInt32
func insertJump(buf []byte, dest uintptr) error {
const instructionSize = 5 // 1 byte opcode + 4 byte address
// Make sure the buffer has enough space. As far as I can tell, there
// should always be at least 32 bytes to work with, but it doesn't hurt
// to check.
if len(buf) < instructionSize {
return errors.New("buffer too small for jump instruction")
}
// Address to jump from
src := uintptr(unsafe.Pointer(unsafe.SliceData(buf))) + instructionSize
buf[0] = opcodeJMP
diff32 := int32(dest - src)
binary.LittleEndian.PutUint32(buf[1:], uint32(diff32))
// Pad the rest of the buffer with INT3 opcodes to match what the compiler does
for i := instructionSize; i < len(buf); i++ {
buf[i] = opcodeINT3
}
return nil
}
// relocateFunc copies machine instructions from src into dest translating
// relative instructions as it goes. dest must be at least as large as src.
//
// The data underlying the slices is assumed to be the same address the code
// would execute from.
func relocateFunc(src, dest []byte) ([]byte, error) {
dest = dest[:len(src)]
for i := 0; i < len(src); {
instruction, err := x86asm.Decode(src[i:], 64)
if err != nil {
return nil, fmt.Errorf("decode error at offset %d: %w", i, err)
}
copy(dest[i:], src[i:i+instruction.Len])
if instruction.PCRel > 0 {
err = fixPCRelAddress(instruction, src[i:i+instruction.Len], dest[i:i+instruction.Len])
if err != nil {
return nil, err
}
}
i += instruction.Len
}
return dest, nil
}
func fixPCRelAddress(inst x86asm.Inst, src, dest []byte) error {
srcPC := uintptr(unsafe.Pointer(unsafe.SliceData(src))) + uintptr(len(src))
destPC := uintptr(unsafe.Pointer(unsafe.SliceData(dest))) + uintptr(len(dest))
switch inst.PCRel {
case 4:
disp := int32(binary.LittleEndian.Uint32(src[inst.PCRelOff:]))
newDisp := (int64(srcPC) + int64(disp)) - int64(destPC)
if newDisp < math.MinInt32 || newDisp > math.MaxInt32 {
return fmt.Errorf("error at address srcPC=0x%x destPC=0x%x: unable to translate relative address (%d overflows int32)", srcPC, destPC, newDisp)
}
binary.LittleEndian.PutUint32(dest[inst.PCRelOff:], uint32(newDisp))
case 1:
// Ignore 1-byte relative addresses because their most likely jumps inside the function.
default:
return fmt.Errorf("unsupported relative address size: %d", inst.PCRel)
}
return nil
}
func disassemble(code []byte) (string, error) {
var buf bytes.Buffer
baseAddr := uintptr(unsafe.Pointer(unsafe.SliceData(code)))
for i := 0; i < len(code); {
instruction, err := x86asm.Decode(code[i:], 64)
if err != nil {
return "", fmt.Errorf("decode error at offset %d: %w", i, err)
}
fmt.Fprintf(&buf, "0x%08x\t%-20s\t%s\n", baseAddr+uintptr(i), hex.EncodeToString(code[i:i+instruction.Len]), instruction.String())
i += instruction.Len
}
return buf.String(), nil
}