@@ -20,6 +20,8 @@ use yktrace::tir::{
20
20
21
21
use dynasmrt:: DynasmApi ;
22
22
23
+ const X86_86_NUM_REGS : u8 = 16 ;
24
+
23
25
#[ derive( Debug , Hash , Eq , PartialEq ) ]
24
26
pub enum CompileError {
25
27
/// We ran out of registers.
@@ -28,13 +30,16 @@ pub enum CompileError {
28
30
/// Compiling this statement is not yet implemented.
29
31
/// The string inside is a hint as to what kind of statement needs to be implemented.
30
32
Unimplemented ( String ) ,
33
+ /// The binary symbol could not be found.
34
+ UnknownSymbol ( String ) ,
31
35
}
32
36
33
37
impl Display for CompileError {
34
38
fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result {
35
39
match self {
36
40
Self :: OutOfRegisters => write ! ( f, "Ran out of registers" ) ,
37
41
Self :: Unimplemented ( s) => write ! ( f, "Unimplemented compilation: {}" , s) ,
42
+ Self :: UnknownSymbol ( s) => write ! ( f, "Unknown symbol: {}" , s) ,
38
43
}
39
44
}
40
45
}
@@ -73,7 +78,14 @@ impl CompiledTrace {
73
78
// For now a compiled trace always returns whatever has been left in register RAX. We also
74
79
// assume for now that this will be a `u64`.
75
80
let func: fn ( ) -> u64 = unsafe { mem:: transmute ( self . mc . ptr ( dynasmrt:: AssemblyOffset ( 0 ) ) ) } ;
76
- func ( )
81
+ self . exec_trace ( func)
82
+ }
83
+
84
+ /// Jump to JITted code. This is a separate unmangled function to make it easy to set a
85
+ /// debugger breakpoint right before entering the trace.
86
+ #[ no_mangle]
87
+ fn exec_trace ( & self , t_fn : fn ( ) -> u64 ) -> u64 {
88
+ t_fn ( )
77
89
}
78
90
}
79
91
@@ -222,6 +234,63 @@ impl TraceCompiler {
222
234
Ok ( ( ) )
223
235
}
224
236
237
+ /// Compile a call to a native symbol. This is used for occasions where you don't want the
238
+ /// callee inlined, or cannot inline it (e.g. it's a foreign function).
239
+ fn c_call (
240
+ & mut self ,
241
+ opnd : & CallOperand ,
242
+ args : & Vec < Operand > ,
243
+ dest : & Option < Place > ,
244
+ ) -> Result < ( ) , CompileError > {
245
+ let sym = if let CallOperand :: Fn ( sym) = opnd {
246
+ sym
247
+ } else {
248
+ return Err ( CompileError :: Unimplemented (
249
+ "unknown call target" . to_owned ( ) ,
250
+ ) ) ;
251
+ } ;
252
+
253
+ // FIXME implement support for arguments.
254
+ if !args. is_empty ( ) {
255
+ return Err ( CompileError :: Unimplemented ( "call with args" . to_owned ( ) ) ) ;
256
+ }
257
+
258
+ // Generally speaking, we don't know what the callee will clobber, so we save our registers
259
+ // before doing the call. For now we assume that the caller is OK with RAX being clobbered
260
+ // though.
261
+ for reg in 1 ..X86_86_NUM_REGS {
262
+ dynasm ! ( self . asm
263
+ ; push Rq ( reg)
264
+ ) ;
265
+ }
266
+
267
+ let sym_addr = TraceCompiler :: find_symbol ( sym) ? as i64 ;
268
+ dynasm ! ( self . asm
269
+ // In Sys-V ABI, `al` is a hidden argument used to specify the number of vector args
270
+ // for a vararg call. We don't support this right now, so set it to zero. FIXME.
271
+ ; xor rax, rax
272
+ ; mov r11, QWORD sym_addr
273
+ ; call r11
274
+ ) ;
275
+
276
+ // Restore registers.
277
+ for reg in ( 1 ..X86_86_NUM_REGS ) . rev ( ) {
278
+ dynasm ! ( self . asm
279
+ ; pop Rq ( reg)
280
+ ) ;
281
+ }
282
+
283
+ // Put return value in place.
284
+ if let Some ( dest) = dest {
285
+ let dest_reg = self . local_to_reg ( dest. local ) ?;
286
+ dynasm ! ( self . asm
287
+ ; mov Rq ( dest_reg) , rax
288
+ ) ;
289
+ }
290
+
291
+ Ok ( ( ) )
292
+ }
293
+
225
294
fn statement ( & mut self , stmt : & Statement ) -> Result < ( ) , CompileError > {
226
295
match stmt {
227
296
Statement :: Assign ( l, r) => {
@@ -247,7 +316,7 @@ impl TraceCompiler {
247
316
Statement :: Leave => self . c_leave ( ) ?,
248
317
Statement :: StorageLive ( _) => { }
249
318
Statement :: StorageDead ( l) => self . free_register ( l) ,
250
- c @ Statement :: Call ( .. ) => return Err ( CompileError :: Unimplemented ( format ! ( "{:?}" , c ) ) ) ,
319
+ Statement :: Call ( target , args , dest ) => self . c_call ( target , args , dest ) ? ,
251
320
Statement :: Nop => { }
252
321
Statement :: Unimplemented ( s) => {
253
322
return Err ( CompileError :: Unimplemented ( format ! ( "{:?}" , s) ) )
@@ -337,25 +406,24 @@ impl TraceCompiler {
337
406
CompiledTrace { mc : tc. finish ( ) }
338
407
}
339
408
340
- #[ allow( dead_code) ] // Not used just yet.
341
- fn find_symbol ( sym : & str ) -> Option < * mut c_void > {
409
+ fn find_symbol ( sym : & str ) -> Result < * mut c_void , CompileError > {
342
410
use std:: ffi:: CString ;
343
411
344
412
let sym_arg = CString :: new ( sym) . unwrap ( ) ;
345
413
let addr = unsafe { dlsym ( RTLD_DEFAULT , sym_arg. into_raw ( ) ) } ;
346
414
347
415
if addr == 0 as * mut c_void {
348
- None
416
+ Err ( CompileError :: UnknownSymbol ( sym . to_owned ( ) ) )
349
417
} else {
350
- Some ( addr)
418
+ Ok ( addr)
351
419
}
352
420
}
353
421
}
354
422
355
423
#[ cfg( test) ]
356
424
mod tests {
357
- use super :: { HashMap , Local , TraceCompiler } ;
358
- use libc:: c_void;
425
+ use super :: { CompileError , HashMap , Local , TraceCompiler } ;
426
+ use libc:: { c_void, getuid } ;
359
427
use std:: collections:: HashSet ;
360
428
use yktrace:: tir:: { CallOperand , Statement , TirOp , TirTrace } ;
361
429
use yktrace:: { start_tracing, TracingKind } ;
@@ -435,7 +503,7 @@ mod tests {
435
503
assert_eq ! ( ct. execute( ) , 13 ) ;
436
504
}
437
505
438
- fn fnested3 ( i : u8 , j : u8 ) -> u8 {
506
+ fn fnested3 ( i : u8 , _j : u8 ) -> u8 {
439
507
let c = i;
440
508
c
441
509
}
@@ -462,7 +530,7 @@ mod tests {
462
530
// Test finding a symbol in a shared object.
463
531
#[ test]
464
532
fn find_symbol_shared ( ) {
465
- assert ! ( TraceCompiler :: find_symbol( "printf" ) == Some ( libc:: printf as * mut c_void) ) ;
533
+ assert ! ( TraceCompiler :: find_symbol( "printf" ) == Ok ( libc:: printf as * mut c_void) ) ;
466
534
}
467
535
468
536
// Test finding a symbol in the main binary.
@@ -472,22 +540,25 @@ mod tests {
472
540
#[ no_mangle]
473
541
fn find_symbol_main ( ) {
474
542
assert ! (
475
- TraceCompiler :: find_symbol( "find_symbol_main" ) == Some ( find_symbol_main as * mut c_void)
543
+ TraceCompiler :: find_symbol( "find_symbol_main" ) == Ok ( find_symbol_main as * mut c_void)
476
544
) ;
477
545
}
478
546
479
547
// Check that a non-existent symbol cannot be found.
480
548
#[ test]
481
549
fn find_nonexistent_symbol ( ) {
482
- assert ! ( TraceCompiler :: find_symbol( "__xxxyyyzzz__" ) . is_none( ) ) ;
550
+ assert_eq ! (
551
+ TraceCompiler :: find_symbol( "__xxxyyyzzz__" ) ,
552
+ Err ( CompileError :: UnknownSymbol ( "__xxxyyyzzz__" . to_owned( ) ) )
553
+ ) ;
483
554
}
484
555
485
556
// A trace which contains a call to something which we don't have SIR for should emit a TIR
486
557
// call operation.
487
558
#[ test]
488
559
pub fn call_symbol ( ) {
489
560
let th = start_tracing ( Some ( TracingKind :: HardwareTracing ) ) ;
490
- let g = core:: intrinsics:: wrapping_add ( 10u64 , 40u64 ) ;
561
+ let _ = core:: intrinsics:: wrapping_add ( 10u64 , 40u64 ) ;
491
562
let sir_trace = th. stop_tracing ( ) . unwrap ( ) ;
492
563
let tir_trace = TirTrace :: new ( & * sir_trace) . unwrap ( ) ;
493
564
@@ -502,4 +573,15 @@ mod tests {
502
573
}
503
574
assert ! ( found_call) ;
504
575
}
576
+
577
+ /// Execute a trace which calls a symbol accepting no arguments, but which does return a value.
578
+ #[ test]
579
+ pub fn exec_call_symbol_no_args_with_rv ( ) {
580
+ let th = start_tracing ( Some ( TracingKind :: HardwareTracing ) ) ;
581
+ let expect = unsafe { getuid ( ) } ;
582
+ let sir_trace = th. stop_tracing ( ) . unwrap ( ) ;
583
+ let tir_trace = TirTrace :: new ( & * sir_trace) . unwrap ( ) ;
584
+ let got = TraceCompiler :: compile ( tir_trace) . execute ( ) ;
585
+ assert_eq ! ( expect as u64 , got) ;
586
+ }
505
587
}
0 commit comments