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)
Зато если такой фокус провернуть с TypedReference, можно увидеть ещё кое-что интересное (это я оставляю на самостоятельное изучение читателям. Можно попробовать скастить в int, тоже любопытно).
Обратное преобразование производится аналогично. Сохранение же кадра стека производится с помощью мониторов:
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.
This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:
- Massacres That Matter - Part 1 - 'Responsibility To Protect' In Egypt, Libya And Syria
- Massacres That Matter - Part 2 - The Media Response On Egypt, Libya And Syria
- National demonstration: No attack on Syria - Saturday 31 August, 12 noon, Temple Place, London, UK
Комментариев нет:
Отправить комментарий