TypedReference
Сам TypedReference удивительнейшая вещица — в него можно завернуть ref и передать другой метод (т.е. из метода, один из параметров которого является ref‘ом). Однако, методы этой структуры не позволяют нам задавать значение — NotSupportedException ожидает нас на вызове соответствующих методов. Но не беда — имеется ключевое слово __refvalue, которое позволяет не только получить значение, но и задать его. Но выглядит это довольно странно:
void Out(ref int someInt) { Input(__makeref(someInt)); } void Input(TypedReference @ref) { int val = __refvalue(@ref, int);//Получаем значение __refvalue(@ref, int) = 0;//Задаём значение в someInt }
При том, что тип задаётся ручками, скастить, например, int в string, не получится — проверка принадлежности типу все-таки проводится.
При этом всём, TypedReference тоже нельзя использовать в замыканиях — так что для того, чтобы создать с замыканием на TypedReference тоже не получится.
RuntimeArgumentHandle
Является ничем иным, как params, только в профиль. По сути, представляет из себя некий список TypedReference‘ов (доступ к которым производитс конструированием ArgIterator‘а), а создаётся тоже… даже и не знаю как это описывать:
void Out(int something) { Input(__arglist(something)); } void Input(__arglist) { new ArgIterator(__arglist); }
При этом, ключевое слово __arglist нельзя использовать в делегатах при их обьявлении. Но RuntimeArgumentHandle можно (но только как параметры, TypedReference и RuntimeArgumentHandle нельзя возвращать из методов). __arglist() также нельзя использовать как аргумент для вызова делегата, но зато __arglist можно. Смысл этой несколько расплывчатой формулировки лучше подкрепить примером:
delegate void ArgWarrior(RuntimeArgumentHandle argh); void Out(int something) { (new ArgWarrior(u => { } ))(__arglist(someting));//не скомпилируется Input(new ArgWarrior(u => { } ), __arglist(someting); } void Input(ArgWarrior argh, __arglist) { argh(__arglist);//а так можно }
И вот я подобрался к ключевому моменту этого марлезонского балета: делегатам.
Манипуляции над _methodPtrAux как способ изысканных издевательств над делегатами
_methodPtrAux — это четвёртое поле в любом делегируемом типе, которое сыграет тут ключевую роль. В чём суть? Суть в том, что _methodPtrAux хранит в себе указатель на уже jit’енный метод. Записав произвольный неуправляемый код по тому указателю, можно таким образом этот неуправляемый код выполнить. Но это тут не главное. Делегат остаётся пригодным к использованию даже после подмены значения _methodPtrAux, и при вызове его, управление перейдёт именно туда, куда указывает значение этого поля. Т.о., имея два делегата с разными входными параметрами, я могу заменить указатель из делегата a на указатель из делегата b. Даже если у них разный набор аргументов, всё сработает. Ключевым моментом будет так-же и то, что даже если различаются типы соответствующих аргументов, clr не забьёт тревогу — int будет скастен в string все желания будут исполнены, никто не уйдёт обиженным, или… RuntimeArgumentHandle будет преобразован в System.Object:
delegate void Encast(RuntimeArgumentHandle @ref); delegate void Uncast(object @object); static void UseWith(Encast en, __arglist) { en(__arglist); } static object m_storedRef; static void Engage(ref object @object) { Encast en = new Encast(@ref => { }); Uncast un = new Uncast(o => { m_storedRef = o;//сюда перейдёт управление после вызова <b>en</b>. }); typeof(Encast).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[3].SetValue(en, typeof(Uncast).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[3].GetValue(un));//меняем указатель у en UseWith(en, __arglist(__makeref(@object))); //вызываем }
Как видно по лямбде, я сразу уже сохраняю полученное значение в статическое поле. Да, тут есть одно непонятное ограничение — если сохранять o в не-статическое поле то можно уронить clr (чтение-запись защищённой памяти). Даже если целевым полем будет не поле, а поле обьекта, что хранится в статическом поле (например, Dictionary) всё должно пройти гладко. Несколько чудно при этом аргумент лямбды выглядит в отладчике: при просмотре можно увидеть только "{object}" (без кавычек) и ничего более. Попытка извлечь тип или привести к String при этом ничего хорошего не сулит (можно уронить clr)
Обратное преобразование производится аналогично. Сохранение же кадра стека производится с помощью мониторов:
static object m_locker = new object(); //... Monitor.Enter(m_locker); Monitor.Exit(m_locker);
m_locker уже заранее заблокирован из другого потока, так что выполнение приостанавливается, т.о. RuntimeArgumentHandle так и остаётся в стеке, не разрушаясь.
Полный код программы выглядит так:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ThreadJiggler { class Program { delegate void Encast(RuntimeArgumentHandle @ref); delegate void Uncast(object @object); static object m_storedRef; static object m_locker = new object(); static bool m_useFlag; static void Main(string[] args) { object @v = "means \"vendetta\""; Victim1(ref @v); Console.WriteLine(@v); } static void UseWith(Encast en, __arglist) { en(__arglist); } static Thread m_someThread; static void Victim1(ref object @object) { Thread t = new Thread(() => { Monitor.Enter(m_locker); { for (; !m_useFlag; ) { Thread.Sleep(10); } Encast en = new Encast(@ref => { TypedReference tr = new ArgIterator(@ref).GetNextArg(); __refvalue( tr, object) = 0; }); Uncast un = new Uncast(o => { m_storedRef = o; }); typeof(Uncast).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[3].SetValue(un, typeof(Encast).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[3].GetValue(en)); un(m_storedRef); } Monitor.Exit(m_locker); }); t.IsBackground = false; t.Start(); { Encast en = new Encast(@ref => { }); Uncast un = new Uncast(o => { m_storedRef = o; m_useFlag = true; Monitor.Enter(m_locker); Monitor.Exit(m_locker); }); typeof(Encast).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[3].SetValue(en, typeof(Uncast).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[3].GetValue(un)); UseWith(en, __arglist(__makeref(@object))); } } } }
В конце Main можно увидеть, что значение @v сменилось на 0.
ссылка на оригинал статьи http://habrahabr.ru/post/192140/
Добавить комментарий