Thread overview
Thunking problems with arch issues
Jul 02, 2016
rikki cattermole
July 02, 2016
The following code works on dmd x64. Fails on dmd x32 and ldc x64. The problem is the passed variable.




import std.stdio;


version (Windows)
{
    import core.sys.windows.windows;

    void makeExecutable(ubyte[] code)
    {
        DWORD old;
        VirtualProtect(code.ptr, code.length, PAGE_EXECUTE_READWRITE, &old);
    }
}
else
version (linux)
{
    import core.sys.posix.sys.mman;
    import core.sys.posix.unistd;

    static if (!is(typeof(&mprotect)))
        extern(C) int mprotect(void*, size_t, int);

    void makeExecutable(ubyte[] code)
    {
        auto pageSize = sysconf(_SC_PAGE_SIZE);
        auto address = ((cast(size_t)code.ptr) & ~(pageSize-1));
        int pageCount =
            (address/pageSize == (address+code.length)/pageSize) ? 1 : 2;
        mprotect(cast(void*)address, pageSize * pageCount,
            PROT_READ | PROT_WRITE | PROT_EXEC);
    }
}
else
    static assert(0, "TODO");

R function(A) delegate2function(R, A...)(R delegate(A) d)
{
    enum size_t TEMPLATE1 = cast(size_t)0x01234567_01234567;
    enum size_t TEMPLATE2 = cast(size_t)0x89ABCDEF_89ABCDEF;

    static R functionTemplate(A args)
    {
        R delegate(A) d;
        d.ptr     = cast(typeof(d.ptr    ))TEMPLATE1;
        d.funcptr = cast(typeof(d.funcptr))TEMPLATE2;
        return d(args);
    }

    static void functionTemplateEnd() {}

    static void replaceWord(ubyte[] a, size_t from, size_t to)
    {
        foreach (i; 0..a.length - size_t.sizeof + 1)
        {
            auto p = cast(size_t*)(a.ptr + i);
            if (*p == from)
            {
                *p = to;
                return;
            }
        }
        assert(0);
    }

    auto templateStart = cast(ubyte*)&functionTemplate;
    auto templateEnd   = cast(ubyte*)&functionTemplateEnd;
    auto templateBytes = templateStart[0 .. templateEnd - templateStart];

    // must allocate type with pointers, otherwise GC won't scan it
    auto functionWords = new void*[(templateBytes.length / (void*).sizeof) + 3];

    // store context in word-aligned boundary, so the GC can find it
    functionWords[0] = d.ptr;
    functionWords[1] = d.funcptr;
    functionWords = functionWords[2..$];

    auto functionBytes = (cast(ubyte[])functionWords)[0..templateBytes.length];
    functionBytes[] = templateBytes[];

    replaceWord(functionBytes, TEMPLATE1, cast(size_t)d.ptr    );
    replaceWord(functionBytes, TEMPLATE2, cast(size_t)d.funcptr);
    makeExecutable(functionBytes);

    return cast(typeof(return)) functionBytes.ptr;
}




public struct sExtThread(T)
{
	uint Id = 0;
	HANDLE Handle;
	alias callbackType = extern (Windows) uint function(void*);	
	

	public void Create(uint delegate(T) c, T param, bool suspended = false, uint stackSize = 16000)
	{	
		Handle = CreateThread(cast(SECURITY_ATTRIBUTES*)null, stackSize, cast(callbackType)delegate2function(c), cast(void*)&param, (suspended) ? CREATE_SUSPENDED : 0, &Id);
	}

	
}



void main()
{
    import std.stdio;

	string q = "WTFSDFA";
	sExtThread!string t;
	t.Create((p)
	{
		writeln(q);
		writeln(p);
		return 1;
	}, "ASDFASDF");
	

	getchar();
}


p is invalid.

Obviously isn't not necessary to pass p but still, somethings wrong. I stole the delegate2function from Vladimir "The Propeller" Panteleev! Thanks Vlad!

It doesn't seem to be a problem with x86 since LDC x64 has the same problem(or writes junk I guess). Could be an issue with delegate2function but it's a bit over my head.

July 02, 2016
Couple of things could be happening.

1) Alignments are off, aligning of data really really matters when dealing with executable code.
2) For Windows only. Don't forget to call FlushInstructionCache. Before executing. https://msdn.microsoft.com/en-us/library/windows/desktop/ms679350(v=vs.85).aspx
3) Don't use new to get your executable memory, nope nope nope. Use things like VirtualAlloc as malloc doesn't work right see the first point.
July 02, 2016
On Saturday, 2 July 2016 at 01:51:03 UTC, rikki cattermole wrote:
> Couple of things could be happening.
>
> 1) Alignments are off, aligning of data really really matters
Where? I rewrote the code to use size_t and same problem. If alignments were off chances are it wouldn't exhibit the issues in the way it does(work fine for 64 and not 86, not for ldc).
> when dealing with executable code.
> 2) For Windows only. Don't forget to call FlushInstructionCache. Before executing. https://msdn.microsoft.com/en-us/library/windows/desktop/ms679350(v=vs.85).aspx
Tried, didn't work.

> 3) Don't use new to get your executable memory, nope nope nope. Use things like VirtualAlloc as malloc doesn't work right see the first point.

Well..

I see these as general statements that don't actually address the real problem in the code. I rewrote the code and it has the same problem. I see no reason why it shouldn't work.

It does not manipulate the code, and new seems to allocate aligned(if not it would work some of the time and fail others).

It specifically has the problem when used as a windows callback. I guess it has to do with the calling convention.



Here is the complete test code:


https://dpaste.dzfl.pl/d8cea3b39d0d