To protect your data, the CISO officer has suggested users to enable GitLab 2FA as soon as possible.

exception_x64.rs 12.2 KB
Newer Older
qinsoon's avatar
qinsoon committed
1
2
3
4
use ast::ir::*;
use compiler::machine_code::CompiledFunction;
use compiler::backend::x86_64;
use utils::Address;
qinsoon's avatar
qinsoon committed
5
use utils::Word;
qinsoon's avatar
qinsoon committed
6
7
8
9
10
11
use utils::POINTER_SIZE;
use runtime::thread;

use std::sync::RwLock;
use std::sync::RwLockReadGuard;
use std::collections::HashMap;
12
use std::fmt;
qinsoon's avatar
qinsoon committed
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
// muentry_throw_exception in swap_stack_x64_sysV.S
// is like a special calling convention to throw_exception_internal
// in order to save all the callee saved registers at a known location

// normal calling convention:
// ---code---                                        ---stack---
// push caller saved                                 caller saved
// call                                              return addr
//          -> (in callee) push rbp                  old rbp
//                         mov  rsp -> rbp           callee saved
//                         push callee saved

// this function's calling convention
// ---code---                                        ---stack---
// push caller saved                                 caller saved
// call                                              return addr
//          -> (in asm)  push callee saved           all callee saved <- 2nd arg
//             (in rust) push rbp                    (by rust) old rbp
//                       mov  rsp -> rbp             (by rust) callee saved
//                       push callee saved

// we do not want to make any assumptionon  where rust saves rbp or callee saved
// so we save them by ourselves in assembly, and pass a pointer as 2nd argument

