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