1 /*
2 * Copyright (C) 2014 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_32.h"
21 #include "berberis/base/bit_util.h"
22 #include "berberis/base/config.h"
23 #include "berberis/calling_conventions/calling_conventions_x86_32.h"
24 #include "berberis/code_gen_lib/code_gen_lib_arch.h"
25 #include "berberis/guest_state/guest_addr.h"
26 #include "berberis/guest_state/guest_state.h"
27 #include "berberis/instrument/trampolines.h"
28 #include "berberis/runtime_primitives/host_code.h"
29 #include "berberis/runtime_primitives/translation_cache.h"
30
31 namespace berberis {
32
33 namespace x86_32 {
34
35 namespace {
36
37 // State register pointer must be callee saved.
38 // Use of EBP allows shorter context read instructions.
39 constexpr Assembler::Register kStateRegister = Assembler::ebp;
40
41 // Emitted code checks if some emulated signal is pending. If yes,
42 // it returns to the main dispatcher to handle the signal.
43 // To ensure we don't loop endlessly in generated code without checks
44 // for pending signals, this must be called on every exit from region (given
45 // there are no loops in regions). Thus we call it in EmitJump for static
46 // branches out of the regions and EmitDispatch for dynamic ones.
EmitCheckSignalsAndMaybeReturn(Assembler * as)47 void EmitCheckSignalsAndMaybeReturn(Assembler* as) {
48 // C++:
49 // std::atomic_int_least8_t pending_signals_status;
50 // uint8_t status = pending_signals_status.load(std::memory_order_acquire);
51 // if (status == kPendingSignalsPresent) { ... }
52 // x86_32 asm:
53 // cmpb pending_signals_status, kPendingSignalsPresent
54 const size_t offset = offsetof(ThreadState, pending_signals_status);
55 as->Cmpb({.base = kStateRegister, .disp = offset}, kPendingSignalsPresent);
56 as->Jcc(Assembler::Condition::kEqual, kEntryExitGeneratedCode);
57 }
58
59 // The offset of insn_addr is hard-coded in runtime_library_x86_32.S. The
60 // static_assert below is to ensure that the offset is still as expected.
61 static_assert(offsetof(ThreadState, cpu.insn_addr) == 0x48, "");
62
EmitDispatch(Assembler * as,Assembler::Register target)63 void EmitDispatch(Assembler* as, Assembler::Register target) {
64 // We are carrying target over in EAX, but we also need it in another
65 // temporary register that we'll clobber during mapping to the host address.
66 Assembler::Register reg1{Assembler::no_register};
67 if (target == Assembler::eax) {
68 reg1 = Assembler::ecx;
69 as->Movl(reg1, target);
70 } else {
71 reg1 = target;
72 as->Movl(Assembler::eax, target);
73 }
74
75 // Allocate another temporary register.
76 Assembler::Register reg2 = reg1 == Assembler::ecx ? Assembler::edx : Assembler::ecx;
77
78 if (!config::kLinkJumpsBetweenRegions) {
79 as->Jmp(kEntryExitGeneratedCode);
80 return;
81 }
82
83 EmitCheckSignalsAndMaybeReturn(as);
84
85 auto* translation_cache = TranslationCache::GetInstance();
86 auto main_table_ptr = translation_cache->main_table_ptr();
87
88 // eax, reg1: guest pc
89 //
90 // movzwl %eax,%reg2
91 // shr $0x10,%reg1
92 // mov main_table_ptr(,%reg1,4),%reg1
93 // jmp *(%reg1,%reg2,4)
94 as->Movzxwl(reg2, Assembler::eax);
95 as->Shrl(reg1, int8_t{16});
96 as->Movl(
97 reg1,
98 {.index = reg1, .scale = Assembler::kTimesFour, .disp = bit_cast<int32_t>(main_table_ptr)});
99 as->Jmpl({.base = reg1, .index = reg2, .scale = Assembler::kTimesFour});
100 }
101
GenTrampolineAdaptor(MachineCode * mc,GuestAddr pc,HostCode marshall,const void * callee,const char * name)102 void GenTrampolineAdaptor(MachineCode* mc,
103 GuestAddr pc,
104 HostCode marshall,
105 const void* callee,
106 const char* name) {
107 Assembler as(mc);
108 // void Trampoline(void*, ThreadState*);
109 // void LogTrampoline(ThreadState*, const char*);
110 EmitAllocStackFrame(&as, 8);
111
112 // Update insn_addr to the current PC. This way, code generated by this
113 // function does not require insn_addr to be up to date upon entry. Note that
114 // the trampoline that we call requires insn_addr to be up to date.
115 as.Movl({.base = kStateRegister, .disp = offsetof(ThreadState, cpu.insn_addr)}, pc);
116 as.Movl({.base = kStateRegister, .disp = offsetof(ThreadState, residence)},
117 kOutsideGeneratedCode);
118
119 if (kInstrumentTrampolines) {
120 if (auto instrument = GetOnTrampolineCall(name)) {
121 as.Movl({.base = as.esp}, kStateRegister);
122 as.Movl({.base = as.esp, .disp = 4}, bit_cast<int32_t>(name));
123 as.Call(AsHostCode(instrument));
124 }
125 }
126
127 as.Movl({.base = as.esp}, reinterpret_cast<uintptr_t>(callee));
128 as.Movl({.base = as.esp, .disp = 4}, kStateRegister);
129 as.Call(marshall);
130
131 if (kInstrumentTrampolines) {
132 if (auto instrument = GetOnTrampolineReturn(name)) {
133 as.Movl({.base = as.esp}, kStateRegister);
134 as.Movl({.base = as.esp, .disp = 4}, bit_cast<int32_t>(name));
135 as.Call(AsHostCode(instrument));
136 }
137 }
138
139 EmitFreeStackFrame(&as, 8);
140 // jump to guest return address
141 as.Movl(as.eax, {.base = kStateRegister, .disp = kReturnAddressRegisterOffset});
142 // We are returning to generated code.
143 as.Movl({.base = kStateRegister, .disp = offsetof(ThreadState, residence)}, kInsideGeneratedCode);
144 EmitDispatch(&as, as.eax);
145 as.Finalize();
146 }
147
148 } // namespace
149
EmitAllocStackFrame(Assembler * as,uint32_t frame_size)150 void EmitAllocStackFrame(Assembler* as, uint32_t frame_size) {
151 if (frame_size > config::kFrameSizeAtTranslatedCode) {
152 uint32_t extra_size = AlignUp(frame_size - config::kFrameSizeAtTranslatedCode,
153 CallingConventions::kStackAlignmentBeforeCall);
154 as->Subl(Assembler::esp, extra_size);
155 }
156 }
157
EmitFreeStackFrame(Assembler * as,uint32_t frame_size)158 void EmitFreeStackFrame(Assembler* as, uint32_t frame_size) {
159 if (frame_size > config::kFrameSizeAtTranslatedCode) {
160 uint32_t extra_size = AlignUp(frame_size - config::kFrameSizeAtTranslatedCode,
161 CallingConventions::kStackAlignmentBeforeCall);
162 as->Addl(Assembler::esp, extra_size);
163 }
164 }
165
EmitJump(Assembler * as,GuestAddr target)166 void EmitJump(Assembler* as, GuestAddr target) {
167 // Attention! Always sync insn_addr as we may be jumping out of translated code (e.g.
168 // non-translated code handler or trampolines that require synced state to run signal handlers).
169 as->Movl(Assembler::eax, target);
170
171 if (!config::kLinkJumpsBetweenRegions) {
172 as->Jmp(kEntryExitGeneratedCode);
173 return;
174 }
175
176 EmitCheckSignalsAndMaybeReturn(as);
177
178 // Now we have same stack state as we had on entry to this
179 // code, so we can just do tail call to other translation unit.
180 as->Jmpl({.disp = bit_cast<int32_t>(TranslationCache::GetInstance()->GetHostCodePtr(target))});
181 }
182
183 // ATTENTION: 'target' should be a general register - see constraints for PseudoIndirectJump!
EmitIndirectJump(Assembler * as,Assembler::Register target)184 void EmitIndirectJump(Assembler* as, Assembler::Register target) {
185 EmitDispatch(as, target);
186 }
187
188 } // namespace x86_32
189
GenTrampolineAdaptor(MachineCode * mc,GuestAddr pc,HostCode marshall,const void * callee,const char * name)190 void GenTrampolineAdaptor(MachineCode* mc,
191 GuestAddr pc,
192 HostCode marshall,
193 const void* callee,
194 const char* name) {
195 x86_32::GenTrampolineAdaptor(mc, pc, marshall, callee, name);
196 }
197
198 } // namespace berberis
199