qinsoon's avatar
qinsoon committed
38
#[no_mangle]
qinsoon's avatar
qinsoon committed
39
#[allow(unreachable_code)]
40
41
42
// last_frame_callee_saved: a pointer passed from assembly, values of 6 callee_saved
// registers are layed out as rbx, rbp, r12-r15 (from low address to high address)
// and return address is put after 6 callee saved regsiters
43
pub extern fn throw_exception_internal(exception_obj: Address, last_frame_callee_saved: Address) -> ! {
qinsoon's avatar
qinsoon committed
44
    trace!("throwing exception: {}", exception_obj);
45
46
47

    trace!("callee saved registers of last frame is saved at {}", last_frame_callee_saved);
    inspect_nearby_address(last_frame_callee_saved, 8);
qinsoon's avatar
qinsoon committed
48
49
50
51
52
    
    let mut cur_thread = thread::MuThread::current_mut();
    // set exception object
    cur_thread.exception_obj = exception_obj;
    
53
54
    let cf_lock   = cur_thread.vm.compiled_funcs().read().unwrap();
    let func_lock = cur_thread.vm.funcs().read().unwrap();
55
56

    let rust_frame_return_addr = unsafe {last_frame_callee_saved.plus(POINTER_SIZE * x86_64::CALLEE_SAVED_GPRs.len()).load::<Address>()};
57
    trace!("return address   : 0x{:x} - throw instruction", rust_frame_return_addr);
qinsoon's avatar
qinsoon committed
58
59
60
    
    // the return address is within throwing frame
    let throw_frame_callsite = rust_frame_return_addr;
61
    let (throw_func, throw_fv) = find_func_for_address(&cf_lock, &func_lock, throw_frame_callsite);
62
    trace!("throwing fucntion: {}", throw_func);
qinsoon's avatar
qinsoon committed
63
64
65
    
    // skip to previous frame
    // this is the frame that throws the exception
66
67
68
    let previous_frame_rbp_loc = last_frame_callee_saved.plus(POINTER_SIZE);
    let rbp = unsafe {previous_frame_rbp_loc.load::<Address>()};
    trace!("rbp of previous frame is {} (last_frame_callee_saved {} + 8)", rbp, last_frame_callee_saved);
qinsoon's avatar
qinsoon committed
69
70
71
72
73
    
    // set cursor to throwing frame
    let mut cursor = FrameCursor {
        rbp: rbp,
        return_addr: unsafe {rbp.plus(POINTER_SIZE).load::<Address>()},
74
75
        func_id: throw_func,
        func_ver_id: throw_fv,
76
77
78
79
80
81
82
83
        callee_saved_locs: hashmap!{
            x86_64::RBX.id() => last_frame_callee_saved,
            x86_64::RBP.id() => previous_frame_rbp_loc,
            x86_64::R12.id() => last_frame_callee_saved.plus(POINTER_SIZE * 2),
            x86_64::R13.id() => last_frame_callee_saved.plus(POINTER_SIZE * 3),
            x86_64::R14.id() => last_frame_callee_saved.plus(POINTER_SIZE * 4),
            x86_64::R15.id() => last_frame_callee_saved.plus(POINTER_SIZE * 5),
        }
qinsoon's avatar
qinsoon committed
84
    };
qinsoon's avatar
qinsoon committed
85
86

    print_backtrace(throw_frame_callsite, cursor.clone());
qinsoon's avatar
qinsoon committed
87
    
88
89
90
    let mut callsite = rust_frame_return_addr;
    
    trace!("Stack Unwinding starts");
qinsoon's avatar
qinsoon committed
91
    loop {
92
        trace!("frame cursor: {}", cursor);
93

qinsoon's avatar
qinsoon committed
94
95
96
97
98
99
100
101
102
        // release the locks, and keep a clone of the frame
        // because we may improperly leave this function
        let frame = {
            let rwlock_cf = match cf_lock.get(&cursor.func_ver_id) {
                Some(ret) => ret,
                None => panic!("cannot find compiled func with func_id {}, possibly didnt find the right frame for return address", cursor.func_id)
            };
            let rwlock_cf = rwlock_cf.read().unwrap();
            rwlock_cf.frame.clone()
qinsoon's avatar
qinsoon committed
103
        };
104
        trace!("frame info: {}", frame);
qinsoon's avatar
qinsoon committed
105
106
107
108
        
        // update callee saved register location
        for reg in x86_64::CALLEE_SAVED_GPRs.iter() {
            let reg_id = reg.id();
qinsoon's avatar
qinsoon committed
109
            trace!("update callee saved register {}", reg_id);
qinsoon's avatar
qinsoon committed
110
111
112
113
            if frame.allocated.contains_key(&reg_id) {
                let offset_from_rbp = frame.allocated.get(&reg_id).unwrap().offset;
                let reg_restore_addr = cursor.rbp.offset(offset_from_rbp);
                
114
                trace!("update callee saved register {} with loc 0x{:x}", reg_id, reg_restore_addr);
qinsoon's avatar
qinsoon committed
115
                cursor.callee_saved_locs.insert(reg_id, reg_restore_addr);
qinsoon's avatar
qinsoon committed
116
117
118
119
120
            } else {
                // rbp won't find a location
                if reg_id == x86_64::RBP.id() {
                    
                } else {
121
                    info!("failed to find an entry for {} in current frame", reg_id);
qinsoon's avatar
qinsoon committed
122
                }
qinsoon's avatar
qinsoon committed
123
124
125
            }
        }
        
qinsoon's avatar
qinsoon committed
126
        // find exception block - comparing callsite with frame info
127
        trace!("checking catch block: looking for callsite 0x{:x}", callsite);
qinsoon's avatar
qinsoon committed
128
129
        let exception_callsites = frame.get_exception_callsites();
        for &(ref possible_callsite, ref dest) in exception_callsites.iter() {
qinsoon's avatar
qinsoon committed
130
            let possible_callsite_addr = possible_callsite.to_address();
qinsoon's avatar
qinsoon committed
131
            trace!("..check {} at 0x{:x}", possible_callsite, possible_callsite_addr);
qinsoon's avatar
qinsoon committed
132
133
            
            if callsite == possible_callsite_addr {
qinsoon's avatar
qinsoon committed
134
                trace!("found catch block at {}", dest);
qinsoon's avatar
qinsoon committed
135
136
137
138
                // found an exception block
                let dest_addr = dest.to_address();
                
                // restore callee saved register and jump to dest_addr
qinsoon's avatar
qinsoon committed
139
140
141
142
143
144
145
                
                // prepare a plain array [rbx, rbp, r12, r13, r14, r15]
                macro_rules! unpack_callee_saved_from_cursor {
                    ($reg: expr) => {
                        match cursor.callee_saved_locs.get(&$reg.id()) {
                            Some(addr) => unsafe {addr.load::<Word>()},
                            None => {
146
                                info!("no {} value was saved along unwinding", $reg.name().unwrap());
qinsoon's avatar
qinsoon committed
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
                                0
                            }
                        }
                    }
                };
                
                let rbx = unpack_callee_saved_from_cursor!(x86_64::RBX);
                let r12 = unpack_callee_saved_from_cursor!(x86_64::R12);
                let r13 = unpack_callee_saved_from_cursor!(x86_64::R13);
                let r14 = unpack_callee_saved_from_cursor!(x86_64::R14);
                let r15 = unpack_callee_saved_from_cursor!(x86_64::R15);
                let rbp = cursor.rbp.as_usize() as Word;
                let array = vec![rbx, rbp, r12, r13, r14, r15];
                
                let rsp = cursor.rbp.offset(frame.cur_offset());
162
163

                info!("going to restore thread to {} with RSP {}", dest_addr, rsp);
qinsoon's avatar
qinsoon committed
164
165
166
                unsafe {thread::exception_restore(dest_addr, array.as_ptr(), rsp)};
                
                unreachable!()
qinsoon's avatar
qinsoon committed
167
168
            }
        }
169
        trace!("didnt find a catch block");
qinsoon's avatar
qinsoon committed
170
171
        
        // keep unwinding
172
        callsite = cursor.return_addr;
173
        cursor.to_previous_frame(&cf_lock, &func_lock);
174
        trace!("cursor unwinds to previous frame: {}", cursor);        
qinsoon's avatar
qinsoon committed
175
176
177
    }
}

