65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 mov 指令实现通用图灵机

starIconstarIconstarIconstarIconstarIcon

5.00/5 (14投票s)

2017 年 1 月 24 日

CPOL

5分钟阅读

viewsIcon

26572

downloadIcon

168

基于 mov 的 UTM 具有图灵完备性论文 x86 和 x86-64

引言

Stephen Dolan 于 2013 年发表了一篇证明 x86 `mov` 指令是图灵完备的论文,随后又有一个 movfuscator 项目,它基于该项目创建了一个完整的编译器。请参阅论文:https://www.cl.cam.ac.uk/~sd601/papers/mov.pdf。论文中提出了一种通用图灵机(UTM),作为仅使用单个跳转指令即可证明图灵完备性的依据。

这些代码还可以作为一些关于如何实现 x86 和 x86-64 条件编译的参考,包括内联汇编和外部汇编,以及一些可能仅限于 Windows 平台但可应用于任何平台的语义。

背景

最好了解计算模型、图灵完备性、通用图灵机、图灵机以及 C 和 x86 汇编语言,同时了解内联汇编器和 Windows 技术,或了解如何将它们移植到其他平台(如 Linux 和 Mac)(这应该不难)。

Using the Code

下载包中包含另外两种图灵机模型:一种是非通用的、状态有限的,另一种是非通用的但输入磁带大小有限。这些模型提供了 C 和汇编格式的额外示例。

代码遵循的定义被呈现出来,它允许对研究论文进行完整的 C 语言模拟。为了在 C 语言中优雅地中止代码而不崩溃,使用了一些“作弊”方法,例如 `cmp` 和 `je` 指令,因为原始研究论文允许通过对内存地址 0 的错误访问触发段错误来终止。

必须根据代码进行推断,以弄清楚图灵机的数据结构和格式,以及输入磁带和如何正确初始化它们。通过查看互斥性、使用频率来定义寄存器,以便以最低影响的方式完成。汇编之前的 C 序言通过简单的图灵机编码进行适应,该编码假定按当前状态的排序顺序排列到内存寻址编码版本中。

代码可以在 Microsoft Visual Studio 2015 上成功编译和运行。

首先,必须根据以下格式对执行某些计算的图灵机进行编码

    //{old state, old symbol, new symbol, direction, new state}
	char turingMachine[][5] = {
		{ '1', '0', '0', 'R', '1' },
		{ '1', '1', '1', 'R', '1' },
		{ '1', '_', '_', 'R', '3' },
		{ '3', '0', '0', 'R', '3' },
		{ '3', '1', '1', 'R', '4' },
		{ '3', '_', '_', 'R', '2' },
		{ '4', '0', '0', 'R', '4' },
		{ '4', '1', '1', 'R', '4' },
		{ '4', '_', '_', 'L', '5' },
		{ '5', '0', '1', 'L', '5' },
		{ '5', '1', '0', 'L', '6' },
		{ '6', '0', '0', 'L', '6' },
		{ '6', '1', '1', 'L', '6' },
		{ '6', '_', '_', 'L', '7' },
		{ '7', '1', '0', 'L', '7' },
		{ '7', '0', '1', 'R', '1' },
		{ '7', '_', '1', 'R', '1' }
	}; //must be sorted
	char Input[] = "0100_1001_";

这是一个执行二进制加法的图灵机,它使用递减然后递增直到 0 的策略。初始输入编码为 4 和 5,因此结果应该是 9 和 0。它必须按当前状态值排序,以便可以处理,因此如果未预排序,则可以使用快速排序或其他选项。以下函数用于对新状态执行二分查找。

int TMCmp(const void* l, const void* r)
{
	if (*(unsigned char*)l == *(unsigned char*)r) return 0;
	return *(unsigned char*)l > *(unsigned char*)r ? 1 : -1;
}

用于外部汇编器支持,这对 x64 是必需的

