1 | //===-- xray_riscv.cpp ----------------------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file is a part of XRay, a dynamic runtime instrumentation system. |
10 | // |
11 | // Implementation of RISC-V specific routines (32- and 64-bit). |
12 | // |
13 | //===----------------------------------------------------------------------===// |
14 | #include "sanitizer_common/sanitizer_common.h" |
15 | #include "xray_defs.h" |
16 | #include "xray_interface_internal.h" |
17 | #include <atomic> |
18 | |
19 | namespace __xray { |
20 | |
21 | // The machine codes for some instructions used in runtime patching. |
22 | enum PatchOpcodes : uint32_t { |
23 | PO_ADDI = 0x00000013, // addi rd, rs1, imm |
24 | PO_ADD = 0x00000033, // add rd, rs1, rs2 |
25 | PO_SW = 0x00002023, // sw rs2, imm(rs1) |
26 | PO_SD = 0x00003023, // sd rs2, imm(rs1) |
27 | PO_LUI = 0x00000037, // lui rd, imm |
28 | PO_OR = 0x00006033, // or rd, rs1, rs2 |
29 | PO_SLLI = 0x00001013, // slli rd, rs1, shamt |
30 | PO_JALR = 0x00000067, // jalr rd, rs1 |
31 | PO_LW = 0x00002003, // lw rd, imm(rs1) |
32 | PO_LD = 0x00003003, // ld rd, imm(rs1) |
33 | PO_J = 0x0000006f, // jal imm |
34 | PO_NOP = PO_ADDI, // addi x0, x0, 0 |
35 | }; |
36 | |
37 | enum RegNum : uint32_t { |
38 | RN_X0 = 0, |
39 | RN_RA = 1, |
40 | RN_SP = 2, |
41 | RN_T1 = 6, |
42 | RN_A0 = 10, |
43 | }; |
44 | |
45 | static inline uint32_t encodeRTypeInstruction(uint32_t Opcode, uint32_t Rs1, |
46 | uint32_t Rs2, uint32_t Rd) { |
47 | return Rs2 << 20 | Rs1 << 15 | Rd << 7 | Opcode; |
48 | } |
49 | |
50 | static inline uint32_t encodeITypeInstruction(uint32_t Opcode, uint32_t Rs1, |
51 | uint32_t Rd, uint32_t Imm) { |
52 | return Imm << 20 | Rs1 << 15 | Rd << 7 | Opcode; |
53 | } |
54 | |
55 | static inline uint32_t encodeSTypeInstruction(uint32_t Opcode, uint32_t Rs1, |
56 | uint32_t Rs2, uint32_t Imm) { |
57 | uint32_t ImmMSB = (Imm & 0xfe0) << 20; |
58 | uint32_t ImmLSB = (Imm & 0x01f) << 7; |
59 | return ImmMSB | Rs2 << 20 | Rs1 << 15 | ImmLSB | Opcode; |
60 | } |
61 | |
62 | static inline uint32_t encodeUTypeInstruction(uint32_t Opcode, uint32_t Rd, |
63 | uint32_t Imm) { |
64 | return Imm << 12 | Rd << 7 | Opcode; |
65 | } |
66 | |
67 | static inline uint32_t encodeJTypeInstruction(uint32_t Opcode, uint32_t Rd, |
68 | uint32_t Imm) { |
69 | uint32_t ImmMSB = (Imm & 0x100000) << 11; |
70 | uint32_t ImmLSB = (Imm & 0x7fe) << 20; |
71 | uint32_t Imm11 = (Imm & 0x800) << 9; |
72 | uint32_t Imm1912 = (Imm & 0xff000); |
73 | return ImmMSB | ImmLSB | Imm11 | Imm1912 | Rd << 7 | Opcode; |
74 | } |
75 | |
76 | static uint32_t hi20(uint32_t val) { return (val + 0x800) >> 12; } |
77 | static uint32_t lo12(uint32_t val) { return val & 0xfff; } |
78 | |
79 | static inline bool patchSled(const bool Enable, const uint32_t FuncId, |
80 | const XRaySledEntry &Sled, |
81 | void (*TracingHook)()) XRAY_NEVER_INSTRUMENT { |
82 | // When |Enable| == true, |
83 | // We replace the following compile-time stub (sled): |
84 | // |
85 | // xray_sled_n: |
86 | // J .tmpN |
87 | // 21 or 33 C.NOPs (42 or 66 bytes) |
88 | // .tmpN |
89 | // |
90 | // With one of the following runtime patches: |
91 | // |
92 | // xray_sled_n (32-bit): |
93 | // addi sp, sp, -16 ;create stack frame |
94 | // sw ra, 12(sp) ;save return address |
95 | // sw a0, 8(sp) ;save register a0 |
96 | // lui ra, %hi(__xray_FunctionEntry/Exit) |
97 | // addi ra, ra, %lo(__xray_FunctionEntry/Exit) |
98 | // lui a0, %hi(function_id) |
99 | // addi a0, a0, %lo(function_id) ;pass function id |
100 | // jalr ra ;call Tracing hook |
101 | // lw a0, 8(sp) ;restore register a0 |
102 | // lw ra, 12(sp) ;restore return address |
103 | // addi sp, sp, 16 ;delete stack frame |
104 | // |
105 | // xray_sled_n (64-bit): |
106 | // addi sp, sp, -32 ;create stack frame |
107 | // sd ra, 24(sp) ;save return address |
108 | // sd a0, 16(sp) ;save register a0 |
109 | // sd t1, 8(sp) ;save register t1 |
110 | // lui t1, %highest(__xray_FunctionEntry/Exit) |
111 | // addi t1, t1, %higher(__xray_FunctionEntry/Exit) |
112 | // slli t1, t1, 32 |
113 | // lui ra, ra, %hi(__xray_FunctionEntry/Exit) |
114 | // addi ra, ra, %lo(__xray_FunctionEntry/Exit) |
115 | // add ra, t1, ra |
116 | // lui a0, %hi(function_id) |
117 | // addi a0, a0, %lo(function_id) ;pass function id |
118 | // jalr ra ;call Tracing hook |
119 | // ld t1, 8(sp) ;restore register t1 |
120 | // ld a0, 16(sp) ;restore register a0 |
121 | // ld ra, 24(sp) ;restore return address |
122 | // addi sp, sp, 32 ;delete stack frame |
123 | // |
124 | // Replacement of the first 4-byte instruction should be the last and atomic |
125 | // operation, so that the user code which reaches the sled concurrently |
126 | // either jumps over the whole sled, or executes the whole sled when the |
127 | // latter is ready. |
128 | // |
129 | // When |Enable|==false, we set back the first instruction in the sled to be |
130 | // J 44 bytes (rv32) |
131 | // J 68 bytes (rv64) |
132 | |
133 | uint32_t *Address = reinterpret_cast<uint32_t *>(Sled.address()); |
134 | if (Enable) { |
135 | #if __riscv_xlen == 64 |
136 | // If the ISA is RV64, the Tracing Hook needs to be typecast to a 64 bit |
137 | // value. |
138 | uint32_t LoTracingHookAddr = lo12(reinterpret_cast<uint64_t>(TracingHook)); |
139 | uint32_t HiTracingHookAddr = hi20(reinterpret_cast<uint64_t>(TracingHook)); |
140 | uint32_t HigherTracingHookAddr = |
141 | lo12((reinterpret_cast<uint64_t>(TracingHook) + 0x80000000) >> 32); |
142 | uint32_t HighestTracingHookAddr = |
143 | hi20((reinterpret_cast<uint64_t>(TracingHook) + 0x80000000) >> 32); |
144 | #elif __riscv_xlen == 32 |
145 | // We typecast the Tracing Hook to a 32 bit value for RV32 |
146 | uint32_t LoTracingHookAddr = lo12(reinterpret_cast<uint32_t>(TracingHook)); |
147 | uint32_t HiTracingHookAddr = hi20((reinterpret_cast<uint32_t>(TracingHook)); |
148 | #endif |
149 | uint32_t LoFunctionID = lo12(val: FuncId); |
150 | uint32_t HiFunctionID = hi20(val: FuncId); |
151 | |
152 | // The sled that is patched in for RISCV64 defined below. We need the entire |
153 | // sleds corresponding to both ISAs to be protected by defines because the |
154 | // first few instructions are all different, because we store doubles in |
155 | // case of RV64 and store words for RV32. Subsequently, we have LUI - and in |
156 | // case of RV64, we need extra instructions from this point on, so we see |
157 | // differences in addresses to which instructions are stored. |
158 | size_t Idx = 1U; |
159 | const uint32_t XLenBytes = __riscv_xlen / 8; |
160 | #if __riscv_xlen == 64 |
161 | const uint32_t LoadOp = PatchOpcodes::PO_LD; |
162 | const uint32_t StoreOp = PatchOpcodes::PO_SD; |
163 | #elif __riscv_xlen == 32 |
164 | const uint32_t LoadOp = PatchOpcodes::PO_LW; |
165 | const uint32_t StoreOp = PatchOpcodes::PO_SW; |
166 | #endif |
167 | |
168 | Address[Idx++] = encodeSTypeInstruction(StoreOp, RegNum::RN_SP, |
169 | RegNum::RN_RA, 3 * XLenBytes); |
170 | Address[Idx++] = encodeSTypeInstruction(StoreOp, RegNum::RN_SP, |
171 | RegNum::RN_A0, 2 * XLenBytes); |
172 | |
173 | #if __riscv_xlen == 64 |
174 | Address[Idx++] = encodeSTypeInstruction(StoreOp, RegNum::RN_SP, |
175 | RegNum::RN_T1, XLenBytes); |
176 | Address[Idx++] = encodeUTypeInstruction(PatchOpcodes::PO_LUI, RegNum::RN_T1, |
177 | HighestTracingHookAddr); |
178 | Address[Idx++] = |
179 | encodeITypeInstruction(PatchOpcodes::PO_ADDI, RegNum::RN_T1, |
180 | RegNum::RN_T1, HigherTracingHookAddr); |
181 | Address[Idx++] = encodeITypeInstruction(PatchOpcodes::PO_SLLI, |
182 | RegNum::RN_T1, RegNum::RN_T1, 32); |
183 | #endif |
184 | Address[Idx++] = encodeUTypeInstruction(PatchOpcodes::PO_LUI, RegNum::RN_RA, |
185 | HiTracingHookAddr); |
186 | Address[Idx++] = encodeITypeInstruction( |
187 | PatchOpcodes::PO_ADDI, RegNum::RN_RA, RegNum::RN_RA, LoTracingHookAddr); |
188 | #if __riscv_xlen == 64 |
189 | Address[Idx++] = encodeRTypeInstruction(PatchOpcodes::PO_ADD, RegNum::RN_RA, |
190 | RegNum::RN_T1, RegNum::RN_RA); |
191 | #endif |
192 | Address[Idx++] = encodeUTypeInstruction(Opcode: PatchOpcodes::PO_LUI, Rd: RegNum::RN_A0, |
193 | Imm: HiFunctionID); |
194 | Address[Idx++] = encodeITypeInstruction( |
195 | Opcode: PatchOpcodes::PO_ADDI, Rs1: RegNum::RN_A0, Rd: RegNum::RN_A0, Imm: LoFunctionID); |
196 | Address[Idx++] = encodeITypeInstruction(Opcode: PatchOpcodes::PO_JALR, |
197 | Rs1: RegNum::RN_RA, Rd: RegNum::RN_RA, Imm: 0); |
198 | |
199 | #if __riscv_xlen == 64 |
200 | Address[Idx++] = |
201 | encodeITypeInstruction(LoadOp, RegNum::RN_SP, RegNum::RN_T1, XLenBytes); |
202 | #endif |
203 | Address[Idx++] = encodeITypeInstruction(LoadOp, RegNum::RN_SP, |
204 | RegNum::RN_A0, 2 * XLenBytes); |
205 | Address[Idx++] = encodeITypeInstruction(LoadOp, RegNum::RN_SP, |
206 | RegNum::RN_RA, 3 * XLenBytes); |
207 | Address[Idx++] = encodeITypeInstruction( |
208 | Opcode: PatchOpcodes::PO_ADDI, Rs1: RegNum::RN_SP, Rd: RegNum::RN_SP, Imm: 4 * XLenBytes); |
209 | |
210 | uint32_t CreateStackSpace = encodeITypeInstruction( |
211 | Opcode: PatchOpcodes::PO_ADDI, Rs1: RegNum::RN_SP, Rd: RegNum::RN_SP, Imm: -4 * XLenBytes); |
212 | |
213 | std::atomic_store_explicit( |
214 | a: reinterpret_cast<std::atomic<uint32_t> *>(Address), i: CreateStackSpace, |
215 | m: std::memory_order_release); |
216 | } else { |
217 | uint32_t CreateBranch = encodeJTypeInstruction( |
218 | // Jump distance is different in both ISAs due to difference in size of |
219 | // sleds |
220 | #if __riscv_xlen == 64 |
221 | PatchOpcodes::PO_J, RegNum::RN_X0, |
222 | 68); // jump encodes an offset of 68 |
223 | #elif __riscv_xlen == 32 |
224 | PatchOpcodes::PO_J, RegNum::RN_X0, |
225 | 44); // jump encodes an offset of 44 |
226 | #endif |
227 | std::atomic_store_explicit( |
228 | reinterpret_cast<std::atomic<uint32_t> *>(Address), CreateBranch, |
229 | std::memory_order_release); |
230 | } |
231 | return true; |
232 | } |
233 | |
234 | bool patchFunctionEntry(const bool Enable, const uint32_t FuncId, |
235 | const XRaySledEntry &Sled, |
236 | const XRayTrampolines &Trampolines, |
237 | bool LogArgs) XRAY_NEVER_INSTRUMENT { |
238 | // We don't support logging argument at this moment, so we always |
239 | // use EntryTrampoline. |
240 | return patchSled(Enable, FuncId, Sled, TracingHook: Trampolines.EntryTrampoline); |
241 | } |
242 | |
243 | bool patchFunctionExit( |
244 | const bool Enable, const uint32_t FuncId, const XRaySledEntry &Sled, |
245 | const XRayTrampolines &Trampolines) XRAY_NEVER_INSTRUMENT { |
246 | return patchSled(Enable, FuncId, Sled, TracingHook: Trampolines.ExitTrampoline); |
247 | } |
248 | |
249 | bool patchFunctionTailExit( |
250 | const bool Enable, const uint32_t FuncId, const XRaySledEntry &Sled, |
251 | const XRayTrampolines &Trampolines) XRAY_NEVER_INSTRUMENT { |
252 | return patchSled(Enable, FuncId, Sled, TracingHook: Trampolines.TailExitTrampoline); |
253 | } |
254 | |
255 | bool patchCustomEvent(const bool Enable, const uint32_t FuncId, |
256 | const XRaySledEntry &Sled) XRAY_NEVER_INSTRUMENT { |
257 | return false; |
258 | } |
259 | |
260 | bool patchTypedEvent(const bool Enable, const uint32_t FuncId, |
261 | const XRaySledEntry &Sled) XRAY_NEVER_INSTRUMENT { |
262 | return false; |
263 | } |
264 | } // namespace __xray |
265 | |
266 | extern "C" void __xray_ArgLoggerEntry() XRAY_NEVER_INSTRUMENT {} |
267 | |