1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "berberis/code_gen_lib/code_gen_lib.h"
18
19 #include "berberis/assembler/machine_code.h"
20 #include "berberis/assembler/x86_64.h"
21 #include "berberis/base/bit_util.h"
22 #include "berberis/base/checks.h"
23 #include "berberis/base/config.h"
24 #include "berberis/calling_conventions/calling_conventions_x86_64.h"
25 #include "berberis/code_gen_lib/gen_adaptor.h"
26 #include "berberis/guest_state/guest_addr.h"
27 #include "berberis/guest_state/guest_state.h"
28 #include "berberis/instrument/trampolines.h"
29 #include "berberis/kernel_api/run_guest_syscall.h"
30 #include "berberis/runtime_primitives/host_code.h"
31 #include "berberis/runtime_primitives/translation_cache.h"
32
33 namespace berberis {
34
35 namespace {
36
37 // Emits checks for pending emulated signals. If pending signals present
38 // generated code returns to the main dispatcher loop to handle them.
39 // To ensure we don't loop endlessly in generated code, it must be called on
40 // every region exit (loops within regions must be taken care of separately).
41 // Thus we call it in EmitDirectDispatch and EmitIndirectDispatch.
EmitCheckSignalsAndMaybeReturn(x86_64::Assembler * as)42 void EmitCheckSignalsAndMaybeReturn(x86_64::Assembler* as) {
43 // C++:
44 // std::atomic_int_least8_t pending_signals_status;
45 // uint8_t status = pending_signals_status.load(std::memory_order_acquire);
46 // if (status == kPendingSignalsPresent) { ... }
47 // x86_64 asm:
48 // cmpb pending_signals_status, kPendingSignalsPresent
49 const size_t offset = offsetof(ThreadState, pending_signals_status);
50 as->Cmpb({.base = x86_64::Assembler::rbp, .disp = offset}, kPendingSignalsPresent);
51 as->Jcc(x86_64::Assembler::Condition::kEqual, kEntryExitGeneratedCode);
52 }
53
54 } // namespace
55
GenTrampolineAdaptor(MachineCode * mc,GuestAddr pc,HostCode marshall,const void * callee,const char * name)56 void GenTrampolineAdaptor(MachineCode* mc,
57 GuestAddr pc,
58 HostCode marshall,
59 const void* callee,
60 const char* name) {
61 x86_64::Assembler as(mc);
62
63 // Update insn_addr to the current PC. This way, code generated by this
64 // function does not require insn_addr to be up to date upon entry. Note that
65 // the trampoline that we call requires insn_addr to be up to date.
66 as.Movq(as.rdi, pc);
67 as.Movq({.base = as.rbp, .disp = offsetof(ThreadState, cpu.insn_addr)}, as.rdi);
68 as.Movq({.base = as.rbp, .disp = offsetof(ThreadState, residence)}, kOutsideGeneratedCode);
69
70 if (kInstrumentTrampolines) {
71 if (auto instrument = GetOnTrampolineCall(name)) {
72 as.Movq(as.rdi, as.rbp);
73 as.Movq(as.rsi, reinterpret_cast<intptr_t>(name));
74 as.Call(AsHostCode(instrument));
75 }
76 }
77
78 // void Trampoline(void*, ThreadState*);
79 as.Movq(as.rdi, reinterpret_cast<intptr_t>(callee));
80 as.Movq(as.rsi, as.rbp);
81 as.Call(marshall);
82
83 if (kInstrumentTrampolines) {
84 if (auto instrument = GetOnTrampolineReturn(name)) {
85 as.Movq(as.rdi, as.rbp);
86 as.Movq(as.rsi, reinterpret_cast<intptr_t>(name));
87 as.Call(AsHostCode(instrument));
88 }
89 }
90
91 // j ra
92 // Prefer rdx, since rax/rcx will result in extra moves inside EmitIndirectDispatch.
93 as.Movq(as.rdx, {.base = as.rbp, .disp = offsetof(ThreadState, cpu.x[RA])});
94 // We are returning to generated code.
95 as.Movq({.base = as.rbp, .disp = offsetof(ThreadState, residence)}, kInsideGeneratedCode);
96 EmitIndirectDispatch(&as, as.rdx);
97 as.Finalize();
98 }
99
EmitSyscall(x86_64::Assembler * as,GuestAddr pc)100 void EmitSyscall(x86_64::Assembler* as, GuestAddr pc) {
101 // We may run guest signal handler during the syscall so insn_addr needs to be synched.
102 as->Movq(as->rdi, pc);
103 as->Movq({.base = as->rbp, .disp = offsetof(ThreadState, cpu.insn_addr)}, as->rdi);
104 as->Movq({.base = as->rbp, .disp = offsetof(ThreadState, residence)}, kOutsideGeneratedCode);
105
106 // void RunGuestSyscall(ThreadState*);
107 as->Movq(as->rdi, as->rbp);
108 as->Call(AsHostCode(RunGuestSyscall));
109
110 // We are returning to generated code.
111 as->Movq({.base = as->rbp, .disp = offsetof(ThreadState, residence)}, kInsideGeneratedCode);
112 // Advance to the next instruction.
113 // TODO(b/161722184): if syscall is interrupted by signal, signal handler might overwrite the
114 // insn_addr, so incrementing insn_addr here may be incorrect. This problem also exists in the
115 // interpreter. On the other hand syscalls can only be interruprted by asynchroneous signals which
116 // are unlikely to overwrite insn_addr.
117 EmitDirectDispatch(as, pc + 4, /*check_pending_signals=*/true);
118 }
119
EmitDirectDispatch(x86_64::Assembler * as,GuestAddr pc,bool check_pending_signals)120 void EmitDirectDispatch(x86_64::Assembler* as, GuestAddr pc, bool check_pending_signals) {
121 // insn_addr is passed between regions in rax.
122 as->Movq(as->rax, pc);
123
124 if (!config::kLinkJumpsBetweenRegions) {
125 as->Jmp(kEntryExitGeneratedCode);
126 return;
127 }
128
129 if (check_pending_signals) {
130 EmitCheckSignalsAndMaybeReturn(as);
131 }
132
133 CHECK_EQ(pc & 0xffff000000000000, 0);
134 as->Movq(as->rcx,
135 reinterpret_cast<uint64_t>(TranslationCache::GetInstance()->GetHostCodePtr(pc)));
136 as->Jmpq({.base = as->rcx});
137 }
138
EmitExitGeneratedCode(x86_64::Assembler * as,x86_64::Assembler::Register target)139 void EmitExitGeneratedCode(x86_64::Assembler* as, x86_64::Assembler::Register target) {
140 // insn_addr is passed between regions in rax.
141 if (target != as->rax) {
142 as->Movq(as->rax, target);
143 }
144
145 as->Jmp(kEntryExitGeneratedCode);
146 }
147
EmitIndirectDispatch(x86_64::Assembler * as,x86_64::Assembler::Register target)148 void EmitIndirectDispatch(x86_64::Assembler* as, x86_64::Assembler::Register target) {
149 // insn_addr is passed between regions in rax.
150 as->Movq(as->rax, target);
151
152 if (!config::kLinkJumpsBetweenRegions) {
153 as->Jmp(kEntryExitGeneratedCode);
154 return;
155 }
156
157 // rax and rcx are used as scratches.
158 if (target == as->rax || target == as->rcx) {
159 as->Movq(as->rdx, target);
160 target = as->rdx;
161 }
162
163 EmitCheckSignalsAndMaybeReturn(as);
164
165 auto main_table_ptr = TranslationCache::GetInstance()->main_table_ptr();
166
167 as->Shrq(as->rax, int8_t{24});
168 as->Andl(as->rax, 0xffffff);
169 as->Movq(as->rcx, reinterpret_cast<uint64_t>(main_table_ptr));
170 as->Movq(as->rcx, {.base = as->rcx, .index = as->rax, .scale = x86_64::Assembler::kTimesEight});
171
172 as->Movq(as->rax, target);
173 as->Andq(as->rax, 0xffffff);
174 as->Movq(as->rcx, {.base = as->rcx, .index = as->rax, .scale = x86_64::Assembler::kTimesEight});
175
176 // insn_addr is passed between regions in rax.
177 as->Movq(as->rax, target);
178
179 as->Jmp(as->rcx);
180 }
181
EmitAllocStackFrame(x86_64::Assembler * as,uint32_t frame_size)182 void EmitAllocStackFrame(x86_64::Assembler* as, uint32_t frame_size) {
183 if (frame_size > config::kFrameSizeAtTranslatedCode) {
184 uint32_t extra_size = AlignUp(frame_size - config::kFrameSizeAtTranslatedCode,
185 x86_64::CallingConventions::kStackAlignmentBeforeCall);
186 as->Subq(as->rsp, extra_size);
187 }
188 }
189
EmitFreeStackFrame(x86_64::Assembler * as,uint32_t frame_size)190 void EmitFreeStackFrame(x86_64::Assembler* as, uint32_t frame_size) {
191 if (frame_size > config::kFrameSizeAtTranslatedCode) {
192 uint32_t extra_size = AlignUp(frame_size - config::kFrameSizeAtTranslatedCode,
193 x86_64::CallingConventions::kStackAlignmentBeforeCall);
194 as->Addq(as->rsp, extra_size);
195 }
196 }
197 } // namespace berberis
198