#include <basetsd.h>
extern "C" void __cdecl MovTM
(ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR);

 其余代码如下

	//<pointer to trigger symbol> <pointer to next state> 
	//<trigger symbol> <pointer to new symbol> 
    //<new symbol> <pointer to direction> 
    //<direction> <pointer to pointer to new state> 
    //<pointer to new state>
	ULONG_PTR* TransitionTape = (ULONG_PTR*)malloc((sizeof(turingMachine) / 5) 
       * 9 * sizeof(ULONG_PTR)); //{old state, old symbol, new symbol, direction, new state}
	ULONG_PTR* InputTape = (ULONG_PTR*)malloc(sizeof(Input) * sizeof(ULONG_PTR) * 2);
	ULONG_PTR* InputTapeLeft = (ULONG_PTR*)malloc(sizeof(Input) * sizeof(ULONG_PTR) * 2);
	for (int i = 0; i < sizeof(turingMachine) / 5; i++) {
		TransitionTape[i * 9] = (ULONG_PTR)(TransitionTape + i * 9) + sizeof(ULONG_PTR) * 2;
		TransitionTape[i * 9 + 3] = (ULONG_PTR)(TransitionTape + i * 9) + sizeof(ULONG_PTR) * 4;
		TransitionTape[i * 9 + 5] = (ULONG_PTR)(TransitionTape + i * 9) + sizeof(ULONG_PTR) * 6;
		TransitionTape[i * 9 + 7] = (ULONG_PTR)(TransitionTape + i * 9) + sizeof(ULONG_PTR) * 8;
		char* ptr = (char*)bsearch(&turingMachine[i][4], turingMachine, 
                     sizeof(turingMachine) / 5, 5, TMCmp);
		int idx;
		if (ptr != NULL) {
			idx = (ptr - (char*)turingMachine) / 5;
			while (idx != 0 && turingMachine[idx - 1][0] == turingMachine[i][4]) idx--;
		}
		TransitionTape[i * 9 + 8] = ptr == NULL ? (turingMachine[i][4] == '2' ? 
        (ULONG_PTR)InputTapeLeft : (ULONG_PTR)InputTapeLeft) : 
        (ULONG_PTR)(TransitionTape + (idx * 9));
		TransitionTape[i * 9 + 2] = turingMachine[i][1];
		TransitionTape[i * 9 + 4] = turingMachine[i][2];
		TransitionTape[i * 9 + 6] = turingMachine[i][3] == 'R' ? sizeof(ULONG_PTR) : 0;
		TransitionTape[i * 9 + 1] = ((i == sizeof(turingMachine) / 5 - 1) || 
           turingMachine[i + 1][0] != turingMachine[i][0]) ? (ULONG_PTR)InputTapeLeft : 
           (ULONG_PTR)(TransitionTape + (i + 1) * 9);
	}
	for (int i = 0; i < sizeof(Input); i++) {
		InputTape[i * 2] = Input[i];
		InputTapeLeft[i * 2] = '_';
		InputTape[i * 2 + 1] = (ULONG_PTR)(InputTape + (i + 1) * 2);
		InputTapeLeft[i * 2 + 1] = (ULONG_PTR)(InputTapeLeft + (i + 1) * 2);
	}
	ULONG_PTR scratch[256]; // must be the size of the alphabet range
	ULONG_PTR TransitionTapePtr = (ULONG_PTR)TransitionTape;
	ULONG_PTR InputTapePtr = 
	(ULONG_PTR)InputTape + sizeof(ULONG_PTR) * 2; //part of tape right of current position,
                                // initially at beginning of input tape after current symbol
	ULONG_PTR InputTapeLeftPtr = 
	(ULONG_PTR)InputTapeLeft; //part of tape left of 
                                  //current position, initially empty
	                              //temporary registers, 
	                              //4 of which are mutually exclusive in usage
