PInvoke methods using SetLastError get dropped on Windows x64 full AOT.
authorlateralusX <lateralusx.github@gmail.com>
Thu, 14 Jul 2016 18:40:58 +0000 (20:40 +0200)
committerlateralusX <lateralusx.github@gmail.com>
Thu, 14 Jul 2016 18:40:58 +0000 (20:40 +0200)
This happens because current implementation is doing a direct call to Win32
GetLastError in order to get the value before it get clobbered by the icall
setting the last error value in mono. When running AOT all constructs that tries
to call a method using a direct address will be dropped since that will not work
in full AOT. This results in incomplete compiled libraries causing runtime exceptions
in full AOT.

The fix on Windows x64 is to introduce a new OP code, GET_LAST_ERROR that will
retrieve the last error without clobbering it in the process. This is done by
emitting code reading the value directly from the TEB (thread environment block)
and will be lowered to just a move from the segment register holding the TEB data
structure on Windows x64. This is how GetLastError is doing this as well, but since
we won’t be able to call the method in a “safe” way in full AOT, using the segment
register directly will solve the issue. We use the segment register for accessing TLS,
so we already relying on the TEB structure.

NOTE, this will only be applied to Windows x64, full AOT for now.

mono/cil/cil-opcodes.xml
mono/cil/opcode.def
mono/metadata/marshal.c
mono/mini/cpu-amd64.md
mono/mini/method-to-ir.c
mono/mini/mini-amd64.c
mono/mini/mini-ops.h

index 087e3f3008721786750ca531e3487a87aa994907..84b0de33e3c1354e98c77ee8c72be5fbb50ed461 100644 (file)
 <opcode name="mono_calli_extra_arg" input="VarPop" output="VarPush" args="InlineSig" o1="0xF0" o2="0x18" flow="call" />
 <opcode name="mono_lddomain" input="Pop0" output="PushI" args="InlineNone" o1="0xF0" o2="0x19" flow="next" />
 <opcode name="mono_atomic_store_i4" input="PopI+PopI" output="Push0" args="InlineI" o1="0xF0" o2="0x1A" flow="next" />
+<opcode name="mono_get_last_error" input="Pop0" output="PushI" args="InlineI" o1="0xF0" o2="0x1B" flow="next" />
 </opdesc>
