/
x86.rs
334 lines (308 loc) · 11.6 KB
/
x86.rs
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
//! Architecture-specific assembly code.
use core::arch::asm;
#[cfg(all(feature = "experimental-relocate", feature = "origin-start"))]
#[cfg(relocation_model = "pic")]
use linux_raw_sys::general::{__NR_mprotect, PROT_READ};
#[cfg(feature = "origin-signal")]
use linux_raw_sys::general::{__NR_rt_sigreturn, __NR_sigreturn};
#[cfg(all(feature = "origin-thread", feature = "thread"))]
use {
core::ffi::c_void,
core::ptr::without_provenance_mut,
linux_raw_sys::general::{__NR_clone, __NR_exit, __NR_munmap},
rustix::thread::RawPid,
};
/// The program entry point.
///
/// # Safety
///
/// This function must never be called explicitly. It is the first thing
/// executed in the program, and it assumes that memory is laid out according
/// to the operating system convention for starting a new program.
#[cfg(feature = "origin-start")]
#[naked]
#[no_mangle]
pub(super) unsafe extern "C" fn _start() -> ! {
// Jump to `entry`, passing it the initial stack pointer value as an
// argument, a null return address, a null frame pointer, and an aligned
// stack pointer. On many architectures, the incoming frame pointer is
// already null.
asm!(
"mov eax, esp", // Save the incoming `esp` value.
"push ebp", // Pad for stack pointer alignment.
"push ebp", // Pad for stack pointer alignment.
"push ebp", // Pad for stack pointer alignment.
"push eax", // Pass saved the incoming `esp` as the arg to `entry`.
"push ebp", // Set the return address to zero.
"jmp {entry}", // Jump to `entry`.
entry = sym super::program::entry,
options(noreturn),
)
}
/// Perform a single load operation, outside the Rust memory model.
///
/// This function conceptually casts `ptr` to a `*const *mut c_void` and loads
/// a `*mut c_void` value from it. However, it does this using `asm`, and
/// `usize` types which don't carry provenance, as it's used by `relocate` to
/// perform relocations which cannot be expressed in the Rust memory model.
///
/// # Safety
///
/// This function must only be called during the relocation process, for
/// relocation purposes. And, `ptr` must contain the address of a memory
/// location that can be loaded from.
#[cfg(all(feature = "experimental-relocate", feature = "origin-start"))]
#[cfg(relocation_model = "pic")]
#[inline]
pub(super) unsafe fn relocation_load(ptr: usize) -> usize {
let r0;
// This is read-only but we don't use `readonly` because this memory access
// happens outside the Rust memory model. As far as Rust knows, this is
// just an arbitrary side-effecting opaque operation.
asm!(
"mov {}, [{}]",
out(reg) r0,
in(reg) ptr,
options(nostack, preserves_flags),
);
r0
}
/// Perform a single store operation, outside the Rust memory model.
///
/// This function conceptually casts `ptr` to a `*mut *mut c_void` and stores
/// a `*mut c_void` value to it. However, it does this using `asm`, and `usize`
/// types which don't carry provenance, as it's used by `relocate` to perform
/// relocations which cannot be expressed in the Rust memory model.
///
/// # Safety
///
/// This function must only be called during the relocation process, for
/// relocation purposes. And, `ptr` must contain the address of a memory
/// location that can be stored to.
#[cfg(all(feature = "experimental-relocate", feature = "origin-start"))]
#[cfg(relocation_model = "pic")]
#[inline]
pub(super) unsafe fn relocation_store(ptr: usize, value: usize) {
asm!(
"mov [{}], {}",
in(reg) ptr,
in(reg) value,
options(nostack, preserves_flags),
);
}
/// Mark “relro” memory as readonly.
///
/// “relro” is a relocation feature in which memory can be readonly after
/// relocations are applied.
///
/// This function conceptually casts `ptr` to a `*mut c_void` and does a
/// `rustix::mm::mprotect(ptr, len, MprotectFlags::READ)`. However, it does
/// this using `asm` and `usize` types which don't carry provenance, as it's
/// used by `relocate` to implement the “relro” feature which cannot be
/// expressed in the Rust memory model.
///
/// # Safety
///
/// This function must only be called during the relocation process, for
/// relocation purposes. And, `ptr` must contain the address of a memory
/// location that can be marked readonly.
#[cfg(all(feature = "experimental-relocate", feature = "origin-start"))]
#[cfg(relocation_model = "pic")]
#[inline]
pub(super) unsafe fn relocation_mprotect_readonly(ptr: usize, len: usize) {
let r0: usize;
// This is read-only but we don't use `readonly` because the side effects
// happen outside the Rust memory model. As far as Rust knows, this is
// just an arbitrary side-effecting opaque operation.
asm!(
"int 0x80",
inlateout("eax") __NR_mprotect as usize => r0,
in("ebx") ptr,
in("ecx") len,
in("edx") PROT_READ,
options(nostack, preserves_flags),
);
assert_eq!(r0, 0);
}
/// The required alignment for the stack pointer.
#[cfg(all(feature = "origin-thread", feature = "thread"))]
pub(super) const STACK_ALIGNMENT: usize = 16;
/// A wrapper around the Linux `clone` system call.
///
/// This can't be implemented in `rustix` because the child starts executing at
/// the same point as the parent and we need to use inline asm to have the
/// child jump to our new-thread entrypoint.
#[cfg(all(feature = "origin-thread", feature = "thread"))]
#[inline]
pub(super) unsafe fn clone(
flags: u32,
child_stack: *mut c_void,
parent_tid: *mut RawPid,
child_tid: *mut RawPid,
newtls: *mut c_void,
fn_: extern "C" fn(),
num_args: usize,
) -> isize {
let mut gs: u32 = 0;
asm!("mov {0:x}, gs", inout(reg) gs);
let entry_number = gs >> 3;
let mut user_desc = rustix::runtime::UserDesc {
entry_number,
base_addr: newtls.addr() as u32,
limit: 0xfffff,
_bitfield_align_1: [],
_bitfield_1: Default::default(),
__bindgen_padding_0: [0_u8; 3_usize],
};
user_desc.set_seg_32bit(1);
user_desc.set_contents(0);
user_desc.set_read_exec_only(0);
user_desc.set_limit_in_pages(1);
user_desc.set_seg_not_present(0);
user_desc.set_useable(1);
let newtls: *const _ = &user_desc;
// Push `fn_` to the child stack, since after all the `clone` args, and
// `num_args` in `ebp`, there are no more free registers.
let child_stack = child_stack.cast::<*mut c_void>().sub(1);
child_stack.write(fn_ as _);
// See the comments for x86's `syscall6` in `rustix`. Inline asm isn't
// allowed to name ebp or esi as operands, so we have to jump through
// extra hoops here.
let r0;
asm!(
"push esi", // Save incoming register value.
"push ebp", // Save incoming register value.
// Pass `num_args` to the child in `ebp`.
"mov ebp, [eax+8]",
"mov esi, [eax+0]", // Pass `newtls` to the `int 0x80`.
"mov eax, [eax+4]", // Pass `__NR_clone` to the `int 0x80`.
// Use `int 0x80` instead of vsyscall, following `clone`'s
// documentation; vsyscall would attempt to return to the parent stack
// in the child.
"int 0x80", // Do the `clone` system call.
"test eax, eax", // Branch if we're in the parent.
"jnz 0f",
// Child thread.
"pop edi", // Load `fn_` from the child stack.
"mov esi, esp", // Snapshot the args pointer.
"push eax", // Pad for stack pointer alignment.
"push ebp", // Pass `num_args` as the third argument.
"push esi", // Pass the args pointer as the second argument.
"push edi", // Pass `fn_` as the first argument.
"xor ebp, ebp", // Zero the frame address.
"push eax", // Zero the return address.
"jmp {entry}", // Call `entry`.
// Parent thread.
"0:",
"pop ebp", // Restore incoming register value.
"pop esi", // Restore incoming register value.
entry = sym super::thread::entry,
inout("eax") &[
newtls.cast::<c_void>().cast_mut(),
without_provenance_mut(__NR_clone as usize),
without_provenance_mut(num_args)
] => r0,
in("ebx") flags,
in("ecx") child_stack,
in("edx") parent_tid,
in("edi") child_tid,
);
r0
}
/// Write a value to the platform thread-pointer register.
#[cfg(all(feature = "origin-thread", feature = "thread"))]
#[inline]
pub(super) unsafe fn set_thread_pointer(ptr: *mut c_void) {
let mut user_desc = rustix::runtime::UserDesc {
entry_number: !0u32,
base_addr: ptr.addr() as u32,
limit: 0xfffff,
_bitfield_align_1: [],
_bitfield_1: Default::default(),
__bindgen_padding_0: [0_u8; 3_usize],
};
user_desc.set_seg_32bit(1);
user_desc.set_contents(0);
user_desc.set_read_exec_only(0);
user_desc.set_limit_in_pages(1);
user_desc.set_seg_not_present(0);
user_desc.set_useable(1);
rustix::runtime::set_thread_area(&mut user_desc).expect("set_thread_area");
asm!("mov gs, {0:x}", in(reg) ((user_desc.entry_number << 3) | 3) as u16);
debug_assert_eq!(*ptr.cast::<*const c_void>(), ptr);
debug_assert_eq!(thread_pointer(), ptr);
}
/// Read the value of the platform thread-pointer register.
#[cfg(all(feature = "origin-thread", feature = "thread"))]
#[inline]
pub(super) fn thread_pointer() -> *mut c_void {
let ptr;
// SAFETY: On x86, reading the thread register itself is expensive, so the
// ABI specifies that the thread pointer value is also stored in memory at
// offset 0 from the thread pointer value, where it can be read with just a
// load.
unsafe {
asm!("mov {}, gs:0", out(reg) ptr, options(nostack, preserves_flags, readonly));
}
ptr
}
/// TLS data ends at the location pointed to by the thread pointer.
#[cfg(all(feature = "origin-thread", feature = "thread"))]
pub(super) const TLS_OFFSET: usize = 0;
/// `munmap` the current thread, then carefully exit the thread without
/// touching the deallocated stack.
#[cfg(all(feature = "origin-thread", feature = "thread"))]
#[inline]
pub(super) unsafe fn munmap_and_exit_thread(map_addr: *mut c_void, map_len: usize) -> ! {
asm!(
// Use `int 0x80` instead of vsyscall, since vsyscall would attempt to
// touch the stack after we `munmap` it.
"int 0x80",
"xor ebx, ebx",
"mov eax, {__NR_exit}",
"int 0x80",
"ud2",
__NR_exit = const __NR_exit,
in("eax") __NR_munmap,
in("ebx") map_addr,
in("ecx") map_len,
options(noreturn, nostack)
);
}
/// Invoke the `__NR_rt_sigreturn` system call to return control from a signal
/// handler.
///
/// # Safety
///
/// This function must never be called other than by the `sa_restorer`
/// mechanism.
#[cfg(feature = "origin-signal")]
#[naked]
pub(super) unsafe extern "C" fn return_from_signal_handler() {
asm!(
"mov eax, {__NR_rt_sigreturn}",
"int 0x80",
"ud2",
__NR_rt_sigreturn = const __NR_rt_sigreturn,
options(noreturn)
);
}
/// Invoke the appropriate system call to return control from a signal
/// handler that does not use `SA_SIGINFO`.
///
/// # Safety
///
/// This function must never be called other than by the `sa_restorer`
/// mechanism.
#[cfg(feature = "origin-signal")]
#[naked]
pub(super) unsafe extern "C" fn return_from_signal_handler_noinfo() {
asm!(
"pop eax",
"mov eax, {__NR_sigreturn}",
"int 0x80",
"ud2",
__NR_sigreturn = const __NR_sigreturn,
options(noreturn)
);
}