MovTM((ULONG_PTR)TransitionTapePtr, (ULONG_PTR)InputTape, 
(ULONG_PTR)InputTapeLeft, (ULONG_PTR)scratch, (ULONG_PTR)InputTapePtr, (ULONG_PTR)InputTapeLeftPtr);
#if !defined(_WIN64)	
	for (int i = 0; i < sizeof(Input); i++) {
		InputTape[i * 2] = Input[i];
		InputTapeLeft[i * 2] = '_';
		InputTape[i * 2 + 1] = (ULONG_PTR)(InputTape + (i + 1) * 2);
		InputTapeLeft[i * 2 + 1] = (ULONG_PTR)(InputTapeLeft + (i + 1) * 2);
	}
#define ADDRPLIER 4
#define SIZEWORD dword
#define X eax
#define Y ebx
#define M ecx
#define Z Y
#define D Z
#define H M
//5 globally used registers
#define L edi
#define R L
#define T R
#define S edx
#define N esi
	__asm {
		mov T, TransitionTapePtr;; current state, initially Q0
		mov S, InputTape;; current symbol, initially Q0
		mov N, InputTapeLeft;; scratch part of tape left of current position, initially empty

	start:
		mov X, [T];; get transition
		mov X, [X];; get trigger symbol
		mov Y, [S];; get current symbol
		mov SIZEWORD ptr [scratch + Y * ADDRPLIER], 0;; compare symbols
		mov SIZEWORD ptr [scratch + X * ADDRPLIER], 1 * ADDRPLIER
		mov M, SIZEWORD ptr [scratch + Y * ADDRPLIER]

		mov X, [T];; get transition
		mov X, [X + 1 * ADDRPLIER];; skip trigger symbol
		mov X, [X];; load new symbol
		mov Y, [S];; load old symbol
		mov [N], Y;; select between X and Y
		mov [N + 1 * ADDRPLIER], X
		mov Z, [N + M]
		mov [S], Z;; write the new symbol

		mov D, [T];; get transition
		mov D, [D + 1 * ADDRPLIER];; skip trigger symbol
		mov D, [D + 1 * ADDRPLIER];; skip new symbol
		mov D, [D];; load direction

			mov R, InputTapePtr
		mov [N], R;; select new value for[S + 1]
			mov L, InputTapeLeftPtr
		mov [N + 1 * ADDRPLIER], L
		mov X, [N + D]
		mov [S + 1 * ADDRPLIER], X
		mov [N], L;; select new value for L
		mov [N + 1 * ADDRPLIER], S
		mov L, [N + D]
			mov InputTapeLeftPtr, L
		mov [N], S;; select new value for R
			mov R, InputTapePtr
		mov [N + 1 * ADDRPLIER], R
		mov R, [N + D]
			mov InputTapePtr, R

		mov SIZEWORD ptr [N], 1 * ADDRPLIER;; set X = not D
		mov SIZEWORD ptr [N + 1 * ADDRPLIER], 0
		mov X, [N + D]
		mov [N], X;; select between D and X
		mov [N + 1 * ADDRPLIER], D
		mov D, [N + M]

			mov L, InputTapeLeftPtr
		mov [N], L;; select new value of S
			mov R, InputTapePtr
		mov [N + 1 * ADDRPLIER], R
		mov S, [N + D]
		mov X, [S + 1 * ADDRPLIER];; get new start of L or R
		mov [N], X;; select new value for L
			mov L, InputTapeLeftPtr
		mov [N + 1 * ADDRPLIER], L
		mov L, [N + D]
			mov InputTapeLeftPtr, L
			mov R, InputTapePtr
		mov [N], R;; select new value for R
		mov [N + 1 * ADDRPLIER], X
		mov R, [N + D]
			mov InputTapePtr, R

			mov T, TransitionTapePtr
		mov X, [T + 1 * ADDRPLIER];; get next transition of this state
		mov Y, [T];; get current transition
		mov Y, [Y + 1 * ADDRPLIER];; skip trigger symbol
		mov Y, [Y + 1 * ADDRPLIER];; skip new symbol
		mov Y, [Y + 1 * ADDRPLIER];; skip direction
		mov Y, [Y];; load transition list of next state
		mov [N], X;; select next transition
		mov [N + 1 * ADDRPLIER], Y
		mov T, [N + M]
			mov TransitionTapePtr, T

		mov X, [T]
		mov SIZEWORD ptr [N], 1 * ADDRPLIER
		mov SIZEWORD ptr [T], 0
		mov H, [N]
		mov [T], X

		mov SIZEWORD ptr [N], 0;; select between 0 and N
		mov [N + 1 * ADDRPLIER], N
		mov X, [N + H]
			cmp X, 0
			je finish
		mov X, [X];; load from 0 or N
		jmp start
	finish:
	}