index ee69b699ba6797fb7f53a0f2599e650a0983198f..888827054788d1860ed7e1e42e8cd354299d106b 100644 (file)
@@ -319,6 +319,7 @@ OPDEF(CEE_MONO_LDPTR_NURSERY_BITS, "mono_ldptr_nursery_bits", Pop0, PushI, Inlin
 OPDEF(CEE_MONO_CALLI_EXTRA_ARG, "mono_calli_extra_arg", VarPop, VarPush, InlineSig, X, 2, 0xF0, 0x18, CALL)
 OPDEF(CEE_MONO_LDDOMAIN, "mono_lddomain", Pop0, PushI, InlineNone, X, 2, 0xF0, 0x19, NEXT)
 OPDEF(CEE_MONO_ATOMIC_STORE_I4, "mono_atomic_store_i4", PopI+PopI, Push0, InlineI, X, 2, 0xF0, 0x1A, NEXT)
+OPDEF(CEE_MONO_GET_LAST_ERROR, "mono_get_last_error", Pop0, PushI, InlineI, X, 2, 0xF0, 0x1B, NEXT)
 #ifndef OPALIAS
 #define _MONO_CIL_OPALIAS_DEFINED_
 #define OPALIAS(a,s,r)
index bf13ae434eb2123aee229ab4d30b90f541d8b872..0ae2e5390462e3bde380d21e4a0575d7cff273e2 100644 (file)
@@ -7456,19 +7456,27 @@ mono_marshal_emit_native_wrapper (MonoImage *image, MonoMethodBuilder *mb, MonoM
        /* Set LastError if needed */
        if (piinfo->piflags & PINVOKE_ATTRIBUTE_SUPPORTS_LAST_ERROR) {
 #ifdef TARGET_WIN32
-               static MonoMethodSignature *get_last_error_sig = NULL;
-               if (!get_last_error_sig) {
-                       get_last_error_sig = mono_metadata_signature_alloc (mono_defaults.corlib, 0);
-                       get_last_error_sig->ret = &mono_defaults.int_class->byval_arg;
-                       get_last_error_sig->pinvoke = 1;
-               }
+               if (!aot) {
+                       static MonoMethodSignature *get_last_error_sig = NULL;
+                       if (!get_last_error_sig) {
+                               get_last_error_sig = mono_metadata_signature_alloc (mono_defaults.corlib, 0);
+                               get_last_error_sig->ret = &mono_defaults.int_class->byval_arg;
+                               get_last_error_sig->pinvoke = 1;
+                       }
 
-               /*
-                * Have to call GetLastError () early and without a wrapper, since various runtime components could
-                * clobber its value.
-                */
-               mono_mb_emit_native_call (mb, get_last_error_sig, GetLastError);
-               mono_mb_emit_icall (mb, mono_marshal_set_last_error_windows);
+                       /*
+                        * Have to call GetLastError () early and without a wrapper, since various runtime components could
+                        * clobber its value.
+                        */
+                       mono_mb_emit_native_call (mb, get_last_error_sig, GetLastError);
+                       mono_mb_emit_icall (mb, mono_marshal_set_last_error_windows);
+               }
+               else
+               {
+                       mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
+                       mono_mb_emit_byte (mb, CEE_MONO_GET_LAST_ERROR);
+                       mono_mb_emit_icall (mb, mono_marshal_set_last_error_windows);
+               }
 #else
                mono_mb_emit_icall (mb, mono_marshal_set_last_error);
 #endif
index 01219b452680de3f456877078820114f54b48f73..3eeae0fc0da1a42d346b733da64fe69f0d1e048b 100755 (executable)
@@ -792,3 +792,4 @@ gc_spill_slot_liveness_def: len:0
 gc_param_slot_liveness_def: len:0
 
 generic_class_init: src1:A len:32 clob:c
+get_last_error: dest:i len:32
index 7f3c4f7e9c803d6ce2bbdae62bd5cee68638ed75..59ad7712d378ca86cb80f6a09ca87b3b8ace7559 100644 (file)
@@ -12957,6 +12957,18 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                        case CEE_MONO_LDDOMAIN:
                                CHECK_STACK_OVF (1);
                                EMIT_NEW_PCONST (cfg, ins, cfg->compile_aot ? NULL : cfg->domain);
+                               ip += 2;
+                               *sp++ = ins;
+                               break;
+                       case CEE_MONO_GET_LAST_ERROR:
+                               CHECK_OPSIZE (2);
+                               CHECK_STACK_OVF (1);
+
+                               MONO_INST_NEW (cfg, ins, OP_GET_LAST_ERROR);
+                               ins->dreg = alloc_dreg (cfg, STACK_I4);
+                               ins->type = STACK_I4;
+                               MONO_ADD_INS (cfg->cbb, ins);
+
                                ip += 2;
                                *sp++ = ins;
                                break;
index e31aada27c3978c4c0ea314a813ecba46e10e223..df97bde80080fd7820dcbf60a0b2ef26dc9cc0d6 100644 (file)
@@ -8,6 +8,7 @@
  *   Dietmar Maurer (dietmar@ximian.com)
  *   Patrik Torstensson
  *   Zoltan Varga (vargaz@gmail.com)
+ *   Johan Lorensson (lateralusx.github@gmail.com)
  *
  * (C) 2003 Ximian, Inc.
  * Copyright 2003-2011 Novell, Inc (http://www.novell.com)
@@ -3741,6 +3742,30 @@ emit_setup_lmf (MonoCompile *cfg, guint8 *code, gint32 lmf_offset, int cfa_offse
        return code;
 }
 
+#ifdef TARGET_WIN32
+
+#define TEB_LAST_ERROR_OFFSET 0x068
+
+static guint8*
+emit_get_last_error (guint8* code, int dreg)
+{
+       /* Threads last error value is located in TEB_LAST_ERROR_OFFSET. */
+       x86_prefix (code, X86_GS_PREFIX);
+       amd64_mov_reg_membase (code, dreg, TEB_LAST_ERROR_OFFSET, 0, sizeof (guint32));
+
+       return code;
+}
+
+#else
+
+static guint8*
+emit_get_last_error (guint8* code, int dreg)
+{
+       g_assert_not_reached ();
+}
+
+#endif
+
 /* benchmark and set based on cpu */
 #define LOOP_ALIGNMENT 8
 #define bb_is_loop_start(bb) ((bb)->loop_body_start && (bb)->nesting)
@@ -6577,6 +6602,9 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                        ins->backend.pc_offset = code - cfg->native_code;
                        bb->spill_slot_defs = g_slist_prepend_mempool (cfg->mempool, bb->spill_slot_defs, ins);
                        break;
+               case OP_GET_LAST_ERROR:
+                       emit_get_last_error(code, ins->dreg);
+                       break;
                default:
                        g_warning ("unknown opcode %s in %s()\n", mono_inst_name (ins->opcode), __FUNCTION__);
                        g_assert_not_reached ();
index 4c29d4a0e4dd0e9f5b3be528f88faa68f1be86ba..07dd0a435376575104bed2e5da8a9ffa6a8bc099 100644 (file)
@@ -1400,3 +1400,4 @@ MINI_OP(OP_OBJC_GET_SELECTOR, "objc_get_selector", IREG, NONE, NONE)
 MINI_OP(OP_GET_SP, "get_sp", IREG, NONE, NONE)
 MINI_OP(OP_SET_SP, "set_sp", NONE, IREG, NONE)
 
+MINI_OP(OP_GET_LAST_ERROR, "get_last_error", IREG, NONE, NONE)