qinsoon's avatar
qinsoon committed
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
fn print_backtrace(callsite: Address, mut cursor: FrameCursor) {
    info!("Mu backtrace:");

    let cur_thread = thread::MuThread::current();

    let cf_lock   = cur_thread.vm.compiled_funcs().read().unwrap();
    let func_lock = cur_thread.vm.funcs().read().unwrap();

    let mut frame_count = 0;
    let mut callsite = callsite;

    loop {
        let func_start = {
            match cf_lock.get(&cursor.func_ver_id) {
                Some(rwlock_cf) => {
                    rwlock_cf.read().unwrap().start.to_address()
                },
                None => unsafe {Address::zero()}
            }
        };
        let func_name = cur_thread.vm.name_of(cursor.func_ver_id);

        info!("frame {:2}: 0x{:x} - {} (fid: #{}, fvid: #{}) at 0x{:x}", frame_count, func_start, func_name, cursor.func_id, cursor.func_ver_id, callsite);

        if cursor.has_previous_frame() {
            frame_count += 1;
            callsite = cursor.return_addr;

            cursor.to_previous_frame(&cf_lock, &func_lock);
        } else {
            break;
        }
    }

    info!("backtrace done.");
}

qinsoon's avatar
qinsoon committed
215
216
217
218
219
220
fn inspect_nearby_address(base: Address, n: isize) {
    let mut i = n;
    while i >= -n {
        unsafe {
            let addr = base.offset(i * POINTER_SIZE as isize);
            let val  = addr.load::<Word>();
221
            trace!("addr: 0x{:x} | val: 0x{:x} {}", addr, val, {if addr == base {"<- base"} else {""}});
qinsoon's avatar
qinsoon committed
222
223
224
225
226
        }
        i -= 1;
    }
}

qinsoon's avatar
qinsoon committed
227
#[derive(Clone)]
qinsoon's avatar
qinsoon committed
228
229
230
231
struct FrameCursor {
    rbp: Address,
    return_addr: Address,
    func_id: MuID,
232
    func_ver_id: MuID,
qinsoon's avatar
qinsoon committed
233
234
235
    callee_saved_locs: HashMap<MuID, Address>
}