#endif
	free(TransitionTape);
	free(InputTape);
	free(InputTapeLeft);

x86 预链接事件命令(safeseh 可能仅在发布版本中需要):ml.exe /safeseh /Fo "$(OutDir)tm.obj" /c tm.asm

x64 预链接事件命令:ml64.exe /DX64 /Fo "$(OutDir)tm.obj" /c tm.asm

x86/64 链接器输入附加依赖项:$(OutDir)\tm.obj

x64 构建后事件(仅用于调试版本,因为 C 运行时程序集链接存在问题):copy "$(MSBuildProgramFiles32)\Microsoft SDKs\Windows Kits\10\ExtensionSDKs\Microsoft.UniversalCRT.Debug\10.0.14393.0\Redist\Debug\x64\ucrtbased.dll" "$(OutDir)" copy "$(MSBuildProgramFiles32)\Microsoft Visual Studio 14.0\VC\redist\debug_nonredist\x64\Microsoft.VC140.DebugCRT\vcruntime140d.dll" "$(OutDir)"

汇编 x64 序言

ifndef X64
.model flat, C
endif
option casemap :none

.code

ifndef X64
ADDRPLIER EQU 4
SIZEWORD TEXTEQU <dword>

X TEXTEQU <eax>
Y TEXTEQU <ebx>
M TEXTEQU <ecx>
Z EQU Y
D EQU Z
H EQU M
L TEXTEQU <edi>
R EQU L
T EQU R
S TEXTEQU <edx>
N TEXTEQU <esi>

;eax, ecx, edx are considered volatile
MovTM PROC USES ebx edi esi TransitionTapePtr:DWORD, InputTape:DWORD, InputTapeLeft: DWORD, 
scratch: DWORD, InputTapePtr: DWORD, InputTapeLeftPtr: DWORD
else
ADDRPLIER EQU 8
SIZEWORD TEXTEQU <qword>

X TEXTEQU <rax>
Y TEXTEQU <rbx>
M TEXTEQU <rdi>
Z EQU Y
D EQU Z
H EQU M
L TEXTEQU <r10>
R EQU L
T TEXTEQU <rcx>
S TEXTEQU <rdx>
N TEXTEQU <r8>
scratch TEXTEQU <r9>
TransitionTapePtr TextEQU <T>
InputTape TextEQU <S>
InputTapeLeft TextEQU <N>

;rax, rcx, rdx, r8, r9, r10, r11 are considered volatile
;x64 calling convention passes arguments fastcall: RCX, RDX, R8, R9, on stack 
;but using default argument scheme uses stack and without first 4 indexes them incorrectly
;PROC -> DWORD PTR[rsp + 40] also could work here to use a more appropriate esp based indexing 
;instead of ebp

MovTM PROC USES rbx rdi Dummy1: QWORD, Dummy2: QWORD, Dummy3: QWORD, 
Dummy4: QWORD, InputTapePtr: QWORD, InputTapeLeftPtr: QWORD
endif

上面 `__asm {}` 块内的代码应放在此处,后面跟着这个汇编 x64 尾声

    		ret
MovTM ENDP

END

