28 июля 2014 г.

Виртуальная машина для BrainFuck на ASM.

Давно ничего не писал так как не было времени. Буду исправляться. Так как немного появилось свободного времени решил запилить чтонить демоническое. А именно виртуальную машину для BrainFuck на ASM. Что из себя она представляет: 8 команд которые что то делают с бесконечной лентой регистров. Можно двигать ленту в лево и в право, увеличивать и уменьшать значение в ячейке, записывать и вводить данные в ячейку. Ну и есть что то типа цикла. Подробней можно почитать на странице педевикии.


Итак, для начала необходимо создать подпрограммы инструкций, таких как "+"," >" и т.д.
Оператор ">" что означает сдвиг ленты в право:

I_Plus proc i:DWORD
 mov   eax, i ; копируем в eax значение i
 inc   eax ; увеличиваем его на 1
 ret
I_Plus endp
Оператор "<" что означает сдвиг ленты в лево:
I_Minus			proc i:DWORD
	mov			eax, i ; копируем в eax i
	dec			eax ; уменьшаем eax на 1
	ret
I_Minus endp
Оператор "+" что означает увеличение текущей ячейки на 1:
Arr_Plus		proc arr_i:DWORD
	mov		eax, arr_i ; скопировать ядрес ячейки в eax
	inc		BYTE ptr [eax] ; увеличить ячейку на 1
	ret
Arr_Plus endp
Оператор "-" что означает уменьшени текущий ячейки на 1:
Arr_Minus		proc arr_i:DWORD
	mov		eax, arr_i ; копируем в eax адрес текущей ячейки
	dec		BYTE ptr [eax] ; уменьшаем на 1
	ret
Arr_Minus 	endp
Оператор "." что означает запись значения ячейки в какоето место:
Putchar_Arr_i	proc uses eax ebx arr_i:DWORD, out_addr:DWORD
	mov		eax, DWORD PTR [arr_i] ; помещаем в eax адрес текущей ячейки
	mov		al, BYTE PTR [eax] ; помещаем в al значение ячейки
	mov		ebx, out_addr ; помещаем в ebx адрес места куда пишем
	mov		BYTE PTR [ebx], al ; записываем значение ячейки
	ret
Putchar_Arr_i endp
Оператор "," что означает запись значения откудато в текущую ячейку:
Getchar_Arr_i	proc uses eax ebx arr_i:DWORD, inn_addr:DWORD
	mov		eax, arr_i ; копируем в eax адрес текущей ячейки
	mov		bl, BYTE PTR [inn_addr] ; в bl копируем значение из источника
	mov		BYTE PTR [eax], bl ; в текущую ячейку копируем значение bl
	ret
Getchar_Arr_i endp
Оператор "[" что есть начало цикла:
While_Start	proc uses eax ebx ecx arr_i:DWORD, buf_arr:DWORD
	mov		eax, buf_arr ; копируем в eax адрес счетчика
	mov		ebx, arr_i ; в ebx копируем адрес текущей ячейки 
	mov		ecx, -1h ; сделаем почти бесконечный цикл
	.if	BYTE PTR [eax] !=	0h ; проверяем, в счетчике у на 0?
		jmp	while_sover ; если 0 то цикл завершен
	.endif
	
search_end:
	.if	BYTE PTR [ebx] == D_WSTOP ; если нашли закрытие цикла
		jmp	while_sover ; выходим
	.endif
	inc		ebx ; иначе двигаемся по ленте
	loop	search_end ; повторяем пока не найдем символа закрытия цикла
	
while_sover:
	ret
While_Start endp
Оператор "]" что есть конец цикла:
While_End proc uses eax ebx ecx arr_i:DWORD, buf_arr:DWORD
	mov		eax, buf_arr ; копируем в eax адрес счетчика
	mov		ebx, arr_i  ; в ebx копируем адрес текущей ячейки
	mov		ecx, -1h ; сделаем почти бесконечный цикл
	.if	BYTE PTR [eax] ==	0h ; проверяем, в счетчике у нас 0?
		jmp	while_eover ; если 0 то цикл завершен
	.endif

search_start:
	.if	BYTE PTR [ebx] ==	D_WSTART ; если нашли открытие цикла
		jmp	while_eover ; выходим
	.endif
	dec		ebx ; иначе двигаемся по ленте в лево
	loop	search_start ; повторяем пока не найдем символа открытия цикла

while_eover:
	mov		edi, ebx ; меняем указатель на текущую оперциюю
	ret
While_End endp
Основное тело программы:
VM_Body	proc	vm_code:DWORD, out_addr:DWORD
	pusha ; сохраним все регистры(пусть будут)
	mov			edi, vm_code ; в edi у нас будет указатель на текущую инструкцию
	mov			ecx, -1  ; делаем опять почти бесконнечный цикл
	mov			eax, offset buf ; в eax у нас буфер
	mov			ebx, out_addr ; в ebx место куда будет выводится результат
	add			edi, 4h ; сместим указатель на начала данных(сделано для получения размера)
start_parse:
	.if BYTE PTR [edi] == D_PLUS_I ; Если встретили ">"
		invoke	I_Plus, eax ; вызвали обработку ">"
		jmp		nextiter ; обработка следующего символа
	.endif
	
	.if BYTE PTR [edi] == D_MINUS_I ; Если встретили "<"
		invoke	I_Minus, eax ; вызвали обработку "<"
		jmp		nextiter ; обработка следующего символа
	.endif
	
	.if BYTE PTR [edi] == D_ARR_PLUS ; Если встретили "+"
		invoke	Arr_Plus, eax ; вызвали обработку "+"
		jmp		nextiter ; обработка следующего символа
	.endif
	
	.if BYTE PTR [edi] == D_ARR_MINUS ; Если встретили "-"
		invoke	Arr_Minus, eax ; вызвали обработку "-"
		jmp		nextiter ; обработка следующего символа
	.endif
	
	.if BYTE PTR [edi] == D_PUT ; Если встретили "."
		invoke	Putchar_Arr_i, eax, ebx ; вызвали обработку "."
		inc		ebx ; переключились на следующий символ
		jmp		nextiter ; обработка следующего символа
	.endif
	
	.if BYTE PTR [edi] == D_GET ; Если встретили ","
		invoke	Getchar_Arr_i, eax, ebx ; вызвали обработку ","
		jmp		nextiter ; обработка следующего символа
	.endif
	
	.if BYTE PTR [edi] == D_WSTART ; Если встретили "["
		invoke	While_Start, edi, eax ; вызвали обработку "["
		jmp		nextiter ; обработка следующего символа
	.endif
	
	.if BYTE PTR [edi] == D_WSTOP ; Если встретили "]"
		invoke	While_End, edi, eax ; вызвали обработку "]"
		jmp		nextiter ; обработка следующего символа
	.endif
	
nextiter:
	inc		edi ; переходим на следующую команду
	.if	edi >= offset output ; обнаружили конец P кода
		jmp	goout ; завершаем программу
	.endif
	loop	start_parse ; обрабатываем след символ
goout:
	popa ; востанавливаем регистры
	ret
VM_Body endp
В принципе вот и все, достаточно не сложно. Такую простоенькую машину можно использовать для скрытия например другова алгоритма, например расчета серийника или некого алгоритма.

Комментариев нет:

Отправить комментарий