236
237
impl fmt::Display for FrameCursor {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
238
        writeln!(f, "\nFrameCursor{{").unwrap();
239
240
241
242
243
244
245
246
247
        writeln!(f, "  rbp=0x{:x}, return_addr=0x{:x}, func_id={}, func_version_id={}", self.rbp, self.return_addr, self.func_id, self.func_ver_id).unwrap();
        writeln!(f, "  callee_saved:").unwrap();
        for (reg, addr) in self.callee_saved_locs.iter() {
            writeln!(f, "    #{} at 0x{:x}", reg, addr).unwrap()
        }
        writeln!(f, "}}")
    }
}

qinsoon's avatar
qinsoon committed
248
impl FrameCursor {
qinsoon's avatar
qinsoon committed
249
250
251
252
    fn has_previous_frame(&self) -> bool {
        !self.return_addr.is_zero()
    }

253
    fn to_previous_frame(&mut self, cf: &RwLockReadGuard<HashMap<MuID, RwLock<CompiledFunction>>>, funcs: &RwLockReadGuard<HashMap<MuID, RwLock<MuFunction>>>) {
254
255
256
257
258
259
        // check if return_addr is valid
        // FIXME: should use a sentinel value here
        if self.return_addr.is_zero() {
            panic!("cannot go to previous frame (return address is zero)");
        }
        
qinsoon's avatar
qinsoon committed
260
261
        let previous_rbp = unsafe {self.rbp.load::<Address>()};
        let previous_return_addr = unsafe {previous_rbp.plus(POINTER_SIZE).load::<Address>()};
262
        let (previous_func, previous_fv_id) = find_func_for_address(cf, funcs, self.return_addr);
qinsoon's avatar
qinsoon committed
263
264
265
        
        self.rbp = previous_rbp;
        self.return_addr = previous_return_addr;
266
267
        self.func_id = previous_func;
        self.func_ver_id = previous_fv_id;
qinsoon's avatar
qinsoon committed
268
269
270
    }
}

qinsoon's avatar
qinsoon committed
271
272
273
const TRACE_FIND_FUNC : bool = false;

#[allow(unused_imports)]
274
fn find_func_for_address (cf: &RwLockReadGuard<HashMap<MuID, RwLock<CompiledFunction>>>, funcs: &RwLockReadGuard<HashMap<MuID, RwLock<MuFunction>>>, pc_addr: Address) -> (MuID, MuID) {
qinsoon's avatar
qinsoon committed
275
    use std::ops::Deref;
276

qinsoon's avatar
qinsoon committed
277
278
279
    if TRACE_FIND_FUNC {
        trace!("trying to find FuncVersion for address 0x{:x}", pc_addr);
    }
qinsoon's avatar
qinsoon committed
280
    for (_, func) in cf.iter() {
281
282
283
284
        let func = func.read().unwrap();
        
        let start = func.start.to_address();
        let end = func.end.to_address();
qinsoon's avatar
qinsoon committed
285
286
287
288
289
290
291
292
293

        if TRACE_FIND_FUNC {
            let f = match funcs.get(&func.func_id) {
                Some(f) => f,
                None => panic!("failed to find func #{}", func.func_id)
            };
            let f_lock = f.read().unwrap();
            trace!("CompiledFunction: func={}, fv_id={}, start=0x{:x}, end=0x{:x}", f_lock.deref(), func.func_ver_id, start, end);
        }
294
        
qinsoon's avatar
qinsoon committed
295
296
        // pc won't be the start of a function, but could be the end
        if pc_addr > start && pc_addr <= end {
qinsoon's avatar
qinsoon committed
297
298
299
            if TRACE_FIND_FUNC {
                trace!("Found CompiledFunction: func_id={}, fv_id={}", func.func_id, func.func_ver_id);
            }
300
301
302
303
            return (func.func_id, func.func_ver_id);
        }
    }
    
qinsoon's avatar
qinsoon committed
304
    panic!("cannot find compiled function for pc 0x{:x}", pc_addr);
305
}