为了简化对双向链表堆栈和内存指针转换表构造的理解,以下 C 伪代码引入了控制流简化,代表了通用功能

    ULONG_PTR CurInputTapePtr = (ULONG_PTR)InputTape;
	TransitionTapePtr = (ULONG_PTR)TransitionTape;
	InputTapePtr = (ULONG_PTR)InputTape + sizeof(ULONG_PTR) * 2;
	InputTapeLeftPtr = (ULONG_PTR)InputTapeLeft;
	do {
		if (*(ULONG_PTR*)(*(ULONG_PTR*)TransitionTapePtr) == *(ULONG_PTR*)CurInputTapePtr) {
			*(ULONG_PTR*)CurInputTapePtr = *(ULONG_PTR*)(*(ULONG_PTR*)
            (*(ULONG_PTR*)TransitionTapePtr + sizeof(ULONG_PTR)));
			if (*(ULONG_PTR*)(*(ULONG_PTR*)(*(ULONG_PTR*)(*(ULONG_PTR*)TransitionTapePtr + 
            sizeof(ULONG_PTR)) + sizeof(ULONG_PTR))) == 0) {
				*(ULONG_PTR*)(CurInputTapePtr + sizeof(ULONG_PTR)) = InputTapePtr;
				InputTapePtr = CurInputTapePtr;
				CurInputTapePtr = InputTapeLeftPtr;
				InputTapeLeftPtr = *(ULONG_PTR*)(CurInputTapePtr + sizeof(ULONG_PTR));
			} else {
				*(ULONG_PTR*)(CurInputTapePtr + sizeof(ULONG_PTR)) = InputTapeLeftPtr;
				InputTapeLeftPtr = CurInputTapePtr;
				CurInputTapePtr = InputTapePtr;
				InputTapePtr = *(ULONG_PTR*)(CurInputTapePtr + sizeof(ULONG_PTR));
			}
			TransitionTapePtr = *(ULONG_PTR*)(*(ULONG_PTR*)(*(ULONG_PTR*)(*(ULONG_PTR*)
                                (*(ULONG_PTR*)TransitionTapePtr + sizeof(ULONG_PTR)) + 
                                sizeof(ULONG_PTR)) + sizeof(ULONG_PTR)));
		} else {
			TransitionTapePtr = *(ULONG_PTR*)(TransitionTapePtr + sizeof(ULONG_PTR));
		}
	} while (TransitionTapePtr != (ULONG_PTR)InputTapeLeft);

关注点

图灵机编码格式的数据结构,该结构使用内存指针和给定状态内的状态转换列表,是通过一些简单的 C 代码推导出来的,并从典型编码中得出。输入磁带及其左侧部分的链表格式也通过推导和模拟得到。

研究论文中发现了 2 个错误,即在这里,我们看到 R 被错误地放置而不是 L:mov [N], X ;; select new value for L \\ mov [N+1], R \\ mov L, [N + D],我们看到“H 为 1,我们必须停止机器”,但实际上代码选择在 H 为 0 时停止而不是 1。为了在前面的代码中纠正这一点,我们交换 0 和 1,mov [N], 0 \\ mov [T], 1 现在如果 H 为 0,我们必须停止机器,这通过 2 条“作弊”指令完成,一条比较和一条条件跳转,仅用于在 C 中实现优雅终止的成功模拟。

研究论文可能存在一些小错误,需要仔细审阅并花时间修复。我们尽量忠实于论文中的伪代码,仅在寄存器添加、从 C 转换以及优雅终止方面进行调整,并用制表符进行了注释。

x86 寄存器数量有限,需要寻找互斥性并以谨慎的方式保存或重新加载它们。`esp` 和 `ebp` 不应使用,尤其是在 C 语言中,恢复它们或访问数据会变得更加困难,尽管付出巨大的努力和谨慎,这是可能的。64 位代码可以访问 r8-r15 寄存器,这使得每个所需变量都可以拥有一个寄存器。调用约定很特殊,必须特别处理。

示例性的 C 语言通用 UTM,以及一个 64 位条件编译版本,使得实现更加通用且易于理解。

结论是,研究论文中的 UTM 虽然更具理论性,并且不像论文的结果那样被广泛实现,但其使用 `mov` 指令进行混淆的能力是正确的。

历史

  • 初始版本
  • x64 和外部汇编文件支持
© . All rights reserved.